{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "b1a31a19", "metadata": {}, "outputs": [], "source": [ "import os\n", "from langchain_openai import ChatOpenAI\n", "from langchain_core.tools import tool\n", "from langchain.agents import create_agent\n", "from dotenv import load_dotenv\n", "\n", "load_dotenv()\n", "\n", "# ============================================================\n", "# 第一步:初始化模型\n", "# ============================================================\n", "model = ChatOpenAI(\n", " model_name=\"deepseek-chat\",\n", " api_key=os.getenv(\"OPENAI_API_KEY\"),\n", " base_url=\"https://api.deepseek.com\"\n", ")\n", "\n", "\n", "# ============================================================\n", "# 第二步:定义工具(只有两个,一清二楚)\n", "# docstring 就是给模型的\"使用说明书\"\n", "# ============================================================\n", "\n", "@tool\n", "def get_weather(city: str) -> str:\n", " \"\"\"查询指定城市的实时天气。\n", "\n", " 当用户问\"某地天气怎么样\"、\"热不热\"、\"要不要带伞\"时调用。\n", "\n", " Args:\n", " city: 城市名称,例如\"北京\"、\"上海\"、\"深圳\"\n", " \"\"\"\n", " # 模拟天气数据(实际项目中对接真实天气 API)\n", " weather_db = {\n", " \"北京\": \"☀️ 晴天,32°C,湿度 45%,适合出行\",\n", " \"上海\": \"🌧️ 小雨,28°C,湿度 80%,建议带伞\",\n", " \"深圳\": \"⛅ 多云,30°C,湿度 65%\",\n", " }\n", " return weather_db.get(city, f\"抱歉,暂无「{city}」的天气数据\")\n", "\n", "\n", "@tool\n", "def calculator(expression: str) -> str:\n", " \"\"\"执行数学计算,支持加减乘除和括号。\n", "\n", " 当用户说\"帮我算\"、\"等于多少\"、\"计算一下\"时调用。\n", "\n", " Args:\n", " expression: 数学表达式,例如 \"128 * 456\"、\"2 ** 10\"、\"(88 + 12) / 5\"\n", " \"\"\"\n", " try:\n", " result = eval(expression)\n", " return f\"🧮 {expression} = {result}\"\n", " except Exception as e:\n", " return f\"❌ 计算出错:{e}\"\n", "\n", "\n", "# ============================================================\n", "# 第三步:组装 Agent\n", "# create_agent 内部自动构建了 Think→Act→Observe 循环\n", "# ============================================================\n", "tools = [get_weather, calculator]\n", "\n", "agent = create_agent(\n", " model=model,\n", " tools=tools,\n", " system_prompt=(\n", " \"你是一个热心肠的生活助手,会查天气、会做算术。\\n\"\n", " \"规则:\\n\"\n", " \"1. 用户问天气 → 调用 get_weather。\\n\"\n", " \"2. 用户要计算 → 调用 calculator。\\n\"\n", " \"3. 一个请求涉及多个工具时,逐个调用,每次观察结果后再决定下一步。\\n\"\n", " \"4. 闲聊类的问候直接回答,不用调工具。\"\n", " ),\n", ")\n", "\n", "# ============================================================\n", "# 第四步:运行 Agent\n", "# ============================================================\n", "response = agent.invoke({\n", " \"messages\": [\n", " {\"role\": \"user\", \"content\": \"北京今天天气怎么样?顺便帮我算一下 356 × 128\"}\n", " ]\n", "})\n", "\n", "print(response[\"messages\"][-1].content)" ] }, { "cell_type": "code", "execution_count": null, "id": "f213ee6d", "metadata": {}, "outputs": [], "source": [ "# ============================================================\n", "# stream_mode=\"values\"\n", "# 每次迭代拿到的是图的完整状态(messages 的完整列表)\n", "# 适合需要\"每步都有完整上下文\"的场景,比如调试、审计日志\n", "# ============================================================\n", "\n", "# 沿用第一节的 agent 和 tools,直接调用 .stream()\n", "inputs = {\"messages\": [(\"human\", \"帮我查一下上海天气,再算算 999 除以 37 等于多少\")]}\n", "\n", "for chunk in agent.stream(inputs, stream_mode=\"values\"):\n", " # chunk 是一个 dict,key 为 \"messages\",value 是截至目前所有消息的完整列表\n", " last_msg = chunk[\"messages\"][-1]\n", " # pretty_print() 会根据消息类型(人类/AI/工具)用不同颜色格式化输出\n", " last_msg.pretty_print()\n", " print(\"---\") # 分隔符,方便看到每次迭代的边界" ] }, { "cell_type": "code", "execution_count": null, "id": "09974934", "metadata": {}, "outputs": [], "source": [ "# ============================================================\n", "# stream_mode=\"updates\"\n", "# 只返回本轮节点产生的新内容,不是全量\n", "# 适合需要\"追踪每个节点的输入输出\"的场景,比如调试工具调用\n", "# ============================================================\n", "\n", "inputs = {\"messages\": [(\"human\", \"深圳天气怎么样?顺便帮我算一下 (88 + 12) × 5\")]}\n", "\n", "for chunk in agent.stream(inputs, stream_mode=\"updates\"):\n", " # chunk 的结构:{ 节点名称: 该节点本次产出的数据 }\n", " # 例如 {\"agent\": {...}} 或 {\"tools\": {...}}\n", " for node_name, node_output in chunk.items():\n", " print(f\"📍 节点: {node_name}\")\n", " print(f\"📤 产出: {node_output}\")\n", " print(\"=\" * 40)" ] }, { "cell_type": "code", "execution_count": null, "id": "d2749d93", "metadata": {}, "outputs": [], "source": [ "# ============================================================\n", "# stream_mode=\"messages\"\n", "# 最细粒度的流式:LLM 每生成一个 token,你就能拿到它\n", "# 适合聊天 UI 那种\"一个字一个字往外蹦\"的打字机效果\n", "# ============================================================\n", "\n", "inputs = {\"messages\": [(\"human\", \"简单介绍一下敏捷开发中的每日站会\")]}\n", "\n", "for chunk in agent.stream(inputs, stream_mode=\"messages\"):\n", " # chunk 是一个元组:(message_obj, metadata_dict)\n", " message, metadata = chunk\n", "\n", " # 关键:message.content 可能为空字符串\n", " # (比如工具调用的内部消息不产生可见文本)\n", " # 所以必须判断 content 是否有值再打印\n", " if message.content:\n", " # end=\"\" 不换行,flush=True 立即刷新缓冲区 → 打字机效果\n", " print(message.content, end=\"\", flush=True)\n", "\n", "print() # 最后补一个换行" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }