chef_agent

This commit is contained in:
2026-09-07 13:24:09 +08:00
parent 86ece1926d
commit bc44de93bc
14 changed files with 133 additions and 63 deletions

View File

@@ -38,15 +38,10 @@ def chat(request: ChatRequest):
@agent_router.post("/chat_stream") @agent_router.post("/chat_stream")
def chat(request: ChatRequest): def chat(request: ChatRequest):
def generate_stream(): return StreamingResponse(
result = ChefService.chat_stream( ChefService.chat_stream(
thread_id=request.thread_id, thread_id=request.thread_id,
message=request.message, message=request.message,
) ),
for chunk in result:
yield chunk
return StreamingResponse(
generate_stream(),
media_type="text/plain; charset=utf-8" media_type="text/plain; charset=utf-8"
) )

View File

@@ -1,7 +1,5 @@
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from typing import Any, Dict from typing import Any, Dict
from core.db import SessionDep
from core.dto.api_response import ApiResponse
from core.task.dlt_scheduler import scheduler from core.task.dlt_scheduler import scheduler
# 彩票相关的路由 # 彩票相关的路由

View File

@@ -0,0 +1,24 @@
import logging
import sys
import structlog
def setup_logging():
logging.basicConfig(
format='%(message)s',
stream=sys.stdout,
level=logging.INFO,
)
structlog.configure(
processors=[
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt='iso'),
# structlog.processors.JSONRenderer(ensure_ascii=False), # 生产输出JSON 开发生产2选一
structlog.dev.ConsoleRenderer() # 开发环境彩色控制台
],
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)

View File

@@ -1,30 +1,16 @@
import logging
import sys
from pathlib import Path
from datetime import datetime from datetime import datetime
from typing import List from typing import List
import structlog
from apscheduler.job import Job from apscheduler.job import Job
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.schedulers.base import STATE_PAUSED, STATE_STOPPED, STATE_RUNNING from apscheduler.schedulers.base import STATE_PAUSED, STATE_STOPPED, STATE_RUNNING
from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.cron import CronTrigger
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
# 添加项目根目录到路径
sys.path.append(str(Path(__file__).parent.parent.parent))
from core.task.dlt_spider import DLTSpider from core.task.dlt_spider import DLTSpider
# 配置日志 logger = structlog.getLogger(__name__)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('lottery_scheduler.log', encoding='utf-8'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def test_fun(): def test_fun():
logger.info(f"当前时间---{datetime.now()}") logger.info(f"当前时间---{datetime.now()}")

View File

@@ -1,17 +1,16 @@
import logging
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from typing import Any, List, Dict, Union, Optional, NamedTuple from typing import Any, List, Dict, Union, Optional, NamedTuple
import time import time
import requests import requests
from pydantic import BaseModel, field_validator import structlog
from sqlalchemy import desc from sqlalchemy import desc
from core.db import get_db_session from core.db import get_db_session
from models import LotteryDLT from models import LotteryDLT
logger = logging.getLogger(__name__) logger = structlog.getLogger(__name__)
def parse_draw_numbers(result_str: str) -> List[str]: def parse_draw_numbers(result_str: str) -> List[str]:

0
exceptions/__init__.py Normal file
View File

3
exceptions/errors.py Normal file
View File

@@ -0,0 +1,3 @@
class AgentNotReadyError(RuntimeError):
"""agent未初始化错误"""
pass

24
exceptions/handlers.py Normal file
View File

@@ -0,0 +1,24 @@
from fastapi import Request, FastAPI
from fastapi.responses import JSONResponse
from exceptions.errors import AgentNotReadyError
async def agent_not_ready_handler(request: Request, exc: Exception):
"""agent未就绪的错误"""
return JSONResponse(
status_code=503,
content={
"code": 503,
"message": str(exc)
}
)
# 集中处理 保持main简洁
def register_exception_handlers(app: FastAPI):
"""集中处理注册异常"""
app.add_exception_handler(
AgentNotReadyError,
agent_not_ready_handler,
)

View File

@@ -1,8 +1,8 @@
from __future__ import annotations from __future__ import annotations
import logging
from typing import Any, cast from typing import Any, cast
import structlog
from langchain.agents import create_agent from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware from langchain.agents.middleware import SummarizationMiddleware
from langchain.agents.middleware.summarization import ContextMessages from langchain.agents.middleware.summarization import ContextMessages
@@ -14,8 +14,10 @@ from psycopg import Connection
from psycopg_pool import ConnectionPool from psycopg_pool import ConnectionPool
from core.config.settings import settings from core.config.settings import settings
from exceptions.errors import AgentNotReadyError
logger = structlog.getLogger(__name__)
logger = logging.getLogger(__name__)
class ChefAgent: class ChefAgent:
"""厨师 Agent。 """厨师 Agent。
@@ -30,7 +32,7 @@ class ChefAgent:
""" """
def __init__(self) -> None: def __init__(self) -> None:
self._pool: ConnectionPool[Connection[Any]] | None = None self._pool = None
self._checkpointer: PostgresSaver | None = None self._checkpointer: PostgresSaver | None = None
self._agent = None self._agent = None
@@ -81,7 +83,8 @@ class ChefAgent:
llm = init_chat_model( llm = init_chat_model(
model="deepseek-v4-flash", model="deepseek-v4-flash",
api_key=settings.DEEPSEEK_API_KEY, model_provider="deepseek",
api_key=settings.DEEPSEEK_API_KEY
) )
logger.info("chef_agent的llm初始化完成") logger.info("chef_agent的llm初始化完成")
@@ -122,16 +125,16 @@ class ChefAgent:
logger.info("chef_agent的CheckPointer开始初始化") logger.info("chef_agent的CheckPointer开始初始化")
logger.info("创建PostgreSQL连接池") logger.info("创建PostgreSQL连接池")
self.pool: ConnectionPool[Connection[Any]] = ConnectionPool( self._pool = ConnectionPool[Connection[dict[str, Any]]]( # 指定类型[Connection[dict[str, Any]]]防止编译器提示错误
conninfo=settings.postgres_url, conninfo=settings.postgres_url,
max_size=10, max_size=10,
kwargs={ kwargs={
"autocommit": True, "autocommit": True,
}, }
) )
logger.info("创建checkpointer") logger.info("创建checkpointer")
checkpointer = PostgresSaver(self.pool) checkpointer = PostgresSaver(self._pool)
logger.info("初始化checkpoint数据表") logger.info("初始化checkpoint数据表")
# 创建 LangGraph checkpoint 所需的数据表 # 创建 LangGraph checkpoint 所需的数据表
@@ -223,7 +226,8 @@ class ChefAgent:
"""获取 Agent。""" """获取 Agent。"""
if self._agent is None: if self._agent is None:
raise RuntimeError( logger.info("ChefAgent 尚未初始化,请先调用 initialize()")
raise AgentNotReadyError(
"ChefAgent 尚未初始化,请先调用 initialize()" "ChefAgent 尚未初始化,请先调用 initialize()"
) )
@@ -235,6 +239,7 @@ class ChefAgent:
if self._pool is not None: if self._pool is not None:
self._pool.close() self._pool.close()
self._pool = None self._pool = None
logger.info("PostgreSQL已关闭")
self._checkpointer = None self._checkpointer = None
self._agent = None self._agent = None

37
main.py
View File

@@ -1,4 +1,4 @@
import logging import structlog
import models import models
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
@@ -7,20 +7,18 @@ from fastapi.middleware.cors import CORSMiddleware
from sqlmodel import SQLModel from sqlmodel import SQLModel
from api.agent_api import agent_router from api.agent_api import agent_router
from core.config.logging_cof import setup_logging
from core.db import engine from core.db import engine
from core.config.settings import settings from core.config.settings import settings
from api.scheduler_api import scheduler_router from api.scheduler_api import scheduler_router
from api.lottery_api import lottery_router from api.lottery_api import lottery_router
from core.task.dlt_scheduler import scheduler from core.task.dlt_scheduler import scheduler
from exceptions.handlers import register_exception_handlers
from lc.chef_agent import chef_agent from lc.chef_agent import chef_agent
# 置日志 # 全局设置日志配置
logging.basicConfig( setup_logging()
level=logging.INFO, logger = structlog.getLogger(__name__)
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# lifespan生命周期 # lifespan生命周期
@asynccontextmanager @asynccontextmanager
@@ -30,23 +28,34 @@ async def lifespan(app: FastAPI):
# 同步建表 # 同步建表
SQLModel.metadata.create_all(engine) SQLModel.metadata.create_all(engine)
logger.info("服务启动,启动定时调度器")
# 启动定时调度器 # 启动定时调度器
scheduler.start() scheduler.start()
# 初始化chef_agent logger.info("服务启动,创建携程初始化chef_agent任务")
# 异步初始化chef_agent
chef_agent.initialize() chef_agent.initialize()
yield # 此处交出控制权,服务开始运行 logger.info("服务启动完成")
# ========== 关闭时执行 ========== try:
logger.info("服务关闭,释放资源") yield # 此处交出控制权,服务开始运行
chef_agent.close() finally:
scheduler.shutdown() # ========== 关闭时执行 ==========
logger.info("服务关闭,释放资源")
chef_agent.close()
scheduler.shutdown()
app = FastAPI(lifespan=lifespan) app = FastAPI(lifespan=lifespan)
# 注册全局异常处理
register_exception_handlers(app)
# 注册路由
app.include_router(scheduler_router) app.include_router(scheduler_router)
app.include_router(lottery_router) app.include_router(lottery_router)
app.include_router(agent_router) app.include_router(agent_router)
# 注册中间件
app.add_middleware( app.add_middleware(
CORSMiddleware, # type: ignore[arg-type] CORSMiddleware, # type: ignore[arg-type]
allow_origins=settings.ALLOWED_ORIGINS, allow_origins=settings.ALLOWED_ORIGINS,

View File

@@ -25,5 +25,6 @@ dependencies = [
"sqlalchemy>=2.0.52", "sqlalchemy>=2.0.52",
"sqlmodel==0.0.42", "sqlmodel==0.0.42",
"starlette>=1.6.0", "starlette>=1.6.0",
"structlog>=26.1.0",
"uvicorn==0.52.4", "uvicorn==0.52.4",
] ]

View File

@@ -1,3 +1,5 @@
from typing import Iterator
from langchain_core.messages import HumanMessage from langchain_core.messages import HumanMessage
from langchain_core.runnables import RunnableConfig from langchain_core.runnables import RunnableConfig
@@ -17,7 +19,11 @@ class ChefService:
return result return result
@staticmethod @staticmethod
def chat_stream(thread_id: str, message: str): def chat_stream(thread_id: str, message: str) -> Iterator[str]:
config: RunnableConfig = {"configurable": {"thread_id": thread_id}} config: RunnableConfig = {"configurable": {"thread_id": thread_id}}
result = chef_agent.agent.stream({"messages": [("user", message)]}, config=config, stream_mode="messages") result = chef_agent.agent.stream({"messages": [("user", message)]}, config=config, stream_mode="messages")
return result
for message_chunk, metadata in result:
content = message_chunk.content
if content:
yield content

29
test.py
View File

@@ -1,11 +1,20 @@
sys_prompt = """ a: int | None = None
你是一名有名的厨师。收到用户提供的食材照片或清单后,按照以下流程步骤操作:
1.识别和评估食材:若用户提供照片,首先辨别所有可见食材,基于食材的外观状态,评估其新鲜度和可用量,整理出一份“可用食材清单”。
2.智能食谱检索:优先调用搜索工具,以“可用食材清单”为核心关键词,查找可行菜谱。
3.多维度评估与排序从营养与烹饪难度这2个维度对检索到的候选食谱进行量化打分并根据得分进行排序制作简单且营养丰富的排名靠前。
4.结构化方案输出:把排序后的食谱整理成一份结构清晰的建议报告,要包括食谱信息、得分、推荐理由、食谱的参考图片,帮助用户快速做出决策。
严格按照流程进行操作,搜索不到再自己发挥。
"""
if __name__ == "__main__": def f(v: int) -> int:
print(sys_prompt) pass
def f2(v: int) -> int:
a = 5
f(a)
class Test:
def __init__(self):
self.a: int | None = None
def f(self, v: int) -> int:
pass
def f2(self):
self.a = 5
self.f(self.a)

11
uv.lock generated
View File

@@ -1428,6 +1428,7 @@ dependencies = [
{ name = "sqlalchemy" }, { name = "sqlalchemy" },
{ name = "sqlmodel" }, { name = "sqlmodel" },
{ name = "starlette" }, { name = "starlette" },
{ name = "structlog" },
{ name = "uvicorn" }, { name = "uvicorn" },
] ]
@@ -1455,6 +1456,7 @@ requires-dist = [
{ name = "sqlalchemy", specifier = ">=2.0.52" }, { name = "sqlalchemy", specifier = ">=2.0.52" },
{ name = "sqlmodel", specifier = "==0.0.42" }, { name = "sqlmodel", specifier = "==0.0.42" },
{ name = "starlette", specifier = ">=1.6.0" }, { name = "starlette", specifier = ">=1.6.0" },
{ name = "structlog", specifier = ">=26.1.0" },
{ name = "uvicorn", specifier = "==0.52.4" }, { name = "uvicorn", specifier = "==0.52.4" },
] ]
@@ -2638,6 +2640,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" }, { url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" },
] ]
[[package]]
name = "structlog"
version = "26.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5e/89/b4a0bcfdf4f71a3dea31379f095929613d7e4528a0996bca6aa964cd0dca/structlog-26.1.0.tar.gz", hash = "sha256:f63a716cbd1b1291cf7661de7794b455acfa4c43c5bcf1630e6ad5ddc1adb3b7", size = 1459881, upload-time = "2026-06-06T07:33:39.348Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/18/489c97b834dfff9cf2fc2507cede4bcd4b11e67f84bc462acd1992496f86/structlog-26.1.0-py3-none-any.whl", hash = "sha256:e081a26d6c373e6d201eca24eede26d8ffab07f88f477822e679183428d3d91e", size = 73764, upload-time = "2026-06-06T07:33:38.046Z" },
]
[[package]] [[package]]
name = "tenacity" name = "tenacity"
version = "9.1.4" version = "9.1.4"