Example Python Application
Provides and explains how to build your own chat application utilizing our (your own deployment of our) API within a flask application.
Understanding the project structure
Poetry used for package management
Non-src-based layout
I.e., the main package is adjacent to the
pyproject.toml
file (since the source code is important, when usually one would use poetry to build the package and send a wheel of it)
Why Poetry?
Automatically and cleanly handles cross-package dependencies, installing valid versions for cross-compatibility if possible
Virtual Environment Management
Resources
Swagger Page:
https://demo.aicrisk.com/Swagger/index.html
NOTE: Outside of the demo server, replace
https://demo.aicrisk.com
with your deployed endpoint
NOTES:
Setup: Rename
example.env
to.env
(Or if there is already a.env file
, edit that) and fill inCLIENT_ID
andCLIENT_SECRET
and any other configurationFor Example, set
USER_ID
if wanting the app to automatically select you (and even sign you in automatically ifAUTO_LOG_ME_IN
is True) as the user to authenticateEnsure proper url to deployment in
API_BASE_URL
Installation
cd into directory containing
pyproject.toml
poetry install
Commands & Usage
CD into airisk_python_demo/airisk_python_demo
Run Dev:
poetry run flask run --port 7221 --cert=adhoc
Run with debugger:
poetry run flask run --port 7221 --cert=adhoc --debugger --debug
The UI
When Signed in as User,
Impersonate User
button allows impersonation of another user without losing initial user tokenExit Impersonation
button when impersonating someone will remove active impersonation and will sign in as initial user.Data Endpoints
SectionView the literal JSON response data from corresponding remote endpoints
UI Endpoints
Examples of using remote data in ones own UI
Select Navigator
sectionView remote data on things relative to other things
Ex. Given current authenticated users available agents, go to conversation data for one agent (the one selected in the dropdown)
Endpoint with Form Link
Example of integrating ones own form and flask view to handle the data input, and to communicate with the remote API with some sore of creation action. Ex. Creating a conversation.
Forms
(Deprecated, replaced with "Option Name" section)Ask Form PageRough example of chat-less interface for using the remote/api/Chat/Askendpoint
Single Object Navigator Util
List Conversations JSON
ButtonJSON data view for all authenticated conversations
Option Name ...
sectionNavigate to a custom chat page with which communicates with the remote server, providing a ChatGPT-like interface for an entire conversation.
Note, since this example app isn't utilizing streaming, one could either adapt this app to do so, or could create an illusion of streaming.
NOTES:
Since in
demo.aicrisk.com
, the callback URLs allowed are HTTPS and not HTTP (e.g.https://localhost:7221/callback
), therefore it is necessary to make flask run with httpsneeded
pyopenssl
and to append--cert=adhoc
to flask cli (or equiv toapp.run
)
Like #1, the callback URLs specify a port
7221
.Need to run flask with
--port 7221
or change the setting
Using Jinja2 as a template language for rendering the front-end HTML based UI using data coming from python
Components
.env
Contains config variables for a more dynamic approach
const.py
loads from
.env
into env variables also defining defaults.Also defines
TokenInput
class for persistence of tokens with support for auto-saving, auto-loading, expiration-aware auth, etc.
decorators.py
@login_required
Custom decorator to redirect client to
/
url (where auth is invoked) for any views accessing the remote api and therefore needing authentication.If running server in debug mode, making a change & saving and then refreshing the page on a non-root URL, the page will fail since it would be obnoxious to implement authentication logic in every view and since these views require a token.
authentication/
Module containing the flask Blueprint encapsulating the app logic (to show how to separate concerns.)
Could have set custom url prefix for this as well but didn't since didn't want to change aicrisk callback urls
contains view functions and data retrieval utilities along with some state management therein
templates/
Directory containing the jinja2 templates used across the project
base.html
is the root template (all others 'extend' it)i.e. when extending a template, you inherit everything by default, including blocks. If defining an extisting block in the child, the whole block from the parent is overridden (unless doing super call therein)
Added CDN for tailwind css for easier style prototyping
Before Running
Make sure to set variables in .env properly. Noteably the
CLIENT_ID
andCLIENT_SECRET
. Also the REDIRECT_URI if on a non-demo instance of AICRiskEnsure callback urls are set in the remote server admin
If running locally,
https://localhost:{PORT}
(+/callback
, ...)Otherwise, set the exact IP or domain in which the local server will be run on
If wanting to automatically sign in as oneself (assuming
USER_ID
is set), also setAUTO_LOG_ME_IN=True
How it Works
On start, the variables in
.env
are loaded into environment variables with fallbacks inconst.py
flask runs app.py (using configured variables) which in turn uses the app factory pattern to register the auth_bp blueprint and its routes to the app
User opens browser to page
If the page is the auth_index page
If
AUTO_RESTORE_SESSION
is True and if the file at the path ofTOKEN_INFO_FILE
exists and contains a valid, not-expired token, then that token is read, validated, and set within the session as "access_token"Otherwise
If
AUTO_RESTORE_SESSION
is False but the token file exists and contains a valid, non-expired tokenThe page is rendered with links to
Go through the auth process from scratch, invoked by redirecting to
auth
viewRead & load token from the file (see
TokenInfo.load
)
Otherwise (token non-existent or expired)
The page is rendered with a link to go through the authentication process
Otherwise (any other page that also isn't the authentication view (
auth
))The client is redirected to
auth_index
page and is provided same means of authenticating as above accordingly
Once
auth
is invokedStarts by passing
client_id
(from env) and redirect_uri (https://{HOST|localhost}:{PORT|7221}/callback
) to the remote authorize endpoint via a GET request (/api/oauth2
) with encoded params placed in the url directlyThe remote server then does its thing internally and redirects the client browser to the passed
redirect_url
(url foroauth_callback
view) with acode
parameter passed along therefromThe redirect urls corresponding view function (
oauth_callback
) then issues a POST request to the remote token endpoint (/api/oauth2/Token
) with parameters for theclient_id
,client_secret
,grant_type="authorization_code"
,redirect_uri
, andcode
with the value of code being that value received from the remote authorize endpointIf successful and yielding a valid response from that POST request, a variable is then pulled from the returned json for
access_token
and is set in the state and saved. This is the JWT/Bearer Token we needed for the rest of the apps views to workCustom UI components are rendered to simplify navigation and to illustrate how to somewhat dynamically set up linking to the other pages
For the list views, a given remote endpoint (ex user list) has
a corresponding local url for retrieving the data.
(Optionally) Some also have a corresponding "*_ui" view which renders the data in a nice user-centric fashion
Also illustrate how one may set up form field choices based on the existing remote data fetched. Ex. selecting conversations only for a particular agent
possible states when in app (non-linear)
Not Authenticated - [client_id, client_secret]
Authenticated as Generic
has token
Authenticated as Generic then User
User Authenticated directly without generic
User Authenticated somehow, and impersonating another user
Last updated