feat: 添加非流式对话接口和相应的集成测试

This commit is contained in:
Wenjie Zhang 2026-03-26 00:26:45 +08:00
parent 4ad4321992
commit 0836fd8d4f
8 changed files with 404 additions and 23 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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"

View File

@ -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,

View File

@ -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

View File

@ -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" },

View File

@ -174,7 +174,7 @@ Context 的价值不只在“配置页面”。它贯穿了从配置加载到实
在聊天请求进入后端时,服务会先解析 `agent_config_id`,再加载对应配置。
当前主流程在 `chat_stream_service.py` 中:
当前主流程在 `chat_service.py` 中:
1. 通过 `agent_config_id` 查找配置
2. 若未指定,则获取该部门下该 Agent 的默认配置