From 0836fd8d4fe83ce34f5e508ac4d68a8d86832537 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Thu, 26 Mar 2026 00:26:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=9D=9E=E6=B5=81?= =?UTF-8?q?=E5=BC=8F=E5=AF=B9=E8=AF=9D=E6=8E=A5=E5=8F=A3=E5=92=8C=E7=9B=B8?= =?UTF-8?q?=E5=BA=94=E7=9A=84=E9=9B=86=E6=88=90=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...chat_stream_service.py => chat_service.py} | 144 +++++++++++++ backend/package/yuxi/services/run_worker.py | 2 +- backend/server/routers/chat_router.py | 44 +++- backend/test/api/test_chat_agent_sync.py | 192 ++++++++++++++++++ backend/test/test_chat_stream_interrupt.py | 4 +- ...t_conversation_service_attachment_state.py | 2 +- backend/uv.lock | 37 ++-- docs/agents/agents-config.md | 2 +- 8 files changed, 404 insertions(+), 23 deletions(-) rename backend/package/yuxi/services/{chat_stream_service.py => chat_service.py} (83%) create mode 100644 backend/test/api/test_chat_agent_sync.py diff --git a/backend/package/yuxi/services/chat_stream_service.py b/backend/package/yuxi/services/chat_service.py similarity index 83% rename from backend/package/yuxi/services/chat_stream_service.py rename to backend/package/yuxi/services/chat_service.py index 870ea486..fcc868fa 100644 --- a/backend/package/yuxi/services/chat_stream_service.py +++ b/backend/package/yuxi/services/chat_service.py @@ -342,6 +342,150 @@ async def check_and_handle_interrupts( logger.error(traceback.format_exc()) +async def agent_chat( + *, + agent_id: str, + query: str, + config: dict, + meta: dict, + image_content: str | None, + current_user, + db, +) -> dict: + """非流式对话,返回完整响应""" + start_time = asyncio.get_event_loop().time() + + if image_content: + human_message = HumanMessage( + content=[ + {"type": "text", "text": query}, + {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_content}"}}, + ] + ) + message_type = "multimodal_image" + else: + human_message = HumanMessage(content=query) + message_type = "text" + + init_msg = {"role": "user", "content": query, "type": "human"} + if image_content: + init_msg["message_type"] = "multimodal_image" + init_msg["image_content"] = image_content + else: + init_msg["message_type"] = "text" + + if conf.enable_content_guard and await content_guard.check(query): + return { + "status": "error", + "error_type": "content_guard_blocked", + "error_message": "输入内容包含敏感词", + "request_id": meta.get("request_id"), + } + + try: + agent = agent_manager.get_agent(agent_id) + except Exception as e: + logger.error(f"Error getting agent {agent_id}: {e}, {traceback.format_exc()}") + return { + "status": "error", + "error_type": "agent_error", + "error_message": f"智能体 {agent_id} 获取失败: {str(e)}", + "request_id": meta.get("request_id"), + } + + messages = [human_message] + + user_id = str(current_user.id) + agent_config_id = config.get("agent_config_id") + try: + agent_config = await _resolve_agent_config(db, agent_id, current_user, agent_config_id) + except ValueError as e: + return { + "status": "error", + "error_type": "invalid_config", + "error_message": str(e), + "request_id": meta.get("request_id"), + } + + if not (thread_id := config.get("thread_id")): + thread_id = str(uuid.uuid4()) + logger.warning(f"No thread_id provided, generated new thread_id: {thread_id}") + + input_context = agent_config | {"user_id": user_id, "thread_id": thread_id} + + try: + conv_repo = ConversationRepository(db) + + try: + await conv_repo.add_message_by_thread_id( + thread_id=thread_id, + role="user", + content=query, + message_type=message_type, + image_content=image_content, + extra_metadata={"raw_message": human_message.model_dump()}, + ) + except Exception as e: + logger.error(f"Error saving user message: {e}") + + langgraph_config = {"configurable": {"thread_id": thread_id, "user_id": user_id}} + + full_msg = None + accumulated_content: list[str] = [] + + async for msg, metadata in agent.stream_messages(messages, input_context=input_context): + if isinstance(msg, AIMessageChunk): + accumulated_content.append(msg.content) + else: + msg_dict = msg.model_dump() if hasattr(msg, "model_dump") else {} + if msg_dict.get("type") == "ai": + full_msg = msg + + full_msg = _ensure_full_msg(full_msg, accumulated_content) + full_content = "".join(accumulated_content) if accumulated_content else (full_msg.content if full_msg else "") + + if conf.enable_content_guard and await content_guard.check(full_content): + await save_partial_message(conv_repo, thread_id, full_msg, "content_guard_blocked") + return { + "status": "interrupted", + "message": "检测到敏感内容,已中断输出", + "request_id": meta.get("request_id"), + "time_cost": asyncio.get_event_loop().time() - start_time, + } + + try: + graph = await agent.get_graph() + state = await graph.aget_state(langgraph_config) + agent_state = extract_agent_state(getattr(state, "values", {})) if state else {} + except Exception: + agent_state = {} + + await save_messages_from_langgraph_state( + agent_instance=agent, + thread_id=thread_id, + conv_repo=conv_repo, + config_dict=langgraph_config, + ) + + return { + "status": "finished", + "response": full_content, + "request_id": meta.get("request_id"), + "thread_id": thread_id, + "agent_state": agent_state, + "time_cost": asyncio.get_event_loop().time() - start_time, + } + + except Exception as e: + logger.error(f"Error in agent_chat: {e}, {traceback.format_exc()}") + return { + "status": "error", + "error_type": "unexpected_error", + "error_message": str(e), + "request_id": meta.get("request_id"), + } + + async def stream_agent_chat( *, agent_id: str, diff --git a/backend/package/yuxi/services/run_worker.py b/backend/package/yuxi/services/run_worker.py index 6741180e..5a5fa05e 100644 --- a/backend/package/yuxi/services/run_worker.py +++ b/backend/package/yuxi/services/run_worker.py @@ -11,7 +11,7 @@ from dataclasses import dataclass, field from sqlalchemy import select from sqlalchemy.exc import OperationalError from yuxi.repositories.agent_run_repository import TERMINAL_RUN_STATUSES, AgentRunRepository -from yuxi.services.chat_stream_service import stream_agent_chat +from yuxi.services.chat_service import stream_agent_chat from yuxi.services.run_queue_service import ( append_run_stream_event, clear_cancel_signal, diff --git a/backend/server/routers/chat_router.py b/backend/server/routers/chat_router.py index 21d14ae1..19a50956 100644 --- a/backend/server/routers/chat_router.py +++ b/backend/server/routers/chat_router.py @@ -14,7 +14,7 @@ from server.utils.auth_middleware import get_db, get_required_user from yuxi import config as conf from yuxi.agents.buildin import agent_manager from yuxi.models import select_model -from yuxi.services.chat_stream_service import get_agent_state_view, stream_agent_chat, stream_agent_resume +from yuxi.services.chat_service import get_agent_state_view, stream_agent_chat, stream_agent_resume, agent_chat from yuxi.services.agent_run_service import ( cancel_agent_run_view, create_agent_run_view, @@ -412,6 +412,48 @@ async def chat_agent( ) +@chat.post("/agent/{agent_id}/sync") +async def chat_agent_sync( + agent_id: str, + query: str = Body(...), + config: dict = Body({}), + meta: dict = Body({}), + image_content: str | None = Body(None), + current_user: User = Depends(get_required_user), + db: AsyncSession = Depends(get_db), +): + """使用特定智能体进行非流式对话(需要登录)""" + logger.info(f"[sync] agent_id: {agent_id}, query: {query}, config: {config}, meta: {meta}") + logger.info(f"[sync] image_content present: {image_content is not None}") + if image_content: + logger.info(f"[sync] image_content length: {len(image_content)}") + + # 确保 request_id 存在 + if "request_id" not in meta or not meta.get("request_id"): + meta["request_id"] = str(uuid.uuid4()) + + meta.update( + { + "query": query, + "agent_id": agent_id, + "server_model_name": config.get("model", agent_id), + "thread_id": config.get("thread_id"), + "user_id": current_user.id, + "has_image": bool(image_content), + } + ) + + return await agent_chat( + agent_id=agent_id, + query=query, + config=config, + meta=meta, + image_content=image_content, + current_user=current_user, + db=db, + ) + + @chat.post("/agent/{agent_id}/runs") async def create_agent_run( agent_id: str, diff --git a/backend/test/api/test_chat_agent_sync.py b/backend/test/api/test_chat_agent_sync.py new file mode 100644 index 00000000..c0aa6c1a --- /dev/null +++ b/backend/test/api/test_chat_agent_sync.py @@ -0,0 +1,192 @@ +""" +Integration tests for chat_agent_sync non-streaming endpoint. +""" + +from __future__ import annotations + +import pytest + +pytestmark = [pytest.mark.asyncio, pytest.mark.integration] + + +async def test_chat_agent_sync_requires_authentication(test_client): + """非流式端点需要认证""" + response = await test_client.post("/api/chat/agent/test_agent/sync", json={"query": "hello"}) + assert response.status_code == 401 + + +async def test_chat_agent_sync_basic_conversation(test_client, admin_headers): + """测试非流式对话基本功能""" + # 先获取可用智能体 + agents_response = await test_client.get("/api/chat/agent", headers=admin_headers) + assert agents_response.status_code == 200, agents_response.text + agents = agents_response.json().get("agents", []) + + if not agents: + pytest.skip("No agents are registered in the system.") + + agent_id = agents[0].get("id") + if not agent_id: + pytest.skip("Agent payload missing id field.") + + # 调用非流式端点 + response = await test_client.post( + f"/api/chat/agent/{agent_id}/sync", + json={"query": "Hello, say 'Hi' back to me"}, + headers=admin_headers, + ) + + assert response.status_code == 200, response.text + payload = response.json() + + # 验证响应结构 + assert "status" in payload, f"Missing 'status' in response: {payload}" + assert payload["status"] in ("finished", "error", "interrupted"), f"Unexpected status: {payload['status']}" + assert "request_id" in payload, f"Missing 'request_id' in response: {payload}" + + # 如果成功完成,验证响应内容 + if payload["status"] == "finished": + assert "response" in payload, f"Missing 'response' in finished status: {payload}" + assert isinstance(payload["response"], str), f"response should be str, got: {type(payload['response'])}" + assert len(payload["response"]) > 0, "response should not be empty" + # thread_id 应该存在 + assert "thread_id" in payload, f"Missing 'thread_id' in response: {payload}" + # time_cost 应该存在 + assert "time_cost" in payload, f"Missing 'time_cost' in response: {payload}" + assert isinstance(payload["time_cost"], float), f"time_cost should be float: {type(payload['time_cost'])}" + + +async def test_chat_agent_sync_with_thread_id(test_client, admin_headers): + """测试非流式对话指定 thread_id""" + # 获取可用智能体 + agents_response = await test_client.get("/api/chat/agent", headers=admin_headers) + assert agents_response.status_code == 200, agents_response.text + agents = agents_response.json().get("agents", []) + + if not agents: + pytest.skip("No agents are registered in the system.") + + agent_id = agents[0].get("id") + if not agent_id: + pytest.skip("Agent payload missing id field.") + + import uuid + thread_id = str(uuid.uuid4()) + + response = await test_client.post( + f"/api/chat/agent/{agent_id}/sync", + json={ + "query": "Hello", + "config": {"thread_id": thread_id}, + }, + headers=admin_headers, + ) + + assert response.status_code == 200, response.text + payload = response.json() + + # 验证 thread_id 是否保持一致 + if payload["status"] == "finished": + assert payload.get("thread_id") == thread_id, f"thread_id mismatch: expected {thread_id}, got {payload.get('thread_id')}" + + +async def test_chat_agent_sync_with_meta(test_client, admin_headers): + """测试非流式对话传递 meta 参数""" + agents_response = await test_client.get("/api/chat/agent", headers=admin_headers) + assert agents_response.status_code == 200, agents_response.text + agents = agents_response.json().get("agents", []) + + if not agents: + pytest.skip("No agents are registered in the system.") + + agent_id = agents[0].get("id") + if not agent_id: + pytest.skip("Agent payload missing id field.") + + import uuid + request_id = str(uuid.uuid4()) + + response = await test_client.post( + f"/api/chat/agent/{agent_id}/sync", + json={ + "query": "Hello", + "meta": {"request_id": request_id}, + }, + headers=admin_headers, + ) + + assert response.status_code == 200, response.text + payload = response.json() + + # 验证 request_id 是否保持一致 + assert payload.get("request_id") == request_id, f"request_id mismatch: expected {request_id}, got {payload.get('request_id')}" + + +async def test_chat_agent_sync_vs_streaming_consistency(test_client, admin_headers): + """对比测试:非流式与流式端点行为一致性""" + # 获取可用智能体 + agents_response = await test_client.get("/api/chat/agent", headers=admin_headers) + assert agents_response.status_code == 200, agents_response.text + agents = agents_response.json().get("agents", []) + + if not agents: + pytest.skip("No agents are registered in the system.") + + agent_id = agents[0].get("id") + if not agent_id: + pytest.skip("Agent payload missing id field.") + + query = "What is 1+1?" + + # 调用流式端点 + import uuid + thread_id = str(uuid.uuid4()) + request_id = str(uuid.uuid4()) + + streaming_response = await test_client.post( + f"/api/chat/agent/{agent_id}", + json={ + "query": query, + "config": {"thread_id": thread_id}, + "meta": {"request_id": request_id}, + }, + headers=admin_headers, + ) + + assert streaming_response.status_code == 200, streaming_response.text + + # 收集流式响应 + streaming_content = [] + async for line in streaming_response.aiter_lines(): + if line: + import json as json_lib + try: + data = json_lib.loads(line) + if data.get("response"): + streaming_content.append(data["response"]) + except: + pass + + # 调用非流式端点 + thread_id2 = str(uuid.uuid4()) + request_id2 = str(uuid.uuid4()) + + sync_response = await test_client.post( + f"/api/chat/agent/{agent_id}/sync", + json={ + "query": query, + "config": {"thread_id": thread_id2}, + "meta": {"request_id": request_id2}, + }, + headers=admin_headers, + ) + + assert sync_response.status_code == 200, sync_response.text + sync_payload = sync_response.json() + + # 两者都应该成功 + assert sync_payload["status"] == "finished", f"Sync failed: {sync_payload}" + + # 非流式响应应该有内容 + assert "response" in sync_payload, f"Missing response in sync payload: {sync_payload}" + assert len(streaming_content) > 0, "Streaming should have collected content" diff --git a/backend/test/test_chat_stream_interrupt.py b/backend/test/test_chat_stream_interrupt.py index 86e3645c..4a8c2265 100644 --- a/backend/test/test_chat_stream_interrupt.py +++ b/backend/test/test_chat_stream_interrupt.py @@ -1,4 +1,4 @@ -"""测试 chat_stream_service 中的 interrupt 相关函数""" +"""测试 chat_service 中的 interrupt 相关函数""" import pytest import sys @@ -6,7 +6,7 @@ import os sys.path.insert(0, os.getcwd()) -from yuxi.services.chat_stream_service import ( +from yuxi.services.chat_service import ( _normalize_interrupt_options, _normalize_interrupt_questions, _build_ask_user_question_payload, diff --git a/backend/test/test_conversation_service_attachment_state.py b/backend/test/test_conversation_service_attachment_state.py index 23034de2..c032ef9e 100644 --- a/backend/test/test_conversation_service_attachment_state.py +++ b/backend/test/test_conversation_service_attachment_state.py @@ -5,7 +5,7 @@ from types import SimpleNamespace import pytest -from yuxi.services import chat_stream_service as chat_svc +from yuxi.services import chat_service as chat_svc from yuxi.services import conversation_service as svc diff --git a/backend/uv.lock b/backend/uv.lock index dc6f7cfe..e7717769 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12, <3.14" resolution-markers = [ "(python_full_version >= '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", @@ -905,8 +905,8 @@ dependencies = [ { name = "scipy" }, { name = "torch", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, - { name = "torchvision", version = "0.23.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, - { name = "torchvision", version = "0.23.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchvision", version = "0.23.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "torchvision", version = "0.23.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "tqdm" }, { name = "typer" }, ] @@ -964,8 +964,8 @@ dependencies = [ { name = "safetensors", extra = ["torch"] }, { name = "torch", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, - { name = "torchvision", version = "0.23.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, - { name = "torchvision", version = "0.23.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchvision", version = "0.23.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "torchvision", version = "0.23.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "tqdm" }, { name = "transformers" }, ] @@ -1272,6 +1272,7 @@ dependencies = [ { name = "griffecli" }, { name = "griffelib" }, ] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/56/28a0accac339c164b52a92c6cfc45a903acc0c174caa5c1713803467b533/griffe-2.0.0.tar.gz", hash = "sha256:c68979cd8395422083a51ea7cf02f9c119d889646d99b7b656ee43725de1b80f", size = 293906, upload-time = "2026-03-23T21:06:53.402Z" } wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/94/ee21d41e7eb4f823b94603b9d40f86d3c7fde80eacc2c3c71845476dddaa/griffe-2.0.0-py3-none-any.whl", hash = "sha256:5418081135a391c3e6e757a7f3f156f1a1a746cc7b4023868ff7d5e2f9a980aa", size = 5214, upload-time = "2026-02-09T19:09:44.105Z" }, ] @@ -1284,6 +1285,7 @@ dependencies = [ { name = "colorama" }, { name = "griffelib" }, ] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/f8/2e129fd4a86e52e58eefe664de05e7d502decf766e7316cc9e70fdec3e18/griffecli-2.0.0.tar.gz", hash = "sha256:312fa5ebb4ce6afc786356e2d0ce85b06c1c20d45abc42d74f0cda65e159f6ef", size = 56213, upload-time = "2026-03-23T21:06:54.8Z" } wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/ed/d93f7a447bbf7a935d8868e9617cbe1cadf9ee9ee6bd275d3040fbf93d60/griffecli-2.0.0-py3-none-any.whl", hash = "sha256:9f7cd9ee9b21d55e91689358978d2385ae65c22f307a63fb3269acf3f21e643d", size = 9345, upload-time = "2026-02-09T19:09:42.554Z" }, ] @@ -1292,6 +1294,7 @@ wheels = [ name = "griffelib" version = "2.0.0" source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/06/eccbd311c9e2b3ca45dbc063b93134c57a1ccc7607c5e545264ad092c4a9/griffelib-2.0.0.tar.gz", hash = "sha256:e504d637a089f5cab9b5daf18f7645970509bf4f53eda8d79ed71cce8bd97934", size = 166312, upload-time = "2026-03-23T21:06:55.954Z" } wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" }, ] @@ -4974,18 +4977,16 @@ name = "torchvision" version = "0.23.0" source = { registry = "https://download.pytorch.org/whl/cpu" } resolution-markers = [ - "python_full_version >= '3.13' and platform_machine == 'aarch64' and platform_python_implementation != 'CPython' and sys_platform == 'linux'", - "python_full_version < '3.13' and platform_machine == 'aarch64' and platform_python_implementation != 'CPython' and sys_platform == 'linux'", "python_full_version >= '3.13' and platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", "python_full_version < '3.13' and platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'", "python_full_version >= '3.13' and sys_platform == 'darwin'", "python_full_version < '3.13' and sys_platform == 'darwin'", ] dependencies = [ - { name = "numpy", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, - { name = "pillow", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "numpy", marker = "(platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "pillow", marker = "(platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or sys_platform == 'darwin'" }, { name = "torch", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, - { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, + { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux'" }, ] wheels = [ { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e0e2c04a91403e8dd3af9756c6a024a1d9c0ed9c0d592a8314ded8f4fe30d440" }, @@ -5002,12 +5003,14 @@ version = "0.23.0+cpu" source = { registry = "https://download.pytorch.org/whl/cpu" } resolution-markers = [ "(python_full_version >= '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version >= '3.13' and platform_machine == 'aarch64' and platform_python_implementation != 'CPython' and sys_platform == 'linux'", "(python_full_version < '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version < '3.13' and platform_machine == 'aarch64' and platform_python_implementation != 'CPython' and sys_platform == 'linux'", ] dependencies = [ - { name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "pillow", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "pillow", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.23.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ae459d4509d3b837b978dc6c66106601f916b6d2cda75c137e3f5f48324ce1da" }, @@ -5673,12 +5676,12 @@ wheels = [ [[package]] name = "yuxi" -version = "0.6.0.dev0" +version = "0.6.0.dev2" source = { virtual = "package/yuxi" } [[package]] name = "yuxi-workspace" -version = "0.6.0.dev0" +version = "0.6.0.dev2" source = { virtual = "." } dependencies = [ { name = "agent-sandbox" }, @@ -5744,8 +5747,8 @@ dependencies = [ { name = "tomli-w" }, { name = "torch", version = "2.8.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, { name = "torch", version = "2.8.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, - { name = "torchvision", version = "0.23.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, - { name = "torchvision", version = "0.23.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torchvision", version = "0.23.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and platform_python_implementation == 'CPython' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "torchvision", version = "0.23.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (platform_python_implementation != 'CPython' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, { name = "tqdm" }, { name = "typer" }, { name = "unstructured" }, diff --git a/docs/agents/agents-config.md b/docs/agents/agents-config.md index e87e3dc1..ec72b9a2 100644 --- a/docs/agents/agents-config.md +++ b/docs/agents/agents-config.md @@ -174,7 +174,7 @@ Context 的价值不只在“配置页面”。它贯穿了从配置加载到实 在聊天请求进入后端时,服务会先解析 `agent_config_id`,再加载对应配置。 -当前主流程在 `chat_stream_service.py` 中: +当前主流程在 `chat_service.py` 中: 1. 通过 `agent_config_id` 查找配置 2. 若未指定,则获取该部门下该 Agent 的默认配置