recommend_graph.py 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. from langchain_openai import ChatOpenAI
  2. from langchain_core.messages import SystemMessage, HumanMessage
  3. from app.tools.product_tools import search_product_by_keyword, search_activity_by_keyword, search_article_by_keyword
  4. from app.config import settings
  5. from app.monitoring import monitor_agent
  6. import json
  7. import logging
  8. from app.prompt_service import get_prompt
  9. logger = logging.getLogger(__name__)
  10. SYSTEM_PROMPT = """你是一个儿童成长营养推荐助手。根据用户的需求和营养标签, 推荐合适的商品、活动或文章。
  11. 推荐原则:
  12. 1. 首先尝试使用搜索工具查找匹配的内容
  13. 2. 如果搜索结果为空, 基于你的知识给出建议
  14. 3. 每项推荐必须附带推荐理由
  15. 4. 以 JSON 格式输出推荐结果
  16. 输出格式:
  17. {
  18. "items": [
  19. {
  20. "source": "tool" 或 "knowledge",
  21. "type": "product" / "activity" / "article",
  22. "id": 数字,
  23. "name": "名称",
  24. "description": "描述",
  25. "reason": "为什么推荐这个"
  26. }
  27. ]
  28. }
  29. """
  30. class RecommendAgent:
  31. def __init__(self):
  32. self.llm = ChatOpenAI(
  33. model=settings.llm_model,
  34. api_key=settings.llm_api_key,
  35. base_url=settings.llm_base_url,
  36. temperature=settings.llm_temperature,
  37. )
  38. self.tools = [
  39. search_product_by_keyword,
  40. search_activity_by_keyword,
  41. search_article_by_keyword,
  42. ]
  43. self.llm_with_tools = self.llm.bind_tools(self.tools)
  44. @monitor_agent("recommend")
  45. async def run(self, query: str, tags: list[str], limit: int = 5) -> dict:
  46. """执行推荐 Agent, 返回推荐结果"""
  47. # 如果传入了 tags, 构造搜索关键词
  48. search_query = query or " ".join(tags)
  49. messages = [
  50. SystemMessage(content=await get_prompt("recommend") or SYSTEM_PROMPT),
  51. HumanMessage(content=f"用户需求: {search_query}\n最大返回数量: {limit}\n请搜索并推荐合适的内容。"),
  52. ]
  53. # LangChain Tool calling 自动完成: LLM 决定调哪个 Tool → 工具返回结果 → LLM 组织回答
  54. response = await self.llm_with_tools.ainvoke(messages)
  55. # 尝试解析 JSON 输出
  56. content = response.content
  57. try:
  58. # 提取 JSON 块
  59. if "```json" in content:
  60. json_str = content.split("```json")[1].split("```")[0].strip()
  61. elif "```" in content:
  62. json_str = content.split("```")[1].split("```")[0].strip()
  63. else:
  64. json_str = content.strip()
  65. result = json.loads(json_str)
  66. return result
  67. except (json.JSONDecodeError, IndexError):
  68. # 非 JSON 输出, 包装为文本回答
  69. logger.warning("Agent 输出非 JSON, raw: %s", content[:200])
  70. return {"items": [], "text": content}