├── chainlit.md
├── src
├── router
│ ├── __init__.py
│ ├── constants.py
│ ├── layers.json
│ └── trainer.py
├── tools
│ └── __init__.py
├── utils
│ ├── __init__.py
│ ├── constants.py
│ ├── chat_profile.py
│ ├── url_extractor.py
│ ├── user_session_helper.py
│ ├── llm_profile_builder.py
│ ├── dict_to_object.py
│ ├── settings_builder.py
│ └── llm_settings_config.py
├── assistants
│ ├── mino
│ │ ├── __init__.py
│ │ ├── assistant_event_handler.py
│ │ ├── mino.py
│ │ └── create_assistant.py
│ └── shared
│ │ └── __init__.py
├── resources
│ ├── vt.jpg
│ ├── groq.ico
│ ├── cohere.ico
│ ├── ollama.png
│ ├── chatgpt-icon.png
│ ├── huggingface.ico
│ ├── openrouter.ico
│ ├── screenshot
│ │ ├── 1.jpg
│ │ ├── 2.jpg
│ │ ├── 3.jpg
│ │ └── 4.png
│ ├── claude-ai-icon.png
│ └── google-gemini-icon.png
└── app.py
├── .python-version
├── .vscode
└── settings.json
├── public
├── custom.css
├── favicon.ico
├── logo_dark.png
└── logo_light.png
├── requirements.txt
├── .env.example
├── .github
└── workflows
│ └── pylint.yml
├── pyproject.toml
├── LICENSE
├── README.md
├── TODO
├── .chainlit
├── config.toml
└── translations
│ └── en-US.json
├── .gitignore
├── requirements.lock
└── requirements-dev.lock
/chainlit.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/router/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/tools/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/utils/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.10.13
2 |
--------------------------------------------------------------------------------
/src/assistants/mino/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assistants/shared/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.analysis.extraPaths": [
3 | "./src"
4 | ]
5 | }
--------------------------------------------------------------------------------
/public/custom.css:
--------------------------------------------------------------------------------
1 | a[href*='https://github.com/Chainlit/chainlit'] {
2 | visibility: hidden;
3 | }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/public/logo_dark.png
--------------------------------------------------------------------------------
/src/resources/vt.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/vt.jpg
--------------------------------------------------------------------------------
/public/logo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/public/logo_light.png
--------------------------------------------------------------------------------
/src/resources/groq.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/groq.ico
--------------------------------------------------------------------------------
/src/resources/cohere.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/cohere.ico
--------------------------------------------------------------------------------
/src/resources/ollama.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/ollama.png
--------------------------------------------------------------------------------
/src/resources/chatgpt-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/chatgpt-icon.png
--------------------------------------------------------------------------------
/src/resources/huggingface.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/huggingface.ico
--------------------------------------------------------------------------------
/src/resources/openrouter.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/openrouter.ico
--------------------------------------------------------------------------------
/src/resources/screenshot/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/screenshot/1.jpg
--------------------------------------------------------------------------------
/src/resources/screenshot/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/screenshot/2.jpg
--------------------------------------------------------------------------------
/src/resources/screenshot/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/screenshot/3.jpg
--------------------------------------------------------------------------------
/src/resources/screenshot/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/screenshot/4.png
--------------------------------------------------------------------------------
/src/resources/claude-ai-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/claude-ai-icon.png
--------------------------------------------------------------------------------
/src/resources/google-gemini-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reku-push/Multi-AI-Chat-APP/HEAD/src/resources/google-gemini-icon.png
--------------------------------------------------------------------------------
/src/utils/constants.py:
--------------------------------------------------------------------------------
1 | APP_NAME = "VT.ai"
2 | MINO_ASSISTANT_NAME = "Mino"
3 | ASSISTANT_TOOL_CODE_INTEPRETER = "code_interpreter"
4 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | chainlit
2 | google-generativeai
3 | litellm
4 | openai
5 | pillow
6 | pydantic==2.10.1 # downgrade pydantic as incompatibilty with chainlit https://github.com/Chainlit/chainlit/issues/1556
7 | python-dotenv
8 | semantic-router[fastembed]
9 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # Do not share your API key with anyone! It should remain a secret.
2 | OPENAI_API_KEY=
3 | COHERE_API_KEY=
4 | HUGGINGFACE_API_KEY=
5 | ANTHROPIC_API_KEY=
6 | GROQ_API_KEY=
7 | OPENROUTER_API_KEY=
8 | GEMINI_API_KEY=
9 | MISTRAL_API_KEY=
10 |
--------------------------------------------------------------------------------
/src/utils/chat_profile.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 | from pydantic import BaseModel
4 |
5 |
6 | class AppChatProfileType(str, Enum):
7 | CHAT = "Chat"
8 | ASSISTANT = "Assistant"
9 |
10 |
11 | class AppChatProfileModel(BaseModel):
12 | title: str
13 | description: str
14 |
--------------------------------------------------------------------------------
/src/utils/url_extractor.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 |
4 | def extract_url(text):
5 | # Regular expression pattern to match URLs
6 | url_pattern = r'https?://[^\s<>"]+|www\.[^\s<>"]+'
7 |
8 | # Find all URLs in the text
9 | urls = re.findall(url_pattern, text)
10 |
11 | return urls
12 |
--------------------------------------------------------------------------------
/src/utils/user_session_helper.py:
--------------------------------------------------------------------------------
1 | import chainlit as cl
2 |
3 |
4 | def set_user_session_value_with_same_key(key):
5 | cl.user_session.set(key, key)
6 |
7 |
8 | def set_user_session_value(key, value):
9 | cl.user_session.set(key, value)
10 |
11 |
12 | def get_user_session_value(key):
13 | return cl.user_session.get(key)
14 |
--------------------------------------------------------------------------------
/src/utils/llm_profile_builder.py:
--------------------------------------------------------------------------------
1 | from typing import Dict
2 |
3 | import chainlit as cl
4 |
5 |
6 | def build_llm_profile(icons_provider_map: Dict[str, str]):
7 | for model_name, icon_path in icons_provider_map.items():
8 | return cl.ChatProfile(
9 | name=model_name,
10 | icon=icon_path,
11 | markdown_description=f"The underlying LLM model is **{model_name}**.",
12 | )
13 |
--------------------------------------------------------------------------------
/src/utils/dict_to_object.py:
--------------------------------------------------------------------------------
1 | class DictToObject:
2 | def __init__(self, dictionary):
3 | for key, value in dictionary.items():
4 | if isinstance(value, dict):
5 | setattr(self, key, DictToObject(value))
6 | else:
7 | setattr(self, key, value)
8 |
9 | def __str__(self):
10 | return "\n".join(f"{key}: {value}" for key, value in self.__dict__.items())
11 |
--------------------------------------------------------------------------------
/.github/workflows/pylint.yml:
--------------------------------------------------------------------------------
1 | name: Pylint
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | python-version: ["3.8", "3.9", "3.10"]
11 | steps:
12 | - uses: actions/checkout@v4
13 | - name: Set up Python ${{ matrix.python-version }}
14 | uses: actions/setup-python@v3
15 | with:
16 | python-version: ${{ matrix.python-version }}
17 | - name: Install dependencies
18 | run: |
19 | python -m pip install --upgrade pip
20 | pip install pylint
21 | - name: Analysing the code with pylint
22 | run: |
23 | pylint $(git ls-files '*.py')
24 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "vtai"
3 | version = "0.1.0"
4 | description = "VT.ai - Full-stack multi-modalities LLMs chat application"
5 | authors = [{ name = "Vinh Nguyen", email = "" }]
6 | dependencies = [
7 | "litellm>=1.35.17",
8 | "chainlit>=1.0.504",
9 | "google-generativeai>=0.5.2",
10 | "semantic-router[fastembed]==0.0.20",
11 | "pillow>=10.3.0",
12 | "openai>=1.23.2",
13 | "pydantic>=2.7.1",
14 | "python-dotenv>=1.0.1",
15 | ]
16 | readme = "README.md"
17 | requires-python = ">= 3.8"
18 |
19 | [build-system]
20 | requires = ["hatchling"]
21 | build-backend = "hatchling.build"
22 |
23 | [tool.rye]
24 | managed = true
25 | dev-dependencies = []
26 |
27 | [tool.hatch.metadata]
28 | allow-direct-references = true
29 |
30 | [tool.hatch.build.targets.wheel]
31 | packages = ["src"]
32 |
--------------------------------------------------------------------------------
/src/router/constants.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class SemanticRouterType(str, Enum):
5 | """
6 | We use https://github.com/aurelio-labs/semantic-router?tab=readme-ov-file
7 |
8 | Semantic Router is a superfast decision-making layer for your LLMs and agents. Rather than waiting for slow LLM generations to make tool-use decisions, we use the magic of semantic vector space to make those decisions — routing our requests using semantic meaning.
9 |
10 | --
11 |
12 | The definition is in `layers.json` file. Here we define routes' name constant.
13 |
14 | It' must be mapped together.
15 | """
16 |
17 | IMAGE_GENERATION = "image-generation"
18 | TEXT_PROCESSING = "text-processing"
19 | CASUAL_CONVERSATION = "casual-conversation"
20 | CURIOUS = "curious"
21 | VISION_IMAGE_PROCESSING = "vision-image-processing"
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Vinh Nguyen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/assistants/mino/assistant_event_handler.py:
--------------------------------------------------------------------------------
1 | from openai import AssistantEventHandler
2 | from typing_extensions import override
3 |
4 |
5 | # ref: https://platform.openai.com/docs/assistants/overview
6 | class EventHandler(AssistantEventHandler):
7 | @override
8 | def on_text_created(self, text) -> None:
9 | print("\nassistant > ", end="", flush=True)
10 |
11 | @override
12 | def on_text_delta(self, delta, snapshot):
13 | print(delta.value, end="", flush=True)
14 |
15 | def on_tool_call_created(self, tool_call):
16 | print(f"\nassistant > {tool_call.type}\n", flush=True)
17 |
18 | def on_tool_call_delta(self, delta, snapshot):
19 | if delta.type == "code_interpreter":
20 | if delta.code_interpreter.input:
21 | print(delta.code_interpreter.input, end="", flush=True)
22 | if delta.code_interpreter.outputs:
23 | print("\n\noutput >", flush=True)
24 | for output in delta.code_interpreter.outputs:
25 | if output.type == "logs":
26 | print(f"\n{output.logs}", flush=True)
27 |
28 |
29 | # Then, we use the `stream` SDK helper
30 | # with the `EventHandler` class to create the Run
31 | # and stream the response.
32 | # with client.beta.threads.runs.stream(
33 | # thread_id=thread.id,
34 | # assistant_id=assistant.id,
35 | # instructions="Please address the user as Jane Doe. The user has a premium account.",
36 | # event_handler=EventHandler(),
37 | # ) as stream:
38 | # stream.until_done()
39 |
--------------------------------------------------------------------------------
/src/assistants/mino/mino.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from dotenv import load_dotenv
4 | from openai import AsyncOpenAI
5 | from openai.types.beta import Assistant
6 | from utils import constants
7 |
8 | # Assistant (beta)
9 | # ref: https://platform.openai.com/docs/assistants/tools/code-interpreter/how-it-works
10 |
11 |
12 | load_dotenv()
13 |
14 | api_key = os.environ.get("OPENAI_API_KEY")
15 | client = AsyncOpenAI(api_key=api_key)
16 |
17 | NAME = "Mino"
18 |
19 | INSTRUCTIONS = """You are a personal math tutor. Write and run code to answer math questions.
20 | Enclose math expressions in $$ (this is helpful to display latex). Example:
21 | ```
22 | Given a formula below $$ s = ut + \frac{1}{2}at^{2} $$ Calculate the value of $s$ when $u = 10\frac{m}{s}$ and $a = 2\frac{m}{s^{2}}$ at $t = 1s$
23 | ```
24 | """
25 | # INSTRUCTIONS = "You are an data science expert who is great at analyzing and visualising datasets. take a look at this data"
26 | # "You are a personal math tutor. Write and run code to answer math questions. Your name is Mino."
27 | MODEL = "gpt-4o"
28 |
29 |
30 | class MinoAssistant:
31 | def __init__(
32 | self,
33 | openai_client: AsyncOpenAI | None,
34 | ):
35 | if openai_client is None:
36 | self.__openai_client__ = AsyncOpenAI(max_retries=2)
37 | else:
38 | self.__openai_client__ = openai_client
39 |
40 | self.name = constants.APP_NAME
41 | self.instructions = INSTRUCTIONS
42 |
43 | async def run_assistant(self) -> Assistant:
44 | tool = constants.ASSISTANT_TOOL_CODE_INTEPRETER
45 | assistant_name = NAME
46 | assistant = await self.__openai_client__.beta.assistants.create(
47 | name=assistant_name,
48 | instructions=INSTRUCTIONS,
49 | tools=[
50 | {
51 | "type": tool,
52 | }
53 | ],
54 | model=MODEL,
55 | )
56 |
57 | return assistant
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
VT.ai
3 |
4 |
5 | Minimal multimodal AI chat app with dynamic conversation routing
6 |
7 |
8 | ## Overview
9 |
10 | VT.ai is a VT.ai - Minimal multimodal AI chat app that provides a seamless chat interface for interacting with various Large Language Models (LLMs). It supports both cloud-based providers and local model execution through [Ollama](https://github.com/ollama/ollama).
11 |
12 | ### Key Features 🚀
13 |
14 | - **Multi-modal Interactions**
15 | - Text and image processing capabilities
16 | - Real-time streaming responses
17 | - [Beta] Advanced Assistant features via OpenAI's Assistant API
18 |
19 | - **Flexible Model Support**
20 | - OpenAI, Anthropic, and Google integration
21 | - Local model execution via Ollama
22 | - Dynamic parameter adjustment (temperature, top-p)
23 |
24 | - **Modern Architecture**
25 | - Built on Chainlit for responsive UI
26 | - SemanticRouter for intelligent conversation routing
27 | - Real-time response streaming
28 | - Customizable model settings
29 |
30 | ## Screenshots
31 |
32 | 
33 | 
34 |
35 | ## Quick Start Guide
36 |
37 | ### Prerequisites
38 |
39 | - Python 3.7+
40 | - (Recommended) `rye` for dependency management
41 | - For local models:
42 | - [Ollama](https://ollama.com/download) client
43 | - Desired [Ollama models](https://ollama.com/library)
44 |
45 | ### Installation
46 |
47 | 1. Clone the repository
48 | 2. Copy `.env.example` to `.env` and configure your API keys
49 | 3. Set up Python environment:
50 | ```bash
51 | python3 -m venv .venv
52 | source .venv/bin/activate
53 | pip3 install -r requirements.txt
54 | ```
55 | 4. Optional: Train semantic router
56 | ```bash
57 | python3 src/router/trainer.py
58 | ```
59 | 5. Launch the application:
60 | ```bash
61 | chainlit run src/app.py -w
62 | ```
63 |
64 | ### Using Local Models with Ollama
65 |
66 | ```bash
67 | # Download model
68 | ollama pull llama3
69 |
70 | # Start Ollama server
71 | ollama serve
72 | ```
73 |
74 | ## Technical Stack
75 |
76 | - **[Chainlit](https://github.com/Chainlit/chainlit)**: Frontend framework
77 | - **[LiteLLM](https://github.com/BerriAI/litellm)**: LLM integration layer
78 | - **[SemanticRouter](https://github.com/aurelio-labs/semantic-router)**: Conversation routing
79 |
80 | ## Contributing
81 |
82 | 1. Fork the repository
83 | 2. Create a feature branch: `git checkout -b feature/amazing-feature`
84 | 3. Commit changes: `git commit -m 'Add amazing feature'`
85 | 4. Push to branch: `git push origin feature/amazing-feature`
86 | 5. Open a Pull Request
87 |
88 |
89 | ## License
90 |
91 | This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
92 |
93 | ## Connect
94 |
95 | -Email: sunstar962090@gmail.com
96 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | # TODO: https://docs.litellm.ai/docs/completion/token_usage
2 |
3 | # TODO: update vision handling handling (local & remote) with gpt-4o https://platform.openai.com/docs/guides/vision
4 |
5 | # TODO handle vision capability for GPT-4o
6 |
7 | # TODO Hello GPT-4o https://openai.com/index/hello-gpt-4o/
8 | https://platform.openai.com/docs/models/gpt-4o
9 |
10 | # TODO: hugging face support https://docs.litellm.ai/docs/providers/huggingface
11 |
12 | # TODO: embed llama.cpp Python binding for Local LLM https://github.com/abetlen/llama-cpp-python
13 |
14 |
15 | # TODO: HN API tools https://github.com/HackerNews/API
16 |
17 | # TODO: deploy 💰 Budgets, Rate Limits https://docs.litellm.ai/docs/proxy/users
18 |
19 |
20 |
21 | # TODO: 🐳 Docker, Deploying LiteLLM Proxy https://docs.litellm.ai/docs/proxy/deploy
22 |
23 |
24 | # TODO: model fallback https://docs.litellm.ai/docs/tutorials/model_fallbacks
25 |
26 |
27 | # TODO: check Cohere docs, seems like currenlty had error https://docs.litellm.ai/docs/providers/cohere
28 |
29 | # TODO: IMPORTANT: about tool use, note to self tool use streaming is not support for most LLM provider (OpenAI, Anthropic) so in other to use tool, need to disable `streaming` param
30 |
31 | # TODO: idea integrate token cost counter https://github.com/AgentOps-AI/tokencost?tab=readme-ov-file
32 |
33 | # TODO: proper support local LLM
34 |
35 | # TODO: handle passing file to code-interpreter for Mino Assistant
36 |
37 | # TODO: https://github.com/jakecyr/openai-function-calling
38 |
39 | # TODO: https://github.com/Chainlit/cookbook/tree/main/openai-functions
40 |
41 | # TODO: https://github.com/Chainlit/cookbook/tree/main/openai-functions-streaming
42 |
43 | # TODO: https://github.com/Chainlit/cookbook/tree/main/openai-concurrent-streaming
44 |
45 | # TODO: https://github.com/Chainlit/cookbook/tree/main/openai-concurrent-functions
46 |
47 | # TODO: starting screen -> generate list of conversation starter buttons.
48 |
49 | # TODO: token count, model name https://docs.litellm.ai/docs/completion/output
50 |
51 | # TODO: TaskList https://docs.chainlit.io/api-reference/elements/tasklist
52 |
53 | # TODO: https://docs.chainlit.io/api-reference/data-persistence/custom-data-layer
54 |
55 | # TODO: callback https://docs.chainlit.io/concepts/action#define-a-python-callback
56 |
57 | # TODO: customize https://docs.chainlit.io/customisation/overview
58 |
59 | # TODO: config https://docs.chainlit.io/backend/config/overview
60 |
61 | # TODO: sync/async https://docs.chainlit.io/guides/sync-async
62 |
63 | # TODO: function call https://docs.litellm.ai/docs/completion/function_call
64 |
65 | # TODO https://docs.litellm.ai/docs/completion/reliable_completions
66 |
67 | # TODO: Auth https://docs.chainlit.io/authentication/overview
68 |
69 | # TODO: Data persistence https://docs.chainlit.io/data-persistence/overview
70 |
71 | # TODO: custom endpoint https://docs.chainlit.io/backend/custom-endpoint
72 |
73 | # TODO: deploy https://docs.chainlit.io/deployment/tutorials
74 |
75 | # TODO: copilot chat widget https://docs.chainlit.io/deployment/copilot
76 |
--------------------------------------------------------------------------------
/.chainlit/config.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | # Whether to enable telemetry (default: true). No personal data is collected.
3 | enable_telemetry = false
4 |
5 |
6 | # List of environment variables to be provided by each user to use the app.
7 | user_env = []
8 |
9 | # Duration (in seconds) during which the session is saved when the connection is lost
10 | session_timeout = 3600
11 |
12 | # Enable third parties caching (e.g LangChain cache)
13 | cache = false
14 |
15 | # Authorized origins
16 | allow_origins = ["*"]
17 |
18 | # Follow symlink for asset mount (see https://github.com/Chainlit/chainlit/issues/317)
19 | # follow_symlink = false
20 |
21 | [features]
22 | # Show the prompt playground
23 | prompt_playground = true
24 |
25 | # Process and display HTML in messages. This can be a security risk (see https://stackoverflow.com/questions/19603097/why-is-it-dangerous-to-render-user-generated-html-or-javascript)
26 | unsafe_allow_html = false
27 |
28 | # Process and display mathematical expressions. This can clash with "$" characters in messages.
29 | latex = true
30 |
31 | # Authorize users to upload files with messages
32 | [features.multi_modal]
33 | accept = ["*/*"]
34 | enabled = true
35 | max_files = 5
36 | max_size_mb = 25
37 |
38 | # Allows user to use speech to text
39 | [features.speech_to_text]
40 | enabled = true
41 | # See all languages here https://github.com/JamesBrill/react-speech-recognition/blob/HEAD/docs/API.md#language-string
42 | language = "en-US"
43 |
44 | [UI]
45 | # Name of the app and chatbot.
46 | name = "VT.ai"
47 |
48 | # Show the readme while the thread is empty.
49 | show_readme_as_default = false
50 |
51 | # Description of the app and chatbot. This is used for HTML tags.
52 | description = "Simple LLM chat frontend"
53 |
54 | # Large size content are by default collapsed for a cleaner ui
55 | default_collapse_content = true
56 |
57 | # The default value for the expand messages settings.
58 | default_expand_messages = false
59 |
60 | # Hide the chain of thought details from the user in the UI.
61 | hide_cot = false
62 |
63 | # Link to your github repo. This will add a github button in the UI's header.
64 | # github = ""
65 |
66 | # Specify a CSS file that can be used to customize the user interface.
67 | # The CSS file can be served from the public directory or via an external link.
68 | # custom_css = "/public/custom.css"
69 |
70 | # Specify a Javascript file that can be used to customize the user interface.
71 | # The Javascript file can be served from the public directory.
72 | # custom_js = "/public/test.js"
73 |
74 | # Specify a custom font url.
75 | custom_font = "https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;700&display=swap"
76 |
77 | # Specify a custom build directory for the frontend.
78 | # This can be used to customize the frontend code.
79 | # Be careful: If this is a relative path, it should not start with a slash.
80 | # custom_build = "./public/build"
81 |
82 | # Override default MUI light theme. (Check theme.ts)
83 | [UI.theme]
84 | font_family = "IBM Plex Sans, sans-serif"
85 |
86 | [UI.theme.light]
87 | background = "#D9D9D9"
88 | paper = "#F2F2F2"
89 |
90 | [UI.theme.light.primary]
91 | main = "#78BFBF"
92 | dark = "#737373"
93 | light = "#D9D9D9"
94 |
95 | # Override default MUI dark theme. (Check theme.ts)
96 | [UI.theme.dark]
97 | background = "#262626"
98 | paper = "#171C26"
99 |
100 | [UI.theme.dark.primary]
101 | main = "#A63333"
102 | dark = "#262626"
103 | light = "#BFB38F"
104 |
105 | [meta]
106 | generated_by = "1.0.504"
107 |
--------------------------------------------------------------------------------
/src/assistants/mino/create_assistant.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import json
3 | import os
4 |
5 | from dotenv import load_dotenv
6 | from openai import AsyncOpenAI
7 |
8 | load_dotenv()
9 |
10 | api_key = os.environ.get("OPENAI_API_KEY")
11 | client = AsyncOpenAI(api_key=api_key)
12 |
13 | tools = [
14 | {"type": "code_interpreter"},
15 | {
16 | "type": "function",
17 | "function": {
18 | "name": "get_current_weather",
19 | "description": "Get the current weather",
20 | "parameters": {
21 | "type": "object",
22 | "properties": {
23 | "location": {
24 | "type": "string",
25 | "description": "The city and state, e.g. San Francisco, CA",
26 | },
27 | "format": {
28 | "type": "string",
29 | "enum": ["celsius", "fahrenheit"],
30 | "description": "The temperature unit to use. Infer this from the users location.",
31 | },
32 | },
33 | "required": ["location", "format"],
34 | },
35 | },
36 | },
37 | {
38 | "type": "function",
39 | "function": {
40 | "name": "get_n_day_weather_forecast",
41 | "description": "Get an N-day weather forecast",
42 | "parameters": {
43 | "type": "object",
44 | "properties": {
45 | "location": {
46 | "type": "string",
47 | "description": "The city and state, e.g. San Francisco, CA",
48 | },
49 | "format": {
50 | "type": "string",
51 | "enum": ["celsius", "fahrenheit"],
52 | "description": "The temperature unit to use. Infer this from the users location.",
53 | },
54 | "num_days": {
55 | "type": "integer",
56 | "description": "The number of days to forecast",
57 | },
58 | },
59 | "required": ["location", "format", "num_days"],
60 | },
61 | },
62 | },
63 | ]
64 |
65 |
66 | def get_current_weather(location: str, format: str):
67 | # return dummy weather
68 | return "The current weather in {} is {} degrees {}".format(location, 20, format)
69 |
70 |
71 | def get_n_day_weather_forecast(location: str, format: str, num_days: int):
72 | # return dummy weather
73 | return "The weather forecast for the next {} days in {} is {} degrees {}".format(
74 | num_days, location, 20, format
75 | )
76 |
77 |
78 | tool_map = {
79 | "get_current_weather": get_current_weather,
80 | "get_n_day_weather_forecast": get_n_day_weather_forecast,
81 | }
82 |
83 |
84 | async def create():
85 | # ... your existing create function code ...
86 |
87 | instructions = """You are a personal math tutor. Write and run code to answer math questions.
88 | Enclose math expressions in $$ (this is helpful to display latex). Example:
89 | ```
90 | Given a formula below $$ s = ut + \frac{1}{2}at^{2} $$ Calculate the value of $s$ when $u = 10\frac{m}{s}$ and $a = 2\frac{m}{s^{2}}$ at $t = 1s$
91 | ```
92 |
93 | You can also answer weather questions!
94 | """
95 |
96 | assistant = await client.beta.assistants.create(
97 | name="Math Tutor And Weather Bot",
98 | instructions=instructions,
99 | tools=tools,
100 | model="gpt-4-1106-preview",
101 | )
102 | assistant_name = "math_tutor_and_weather_bot"
103 | # append key value pair to assistants.json
104 |
105 | def load_or_create_json(filename):
106 | try:
107 | return json.load(open(filename, "r"))
108 | except FileNotFoundError:
109 | return {}
110 |
111 | assistant_dict = load_or_create_json("assistants.json")
112 | assistant_dict[assistant_name] = assistant.id
113 | json.dump(assistant_dict, open("assistants.json", "w"))
114 |
115 |
116 | if __name__ == "__main__":
117 | asyncio.run(create())
118 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/python
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python
3 |
4 | ### Python ###
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 | *.py[cod]
8 | *$py.class
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | build/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | wheels/
27 | share/python-wheels/
28 | *.egg-info/
29 | .installed.cfg
30 | *.egg
31 | MANIFEST
32 |
33 | # PyInstaller
34 | # Usually these files are written by a python script from a template
35 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
36 | *.manifest
37 | *.spec
38 |
39 | # Installer logs
40 | pip-log.txt
41 | pip-delete-this-directory.txt
42 |
43 | # Unit test / coverage reports
44 | htmlcov/
45 | .tox/
46 | .nox/
47 | .coverage
48 | .coverage.*
49 | .cache
50 | nosetests.xml
51 | coverage.xml
52 | *.cover
53 | *.py,cover
54 | .hypothesis/
55 | .pytest_cache/
56 | cover/
57 |
58 | # Translations
59 | *.mo
60 | *.pot
61 |
62 | # Django stuff:
63 | *.log
64 | local_settings.py
65 | db.sqlite3
66 | db.sqlite3-journal
67 |
68 | # Flask stuff:
69 | instance/
70 | .webassets-cache
71 |
72 | # Scrapy stuff:
73 | .scrapy
74 |
75 | # Sphinx documentation
76 | docs/_build/
77 |
78 | # PyBuilder
79 | .pybuilder/
80 | target/
81 |
82 | # Jupyter Notebook
83 | .ipynb_checkpoints
84 |
85 | # IPython
86 | profile_default/
87 | ipython_config.py
88 |
89 | # pyenv
90 | # For a library or package, you might want to ignore these files since the code is
91 | # intended to run in multiple environments; otherwise, check them in:
92 | # .python-version
93 |
94 | # pipenv
95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
98 | # install all needed dependencies.
99 | #Pipfile.lock
100 |
101 | # poetry
102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
103 | # This is especially recommended for binary packages to ensure reproducibility, and is more
104 | # commonly ignored for libraries.
105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
106 | #poetry.lock
107 |
108 | # pdm
109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
110 | #pdm.lock
111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
112 | # in version control.
113 | # https://pdm.fming.dev/#use-with-ide
114 | .pdm.toml
115 |
116 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
117 | __pypackages__/
118 |
119 | # Celery stuff
120 | celerybeat-schedule
121 | celerybeat.pid
122 |
123 | # SageMath parsed files
124 | *.sage.py
125 |
126 | # Environments
127 | .env
128 | .venv
129 | env/
130 | venv/
131 | ENV/
132 | env.bak/
133 | venv.bak/
134 |
135 | # Spyder project settings
136 | .spyderproject
137 | .spyproject
138 |
139 | # Rope project settings
140 | .ropeproject
141 |
142 | # mkdocs documentation
143 | /site
144 |
145 | # mypy
146 | .mypy_cache/
147 | .dmypy.json
148 | dmypy.json
149 |
150 | # Pyre type checker
151 | .pyre/
152 |
153 | # pytype static type analyzer
154 | .pytype/
155 |
156 | # Cython debug symbols
157 | cython_debug/
158 |
159 | # PyCharm
160 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
161 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
162 | # and can be added to the global gitignore or merged into this file. For a more nuclear
163 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
164 | #.idea/
165 |
166 | ### Python Patch ###
167 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
168 | poetry.toml
169 |
170 | # ruff
171 | .ruff_cache/
172 |
173 | # LSP config files
174 | pyrightconfig.json
175 |
176 | .files/
177 | .files/*
178 |
179 | # End of https://www.toptal.com/developers/gitignore/api/python
--------------------------------------------------------------------------------
/src/utils/settings_builder.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Dict
2 |
3 | import chainlit as cl
4 | from chainlit.input_widget import Select, Slider, Switch
5 |
6 | from utils import llm_settings_config as conf
7 |
8 |
9 | async def build_settings() -> Dict[str, Any]:
10 | """
11 | Builds and sends chat settings to the user for configuration.
12 | """
13 | settings = await cl.ChatSettings(
14 | [
15 | Switch(
16 | id=conf.SETTINGS_USE_DYNAMIC_CONVERSATION_ROUTING,
17 | label="[Experiment] Use dynamic conversation routing",
18 | description=f"This experimental feature automatically switches to specialized models based on your input. For example, if you ask to generate an image, it will use an image generation model like DALL·E 3. Note that this action requires an OpenAI API key. Default value is {conf.SETTINGS_USE_DYNAMIC_CONVERSATION_ROUTING_DEFAULT_VALUE}",
19 | initial=conf.SETTINGS_USE_DYNAMIC_CONVERSATION_ROUTING_DEFAULT_VALUE,
20 | ),
21 | Select(
22 | id=conf.SETTINGS_CHAT_MODEL,
23 | label="Chat Model",
24 | description="""
25 | Select the Large Language Model (LLM) you want to use for chat conversations. Different models have varying strengths and capabilities.
26 |
27 | (NOTE) For using Ollama to get up and running with large language models locally. Please refer to quick start guide: https://github.com/ollama/ollama/blob/main/README.md#quickstart""",
28 | values=conf.MODELS,
29 | initial_value=conf.DEFAULT_MODEL,
30 | ),
31 | Slider(
32 | id=conf.SETTINGS_TEMPERATURE,
33 | label="Temperature",
34 | description="What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.",
35 | min=0,
36 | max=2.0,
37 | step=0.1,
38 | initial=conf.DEFAULT_TEMPERATURE,
39 | tooltip="Adjust the temperature parameter",
40 | ),
41 | Slider(
42 | id=conf.SETTINGS_TOP_P,
43 | label="Top P",
44 | description="An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.",
45 | min=0.1,
46 | max=1.0,
47 | step=0.1,
48 | initial=conf.DEFAULT_TOP_P,
49 | tooltip="Adjust the top P parameter",
50 | ),
51 | Select(
52 | id=conf.SETTINGS_VISION_MODEL,
53 | label="Vision Model",
54 | description="Choose the vision model to analyze and understand images. This enables features like image description and object recognition.",
55 | values=conf.VISION_MODEL_MODELS,
56 | initial_value=conf.DEFAULT_VISION_MODEL,
57 | ),
58 | Switch(
59 | id=conf.SETTINGS_ENABLE_TTS_RESPONSE,
60 | label="Enable TTS",
61 | description=f"This feature allows you to hear the chat responses spoken aloud, which can be helpful for accessibility or multitasking. Note that this action requires an OpenAI API key. Default value is {conf.SETTINGS_ENABLE_TTS_RESPONSE_DEFAULT_VALUE}.",
62 | initial=conf.SETTINGS_ENABLE_TTS_RESPONSE_DEFAULT_VALUE,
63 | ),
64 | Select(
65 | id=conf.SETTINGS_TTS_MODEL,
66 | label="TTS Model",
67 | description="Select the TTS model to use for generating speech. Different models offer distinct voice styles and characteristics.",
68 | values=conf.TTS_MODEL_MODELS,
69 | initial_value=conf.DEFAULT_TTS_MODEL,
70 | ),
71 | Select(
72 | id=conf.SETTINGS_TTS_VOICE_PRESET_MODEL,
73 | label="TTS - Voice options",
74 | description="Choose the specific voice preset you prefer for TTS responses. Each preset offers a unique vocal style and tone.",
75 | values=conf.TTS_VOICE_PRESETS,
76 | initial_value=conf.DEFAULT_TTS_PRESET,
77 | ),
78 | Switch(
79 | id=conf.SETTINGS_TRIMMED_MESSAGES,
80 | label="Trimming Input Messages",
81 | description="Ensure messages does not exceed a model's token limit",
82 | initial=conf.SETTINGS_TRIMMED_MESSAGES_DEFAULT_VALUE,
83 | ),
84 | Select(
85 | id=conf.SETTINGS_IMAGE_GEN_IMAGE_STYLE,
86 | label="Image Gen Image Style",
87 | description="Set the style of the generated images.Vivid : hyper-real and dramatic images. Natural: produce more natural, less hyper-real looking images.",
88 | values=conf.SETTINGS_IMAGE_GEN_IMAGE_STYLES,
89 | initial_value=conf.SETTINGS_IMAGE_GEN_IMAGE_STYLES[0],
90 | ),
91 | Select(
92 | id=conf.SETTINGS_IMAGE_GEN_IMAGE_QUALITY,
93 | label="Image Gen Image Quality",
94 | description="Set the quality of the image that will be generated. HD creates images with finer details and greater consistency across the image",
95 | values=conf.SETTINGS_IMAGE_GEN_IMAGE_QUALITIES,
96 | initial_value=conf.SETTINGS_IMAGE_GEN_IMAGE_QUALITIES[0],
97 | ),
98 | ]
99 | ).send()
100 |
101 | return settings
102 |
--------------------------------------------------------------------------------
/src/utils/llm_settings_config.py:
--------------------------------------------------------------------------------
1 | from chainlit.types import ChatProfile
2 |
3 | from utils.chat_profile import AppChatProfileModel, AppChatProfileType
4 |
5 | # settings
6 | SETTINGS_CHAT_MODEL = "settings_chat_model"
7 | SETTINGS_VISION_MODEL = "settings_vision_model"
8 | SETTINGS_IMAGE_GEN_LLM_MODEL = "settings_image_gen_llm_model"
9 | SETTINGS_IMAGE_GEN_IMAGE_STYLE = "settings_image_gen_image_style"
10 | SETTINGS_IMAGE_GEN_IMAGE_QUALITY = "settings_image_gen_image_quality"
11 | SETTINGS_TTS_MODEL = "settings_tts_model"
12 | SETTINGS_TTS_VOICE_PRESET_MODEL = "settings_tts_voice_preset_model"
13 | SETTINGS_ENABLE_TTS_RESPONSE = "settings_enable_tts_response"
14 |
15 | SETTINGS_USE_DYNAMIC_CONVERSATION_ROUTING = "settings_use_dynamic_conversation_routing"
16 | SETTINGS_TRIMMED_MESSAGES = "settings_trimmed_messages"
17 | SETTINGS_USE_DYNAMIC_CONVERSATION_ROUTING_DEFAULT_VALUE = True
18 | SETTINGS_TRIMMED_MESSAGES_DEFAULT_VALUE = True
19 | SETTINGS_ENABLE_TTS_RESPONSE_DEFAULT_VALUE = True
20 |
21 | # ref https://platform.openai.com/docs/api-reference/chat
22 | SETTINGS_TEMPERATURE = "settings_temperature"
23 | DEFAULT_TEMPERATURE = 0.8
24 | SETTINGS_TOP_K = "settings_top_k"
25 | SETTINGS_TOP_P = "settings_top_p"
26 | DEFAULT_TOP_P = 1
27 |
28 | # set models alias mapping
29 |
30 | DEFAULT_MODEL = "gpt-4o-mini"
31 | DEFAULT_IMAGE_GEN_MODEL = "dall-e-3"
32 | DEFAULT_VISION_MODEL = "gemini/gemini-1.5-pro-latest"
33 | DEFAULT_TTS_MODEL = "tts-1"
34 | DEFAULT_TTS_PRESET = "nova"
35 | DEFAULT_WHISPER_MODEL = "whisper-1"
36 |
37 | SETTINGS_IMAGE_GEN_IMAGE_STYLES = ["vivid", "natural"]
38 | SETTINGS_IMAGE_GEN_IMAGE_QUALITIES = ["standard", "hd"]
39 |
40 | TTS_MODELS_MAP = {
41 | "OpenAI - Text-to-speech 1": "tts-1",
42 | "OpenAI - Text-to-speech 1 HD": "tts-1-hd",
43 | }
44 |
45 | TTS_VOICE_PRESETS = [
46 | "alloy",
47 | "echo",
48 | "fable",
49 | "onyx",
50 | "nova",
51 | "shimmer",
52 | ]
53 |
54 | IMAGE_GEN_MODELS_ALIAS_MAP = {
55 | "OpenAI - DALL·E 3": "dall-e-3",
56 | }
57 |
58 | VISION_MODEL_MAP = {
59 | "OpenAI - GPT-4o": "gpt-4o",
60 | "OpenAI - GPT 4 Turbo": "gpt-4-turbo",
61 | "Google - Gemini 1.5 Flash": "gemini/gemini-1.5-flash-latest",
62 | "Google - Gemini 1.5 Pro": "gemini/gemini-1.5-pro-latest",
63 | "Ollama - LLama 3.2 Vision": "ollama/llama3.2-vision",
64 | }
65 |
66 | MODEL_ALIAS_MAP = {
67 | "OpenAI - GPT-4o": "gpt-4o",
68 | "OpenAI - GPT-4o-Mini": "gpt-4o-mini",
69 | "Anthropic - Claude 3.5 Sonnet": "claude-3-5-sonnet-20241022",
70 | "Anthropic - Claude 3.5 Haiky": "claude-3-5-haiku-20241022",
71 | "Groq - Llama 3 8b": "groq/llama3-8b-8192",
72 | "Groq - Llama 3 70b": "groq/llama3-70b-8192",
73 | "Groq - Mixtral 8x7b": "groq/mixtral-8x7b-32768",
74 | "Cohere - Command": "command",
75 | "Cohere - Command-R": "command-r",
76 | "Cohere - Command-Light": "command-light",
77 | "Cohere - Command-R-Plus": "command-r-plus",
78 | "Google - Gemini 1.5 Pro": "gemini/gemini-1.5-pro-latest",
79 | "Google - gemini-1.5-pro-002": "gemini/gemini-1.5-pro-002",
80 | "Google - Gemini 1.5 Flash": "gemini/gemini-1.5-flash-latest",
81 | "OpenRouter - Qwen2.5-coder 32b": "openrouter/qwen/qwen-2.5-coder-32b-instruct",
82 | "OpenRouter - Mistral 7b instruct": "openrouter/mistralai/mistral-7b-instruct",
83 | "OpenRouter - Mistral 7b instruct Free": "openrouter/mistralai/mistral-7b-instruct:free",
84 | "Ollama - Qwen2.5-coder 7b": "ollama/qwen2.5-coder",
85 | "Ollama - Qwen2.5-coder 14b": "ollama/qwen2.5-coder:14b",
86 | "Ollama - Qwen2.5-coder 32b": "ollama/qwen2.5-coder:32b",
87 | "Ollama - LLama 3.2 Vision": "ollama/llama3.2-vision",
88 | "Ollama - LLama 3": "ollama/llama3",
89 | "Ollama - LLama 3.1": "ollama/llama3.1",
90 | "Ollama - LLama 3.2 - Mini": "ollama/llama3.2",
91 | "Ollama - Phi-3": "ollama/phi3",
92 | "Ollama - Command R": "ollama/command-r",
93 | "Ollama - Command R+": "ollama/command-r-plus",
94 | "Ollama - Mistral 7B Instruct": "ollama/mistral",
95 | "Ollama - Mixtral 8x7B Instruct": "ollama/mixtral",
96 | }
97 |
98 | ICONS_PROVIDER_MAP = {
99 | "VT.ai": "./src/resources/vt.jpg",
100 | "Mino": "./src/resources/vt.jpg",
101 | "tts-1": "./src/resources/chatgpt-icon.png",
102 | "tts-1-hd": "./src/resources/chatgpt-icon.png",
103 | "OpenAI": "./src/resources/chatgpt-icon.png",
104 | "Ollama": "./src/resources/ollama.png",
105 | "Anthropic": "./src/resources/claude-ai-icon.png",
106 | "OpenRouter" "Google": "./src/resources/google-gemini-icon.png",
107 | "Groq": "./src/resources/groq.ico",
108 | "dall-e-3": "./src/resources/chatgpt-icon.png",
109 | "gpt-4": "./src/resources/chatgpt-icon.png",
110 | "gpt-4o": "./src/resources/chatgpt-icon.png",
111 | "gpt-4-turbo": "./src/resources/chatgpt-icon.png",
112 | "gpt-3.5-turbo": "./src/resources/chatgpt-icon.png",
113 | "command": "./src/resources/cohere.ico",
114 | "command-r": "./src/resources/cohere.ico",
115 | "command-light": "./src/resources/cohere.ico",
116 | "command-r-plus": "./src/resources/cohere.ico",
117 | "claude-2": "./src/resources/claude-ai-icon.png",
118 | "claude-3-sonnet-20240229": "./src/resources/claude-ai-icon.png",
119 | "claude-3-haiku-20240307": "./src/resources/claude-ai-icon.png",
120 | "claude-3-opus-20240229": "./src/resources/claude-ai-icon.png",
121 | "groq/llama3-8b-8192": "./src/resources/groq.ico",
122 | "groq/llama3-70b-8192": "./src/resources/groq.ico",
123 | "groq/mixtral-8x7b-32768": "./src/resources/groq.ico",
124 | "gemini/gemini-1.5-pro-latest": "./src/resources/google-gemini-icon.png",
125 | "gemini/gemini-1.5-flash-latest": "./src/resources/google-gemini-icon.png",
126 | "openrouter/mistralai/mistral-7b-instruct": "./src/resources/openrouter.ico",
127 | "OpenRouter - Mistral 7b instruct Free": "./src/resources/openrouter.ico",
128 | "ollama/llama3": "./src/resources/ollama.png",
129 | "ollama/mistral": "./src/resources/ollama.png",
130 | }
131 |
132 | NAMES = list(MODEL_ALIAS_MAP.keys())
133 | MODELS = list(MODEL_ALIAS_MAP.values())
134 |
135 | IMAGE_GEN_NAMES = list(IMAGE_GEN_MODELS_ALIAS_MAP.keys())
136 | IMAGE_GEN_MODELS = list(IMAGE_GEN_MODELS_ALIAS_MAP.values())
137 |
138 | VISION_MODEL_NAMES = list(VISION_MODEL_MAP.keys())
139 | VISION_MODEL_MODELS = list(VISION_MODEL_MAP.values())
140 |
141 | TTS_MODEL_NAMES = list(TTS_MODELS_MAP.keys())
142 | TTS_MODEL_MODELS = list(TTS_MODELS_MAP.values())
143 |
144 | APP_CHAT_PROFILE_CHAT = AppChatProfileModel(
145 | title=AppChatProfileType.CHAT.value,
146 | description="Multi-modal chat with LLM.",
147 | )
148 |
149 | APP_CHAT_PROFILE_ASSISTANT = AppChatProfileModel(
150 | title=AppChatProfileType.ASSISTANT.value,
151 | description="[Beta] Use Mino built-in Assistant to ask complex question. Currently support Math Calculator",
152 | )
153 |
154 | APP_CHAT_PROFILES = [APP_CHAT_PROFILE_CHAT, APP_CHAT_PROFILE_ASSISTANT]
155 |
156 | CHAT_PROFILES = [
157 | ChatProfile(name=profile.title, markdown_description=profile.description)
158 | for profile in APP_CHAT_PROFILES
159 | ]
160 |
--------------------------------------------------------------------------------
/src/router/layers.json:
--------------------------------------------------------------------------------
1 | {
2 | "encoder_type": "openai",
3 | "encoder_name": "text-embedding-3-small",
4 | "routes": [
5 | {
6 | "name": "text-processing",
7 | "utterances": [
8 | "Summarize this text for me",
9 | "Can you extract the key points from this passage?",
10 | "Provide a summary of this article",
11 | "Give me a concise overview of this document",
12 | "Distill the main ideas from this text",
13 | "Analyze the sentiment of this review",
14 | "What is the overall tone of this piece?",
15 | "Detect the emotion expressed in this message",
16 | "Classify the topic of this text",
17 | "Categorize this content into a specific domain",
18 | "Translate this text to Spanish",
19 | "Convert this document to French",
20 | "Rephrase this sentence in simpler terms",
21 | "Simplify this paragraph for easier reading",
22 | "Explain this technical jargon in plain language",
23 | "Find and correct any grammar errors in this text",
24 | "Check this writing for spelling mistakes",
25 | "Edit this document for style and clarity"
26 | ],
27 | "description": null,
28 | "function_schema": null,
29 | "llm": null,
30 | "score_threshold": 0.82
31 | },
32 | {
33 | "name": "vision-image-processing",
34 | "utterances": [
35 | "Explain this image",
36 | "Explain this image with url",
37 | "What's in this image?",
38 | "Describe the contents of this picture",
39 | "Tell me what you see in this image",
40 | "Identify the objects and elements in this scene",
41 | "What's happening in this visual?",
42 | "Give me a detailed description of this picture",
43 | "Analyze the components of this image for me",
44 | "Break down the different parts of this scene",
45 | "List the key features you notice in this image",
46 | "Explain what's depicted in this visual representation"
47 | ],
48 | "description": null,
49 | "function_schema": null,
50 | "llm": null,
51 | "score_threshold": 0.82
52 | },
53 | {
54 | "name": "casual-conversation",
55 | "utterances": [
56 | "How's your day going?",
57 | "Did you catch the game last night?",
58 | "I'm so ready for the weekend",
59 | "Any fun plans coming up?",
60 | "The weather has been gorgeous lately",
61 | "Have you tried that new restaurant downtown?",
62 | "I've been binge-watching this great new show",
63 | "I could use a vacation, how about you?",
64 | "My commute was a nightmare this morning",
65 | "I'm thinking of picking up a new hobby, any ideas?",
66 | "Did you hear about the latest celebrity gossip?",
67 | "I can't believe how quickly the time flies",
68 | "I'm already counting down to the holidays!",
69 | "You'll never guess what happened to me today",
70 | "Do you have any book recommendations?"
71 | ],
72 | "description": null,
73 | "function_schema": null,
74 | "llm": null,
75 | "score_threshold": 0.82
76 | },
77 | {
78 | "name": "image-generation",
79 | "utterances": [
80 | "please help me generate a photo of a car",
81 | "a image of a cute puppy playing in a meadow",
82 | "a image of a majestic mountain landscape at sunset",
83 | "a photo of a modern city skyline at night",
84 | "generate a image of a tropical beach with palm trees",
85 | "generate another image of a futuristic spacecraft",
86 | "a image of a cozy cabin in the woods during winter",
87 | "a photo of a vintage car from the 1950s",
88 | "generate a image of an underwater coral reef scene",
89 | "generate another image of a fantastical creature from mythology",
90 | "a image of a serene Japanese zen garden",
91 | "a photo of a delicious gourmet meal on a plate",
92 | "generate a image of a whimsical treehouse in a magical forest",
93 | "generate another image of an abstract painting with bold colors",
94 | "a image of a historic castle on a hilltop",
95 | "a photo of a beautiful flower bouquet in a vase",
96 | "generate a image of a sleek and modern sports car",
97 | "generate another image of a breathtaking northern lights display",
98 | "a image of a cozy reading nook with bookshelves",
99 | "a photo of a stunning desert landscape at sunset",
100 | "generate a image of a realistic portrait of a person",
101 | "a image of a playful kitten chasing a toy",
102 | "a photo of a golden retriever puppy with a wagging tail",
103 | "generate a image of a majestic lion in the savannah",
104 | "generate another image of a school of tropical fish swimming",
105 | "a image of a fluffy white rabbit in a garden",
106 | "a photo of a parrot with colorful feathers",
107 | "generate a image of a curious owl perched on a branch",
108 | "generate another image of a dolphin jumping out of the water",
109 | "a image of a tortoise basking in the sun",
110 | "a photo of a horse running through a grassy field",
111 | "a image of a cute kitten sleeping in a basket",
112 | "a photo of a playful dog chasing a frisbee in the park",
113 | "generate a image of a cat lounging on a sunny windowsill",
114 | "generate another image of a dog fetching a stick in the water",
115 | "a image of a kitten playing with a ball of yarn",
116 | "a photo of a dog begging for treats with puppy eyes",
117 | "generate a image of a cat stretching on a cozy couch",
118 | "generate another image of a dog running on the beach",
119 | "a image of a cat grooming itself with its paw",
120 | "a photo of a dog wearing a colorful bandana"
121 | ],
122 | "description": null,
123 | "function_schema": null,
124 | "llm": null,
125 | "score_threshold": 0.82
126 | },
127 | {
128 | "name": "curious",
129 | "utterances": [
130 | "Tell me something interesting",
131 | "What's something fascinating you've learned recently?",
132 | "Share an intriguing fact with me",
133 | "I'm curious to know more about...",
134 | "Enlighten me on the topic of...",
135 | "Can you explain the concept of...?",
136 | "I've always wondered about...",
137 | "How does ... work?",
138 | "What's the story behind...?",
139 | "I'm really interested in learning about...",
140 | "Teach me something new today",
141 | "Give me a fun trivia fact",
142 | "What's the most mind-blowing thing you know?",
143 | "Hit me with some knowledge I don't have yet",
144 | "I love learning new things, what can you tell me?"
145 | ],
146 | "description": null,
147 | "function_schema": null,
148 | "llm": null,
149 | "score_threshold": 0.82
150 | }
151 | ]
152 | }
--------------------------------------------------------------------------------
/src/router/trainer.py:
--------------------------------------------------------------------------------
1 | import os
2 | from getpass import getpass
3 |
4 | import dotenv
5 | from semantic_router import Route
6 | from semantic_router.encoders import FastEmbedEncoder, OpenAIEncoder
7 | from semantic_router.layer import RouteLayer
8 |
9 | dotenv.load_dotenv(dotenv.find_dotenv())
10 |
11 | # usage:
12 | # ```
13 | # $ python src/router/trainer.py
14 | # ```
15 | # then layers.json file will be updated
16 |
17 | # check for OpenAI API key, default default we will use GPT-3.5-turbo model
18 |
19 |
20 | def main() -> None:
21 | routes = [
22 | Route(
23 | name="text-processing",
24 | utterances=[
25 | "Summarize this text for me",
26 | "Can you extract the key points from this passage?",
27 | "Provide a summary of this article",
28 | "Give me a concise overview of this document",
29 | "Distill the main ideas from this text",
30 | "Analyze the sentiment of this review",
31 | "What is the overall tone of this piece?",
32 | "Detect the emotion expressed in this message",
33 | "Classify the topic of this text",
34 | "Categorize this content into a specific domain",
35 | "Translate this text to Spanish",
36 | "Convert this document to French",
37 | "Rephrase this sentence in simpler terms",
38 | "Simplify this paragraph for easier reading",
39 | "Explain this technical jargon in plain language",
40 | "Find and correct any grammar errors in this text",
41 | "Check this writing for spelling mistakes",
42 | "Edit this document for style and clarity",
43 | ],
44 | ),
45 | Route(
46 | name="vision-image-processing",
47 | utterances=[
48 | "Explain this image",
49 | "Explain this image with url",
50 | "What's in this image?",
51 | "Describe the contents of this picture",
52 | "Tell me what you see in this image",
53 | "Identify the objects and elements in this scene",
54 | "What's happening in this visual?",
55 | "Give me a detailed description of this picture",
56 | "Analyze the components of this image for me",
57 | "Break down the different parts of this scene",
58 | "List the key features you notice in this image",
59 | "Explain what's depicted in this visual representation",
60 | ],
61 | ),
62 | Route(
63 | name="casual-conversation",
64 | utterances=[
65 | "How's your day going?",
66 | "Did you catch the game last night?",
67 | "I'm so ready for the weekend",
68 | "Any fun plans coming up?",
69 | "The weather has been gorgeous lately",
70 | "Have you tried that new restaurant downtown?",
71 | "I've been binge-watching this great new show",
72 | "I could use a vacation, how about you?",
73 | "My commute was a nightmare this morning",
74 | "I'm thinking of picking up a new hobby, any ideas?",
75 | "Did you hear about the latest celebrity gossip?",
76 | "I can't believe how quickly the time flies",
77 | "I'm already counting down to the holidays!",
78 | "You'll never guess what happened to me today",
79 | "Do you have any book recommendations?",
80 | ],
81 | ),
82 | Route(
83 | name="image-generation",
84 | utterances=[
85 | "please help me generate a photo of a car",
86 | "a image of a cute puppy playing in a meadow",
87 | "a image of a majestic mountain landscape at sunset",
88 | "a photo of a modern city skyline at night",
89 | "generate a image of a tropical beach with palm trees",
90 | "generate another image of a futuristic spacecraft",
91 | "a image of a cozy cabin in the woods during winter",
92 | "a photo of a vintage car from the 1950s",
93 | "generate a image of an underwater coral reef scene",
94 | "generate another image of a fantastical creature from mythology",
95 | "a image of a serene Japanese zen garden",
96 | "a photo of a delicious gourmet meal on a plate",
97 | "generate a image of a whimsical treehouse in a magical forest",
98 | "generate another image of an abstract painting with bold colors",
99 | "a image of a historic castle on a hilltop",
100 | "a photo of a beautiful flower bouquet in a vase",
101 | "generate a image of a sleek and modern sports car",
102 | "generate another image of a breathtaking northern lights display",
103 | "a image of a cozy reading nook with bookshelves",
104 | "a photo of a stunning desert landscape at sunset",
105 | "generate a image of a realistic portrait of a person",
106 | "a image of a playful kitten chasing a toy",
107 | "a photo of a golden retriever puppy with a wagging tail",
108 | "generate a image of a majestic lion in the savannah",
109 | "generate another image of a school of tropical fish swimming",
110 | "a image of a fluffy white rabbit in a garden",
111 | "a photo of a parrot with colorful feathers",
112 | "generate a image of a curious owl perched on a branch",
113 | "generate another image of a dolphin jumping out of the water",
114 | "a image of a tortoise basking in the sun",
115 | "a photo of a horse running through a grassy field",
116 | "a image of a cute kitten sleeping in a basket",
117 | "a photo of a playful dog chasing a frisbee in the park",
118 | "generate a image of a cat lounging on a sunny windowsill",
119 | "generate another image of a dog fetching a stick in the water",
120 | "a image of a kitten playing with a ball of yarn",
121 | "a photo of a dog begging for treats with puppy eyes",
122 | "generate a image of a cat stretching on a cozy couch",
123 | "generate another image of a dog running on the beach",
124 | "a image of a cat grooming itself with its paw",
125 | "a photo of a dog wearing a colorful bandana",
126 | ],
127 | ),
128 | Route(
129 | name="curious",
130 | utterances=[
131 | "Tell me something interesting",
132 | "What's something fascinating you've learned recently?",
133 | "Share an intriguing fact with me",
134 | "I'm curious to know more about...",
135 | "Enlighten me on the topic of...",
136 | "Can you explain the concept of...?",
137 | "I've always wondered about...",
138 | "How does ... work?",
139 | "What's the story behind...?",
140 | "I'm really interested in learning about...",
141 | "Teach me something new today",
142 | "Give me a fun trivia fact",
143 | "What's the most mind-blowing thing you know?",
144 | "Hit me with some knowledge I don't have yet",
145 | "I love learning new things, what can you tell me?",
146 | ],
147 | ),
148 | ]
149 |
150 | encoder = OpenAIEncoder(name="text-embedding-3-small")
151 | # FastEmbedEncoder()
152 | # OpenAIEncoder(name="text-embedding-3-small")
153 |
154 | if encoder is OpenAIEncoder:
155 | os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY") or getpass(
156 | "Enter OpenAI API Key: "
157 | )
158 |
159 | layer = RouteLayer(encoder=encoder, routes=routes)
160 | layer.to_json("./src/router/layers.json")
161 |
162 |
163 | if __name__ == "__main__":
164 | main()
165 |
--------------------------------------------------------------------------------
/requirements.lock:
--------------------------------------------------------------------------------
1 | # generated by rye
2 | # use `rye lock` or `rye sync` to update this lockfile
3 | #
4 | # last locked with the following flags:
5 | # pre: false
6 | # features: []
7 | # all-features: false
8 | # with-sources: false
9 |
10 | -e file:.
11 | aiofiles==23.2.1
12 | # via chainlit
13 | aiohttp==3.9.5
14 | # via cohere
15 | # via litellm
16 | # via python-graphql-client
17 | aiosignal==1.3.1
18 | # via aiohttp
19 | annotated-types==0.6.0
20 | # via pydantic
21 | anyio==3.7.1
22 | # via asyncer
23 | # via httpx
24 | # via openai
25 | # via starlette
26 | # via watchfiles
27 | async-timeout==4.0.3
28 | # via aiohttp
29 | asyncer==0.0.2
30 | # via chainlit
31 | attrs==23.2.0
32 | # via aiohttp
33 | backoff==2.2.1
34 | # via cohere
35 | bidict==0.23.1
36 | # via python-socketio
37 | black==23.12.1
38 | # via semantic-router
39 | cachetools==5.3.3
40 | # via google-auth
41 | certifi==2024.2.2
42 | # via httpcore
43 | # via httpx
44 | # via requests
45 | chainlit==1.0.504
46 | # via vtai
47 | charset-normalizer==3.3.2
48 | # via requests
49 | chevron==0.14.0
50 | # via literalai
51 | click==8.1.7
52 | # via black
53 | # via chainlit
54 | # via litellm
55 | # via uvicorn
56 | cohere==4.57
57 | # via semantic-router
58 | coloredlogs==15.0.1
59 | # via onnxruntime
60 | colorlog==6.8.2
61 | # via semantic-router
62 | dataclasses-json==0.5.14
63 | # via chainlit
64 | deprecated==1.2.14
65 | # via opentelemetry-api
66 | # via opentelemetry-exporter-otlp-proto-grpc
67 | # via opentelemetry-exporter-otlp-proto-http
68 | distro==1.9.0
69 | # via openai
70 | exceptiongroup==1.2.1
71 | # via anyio
72 | fastapi==0.110.2
73 | # via chainlit
74 | # via fastapi-socketio
75 | fastapi-socketio==0.0.10
76 | # via chainlit
77 | fastavro==1.9.4
78 | # via cohere
79 | fastembed==0.1.3
80 | # via semantic-router
81 | filelock==3.13.4
82 | # via huggingface-hub
83 | filetype==1.2.0
84 | # via chainlit
85 | flatbuffers==24.3.25
86 | # via onnxruntime
87 | frozenlist==1.4.1
88 | # via aiohttp
89 | # via aiosignal
90 | fsspec==2024.3.1
91 | # via huggingface-hub
92 | google-ai-generativelanguage==0.6.2
93 | # via google-generativeai
94 | google-api-core==2.18.0
95 | # via google-ai-generativelanguage
96 | # via google-api-python-client
97 | # via google-generativeai
98 | google-api-python-client==2.126.0
99 | # via google-generativeai
100 | google-auth==2.29.0
101 | # via google-ai-generativelanguage
102 | # via google-api-core
103 | # via google-api-python-client
104 | # via google-auth-httplib2
105 | # via google-generativeai
106 | google-auth-httplib2==0.2.0
107 | # via google-api-python-client
108 | google-generativeai==0.5.2
109 | # via vtai
110 | googleapis-common-protos==1.63.0
111 | # via google-api-core
112 | # via grpcio-status
113 | # via opentelemetry-exporter-otlp-proto-grpc
114 | # via opentelemetry-exporter-otlp-proto-http
115 | grpcio==1.62.2
116 | # via google-api-core
117 | # via grpcio-status
118 | # via opentelemetry-exporter-otlp-proto-grpc
119 | grpcio-status==1.62.2
120 | # via google-api-core
121 | h11==0.14.0
122 | # via httpcore
123 | # via uvicorn
124 | # via wsproto
125 | httpcore==1.0.5
126 | # via httpx
127 | httplib2==0.22.0
128 | # via google-api-python-client
129 | # via google-auth-httplib2
130 | httpx==0.27.0
131 | # via chainlit
132 | # via literalai
133 | # via openai
134 | huggingface-hub==0.19.4
135 | # via fastembed
136 | # via tokenizers
137 | humanfriendly==10.0
138 | # via coloredlogs
139 | idna==3.7
140 | # via anyio
141 | # via httpx
142 | # via requests
143 | # via yarl
144 | importlib-metadata==6.11.0
145 | # via cohere
146 | # via litellm
147 | # via opentelemetry-api
148 | jinja2==3.1.3
149 | # via litellm
150 | lazify==0.4.0
151 | # via chainlit
152 | litellm==1.35.17
153 | # via vtai
154 | literalai==0.0.504
155 | # via chainlit
156 | markupsafe==2.1.5
157 | # via jinja2
158 | marshmallow==3.21.1
159 | # via dataclasses-json
160 | mpmath==1.3.0
161 | # via sympy
162 | multidict==6.0.5
163 | # via aiohttp
164 | # via yarl
165 | mypy-extensions==1.0.0
166 | # via black
167 | # via typing-inspect
168 | nest-asyncio==1.6.0
169 | # via chainlit
170 | numpy==1.26.4
171 | # via onnx
172 | # via onnxruntime
173 | # via semantic-router
174 | onnx==1.16.0
175 | # via fastembed
176 | onnxruntime==1.17.3
177 | # via fastembed
178 | openai==1.23.2
179 | # via litellm
180 | # via semantic-router
181 | # via vtai
182 | opentelemetry-api==1.24.0
183 | # via opentelemetry-exporter-otlp-proto-grpc
184 | # via opentelemetry-exporter-otlp-proto-http
185 | # via opentelemetry-instrumentation
186 | # via opentelemetry-sdk
187 | # via uptrace
188 | opentelemetry-exporter-otlp==1.24.0
189 | # via uptrace
190 | opentelemetry-exporter-otlp-proto-common==1.24.0
191 | # via opentelemetry-exporter-otlp-proto-grpc
192 | # via opentelemetry-exporter-otlp-proto-http
193 | opentelemetry-exporter-otlp-proto-grpc==1.24.0
194 | # via opentelemetry-exporter-otlp
195 | opentelemetry-exporter-otlp-proto-http==1.24.0
196 | # via opentelemetry-exporter-otlp
197 | opentelemetry-instrumentation==0.45b0
198 | # via uptrace
199 | opentelemetry-proto==1.24.0
200 | # via opentelemetry-exporter-otlp-proto-common
201 | # via opentelemetry-exporter-otlp-proto-grpc
202 | # via opentelemetry-exporter-otlp-proto-http
203 | opentelemetry-sdk==1.24.0
204 | # via opentelemetry-exporter-otlp-proto-grpc
205 | # via opentelemetry-exporter-otlp-proto-http
206 | # via uptrace
207 | opentelemetry-semantic-conventions==0.45b0
208 | # via opentelemetry-sdk
209 | packaging==23.2
210 | # via black
211 | # via chainlit
212 | # via huggingface-hub
213 | # via literalai
214 | # via marshmallow
215 | # via onnxruntime
216 | pathspec==0.12.1
217 | # via black
218 | pillow==10.3.0
219 | # via vtai
220 | platformdirs==4.2.0
221 | # via black
222 | proto-plus==1.23.0
223 | # via google-ai-generativelanguage
224 | # via google-api-core
225 | protobuf==4.25.3
226 | # via google-ai-generativelanguage
227 | # via google-api-core
228 | # via google-generativeai
229 | # via googleapis-common-protos
230 | # via grpcio-status
231 | # via onnx
232 | # via onnxruntime
233 | # via opentelemetry-proto
234 | # via proto-plus
235 | pyasn1==0.6.0
236 | # via pyasn1-modules
237 | # via rsa
238 | pyasn1-modules==0.4.0
239 | # via google-auth
240 | pydantic==2.7.1
241 | # via chainlit
242 | # via fastapi
243 | # via google-generativeai
244 | # via literalai
245 | # via openai
246 | # via semantic-router
247 | # via vtai
248 | pydantic-core==2.18.2
249 | # via pydantic
250 | pyjwt==2.8.0
251 | # via chainlit
252 | pyparsing==3.1.2
253 | # via httplib2
254 | python-dotenv==1.0.1
255 | # via chainlit
256 | # via litellm
257 | # via vtai
258 | python-engineio==4.9.0
259 | # via python-socketio
260 | python-graphql-client==0.4.3
261 | # via chainlit
262 | python-multipart==0.0.9
263 | # via chainlit
264 | python-socketio==5.11.2
265 | # via fastapi-socketio
266 | pyyaml==6.0.1
267 | # via huggingface-hub
268 | # via semantic-router
269 | regex==2023.12.25
270 | # via tiktoken
271 | requests==2.31.0
272 | # via cohere
273 | # via fastembed
274 | # via google-api-core
275 | # via huggingface-hub
276 | # via litellm
277 | # via opentelemetry-exporter-otlp-proto-http
278 | # via python-graphql-client
279 | # via tiktoken
280 | rsa==4.9
281 | # via google-auth
282 | semantic-router==0.0.20
283 | # via vtai
284 | setuptools==69.5.1
285 | # via opentelemetry-instrumentation
286 | simple-websocket==1.0.0
287 | # via python-engineio
288 | sniffio==1.3.1
289 | # via anyio
290 | # via httpx
291 | # via openai
292 | starlette==0.37.2
293 | # via chainlit
294 | # via fastapi
295 | sympy==1.12
296 | # via onnxruntime
297 | syncer==2.0.3
298 | # via chainlit
299 | tiktoken==0.6.0
300 | # via litellm
301 | tokenizers==0.15.2
302 | # via fastembed
303 | # via litellm
304 | tomli==2.0.1
305 | # via black
306 | # via chainlit
307 | tqdm==4.66.2
308 | # via fastembed
309 | # via google-generativeai
310 | # via huggingface-hub
311 | # via openai
312 | typing-extensions==4.11.0
313 | # via black
314 | # via fastapi
315 | # via google-generativeai
316 | # via huggingface-hub
317 | # via openai
318 | # via opentelemetry-sdk
319 | # via pydantic
320 | # via pydantic-core
321 | # via typing-inspect
322 | # via uvicorn
323 | typing-inspect==0.9.0
324 | # via dataclasses-json
325 | uptrace==1.24.0
326 | # via chainlit
327 | uritemplate==4.1.1
328 | # via google-api-python-client
329 | urllib3==2.2.1
330 | # via cohere
331 | # via requests
332 | uvicorn==0.25.0
333 | # via chainlit
334 | watchfiles==0.20.0
335 | # via chainlit
336 | websockets==12.0
337 | # via python-graphql-client
338 | wrapt==1.16.0
339 | # via deprecated
340 | # via opentelemetry-instrumentation
341 | wsproto==1.2.0
342 | # via simple-websocket
343 | yarl==1.9.4
344 | # via aiohttp
345 | zipp==3.18.1
346 | # via importlib-metadata
347 |
--------------------------------------------------------------------------------
/requirements-dev.lock:
--------------------------------------------------------------------------------
1 | # generated by rye
2 | # use `rye lock` or `rye sync` to update this lockfile
3 | #
4 | # last locked with the following flags:
5 | # pre: false
6 | # features: []
7 | # all-features: false
8 | # with-sources: false
9 |
10 | -e file:.
11 | aiofiles==23.2.1
12 | # via chainlit
13 | aiohttp==3.9.5
14 | # via cohere
15 | # via litellm
16 | # via python-graphql-client
17 | aiosignal==1.3.1
18 | # via aiohttp
19 | annotated-types==0.6.0
20 | # via pydantic
21 | anyio==3.7.1
22 | # via asyncer
23 | # via httpx
24 | # via openai
25 | # via starlette
26 | # via watchfiles
27 | async-timeout==4.0.3
28 | # via aiohttp
29 | asyncer==0.0.2
30 | # via chainlit
31 | attrs==23.2.0
32 | # via aiohttp
33 | backoff==2.2.1
34 | # via cohere
35 | bidict==0.23.1
36 | # via python-socketio
37 | black==23.12.1
38 | # via semantic-router
39 | cachetools==5.3.3
40 | # via google-auth
41 | certifi==2024.2.2
42 | # via httpcore
43 | # via httpx
44 | # via requests
45 | chainlit==1.0.504
46 | # via vtai
47 | charset-normalizer==3.3.2
48 | # via requests
49 | chevron==0.14.0
50 | # via literalai
51 | click==8.1.7
52 | # via black
53 | # via chainlit
54 | # via litellm
55 | # via uvicorn
56 | cohere==4.57
57 | # via semantic-router
58 | coloredlogs==15.0.1
59 | # via onnxruntime
60 | colorlog==6.8.2
61 | # via semantic-router
62 | dataclasses-json==0.5.14
63 | # via chainlit
64 | deprecated==1.2.14
65 | # via opentelemetry-api
66 | # via opentelemetry-exporter-otlp-proto-grpc
67 | # via opentelemetry-exporter-otlp-proto-http
68 | distro==1.9.0
69 | # via openai
70 | exceptiongroup==1.2.1
71 | # via anyio
72 | fastapi==0.110.2
73 | # via chainlit
74 | # via fastapi-socketio
75 | fastapi-socketio==0.0.10
76 | # via chainlit
77 | fastavro==1.9.4
78 | # via cohere
79 | fastembed==0.1.3
80 | # via semantic-router
81 | filelock==3.13.4
82 | # via huggingface-hub
83 | filetype==1.2.0
84 | # via chainlit
85 | flatbuffers==24.3.25
86 | # via onnxruntime
87 | frozenlist==1.4.1
88 | # via aiohttp
89 | # via aiosignal
90 | fsspec==2024.3.1
91 | # via huggingface-hub
92 | google-ai-generativelanguage==0.6.2
93 | # via google-generativeai
94 | google-api-core==2.18.0
95 | # via google-ai-generativelanguage
96 | # via google-api-python-client
97 | # via google-generativeai
98 | google-api-python-client==2.126.0
99 | # via google-generativeai
100 | google-auth==2.29.0
101 | # via google-ai-generativelanguage
102 | # via google-api-core
103 | # via google-api-python-client
104 | # via google-auth-httplib2
105 | # via google-generativeai
106 | google-auth-httplib2==0.2.0
107 | # via google-api-python-client
108 | google-generativeai==0.5.2
109 | # via vtai
110 | googleapis-common-protos==1.63.0
111 | # via google-api-core
112 | # via grpcio-status
113 | # via opentelemetry-exporter-otlp-proto-grpc
114 | # via opentelemetry-exporter-otlp-proto-http
115 | grpcio==1.62.2
116 | # via google-api-core
117 | # via grpcio-status
118 | # via opentelemetry-exporter-otlp-proto-grpc
119 | grpcio-status==1.62.2
120 | # via google-api-core
121 | h11==0.14.0
122 | # via httpcore
123 | # via uvicorn
124 | # via wsproto
125 | httpcore==1.0.5
126 | # via httpx
127 | httplib2==0.22.0
128 | # via google-api-python-client
129 | # via google-auth-httplib2
130 | httpx==0.27.0
131 | # via chainlit
132 | # via literalai
133 | # via openai
134 | huggingface-hub==0.19.4
135 | # via fastembed
136 | # via tokenizers
137 | humanfriendly==10.0
138 | # via coloredlogs
139 | idna==3.7
140 | # via anyio
141 | # via httpx
142 | # via requests
143 | # via yarl
144 | importlib-metadata==6.11.0
145 | # via cohere
146 | # via litellm
147 | # via opentelemetry-api
148 | jinja2==3.1.3
149 | # via litellm
150 | lazify==0.4.0
151 | # via chainlit
152 | litellm==1.35.17
153 | # via vtai
154 | literalai==0.0.504
155 | # via chainlit
156 | markupsafe==2.1.5
157 | # via jinja2
158 | marshmallow==3.21.1
159 | # via dataclasses-json
160 | mpmath==1.3.0
161 | # via sympy
162 | multidict==6.0.5
163 | # via aiohttp
164 | # via yarl
165 | mypy-extensions==1.0.0
166 | # via black
167 | # via typing-inspect
168 | nest-asyncio==1.6.0
169 | # via chainlit
170 | numpy==1.26.4
171 | # via onnx
172 | # via onnxruntime
173 | # via semantic-router
174 | onnx==1.16.0
175 | # via fastembed
176 | onnxruntime==1.17.3
177 | # via fastembed
178 | openai==1.23.2
179 | # via litellm
180 | # via semantic-router
181 | # via vtai
182 | opentelemetry-api==1.24.0
183 | # via opentelemetry-exporter-otlp-proto-grpc
184 | # via opentelemetry-exporter-otlp-proto-http
185 | # via opentelemetry-instrumentation
186 | # via opentelemetry-sdk
187 | # via uptrace
188 | opentelemetry-exporter-otlp==1.24.0
189 | # via uptrace
190 | opentelemetry-exporter-otlp-proto-common==1.24.0
191 | # via opentelemetry-exporter-otlp-proto-grpc
192 | # via opentelemetry-exporter-otlp-proto-http
193 | opentelemetry-exporter-otlp-proto-grpc==1.24.0
194 | # via opentelemetry-exporter-otlp
195 | opentelemetry-exporter-otlp-proto-http==1.24.0
196 | # via opentelemetry-exporter-otlp
197 | opentelemetry-instrumentation==0.45b0
198 | # via uptrace
199 | opentelemetry-proto==1.24.0
200 | # via opentelemetry-exporter-otlp-proto-common
201 | # via opentelemetry-exporter-otlp-proto-grpc
202 | # via opentelemetry-exporter-otlp-proto-http
203 | opentelemetry-sdk==1.24.0
204 | # via opentelemetry-exporter-otlp-proto-grpc
205 | # via opentelemetry-exporter-otlp-proto-http
206 | # via uptrace
207 | opentelemetry-semantic-conventions==0.45b0
208 | # via opentelemetry-sdk
209 | packaging==23.2
210 | # via black
211 | # via chainlit
212 | # via huggingface-hub
213 | # via literalai
214 | # via marshmallow
215 | # via onnxruntime
216 | pathspec==0.12.1
217 | # via black
218 | pillow==10.3.0
219 | # via vtai
220 | platformdirs==4.2.0
221 | # via black
222 | proto-plus==1.23.0
223 | # via google-ai-generativelanguage
224 | # via google-api-core
225 | protobuf==4.25.3
226 | # via google-ai-generativelanguage
227 | # via google-api-core
228 | # via google-generativeai
229 | # via googleapis-common-protos
230 | # via grpcio-status
231 | # via onnx
232 | # via onnxruntime
233 | # via opentelemetry-proto
234 | # via proto-plus
235 | pyasn1==0.6.0
236 | # via pyasn1-modules
237 | # via rsa
238 | pyasn1-modules==0.4.0
239 | # via google-auth
240 | pydantic==2.7.1
241 | # via chainlit
242 | # via fastapi
243 | # via google-generativeai
244 | # via literalai
245 | # via openai
246 | # via semantic-router
247 | # via vtai
248 | pydantic-core==2.18.2
249 | # via pydantic
250 | pyjwt==2.8.0
251 | # via chainlit
252 | pyparsing==3.1.2
253 | # via httplib2
254 | python-dotenv==1.0.1
255 | # via chainlit
256 | # via litellm
257 | # via vtai
258 | python-engineio==4.9.0
259 | # via python-socketio
260 | python-graphql-client==0.4.3
261 | # via chainlit
262 | python-multipart==0.0.9
263 | # via chainlit
264 | python-socketio==5.11.2
265 | # via fastapi-socketio
266 | pyyaml==6.0.1
267 | # via huggingface-hub
268 | # via semantic-router
269 | regex==2023.12.25
270 | # via tiktoken
271 | requests==2.31.0
272 | # via cohere
273 | # via fastembed
274 | # via google-api-core
275 | # via huggingface-hub
276 | # via litellm
277 | # via opentelemetry-exporter-otlp-proto-http
278 | # via python-graphql-client
279 | # via tiktoken
280 | rsa==4.9
281 | # via google-auth
282 | semantic-router==0.0.20
283 | # via vtai
284 | setuptools==69.5.1
285 | # via opentelemetry-instrumentation
286 | simple-websocket==1.0.0
287 | # via python-engineio
288 | sniffio==1.3.1
289 | # via anyio
290 | # via httpx
291 | # via openai
292 | starlette==0.37.2
293 | # via chainlit
294 | # via fastapi
295 | sympy==1.12
296 | # via onnxruntime
297 | syncer==2.0.3
298 | # via chainlit
299 | tiktoken==0.6.0
300 | # via litellm
301 | tokenizers==0.15.2
302 | # via fastembed
303 | # via litellm
304 | tomli==2.0.1
305 | # via black
306 | # via chainlit
307 | tqdm==4.66.2
308 | # via fastembed
309 | # via google-generativeai
310 | # via huggingface-hub
311 | # via openai
312 | typing-extensions==4.11.0
313 | # via black
314 | # via fastapi
315 | # via google-generativeai
316 | # via huggingface-hub
317 | # via openai
318 | # via opentelemetry-sdk
319 | # via pydantic
320 | # via pydantic-core
321 | # via typing-inspect
322 | # via uvicorn
323 | typing-inspect==0.9.0
324 | # via dataclasses-json
325 | uptrace==1.24.0
326 | # via chainlit
327 | uritemplate==4.1.1
328 | # via google-api-python-client
329 | urllib3==2.2.1
330 | # via cohere
331 | # via requests
332 | uvicorn==0.25.0
333 | # via chainlit
334 | watchfiles==0.20.0
335 | # via chainlit
336 | websockets==12.0
337 | # via python-graphql-client
338 | wrapt==1.16.0
339 | # via deprecated
340 | # via opentelemetry-instrumentation
341 | wsproto==1.2.0
342 | # via simple-websocket
343 | yarl==1.9.4
344 | # via aiohttp
345 | zipp==3.18.1
346 | # via importlib-metadata
347 |
--------------------------------------------------------------------------------
/.chainlit/translations/en-US.json:
--------------------------------------------------------------------------------
1 | {
2 | "components": {
3 | "atoms": {
4 | "buttons": {
5 | "userButton": {
6 | "menu": {
7 | "settings": "Settings",
8 | "settingsKey": "S",
9 | "APIKeys": "API Keys",
10 | "logout": "Logout"
11 | }
12 | }
13 | }
14 | },
15 | "molecules": {
16 | "newChatButton": {
17 | "newChat": "New Chat"
18 | },
19 | "tasklist": {
20 | "TaskList": {
21 | "title": "\ud83d\uddd2\ufe0f Task List",
22 | "loading": "Loading...",
23 | "error": "An error occured"
24 | }
25 | },
26 | "attachments": {
27 | "cancelUpload": "Cancel upload",
28 | "removeAttachment": "Remove attachment"
29 | },
30 | "newChatDialog": {
31 | "createNewChat": "Create new chat?",
32 | "clearChat": "This will clear the current messages and start a new chat.",
33 | "cancel": "Cancel",
34 | "confirm": "Confirm"
35 | },
36 | "settingsModal": {
37 | "settings": "Settings",
38 | "expandMessages": "Expand Messages",
39 | "hideChainOfThought": "Hide Chain of Thought",
40 | "darkMode": "Dark Mode"
41 | },
42 | "detailsButton": {
43 | "using": "Using",
44 | "running": "Running",
45 | "took_one": "Took {{count}} step",
46 | "took_other": "Took {{count}} steps"
47 | },
48 | "auth": {
49 | "authLogin": {
50 | "title": "Login to access the app.",
51 | "form": {
52 | "email": "Email address",
53 | "password": "Password",
54 | "noAccount": "Don't have an account?",
55 | "alreadyHaveAccount": "Already have an account?",
56 | "signup": "Sign Up",
57 | "signin": "Sign In",
58 | "or": "OR",
59 | "continue": "Continue",
60 | "forgotPassword": "Forgot password?",
61 | "passwordMustContain": "Your password must contain:",
62 | "emailRequired": "email is a required field",
63 | "passwordRequired": "password is a required field"
64 | },
65 | "error": {
66 | "default": "Unable to sign in.",
67 | "signin": "Try signing in with a different account.",
68 | "oauthsignin": "Try signing in with a different account.",
69 | "redirect_uri_mismatch": "The redirect URI is not matching the oauth app configuration.",
70 | "oauthcallbackerror": "Try signing in with a different account.",
71 | "oauthcreateaccount": "Try signing in with a different account.",
72 | "emailcreateaccount": "Try signing in with a different account.",
73 | "callback": "Try signing in with a different account.",
74 | "oauthaccountnotlinked": "To confirm your identity, sign in with the same account you used originally.",
75 | "emailsignin": "The e-mail could not be sent.",
76 | "emailverify": "Please verify your email, a new email has been sent.",
77 | "credentialssignin": "Sign in failed. Check the details you provided are correct.",
78 | "sessionrequired": "Please sign in to access this page."
79 | }
80 | },
81 | "authVerifyEmail": {
82 | "almostThere": "You're almost there! We've sent an email to ",
83 | "verifyEmailLink": "Please click on the link in that email to complete your signup.",
84 | "didNotReceive": "Can't find the email?",
85 | "resendEmail": "Resend email",
86 | "goBack": "Go Back",
87 | "emailSent": "Email sent successfully.",
88 | "verifyEmail": "Verify your email address"
89 | },
90 | "providerButton": {
91 | "continue": "Continue with {{provider}}",
92 | "signup": "Sign up with {{provider}}"
93 | },
94 | "authResetPassword": {
95 | "newPasswordRequired": "New password is a required field",
96 | "passwordsMustMatch": "Passwords must match",
97 | "confirmPasswordRequired": "Confirm password is a required field",
98 | "newPassword": "New password",
99 | "confirmPassword": "Confirm password",
100 | "resetPassword": "Reset Password"
101 | },
102 | "authForgotPassword": {
103 | "email": "Email address",
104 | "emailRequired": "email is a required field",
105 | "emailSent": "Please check the email address {{email}} for instructions to reset your password.",
106 | "enterEmail": "Enter your email address and we will send you instructions to reset your password.",
107 | "resendEmail": "Resend email",
108 | "continue": "Continue",
109 | "goBack": "Go Back"
110 | }
111 | }
112 | },
113 | "organisms": {
114 | "chat": {
115 | "history": {
116 | "index": {
117 | "showHistory": "Show history",
118 | "lastInputs": "Last Inputs",
119 | "noInputs": "Such empty...",
120 | "loading": "Loading..."
121 | }
122 | },
123 | "inputBox": {
124 | "input": {
125 | "placeholder": "Type your message here..."
126 | },
127 | "speechButton": {
128 | "start": "Start recording",
129 | "stop": "Stop recording"
130 | },
131 | "SubmitButton": {
132 | "sendMessage": "Send message",
133 | "stopTask": "Stop Task"
134 | },
135 | "UploadButton": {
136 | "attachFiles": "Attach files"
137 | },
138 | "waterMark": {
139 | "text": "Built with"
140 | }
141 | },
142 | "Messages": {
143 | "index": {
144 | "running": "Running",
145 | "executedSuccessfully": "executed successfully",
146 | "failed": "failed",
147 | "feedbackUpdated": "Feedback updated",
148 | "updating": "Updating"
149 | }
150 | },
151 | "dropScreen": {
152 | "dropYourFilesHere": "Drop your files here"
153 | },
154 | "index": {
155 | "failedToUpload": "Failed to upload",
156 | "cancelledUploadOf": "Cancelled upload of",
157 | "couldNotReachServer": "Could not reach the server",
158 | "continuingChat": "Continuing previous chat"
159 | },
160 | "settings": {
161 | "settingsPanel": "Settings panel",
162 | "reset": "Reset",
163 | "cancel": "Cancel",
164 | "confirm": "Confirm"
165 | }
166 | },
167 | "threadHistory": {
168 | "sidebar": {
169 | "filters": {
170 | "FeedbackSelect": {
171 | "feedbackAll": "Feedback: All",
172 | "feedbackPositive": "Feedback: Positive",
173 | "feedbackNegative": "Feedback: Negative"
174 | },
175 | "SearchBar": {
176 | "search": "Search"
177 | }
178 | },
179 | "DeleteThreadButton": {
180 | "confirmMessage": "This will delete the thread as well as it's messages and elements.",
181 | "cancel": "Cancel",
182 | "confirm": "Confirm",
183 | "deletingChat": "Deleting chat",
184 | "chatDeleted": "Chat deleted"
185 | },
186 | "index": {
187 | "pastChats": "Past Chats"
188 | },
189 | "ThreadList": {
190 | "empty": "Empty...",
191 | "today": "Today",
192 | "yesterday": "Yesterday",
193 | "previous7days": "Previous 7 days",
194 | "previous30days": "Previous 30 days"
195 | },
196 | "TriggerButton": {
197 | "closeSidebar": "Close sidebar",
198 | "openSidebar": "Open sidebar"
199 | }
200 | },
201 | "Thread": {
202 | "backToChat": "Go back to chat",
203 | "chatCreatedOn": "This chat was created on"
204 | }
205 | },
206 | "header": {
207 | "chat": "Chat",
208 | "readme": "Readme"
209 | }
210 | }
211 | },
212 | "hooks": {
213 | "useLLMProviders": {
214 | "failedToFetchProviders": "Failed to fetch providers:"
215 | }
216 | },
217 | "pages": {
218 | "Design": {},
219 | "Env": {
220 | "savedSuccessfully": "Saved successfully",
221 | "requiredApiKeys": "Required API Keys",
222 | "requiredApiKeysInfo": "To use this app, the following API keys are required. The keys are stored on your device's local storage."
223 | },
224 | "Page": {
225 | "notPartOfProject": "You are not part of this project."
226 | },
227 | "ResumeButton": {
228 | "resumeChat": "Resume Chat"
229 | }
230 | }
231 | }
--------------------------------------------------------------------------------
/src/app.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pathlib
3 | import tempfile
4 | from datetime import datetime
5 | from getpass import getpass
6 | from pathlib import Path
7 | from typing import Any, Dict, List
8 |
9 | import chainlit as cl
10 | import dotenv
11 | import litellm
12 | import utils.constants as const
13 | from assistants.mino.create_assistant import tool_map
14 | from assistants.mino.mino import INSTRUCTIONS, MinoAssistant
15 | from chainlit.element import Element
16 | from litellm.utils import trim_messages
17 | from openai import AsyncOpenAI, OpenAI
18 | from openai.types.beta.threads import (
19 | ImageFileContentBlock,
20 | Message,
21 | TextContentBlock,
22 | )
23 | from openai.types.beta.threads.runs import RunStep
24 | from openai.types.beta.threads.runs.tool_calls_step_details import ToolCall
25 | from router.constants import SemanticRouterType
26 | from semantic_router.layer import RouteLayer
27 | from utils import llm_settings_config as conf
28 | from utils.chat_profile import AppChatProfileType
29 | from utils.dict_to_object import DictToObject
30 |
31 | from utils.llm_profile_builder import build_llm_profile
32 | from utils.settings_builder import build_settings
33 | from utils.url_extractor import extract_url
34 |
35 | # Load .env
36 | dotenv.load_dotenv(dotenv.find_dotenv())
37 |
38 | # Model alias map for litellm
39 | litellm.model_alias_map = conf.MODEL_ALIAS_MAP
40 |
41 | # Load semantic router layer from JSON file
42 | route_layer = RouteLayer.from_json("./src/router/layers.json")
43 |
44 | # Create temporary directory for TTS audio files
45 | temp_dir = tempfile.TemporaryDirectory()
46 |
47 | # Set LLM Providers API Keys from environment variable or user input
48 | # OpenAI - API Key
49 | os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY") or getpass(
50 | "Enter OpenAI API Key: "
51 | )
52 | # OpenAI - Organization ID - track expense
53 | # os.environ["OPENAI_ORGANIZATION"] = os.getenv("OPENAI_ORGANIZATION") or getpass(
54 | # "(Optional) Enter OpenAI Orginazation ID, for billing management. You can skip this, by pressing the Return key..."
55 | # ) # OPTIONAL
56 |
57 | # os.environ["ASSISTANT_ID"] = os.getenv("ASSISTANT_ID") or getpass(
58 | # "(Optional) Enter pre-defined OpenAI Assistant ID, this is used for assistant conversation thread. You can skip this."
59 | # ) # OPTIONAL
60 |
61 | os.environ["GEMINI_API_KEY"] = os.getenv("GEMINI_API_KEY") or getpass(
62 | "Enter Google Gemini API Key, this is used for Vision capability. You can skip this, by pressing the Return key..."
63 | )
64 |
65 |
66 | assistant_id = os.environ.get("ASSISTANT_ID")
67 |
68 | # List of allowed mime types
69 | allowed_mime = ["text/csv", "application/pdf"]
70 |
71 |
72 | # Initialize OpenAI client
73 | openai_client = OpenAI(max_retries=2)
74 | async_openai_client = AsyncOpenAI(max_retries=2)
75 |
76 |
77 | # constants
78 | APP_NAME = const.APP_NAME
79 |
80 | # NOTE: 💡 Check ./TODO file for TODO list
81 |
82 |
83 | @cl.set_chat_profiles
84 | async def build_chat_profile():
85 | return conf.CHAT_PROFILES
86 |
87 |
88 | @cl.on_chat_start
89 | async def start_chat():
90 | """
91 | Initializes the chat session.
92 | Builds LLM profiles, configures chat settings, and sets initial system message.
93 | """
94 | # Initialize default settings
95 | cl.user_session.set(
96 | conf.SETTINGS_CHAT_MODEL, "default_model_name"
97 | ) # Set your default model
98 |
99 | # build llm profile
100 | build_llm_profile(conf.ICONS_PROVIDER_MAP)
101 |
102 | # settings configuration
103 | settings = await build_settings()
104 |
105 | # set selected LLM model for current settion's model
106 | await __config_chat_session(settings)
107 |
108 | if _is_currently_in_assistant_profile():
109 | thread = await async_openai_client.beta.threads.create()
110 | cl.user_session.set("thread", thread)
111 |
112 |
113 | @cl.step(name=APP_NAME, type="run")
114 | async def run(thread_id: str, human_query: str, file_ids: List[str] = []):
115 | # Add the message to the thread
116 | init_message = await async_openai_client.beta.threads.messages.create(
117 | thread_id=thread_id,
118 | role="user",
119 | content=human_query,
120 | )
121 |
122 | # Create the run
123 | if assistant_id is None or len(assistant_id) == 0:
124 | mino = MinoAssistant(openai_client=async_openai_client)
125 | assistant = await mino.run_assistant()
126 | run = await async_openai_client.beta.threads.runs.create(
127 | thread_id=thread_id,
128 | assistant_id=assistant.id,
129 | )
130 | else:
131 | run = await async_openai_client.beta.threads.runs.create(
132 | thread_id=thread_id,
133 | assistant_id=assistant_id,
134 | )
135 |
136 | message_references = {} # type: Dict[str, cl.Message]
137 | step_references = {} # type: Dict[str, cl.Step]
138 | tool_outputs = []
139 | # Periodically check for updates
140 | while True:
141 | run = await async_openai_client.beta.threads.runs.retrieve(
142 | thread_id=thread_id, run_id=run.id
143 | )
144 |
145 | # Fetch the run steps
146 | run_steps = await async_openai_client.beta.threads.runs.steps.list(
147 | thread_id=thread_id, run_id=run.id, order="asc"
148 | )
149 |
150 | for step in run_steps.data:
151 | # Fetch step details
152 | run_step = await async_openai_client.beta.threads.runs.steps.retrieve(
153 | thread_id=thread_id, run_id=run.id, step_id=step.id
154 | )
155 | step_details = run_step.step_details
156 | # Update step content in the Chainlit UI
157 | if step_details.type == "message_creation":
158 | thread_message = (
159 | await async_openai_client.beta.threads.messages.retrieve(
160 | message_id=step_details.message_creation.message_id,
161 | thread_id=thread_id,
162 | )
163 | )
164 | await __process_thread_message(message_references, thread_message)
165 |
166 | if step_details.type == "tool_calls":
167 | for tool_call in step_details.tool_calls:
168 | if isinstance(tool_call, dict):
169 | tool_call = DictToObject(tool_call)
170 |
171 | if tool_call.type == "code_interpreter":
172 | await __process_tool_call(
173 | step_references=step_references,
174 | step=step,
175 | tool_call=tool_call,
176 | name=tool_call.type,
177 | input=tool_call.code_interpreter.input
178 | or "# Generating code",
179 | output=tool_call.code_interpreter.outputs,
180 | show_input="python",
181 | )
182 |
183 | tool_outputs.append(
184 | {
185 | "output": tool_call.code_interpreter.outputs or "",
186 | "tool_call_id": tool_call.id,
187 | }
188 | )
189 |
190 | elif tool_call.type == "retrieval":
191 | await __process_tool_call(
192 | step_references=step_references,
193 | step=step,
194 | tool_call=tool_call,
195 | name=tool_call.type,
196 | input="Retrieving information",
197 | output="Retrieved information",
198 | )
199 |
200 | elif tool_call.type == "function":
201 | function_name = tool_call.function.name
202 | function_args = json.loads(tool_call.function.arguments)
203 |
204 | function_output = tool_map[function_name](
205 | **json.loads(tool_call.function.arguments)
206 | )
207 |
208 | await __process_tool_call(
209 | step_references=step_references,
210 | step=step,
211 | tool_call=tool_call,
212 | name=function_name,
213 | input=function_args,
214 | output=function_output,
215 | show_input="json",
216 | )
217 |
218 | tool_outputs.append(
219 | {"output": function_output, "tool_call_id": tool_call.id}
220 | )
221 | if (
222 | run.status == "requires_action"
223 | and run.required_action.type == "submit_tool_outputs"
224 | ):
225 | await async_openai_client.beta.threads.runs.submit_tool_outputs(
226 | thread_id=thread_id,
227 | run_id=run.id,
228 | tool_outputs=tool_outputs,
229 | )
230 |
231 | await cl.sleep(2) # Refresh every 2 seconds
232 | if run.status in ["cancelled", "failed", "completed", "expired"]:
233 | break
234 |
235 |
236 | @cl.on_message
237 | async def on_message(message: cl.Message) -> None:
238 | """
239 | Handles incoming messages from the user.
240 | Processes text messages, file attachment, and routes conversations accordingly.
241 | """
242 |
243 | if _is_currently_in_assistant_profile():
244 | thread = cl.user_session.get("thread") # type: Thread
245 | files_ids = await __process_files(message.elements)
246 | await run(thread_id=thread.id, human_query=message.content, file_ids=files_ids)
247 |
248 | else:
249 | # Chatbot memory
250 | messages = cl.user_session.get("message_history") or [] # Get message history
251 |
252 | if len(message.elements) > 0:
253 | await __handle_files_attachment(
254 | message, messages
255 | ) # Process file attachments
256 | else:
257 | await __handle_conversation(message, messages) # Process text messages
258 |
259 |
260 | @cl.on_settings_update
261 | async def update_settings(settings: Dict[str, Any]) -> None:
262 | """
263 | Updates chat settings based on user preferences.
264 | """
265 |
266 | if settings_temperature := settings[conf.SETTINGS_TEMPERATURE]:
267 | cl.user_session.set(conf.SETTINGS_TEMPERATURE, settings_temperature)
268 |
269 | if settings_top_p := settings[conf.SETTINGS_TOP_P]:
270 | cl.user_session.set(conf.SETTINGS_TOP_P, settings_top_p)
271 |
272 | cl.user_session.set(conf.SETTINGS_CHAT_MODEL, settings[conf.SETTINGS_CHAT_MODEL])
273 | cl.user_session.set(
274 | conf.SETTINGS_IMAGE_GEN_IMAGE_STYLE,
275 | settings[conf.SETTINGS_IMAGE_GEN_IMAGE_STYLE],
276 | )
277 | cl.user_session.set(
278 | conf.SETTINGS_IMAGE_GEN_IMAGE_QUALITY,
279 | settings[conf.SETTINGS_IMAGE_GEN_IMAGE_QUALITY],
280 | )
281 | cl.user_session.set(
282 | conf.SETTINGS_VISION_MODEL, settings[conf.SETTINGS_VISION_MODEL]
283 | )
284 | cl.user_session.set(
285 | conf.SETTINGS_USE_DYNAMIC_CONVERSATION_ROUTING,
286 | settings[conf.SETTINGS_USE_DYNAMIC_CONVERSATION_ROUTING],
287 | )
288 | cl.user_session.set(conf.SETTINGS_TTS_MODEL, settings[conf.SETTINGS_TTS_MODEL])
289 | cl.user_session.set(
290 | conf.SETTINGS_TTS_VOICE_PRESET_MODEL,
291 | settings[conf.SETTINGS_TTS_VOICE_PRESET_MODEL],
292 | )
293 | cl.user_session.set(
294 | conf.SETTINGS_ENABLE_TTS_RESPONSE, settings[conf.SETTINGS_ENABLE_TTS_RESPONSE]
295 | )
296 |
297 |
298 | @cl.action_callback("speak_chat_response_action")
299 | async def on_speak_chat_response(action: cl.Action) -> None:
300 | """
301 | Handles the action triggered by the user.
302 | """
303 | await action.remove()
304 | value = action.value
305 | return await __handle_tts_response(value)
306 |
307 |
308 | async def __handle_tts_response(context: str) -> None:
309 | """
310 | Generates and sends a TTS audio response using OpenAI's Audio API.
311 | """
312 | enable_tts_response = __get_settings(conf.SETTINGS_ENABLE_TTS_RESPONSE)
313 | if enable_tts_response is False:
314 | return
315 |
316 | if len(context) == 0:
317 | return
318 |
319 | model = __get_settings(conf.SETTINGS_TTS_MODEL)
320 | voice = __get_settings(conf.SETTINGS_TTS_VOICE_PRESET_MODEL)
321 |
322 | with openai_client.audio.speech.with_streaming_response.create(
323 | model=model, voice=voice, input=context
324 | ) as response:
325 | temp_filepath = os.path.join(temp_dir.name, "tts-output.mp3")
326 | response.stream_to_file(temp_filepath)
327 |
328 | await cl.Message(
329 | author=model,
330 | content="",
331 | elements=[
332 | cl.Audio(name="", path=temp_filepath, display="inline"),
333 | cl.Text(
334 | name="Note",
335 | display="inline",
336 | content=f"You're hearing an AI voice generated by OpenAI's {model} model, using the {voice} style. You can customize this in Settings if you'd like!",
337 | ),
338 | ],
339 | ).send()
340 |
341 | __update_msg_history_from_assistant_with_ctx(context)
342 |
343 |
344 | def __update_msg_history_from_user_with_ctx(context: str):
345 | __update_msg_history_with_ctx(context=context, role="user")
346 |
347 |
348 | def __update_msg_history_from_assistant_with_ctx(context: str):
349 | __update_msg_history_with_ctx(context=context, role="assistant")
350 |
351 |
352 | def __update_msg_history_with_ctx(context: str, role: str):
353 | if len(role) == 0 or len(context) == 0:
354 | return
355 |
356 | messages = cl.user_session.get("message_history") or []
357 | messages.append({"role": role, "content": context})
358 |
359 |
360 | async def __handle_conversation(
361 | message: cl.Message, messages: List[Dict[str, str]]
362 | ) -> None:
363 | """
364 | Handles text-based conversations with the user.
365 | Routes the conversation based on settings and semantic understanding.
366 | """
367 | model = __get_settings(conf.SETTINGS_CHAT_MODEL) # Get selected LLM model
368 | msg = cl.Message(content="", author=model)
369 | await msg.send()
370 |
371 | query = message.content # Get user query
372 | # Add query to message history
373 | __update_msg_history_from_user_with_ctx(query)
374 |
375 | if _is_currently_in_assistant_profile():
376 | mino = MinoAssistant(openai_client=async_openai_client)
377 | msg = cl.Message(content="", author=mino.name)
378 | await msg.send()
379 | await mino.run_assistant()
380 |
381 | else:
382 | use_dynamic_conversation_routing = __get_settings(
383 | conf.SETTINGS_USE_DYNAMIC_CONVERSATION_ROUTING
384 | )
385 |
386 | if use_dynamic_conversation_routing:
387 | await __handle_dynamic_conversation_routing_chat(
388 | messages, model, msg, query
389 | )
390 | else:
391 | await __handle_trigger_async_chat(
392 | llm_model=model, messages=messages, current_message=msg
393 | )
394 |
395 |
396 | def __get_user_session_id() -> str:
397 | return cl.user_session.get("id") or ""
398 |
399 |
400 | def __get_settings(key: str) -> Any:
401 | """
402 | Retrieves a specific setting value from the user session.
403 | """
404 | settings = cl.user_session.get("chat_settings")
405 | if settings is None:
406 | return
407 |
408 | return settings[key]
409 |
410 |
411 | async def __handle_vision(
412 | input_image: str,
413 | prompt: str,
414 | is_local: bool = False,
415 | ) -> None:
416 | """
417 | Handles vision processing tasks using the specified vision model.
418 | Sends the processed image and description to the user.
419 | """
420 | vision_model = (
421 | conf.DEFAULT_VISION_MODEL
422 | if is_local
423 | else __get_settings(conf.SETTINGS_VISION_MODEL)
424 | )
425 |
426 | supports_vision = litellm.supports_vision(model=vision_model)
427 |
428 | if supports_vision is False:
429 | print(f"Unsupported vision model: {vision_model}")
430 | await cl.Message(
431 | content="",
432 | elements=[
433 | cl.Text(
434 | name="Note",
435 | display="inline",
436 | content=f"It seems the vision model `{vision_model}` doesn't support image processing. Please choose a different model in Settings that offers Vision capabilities.",
437 | )
438 | ],
439 | ).send()
440 | return
441 |
442 | message = cl.Message(
443 | content="I'm analyzing the image. This might take a moment.",
444 | author=vision_model,
445 | )
446 |
447 | await message.send()
448 | vresponse = await litellm.acompletion(
449 | user=__get_user_session_id(),
450 | model=vision_model,
451 | messages=[
452 | {
453 | "role": "user",
454 | "content": [
455 | {"type": "text", "text": prompt},
456 | {"type": "image_url", "image_url": {"url": input_image}},
457 | ],
458 | }
459 | ],
460 | )
461 |
462 | description = vresponse.choices[0].message.content
463 |
464 | if is_local:
465 | image = cl.Image(path=input_image, name=prompt, display="inline")
466 | else:
467 | image = cl.Image(url=input_image, name=prompt, display="inline")
468 |
469 | message = cl.Message(
470 | author=vision_model,
471 | content="",
472 | elements=[
473 | image,
474 | cl.Text(name="Explain", content=description, display="inline"),
475 | ],
476 | actions=[
477 | cl.Action(
478 | name="speak_chat_response_action",
479 | value=description,
480 | label="Speak response",
481 | )
482 | ],
483 | )
484 |
485 | __update_msg_history_from_assistant_with_ctx(description)
486 |
487 | await message.send()
488 |
489 |
490 | async def __handle_trigger_async_chat(
491 | llm_model: str, messages, current_message: cl.Message
492 | ) -> None:
493 | """
494 | Triggers an asynchronous chat completion using the specified LLM model.
495 | Streams the response back to the user and updates the message history.
496 | """
497 |
498 | temperature = __get_settings(conf.SETTINGS_TEMPERATURE)
499 | top_p = __get_settings(conf.SETTINGS_TOP_P)
500 | try:
501 | # use LiteLLM for other providers
502 | stream = await litellm.acompletion(
503 | model=llm_model,
504 | messages=messages,
505 | stream=True, # TODO: IMPORTANT: about tool use, note to self tool use streaming is not support for most LLM provider (OpenAI, Anthropic) so in other to use tool, need to disable `streaming` param
506 | num_retries=2,
507 | temperature=temperature,
508 | top_p=top_p,
509 | )
510 |
511 | async for part in stream:
512 | if token := part.choices[0].delta.content or "":
513 | await current_message.stream_token(token)
514 |
515 | content = current_message.content
516 | __update_msg_history_from_assistant_with_ctx(content)
517 |
518 | enable_tts_response = __get_settings(conf.SETTINGS_ENABLE_TTS_RESPONSE)
519 | if enable_tts_response:
520 | current_message.actions = [
521 | cl.Action(
522 | name="speak_chat_response_action",
523 | value=content,
524 | label="Speak response",
525 | )
526 | ]
527 |
528 | await current_message.update()
529 |
530 | except Exception as e:
531 | await __handle_exception_error(e)
532 |
533 |
534 | async def __handle_exception_error(e: Exception) -> None:
535 | """
536 | Handles exceptions that occur during LLM interactions.
537 | """
538 |
539 | await cl.Message(
540 | content=(
541 | f"Something went wrong, please try again. Error type: {type(e)}, Error: {e}"
542 | )
543 | ).send()
544 |
545 | print(f"Error type: {type(e)}, Error: {e}")
546 |
547 |
548 | async def __config_chat_session(settings: Dict[str, Any]) -> None:
549 | """
550 | Configures the chat session based on user settings and sets the initial system message.
551 | """
552 |
553 | chat_profile = cl.user_session.get("chat_profile")
554 | if chat_profile == AppChatProfileType.CHAT.value:
555 | cl.user_session.set(
556 | conf.SETTINGS_CHAT_MODEL, settings[conf.SETTINGS_CHAT_MODEL]
557 | )
558 |
559 | system_message = {
560 | "role": "system",
561 | "content": "You are a helpful assistant who tries their best to answer questions: ",
562 | }
563 |
564 | cl.user_session.set("message_history", [system_message])
565 |
566 | msg = "Hello! I'm here to assist you. Please don't hesitate to ask me anything you'd like to know."
567 | await cl.Message(content=msg).send()
568 |
569 | elif chat_profile == AppChatProfileType.ASSISTANT.value:
570 | system_message = {"role": "system", "content": INSTRUCTIONS}
571 |
572 | cl.user_session.set("message_history", [system_message])
573 |
574 | msg = "Hello! I'm Mino, your Assistant. I'm here to assist you. Please don't hesitate to ask me anything you'd like to know. Currently, I can write and run code to answer math questions."
575 | await cl.Message(content=msg).send()
576 |
577 |
578 | async def __handle_trigger_async_image_gen(query: str) -> None:
579 | """
580 | Triggers asynchronous image generation using the default image generation model.
581 | Sends the generated image and description to the user.
582 | """
583 | image_gen_model = conf.DEFAULT_IMAGE_GEN_MODEL
584 | __update_msg_history_from_user_with_ctx(query)
585 |
586 | message = cl.Message(
587 | content="Sure! I'll create an image based on your description. This might take a moment, please be patient.",
588 | author=image_gen_model,
589 | )
590 | await message.send()
591 |
592 | style = __get_settings(conf.SETTINGS_IMAGE_GEN_IMAGE_STYLE)
593 | quality = __get_settings(conf.SETTINGS_IMAGE_GEN_IMAGE_QUALITY)
594 | try:
595 | image_response = await litellm.aimage_generation(
596 | user=__get_user_session_id(),
597 | prompt=query,
598 | model=image_gen_model,
599 | style=style,
600 | quality=quality,
601 | )
602 |
603 | image_gen_data = image_response["data"][0]
604 | image_url = image_gen_data["url"]
605 | revised_prompt = image_gen_data["revised_prompt"]
606 |
607 | message = cl.Message(
608 | author=image_gen_model,
609 | content="Here's the image, along with a refined description based on your input:",
610 | elements=[
611 | cl.Image(url=image_url, name=query, display="inline"),
612 | cl.Text(name="Description", content=revised_prompt, display="inline"),
613 | ],
614 | actions=[
615 | cl.Action(
616 | name="speak_chat_response_action",
617 | value=revised_prompt,
618 | label="Speak response",
619 | )
620 | ],
621 | )
622 |
623 | __update_msg_history_from_assistant_with_ctx(revised_prompt)
624 |
625 | await message.send()
626 |
627 | except Exception as e:
628 | await __handle_exception_error(e)
629 |
630 |
631 | async def __handle_files_attachment(
632 | message: cl.Message, messages: List[Dict[str, str]]
633 | ) -> None:
634 | """
635 | Handles file attachments from the user.
636 | Processes images using vision models and text files as chat input.
637 | """
638 | if not message.elements:
639 | await cl.Message(content="No file attached").send()
640 | return
641 |
642 | prompt = message.content
643 |
644 | for file in message.elements:
645 | path = str(file.path)
646 | mime_type = file.mime or ""
647 |
648 | if "image" in mime_type:
649 | await __handle_vision(path, prompt=prompt, is_local=True)
650 |
651 | elif "text" in mime_type:
652 | p = pathlib.Path(path, encoding="utf-8")
653 | s = p.read_text(encoding="utf-8")
654 | message.content = s
655 | await __handle_conversation(message, messages)
656 |
657 | elif "audio" in mime_type:
658 | f = pathlib.Path(path)
659 | await __handle_audio_transcribe(path, f)
660 |
661 |
662 | async def __handle_audio_transcribe(path, audio_file):
663 | model = conf.DEFAULT_WHISPER_MODEL
664 | transcription = await async_openai_client.audio.transcriptions.create(
665 | model=model, file=audio_file
666 | )
667 | text = transcription.text
668 |
669 | await cl.Message(
670 | content="",
671 | author=model,
672 | elements=[
673 | cl.Audio(name="Audio", path=path, display="inline"),
674 | cl.Text(content=text, name="Transcript", display="inline"),
675 | ],
676 | ).send()
677 |
678 | __update_msg_history_from_assistant_with_ctx(text)
679 | return text
680 |
681 |
682 | async def __handle_dynamic_conversation_routing_chat(
683 | messages: List[Dict[str, str]], model: str, msg: cl.Message, query: str
684 | ) -> None:
685 | """
686 | Routes the conversation dynamically based on the semantic understanding of the user's query.
687 | Handles image generation, vision processing, and default chat interactions.
688 | """
689 | route_choice = route_layer(query)
690 | route_choice_name = route_choice.name
691 |
692 | should_trimmed_messages = __get_settings(conf.SETTINGS_TRIMMED_MESSAGES)
693 | if should_trimmed_messages:
694 | messages = trim_messages(messages, model)
695 |
696 | print(
697 | f"""💡
698 | Query: {query}
699 | Is classified as route: {route_choice_name}
700 | running router..."""
701 | )
702 |
703 | if route_choice_name == SemanticRouterType.IMAGE_GENERATION:
704 | print(
705 | f"""💡
706 | Running route_choice_name: {route_choice_name}.
707 | Processing image generation..."""
708 | )
709 | await __handle_trigger_async_image_gen(query)
710 |
711 | elif route_choice_name == SemanticRouterType.VISION_IMAGE_PROCESSING:
712 | urls = extract_url(query)
713 | if len(urls) > 0:
714 | print(
715 | f"""💡
716 | Running route_choice_name: {route_choice_name}.
717 | Received image urls/paths.
718 | Processing with Vision model..."""
719 | )
720 | url = urls[0]
721 | await __handle_vision(input_image=url, prompt=query, is_local=False)
722 | else:
723 | print(
724 | f"""💡
725 | Running route_choice_name: {route_choice_name}.
726 | Received no image urls/paths.
727 | Processing with async chat..."""
728 | )
729 | await __handle_trigger_async_chat(
730 | llm_model=model, messages=messages, current_message=msg
731 | )
732 | else:
733 | print(
734 | f"""💡
735 | Running route_choice_name: {route_choice_name}.
736 | Processing with async chat..."""
737 | )
738 | await __handle_trigger_async_chat(
739 | llm_model=model, messages=messages, current_message=msg
740 | )
741 |
742 |
743 | def _is_currently_in_assistant_profile() -> bool:
744 | chat_profile = cl.user_session.get("chat_profile")
745 | return chat_profile == "Assistant"
746 |
747 |
748 | # Check if the files uploaded are allowed
749 | async def __check_files(files: List[Element]):
750 | for file in files:
751 | if file.mime not in allowed_mime:
752 | return False
753 | return True
754 |
755 |
756 | # Upload files to the assistant
757 | async def __upload_files(files: List[Element]):
758 | file_ids = []
759 | for file in files:
760 | uploaded_file = await async_openai_client.files.create(
761 | file=Path(file.path), purpose="assistants"
762 | )
763 | file_ids.append(uploaded_file.id)
764 | return file_ids
765 |
766 |
767 | async def __process_files(files: List[Element]):
768 | # Upload files if any and get file_ids
769 | file_ids = []
770 | if len(files) > 0:
771 | files_ok = await __check_files(files)
772 |
773 | if not files_ok:
774 | file_error_msg = f"Hey, it seems you have uploaded one or more files that we do not support currently, please upload only : {(',').join(allowed_mime)}"
775 | await cl.Message(content=file_error_msg).send()
776 | return file_ids
777 |
778 | file_ids = await __upload_files(files)
779 |
780 | return file_ids
781 |
782 |
783 | async def __process_thread_message(
784 | message_references: Dict[str, cl.Message], thread_message: Message
785 | ):
786 | for idx, content_message in enumerate(thread_message.content):
787 | id = thread_message.id + str(idx)
788 | if isinstance(content_message, TextContentBlock):
789 | if id in message_references:
790 | msg = message_references[id]
791 | msg.content = content_message.text.value
792 | await msg.update()
793 | else:
794 | message_references[id] = cl.Message(
795 | author=APP_NAME,
796 | content=content_message.text.value,
797 | )
798 |
799 | res_message = message_references[id].content
800 | enable_tts_response = __get_settings(conf.SETTINGS_ENABLE_TTS_RESPONSE)
801 | if enable_tts_response:
802 | message_references[id].actions = [
803 | cl.Action(
804 | name="speak_chat_response_action",
805 | value=res_message,
806 | label="Speak response",
807 | )
808 | ]
809 |
810 | await message_references[id].send()
811 | elif isinstance(content_message, ImageFileContentBlock):
812 | image_id = content_message.image_file.file_id
813 | response = (
814 | await async_openai_client.files.with_raw_response.retrieve_content(
815 | image_id
816 | )
817 | )
818 | elements = [
819 | cl.Image(
820 | name=image_id,
821 | content=response.content,
822 | display="inline",
823 | size="large",
824 | ),
825 | ]
826 |
827 | if id not in message_references:
828 | message_references[id] = cl.Message(
829 | author=APP_NAME,
830 | content="",
831 | elements=elements,
832 | )
833 |
834 | res_message = message_references[id].content
835 |
836 | enable_tts_response = __get_settings(conf.SETTINGS_ENABLE_TTS_RESPONSE)
837 | if enable_tts_response:
838 | message_references[id].actions = [
839 | cl.Action(
840 | name="speak_chat_response_action",
841 | value=res_message,
842 | label="Speak response",
843 | )
844 | ]
845 |
846 | await message_references[id].send()
847 | else:
848 | print("unknown message type", type(content_message))
849 |
850 |
851 | async def __process_tool_call(
852 | step_references: Dict[str, cl.Step],
853 | step: RunStep,
854 | tool_call: ToolCall,
855 | name: str,
856 | input: Any,
857 | output: Any,
858 | show_input: str = None,
859 | ):
860 | cl_step = None
861 | update = False
862 | if tool_call.id not in step_references:
863 | cl_step = cl.Step(
864 | name=name,
865 | type="tool",
866 | parent_id=cl.context.current_step.id,
867 | show_input=show_input,
868 | )
869 | step_references[tool_call.id] = cl_step
870 | else:
871 | update = True
872 | cl_step = step_references[tool_call.id]
873 |
874 | if step.created_at:
875 | cl_step.start = datetime.fromtimestamp(step.created_at).isoformat()
876 | if step.completed_at:
877 | cl_step.end = datetime.fromtimestamp(step.completed_at).isoformat()
878 | cl_step.input = input
879 | cl_step.output = output
880 |
881 | if update:
882 | await cl_step.update()
883 | else:
884 | await cl_step.send()
885 |
--------------------------------------------------------------------------------