feat:初始化工程

This commit is contained in:
2026-05-27 19:04:09 +08:00
commit 613f426aee
11 changed files with 253 additions and 0 deletions

68
.gitignore vendored Normal file
View File

@@ -0,0 +1,68 @@
# Python 字节码文件
__pycache__/
*.py[cod]
*$py.class
# C 扩展
*.so
# 分发/打包
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# 虚拟环境
venv/
env/
ENV/
.env
.venv
# 测试
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Django 相关
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
media/
# PyCharm IDE
.idea/
*.iml
*.iws
*.ipr
# VS Code
.vscode/
*.code-workspace
.history/
# 其他
.DS_Store
logs/
packages/

11
Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM python:3.10-slim
WORKDIR /app
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' > /etc/timezone
COPY ./packages /app/packages
COPY requirements.txt /app/
RUN pip install --no-cache-dir --no-index --find-links=/app/packages -r requirements.txt
COPY . /app/
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8300"]
# pip download -r requirements.txt -d ./packages --only-binary=:all: --platform manylinux2014_x86_64 -i https://pypi.tuna.tsinghua.edu.cn/simple

45
agent.py Normal file
View File

@@ -0,0 +1,45 @@
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
import sqlite3
from langchain_tavily import TavilySearch
from langgraph.checkpoint.sqlite import SqliteSaver
# 连接sqlite
connection = sqlite3.connect("chief.db", check_same_thread=False)
# 初始化checkpointer
checkpointer = SqliteSaver(connection)
# 自动建表
checkpointer.setup()
# web搜索工具使用tavily作为web搜索工具
web_search = TavilySearch(
tavily_api_key="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",
max_results=5,
topic="general"
)
system_prompt = """
你是一名私人厨师。收到用户提供的清单后,请按以下流程操作:
1.识别和评估食材:根据用户提供的信息,整理出一份“当前可用食材清单”。
2.智能食谱检索:优先调用 web_search 工具,以“可用食材清单”为核心关键词,查找可行菜谱。
3.多维度评估与排序:从营养价值和制作难度两个维度对检索到的候选食谱进行量化打分,并根据得分排序,制作简单且营养丰富的排名靠前。
4.结构化方案输出:把排序后的食谱整理为一份结构清晰的建议报告,要包含食谱信息、得分、推荐理由、食谱的参考图片,帮助用户快速做出决策。
请严格按照流程,优先调用 web_search 工具搜索食谱,搜索不到的情况下才能自己发挥。
"""
model = init_chat_model(
model="qwen3-max-2026-01-23",
model_provider="openai",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
api_key="sk-52bcd98e9c1d45908437c4e8706eefff",
temperature=1.5,
)
agent = create_agent(
model=model, # 模型
tools=[web_search], # 工具
checkpointer=checkpointer, # 记忆
system_prompt=system_prompt # 系统提示词
)

BIN
chief.db Normal file

Binary file not shown.

BIN
chief.db-shm Normal file

Binary file not shown.

BIN
chief.db-wal Normal file

Binary file not shown.

19
main.py Normal file
View File

@@ -0,0 +1,19 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from router import router
# 初始化FastAPI
app = FastAPI(
title="Personal Chief API",
description="私厨",
version="0.1.0"
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(router)

4
model.py Normal file
View File

@@ -0,0 +1,4 @@
from pydantic import BaseModel
class ChatRequest(BaseModel):
message: str

9
requirements.txt Normal file
View File

@@ -0,0 +1,9 @@
fastapi~=0.136.3
uvicorn~=0.48.0
langchain~=1.3.2
langchain-openai~=1.2.2
langchain-tavily~=0.2.18
langgraph~=1.2.2
langgraph-checkpoint-sqlite~=3.1.0
pydantic~=2.12.5
langchain-core~=1.4.0

32
router.py Normal file
View File

@@ -0,0 +1,32 @@
import uuid
from fastapi import APIRouter
from fastapi.responses import StreamingResponse
from model import ChatRequest
from service import get_messages, clear_messages, search_recipes
router = APIRouter()
@router.post("/chat/stream")
async def chat_endpoint(request: ChatRequest):
"""流式对话"""
return StreamingResponse(
search_recipes(request.message, str(uuid.uuid4())),
media_type="text/event-stream"
)
@router.get("/chat/messages")
async def get_chat_messages(thread_id: str):
"""获取历史消息"""
messages = get_messages(thread_id)
return {"messages": messages}
@router.delete("/chat/messages")
async def clear_chat_messages(thread_id: str):
"""清空历史消息"""
clear_messages(thread_id)
return {"success": True}

65
service.py Normal file
View File

@@ -0,0 +1,65 @@
from langchain_core.messages import HumanMessage, AIMessageChunk, AIMessage
from agent import agent, checkpointer
async def search_recipes(prompt: str, thread_id: str):
"""调用agent搜索食谱"""
print(f"[用户]: {prompt}, thread_id: {thread_id}")
try:
message = HumanMessage(content=prompt)
# 流式调用Agent
for chunk, metadata in agent.stream(
{"messages": [message]},
{"configurable": {"thread_id": thread_id}},
stream_mode="messages"
):
if isinstance(chunk, AIMessageChunk) and chunk.content:
yield chunk.content
except Exception as e:
print(f"\n[错误]: {str(e)}")
yield "信息检索失败,试试看手动输入食物列表?"
# 清空会话
def clear_messages(thread_id: str):
"""清空会话"""
print(f"清空历史消息thread_id: {thread_id}")
checkpointer.delete_thread(thread_id)
# 查询会话历史
def get_messages(thread_id: str) -> list[dict[str, str]]:
"""获取会话历史"""
print(f"获取历史消息thread_id: {thread_id}")
# 根据 thread_id 查询 checkpoint
checkpoint = checkpointer.get({"configurable": {"thread_id": thread_id}})
# 如果不存在,返回空列表
if not checkpoint:
return []
# 安全获取 messages
channel_values = checkpoint.get("channel_values")
if not channel_values:
return []
messages = channel_values.get("messages", [])
if not messages:
return []
# 转换消息格式
result = []
for msg in messages:
if not msg.content:
continue
if isinstance(msg, HumanMessage):
result.append({"role": "user", "content": msg.content})
elif isinstance(msg, AIMessage):
result.append({"role": "assistant", "content": msg.content})
return result