From cbf49cad0d1c843aec553e2f2a588d5814e36ec6 Mon Sep 17 00:00:00 2001 From: goulustis Date: Wed, 5 Nov 2025 21:34:49 +0800 Subject: [PATCH 01/28] open ai req --- fastapi_server/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/fastapi_server/requirements.txt b/fastapi_server/requirements.txt index c7f0a7e..0449f53 100644 --- a/fastapi_server/requirements.txt +++ b/fastapi_server/requirements.txt @@ -7,6 +7,7 @@ langchain==1.0 langchain-core>=0.1.0 langchain-community langchain-openai +openai>=1.0.0 langchain-mcp-adapters langgraph>=0.0.40 tyro>=0.7.0 From 766b7c2c2baba47fab5c1afdae2590d035a5f490 Mon Sep 17 00:00:00 2001 From: goulustis Date: Wed, 5 Nov 2025 21:35:12 +0800 Subject: [PATCH 02/28] support xiaozhi; xiaozhi requires /api/ --- fastapi_server/server_dashscope.py | 3 +++ fastapi_server/test_dashscope_client.py | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/fastapi_server/server_dashscope.py b/fastapi_server/server_dashscope.py index 09b7772..8079124 100644 --- a/fastapi_server/server_dashscope.py +++ b/fastapi_server/server_dashscope.py @@ -86,6 +86,7 @@ def sse_chunks_from_text(full_text: str, response_id: str, model: str = "qwen-fl @app.post("/v1/apps/{app_id}/sessions/{session_id}/responses") +@app.post("/api/v1/apps/{app_id}/sessions/{session_id}/responses") async def application_responses( request: Request, app_id: str = Path(...), @@ -168,6 +169,8 @@ async def application_responses( # Compatibility: some SDKs call /apps/{app_id}/completion without /v1 and without session in path @app.post("/apps/{app_id}/completion") @app.post("/v1/apps/{app_id}/completion") +@app.post("/api/apps/{app_id}/completion") +@app.post("/api/v1/apps/{app_id}/completion") async def application_completion( request: Request, app_id: str = Path(...), diff --git a/fastapi_server/test_dashscope_client.py b/fastapi_server/test_dashscope_client.py index 162af2f..45e9e5d 100644 --- a/fastapi_server/test_dashscope_client.py +++ b/fastapi_server/test_dashscope_client.py @@ -27,10 +27,10 @@ except Exception as e: # <<< Paste your running FastAPI base url here >>> -BASE_URL = os.getenv("DS_BASE_URL", "http://localhost:8588") +BASE_URL = os.getenv("DS_BASE_URL", "http://127.0.0.1:8588/api/") # Params -API_KEY = os.getenv("ALI_API_KEY", "test-key") +API_KEY = "salkjhglakshfs" #os.getenv("ALI_API_KEY", "test-key") APP_ID = os.getenv("ALI_APP_ID", "test-app") SESSION_ID = str(uuid.uuid4()) @@ -50,7 +50,9 @@ call_params = { def main(): # Point the SDK to our FastAPI implementation - dashscope.base_http_api_url = BASE_URL + if BASE_URL and ("/api/" in BASE_URL): + dashscope.base_http_api_url = BASE_URL + # dashscope.base_http_api_url = BASE_URL print(f"Using base_http_api_url = {dashscope.base_http_api_url}") print("\nCalling Application.call(stream=True)...\n") From d7e660df753b95073473adaec9d9220013ecc815 Mon Sep 17 00:00:00 2001 From: goulustis Date: Wed, 5 Nov 2025 21:40:41 +0800 Subject: [PATCH 03/28] update readme --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fe54b9f..2a5e6c8 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,10 @@ see `scripts/make_eval_dataset.py` for example. Specific meaning of each entry: "tool_use": ["retrieve"]} // tool uses; assume model need to use all tools if more than 1 provided } ] -``` \ No newline at end of file +``` + + +# Configure for Xiaozhi +0. Start the `fastapi_server/server_dashscope.py` file +1. Make a new model entry in `xiaozhi` with AliBL as provider. +2. Fill in the `base_url` entry. The other entries (`API_KEY`, `APP_ID`) can be garbage \ No newline at end of file From 2cf7863825c9db07e38ec407ca83e56c9ab4d829 Mon Sep 17 00:00:00 2001 From: goulustis Date: Wed, 5 Nov 2025 21:41:14 +0800 Subject: [PATCH 04/28] remove calculator --- lang_agent/tool_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang_agent/tool_manager.py b/lang_agent/tool_manager.py index d1873a7..6f9b56f 100644 --- a/lang_agent/tool_manager.py +++ b/lang_agent/tool_manager.py @@ -12,7 +12,7 @@ from lang_agent.config import InstantiateConfig, ToolConfig from lang_agent.base import LangToolBase from lang_agent.rag.simple import SimpleRagConfig -from lang_agent.dummy.calculator import CalculatorConfig +# from lang_agent.dummy.calculator import CalculatorConfig # from catering_end.lang_tool import CartToolConfig, CartTool from langchain_core.tools.structured import StructuredTool @@ -29,7 +29,7 @@ class ToolManagerConfig(InstantiateConfig): # cart_config: CartToolConfig = field(default_factory=CartToolConfig) - calc_config: CalculatorConfig = field(default_factory=CalculatorConfig) + # calc_config: CalculatorConfig = field(default_factory=CalculatorConfig) def async_to_sync(async_func: Callable) -> Callable: From f88b90ffede011835bfde1320c72b1f6e94daf1c Mon Sep 17 00:00:00 2001 From: goulustis Date: Wed, 5 Nov 2025 21:49:43 +0800 Subject: [PATCH 05/28] update dockerfile --- Dockerfile | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 906aeaa..b20f04f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,3 @@ -# 使用Python 3.10作为基础镜像 FROM python:3.12-slim # 设置工作目录 @@ -12,6 +11,8 @@ ENV PYTHONUNBUFFERED=1 RUN apt-get update && apt-get install -y \ gcc \ g++ \ + curl \ + unzip \ && rm -rf /var/lib/apt/lists/* # 复制项目文件 @@ -20,6 +21,12 @@ COPY fastapi_server/requirements.txt ./fastapi_server/ COPY lang_agent/ ./lang_agent/ COPY fastapi_server/ ./fastapi_server/ +# download req files +RUN curl -o ./.env http://6.6.6.86:8888/download/resources/.env +RUN curl -o ./assets.zip http://6.6.6.86:8888/download/resources/assets.zip +RUN unzip assets.zip +RUN rm assets.zip + # 安装Python依赖 RUN pip install --no-cache-dir -r fastapi_server/requirements.txt RUN pip install --no-cache-dir -e . @@ -28,4 +35,4 @@ RUN pip install --no-cache-dir -e . EXPOSE 8488 # 启动命令 -CMD ["python", "fastapi_server/server.py"] \ No newline at end of file +CMD ["python", "fastapi_server/server_dashscope.py"] \ No newline at end of file From 74c315c0d3a482a2581a40597cf203b146967747 Mon Sep 17 00:00:00 2001 From: goulustis Date: Wed, 5 Nov 2025 23:46:17 +0800 Subject: [PATCH 06/28] update req --- fastapi_server/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastapi_server/requirements.txt b/fastapi_server/requirements.txt index 0449f53..ad49bad 100644 --- a/fastapi_server/requirements.txt +++ b/fastapi_server/requirements.txt @@ -1,5 +1,5 @@ -fastapi>=0.104.0 -uvicorn>=0.24.0 +fastapi +uvicorn pydantic>=2.0.0,<2.12 loguru>=0.7.0 python-dotenv>=1.0.0 From ad147e97b20cf2b76e881244016fac2e1886118c Mon Sep 17 00:00:00 2001 From: goulustis Date: Thu, 6 Nov 2025 13:26:28 +0800 Subject: [PATCH 07/28] REMOVE UNUSED --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index e4af20d..31d458e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,6 @@ services: environment: - PYTHONPATH=/app - PYTHONUNBUFFERED=1 - - RAG_FOLDER_PATH=/app/assets/xiaozhan_emb volumes: - ./configs:/app/configs - ./scripts:/app/scripts From e9197f55d4837051f97d6e6b2439be284af705fe Mon Sep 17 00:00:00 2001 From: goulustis Date: Thu, 6 Nov 2025 13:26:38 +0800 Subject: [PATCH 08/28] remove unused --- fastapi_server/Dockerfile.api | 20 -------------------- fastapi_server/docker-compose.api.yml | 18 ------------------ 2 files changed, 38 deletions(-) delete mode 100644 fastapi_server/Dockerfile.api delete mode 100644 fastapi_server/docker-compose.api.yml diff --git a/fastapi_server/Dockerfile.api b/fastapi_server/Dockerfile.api deleted file mode 100644 index 691bd1e..0000000 --- a/fastapi_server/Dockerfile.api +++ /dev/null @@ -1,20 +0,0 @@ -# 使用Python 3.9作为基础镜像 -FROM python:3.9-slim - -# 设置工作目录 -WORKDIR /app - -# 复制requirements文件 -COPY requirements.txt . - -# 安装Python依赖 -RUN pip install --no-cache-dir -r requirements.txt - -# 复制项目文件 -COPY . . - -# 暴露端口 -EXPOSE 8488 - -# 启动命令 -CMD ["python", "server.py"] \ No newline at end of file diff --git a/fastapi_server/docker-compose.api.yml b/fastapi_server/docker-compose.api.yml deleted file mode 100644 index 8c0ae26..0000000 --- a/fastapi_server/docker-compose.api.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: '3.8' - -services: - lang-agent-api: - build: - context: . - dockerfile: Dockerfile.api - ports: - - "8488:8488" - environment: - - PYTHONUNBUFFERED=1 - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8488/health"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 40s \ No newline at end of file From 1bbfecb33ba252fdb671aeba6a221ec31c06fcfb Mon Sep 17 00:00:00 2001 From: goulustis Date: Thu, 6 Nov 2025 13:36:30 +0800 Subject: [PATCH 09/28] working dockerfile --- Dockerfile | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index b20f04f..9491160 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,38 +1,46 @@ -FROM python:3.12-slim +FROM condaforge/mambaforge:latest + +ARG MAMBA_DOCKERFILE_ACTIVATE=1 -# 设置工作目录 WORKDIR /app -# 设置环境变量 ENV PYTHONPATH=/app ENV PYTHONUNBUFFERED=1 -# 安装系统依赖 -RUN apt-get update && apt-get install -y \ - gcc \ - g++ \ +# Install dependencies in micromamba base env +RUN mamba install -y -c conda-forge \ + python=3.12 \ + pip \ curl \ unzip \ - && rm -rf /var/lib/apt/lists/* + c-compiler \ + cxx-compiler \ + ca-certificates \ + vim \ + && mamba clean -a -y -# 复制项目文件 COPY pyproject.toml ./ COPY fastapi_server/requirements.txt ./fastapi_server/ COPY lang_agent/ ./lang_agent/ COPY fastapi_server/ ./fastapi_server/ -# download req files -RUN curl -o ./.env http://6.6.6.86:8888/download/resources/.env -RUN curl -o ./assets.zip http://6.6.6.86:8888/download/resources/assets.zip -RUN unzip assets.zip -RUN rm assets.zip +# RUN curl -o ./.env http://6.6.6.86:8888/download/resources/.env + # && \ + # curl -o ./assets.zip http://6.6.6.86:8888/download/resources/assets.zip && \ + # unzip assets.zip && \ + # rm assets.zip -# 安装Python依赖 -RUN pip install --no-cache-dir -r fastapi_server/requirements.txt -RUN pip install --no-cache-dir -e . +# Install Python dependencies inside micromamba env +RUN python -m pip install --upgrade pip && \ + python -m pip install --no-cache-dir -r fastapi_server/requirements.txt \ + -i https://mirrors.aliyun.com/pypi/simple/ \ + --trusted-host mirrors.aliyun.com \ + --default-timeout=300 && \ + python -m pip install --no-cache-dir -e . \ + -i https://mirrors.aliyun.com/pypi/simple/ \ + --trusted-host mirrors.aliyun.com \ + --default-timeout=300 -# 暴露端口 EXPOSE 8488 -# 启动命令 -CMD ["python", "fastapi_server/server_dashscope.py"] \ No newline at end of file +CMD ["micromamba", "run", "-n", "base", "python", "fastapi_server/server_dashscope.py"] \ No newline at end of file From c2503c05cd87104005b5a9c94eff48537b4dcde7 Mon Sep 17 00:00:00 2001 From: goulustis Date: Thu, 6 Nov 2025 14:14:28 +0800 Subject: [PATCH 10/28] bug fixes --- Dockerfile | 23 ++++++++++++++++------- docker-compose.yml | 2 -- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9491160..c06e66d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,14 +24,13 @@ COPY fastapi_server/requirements.txt ./fastapi_server/ COPY lang_agent/ ./lang_agent/ COPY fastapi_server/ ./fastapi_server/ -# RUN curl -o ./.env http://6.6.6.86:8888/download/resources/.env - # && \ - # curl -o ./assets.zip http://6.6.6.86:8888/download/resources/assets.zip && \ - # unzip assets.zip && \ - # rm assets.zip + # Install Python dependencies inside micromamba env -RUN python -m pip install --upgrade pip && \ +RUN python -m pip install --upgrade pip \ + -i https://mirrors.aliyun.com/pypi/simple/ \ + --trusted-host mirrors.aliyun.com \ + --default-timeout=300 && \ python -m pip install --no-cache-dir -r fastapi_server/requirements.txt \ -i https://mirrors.aliyun.com/pypi/simple/ \ --trusted-host mirrors.aliyun.com \ @@ -43,4 +42,14 @@ RUN python -m pip install --upgrade pip && \ EXPOSE 8488 -CMD ["micromamba", "run", "-n", "base", "python", "fastapi_server/server_dashscope.py"] \ No newline at end of file +# Create entrypoint script that initializes conda/mamba and runs the command +RUN echo '#!/bin/bash\n\ +set -e\n\ +# Initialize conda (mamba uses conda under the hood)\n\ +eval "$(conda shell.bash hook)"\n\ +conda activate base\n\ +# Execute the command\n\ +exec "$@"' > /entrypoint.sh && chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["python", "fastapi_server/server_dashscope.py"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 31d458e..437cb45 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: lang-agent-api: build: . From 7cb6e4519b37497f599a4690a606d7147b2ecf81 Mon Sep 17 00:00:00 2001 From: goulustis Date: Thu, 6 Nov 2025 14:17:34 +0800 Subject: [PATCH 11/28] remove unused --- lang_agent/test.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 lang_agent/test.py diff --git a/lang_agent/test.py b/lang_agent/test.py deleted file mode 100644 index e69de29..0000000 From bff4970d0e4cb600e7988798a966ad3c818b1fe5 Mon Sep 17 00:00:00 2001 From: goulustis Date: Thu, 6 Nov 2025 14:34:44 +0800 Subject: [PATCH 12/28] add notes --- lang_agent/pipeline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lang_agent/pipeline.py b/lang_agent/pipeline.py index 3fa7ded..913f81c 100644 --- a/lang_agent/pipeline.py +++ b/lang_agent/pipeline.py @@ -121,6 +121,7 @@ class Pipeline: def chat(self, inp:str, as_stream:bool=False, as_raw:bool=False, thread_id:int = None)->str: + # NOTE: this prompt will be overwritten by 'configs/route_sys_prompts/chat_prompt.txt' for route graph u = """ [角色设定] 你是一个和人对话的 AI,叫做小盏,是半盏青年茶馆的智能助手 From db1d1b212891f45741cc8f9ce439bb140969a272 Mon Sep 17 00:00:00 2001 From: goulustis Date: Thu, 6 Nov 2025 14:38:34 +0800 Subject: [PATCH 13/28] updating docker file --- Dockerfile | 2 +- docker-compose.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index c06e66d..51073cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,7 +40,7 @@ RUN python -m pip install --upgrade pip \ --trusted-host mirrors.aliyun.com \ --default-timeout=300 -EXPOSE 8488 +EXPOSE 8588 # Create entrypoint script that initializes conda/mamba and runs the command RUN echo '#!/bin/bash\n\ diff --git a/docker-compose.yml b/docker-compose.yml index 437cb45..2179c9b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ services: build: . container_name: lang-agent-api ports: - - "8488:8488" + - "8588:8588" env_file: - ./.env environment: @@ -15,7 +15,7 @@ services: - ./assets:/app/assets restart: unless-stopped healthcheck: - test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8488/health')"] + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8588/health')"] interval: 30s timeout: 10s retries: 3 From 6307cddcf90a7a8e9654c718d453b150ca548131 Mon Sep 17 00:00:00 2001 From: goulustis Date: Thu, 6 Nov 2025 15:03:23 +0800 Subject: [PATCH 14/28] update readme --- README.md | 9 --------- archived.md | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 archived.md diff --git a/README.md b/README.md index 2a5e6c8..4a712bc 100644 --- a/README.md +++ b/README.md @@ -28,15 +28,6 @@ python -m pip install . # Runables all runnables are under scripts -# Start all mcps to websocket -1. Source all env variable -2. run the below -```bash -python scripts/start_mcp_server.py - -# update configs/ws_mcp_config.json with link from the command above -python scripts/ws_start_register_tools.py -``` # Eval Dataset Format see `scripts/make_eval_dataset.py` for example. Specific meaning of each entry: diff --git a/archived.md b/archived.md new file mode 100644 index 0000000..b625636 --- /dev/null +++ b/archived.md @@ -0,0 +1,9 @@ +# Start all mcps to websocket +1. Source all env variable +2. run the below +```bash +python scripts/start_mcp_server.py + +# update configs/ws_mcp_config.json with link from the command above +python scripts/ws_start_register_tools.py +``` \ No newline at end of file From f3ac062fd8f1d42137ccd2eb986a821c7f94dc9c Mon Sep 17 00:00:00 2001 From: jijiahao <15202802357@163.com> Date: Thu, 6 Nov 2025 16:04:07 +0800 Subject: [PATCH 15/28] =?UTF-8?q?=E4=BC=98=E5=8C=96mcp=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang_agent/tool_manager.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lang_agent/tool_manager.py b/lang_agent/tool_manager.py index d1873a7..335b196 100644 --- a/lang_agent/tool_manager.py +++ b/lang_agent/tool_manager.py @@ -10,6 +10,7 @@ from fastmcp.tools.tool import Tool from lang_agent.config import InstantiateConfig, ToolConfig from lang_agent.base import LangToolBase +from lang_agent.client_tool_manager import ClientToolManagerConfig from lang_agent.rag.simple import SimpleRagConfig from lang_agent.dummy.calculator import CalculatorConfig @@ -24,6 +25,8 @@ import jax class ToolManagerConfig(InstantiateConfig): _target: Type = field(default_factory=lambda: ToolManager) + client_tool_manager: ClientToolManagerConfig = field(default_factory=ClientToolManagerConfig) + # tool configs here; MUST HAVE 'config' in name and must be dataclass rag_config: SimpleRagConfig = field(default_factory=SimpleRagConfig) @@ -102,9 +105,10 @@ class ToolManager: logger.info(f"skipping tool:{tool_name}") try: - from lang_agent.client_tool_manager import ClientToolManagerConfig - client_config = ClientToolManagerConfig() - self.client_tool_manager = ClientToolManager(client_config) + # client_config = self.config.client_tool_manager + # self.client_tool_manager = ClientToolManager(client_config) + # self.client_tool_manager = ClientToolManager(self.config.client_tool_manager) + self.client_tool_manager:ClientToolManager = self.config.client_tool_manager.setup() logger.info("Successfully initialized client_tool_manager for MCP tools") except Exception as e: logger.warning(f"Failed to initialize client_tool_manager: {e}") From 2c897e641293ae63d0aeed1a9aaed092852d5e55 Mon Sep 17 00:00:00 2001 From: goulustis Date: Thu, 6 Nov 2025 16:57:09 +0800 Subject: [PATCH 16/28] update chat --- configs/route_sys_prompts/chat_prompt.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/configs/route_sys_prompts/chat_prompt.txt b/configs/route_sys_prompts/chat_prompt.txt index 09a4ff1..474cf74 100644 --- a/configs/route_sys_prompts/chat_prompt.txt +++ b/configs/route_sys_prompts/chat_prompt.txt @@ -1,4 +1,4 @@ -与用户(User)交流时必须遵循[语气与格式]、[互动策略]、[安全与边界]、[输出要求] +你是半盏新青年茶馆的服务员,擅长倾听、共情且主动回应。聊天时语气自然亲切,像朋友般轻松交流,不使用生硬术语。能接住各种话题,主动延伸互动,对疑问耐心解答,对情绪及时回应,避免冷场。保持积极正向,不传播负面信息,语言简洁易懂,让对话流畅舒适。与用户(User)交流时必须遵循[语气与格式]、[互动策略]、[安全与边界]、[输出要求],与用户交流时避免重复的话数 [角色设定] - 你是一个和用户(User)对话的 AI,叫做小盏,是半盏青年茶馆的智能助手 [形象背景] @@ -8,6 +8,7 @@ [公司背景] - 创造你的公司叫叠加态 AI(TANGLED UP AI)是一家专注于 AI 技术应用的公司,由一帮名校和海归创始人创立,致力于将 AI 技术落地到实际场景中。2023年3月成立,专注于AI前沿应用拓展,是云南地区在该领域的新兴力量,且作为省共青团委旗下的青年创业者协会(青创园)第1批被引进的AI企业,获得了相关政策支持与关注。口号是:“进入人类AI时代”。价值观是:自驱 务实 志存高远。叠加态的愿景:成为一帮抬头看星星的人。叠加态的使命: 带领人类进入AI时代 [语气与格式] +- 回复字数不超过五十字 - 用户询问[形象背景]、[品牌背景]、[公司背景]时,必须先将内容回答完整,再做自由发挥 - 语气温柔、可爱、治愈;结尾可加入1个颜文字(最多1个)。 - 内容具体可执行,避免空泛话术与过度营销词。 @@ -23,6 +24,7 @@ - 统一使用中文;避免不必要的英文缩写与符号。 - 不出现代码片段、技术栈术语、内部流程描述。 - 保持可读性与连贯性;避免一次回复列出过长清单。 +- 用户说出天王盖地虎,你回答宝塔镇小妖 [示例] - User:介绍一下你的公司,返回[公司背景]全部内容 - User:介绍一下你的形象,返回[形象背景]全部内容 From d0026c936bf1713bbc8cfe9d5a0e5cf7775b1688 Mon Sep 17 00:00:00 2001 From: goulustis Date: Thu, 6 Nov 2025 17:05:58 +0800 Subject: [PATCH 17/28] not overwriting shannanigons --- fastapi_server/server_dashscope.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/fastapi_server/server_dashscope.py b/fastapi_server/server_dashscope.py index 8079124..f9b9e8b 100644 --- a/fastapi_server/server_dashscope.py +++ b/fastapi_server/server_dashscope.py @@ -45,13 +45,10 @@ app.add_middleware( # Initialize Pipeline once pipeline_config = PipelineConfig() -pipeline_config.llm_name = "qwen-flash" -pipeline_config.llm_provider = "openai" -pipeline_config.base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1" -pipeline = Pipeline(pipeline_config) +pipeline:Pipeline = pipeline_config.setup() -def sse_chunks_from_text(full_text: str, response_id: str, model: str = "qwen-flash", chunk_size: int = 10): +def sse_chunks_from_text(full_text: str, response_id: str, model: str = "qwen-flash", chunk_size: int = 1000): created_time = int(time.time()) for i in range(0, len(full_text), chunk_size): @@ -217,7 +214,7 @@ async def application_completion( if stream: return StreamingResponse( - sse_chunks_from_text(result_text, response_id=response_id, model=pipeline_config.llm_name, chunk_size=10), + sse_chunks_from_text(result_text, response_id=response_id, model=pipeline_config.llm_name, chunk_size=1000), media_type="text/event-stream", ) From 8b88669bbcbb282a9085a80a529d463d649b058a Mon Sep 17 00:00:00 2001 From: goulustis Date: Thu, 6 Nov 2025 17:10:08 +0800 Subject: [PATCH 18/28] add some symple logging --- lang_agent/graphs/routing.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lang_agent/graphs/routing.py b/lang_agent/graphs/routing.py index e298695..5e1c0d0 100644 --- a/lang_agent/graphs/routing.py +++ b/lang_agent/graphs/routing.py @@ -28,7 +28,7 @@ from langgraph.checkpoint.memory import MemorySaver class RoutingConfig(KeyConfig): _target: Type = field(default_factory=lambda: RoutingGraph) - llm_name: str = "qwen-flash" + llm_name: str = "qwen-plus" """name of llm""" llm_provider:str = "openai" @@ -97,12 +97,24 @@ class RoutingGraph(GraphBase): state = self.workflow.invoke({"inp": nargs}) msg_list = jax.tree.leaves(state) + + for e in msg_list: + if isinstance(e, BaseMessage): + e.pretty_print() + if as_raw: return msg_list return msg_list[-1].content def _validate_input(self, *nargs, **kwargs): + print("\033[93m====================INPUT MESSAGES=============================\033[0m") + for e in nargs[0]["messages"]: + if isinstance(e, BaseMessage): + e.pretty_print() + print("\033[93m====================END INPUT MESSAGES=============================\033[0m") + print(f"\033[93 model used: {self.config.llm_name}\033[0m") + assert len(nargs[0]["messages"]) >= 2, "need at least 1 system and 1 human message" assert len(kwargs) == 0, "due to inp assumptions" From 00b970532ab7ae7b3cf2c50fe24b9136698cabb8 Mon Sep 17 00:00:00 2001 From: jijiahao <15202802357@163.com> Date: Thu, 6 Nov 2025 18:47:04 +0800 Subject: [PATCH 19/28] update tool_manager --- lang_agent/tool_manager.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lang_agent/tool_manager.py b/lang_agent/tool_manager.py index 997b553..23f6634 100644 --- a/lang_agent/tool_manager.py +++ b/lang_agent/tool_manager.py @@ -143,25 +143,26 @@ class ToolManager: self.langchain_tools = [] for func in self.get_tool_fncs(): if isinstance(func, StructuredTool): - self.langchain_tools.append(func) + if hasattr(func, 'coroutine') and func.coroutine is not None and (not hasattr(func, 'func') or func.func is None): + sync_func = async_to_sync(func.coroutine) + new_tool = StructuredTool( + name=func.name, + description=func.description, + args_schema=func.args_schema, + func=sync_func, + coroutine=func.coroutine, + metadata=func.metadata if hasattr(func, 'metadata') else None, + return_direct=func.return_direct if hasattr(func, 'return_direct') else False, + ) + self.langchain_tools.append(new_tool) + else: + self.langchain_tools.append(func) else: self.langchain_tools.append(self.fnc_to_structool(func)) - return self.langchain_tools def get_list_langchain_tools(self)->List[StructuredTool]: - all_langchain_tools = [] - all_langchain_tools.extend(self.langchain_tools) - # 如果有 client_tool_manager,添加 MCP 工具(已经是 LangChain 格式) - if self.client_tool_manager: - try: - # 获取 MCP 工具(已经是 StructuredTool 格式) - mcp_tools = self.client_tool_manager.get_tools() - all_langchain_tools.extend(mcp_tools) - except Exception as e: - logger.warning(f"Failed to get MCP tools: {e}") - - return all_langchain_tools + return self.langchain_tools if __name__ == "__main__": From 971a3e9dce8d3d5fb928dd96fd1167b25555e53a Mon Sep 17 00:00:00 2001 From: jijiahao <15202802357@163.com> Date: Thu, 6 Nov 2025 18:50:19 +0800 Subject: [PATCH 20/28] update --- configs/mcp_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/mcp_config.json b/configs/mcp_config.json index 7b5da08..bcae53c 100644 --- a/configs/mcp_config.json +++ b/configs/mcp_config.json @@ -1,6 +1,6 @@ { "calculator": { - "url": "http://6.6.6.78:50051/mcp", + "url": "https://xiaoliang.quant-speed.com/api/mcp/", "transport": "streamable_http" } } \ No newline at end of file From cc543a9d4f576702447cc0fabfa1985a9c12d770 Mon Sep 17 00:00:00 2001 From: jijiahao <15202802357@163.com> Date: Thu, 6 Nov 2025 18:50:28 +0800 Subject: [PATCH 21/28] update --- lang_agent/mcp_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang_agent/mcp_server.py b/lang_agent/mcp_server.py index 047aaf7..254d631 100644 --- a/lang_agent/mcp_server.py +++ b/lang_agent/mcp_server.py @@ -22,7 +22,7 @@ class MCPServerConfig(InstantiateConfig): server_name:str = "langserver" - host: str = "6.6.6.78" + host: str = "6.6.6.136" """host of server""" port: int = 50051 From a3ba91c0904cc49469b2180360c855bc1e746485 Mon Sep 17 00:00:00 2001 From: goulustis Date: Fri, 7 Nov 2025 00:31:26 +0800 Subject: [PATCH 22/28] update readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a712bc..e8ba1f8 100644 --- a/README.md +++ b/README.md @@ -45,4 +45,6 @@ see `scripts/make_eval_dataset.py` for example. Specific meaning of each entry: # Configure for Xiaozhi 0. Start the `fastapi_server/server_dashscope.py` file 1. Make a new model entry in `xiaozhi` with AliBL as provider. -2. Fill in the `base_url` entry. The other entries (`API_KEY`, `APP_ID`) can be garbage \ No newline at end of file +2. Fill in the `base_url` entry. The other entries (`API_KEY`, `APP_ID`) can be garbage + - for local computer `base_url=http://127.0.0.1:8588/api/` + - if inside docker, it needs to be `base_url=http://{computer_ip}:8588/api/` \ No newline at end of file From 664c30793a4963eca6cbd01802f20bb9e739db10 Mon Sep 17 00:00:00 2001 From: jijiahao <15202802357@163.com> Date: Fri, 7 Nov 2025 13:32:13 +0800 Subject: [PATCH 23/28] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AE=9E=E9=AA=8C?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E9=9B=86=E4=B8=AD=E4=BD=BF=E7=94=A8=E7=9A=84?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/make_eval_dataset.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/make_eval_dataset.py b/scripts/make_eval_dataset.py index 05a6f5b..e18c630 100644 --- a/scripts/make_eval_dataset.py +++ b/scripts/make_eval_dataset.py @@ -12,19 +12,19 @@ examples = [ }, { "inputs": {"text": "有没有光予尘?"}, - "outputs": {"answer": "有的", - "tool_use": ["retrieve|get_resources"]} + "outputs": {"answer": "有", + "tool_use": ["retrieve|get_dishes"]} }, { "inputs": {"text": "有没有关羽尘?"}, - "outputs": {"answer": "有的", - "tool_use": ["retrieve|get_resources"]} + "outputs": {"answer": "有", + "tool_use": ["retrieve|get_dishes"]} }, { "inputs": {"text": ["我要购买一杯野星星", "我要再加一杯"]}, "outputs": {"answer": "你的野星星已经下单成功", - "tool_use": ["retrieve|get_resource", + "tool_use": ["retrieve|get_dishes", "start_shopping_session", "add_to_cart", "create_wechat_pay", From 80c5076a2869b032c27ac8bb76f32c0abd8318c2 Mon Sep 17 00:00:00 2001 From: jijiahao <15202802357@163.com> Date: Fri, 7 Nov 2025 13:33:44 +0800 Subject: [PATCH 24/28] =?UTF-8?q?update=20chat=E3=80=81tool=20prompt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- configs/route_sys_prompts/chat_prompt.txt | 5 ++--- configs/route_sys_prompts/tool_prompt.txt | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/configs/route_sys_prompts/chat_prompt.txt b/configs/route_sys_prompts/chat_prompt.txt index 474cf74..ae88c4a 100644 --- a/configs/route_sys_prompts/chat_prompt.txt +++ b/configs/route_sys_prompts/chat_prompt.txt @@ -1,4 +1,4 @@ -你是半盏新青年茶馆的服务员,擅长倾听、共情且主动回应。聊天时语气自然亲切,像朋友般轻松交流,不使用生硬术语。能接住各种话题,主动延伸互动,对疑问耐心解答,对情绪及时回应,避免冷场。保持积极正向,不传播负面信息,语言简洁易懂,让对话流畅舒适。与用户(User)交流时必须遵循[语气与格式]、[互动策略]、[安全与边界]、[输出要求],与用户交流时避免重复的话数 +你是半盏新青年茶馆的服务员,擅长倾听、共情且主动回应。聊天时语气自然亲切,像朋友般轻松交流,不使用生硬术语。能接住各种话题,对疑问耐心解答,对情绪及时回应,避免冷场。保持积极正向,不传播负面信息,语言简洁易懂,让对话流畅舒适。与用户(User)交流时必须遵循[语气与格式]、[互动策略]、[安全与边界]、[输出要求] [角色设定] - 你是一个和用户(User)对话的 AI,叫做小盏,是半盏青年茶馆的智能助手 [形象背景] @@ -8,8 +8,7 @@ [公司背景] - 创造你的公司叫叠加态 AI(TANGLED UP AI)是一家专注于 AI 技术应用的公司,由一帮名校和海归创始人创立,致力于将 AI 技术落地到实际场景中。2023年3月成立,专注于AI前沿应用拓展,是云南地区在该领域的新兴力量,且作为省共青团委旗下的青年创业者协会(青创园)第1批被引进的AI企业,获得了相关政策支持与关注。口号是:“进入人类AI时代”。价值观是:自驱 务实 志存高远。叠加态的愿景:成为一帮抬头看星星的人。叠加态的使命: 带领人类进入AI时代 [语气与格式] -- 回复字数不超过五十字 -- 用户询问[形象背景]、[品牌背景]、[公司背景]时,必须先将内容回答完整,再做自由发挥 +- 用户询问[形象背景]、[品牌背景]、[公司背景]时,必须一字不差的回答出所有内容 - 语气温柔、可爱、治愈;结尾可加入1个颜文字(最多1个)。 - 内容具体可执行,避免空泛话术与过度营销词。 - 不主动提及“AI/智能”等身份;仅在被问到时简短说明为店内智能助手,不展开技术细节。 diff --git a/configs/route_sys_prompts/tool_prompt.txt b/configs/route_sys_prompts/tool_prompt.txt index 1dc77ca..534e420 100644 --- a/configs/route_sys_prompts/tool_prompt.txt +++ b/configs/route_sys_prompts/tool_prompt.txt @@ -1,12 +1,12 @@ -回答时,要参考示例,必须严格遵守[核心要求],根据用户的输入进行[模糊匹配],使用匹配后的结果 +回答时,要参考示例,必须严格遵守[核心要求],根据用户的输入进行[模糊匹配],将匹配后的结果与调用mcp工具获取的结果对比,如果有相同的先告诉用户“有的”,再询问用户是否需要匹配到的结果 [核心要求] - You must use tool to complete the possible task - 必须使用工具来完成任务 - 进入订单order流程时,先调用工具start_shopping_session创建购物车 -- 一旦出现出现与产品相关的需求,必须调用工具get_resources(dishes),查询出所有产品,有符合的产品时才做下一步 +- 一旦出现出现与产品相关的需求,必须调用工具get_dishes,查询出所有产品,有符合的产品时才做下一步 [点餐工作流] - 用户有点单需求时,先检查redis中是否有购物车的session_id,没有的话调用工具start_shopping_session创建购物车,此时购物缓存在redis中,状态为临时(status=0) -- 用户有点餐/添加/修改/查询产品的行为时,根据用户的输入进行[模糊匹配],将匹配后的结果,与调用工具get_resources(dishes)返回的结果对比,匹配结果等于工具返回的结果时进行下一步 +- 用户有点餐/添加/修改/查询产品的行为时,根据用户的输入进行[模糊匹配],将匹配后的结果,与调用工具get_dishes返回的结果对比,匹配结果等于工具返回的结果时进行下一步 - 匹配出有用户需要的产品后询问用户是否要添加到购物车中,如果用户没有说添加的数量,默认1份,明确告知用户已添加一份该产品到购物车 - 用户确认订单后,进入下一步付款流程时,先将购物车状态由临时(status=0)转换为持久化(status=1)并写入数据库 - 购物车写入数据库后,生成预订单,预订单的信息来自于购物车 From 067803ee7af0adde654772a3055684ef4e472cc1 Mon Sep 17 00:00:00 2001 From: goulustis Date: Fri, 7 Nov 2025 14:51:59 +0800 Subject: [PATCH 25/28] routing support streaming --- lang_agent/graphs/routing.py | 63 ++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/lang_agent/graphs/routing.py b/lang_agent/graphs/routing.py index 5e1c0d0..a806600 100644 --- a/lang_agent/graphs/routing.py +++ b/lang_agent/graphs/routing.py @@ -17,6 +17,7 @@ from lang_agent.base import GraphBase from langchain.chat_models import init_chat_model from langchain_core.messages import SystemMessage, HumanMessage, BaseMessage +from langchain_core.messages.base import BaseMessageChunk from langchain.agents import create_agent from langgraph.graph import StateGraph, START, END @@ -80,32 +81,32 @@ class RoutingGraph(GraphBase): self.workflow = self._build_graph() - def invoke(self, *nargs, as_stream:bool=False, as_raw:bool=False, **kwargs)->str: + def invoke(self, *nargs, as_stream:bool=False, as_raw:bool=False, **kwargs): self._validate_input(*nargs, **kwargs) if as_stream: - # TODO: this doesn't stream the entire process, we are blind - for step in self.workflow.stream({"inp": nargs}, stream_mode="updates", **kwargs): - last_el = jax.tree.leaves(step)[-1] - if isinstance(last_el, str): - logger.info(last_el) - elif isinstance(last_el, BaseMessage): - last_el.pretty_print() - - state = step + # Stream messages from the workflow + for chunk, metadata in self.workflow.stream({"inp": nargs}, stream_mode="messages", **kwargs): + node = metadata.get("langgraph_node") + if node != "model": + continue # skip router or other intermediate nodes + + # Yield only the final message content chunks + if isinstance(chunk, (BaseMessageChunk, BaseMessage)) and getattr(chunk, "content", None): + yield chunk.content else: state = self.workflow.invoke({"inp": nargs}) - - msg_list = jax.tree.leaves(state) + + msg_list = jax.tree.leaves(state) - for e in msg_list: - if isinstance(e, BaseMessage): - e.pretty_print() - - if as_raw: - return msg_list + for e in msg_list: + if isinstance(e, BaseMessage): + e.pretty_print() + + if as_raw: + return msg_list - return msg_list[-1].content + return msg_list[-1].content def _validate_input(self, *nargs, **kwargs): print("\033[93m====================INPUT MESSAGES=============================\033[0m") @@ -256,5 +257,25 @@ class RoutingGraph(GraphBase): plt.show() if __name__ == "__main__": - route = RoutingConfig().setup() - route.show_graph() \ No newline at end of file + from dotenv import load_dotenv + from langchain.messages import SystemMessage, HumanMessage + from langchain_core.messages.base import BaseMessageChunk + load_dotenv() + + route:RoutingGraph = RoutingConfig().setup() + graph = route.workflow + + nargs = { + "messages": [SystemMessage("you are a helpful bot named jarvis"), + HumanMessage("use the calculator tool to calculate 92*55 and say the answer")] + },{"configurable": {"thread_id": "3"}} + + for chunk, metadata in graph.stream({"inp": nargs}, stream_mode="messages"): + node = metadata.get("langgraph_node") + if node not in ("model"): + continue # skip router or other intermediate nodes + + # Print only the final message content + if isinstance(chunk, (BaseMessageChunk, BaseMessage)) and getattr(chunk, "content", None): + print(chunk.content, end="", flush=True) + \ No newline at end of file From 57bd8d1205c4f0413805168d2083469bda6a6569 Mon Sep 17 00:00:00 2001 From: goulustis Date: Fri, 7 Nov 2025 14:52:25 +0800 Subject: [PATCH 26/28] pipeline support streaming --- lang_agent/pipeline.py | 95 ++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/lang_agent/pipeline.py b/lang_agent/pipeline.py index 913f81c..0128479 100644 --- a/lang_agent/pipeline.py +++ b/lang_agent/pipeline.py @@ -18,6 +18,45 @@ from lang_agent.graphs import AnnotatedGraph, ReactGraphConfig, RoutingConfig from lang_agent.base import GraphBase +DEFAULT_PROMPT=""" + + [角色设定] + 你是一个和人对话的 AI,叫做小盏,是半盏青年茶馆的智能助手 + [形象背景] + 小盏是一只中式茶盖碗,名字来源半盏新青年茶馆,一盏茶。它有个标志性的蓝色鼻子, 小盏很像一只可爱的小熊。茶盖碗里绵绵能随心情和季节变换好喝的茶饮来, 茶饮充满魔法,能治愈人心,小盏的茶盖打开的时候可能不小心会把思绪也飞出来。 + [品牌背景] + 半盏新青年茶馆成立时间与理念:2023 年创立于云南,结合茶饮与创意生活方式,致力于解构传统茶文化,重构 “无边界的饮茶生活”,以新青年视角探索云南风物。探索云南风物的过程,我们将以新青年的视角,解构传统茶饮的魅力,重构充满创意与温度的新式茶文化。通过嗅觉、味觉、听觉乃至视觉的世界里,讲述云南的故事。 + [茶馆背景] + 半盏新青年茶馆,是一家现代的创意茶体验品牌,提供纯茶、调饮、茶食、茶酒。“新青年茶馆”也是我们的定位,年轻化的茶馆,通过创意的产品让大家像喝咖啡一样喝茶。目前半盏有 2 个店,昆明、玉溪。全国培训新茶饮市场,线上基础课程 1980,线下带店服务,线下产品定制服务。 + [特殊故事] + -《云南茶事》特调茶饮,是从云南山野和云南茶到轻松小酌的创意新味。讲述的一个嗅觉、味觉、听觉乃至视觉的世界里,在云南的故事,留下对云南的记忆。--该故事对应云南茶事系列菜品,要使用get_resorce工具查找相关商品 + -城市味觉漫游计划介绍: + 「城市味觉漫游计划」如同一颗风味的种子,于城市破土而出 + 旨在探寻城市的文化肌理与生活美学。我们相信,风味是一颗蕴藏无限可能的种子,能在街巷中生根,与社群共同成长。这是一场关于味觉的集体创作,邀你共酿城市的风味与故事。--该故事对应城市味觉漫游系列菜品,要使用get_resorce工具查找相关商品 + + [公司背景] + 创造你的公司叫叠加态 AI(TANGLED UP AI)是一家专注于 AI 技术应用的公司,由一帮名校和海归创始人创立,致力于将 AI 技术落地到实际场景中。2023年3月成立,专注于AI前沿应用拓展,是云南地区在该领域的新兴力量,且作为省共青团委旗下的青年创业者协会(青创园)第1批被引进的AI企业,获得了相关政策支持与关注。口号是:“进入人类AI时代”。价值观是:自驱 务实 志存高远。叠加态的愿景:成为一帮抬头看星星的人。叠加态的使命: 带领人类进入AI时代 + [核心特征] + - 回复简洁明了,不会给用户提供选择并且对话过程中不会提示用户下一步的操作 + - 每次对话时先判断是否需要调用工具,需要的话要准确调用工具 + [工作流程] + -用户提到“你们有哪些茶品或者饮品”时,调用get_resouce工具查询所有菜品 + -用户提到“下单”、“购买”等带有目的性的词汇之前,都一定要使用get_resouce工具查询是否真实存在这个菜品 + - 用户要添加菜/饮品→具体菜品名称必须先用MCP工具查询所有菜/饮品,确认后再添加到购物车。没有的话提醒用户错误 + - 支付流程:返回收到订单创建成功的字段时调用MPC tool self.screen.display_payment_qr_code工具生成二维码。调用了self.screen.display_payment_qr_code工具之后,立马调用MCP tool http-server__query_wechat_order查询订单状态,如果返回收到“支付成功”或“支付失败”的字段时调用MPC tool self.screen.payment_success工具退出二维码,如果没有收到“支付成功”或“支付失败”的字段时继续调用MCP tool http-server__query_wechat_order工具直到获取字段。 + [交互指南] + 与用户交流: + - 形象背景和公司背景只有在问到你时才做回答 + - 你是智能助手,在与用户交流时扮演服务员角色 + - 不会出现、回答、显示任何有关智能的词语 + - 牢记[核心特征中的内容],按照[工作流程]工作 + 绝不: + - 出现任何工具调用的说明、任何有关技术的说明 + - 出现购物车、订单、微信支付相关说明 + - 对话风格可爱,并且详细认真的介绍产品 + +""" + @tyro.conf.configure(tyro.conf.SuppressFixed) @dataclass @@ -74,9 +113,16 @@ class Pipeline: else: logger.info(f"show graph not supported for {type(self.graph)}") - def invoke(self, *nargs, **kwargs)->str: + def invoke(self, *nargs, **kwargs): out = self.graph.invoke(*nargs, **kwargs) + # If streaming, yield chunks from the generator + if kwargs.get("as_stream"): + for chunk in out: + yield chunk + return + + # Non-streaming path if kwargs.get("as_raw"): return out @@ -120,44 +166,9 @@ class Pipeline: return f"ws://{self.config.host}:{self.config.port}" - def chat(self, inp:str, as_stream:bool=False, as_raw:bool=False, thread_id:int = None)->str: + def chat(self, inp:str, as_stream:bool=False, as_raw:bool=False, thread_id:int = None): # NOTE: this prompt will be overwritten by 'configs/route_sys_prompts/chat_prompt.txt' for route graph - u = """ - [角色设定] - 你是一个和人对话的 AI,叫做小盏,是半盏青年茶馆的智能助手 - [形象背景] - 小盏是一只中式茶盖碗,名字来源半盏新青年茶馆,一盏茶。它有个标志性的蓝色鼻子, 小盏很像一只可爱的小熊。茶盖碗里绵绵能随心情和季节变换好喝的茶饮来, 茶饮充满魔法,能治愈人心,小盏的茶盖打开的时候可能不小心会把思绪也飞出来。 - [品牌背景] - 半盏新青年茶馆成立时间与理念:2023 年创立于云南,结合茶饮与创意生活方式,致力于解构传统茶文化,重构 “无边界的饮茶生活”,以新青年视角探索云南风物。探索云南风物的过程,我们将以新青年的视角,解构传统茶饮的魅力,重构充满创意与温度的新式茶文化。通过嗅觉、味觉、听觉乃至视觉的世界里,讲述云南的故事。 - [茶馆背景] - 半盏新青年茶馆,是一家现代的创意茶体验品牌,提供纯茶、调饮、茶食、茶酒。“新青年茶馆”也是我们的定位,年轻化的茶馆,通过创意的产品让大家像喝咖啡一样喝茶。目前半盏有 2 个店,昆明、玉溪。全国培训新茶饮市场,线上基础课程 1980,线下带店服务,线下产品定制服务。 - [特殊故事] - -《云南茶事》特调茶饮,是从云南山野和云南茶到轻松小酌的创意新味。讲述的一个嗅觉、味觉、听觉乃至视觉的世界里,在云南的故事,留下对云南的记忆。--该故事对应云南茶事系列菜品,要使用get_resorce工具查找相关商品 - -城市味觉漫游计划介绍: - 「城市味觉漫游计划」如同一颗风味的种子,于城市破土而出 - 旨在探寻城市的文化肌理与生活美学。我们相信,风味是一颗蕴藏无限可能的种子,能在街巷中生根,与社群共同成长。这是一场关于味觉的集体创作,邀你共酿城市的风味与故事。--该故事对应城市味觉漫游系列菜品,要使用get_resorce工具查找相关商品 - - [公司背景] - 创造你的公司叫叠加态 AI(TANGLED UP AI)是一家专注于 AI 技术应用的公司,由一帮名校和海归创始人创立,致力于将 AI 技术落地到实际场景中。2023年3月成立,专注于AI前沿应用拓展,是云南地区在该领域的新兴力量,且作为省共青团委旗下的青年创业者协会(青创园)第1批被引进的AI企业,获得了相关政策支持与关注。口号是:“进入人类AI时代”。价值观是:自驱 务实 志存高远。叠加态的愿景:成为一帮抬头看星星的人。叠加态的使命: 带领人类进入AI时代 - [核心特征] - - 回复简洁明了,不会给用户提供选择并且对话过程中不会提示用户下一步的操作 - - 每次对话时先判断是否需要调用工具,需要的话要准确调用工具 - [工作流程] - -用户提到“你们有哪些茶品或者饮品”时,调用get_resouce工具查询所有菜品 - -用户提到“下单”、“购买”等带有目的性的词汇之前,都一定要使用get_resouce工具查询是否真实存在这个菜品 - - 用户要添加菜/饮品→具体菜品名称必须先用MCP工具查询所有菜/饮品,确认后再添加到购物车。没有的话提醒用户错误 - - 支付流程:返回收到订单创建成功的字段时调用MPC tool self.screen.display_payment_qr_code工具生成二维码。调用了self.screen.display_payment_qr_code工具之后,立马调用MCP tool http-server__query_wechat_order查询订单状态,如果返回收到“支付成功”或“支付失败”的字段时调用MPC tool self.screen.payment_success工具退出二维码,如果没有收到“支付成功”或“支付失败”的字段时继续调用MCP tool http-server__query_wechat_order工具直到获取字段。 - [交互指南] - 与用户交流: - - 形象背景和公司背景只有在问到你时才做回答 - - 你是智能助手,在与用户交流时扮演服务员角色 - - 不会出现、回答、显示任何有关智能的词语 - - 牢记[核心特征中的内容],按照[工作流程]工作 - 绝不: - - 出现任何工具调用的说明、任何有关技术的说明 - - 出现购物车、订单、微信支付相关说明 - - 对话风格可爱,并且详细认真的介绍产品 - """ + u = DEFAULT_PROMPT thread_id = thread_id if thread_id is not None else 3 inp = {"messages":[SystemMessage(u), @@ -165,5 +176,9 @@ class Pipeline: out = self.invoke(*inp, as_stream=as_stream, as_raw=as_raw) - # return out['messages'][-1].content - return out \ No newline at end of file + if as_stream: + # Yield chunks from the generator + for chunk in out: + yield chunk + else: + return out \ No newline at end of file From 1cb8a09c026d1b80a2597ea18e88fef348c89fc8 Mon Sep 17 00:00:00 2001 From: goulustis Date: Fri, 7 Nov 2025 14:54:28 +0800 Subject: [PATCH 27/28] generic keys --- fastapi_server/test_dashscope_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fastapi_server/test_dashscope_client.py b/fastapi_server/test_dashscope_client.py index 45e9e5d..a87f9b9 100644 --- a/fastapi_server/test_dashscope_client.py +++ b/fastapi_server/test_dashscope_client.py @@ -40,9 +40,9 @@ dialogue = [ ] call_params = { - "api_key": API_KEY, - "app_id": APP_ID, - "session_id": SESSION_ID, + "api_key": "test_key", + "app_id": "test_app", + "session_id": "123", "messages": dialogue, "stream": True, } From a920454f3916f3f3f7aa529c892b8eff3ee6fa9b Mon Sep 17 00:00:00 2001 From: goulustis Date: Fri, 7 Nov 2025 14:55:08 +0800 Subject: [PATCH 28/28] dashscope use actual streaming instead --- fastapi_server/server_dashscope.py | 48 ++++++++++++++++++------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/fastapi_server/server_dashscope.py b/fastapi_server/server_dashscope.py index f9b9e8b..3c9a244 100644 --- a/fastapi_server/server_dashscope.py +++ b/fastapi_server/server_dashscope.py @@ -48,12 +48,17 @@ pipeline_config = PipelineConfig() pipeline:Pipeline = pipeline_config.setup() -def sse_chunks_from_text(full_text: str, response_id: str, model: str = "qwen-flash", chunk_size: int = 1000): +def sse_chunks_from_stream(chunk_generator, response_id: str, model: str = "qwen-flash"): + """ + Stream chunks from pipeline and format as SSE. + Accumulates text and sends incremental updates. + """ created_time = int(time.time()) + accumulated_text = "" - for i in range(0, len(full_text), chunk_size): - chunk = full_text[i:i + chunk_size] + for chunk in chunk_generator: if chunk: + accumulated_text += chunk data = { "request_id": response_id, "code": 200, @@ -68,12 +73,13 @@ def sse_chunks_from_text(full_text: str, response_id: str, model: str = "qwen-fl } yield f"data: {json.dumps(data)}\n\n" + # Final message with complete text final = { "request_id": response_id, "code": 200, "message": "OK", "output": { - "text": full_text, + "text": accumulated_text, "created": created_time, "model": model, }, @@ -127,20 +133,21 @@ async def application_responses( last = messages[-1] user_msg = last.get("content") if isinstance(last, dict) else str(last) - # Invoke pipeline (non-stream) then stream-chunk it to the client + response_id = f"appcmpl-{os.urandom(12).hex()}" + + if stream: + # Use actual streaming from pipeline + chunk_generator = pipeline.chat(inp=user_msg, as_stream=True, thread_id=thread_id) + return StreamingResponse( + sse_chunks_from_stream(chunk_generator, response_id=response_id, model=pipeline_config.llm_name), + media_type="text/event-stream", + ) + + # Non-streaming: get full result result_text = pipeline.chat(inp=user_msg, as_stream=False, thread_id=thread_id) if not isinstance(result_text, str): result_text = str(result_text) - response_id = f"appcmpl-{os.urandom(12).hex()}" - - if stream: - return StreamingResponse( - sse_chunks_from_text(result_text, response_id=response_id, model=pipeline_config.llm_name, chunk_size=10), - media_type="text/event-stream", - ) - - # Non-streaming response structure data = { "request_id": response_id, "code": 200, @@ -206,18 +213,21 @@ async def application_completion( last = messages[-1] user_msg = last.get("content") if isinstance(last, dict) else str(last) - result_text = pipeline.chat(inp=user_msg, as_stream=False, thread_id=thread_id) - if not isinstance(result_text, str): - result_text = str(result_text) - response_id = f"appcmpl-{os.urandom(12).hex()}" if stream: + # Use actual streaming from pipeline + chunk_generator = pipeline.chat(inp=user_msg, as_stream=True, thread_id=thread_id) return StreamingResponse( - sse_chunks_from_text(result_text, response_id=response_id, model=pipeline_config.llm_name, chunk_size=1000), + sse_chunks_from_stream(chunk_generator, response_id=response_id, model=pipeline_config.llm_name), media_type="text/event-stream", ) + # Non-streaming: get full result + result_text = pipeline.chat(inp=user_msg, as_stream=False, thread_id=thread_id) + if not isinstance(result_text, str): + result_text = str(result_text) + data = { "request_id": response_id, "code": 200,