from __future__ import annotations from typing import Any, cast import structlog from langchain.agents import create_agent from langchain.agents.middleware import SummarizationMiddleware from langchain.agents.middleware.summarization import ContextMessages from langchain.chat_models import init_chat_model from langchain_core.language_models import BaseChatModel from langchain_tavily import TavilySearch from langgraph.checkpoint.postgres import PostgresSaver from psycopg import Connection from psycopg_pool import ConnectionPool from core.config.settings import settings from exceptions.errors import AgentNotReadyError logger = structlog.getLogger(__name__) class ChefAgent: """厨师 Agent。 负责: - LLM 初始化 - Tool 初始化 - CheckPointer 初始化 - Middleware 初始化 - Agent 初始化 - 生命周期管理 """ def __init__(self) -> None: self._pool = None self._checkpointer: PostgresSaver | None = None self._agent = None def initialize(self) -> ChefAgent: """初始化 Agent。 Returns: 当前 Agent 实例,方便链式调用。 """ logger.info("开始初始化chef_agent...") if self._agent is not None: return self # 1. 初始化 LLM llm = self._create_llm() # 2. 初始化工具 tools = self._create_tools() # 3. 初始化 Middleware middleware = self._create_middleware(llm) # 4. 初始化 CheckPointer self._checkpointer = self._create_checkpointer() # 5. 初始化 Agent logger.info("chef_agent的agent开始初始化...") self._agent = create_agent( model=llm, tools=tools, middleware=middleware, checkpointer=self._checkpointer, system_prompt=self._system_prompt(), ) logger.info("chef_agent的agent初始化完成") logger.info("chef_agent初始化完成") return self @staticmethod def _create_llm() -> BaseChatModel: """创建 LLM。""" logger.info("chef_agent初始化llm...") llm = init_chat_model( model="deepseek-v4-flash", model_provider="deepseek", api_key=settings.DEEPSEEK_API_KEY ) logger.info("chef_agent的llm初始化完成") return cast(BaseChatModel, llm) @staticmethod def _create_tools() -> list[Any]: """创建 Agent Tools。""" logger.info("chef_agent工具开始初始化...") search_tool = TavilySearch( tavily_api_key=settings.TAVILY_API_KEY, max_results=5, topic="general", ) logger.info("chef_agent工具初始化完成") return [search_tool] @staticmethod def _create_middleware( llm: BaseChatModel, ) -> list[Any]: """创建 Agent Middleware。""" logger.info("chef_agent中间件开始初始化...") summarization = SummarizationMiddleware( model=llm, trigger=cast(ContextMessages, ("messages", 10)), keep=cast(ContextMessages, ("messages", 5)), ) logger.info("chef_agent中间件初始化完成") return [summarization] def _create_checkpointer(self) -> PostgresSaver: """创建 PostgreSQL CheckPointer。""" logger.info("chef_agent的CheckPointer开始初始化") logger.info("创建PostgreSQL连接池") self._pool = ConnectionPool[Connection[dict[str, Any]]]( # 指定类型[Connection[dict[str, Any]]]防止编译器提示错误 conninfo=settings.postgres_url, max_size=10, kwargs={ "autocommit": True, } ) logger.info("创建checkpointer") checkpointer = PostgresSaver(self._pool) logger.info("初始化checkpoint数据表") # 创建 LangGraph checkpoint 所需的数据表 checkpointer.setup() logger.info("chef_agent的CheckPointer初始化完成") return checkpointer @staticmethod def _system_prompt() -> str: """Agent System Prompt。""" return """ 你是一名专业厨师。 你的任务是根据用户提供的食材照片或食材清单,为用户推荐合适的菜谱。 请严格按照以下流程执行: ## 1. 识别和评估食材 如果用户提供的是食材照片: - 识别照片中可见的食材 - 根据外观判断食材的新鲜程度 - 估算大致可用量 - 排除明显不可食用或状态异常的食材 整理成「可用食材清单」。 如果用户直接提供食材清单,则直接使用用户提供的信息。 ## 2. 搜索菜谱 优先使用搜索工具。 以「可用食材清单」作为核心关键词,搜索适合这些食材的菜谱。 优先考虑: - 食材匹配度高 - 操作简单 - 营养均衡 - 家庭烹饪可执行 除非搜索不到合适结果,否则不要直接凭经验编造菜谱。 ## 3. 评估和排序 对搜索到的候选菜谱进行综合评价。 从以下维度进行评分: - 食材匹配度 - 营养价值 - 烹饪难度 - 烹饪时间 综合评分后进行排序。 优先推荐: 「简单 + 食材匹配度高 + 营养丰富」 的菜谱。 ## 4. 输出结果 最终输出结构化的菜谱推荐报告。 每个推荐至少包含: - 菜谱名称 - 所需食材 - 核心烹饪步骤 - 烹饪时间 - 难度 - 综合评分 - 推荐理由 - 参考来源 如果搜索结果中存在可靠的菜谱图片,则提供图片参考。 如果搜索不到合适菜谱,再根据已有知识进行合理推荐,并明确说明这是基于模型知识给出的建议。 """.strip() @property def agent(self): """获取 Agent。""" if self._agent is None: logger.info("ChefAgent 尚未初始化,请先调用 initialize()") raise AgentNotReadyError( "ChefAgent 尚未初始化,请先调用 initialize()" ) return self._agent def close(self) -> None: """释放资源。""" if self._pool is not None: self._pool.close() self._pool = None logger.info("PostgreSQL已关闭") self._checkpointer = None self._agent = None # 使用with上下文管理方式来使用的话需要这2个方法 # def __enter__(self) -> ChefAgent: # return self.initialize() # # def __exit__(self, exc_type, exc_value, traceback) -> None: # self.close() chef_agent = ChefAgent()