feat: 重构对话线程相关API,统一使用线程ID,移除不必要的智能体ID参数
This commit is contained in:
parent
9f51cfd96b
commit
efecc0692d
@ -54,7 +54,6 @@ class BaseAgent:
|
||||
"name": getattr(self, "name", "Unknown"),
|
||||
"description": getattr(self, "description", "Unknown"),
|
||||
"metadata": metadata,
|
||||
# "examples": metadata.get("examples", []),
|
||||
"configurable_items": configurable_items,
|
||||
"capabilities": getattr(self, "capabilities", []), # 智能体能力列表
|
||||
}
|
||||
|
||||
@ -866,16 +866,10 @@ async def stream_agent_resume(
|
||||
|
||||
async def get_agent_state_view(
|
||||
*,
|
||||
agent_id: str,
|
||||
thread_id: str,
|
||||
current_user_id: str,
|
||||
db,
|
||||
) -> dict:
|
||||
if not agent_manager.get_agent(agent_id):
|
||||
from fastapi import HTTPException
|
||||
|
||||
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
||||
|
||||
conv_repo = ConversationRepository(db)
|
||||
conversation = await conv_repo.get_conversation_by_thread_id(thread_id)
|
||||
if not conversation or conversation.user_id != str(current_user_id) or conversation.status == "deleted":
|
||||
@ -883,29 +877,10 @@ async def get_agent_state_view(
|
||||
|
||||
raise HTTPException(status_code=404, detail="对话线程不存在")
|
||||
|
||||
agent = agent_manager.get_agent(agent_id)
|
||||
agent = agent_manager.get_agent(conversation.agent_id)
|
||||
graph = await agent.get_graph()
|
||||
langgraph_config = {"configurable": {"user_id": str(current_user_id), "thread_id": thread_id}}
|
||||
state = await graph.aget_state(langgraph_config)
|
||||
agent_state = extract_agent_state(getattr(state, "values", {})) if state else {}
|
||||
|
||||
# 如果 state 中没有 files,从附件构建
|
||||
# 这确保了上传附件后立即可以在文件列表中看到文件
|
||||
if not agent_state.get("files") or agent_state["files"] == {}:
|
||||
try:
|
||||
attachments = await conv_repo.get_attachments_by_thread_id(thread_id)
|
||||
logger.info(f"[get_agent_state_view] found {len(attachments)} attachments in DB")
|
||||
if attachments:
|
||||
first_status = attachments[0].get("status")
|
||||
first_has_markdown = bool(attachments[0].get("markdown"))
|
||||
logger.info(
|
||||
f"[get_agent_state_view] first attachment status: {first_status}, "
|
||||
f"has markdown: {first_has_markdown}"
|
||||
)
|
||||
files = _build_state_files(attachments)
|
||||
agent_state["files"] = files
|
||||
logger.info(f"[get_agent_state_view] Built files from attachments: {len(files)} files")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fetch attachments for thread {thread_id}: {e}")
|
||||
|
||||
return {"agent_state": agent_state}
|
||||
|
||||
@ -478,3 +478,66 @@ async def delete_thread_attachment_view(
|
||||
)
|
||||
|
||||
return {"message": "附件已删除"}
|
||||
|
||||
|
||||
async def get_thread_history_view(
|
||||
*,
|
||||
thread_id: str,
|
||||
current_user_id: str,
|
||||
db: AsyncSession,
|
||||
) -> dict:
|
||||
"""获取对话历史消息,包含用户反馈状态"""
|
||||
conv_repo = ConversationRepository(db)
|
||||
conversation = await conv_repo.get_conversation_by_thread_id(thread_id)
|
||||
if not conversation or conversation.user_id != str(current_user_id) or conversation.status == "deleted":
|
||||
raise HTTPException(status_code=404, detail="对话线程不存在")
|
||||
|
||||
messages = await conv_repo.get_messages_by_thread_id(thread_id)
|
||||
|
||||
history: list[dict] = []
|
||||
role_type_map = {"user": "human", "assistant": "ai", "tool": "tool", "system": "system"}
|
||||
|
||||
for msg in messages:
|
||||
user_feedback = None
|
||||
if msg.feedbacks:
|
||||
for feedback in msg.feedbacks:
|
||||
if feedback.user_id == str(current_user_id):
|
||||
user_feedback = {
|
||||
"id": feedback.id,
|
||||
"rating": feedback.rating,
|
||||
"reason": feedback.reason,
|
||||
"created_at": feedback.created_at.isoformat() if feedback.created_at else None,
|
||||
}
|
||||
break
|
||||
|
||||
msg_dict = {
|
||||
"id": msg.id,
|
||||
"type": role_type_map.get(msg.role, msg.role),
|
||||
"content": msg.content,
|
||||
"created_at": msg.created_at.isoformat() if msg.created_at else None,
|
||||
"error_type": msg.extra_metadata.get("error_type") if msg.extra_metadata else None,
|
||||
"error_message": msg.extra_metadata.get("error_message") if msg.extra_metadata else None,
|
||||
"extra_metadata": msg.extra_metadata,
|
||||
"message_type": msg.message_type,
|
||||
"image_content": msg.image_content,
|
||||
"feedback": user_feedback,
|
||||
}
|
||||
|
||||
if msg.tool_calls:
|
||||
msg_dict["tool_calls"] = [
|
||||
{
|
||||
"id": str(tc.id),
|
||||
"name": tc.tool_name,
|
||||
"function": {"name": tc.tool_name},
|
||||
"args": tc.tool_input or {},
|
||||
"tool_call_result": {"content": (tc.tool_output or "")} if tc.status == "success" else None,
|
||||
"status": tc.status,
|
||||
"error_message": tc.error_message,
|
||||
}
|
||||
for tc in msg.tool_calls
|
||||
]
|
||||
|
||||
history.append(msg_dict)
|
||||
|
||||
logger.info(f"Loaded {len(history)} messages with feedback for thread {thread_id}")
|
||||
return {"history": history}
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from yuxi.agents.buildin import agent_manager
|
||||
from yuxi.repositories.conversation_repository import ConversationRepository
|
||||
from yuxi.utils.logging_config import logger
|
||||
|
||||
|
||||
async def get_agent_history_view(
|
||||
*,
|
||||
agent_id: str,
|
||||
thread_id: str,
|
||||
current_user_id: str,
|
||||
db: AsyncSession,
|
||||
) -> dict:
|
||||
if not agent_manager.get_agent(agent_id):
|
||||
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
||||
|
||||
conv_repo = ConversationRepository(db)
|
||||
conversation = await conv_repo.get_conversation_by_thread_id(thread_id)
|
||||
if not conversation or conversation.user_id != str(current_user_id) or conversation.status == "deleted":
|
||||
raise HTTPException(status_code=404, detail="对话线程不存在")
|
||||
|
||||
messages = await conv_repo.get_messages_by_thread_id(thread_id)
|
||||
|
||||
history: list[dict] = []
|
||||
role_type_map = {"user": "human", "assistant": "ai", "tool": "tool", "system": "system"}
|
||||
|
||||
for msg in messages:
|
||||
user_feedback = None
|
||||
if msg.feedbacks:
|
||||
for feedback in msg.feedbacks:
|
||||
if feedback.user_id == str(current_user_id):
|
||||
user_feedback = {
|
||||
"id": feedback.id,
|
||||
"rating": feedback.rating,
|
||||
"reason": feedback.reason,
|
||||
"created_at": feedback.created_at.isoformat() if feedback.created_at else None,
|
||||
}
|
||||
break
|
||||
|
||||
msg_dict = {
|
||||
"id": msg.id,
|
||||
"type": role_type_map.get(msg.role, msg.role),
|
||||
"content": msg.content,
|
||||
"created_at": msg.created_at.isoformat() if msg.created_at else None,
|
||||
"error_type": msg.extra_metadata.get("error_type") if msg.extra_metadata else None,
|
||||
"error_message": msg.extra_metadata.get("error_message") if msg.extra_metadata else None,
|
||||
"extra_metadata": msg.extra_metadata,
|
||||
"message_type": msg.message_type,
|
||||
"image_content": msg.image_content,
|
||||
"feedback": user_feedback,
|
||||
}
|
||||
|
||||
if msg.tool_calls:
|
||||
msg_dict["tool_calls"] = [
|
||||
{
|
||||
"id": str(tc.id),
|
||||
"name": tc.tool_name,
|
||||
"function": {"name": tc.tool_name},
|
||||
"args": tc.tool_input or {},
|
||||
"tool_call_result": {"content": (tc.tool_output or "")} if tc.status == "success" else None,
|
||||
"status": tc.status,
|
||||
"error_message": tc.error_message,
|
||||
}
|
||||
for tc in msg.tool_calls
|
||||
]
|
||||
|
||||
history.append(msg_dict)
|
||||
|
||||
logger.info(f"Loaded {len(history)} messages with feedback for thread {thread_id}")
|
||||
return {"history": history}
|
||||
@ -22,10 +22,12 @@ from yuxi.services.agent_run_service import (
|
||||
get_agent_run_view,
|
||||
stream_agent_run_events,
|
||||
)
|
||||
from yuxi.repositories.conversation_repository import ConversationRepository
|
||||
from yuxi.services.conversation_service import (
|
||||
create_thread_view,
|
||||
delete_thread_attachment_view,
|
||||
delete_thread_view,
|
||||
get_thread_history_view,
|
||||
list_thread_attachments_view,
|
||||
list_threads_view,
|
||||
update_thread_view,
|
||||
@ -37,7 +39,6 @@ from yuxi.services.thread_files_service import (
|
||||
resolve_thread_artifact_view,
|
||||
)
|
||||
from yuxi.services.feedback_service import get_message_feedback_view, submit_message_feedback_view
|
||||
from yuxi.services.history_query_service import get_agent_history_view
|
||||
from yuxi.repositories.agent_config_repository import AgentConfigRepository
|
||||
from yuxi.utils.logging_config import logger
|
||||
from yuxi.utils.image_processor import process_uploaded_image
|
||||
@ -170,20 +171,7 @@ async def call(query: str = Body(...), meta: dict = Body(None), current_user: Us
|
||||
async def get_agent(current_user: User = Depends(get_required_user)):
|
||||
"""获取所有可用智能体的基本信息(需要登录)"""
|
||||
agents_info = await agent_manager.get_agents_info(include_configurable_items=False)
|
||||
|
||||
# Return agents with basic information (without configurable_items for performance)
|
||||
agents = [
|
||||
{
|
||||
"id": agent_info["id"],
|
||||
"name": agent_info.get("name", "Unknown"),
|
||||
"description": agent_info.get("description", ""),
|
||||
"metadata": agent_info.get("metadata", {}),
|
||||
"capabilities": agent_info.get("capabilities", []), # 智能体能力列表
|
||||
}
|
||||
for agent_info in agents_info
|
||||
]
|
||||
|
||||
return {"agents": agents}
|
||||
return {"agents": agents_info}
|
||||
|
||||
|
||||
@chat.get("/agent/{agent_id}")
|
||||
@ -197,14 +185,7 @@ async def get_single_agent(agent_id: str, current_user: User = Depends(get_requi
|
||||
# 获取智能体的完整信息(包含 configurable_items)
|
||||
agent_info = await agent.get_info()
|
||||
|
||||
return {
|
||||
"id": agent_info["id"],
|
||||
"name": agent_info.get("name", "Unknown"),
|
||||
"description": agent_info.get("description", ""),
|
||||
"metadata": agent_info.get("metadata", {}),
|
||||
"configurable_items": agent_info.get("configurable_items", []),
|
||||
"capabilities": agent_info.get("capabilities", []),
|
||||
}
|
||||
return agent_info
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
@ -219,9 +200,6 @@ async def list_agent_configs(
|
||||
current_user: User = Depends(get_required_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if not current_user.department_id:
|
||||
raise HTTPException(status_code=400, detail="当前用户未绑定部门")
|
||||
|
||||
if not agent_manager.get_agent(agent_id):
|
||||
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
||||
|
||||
@ -256,9 +234,6 @@ async def get_agent_config_profile(
|
||||
current_user: User = Depends(get_required_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if not current_user.department_id:
|
||||
raise HTTPException(status_code=400, detail="当前用户未绑定部门")
|
||||
|
||||
if not agent_manager.get_agent(agent_id):
|
||||
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
||||
|
||||
@ -277,9 +252,6 @@ async def create_agent_config_profile(
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if not current_user.department_id:
|
||||
raise HTTPException(status_code=400, detail="当前用户未绑定部门")
|
||||
|
||||
if not agent_manager.get_agent(agent_id):
|
||||
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
||||
|
||||
@ -309,9 +281,6 @@ async def update_agent_config_profile(
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if not current_user.department_id:
|
||||
raise HTTPException(status_code=400, detail="当前用户未绑定部门")
|
||||
|
||||
if not agent_manager.get_agent(agent_id):
|
||||
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
||||
|
||||
@ -340,9 +309,6 @@ async def set_agent_config_default(
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if not current_user.department_id:
|
||||
raise HTTPException(status_code=400, detail="当前用户未绑定部门")
|
||||
|
||||
if not agent_manager.get_agent(agent_id):
|
||||
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
||||
|
||||
@ -362,9 +328,6 @@ async def delete_agent_config_profile(
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if not current_user.department_id:
|
||||
raise HTTPException(status_code=400, detail="当前用户未绑定部门")
|
||||
|
||||
if not agent_manager.get_agent(agent_id):
|
||||
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
|
||||
|
||||
@ -488,17 +451,6 @@ async def stream_run_events(
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@chat.get("/thread/{thread_id}/active_run")
|
||||
async def get_thread_active_run(
|
||||
thread_id: str,
|
||||
current_user: User = Depends(get_required_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取当前会话活跃 run(需要登录)"""
|
||||
return await get_active_run_by_thread(thread_id=thread_id, current_user_id=str(current_user.id), db=db)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# > === 模型管理分组 ===
|
||||
# =============================================================================
|
||||
@ -520,10 +472,9 @@ async def update_chat_models(model_provider: str, model_names: list[str], curren
|
||||
return {"models": conf.model_names[model_provider].models}
|
||||
|
||||
|
||||
@chat.post("/agent/{agent_id}/resume")
|
||||
async def resume_agent_chat(
|
||||
agent_id: str,
|
||||
thread_id: str = Body(...),
|
||||
@chat.post("/thread/{thread_id}/resume")
|
||||
async def resume_thread_chat(
|
||||
thread_id: str,
|
||||
approved: bool | None = Body(None),
|
||||
answer: dict | None = Body(None),
|
||||
config: dict = Body({}),
|
||||
@ -532,6 +483,13 @@ async def resume_agent_chat(
|
||||
):
|
||||
"""恢复被人工审批中断的对话(需要登录)"""
|
||||
|
||||
# 验证 thread 存在且属于当前用户
|
||||
conv_repo = ConversationRepository(db)
|
||||
conversation = await conv_repo.get_conversation_by_thread_id(thread_id)
|
||||
if not conversation or conversation.user_id != str(current_user.id) or conversation.status == "deleted":
|
||||
raise HTTPException(status_code=404, detail="对话线程不存在")
|
||||
agent_id = conversation.agent_id
|
||||
|
||||
def normalize_resume_input(raw_answer: Any, raw_approved: bool | None) -> Any:
|
||||
def normalize_single_answer(value: Any) -> Any:
|
||||
if isinstance(value, str):
|
||||
@ -602,6 +560,7 @@ async def resume_agent_chat(
|
||||
meta["request_id"] = str(uuid.uuid4())
|
||||
return StreamingResponse(
|
||||
stream_agent_resume(
|
||||
agent_id=agent_id,
|
||||
thread_id=thread_id,
|
||||
resume_input=resume_input,
|
||||
meta=meta,
|
||||
@ -613,35 +572,42 @@ async def resume_agent_chat(
|
||||
)
|
||||
|
||||
|
||||
@chat.get("/agent/{agent_id}/history")
|
||||
async def get_agent_history(
|
||||
agent_id: str, thread_id: str, current_user: User = Depends(get_required_user), db: AsyncSession = Depends(get_db)
|
||||
@chat.get("/thread/{thread_id}/active_run")
|
||||
async def get_thread_active_run(
|
||||
thread_id: str,
|
||||
current_user: User = Depends(get_required_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取智能体历史消息(需要登录)- 包含用户反馈状态"""
|
||||
"""获取当前会话活跃 run(需要登录)"""
|
||||
return await get_active_run_by_thread(thread_id=thread_id, current_user_id=str(current_user.id), db=db)
|
||||
|
||||
|
||||
@chat.get("/thread/{thread_id}/history")
|
||||
async def get_thread_history(
|
||||
thread_id: str, current_user: User = Depends(get_required_user), db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""获取对话历史消息(需要登录)- 包含用户反馈状态"""
|
||||
try:
|
||||
return await get_agent_history_view(
|
||||
agent_id=agent_id,
|
||||
return await get_thread_history_view(
|
||||
thread_id=thread_id,
|
||||
current_user_id=str(current_user.id),
|
||||
db=db,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取智能体历史消息出错: {e}, {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"获取智能体历史消息出错: {str(e)}")
|
||||
logger.error(f"获取对话历史消息出错: {e}, {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"获取对话历史消息出错: {str(e)}")
|
||||
|
||||
|
||||
@chat.get("/agent/{agent_id}/state")
|
||||
async def get_agent_state(
|
||||
agent_id: str,
|
||||
@chat.get("/thread/{thread_id}/state")
|
||||
async def get_thread_state(
|
||||
thread_id: str,
|
||||
current_user: User = Depends(get_required_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取智能体当前状态(需要登录)"""
|
||||
"""获取对话当前状态(需要登录)"""
|
||||
try:
|
||||
return await get_agent_state_view(
|
||||
agent_id=agent_id,
|
||||
thread_id=thread_id,
|
||||
current_user_id=str(current_user.id),
|
||||
db=db,
|
||||
@ -649,8 +615,8 @@ async def get_agent_state(
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"获取AgentState出错: {e}, {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"获取AgentState出错: {str(e)}")
|
||||
logger.error(f"获取对话状态出错: {e}, {traceback.format_exc()}")
|
||||
raise HTTPException(status_code=500, detail=f"获取对话状态出错: {str(e)}")
|
||||
|
||||
|
||||
# ==================== 线程管理 API ====================
|
||||
|
||||
@ -131,6 +131,11 @@ async def get_required_user(user: User | None = Depends(get_current_user)):
|
||||
detail="请登录后再访问",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
if not user.department_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="当前用户未绑定部门",
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
|
||||
@ -4,15 +4,52 @@ Integration tests for batch question resume payload validation.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
|
||||
|
||||
|
||||
async def test_resume_rejects_non_dict_answer(test_client, admin_headers):
|
||||
async def _get_agent_id(test_client, headers):
|
||||
"""Get an agent ID for testing."""
|
||||
agents_response = await test_client.get("/api/chat/agent", headers=headers)
|
||||
if agents_response.status_code != 200:
|
||||
pytest.skip(f"Failed to get agents: {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.")
|
||||
|
||||
return agent_id
|
||||
|
||||
|
||||
async def _create_test_thread(test_client, admin_headers) -> str:
|
||||
"""Helper to create a thread for resume testing using /thread endpoint."""
|
||||
agent_id = await _get_agent_id(test_client, admin_headers)
|
||||
thread_id = f"test-thread-{uuid.uuid4().hex[:8]}"
|
||||
|
||||
# Create thread via /thread endpoint
|
||||
response = await test_client.post(
|
||||
"/api/chat/agent/dummy-agent/resume",
|
||||
json={"thread_id": "thread-test", "answer": "approve"},
|
||||
"/api/chat/thread",
|
||||
json={"agent_id": agent_id, "title": "Test thread for resume"},
|
||||
headers=admin_headers,
|
||||
)
|
||||
if response.status_code != 200:
|
||||
pytest.skip(f"Failed to create thread: {response.text}")
|
||||
|
||||
thread_data = response.json()
|
||||
return thread_data.get("id") or thread_id
|
||||
|
||||
|
||||
async def test_resume_rejects_non_dict_answer(test_client, admin_headers):
|
||||
"""Test that non-dict answer is rejected at the API level."""
|
||||
response = await test_client.post(
|
||||
"/api/chat/thread/fake-thread-id/resume",
|
||||
json={"thread_id": "fake-thread-id", "answer": "approve"},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
@ -21,9 +58,11 @@ async def test_resume_rejects_non_dict_answer(test_client, admin_headers):
|
||||
|
||||
|
||||
async def test_resume_rejects_empty_answer_map(test_client, admin_headers):
|
||||
"""Test that empty answer map is rejected."""
|
||||
thread_id = await _create_test_thread(test_client, admin_headers)
|
||||
response = await test_client.post(
|
||||
"/api/chat/agent/dummy-agent/resume",
|
||||
json={"thread_id": "thread-test", "answer": {}},
|
||||
f"/api/chat/thread/{thread_id}/resume",
|
||||
json={"thread_id": thread_id, "answer": {}},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
@ -32,9 +71,11 @@ async def test_resume_rejects_empty_answer_map(test_client, admin_headers):
|
||||
|
||||
|
||||
async def test_resume_rejects_empty_question_id(test_client, admin_headers):
|
||||
"""Test that empty question_id is rejected."""
|
||||
thread_id = await _create_test_thread(test_client, admin_headers)
|
||||
response = await test_client.post(
|
||||
"/api/chat/agent/dummy-agent/resume",
|
||||
json={"thread_id": "thread-test", "answer": {"": "选项A"}},
|
||||
f"/api/chat/thread/{thread_id}/resume",
|
||||
json={"thread_id": thread_id, "answer": {"": "选项A"}},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
@ -43,9 +84,11 @@ async def test_resume_rejects_empty_question_id(test_client, admin_headers):
|
||||
|
||||
|
||||
async def test_resume_rejects_empty_answer_text(test_client, admin_headers):
|
||||
"""Test that empty answer text is rejected."""
|
||||
thread_id = await _create_test_thread(test_client, admin_headers)
|
||||
response = await test_client.post(
|
||||
"/api/chat/agent/dummy-agent/resume",
|
||||
json={"thread_id": "thread-test", "answer": {"q1": " "}},
|
||||
f"/api/chat/thread/{thread_id}/resume",
|
||||
json={"thread_id": thread_id, "answer": {"q1": " "}},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
@ -54,11 +97,14 @@ async def test_resume_rejects_empty_answer_text(test_client, admin_headers):
|
||||
|
||||
|
||||
async def test_resume_accepts_batch_answer_map(test_client, admin_headers):
|
||||
"""Test that valid batch answer map is accepted."""
|
||||
thread_id = await _create_test_thread(test_client, admin_headers)
|
||||
response = await test_client.post(
|
||||
"/api/chat/agent/dummy-agent/resume",
|
||||
json={"thread_id": "thread-test", "answer": {"q1": "选项A", "q2": ["选项B", "选项C"]}},
|
||||
f"/api/chat/thread/{thread_id}/resume",
|
||||
json={"thread_id": thread_id, "answer": {"q1": "选项A", "q2": ["选项B", "选项C"]}},
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
# 通过参数校验后会进入流式逻辑,dummy-agent 不存在时也会以 200 返回 error chunk。
|
||||
assert response.status_code == 200
|
||||
# Will return 200 if conversation exists and is in interrupt state,
|
||||
# or 404 if conversation not found
|
||||
assert response.status_code in (200, 404)
|
||||
|
||||
@ -90,8 +90,8 @@ export const agentApi = {
|
||||
* @param {string} threadId - 会话ID
|
||||
* @returns {Promise} - 历史消息
|
||||
*/
|
||||
getAgentHistory: (agentId, threadId) =>
|
||||
apiGet(`/api/chat/agent/${agentId}/history?thread_id=${threadId}`),
|
||||
getAgentHistory: (threadId) =>
|
||||
apiGet(`/api/chat/thread/${threadId}/history`),
|
||||
|
||||
/**
|
||||
* 获取指定会话的 AgentState
|
||||
@ -99,8 +99,8 @@ export const agentApi = {
|
||||
* @param {string} threadId - 会话ID
|
||||
* @returns {Promise} - AgentState
|
||||
*/
|
||||
getAgentState: (agentId, threadId) =>
|
||||
apiGet(`/api/chat/agent/${agentId}/state?thread_id=${threadId}`),
|
||||
getAgentState: (threadId) =>
|
||||
apiGet(`/api/chat/thread/${threadId}/state`),
|
||||
|
||||
/**
|
||||
* Submit feedback for a message
|
||||
@ -168,14 +168,14 @@ export const agentApi = {
|
||||
* @param {Object} options - 可选参数(signal, headers等)
|
||||
* @returns {Promise} - 恢复响应流
|
||||
*/
|
||||
resumeAgentChat: (agentId, data, options = {}) => {
|
||||
resumeAgentChat: (threadId, data, options = {}) => {
|
||||
const { signal, headers: extraHeaders, ...restOptions } = options || {}
|
||||
const baseHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
...useUserStore().getAuthHeaders()
|
||||
}
|
||||
|
||||
return fetch(`/api/chat/agent/${agentId}/resume`, {
|
||||
return fetch(`/api/chat/thread/${threadId}/resume`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
signal,
|
||||
|
||||
@ -769,7 +769,7 @@ const fetchThreadMessages = async ({ agentId, threadId, delay = 0 }) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await agentApi.getAgentHistory(agentId, threadId)
|
||||
const response = await agentApi.getAgentHistory(threadId)
|
||||
threadMessages.value[threadId] = response.history || []
|
||||
} catch (error) {
|
||||
handleChatError(error, 'load')
|
||||
@ -808,9 +808,9 @@ const refreshThreadFilesAndAttachments = async (threadId) => {
|
||||
}
|
||||
|
||||
const fetchAgentState = async (agentId, threadId) => {
|
||||
if (!agentId || !threadId) return
|
||||
if (!threadId) return
|
||||
try {
|
||||
const res = await agentApi.getAgentState(agentId, threadId)
|
||||
const res = await agentApi.getAgentState(threadId)
|
||||
const targetChatId = currentChatId.value || threadId
|
||||
const ts = getThreadState(targetChatId)
|
||||
if (ts) {
|
||||
|
||||
@ -106,7 +106,7 @@ export function useApproval({ getThreadState, resetOnGoingConv, fetchThreadMessa
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await agentApi.resumeAgentChat(currentAgentId, requestBody, {
|
||||
const response = await agentApi.resumeAgentChat(threadId, requestBody, {
|
||||
signal: threadState.streamAbortController?.signal
|
||||
})
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user