From 3405d1b355566676d834085fa9f919dd46e777ed Mon Sep 17 00:00:00 2001 From: Azure <983547216@qq.com> Date: Wed, 9 Sep 2026 09:15:12 +0800 Subject: [PATCH] chef_agent --- lc/chef_agent.py | 15 +++-- lg/__init__.py | 0 lg/fan_in.ipynb | 131 ++++++++++++++++++++++++++++++++++++++++++++ lg/node_error.ipynb | 111 +++++++++++++++++++++++++++++++++++++ pyproject.toml | 1 + uv.lock | 2 + 6 files changed, 256 insertions(+), 4 deletions(-) create mode 100644 lg/__init__.py create mode 100644 lg/fan_in.ipynb create mode 100644 lg/node_error.ipynb diff --git a/lc/chef_agent.py b/lc/chef_agent.py index 8c44f06..d16c12d 100644 --- a/lc/chef_agent.py +++ b/lc/chef_agent.py @@ -8,6 +8,7 @@ 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_deepseek import ChatDeepSeek from langchain_tavily import TavilySearch from langgraph.checkpoint.postgres import PostgresSaver from psycopg import Connection @@ -81,14 +82,20 @@ class ChefAgent: logger.info("chef_agent初始化llm...") - llm = init_chat_model( + # llm = init_chat_model( + # model="deepseek-v4-flash", + # model_provider="deepseek", + # api_key=settings.DEEPSEEK_API_KEY, + # extra_body={"thingking":{"type": "disabled"}} + # ) + llm = ChatDeepSeek( model="deepseek-v4-flash", - model_provider="deepseek", - api_key=settings.DEEPSEEK_API_KEY + api_key=settings.DEEPSEEK_API_KEY, + extra_body={"thingking": {"type": "disabled"}} ) logger.info("chef_agent的llm初始化完成") - return cast(BaseChatModel, llm) + return llm @staticmethod def _create_tools() -> list[Any]: diff --git a/lg/__init__.py b/lg/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lg/fan_in.ipynb b/lg/fan_in.ipynb new file mode 100644 index 0000000..8757467 --- /dev/null +++ b/lg/fan_in.ipynb @@ -0,0 +1,131 @@ +{ + "cells": [ + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2026-09-08T03:13:46.165453300Z", + "start_time": "2026-09-08T03:13:45.410229600Z" + } + }, + "source": [ + "import logging\n", + "from typing import TypedDict\n", + "\n", + "from IPython.display import display\n", + "from langchain_core.runnables import RunnableConfig\n", + "from langgraph.constants import START, END\n", + "from langgraph.graph import StateGraph\n", + "\n", + "logger = logging.getLogger(__name__)\n", + "\n", + "\n", + "# 全局状态\n", + "class EmptyState(TypedDict):\n", + " pass\n", + "\n", + "def node_a(state: EmptyState, config: RunnableConfig) -> EmptyState:\n", + " cur_step = config[\"metadata\"][\"langgraph_step\"]\n", + " print(f\"Node A节点当前在: {cur_step}步\")\n", + " return {}\n", + "\n", + "def node_b(state: EmptyState, config: RunnableConfig) -> EmptyState:\n", + " cur_step = config[\"metadata\"][\"langgraph_step\"]\n", + " print(f\"Node B节点当前在: {cur_step}步\")\n", + " return {}\n", + "\n", + "def node_c(state: EmptyState, config: RunnableConfig) -> EmptyState:\n", + " cur_step = config[\"metadata\"][\"langgraph_step\"]\n", + " print(f\"Node C节点当前在: {cur_step}步\")\n", + " return {}\n", + "\n", + "def node_d(state: EmptyState, config: RunnableConfig) -> EmptyState:\n", + " cur_step = config[\"metadata\"][\"langgraph_step\"]\n", + " print(f\"Node D节点当前在: {cur_step}步\")\n", + " return {}\n", + "\n", + "def node_e(state: EmptyState, config: RunnableConfig) -> EmptyState:\n", + " cur_step = config[\"metadata\"][\"langgraph_step\"]\n", + " print(f\"Node E节点当前在: {cur_step}步\")\n", + " return {}\n", + "\n", + "builder = StateGraph(state_schema=EmptyState)\n", + "\n", + "builder.add_node(\"node_a\", node_a)\n", + "builder.add_node(\"node_b\", node_b)\n", + "builder.add_node(\"node_c\", node_c)\n", + "builder.add_node(\"node_d\", node_d)\n", + "builder.add_node(\"node_e\", node_e)\n", + "\n", + "builder.add_edge(START, \"node_a\")\n", + "builder.add_edge(\"node_a\", \"node_b\")\n", + "builder.add_edge(\"node_a\", \"node_c\")\n", + "builder.add_edge(\"node_b\", \"node_d\")\n", + "# 这种连接边d、c不需要都执行完也会执行e\n", + "# builder.add_edge(\"node_d\", \"node_e\")\n", + "# builder.add_edge(\"node_c\", \"node_e\")\n", + "# 这种连接边d、c都要执行完才会执行e\n", + "builder.add_edge([\"node_c\", \"node_d\"], \"node_e\")\n", + "\n", + "builder.add_edge(\"node_e\", END)\n", + "\n", + "workflow = builder.compile()\n", + "result = workflow.invoke({})\n", + "print(result)\n", + "\n", + "display(workflow)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Node A节点当前在: 1步\n", + "Node B节点当前在: 2步\n", + "Node C节点当前在: 2步\n", + "Node D节点当前在: 3步\n", + "Node E节点当前在: 4步\n", + "None\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ], + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 10 + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/lg/node_error.ipynb b/lg/node_error.ipynb new file mode 100644 index 0000000..3a65cb9 --- /dev/null +++ b/lg/node_error.ipynb @@ -0,0 +1,111 @@ +{ + "cells": [ + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2026-09-08T07:56:57.833862800Z", + "start_time": "2026-09-08T07:56:51.946284300Z" + } + }, + "source": [ + "import asyncio\n", + "import logging\n", + "\n", + "from IPython.display import display\n", + "from langchain_core.runnables import RunnableConfig\n", + "from langgraph.constants import START, END\n", + "from langgraph.graph import StateGraph\n", + "from langgraph.types import RetryPolicy\n", + "from pydantic import BaseModel\n", + "from requests import HTTPError\n", + "\n", + "logger = logging.getLogger(__name__)\n", + "\n", + "\n", + "# 全局状态\n", + "class EmptyState(BaseModel):\n", + " pass\n", + "\n", + "async def node_a(state: EmptyState, config: RunnableConfig) -> EmptyState:\n", + " print(\"11111111111\")\n", + " await asyncio.sleep(5)\n", + " raise HTTPError(\"node_a\")\n", + "\n", + "\n", + "builder = StateGraph(state_schema=EmptyState)\n", + "builder.add_node(\"node_a\", node_a, retry_policy=RetryPolicy(max_attempts=3), timeout=1)\n", + "\n", + "builder.add_edge(START, \"node_a\")\n", + "builder.add_edge(\"node_a\", END)\n", + "\n", + "workflow = builder.compile()\n", + "try:\n", + " result = await workflow.ainvoke({})\n", + " # print(result)\n", + "except HTTPError as e:\n", + " pass\n", + "\n", + "\n", + "display(workflow)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "11111111111\n", + "11111111111\n", + "11111111111\n" + ] + }, + { + "ename": "NodeTimeoutError", + "evalue": "Node 'node_a' exceeded its run timeout of 1.000s (elapsed: 1.007s).", + "output_type": "error", + "traceback": [ + "\u001B[31m---------------------------------------------------------------------------\u001B[39m", + "\u001B[31mTimeoutError\u001B[39m Traceback (most recent call last)", + "\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\_retry.py:489\u001B[39m, in \u001B[36m_arun_with_timeout\u001B[39m\u001B[34m(task, config, timeout, attempt_ctx, stream)\u001B[39m\n\u001B[32m 488\u001B[39m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[32m--> \u001B[39m\u001B[32m489\u001B[39m \u001B[38;5;28;01mawait\u001B[39;00m watchdog\n\u001B[32m 490\u001B[39m \u001B[38;5;28;01mexcept\u001B[39;00m asyncio.TimeoutError \u001B[38;5;28;01mas\u001B[39;00m exc:\n", + "\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\_retry.py:419\u001B[39m, in \u001B[36m_run_timeout_watchdog\u001B[39m\u001B[34m(run_timeout_s)\u001B[39m\n\u001B[32m 418\u001B[39m \u001B[38;5;28;01mawait\u001B[39;00m asyncio.sleep(run_timeout_s)\n\u001B[32m--> \u001B[39m\u001B[32m419\u001B[39m \u001B[38;5;28;01mraise\u001B[39;00m asyncio.TimeoutError\n", + "\u001B[31mTimeoutError\u001B[39m: ", + "\nThe above exception was the direct cause of the following exception:\n", + "\u001B[31mNodeTimeoutError\u001B[39m Traceback (most recent call last)", + "\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[25]\u001B[39m\u001B[32m, line 36\u001B[39m\n\u001B[32m 32\u001B[39m workflow = builder.compile()\n\u001B[32m 33\u001B[39m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[32m 34\u001B[39m result = \u001B[38;5;28;01mawait\u001B[39;00m workflow.ainvoke({})\n\u001B[32m 35\u001B[39m \u001B[38;5;66;03m# print(result)\u001B[39;00m\n\u001B[32m---> \u001B[39m\u001B[32m36\u001B[39m \u001B[38;5;28;01mexcept\u001B[39;00m HTTPError \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[32m 37\u001B[39m \u001B[38;5;28;01mpass\u001B[39;00m\n\u001B[32m 38\u001B[39m \n\u001B[32m 39\u001B[39m \n", + "\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\main.py:4090\u001B[39m, in \u001B[36mPregel.ainvoke\u001B[39m\u001B[34m(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, control, version, **kwargs)\u001B[39m\n\u001B[32m 4087\u001B[39m chunks.append(chunk)\n\u001B[32m 4088\u001B[39m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[32m 4089\u001B[39m \u001B[38;5;66;03m# v1: collect interrupts from updates stream\u001B[39;00m\n\u001B[32m-> \u001B[39m\u001B[32m4090\u001B[39m \u001B[38;5;28;01masync\u001B[39;00m \u001B[38;5;28;01mfor\u001B[39;00m chunk \u001B[38;5;129;01min\u001B[39;00m \u001B[38;5;28mself\u001B[39m.astream(\n\u001B[32m 4091\u001B[39m \u001B[38;5;28minput\u001B[39m,\n\u001B[32m 4092\u001B[39m config,\n\u001B[32m 4093\u001B[39m context=context,\n\u001B[32m 4094\u001B[39m stream_mode=(\n\u001B[32m 4095\u001B[39m [\u001B[33m\"\u001B[39m\u001B[33mupdates\u001B[39m\u001B[33m\"\u001B[39m, \u001B[33m\"\u001B[39m\u001B[33mvalues\u001B[39m\u001B[33m\"\u001B[39m] \u001B[38;5;28;01mif\u001B[39;00m stream_mode == \u001B[33m\"\u001B[39m\u001B[33mvalues\u001B[39m\u001B[33m\"\u001B[39m \u001B[38;5;28;01melse\u001B[39;00m stream_mode\n\u001B[32m 4096\u001B[39m ),\n\u001B[32m 4097\u001B[39m print_mode=print_mode,\n\u001B[32m 4098\u001B[39m output_keys=output_keys,\n\u001B[32m 4099\u001B[39m interrupt_before=interrupt_before,\n\u001B[32m 4100\u001B[39m interrupt_after=interrupt_after,\n\u001B[32m 4101\u001B[39m durability=durability,\n\u001B[32m 4102\u001B[39m control=control,\n\u001B[32m 4103\u001B[39m **kwargs,\n\u001B[32m 4104\u001B[39m ):\n\u001B[32m 4105\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m stream_mode == \u001B[33m\"\u001B[39m\u001B[33mvalues\u001B[39m\u001B[33m\"\u001B[39m:\n\u001B[32m 4106\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mlen\u001B[39m(chunk) == \u001B[32m2\u001B[39m:\n", + "\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\main.py:3440\u001B[39m, in \u001B[36mPregel.astream\u001B[39m\u001B[34m(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, control, subgraphs, debug, version, **kwargs)\u001B[39m\n\u001B[32m 3438\u001B[39m \u001B[38;5;28;01mfor\u001B[39;00m task \u001B[38;5;129;01min\u001B[39;00m \u001B[38;5;28;01mawait\u001B[39;00m loop.amatch_cached_writes():\n\u001B[32m 3439\u001B[39m loop.output_writes(task.id, task.writes, cached=\u001B[38;5;28;01mTrue\u001B[39;00m)\n\u001B[32m-> \u001B[39m\u001B[32m3440\u001B[39m \u001B[38;5;28;01masync\u001B[39;00m \u001B[38;5;28;01mfor\u001B[39;00m _ \u001B[38;5;129;01min\u001B[39;00m runner.atick(\n\u001B[32m 3441\u001B[39m [t \u001B[38;5;28;01mfor\u001B[39;00m t \u001B[38;5;129;01min\u001B[39;00m loop.tasks.values() \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m t.writes],\n\u001B[32m 3442\u001B[39m timeout=\u001B[38;5;28mself\u001B[39m.step_timeout,\n\u001B[32m 3443\u001B[39m get_waiter=get_waiter,\n\u001B[32m 3444\u001B[39m schedule_task=loop.aaccept_push,\n\u001B[32m 3445\u001B[39m ):\n\u001B[32m 3446\u001B[39m \u001B[38;5;66;03m# emit output\u001B[39;00m\n\u001B[32m 3447\u001B[39m \u001B[38;5;28;01mfor\u001B[39;00m o \u001B[38;5;129;01min\u001B[39;00m _output(\n\u001B[32m 3448\u001B[39m stream_mode,\n\u001B[32m 3449\u001B[39m print_mode,\n\u001B[32m (...)\u001B[39m\u001B[32m 3455\u001B[39m _state_mapper,\n\u001B[32m 3456\u001B[39m ):\n\u001B[32m 3457\u001B[39m \u001B[38;5;28;01myield\u001B[39;00m o\n", + "\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\_runner.py:396\u001B[39m, in \u001B[36mPregelRunner.atick\u001B[39m\u001B[34m(self, tasks, reraise, timeout, retry_policy, get_waiter, schedule_task)\u001B[39m\n\u001B[32m 394\u001B[39m scheduled_error_handler = \u001B[38;5;28;01mFalse\u001B[39;00m\n\u001B[32m 395\u001B[39m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[32m--> \u001B[39m\u001B[32m396\u001B[39m \u001B[38;5;28;01mawait\u001B[39;00m arun_with_retry(\n\u001B[32m 397\u001B[39m t,\n\u001B[32m 398\u001B[39m retry_policy,\n\u001B[32m 399\u001B[39m stream=\u001B[38;5;28mself\u001B[39m.use_astream,\n\u001B[32m 400\u001B[39m configurable={\n\u001B[32m 401\u001B[39m CONFIG_KEY_CALL: partial(\n\u001B[32m 402\u001B[39m _acall,\n\u001B[32m 403\u001B[39m weakref.ref(t),\n\u001B[32m 404\u001B[39m stream=\u001B[38;5;28mself\u001B[39m.use_astream,\n\u001B[32m 405\u001B[39m retry_policy=retry_policy,\n\u001B[32m 406\u001B[39m futures=weakref.ref(futures),\n\u001B[32m 407\u001B[39m schedule_task=schedule_task,\n\u001B[32m 408\u001B[39m submit=\u001B[38;5;28mself\u001B[39m.submit,\n\u001B[32m 409\u001B[39m loop=loop,\n\u001B[32m 410\u001B[39m ),\n\u001B[32m 411\u001B[39m },\n\u001B[32m 412\u001B[39m )\n\u001B[32m 413\u001B[39m \u001B[38;5;28mself\u001B[39m.commit(t, \u001B[38;5;28;01mNone\u001B[39;00m)\n\u001B[32m 414\u001B[39m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mException\u001B[39;00m \u001B[38;5;28;01mas\u001B[39;00m exc:\n", + "\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\_retry.py:745\u001B[39m, in \u001B[36marun_with_retry\u001B[39m\u001B[34m(task, retry_policy, stream, match_cached_writes, configurable)\u001B[39m\n\u001B[32m 743\u001B[39m \u001B[38;5;28;01mbreak\u001B[39;00m\n\u001B[32m 744\u001B[39m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;01mawait\u001B[39;00m task.proc.ainvoke(task.input, config)\n\u001B[32m--> \u001B[39m\u001B[32m745\u001B[39m result = \u001B[38;5;28;01mawait\u001B[39;00m _arun_with_timeout(\n\u001B[32m 746\u001B[39m task, config, resolved_timeout, attempt_ctx, stream=stream\n\u001B[32m 747\u001B[39m )\n\u001B[32m 748\u001B[39m _finish_timed_attempt(config, attempt_ctx)\n\u001B[32m 749\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m stream:\n\u001B[32m 750\u001B[39m \u001B[38;5;66;03m# if successful, end\u001B[39;00m\n", + "\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\_retry.py:496\u001B[39m, in \u001B[36m_arun_with_timeout\u001B[39m\u001B[34m(task, config, timeout, attempt_ctx, stream)\u001B[39m\n\u001B[32m 494\u001B[39m bg.cancel()\n\u001B[32m 495\u001B[39m bg.add_done_callback(_drain_cancelled)\n\u001B[32m--> \u001B[39m\u001B[32m496\u001B[39m \u001B[38;5;28;01mraise\u001B[39;00m NodeTimeoutError(\n\u001B[32m 497\u001B[39m task.name,\n\u001B[32m 498\u001B[39m elapsed,\n\u001B[32m 499\u001B[39m kind=kind,\n\u001B[32m 500\u001B[39m idle_timeout=idle_timeout_s,\n\u001B[32m 501\u001B[39m run_timeout=run_timeout_s,\n\u001B[32m 502\u001B[39m ) \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01mexc\u001B[39;00m\n\u001B[32m 503\u001B[39m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mRuntimeError\u001B[39;00m(\n\u001B[32m 504\u001B[39m \u001B[33mf\u001B[39m\u001B[33m\"\u001B[39m\u001B[38;5;132;01m{\u001B[39;00mkind\u001B[38;5;132;01m}\u001B[39;00m\u001B[33m timeout watchdog completed without raising TimeoutError\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m 505\u001B[39m )\n\u001B[32m 506\u001B[39m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mRuntimeError\u001B[39;00m(\u001B[33m\"\u001B[39m\u001B[33mtimeout wait completed without task or watchdog\u001B[39m\u001B[33m\"\u001B[39m)\n", + "\u001B[31mNodeTimeoutError\u001B[39m: Node 'node_a' exceeded its run timeout of 1.000s (elapsed: 1.007s).", + "During task with name 'node_a' and id 'b35c22ae-a4be-38ef-f687-20b76ea2dd87'" + ] + } + ], + "execution_count": 25 + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml index 101da80..52dbcae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ dependencies = [ "apscheduler==3.11.3", "fastapi>=0.141.1", "ipykernel>=7.3.0", + "ipython>=9.17.1", "langchain>=1.4.0", "langchain-core==1.6.2", "langchain-deepseek==1.1.0", diff --git a/uv.lock b/uv.lock index 9ff093b..0c4206c 100644 --- a/uv.lock +++ b/uv.lock @@ -1409,6 +1409,7 @@ dependencies = [ { name = "apscheduler" }, { name = "fastapi" }, { name = "ipykernel" }, + { name = "ipython" }, { name = "langchain" }, { name = "langchain-core" }, { name = "langchain-deepseek" }, @@ -1437,6 +1438,7 @@ requires-dist = [ { name = "apscheduler", specifier = "==3.11.3" }, { name = "fastapi", specifier = ">=0.141.1" }, { name = "ipykernel", specifier = ">=7.3.0" }, + { name = "ipython", specifier = ">=9.17.1" }, { name = "langchain", specifier = ">=1.4.0" }, { name = "langchain-core", specifier = "==1.6.2" }, { name = "langchain-deepseek", specifier = "==1.1.0" },