Files
lotteryServer/lc/chef_agent.py
2026-09-06 19:25:10 +08:00

251 lines
6.8 KiB
Python

from __future__ import annotations
import logging
from typing import Any, cast
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
logger = logging.getLogger(__name__)
class ChefAgent:
"""厨师 Agent。
负责:
- LLM 初始化
- Tool 初始化
- CheckPointer 初始化
- Middleware 初始化
- Agent 初始化
- 生命周期管理
"""
def __init__(self) -> None:
self._pool: ConnectionPool[Connection[Any]] | None = 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",
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[Any]] = ConnectionPool(
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:
raise RuntimeError(
"ChefAgent 尚未初始化,请先调用 initialize()"
)
return self._agent
def close(self) -> None:
"""释放资源。"""
if self._pool is not None:
self._pool.close()
self._pool = None
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()