├── 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 | ![Multi LLM Providers](./src/resources/screenshot/1.jpg) 33 | ![Multi-modal Conversation](./src/resources/screenshot/2.jpg) 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 | --------------------------------------------------------------------------------