Rean 4 settimane fa
parent
commit
03b2e2c72b

BIN
tmp/agent.db


+ 70 - 276
陈敬安/theme4/theme4_ex3.ipynb

@@ -44,13 +44,54 @@
    ]
   },
   {
+   "cell_type": "markdown",
+   "id": "fc4c03b3",
+   "metadata": {},
+   "source": [
+    "### 主题7. Monitoring\n",
+    "目标: 能够观察服务运行情况,结构化的查看 agent、workflow 的每一步运行,学会 debug\n",
+    "\n",
+    "TODO:\n",
+    "\n",
+    "模型调用tools时会报错,并非流式传输导致"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "fa6aa00c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from langfuse import Langfuse, get_client\n",
+    "import openlit\n",
+    "\n",
+    "langfuse = Langfuse(\n",
+    "  secret_key=os.getenv('LANGFUSE_SECRET_KEY'),\n",
+    "  public_key=os.getenv('LANGFUSE_PUBLIC_KEY'),\n",
+    "  host=os.getenv('LANGFUSE_HOST'),\n",
+    ")\n",
+    "\n",
+    "langfuse = get_client()\n",
+    " \n",
+    "# Verify connection\n",
+    "if langfuse.auth_check():\n",
+    "    print(\"Langfuse client is authenticated and ready!\")\n",
+    "else:\n",
+    "    print(\"Authentication failed. Please check your credentials and host.\")\n",
+    "\n",
+    "print(\"Starting OpenLit with Langfuse integration...\")\n",
+    "openlit.init(tracer=langfuse._otel_tracer, disable_batch=True)"
+   ]
+  },
+  {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": null,
    "id": "41fd0038",
    "metadata": {},
    "outputs": [],
    "source": [
-    "from agno.agent import Agent\n",
+    "from agno.agent import Agent, RunResponseEvent\n",
     "from agno.tools import tool\n",
     "from agno.memory.v2.db.sqlite import SqliteMemoryDb\n",
     "from agno.memory.v2.memory import Memory\n",
@@ -59,7 +100,6 @@
     "from typing import Optional, Tuple\n",
     "from dataclasses import dataclass\n",
     "from enum import Enum\n",
-    "import time\n",
     "\n",
     "SYSTEM_PROMPT = \"\"\"\n",
     "你是一个转账助手,帮助用户完成转账操作。\n",
@@ -166,246 +206,16 @@
     "    enable_user_memories=True,\n",
     "    storage=storage,\n",
     "    add_history_to_messages=True,\n",
+    "    # enable_session_summaries=True,\n",
     ")"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": null,
    "id": "fb2117b1",
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "转账助手已启动。请开始对话。\n"
-     ]
-    },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "a95695bf39f84f5b8c9cdb1251f7995f",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Output()"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "fdfb2b68c45b44619135d51ba709421a",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Output()"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "4e508c5b43a846748cb26de3897265e3",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Output()"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "fbff122df31749959d4b2af37d9e579e",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Output()"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "5237ded427d64f20a9157511cafe0c6a",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Output()"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "daaa1ff32aeb4f98b0c738dc555cf327",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Output()"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "608035432a5d44039c32628a1fc8d460",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Output()"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "a548a49f4dc0491abb69679c71b4d46a",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Output()"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "application/vnd.jupyter.widget-view+json": {
-       "model_id": "e745199fedab4c898e105ee1b3311a25",
-       "version_major": 2,
-       "version_minor": 0
-      },
-      "text/plain": [
-       "Output()"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "对话结束。\n"
-     ]
-    }
-   ],
+   "outputs": [],
    "source": [
     "print(\"转账助手已启动。请开始对话。\")\n",
     "agent.print_response(\"你好\", stream=True, user_id=user_id)\n",
@@ -417,54 +227,38 @@
     "    if not user_input.strip():\n",
     "        print(\"输入不能为空,请重新输入。\")\n",
     "        continue\n",
-    "    # print(f\"用户: {user_input}\")\n",
     "\n",
     "    agent.print_response(user_input, stream=True, user_id=user_id)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": null,
+   "id": "9e622778",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# langfuse version\n",
+    "print(\"转账助手已启动。请开始对话。\")\n",
+    "agent.print_response(\"你好\", stream=True, user_id=user_id)\n",
+    "while True:\n",
+    "    user_input = input(\"用户: \")\n",
+    "    if user_input.lower() in ['exit', 'quit']:\n",
+    "        print(\"对话结束。\")\n",
+    "        break\n",
+    "    if not user_input.strip():\n",
+    "        print(\"输入不能为空,请重新输入。\")\n",
+    "        continue\n",
+    "\n",
+    "    agent.print_response(user_input, stream=False, user_id=user_id)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
    "id": "95207ed5",
    "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Memories about 上个会话:\n"
-     ]
-    },
-    {
-     "data": {
-      "text/html": [
-       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\">[</span>\n",
-       "<span style=\"color: #7fbf7f; text-decoration-color: #7fbf7f\">│   </span><span style=\"color: #800080; text-decoration-color: #800080; font-weight: bold\">UserMemory</span><span style=\"font-weight: bold\">(</span>\n",
-       "<span style=\"color: #7fbf7f; text-decoration-color: #7fbf7f\">│   │   </span><span style=\"color: #808000; text-decoration-color: #808000\">memory</span>=<span style=\"color: #008000; text-decoration-color: #008000\">'Her phone number is 13800138000.'</span>,\n",
-       "<span style=\"color: #7fbf7f; text-decoration-color: #7fbf7f\">│   │   </span><span style=\"color: #808000; text-decoration-color: #808000\">topics</span>=<span style=\"font-weight: bold\">[</span><span style=\"color: #008000; text-decoration-color: #008000\">'contact'</span><span style=\"font-weight: bold\">]</span>,\n",
-       "<span style=\"color: #7fbf7f; text-decoration-color: #7fbf7f\">│   │   </span><span style=\"color: #808000; text-decoration-color: #808000\">input</span>=<span style=\"color: #008000; text-decoration-color: #008000\">'手机号:13800138000'</span>,\n",
-       "<span style=\"color: #7fbf7f; text-decoration-color: #7fbf7f\">│   │   </span><span style=\"color: #808000; text-decoration-color: #808000\">last_updated</span>=<span style=\"color: #800080; text-decoration-color: #800080; font-weight: bold\">datetime</span><span style=\"color: #800080; text-decoration-color: #800080; font-weight: bold\">.datetime</span><span style=\"font-weight: bold\">(</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">2025</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">7</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">22</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">15</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">55</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">33</span>, <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">469818</span><span style=\"font-weight: bold\">)</span>,\n",
-       "<span style=\"color: #7fbf7f; text-decoration-color: #7fbf7f\">│   │   </span><span style=\"color: #808000; text-decoration-color: #808000\">memory_id</span>=<span style=\"color: #008000; text-decoration-color: #008000\">'e9590a07-edbe-410b-b336-c454654dbd8f'</span>\n",
-       "<span style=\"color: #7fbf7f; text-decoration-color: #7fbf7f\">│   </span><span style=\"font-weight: bold\">)</span>\n",
-       "<span style=\"font-weight: bold\">]</span>\n",
-       "</pre>\n"
-      ],
-      "text/plain": [
-       "\u001b[1m[\u001b[0m\n",
-       "\u001b[2;32m│   \u001b[0m\u001b[1;35mUserMemory\u001b[0m\u001b[1m(\u001b[0m\n",
-       "\u001b[2;32m│   │   \u001b[0m\u001b[33mmemory\u001b[0m=\u001b[32m'Her phone number is 13800138000.'\u001b[0m,\n",
-       "\u001b[2;32m│   │   \u001b[0m\u001b[33mtopics\u001b[0m=\u001b[1m[\u001b[0m\u001b[32m'contact'\u001b[0m\u001b[1m]\u001b[0m,\n",
-       "\u001b[2;32m│   │   \u001b[0m\u001b[33minput\u001b[0m=\u001b[32m'手机号:13800138000'\u001b[0m,\n",
-       "\u001b[2;32m│   │   \u001b[0m\u001b[33mlast_updated\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m7\u001b[0m, \u001b[1;36m22\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m55\u001b[0m, \u001b[1;36m33\u001b[0m, \u001b[1;36m469818\u001b[0m\u001b[1m)\u001b[0m,\n",
-       "\u001b[2;32m│   │   \u001b[0m\u001b[33mmemory_id\u001b[0m=\u001b[32m'e9590a07-edbe-410b-b336-c454654dbd8f'\u001b[0m\n",
-       "\u001b[2;32m│   \u001b[0m\u001b[1m)\u001b[0m\n",
-       "\u001b[1m]\u001b[0m\n"
-      ]
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    }
-   ],
+   "outputs": [],
    "source": [
     "from rich.pretty import pprint\n",
     "\n",

BIN
陈敬安/theme4/tmp/agent.db


+ 23 - 0
陈敬安/theme5/Dockerfile

@@ -0,0 +1,23 @@
+FROM python:3.11-slim
+
+# 设置工作目录
+WORKDIR /app
+
+# 复制依赖文件
+COPY requirements.txt .
+
+# 安装依赖
+RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn --no-cache-dir -r requirements.txt
+
+# 复制应用代码和环境变量文件
+COPY theme5_ex4_fastapi.py .
+COPY .env .
+
+# 创建数据目录
+RUN mkdir tmp
+
+# 暴露端口
+EXPOSE 8000
+
+# 启动命令
+CMD ["uvicorn", "theme5_ex4_fastapi:app", "--host", "0.0.0.0", "--port", "8000"]

+ 7 - 0
陈敬安/theme5/requirements.txt

@@ -0,0 +1,7 @@
+fastapi==0.116.1
+uvicorn==0.35.0
+python-dotenv==1.1.1
+sse-starlette==2.4.1
+agno==1.7.2
+SQLAlchemy==2.0.41
+openai==1.95.1

+ 579 - 0
陈敬安/theme5/theme5_ex4_agno.ipynb

@@ -0,0 +1,579 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "a3ae7ae7",
+   "metadata": {},
+   "source": [
+    "### 练习4. Agent API 服务\n",
+    "\n",
+    "#### 目标\n",
+    "1. 使用FastAPI公开Agent的SSE接口\n",
+    "2. 使用一个简单的客户端模拟用户来调用这个SSE\n",
+    "\n",
+    "#### 要求\n",
+    "1. 练习2中的收集信息的AI 通过 Agno实现,可以和用户进行流式对话\n",
+    "2. 通过FastAPI SSE 协议输出Agent Stream Response\n",
+    "    - https://github.com/agno-agi/agent-api \n",
+    "3. 脚本运行在控制台,可以实现打字机效果的信息收集\n",
+    "    - AI 的输出应该和deepseek 一样,是打字机效果\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "6df7a17d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import os\n",
+    "from dotenv import load_dotenv\n",
+    "\n",
+    "load_dotenv()\n",
+    "base_url = os.getenv('BAILIAN_API_BASE_URL')\n",
+    "api_key = os.getenv('BAILIAN_API_KEY')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "id": "500dca93",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from agno.agent import Agent\n",
+    "from agno.tools import tool\n",
+    "from agno.memory.v2.db.sqlite import SqliteMemoryDb\n",
+    "from agno.memory.v2.memory import Memory\n",
+    "from agno.storage.sqlite import SqliteStorage\n",
+    "from agno.models.openai.like import OpenAILike\n",
+    "from typing import Optional, Dict, Any\n",
+    "from enum import Enum\n",
+    "import json\n",
+    "\n",
+    "SYSTEM_PROMPT = \"\"\"\n",
+    "    你是一个信息收集助手。你需要依次收集用户的姓名、年龄和感兴趣的行业。\n",
+    "    如果用户输入其他无关内容,请提醒用户需要先完成信息收集。\n",
+    "    开始对话前,请先介绍自己。\n",
+    "    \n",
+    "    工具使用规则:\n",
+    "    1. 每当用户提供有效信息时,必须立即使用 add_info 工具保存信息\n",
+    "    2. 使用 get_info 工具检查特定信息是否已收集\n",
+    "    3. 使用 list_info 工具确认所有已收集的信息\n",
+    "\n",
+    "    信息收集策略:\n",
+    "    1. 使用开放式问题引导用户提供更多信息\n",
+    "    2. 对模糊的信息进行澄清和确认\n",
+    "    3. 根据上下文,智能地推断和补充相关信息\n",
+    "    4. 保持对话的自然性和连贯性\n",
+    "    5. 务必保证信息的准确性和完整性\n",
+    "    6. 只有当信息符合需要收集的内容的规则时(例如姓名必须是符合姓名规则的,年龄必须是1~100内的数字等等),才将其记录到已收集信息中\n",
+    "    7. 如果用户输入年龄时给出的是出生年份,请帮用户计算实际年龄(今年是2025年)。\n",
+    "    8. 如果用户输入的年龄没有明确的数字,则此输入无效。\n",
+    "\n",
+    "    对话要求:\n",
+    "    1. 保持友好、专业的语调\n",
+    "    2. 每次回复要简洁明了\n",
+    "    3. 每个信息收集成功后,确认用户输入的内容,格式为:\"好的,已经收集到您的(此处为收集的项目)是:\",并询问下一个问题。\n",
+    "    4. 当用户询问一些其他问题时,请礼貱地告诉用户,你是一个信息收集助手,不回答其它不相关问题。提醒用户还没收集完成。\n",
+    "    5. 所有问题回答并收集完毕后,请务必要总结用户的所有信息,并告知用户\"信息已收集完毕\"。\n",
+    "    6. 如果没有对话历史,请先介绍自己,再进行询问。\n",
+    "    7. 如果用户回答与问题无关,请礼貌且友好地提醒用户需要回答相关问题。\n",
+    "    8. 请不要直接将问题内容直接输出,而是要根据用户的回答进行智能化的处理和回复。\n",
+    "\n",
+    "    注意事项:\n",
+    "    除非用户输入与信息收集无关,否则一定要调用对应的tools再给予回复。\n",
+    "    除非用户输入与信息收集无关,否则一定要调用对应的tools再给予回复。\n",
+    "    除非用户输入与信息收集无关,否则一定要调用对应的tools再给予回复。\n",
+    "    除非用户输入与信息收集无关,否则一定要调用对应的tools再给予回复。\n",
+    "    除非用户输入与信息收集无关,否则一定要调用对应的tools再给予回复。\n",
+    "    除非用户输入与信息收集无关,否则一定要调用对应的tools再给予回复。\n",
+    "\"\"\"\n",
+    "\n",
+    "# 初始化空的session状态\n",
+    "initial_state = {\n",
+    "    \"name\": None,\n",
+    "    \"age\": None,\n",
+    "    \"industry\": None\n",
+    "}\n",
+    "\n",
+    "class DataField(str, Enum):\n",
+    "    NAME = \"name\"\n",
+    "    AGE = \"age\"\n",
+    "    INDUSTRY = \"industry\"\n",
+    "\n",
+    "@tool(name=\"add_info\", description=\"添加或修改用户信息\")\n",
+    "def add_info(agent: Agent, field: DataField, value: str) -> str:\n",
+    "    if field == DataField.NAME:\n",
+    "        agent.session_state[\"name\"] = value\n",
+    "        return json.dumps({\"result\": f\"已收集姓名: {value}\"})\n",
+    "    elif field == DataField.AGE:\n",
+    "        try:\n",
+    "            age = int(value)\n",
+    "            agent.session_state[\"age\"] = age\n",
+    "            return json.dumps({\"result\": f\"已收集年龄: {age}\"})\n",
+    "        except ValueError:\n",
+    "            return json.dumps({\"result\": \"年龄输入无效,请输入一个正确的数字。\"})\n",
+    "    elif field == DataField.INDUSTRY:\n",
+    "        agent.session_state[\"industry\"] = value\n",
+    "        return json.dumps({\"result\": f\"已收集感兴趣的行业: {value}\"})\n",
+    "\n",
+    "@tool(name=\"get_info\", description=\"获取用户信息\")\n",
+    "def get_info(agent:Agent, field: DataField) -> str:\n",
+    "    info = agent.session_state.get(field)\n",
+    "    if info is None:\n",
+    "        info = \"未收集\" if field != DataField.AGE else \"未收集\"\n",
+    "    return json.dumps({\"result\": str(info)})\n",
+    "\n",
+    "@tool(name=\"list_info\", description=\"列出所有用户信息\")\n",
+    "def list_info(agent:Agent) -> str:\n",
+    "    user_info = agent.session_state\n",
+    "    info = {\n",
+    "        \"姓名\": user_info.get(\"name\") or \"未收集\",\n",
+    "        \"年龄\": user_info.get(\"age\") or \"未收集\",\n",
+    "        \"感兴趣的行业\": user_info.get(\"industry\") or \"未收集\"\n",
+    "    }\n",
+    "    return json.dumps({\"result\": info})\n",
+    "\n",
+    "# 配置\n",
+    "user_id = \"test\"\n",
+    "session_id = \"325\"\n",
+    "db_file = \"tmp/agent.db\"\n",
+    "\n",
+    "# 模型配置\n",
+    "model = OpenAILike(\n",
+    "    id=\"qwen3-32b\",\n",
+    "    api_key=api_key,\n",
+    "    base_url=base_url,\n",
+    "    request_params={\n",
+    "        \"extra_body\": {\"enable_thinking\": False},\n",
+    "        # \"response_format\": {\"type\": \"json_object\"}\n",
+    "    },\n",
+    ")\n",
+    "\n",
+    "# 初始化内存和存储\n",
+    "memory = Memory(\n",
+    "    model=model,\n",
+    "    db=SqliteMemoryDb(table_name=\"user_memories\", db_file=db_file),\n",
+    ")\n",
+    "storage = SqliteStorage(table_name=\"agent_sessions\", db_file=db_file)\n",
+    "\n",
+    "# 创建agent实例\n",
+    "agent = Agent(\n",
+    "    model=model,\n",
+    "    tools=[add_info, get_info, list_info],\n",
+    "    instructions=SYSTEM_PROMPT,\n",
+    "    add_datetime_to_instructions=True,\n",
+    "    show_tool_calls=True,\n",
+    "    markdown=True,\n",
+    "    memory=memory,\n",
+    "    enable_user_memories=True,\n",
+    "    storage=storage,\n",
+    "    add_history_to_messages=True,\n",
+    "    enable_session_summaries=True,\n",
+    "    session_state=initial_state,\n",
+    "    add_state_in_messages=True,\n",
+    "    session_id=session_id,\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "id": "df49f6e6",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "b4fbe5cc4de0453fb01ba38aebd18695",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Output()"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #800000; text-decoration-color: #800000; font-weight: bold\">ERROR   </span> API status error from OpenAI API: Error code: <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">400</span> - <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'error'</span>: <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'code'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'invalid_parameter_error'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'param'</span>:\n",
+       "         <span style=\"color: #800080; text-decoration-color: #800080; font-style: italic\">None</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'message'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">\"&lt;400&gt; InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in </span>  \n",
+       "         <span style=\"color: #008000; text-decoration-color: #008000\">some form, to use 'response_format' of type 'json_object'.\"</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'type'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'invalid_request_error'</span><span style=\"font-weight: bold\">}</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'id'</span>:      \n",
+       "         <span style=\"color: #008000; text-decoration-color: #008000\">'chatcmpl-1e479ee3-9537-9c96-a975-f7636f971412'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'request_id'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'1e479ee3-9537-9c96-a975-f7636f971412'</span><span style=\"font-weight: bold\">}</span>    \n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[1;31mERROR   \u001b[0m API status error from OpenAI API: Error code: \u001b[1;36m400\u001b[0m - \u001b[1m{\u001b[0m\u001b[32m'error'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'code'\u001b[0m: \u001b[32m'invalid_parameter_error'\u001b[0m, \u001b[32m'param'\u001b[0m:\n",
+       "         \u001b[3;35mNone\u001b[0m, \u001b[32m'message'\u001b[0m: \u001b[32m\"\u001b[0m\u001b[32m<\u001b[0m\u001b[32m400\u001b[0m\u001b[32m>\u001b[0m\u001b[32m InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in \u001b[0m  \n",
+       "         \u001b[32msome form, to use 'response_format' of type 'json_object'.\"\u001b[0m, \u001b[32m'type'\u001b[0m: \u001b[32m'invalid_request_error'\u001b[0m\u001b[1m}\u001b[0m, \u001b[32m'id'\u001b[0m:      \n",
+       "         \u001b[32m'chatcmpl-1e479ee3-9537-9c96-a975-f7636f971412'\u001b[0m, \u001b[32m'request_id'\u001b[0m: \u001b[32m'1e479ee3-9537-9c96-a975-f7636f971412'\u001b[0m\u001b[1m}\u001b[0m    \n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #808000; text-decoration-color: #808000\">WARNING </span> Error in memory/summary operation: <span style=\"font-weight: bold\">&lt;</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">400</span><span style=\"font-weight: bold\">&gt;</span> InternalError.Algo.InvalidParameter: <span style=\"color: #008000; text-decoration-color: #008000\">'messages'</span> must contain the \n",
+       "         word <span style=\"color: #008000; text-decoration-color: #008000\">'json'</span> in some form, to use <span style=\"color: #008000; text-decoration-color: #008000\">'response_format'</span> of type <span style=\"color: #008000; text-decoration-color: #008000\">'json_object'</span>.                                 \n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[33mWARNING \u001b[0m Error in memory/summary operation: \u001b[1m<\u001b[0m\u001b[1;36m400\u001b[0m\u001b[1m>\u001b[0m InternalError.Algo.InvalidParameter: \u001b[32m'messages'\u001b[0m must contain the \n",
+       "         word \u001b[32m'json'\u001b[0m in some form, to use \u001b[32m'response_format'\u001b[0m of type \u001b[32m'json_object'\u001b[0m.                                 \n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "2b2c64e6403f4b44bdf9c6e56bc86f85",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Output()"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #800000; text-decoration-color: #800000; font-weight: bold\">ERROR   </span> API status error from OpenAI API: Error code: <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">400</span> - <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'error'</span>: <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'code'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'invalid_parameter_error'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'param'</span>:\n",
+       "         <span style=\"color: #800080; text-decoration-color: #800080; font-style: italic\">None</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'message'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">\"&lt;400&gt; InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in </span>  \n",
+       "         <span style=\"color: #008000; text-decoration-color: #008000\">some form, to use 'response_format' of type 'json_object'.\"</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'type'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'invalid_request_error'</span><span style=\"font-weight: bold\">}</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'id'</span>:      \n",
+       "         <span style=\"color: #008000; text-decoration-color: #008000\">'chatcmpl-10940767-691f-94db-ae9b-22cecb594092'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'request_id'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'10940767-691f-94db-ae9b-22cecb594092'</span><span style=\"font-weight: bold\">}</span>    \n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[1;31mERROR   \u001b[0m API status error from OpenAI API: Error code: \u001b[1;36m400\u001b[0m - \u001b[1m{\u001b[0m\u001b[32m'error'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'code'\u001b[0m: \u001b[32m'invalid_parameter_error'\u001b[0m, \u001b[32m'param'\u001b[0m:\n",
+       "         \u001b[3;35mNone\u001b[0m, \u001b[32m'message'\u001b[0m: \u001b[32m\"\u001b[0m\u001b[32m<\u001b[0m\u001b[32m400\u001b[0m\u001b[32m>\u001b[0m\u001b[32m InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in \u001b[0m  \n",
+       "         \u001b[32msome form, to use 'response_format' of type 'json_object'.\"\u001b[0m, \u001b[32m'type'\u001b[0m: \u001b[32m'invalid_request_error'\u001b[0m\u001b[1m}\u001b[0m, \u001b[32m'id'\u001b[0m:      \n",
+       "         \u001b[32m'chatcmpl-10940767-691f-94db-ae9b-22cecb594092'\u001b[0m, \u001b[32m'request_id'\u001b[0m: \u001b[32m'10940767-691f-94db-ae9b-22cecb594092'\u001b[0m\u001b[1m}\u001b[0m    \n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #808000; text-decoration-color: #808000\">WARNING </span> Error in memory/summary operation: <span style=\"font-weight: bold\">&lt;</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">400</span><span style=\"font-weight: bold\">&gt;</span> InternalError.Algo.InvalidParameter: <span style=\"color: #008000; text-decoration-color: #008000\">'messages'</span> must contain the \n",
+       "         word <span style=\"color: #008000; text-decoration-color: #008000\">'json'</span> in some form, to use <span style=\"color: #008000; text-decoration-color: #008000\">'response_format'</span> of type <span style=\"color: #008000; text-decoration-color: #008000\">'json_object'</span>.                                 \n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[33mWARNING \u001b[0m Error in memory/summary operation: \u001b[1m<\u001b[0m\u001b[1;36m400\u001b[0m\u001b[1m>\u001b[0m InternalError.Algo.InvalidParameter: \u001b[32m'messages'\u001b[0m must contain the \n",
+       "         word \u001b[32m'json'\u001b[0m in some form, to use \u001b[32m'response_format'\u001b[0m of type \u001b[32m'json_object'\u001b[0m.                                 \n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "d2c852ff4dd14ba48ef28fcbb603ac5d",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Output()"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #800000; text-decoration-color: #800000; font-weight: bold\">ERROR   </span> API status error from OpenAI API: Error code: <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">400</span> - <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'error'</span>: <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'code'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'invalid_parameter_error'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'param'</span>:\n",
+       "         <span style=\"color: #800080; text-decoration-color: #800080; font-style: italic\">None</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'message'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">\"&lt;400&gt; InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in </span>  \n",
+       "         <span style=\"color: #008000; text-decoration-color: #008000\">some form, to use 'response_format' of type 'json_object'.\"</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'type'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'invalid_request_error'</span><span style=\"font-weight: bold\">}</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'id'</span>:      \n",
+       "         <span style=\"color: #008000; text-decoration-color: #008000\">'chatcmpl-64e4ea6b-2d1a-9c6b-8055-31ad9e4f84a7'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'request_id'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'64e4ea6b-2d1a-9c6b-8055-31ad9e4f84a7'</span><span style=\"font-weight: bold\">}</span>    \n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[1;31mERROR   \u001b[0m API status error from OpenAI API: Error code: \u001b[1;36m400\u001b[0m - \u001b[1m{\u001b[0m\u001b[32m'error'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'code'\u001b[0m: \u001b[32m'invalid_parameter_error'\u001b[0m, \u001b[32m'param'\u001b[0m:\n",
+       "         \u001b[3;35mNone\u001b[0m, \u001b[32m'message'\u001b[0m: \u001b[32m\"\u001b[0m\u001b[32m<\u001b[0m\u001b[32m400\u001b[0m\u001b[32m>\u001b[0m\u001b[32m InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in \u001b[0m  \n",
+       "         \u001b[32msome form, to use 'response_format' of type 'json_object'.\"\u001b[0m, \u001b[32m'type'\u001b[0m: \u001b[32m'invalid_request_error'\u001b[0m\u001b[1m}\u001b[0m, \u001b[32m'id'\u001b[0m:      \n",
+       "         \u001b[32m'chatcmpl-64e4ea6b-2d1a-9c6b-8055-31ad9e4f84a7'\u001b[0m, \u001b[32m'request_id'\u001b[0m: \u001b[32m'64e4ea6b-2d1a-9c6b-8055-31ad9e4f84a7'\u001b[0m\u001b[1m}\u001b[0m    \n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #808000; text-decoration-color: #808000\">WARNING </span> Error in memory/summary operation: <span style=\"font-weight: bold\">&lt;</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">400</span><span style=\"font-weight: bold\">&gt;</span> InternalError.Algo.InvalidParameter: <span style=\"color: #008000; text-decoration-color: #008000\">'messages'</span> must contain the \n",
+       "         word <span style=\"color: #008000; text-decoration-color: #008000\">'json'</span> in some form, to use <span style=\"color: #008000; text-decoration-color: #008000\">'response_format'</span> of type <span style=\"color: #008000; text-decoration-color: #008000\">'json_object'</span>.                                 \n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[33mWARNING \u001b[0m Error in memory/summary operation: \u001b[1m<\u001b[0m\u001b[1;36m400\u001b[0m\u001b[1m>\u001b[0m InternalError.Algo.InvalidParameter: \u001b[32m'messages'\u001b[0m must contain the \n",
+       "         word \u001b[32m'json'\u001b[0m in some form, to use \u001b[32m'response_format'\u001b[0m of type \u001b[32m'json_object'\u001b[0m.                                 \n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "a02e667920734d48b41c1b8ae2d858df",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Output()"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #800000; text-decoration-color: #800000; font-weight: bold\">ERROR   </span> API status error from OpenAI API: Error code: <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">400</span> - <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'error'</span>: <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'code'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'invalid_parameter_error'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'param'</span>:\n",
+       "         <span style=\"color: #800080; text-decoration-color: #800080; font-style: italic\">None</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'message'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">\"&lt;400&gt; InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in </span>  \n",
+       "         <span style=\"color: #008000; text-decoration-color: #008000\">some form, to use 'response_format' of type 'json_object'.\"</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'type'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'invalid_request_error'</span><span style=\"font-weight: bold\">}</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'id'</span>:      \n",
+       "         <span style=\"color: #008000; text-decoration-color: #008000\">'chatcmpl-01755646-39d4-92a6-a669-82a9adee8a80'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'request_id'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'01755646-39d4-92a6-a669-82a9adee8a80'</span><span style=\"font-weight: bold\">}</span>    \n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[1;31mERROR   \u001b[0m API status error from OpenAI API: Error code: \u001b[1;36m400\u001b[0m - \u001b[1m{\u001b[0m\u001b[32m'error'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'code'\u001b[0m: \u001b[32m'invalid_parameter_error'\u001b[0m, \u001b[32m'param'\u001b[0m:\n",
+       "         \u001b[3;35mNone\u001b[0m, \u001b[32m'message'\u001b[0m: \u001b[32m\"\u001b[0m\u001b[32m<\u001b[0m\u001b[32m400\u001b[0m\u001b[32m>\u001b[0m\u001b[32m InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in \u001b[0m  \n",
+       "         \u001b[32msome form, to use 'response_format' of type 'json_object'.\"\u001b[0m, \u001b[32m'type'\u001b[0m: \u001b[32m'invalid_request_error'\u001b[0m\u001b[1m}\u001b[0m, \u001b[32m'id'\u001b[0m:      \n",
+       "         \u001b[32m'chatcmpl-01755646-39d4-92a6-a669-82a9adee8a80'\u001b[0m, \u001b[32m'request_id'\u001b[0m: \u001b[32m'01755646-39d4-92a6-a669-82a9adee8a80'\u001b[0m\u001b[1m}\u001b[0m    \n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #808000; text-decoration-color: #808000\">WARNING </span> Error in memory/summary operation: <span style=\"font-weight: bold\">&lt;</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">400</span><span style=\"font-weight: bold\">&gt;</span> InternalError.Algo.InvalidParameter: <span style=\"color: #008000; text-decoration-color: #008000\">'messages'</span> must contain the \n",
+       "         word <span style=\"color: #008000; text-decoration-color: #008000\">'json'</span> in some form, to use <span style=\"color: #008000; text-decoration-color: #008000\">'response_format'</span> of type <span style=\"color: #008000; text-decoration-color: #008000\">'json_object'</span>.                                 \n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[33mWARNING \u001b[0m Error in memory/summary operation: \u001b[1m<\u001b[0m\u001b[1;36m400\u001b[0m\u001b[1m>\u001b[0m InternalError.Algo.InvalidParameter: \u001b[32m'messages'\u001b[0m must contain the \n",
+       "         word \u001b[32m'json'\u001b[0m in some form, to use \u001b[32m'response_format'\u001b[0m of type \u001b[32m'json_object'\u001b[0m.                                 \n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "ae031d34aceb41fcbca3de0448cf8295",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Output()"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #800000; text-decoration-color: #800000; font-weight: bold\">ERROR   </span> API status error from OpenAI API: Error code: <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">400</span> - <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'error'</span>: <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'code'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'invalid_parameter_error'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'param'</span>:\n",
+       "         <span style=\"color: #800080; text-decoration-color: #800080; font-style: italic\">None</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'message'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">\"&lt;400&gt; InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in </span>  \n",
+       "         <span style=\"color: #008000; text-decoration-color: #008000\">some form, to use 'response_format' of type 'json_object'.\"</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'type'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'invalid_request_error'</span><span style=\"font-weight: bold\">}</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'id'</span>:      \n",
+       "         <span style=\"color: #008000; text-decoration-color: #008000\">'chatcmpl-125922ae-83ae-9a7d-8e43-041960af93d4'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'request_id'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'125922ae-83ae-9a7d-8e43-041960af93d4'</span><span style=\"font-weight: bold\">}</span>    \n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[1;31mERROR   \u001b[0m API status error from OpenAI API: Error code: \u001b[1;36m400\u001b[0m - \u001b[1m{\u001b[0m\u001b[32m'error'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'code'\u001b[0m: \u001b[32m'invalid_parameter_error'\u001b[0m, \u001b[32m'param'\u001b[0m:\n",
+       "         \u001b[3;35mNone\u001b[0m, \u001b[32m'message'\u001b[0m: \u001b[32m\"\u001b[0m\u001b[32m<\u001b[0m\u001b[32m400\u001b[0m\u001b[32m>\u001b[0m\u001b[32m InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in \u001b[0m  \n",
+       "         \u001b[32msome form, to use 'response_format' of type 'json_object'.\"\u001b[0m, \u001b[32m'type'\u001b[0m: \u001b[32m'invalid_request_error'\u001b[0m\u001b[1m}\u001b[0m, \u001b[32m'id'\u001b[0m:      \n",
+       "         \u001b[32m'chatcmpl-125922ae-83ae-9a7d-8e43-041960af93d4'\u001b[0m, \u001b[32m'request_id'\u001b[0m: \u001b[32m'125922ae-83ae-9a7d-8e43-041960af93d4'\u001b[0m\u001b[1m}\u001b[0m    \n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #808000; text-decoration-color: #808000\">WARNING </span> Error in memory/summary operation: <span style=\"font-weight: bold\">&lt;</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">400</span><span style=\"font-weight: bold\">&gt;</span> InternalError.Algo.InvalidParameter: <span style=\"color: #008000; text-decoration-color: #008000\">'messages'</span> must contain the \n",
+       "         word <span style=\"color: #008000; text-decoration-color: #008000\">'json'</span> in some form, to use <span style=\"color: #008000; text-decoration-color: #008000\">'response_format'</span> of type <span style=\"color: #008000; text-decoration-color: #008000\">'json_object'</span>.                                 \n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[33mWARNING \u001b[0m Error in memory/summary operation: \u001b[1m<\u001b[0m\u001b[1;36m400\u001b[0m\u001b[1m>\u001b[0m InternalError.Algo.InvalidParameter: \u001b[32m'messages'\u001b[0m must contain the \n",
+       "         word \u001b[32m'json'\u001b[0m in some form, to use \u001b[32m'response_format'\u001b[0m of type \u001b[32m'json_object'\u001b[0m.                                 \n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "f980a267cb2d4fd5bcb75cb53662f636",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "Output()"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #800000; text-decoration-color: #800000; font-weight: bold\">ERROR   </span> API status error from OpenAI API: Error code: <span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">400</span> - <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'error'</span>: <span style=\"font-weight: bold\">{</span><span style=\"color: #008000; text-decoration-color: #008000\">'code'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'invalid_parameter_error'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'param'</span>:\n",
+       "         <span style=\"color: #800080; text-decoration-color: #800080; font-style: italic\">None</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'message'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">\"&lt;400&gt; InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in </span>  \n",
+       "         <span style=\"color: #008000; text-decoration-color: #008000\">some form, to use 'response_format' of type 'json_object'.\"</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'type'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'invalid_request_error'</span><span style=\"font-weight: bold\">}</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'id'</span>:      \n",
+       "         <span style=\"color: #008000; text-decoration-color: #008000\">'chatcmpl-e66c74dc-790f-97ec-8fe7-92317f6ead7b'</span>, <span style=\"color: #008000; text-decoration-color: #008000\">'request_id'</span>: <span style=\"color: #008000; text-decoration-color: #008000\">'e66c74dc-790f-97ec-8fe7-92317f6ead7b'</span><span style=\"font-weight: bold\">}</span>    \n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[1;31mERROR   \u001b[0m API status error from OpenAI API: Error code: \u001b[1;36m400\u001b[0m - \u001b[1m{\u001b[0m\u001b[32m'error'\u001b[0m: \u001b[1m{\u001b[0m\u001b[32m'code'\u001b[0m: \u001b[32m'invalid_parameter_error'\u001b[0m, \u001b[32m'param'\u001b[0m:\n",
+       "         \u001b[3;35mNone\u001b[0m, \u001b[32m'message'\u001b[0m: \u001b[32m\"\u001b[0m\u001b[32m<\u001b[0m\u001b[32m400\u001b[0m\u001b[32m>\u001b[0m\u001b[32m InternalError.Algo.InvalidParameter: 'messages' must contain the word 'json' in \u001b[0m  \n",
+       "         \u001b[32msome form, to use 'response_format' of type 'json_object'.\"\u001b[0m, \u001b[32m'type'\u001b[0m: \u001b[32m'invalid_request_error'\u001b[0m\u001b[1m}\u001b[0m, \u001b[32m'id'\u001b[0m:      \n",
+       "         \u001b[32m'chatcmpl-e66c74dc-790f-97ec-8fe7-92317f6ead7b'\u001b[0m, \u001b[32m'request_id'\u001b[0m: \u001b[32m'e66c74dc-790f-97ec-8fe7-92317f6ead7b'\u001b[0m\u001b[1m}\u001b[0m    \n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #808000; text-decoration-color: #808000\">WARNING </span> Error in memory/summary operation: <span style=\"font-weight: bold\">&lt;</span><span style=\"color: #008080; text-decoration-color: #008080; font-weight: bold\">400</span><span style=\"font-weight: bold\">&gt;</span> InternalError.Algo.InvalidParameter: <span style=\"color: #008000; text-decoration-color: #008000\">'messages'</span> must contain the \n",
+       "         word <span style=\"color: #008000; text-decoration-color: #008000\">'json'</span> in some form, to use <span style=\"color: #008000; text-decoration-color: #008000\">'response_format'</span> of type <span style=\"color: #008000; text-decoration-color: #008000\">'json_object'</span>.                                 \n",
+       "</pre>\n"
+      ],
+      "text/plain": [
+       "\u001b[33mWARNING \u001b[0m Error in memory/summary operation: \u001b[1m<\u001b[0m\u001b[1;36m400\u001b[0m\u001b[1m>\u001b[0m InternalError.Algo.InvalidParameter: \u001b[32m'messages'\u001b[0m must contain the \n",
+       "         word \u001b[32m'json'\u001b[0m in some form, to use \u001b[32m'response_format'\u001b[0m of type \u001b[32m'json_object'\u001b[0m.                                 \n"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"></pre>\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "对话结束。\n"
+     ]
+    }
+   ],
+   "source": [
+    "# 主对话循环\n",
+    "agent.print_response(\"你好\", stream=True, user_id=user_id)\n",
+    "while True:\n",
+    "    user_input = input(\"用户: \")\n",
+    "    if user_input.lower() in ['exit', 'quit']:\n",
+    "        print(\"对话结束。\")\n",
+    "        break\n",
+    "    if not user_input.strip():\n",
+    "        print(\"输入不能为空,请重新输入。\")\n",
+    "        continue\n",
+    "\n",
+    "    agent.print_response(\"\"+user_input, stream=True, user_id=user_id)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "ai-learning",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.13"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}

+ 231 - 0
陈敬安/theme5/theme5_ex4_fastapi.py

@@ -0,0 +1,231 @@
+import os
+from dotenv import load_dotenv
+from agno.agent import Agent, RunResponseEvent
+from agno.tools import tool
+from agno.memory.v2.db.sqlite import SqliteMemoryDb
+from agno.memory.v2.memory import Memory
+from agno.storage.sqlite import SqliteStorage
+from agno.models.openai.like import OpenAILike
+from typing import Iterator, Optional, Dict, Any
+from enum import Enum
+import json
+import uvicorn
+from fastapi import FastAPI, Request
+from fastapi.responses import StreamingResponse
+from sse_starlette.sse import EventSourceResponse
+import asyncio
+import json
+from typing import AsyncGenerator
+
+load_dotenv()
+base_url = os.getenv('BAILIAN_API_BASE_URL')
+api_key = os.getenv('BAILIAN_API_KEY')
+
+SYSTEM_PROMPT = """
+    你是一个信息收集助手。你需要依次收集用户的姓名、年龄和感兴趣的行业。
+    如果用户输入其他无关内容,请提醒用户需要先完成信息收集。
+    开始对话前,请先介绍自己。
+    
+    工具使用规则:
+    1. 每当用户提供有效信息时,必须立即使用 add_info 工具保存信息
+    2. 使用 get_info 工具检查特定信息是否已收集
+    3. 使用 list_info 工具确认所有已收集的信息
+    4. 当用户需要重新收集信息时,使用 reset_info 工具清空所有信息,再重新开始收集
+
+    信息收集策略:
+    1. 使用开放式问题引导用户提供更多信息
+    2. 对模糊的信息进行澄清和确认
+    3. 根据上下文,智能地推断和补充相关信息
+    4. 保持对话的自然性和连贯性
+    5. 务必保证信息的准确性和完整性
+    6. 只有当信息符合需要收集的内容的规则时(例如姓名必须是符合姓名规则的,年龄必须是1~100内的数字等等),才将其记录到已收集信息中
+    7. 如果用户输入年龄时给出的是出生年份,请帮用户计算实际年龄(今年是2025年)。
+    8. 如果用户输入的年龄没有明确的数字,则此输入无效。
+    9. 如果用户一次性将所有信息给出,请将其拆分为单独的字段进行处理,不必重复提问。
+
+    对话要求:
+    1. 保持友好、专业的语调
+    2. 每次回复要简洁明了
+    3. 每个信息收集成功后,确认用户输入的内容,格式为:"好的,已经收集到您的(此处为收集的项目)是:",并询问下一个问题。
+    4. 当用户询问一些其他问题时,请礼貱地告诉用户,你是一个信息收集助手,不回答其它不相关问题。提醒用户还没收集完成。
+    5. 所有问题回答并收集完毕后,请务必要总结用户的所有信息,并告知用户"信息已收集完毕"。
+    6. 如果没有对话历史,请先介绍自己,再进行询问。
+    7. 如果用户回答与问题无关,请礼貌且友好地提醒用户需要回答相关问题。
+    8. 请不要直接将问题内容直接输出,而是要根据用户的回答进行智能化的处理和回复。
+
+    注意事项:
+    除非用户输入与信息收集无关,否则一定要调用对应的tools再给予回复。
+    除非用户输入与信息收集无关,否则一定要调用对应的tools再给予回复。
+    除非用户输入与信息收集无关,否则一定要调用对应的tools再给予回复。
+    除非用户输入与信息收集无关,否则一定要调用对应的tools再给予回复。
+    除非用户输入与信息收集无关,否则一定要调用对应的tools再给予回复。
+    除非用户输入与信息收集无关,否则一定要调用对应的tools再给予回复。
+"""
+
+# 初始化空的session状态
+initial_state = {
+    "name": None,
+    "age": None,
+    "industry": None
+}
+
+class DataField(str, Enum):
+    NAME = "name"
+    AGE = "age"
+    INDUSTRY = "industry"
+
+@tool(name="add_info", description="添加或修改用户信息")
+def add_info(agent: Agent, field: DataField, value: str) -> str:
+    if field == DataField.NAME:
+        agent.session_state["name"] = value
+        return json.dumps({"result": f"已收集姓名: {value}"})
+    elif field == DataField.AGE:
+        try:
+            age = int(value)
+            agent.session_state["age"] = age
+            return json.dumps({"result": f"已收集年龄: {age}"})
+        except ValueError:
+            return json.dumps({"result": "年龄输入无效,请输入一个正确的数字。"})
+    elif field == DataField.INDUSTRY:
+        agent.session_state["industry"] = value
+        return json.dumps({"result": f"已收集感兴趣的行业: {value}"})
+
+@tool(name="add_all_info", description="一次性添加所有用户信息")
+def add_all_info(agent: Agent, name: str, age: str, industry: str) -> str:
+    results = []
+    # 姓名
+    if name and isinstance(name, str):
+        agent.session_state["name"] = name
+        results.append(f"已收集姓名: {name}")
+    else:
+        results.append("姓名输入无效")
+    # 年龄
+    try:
+        age_int = int(age)
+        if 1 <= age_int <= 100:
+            agent.session_state["age"] = age_int
+            results.append(f"已收集年龄: {age_int}")
+        else:
+            results.append("年龄输入无效")
+    except Exception:
+        results.append("年龄输入无效")
+    # 行业
+    if industry and isinstance(industry, str):
+        agent.session_state["industry"] = industry
+        results.append(f"已收集感兴趣的行业: {industry}")
+    else:
+        results.append("行业输入无效")
+    # 检查是否全部收集
+    if all([
+        agent.session_state["name"],
+        agent.session_state["age"],
+        agent.session_state["industry"]
+    ]):
+        summary = {
+            "姓名": agent.session_state["name"],
+            "年龄": agent.session_state["age"],
+            "感兴趣的行业": agent.session_state["industry"]
+        }
+        results.append(f"信息已收集完毕: {summary}")
+    return json.dumps({"result": results})
+
+@tool(name="get_info", description="获取用户信息")
+def get_info(agent:Agent, field: DataField) -> str:
+    info = agent.session_state.get(field)
+    if info is None:
+        info = "未收集" if field != DataField.AGE else "未收集"
+    return json.dumps({"result": str(info)})
+
+@tool(name="list_info", description="列出所有用户信息")
+def list_info(agent:Agent) -> str:
+    user_info = agent.session_state
+    info = {
+        "姓名": user_info.get("name") or "未收集",
+        "年龄": user_info.get("age") or "未收集",
+        "感兴趣的行业": user_info.get("industry") or "未收集"
+    }
+    return json.dumps({"result": info})
+
+@tool(name="reset_info", description="重置所有用户信息")
+def reset_info(agent: Agent) -> str:
+    agent.session_state["name"] = None
+    agent.session_state["age"] = None
+    agent.session_state["industry"] = None
+    return json.dumps({"result": "所有信息已重置"})
+
+# 配置
+user_id = "test"
+session_id = "325"
+db_file = "tmp/agent.db"
+
+# 模型配置
+model = OpenAILike(
+    id="qwen3-32b",
+    api_key=api_key,
+    base_url=base_url,
+    request_params={
+        "extra_body": {"enable_thinking": False},
+        # "response_format": {"type": "json_object"}
+    },
+)
+
+# 初始化内存和存储
+memory = Memory(
+    model=model,
+    db=SqliteMemoryDb(table_name="user_memories", db_file=db_file),
+)
+storage = SqliteStorage(table_name="agent_sessions", db_file=db_file)
+
+# 创建agent实例
+agent = Agent(
+    model=model,
+    tools=[add_info, add_all_info, get_info, list_info, reset_info],
+    instructions=SYSTEM_PROMPT,
+    add_datetime_to_instructions=True,
+    # show_tool_calls=True,
+    markdown=True,
+    memory=memory,
+    enable_user_memories=True,
+    storage=storage,
+    add_history_to_messages=True,
+    enable_session_summaries=True,
+    session_state=initial_state,
+    add_state_in_messages=True,
+    session_id=session_id,
+)
+
+app = FastAPI()
+
+async def generate_agent_response(prompt: str, user_id: str) -> AsyncGenerator[str, None]:
+    """生成Agent响应的异步生成器"""
+    try:
+        response_generator: Iterator[RunResponseEvent] = agent.run(prompt, user_id=user_id, stream=True)
+        for chunk in response_generator:
+            # RunResponseEvent通常是一个对象,需根据其属性获取内容
+            if hasattr(chunk, "content"):
+                yield json.dumps({"type": "message", "content": chunk.content})
+            else:
+                yield json.dumps({"type": "message", "content": str(chunk)})
+            # await asyncio.sleep(0.05)
+        
+        # 发送当前状态
+        state_info = json.loads(list_info(agent))
+        yield json.dumps({"type": "state", "content": state_info["result"]})
+        
+    except Exception as e:
+        yield json.dumps({"type": "error", "content": str(e)})
+
+@app.get("/")
+async def root():
+    return {"message": "信息收集助手 API"}
+
+@app.get("/chat/sse")
+async def chat_sse(prompt: str, user_id: str = "default"):
+    """SSE聊天端点"""
+    return EventSourceResponse(
+        generate_agent_response(prompt, user_id),
+        media_type="text/event-stream"
+    )
+
+if __name__ == "__main__":
+    uvicorn.run(app, host="localhost", port=8000)

+ 105 - 0
陈敬安/theme5/theme5_ex4_sse_client.py

@@ -0,0 +1,105 @@
+import requests
+import json
+import sys
+import os
+from typing import Iterator
+
+class SSEClient:
+    def __init__(self, response):
+        self.response = response
+        self.buffer = ""
+
+    def events(self) -> Iterator[str]:
+        """解析和生成SSE事件"""
+        for chunk in self.response.iter_lines(decode_unicode=True):
+            if chunk:
+                if chunk.startswith("data:"):
+                    yield chunk[5:].strip()  # 移除 "data:" 前缀
+
+def print_with_flush(text: str, end: str = ""):
+    """实现打字机效果的打印函数"""
+    if text is None or " completed in " in text:
+        return
+    print(text, end=end, flush=True)
+
+def format_state_info(state_info: dict) -> str:
+    """格式化状态信息"""
+    return (
+        "\n当前已收集的信息:\n"
+        f"姓名: {state_info.get('姓名', '未收集')}\n"
+        f"年龄: {state_info.get('年龄', '未收集')}\n"
+        f"感兴趣的行业: {state_info.get('感兴趣的行业', '未收集')}\n"
+    )
+
+def chat_with_agent():
+    """与Agent进行SSE对话"""
+    url = "http://localhost:8000/chat/sse"
+    user_id = "test_user"
+    headers = {
+        'Accept': 'text/event-stream',
+        'Cache-Control': 'no-cache',
+        'Connection': 'keep-alive'
+    }
+    
+    print("欢迎使用信息收集助手!(输入 'exit' 或 'quit' 退出)")
+    
+    # 发送初始问候
+    try:
+        response = requests.get(
+            f"{url}?prompt=你好&user_id={user_id}", 
+            stream=True,
+            headers=headers
+        )
+        client = SSEClient(response)
+        
+        # 处理初始响应
+        for event in client.events():
+            try:
+                data = json.loads(event)
+                if data["type"] == "message":
+                    print_with_flush(data["content"])
+                elif data["type"] == "state":
+                    print(format_state_info(data["content"]))
+            except json.JSONDecodeError:
+                print(f"\n警告: 无法解析消息: {event}")
+        
+        # 主对话循环
+        while True:
+            user_input = input("\n请输入(exit/quit以退出): ").strip()
+            if user_input.lower() in ['exit', 'quit']:
+                print("对话结束。")
+                break
+            if not user_input:
+                print("输入不能为空,请重新输入。")
+                continue
+
+            # 发送用户输入并获取流式响应
+            response = requests.get(
+                f"{url}?prompt={user_input}&user_id={user_id}", 
+                stream=True,
+                headers=headers
+            )
+            client = SSEClient(response)
+            
+            # 处理响应
+            for event in client.events():
+                try:
+                    data = json.loads(event)
+                    if data["type"] == "message":
+                        print_with_flush(data["content"])
+                    elif data["type"] == "state":
+                        print(format_state_info(data["content"]))
+                    # elif data["type"] == "error":
+                    #     print(f"\n错误: {data['content']}")
+                except json.JSONDecodeError:
+                    print(f"\n警告: 无法解析消息: {event}")
+                    
+    except KeyboardInterrupt:
+        print("\n对话被用户中断。")
+    except Exception as e:
+        print(f"\n发生错误: {str(e)}")
+    finally:
+        print("\n感谢使用!")
+
+if __name__ == "__main__":
+    chat_with_agent()

BIN
陈敬安/theme5/tmp/agent.db