search_route_support.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. """搜索路由辅助 —— 工具调用追踪、错误解析与用户友好消息构建."""
  2. from __future__ import annotations
  3. from contextlib import contextmanager
  4. from typing import Any, Iterator, List, Optional
  5. from pydantic import BaseModel
  6. SEARCH_AGENT_ERROR_MESSAGES: dict[str, str] = {
  7. "search_agent_init_timeout": "检索服务初始化超时,请稍后重试。",
  8. "search_agent_timeout": "检索超时,请稍后重试或缩短描述。",
  9. "search_agent_intent_failed": "暂时无法理解检索意图(LLM 不可用或返回异常),请改写为更具体的会议/主题/年份。",
  10. "search_agent_internal_error": "检索服务内部错误,请查看后端日志或稍后重试。",
  11. "search_agent_stream_incomplete": "检索流未正常结束,请重试。",
  12. "search_agent_llm_unavailable": "未配置 LLM,无法解析复杂检索意图;请配置 API Key 或使用更明确的会议+年份查询。",
  13. }
  14. class ToolCallInfo(BaseModel):
  15. name: str
  16. status: str
  17. params: Optional[dict[str, Any]] = None
  18. result_summary: Optional[str] = None
  19. def user_facing_error_message(code: str) -> str:
  20. return SEARCH_AGENT_ERROR_MESSAGES.get(code, code or "search_agent_error")
  21. @contextmanager
  22. def track_tool_call(
  23. tool_calls: List[ToolCallInfo],
  24. name: str,
  25. params: Optional[dict[str, Any]] = None,
  26. ) -> Iterator[ToolCallInfo]:
  27. tc = ToolCallInfo(name=name, status="running", params=params)
  28. tool_calls.append(tc)
  29. try:
  30. yield tc
  31. if tc.status == "running":
  32. tc.status = "success"
  33. except Exception as e:
  34. tc.status = "error"
  35. if not tc.result_summary:
  36. tc.result_summary = f"执行失败: {str(e)[:120]}"
  37. raise
  38. def normalize_tool_calls(tool_calls: List[Any]) -> List[ToolCallInfo]:
  39. safe_calls: List[ToolCallInfo] = []
  40. for x in tool_calls:
  41. if isinstance(x, ToolCallInfo):
  42. safe_calls.append(x)
  43. elif isinstance(x, dict):
  44. try:
  45. safe_calls.append(ToolCallInfo(**x))
  46. except Exception:
  47. safe_calls.append(
  48. ToolCallInfo(
  49. name="tool_call",
  50. status="error",
  51. result_summary=str(x)[:200],
  52. )
  53. )
  54. else:
  55. safe_calls.append(
  56. ToolCallInfo(name="tool_call", status="error", result_summary=str(x)[:200])
  57. )
  58. return safe_calls
  59. def last_pipeline_tool_error(tool_calls: List[ToolCallInfo]) -> Optional[str]:
  60. for tc in reversed(tool_calls or []):
  61. if getattr(tc, "name", None) != "search_pipeline":
  62. continue
  63. if getattr(tc, "status", None) != "error":
  64. continue
  65. s = (getattr(tc, "result_summary", None) or "").strip()
  66. return s or "search_pipeline_error"
  67. return None