From 613f426aee25f5d354a22a23435ae41154e12d1a Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Wed, 27 May 2026 19:04:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=88=9D=E5=A7=8B=E5=8C=96=E5=B7=A5?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 68 +++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 11 ++++++++ agent.py | 45 +++++++++++++++++++++++++++++++ chief.db | Bin 0 -> 4096 bytes chief.db-shm | Bin 0 -> 32768 bytes chief.db-wal | Bin 0 -> 24752 bytes main.py | 19 +++++++++++++ model.py | 4 +++ requirements.txt | 9 +++++++ router.py | 32 ++++++++++++++++++++++ service.py | 65 ++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 253 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 agent.py create mode 100644 chief.db create mode 100644 chief.db-shm create mode 100644 chief.db-wal create mode 100644 main.py create mode 100644 model.py create mode 100644 requirements.txt create mode 100644 router.py create mode 100644 service.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ddac70 --- /dev/null +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2e356fa --- /dev/null +++ b/Dockerfile @@ -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 diff --git a/agent.py b/agent.py new file mode 100644 index 0000000..cea8062 --- /dev/null +++ b/agent.py @@ -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 # 系统提示词 +) diff --git a/chief.db b/chief.db new file mode 100644 index 0000000000000000000000000000000000000000..e491245918cbba150aa05c9bc0521d08e455860c GIT binary patch literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WY7!u;st3JAlr;ljiVtj n8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O6ovo*n>_}y literal 0 HcmV?d00001 diff --git a/chief.db-shm b/chief.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..0b11a6c99236eaa20926506223c0aa0034d7898d GIT binary patch literal 32768 zcmeI)Ee^s!5C+h;{Agqij^N-RI5Y`&;07dw8z49d2jCp2A`x%`V5cDjgFw>0FPUu9 zG`l;me#&Z+MT%5;mJRx8?WG!y7VFz)em_l*%fofQ+ZNS%HNMQsAHPpq)rox{A9=lg zFX`<14xO~db2nAoLkJKcK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1ez?6*K;PC+@anGd=rS7FG8T5 z0x@Yw2((kcY^`?wcU^}-OrsM51PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ bfB*pk1PBlyK!5-N0t5&UAV7e?e+WDQcS|UY literal 0 HcmV?d00001 diff --git a/chief.db-wal b/chief.db-wal new file mode 100644 index 0000000000000000000000000000000000000000..4c6b933d26647b269eedc0ba84b927756d842761 GIT binary patch literal 24752 zcmeI(PiWIn90%|u{j)LFRZq2o_@K&Gtb;QAa}jNA9G!7OISjRWeCV6@9HShCEA+NvRsk`BO z-0=?wIBp+jZ|lRaudi-AdNA@ly|JA9KE{f8fBBD(U$yu8{qmJb$)Gy9S+15;N=8^~ zLEz32!s-g_6|mbhHoJY$Jz;yNKi~OwhXOZzbu1G7#t8#)ZXh0A+pZGIAOHafKmY;| zfB*y_009U<;2#Rqj`BURM1o)0Pt_YmeRfWtov)M)lUlw*;e0{N$Rd$5SxF?m3uLH| zY}x2sRaZ5|(1dWjbd8Ol}S##keQNXGC0_=-Oe9UQLDO172jty z+O+md?^JZ>Hd-%v%}G|8$hKA}=~UHJssFCH%`55xWi!RjedDu~w0F0b{aoMM_;%UP`J3wnhgVO2dHU|m z!H8X0UrMl#ut5L<5P$##AOHafKmY;|fB*y_ur~$lHG+T}M-X@xc=C2;?Ny&WR;&x` z%^An2AOHafKmY;|fB*y_009U<00N$X6Zh|~3p~s{9$n1lqC4afguLcXUcn#d5d`~* zlSj}`BAVAoCPy3@}C55VnMz z9}9+KXXEWc&g&DXr0e?-@(A2Khz`@|Mdr7e!~Y?Vz 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