Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

In this article, we will describe how to build your first module for FinanceGPT Unique AI so you can run your own code against the chat.

As an example, we are going to deploy the most simple app from this repository the demo app: https://github.com/Unique-AG/sdk-deploy-template

...

To do that we need to create a connection between Unique FinanceGPT AI and your development environment. For this we use ngrok but you can also use any other way to get a webhook redirected to your development machine. E.g. Azure functions or other mechanisms. It is important that the data can connect from your FinanceGPT Unique AI environment to your local machine somehow.

...

  1. no trailing / be careful

  2. public/chat (not the other way around)

  3. make sure you use the correct host! you can look it up in any graphql request on the front end like in the picture below (this is the one from the next multi-tenant):

    Screenshot 2024-05-15 at 22.54.09.png

Defining the module

Create a Module Template

...

Run the GraphQL via Postman

Now you will need to execute a graphql request like this one for example in Postman:

  1. Make sure you have set the correct URL:

    • Local dev setup, use http://localhost:8092/graphql

    • For remote setup, see the graphql request from the browser like above

  2. The name here is very important. Your app will receive a payload that contains this name should it be called upon.

  3. the descriptions are also very important. Here you see that this function will only be called if asked for a demo. This is up to you to provide and also test how well it reacts to its intent.

...

And make sure you have set the correct Bearer token like in the example below, which is the token copied from above.

Note

There is no Bearer in front and no carriage return at the end!

...

Here is the mutation as text:

...

in the UI

Use the “AI Module Templates” section in the Unique solution’s frontend to create a custom module template for your SDK module. Documentation on this can be found here: https://unique-ch.atlassian.net/wiki/spaces/PUB/pages/586186776/AI+Module+Templates#Creating-an-AI-Module-Template.

Use the Module in a space

Now if you go into the spaces you can choose this as a module so:

Select the Email Writer module first:

...

Go ahead and publish it. Dont worry, only you can see this space for now.

...

Select the created Module Template

See the last entry and select this (Demo App Template Name) and also for example the email writer module.

...

Publish again.

...

Running your Python app

Now you are all set to run your app locally so it can react to chat messages:

In the location where you cloned the template repo https://github.com/Unique-AG/sdk-deploy-template

The .env file

Note

Go into the directory of the assistant_demo app and add the .env file with your settings:

  • All of these are different for you! Make sure they are correct biggest source of mistakes usually!

  • Look at the prefixes of the key app using

  • Remember how a correct API_BASE is defined

Note

90% of the errors happen here 😃

Code Block
API_KEY=ukey_qXqoh5lGhTa399CPNHHLUqnkIaWXlwRwAWtPJSrubyM
APP_ID=app_ueltalg142m341pskcankxk0
API_BASE=https://gateway.oleole.unique.app/public/chat
ENDPOINT_SECRET=usig_ad4wJ17fETob6uh0CYasNVn_aYtU1MRH4YnMrMiXjNE

Now go to the terminal and execute the following in that folder:

Code Block
➜  assistant_demo git:(main) ✗ poetry run flask run --port 5001 --debug
Creating virtualenv assistant-demo-C-sDUm3y-py3.12 in /Users/andreashauri/Library/Caches/pypoetry/virtualenvs
Command not found: flask
➜  assistant_demo git:(main) ✗ poetry install
Installing dependencies from lock file

Package operations: 17 installs, 0 updates, 0 removals

  - Installing markupsafe (2.1.5)
  - Installing blinker (1.7.0)
  - Installing certifi (2024.2.2)
  - Installing charset-normalizer (3.3.2)
  - Installing click (8.1.7)
  - Installing idna (3.6)
  - Installing itsdangerous (2.1.2)
  - Installing jinja2 (3.1.3)
  - Installing packaging (24.0)
  - Installing typing-extensions (4.10.0)
  - Installing urllib3 (2.2.1)
  - Installing werkzeug (3.0.1)
  - Installing flask (3.0.2)
  - Installing gunicorn (21.2.0)
  - Installing python-dotenv (1.0.1)
  - Installing requests (2.31.0)
  - Installing unique-sdk (0.7.0)

Installing the current project: assistant_demo (0.1.0)
➜  assistant_demo git:(main) ✗ poetry run flask run --port 5001 --debug
 * Serving Flask app 'assistant_demo/app.py'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5001
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 346-338-416
Info

Note that the app is running on port 5001 on localhost but remember ngrock is redirecting the information.

Modify the App to act as a Module

We can now edit the app and make some modifications:

This is the app.py of the example we need to modify it in a few places:

Code Block
languagepy
import json
import os
from http import HTTPStatus
from logging.config import dictConfig

import unique_sdk
from dotenv import load_dotenv
from flask import Flask, jsonify, request

load_dotenv()

unique_sdk.api_key = os.environ.get("API_KEY")
unique_sdk.app_id = os.environ.get("APP_ID")
if os.environ.get("API_BASE"):
    unique_sdk.api_base = os.environ.get("API_BASE")

assistant_id = os.environ.get("ASSISTANT_ID")
if os.environ.get("ENDPOINT_SECRET"):
    endpoint_secret = os.environ.get("ENDPOINT_SECRET")

dictConfig(
    {
        "version": 1,
        "root": {"level": "DEBUG", "handlers": ["console"]},
        "handlers": {
            "console": {
}                "class": "logging.StreamHandler",
   }                     description"level": "DEBUG"This,
function is specifically designed for demos"        }
        },
    }
)

app = Flask(__name__)


}
@app.route("/")
def index():
    return "Hello from the Assistant Demo! 🚀"


@app.route("/webhook", methods=["POST"])
def weightwebhook():
5500    event = None
  }  payload = request.data
)
{    app.logger.info("Received webhook request.")

 templateName   try:
     companyId   event  }
}

Use the Module in a space

Now if you go into the spaces you can choose this as a module so:

Select the Email Writer module first:

...

Go ahead and publish it. Dont worry, only you can see this space for now.

...

Select the created Module Template

See the last entry and select this (Demo App Template Name) and also for example the email writer module.

...

Publish again.

...

Running your Python app

Now you are all set to run your app locally so it can react to chat messages:

In the location where you cloned the template repo https://github.com/Unique-AG/sdk-deploy-template

The .env file

Note

Go into the directory of the assistant_demo app and add the .env file with your settings:

  • All of these are different for you! Make sure they are correct biggest source of mistakes usually!

  • Look at the prefixes of the key app using

  • Remember how a correct API_BASE is defined

Note

90% of the errors happen here 😃

Code Block
API_KEY=ukey_qXqoh5lGhTa399CPNHHLUqnkIaWXlwRwAWtPJSrubyM
APP_ID=app_ueltalg142m341pskcankxk0
API_BASE=https://gateway.oleole.unique.app/public/chat
ENDPOINT_SECRET=usig_ad4wJ17fETob6uh0CYasNVn_aYtU1MRH4YnMrMiXjNE

Now go to the terminal and execute the following in that folder:

Code Block
➜  assistant_demo git:(main) ✗ poetry run flask run --port 5001 --debug
Creating virtualenv assistant-demo-C-sDUm3y-py3.12 in /Users/andreashauri/Library/Caches/pypoetry/virtualenvs
Command not found: flask
➜  assistant_demo git:(main) ✗ poetry install
Installing dependencies from lock file

Package operations: 17 installs, 0 updates, 0 removals

  - Installing markupsafe (2.1.5)
  - Installing blinker (1.7.0)
  - Installing certifi (2024.2.2)
  - Installing charset-normalizer (3.3.2)
  - Installing click (8.1.7)
  - Installing idna (3.6)
  - Installing itsdangerous (2.1.2)
  - Installing jinja2 (3.1.3)
  - Installing packaging (24.0)
  - Installing typing-extensions (4.10.0)
  - Installing urllib3 (2.2.1)
  - Installing werkzeug (3.0.1)
  - Installing flask (3.0.2)
  - Installing gunicorn (21.2.0)
  - Installing python-dotenv (1.0.1)
  - Installing requests (2.31.0)
  - Installing unique-sdk (0.7.0)

Installing the current project: assistant_demo (0.1.0)
➜  assistant_demo git:(main) ✗ poetry run flask run --port 5001 --debug
 * Serving Flask app 'assistant_demo/app.py'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5001
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 346-338-416
Info

Note that the app is running on port 5001 on localhost but remember ngrock is redirecting the information.

Modify the App to act as a Module

...

Code Block
languagepy
import json
import os
from http import HTTPStatus
from logging.config import dictConfig

import unique_sdk
from dotenv import load_dotenv
from flask import Flask, jsonify, request

load_dotenv()

unique_sdk.api_key = os.environ.get("API_KEY")
unique_sdk.app_id = os.environ.get("APP_ID")
if os.environ.get("API_BASE"):
    unique_sdk.api_base = os.environ.get("API_BASE")

assistant_id = os.environ.get("ASSISTANT_ID")
if os.environ.get("ENDPOINT_SECRET"):
    endpoint_secret = os.environ.get("ENDPOINT_SECRET")

dictConfig(
    {
        "version": 1,
        "root": {"level": "DEBUG", "handlers": ["console"]},
        "handlers": {
            "console": {
                "class": "logging.StreamHandler",
                "level": "DEBUG",
            }
        },
    }
)

app = Flask(__name__)


@app.route("/")
def index():
    return "Hello from the Assistant Demo! 🚀"


@app.route("/webhook", methods=["POST"])
def webhook():
    event = None
    payload = request.data

    app.logger.info("Received webhook request.")

    try:
        event = json.loads(payload)
 = json.loads(payload)
    except json.decoder.JSONDecodeError:
        return "Invalid payload", 400

    if endpoint_secret:
        # Only verify the event if there is an endpoint secret defined
        # Otherwise use the basic event deserialized with json
        sig_header = request.headers.get("X-Unique-Signature")
        timestamp = request.headers.get("X-Unique-Created-At")

        if not sig_header or not timestamp:
            print("⚠️  Webhook signature or timestamp headers missing.")
            return jsonify(success=False), HTTPStatus.BAD_REQUEST

        try:
            event = unique_sdk.Webhook.construct_event(
                payload, sig_header, timestamp, endpoint_secret
            )
        except unique_sdk.SignatureVerificationError as e:
            print("⚠️  Webhook signature verification failed. " + str(e))
            return jsonify(success=False), HTTPStatus.BAD_REQUEST

    if (
        event
        and event["event"] == "unique.chat.user-message.created"
        and event["payload"]["assistantId"] == assistant_id
    ):
        message = event["payload"]["text"]
        app.logger.info(f"Received message: {message}")

        # Send a message back to the user
        unique_sdk.Message.create(
            user_id=event["userId"],
            company_id=event["companyId"],
            chatId=event["payload"]["chatId"],
            assistantId=assistant_id,
            text="Hello from the Assistant Demo! 🚀",
            role="ASSISTANT",
        )

    return "OK", 200

...

Code Block
 * Detected change in '/Users/andreashauri/unique/dev/sdk-deploy-template/assistant_demo/assistant_demo/app.py', reloading
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 346-338-416
Received webhook request.
⚠️  Webhook signature verification failed. No signatures found matching the expected signature for payload. Are you passing the raw body you received from Unique? https://unique.ch/docs/webhooks/signatures
Received webhook request.
Received Nomessage: signaturesmake foundme matchinga thedemo expected signature for payload. Are you passing the raw body you received from Unique? https://unique.ch/docs/webhooks/signatures
Received webhook request.
Received message: make me a demo please
127.0.0.1 - - [15/May/2024 23:53:27] "POST /webhook HTTP/1.1" 400 -
127.0.0.1 - - [15/May/2024 23:53:28] "POST /webhook HTTP/1.1" 200 -

The frontend of Unique:

...

please
127.0.0.1 - - [15/May/2024 23:53:27] "POST /webhook HTTP/1.1" 400 -
127.0.0.1 - - [15/May/2024 23:53:28] "POST /webhook HTTP/1.1" 200 -

The frontend of Unique:

...

Comminication pattern in prod deployment.

Once the built module is deployed in an environment of your choice its important that the unique cluster can communicate with the module. This pattern looks like this:
So that means the custom module must be reachable via https from the unique cluster
and the custom Module on the other hand must be able to connect to the unique cluster via https.

Drawio
mVer2
zoom1
simple0
inComment0
custContentId1162674375
pageId548012135
lbox1
diagramDisplayNamecommunication-unique-module.drawio
contentVer1
revision1
baseUrlhttps://unique-ch.atlassian.net/wiki
diagramNamecommunication-unique-module.drawio
pCenter0
width721
links
tbstyle
height322.5

...

...