2.agent_dome.py
"""
LangChain Agents 示例
文档: https://docs.langchain.com/oss/python/langchain/agents
主要内容:
1. 基本使用 - create_agent
2. Model 配置
3. 工具定义和调用
4. 中间件 (Middleware)
5. 状态管理 (State)
6. 结构化输出 (Structured Output)
"""
from typing import Any, Callable
from dataclasses import dataclass
from pydantic import BaseModel
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import (
wrap_model_call,
AgentMiddleware,
ModelRequest,
ModelResponse,
)
from langchain.tools import tool
from langchain_deepseek import ChatDeepSeek
from langgraph.store.memory import InMemoryStore
from env_utils import DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL
# ============================================================================
# Demo 1: 基本使用 - 创建简单的 Agent
# ============================================================================
@tool
def search(query: str) -> str:
"""搜索信息"""
# 模拟搜索功能
results = {
"天气": "今天北京天气晴朗,温度22度",
"新闻": "最新科技新闻:AI技术持续发展",
"Python": "Python是一种流行的编程语言",
}
print('search tool 查询内容:', query)
for key in results:
if key in query:
return results[key]
return f"未找到关于 '{query}' 的信息"
@tool
def calculator(expression: str) -> str:
"""计算数学表达式"""
try:
result = eval(expression)
return f"计算结果: {result}"
except Exception as e:
return f"计算错误: {str(e)}"
def demo1_basic_agent():
"""基本使用 - 创建简单的 Agent"""
print("=" * 50)
print("Demo 1: 基本使用 - 创建简单的 Agent")
print("=" * 50)
# 方式1: 使用模型字符串
# agent = create_agent(
# model="deepseek-chat",
# tools=[search, calculator],
# name="simple_assistant"
# )
# 调用 agent
# result = agent.invoke({
# "messages": [{"role": "user", "content": "北京的天气怎么样?"}]
# })
# 方式2: 使用 ChatDeepSeek 模型实例
# model = ChatDeepSeek(
# model="deepseek-chat",
# temperature=0.1,
# max_tokens=1000,
# timeout=30,
# api_key=DEEPSEEK_API_KEY,
# base_url=DEEPSEEK_BASE_URL
# )
# 方式3 推荐
# 调用init_chat_model函数初始化模型,参数model用来指定模型名称,Langchain会根据模型名字自动设定base_url,并从环境变量中获取api_key
from langchain.chat_models import init_chat_model
model = init_chat_model(
model="deepseek-chat",
base_url=DEEPSEEK_BASE_URL, # 这个参数没有用,Langchain会根据模型名字自动设定base_url
api_key=DEEPSEEK_API_KEY,
# temperature=1.5,
)
# 不支持的模型,需要指定模型提供者,ase_url,api_key
# model = init_chat_model(
# # model="qwen-max", # 模型名称,这里可以自定义,我们用的是阿里的qwen-max
# model="deepseek-chat", # 模型名称,这里可以自定义,我们用的是阿里的qwen-max
# # 如果是Langchain不支持的模型,需要指定模型提供者(虽然我们用的是阿里,但是阿里兼容openai,所以这里用openai)
# model_provider="openai",
# base_url=DEEPSEEK_BASE_URL,
# api_key=DEEPSEEK_API_KEY
# )
agent_with_model = create_agent(model, tools=[search, calculator])
# 调用 agent
result = agent_with_model.invoke({
"messages": [{"role": "user", "content": "北京的天气怎么样?"}]
})
print("\n result type:", type(result))
# output: result type: <class 'dict'>
print(f"响应: {result}")
print()
# output:
# 响应: {'messages': [HumanMessage(content='北京的天气怎么样?', additional_kwargs={}, response_metadata={}, id='7d2ae0ac-fc1e-4ad7-b0ae-fe03d866d504'), AIMessage(content='让我帮你查一下北京的天气情况。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 53, 'prompt_tokens': 314, 'total_tokens': 367, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 314}, 'model_provider': 'deepseek', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_058df29938_prod0820_fp8_kvcache_20260402', 'id': '9b88ff07-402d-443b-aad6-a5f88be4d53a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019e074f-e455-7150-8e05-ce89522864ac-0', tool_calls=[{'name': 'search', 'args': {'query': '北京天气 今天'}, 'id': 'call_00_Hidf1ib9kWm0VYvYd3Xb0177', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 314, 'output_tokens': 53, 'total_tokens': 367, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}), ToolMessage(content='今天北京天气晴朗,温度22度', name='search', id='5790d119-9e05-4f0e-8e9c-ececcada0c85', tool_call_id='call_00_Hidf1ib9kWm0VYvYd3Xb0177'), AIMessage(content='今天北京的天气情况如下:\n\n- **天气状况**:晴朗 ☀️\n- **温度**:22°C\n\n天气不错,适合外出活动!不过早晚温差可能较大,建议带件外套。如果你需要更详细的天气信息(比如风力、湿度、未来几天的预报等),可以告诉我,我再帮你查一下。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 70, 'prompt_tokens': 386, 'total_tokens': 456, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 256}, 'prompt_cache_hit_tokens': 256, 'prompt_cache_miss_tokens': 130}, 'model_provider': 'deepseek', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_058df29938_prod0820_fp8_kvcache_20260402', 'id': 'a32a2e6c-1c51-4b5a-8a4e-2f3ebf577c9b', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019e074f-eaa2-7150-95d1-b2b6eb675f89-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 386, 'output_tokens': 70, 'total_tokens': 456, 'input_token_details': {'cache_read': 256}, 'output_token_details': {}})]}
# 从结果中提取消息列表
messages = result.get('messages', [])
if messages:
# 打印方式一:
# 获取最后一条消息(通常是最终的 AI 响应)
# last_message = messages[-1]
# print(f"最后一条消息类型: {type(last_message).__name__}")
# print(f"内容: {last_message.content}")
# output:
# 最后一条消息类型: AIMessage
# 内容: 今天北京的天气情况如下:
# - **天气状况**:晴朗 ☀️
# - **气温**:22°C
# 天气不错,适合外出活动!不过早晚温差可能较大,建议适当增减衣物。如果你需要更详细的天气信息(如风力、湿度、未来几天的预报等),可以告诉我,我再帮你查询。
# 打印方式二 美化打印
for message in messages:
# message.pretty_print()
# output:
# ================================ Human Message =================================
# 北京的天气怎么样?
# ================================== Ai Message ==================================
# 让我帮你查一下北京的天气情况。
# Tool Calls:
# search (call_00_sSgLN3hZmJYvRQ92RzR10083)
# Call ID: call_00_sSgLN3hZmJYvRQ92RzR10083
# Args:
# query: 北京天气 今天
# ================================= Tool Message =================================
# Name: search
# 今天北京天气晴朗,温度22度
# ================================== Ai Message ==================================
# 今天北京的天气情况如下:
# - **天气状况**:晴朗 ☀️
# - **温度**:22°C
# 天气不错,适合外出活动!不过早晚温差可能较大,建议带件外套。如果你需要更详细的天气信息(比如风力、湿度、未来几天的预报等),可以告诉我,我再帮你查一下!
# 打印方式三: json格式打印每一项结果
print("="*50)
print(message.model_dump_json(indent=2))
print("="*50)
# output:
# ==================================================
# {
# "content": "北京的天气怎么样?",
# "additional_kwargs": {},
# "response_metadata": {},
# "type": "human",
# "name": null,
# "id": "b9a906b2-603a-48ab-a121-f559fb308d49"
# }
# ==================================================
# ==================================================
# {
# "content": "让我帮你查一下北京的天气情况。",
# "additional_kwargs": {
# "refusal": null
# },
# "response_metadata": {
# "token_usage": {
# "completion_tokens": 53,
# "prompt_tokens": 314,
# "total_tokens": 367,
# "completion_tokens_details": null,
# "prompt_tokens_details": {
# "audio_tokens": null,
# "cached_tokens": 256
# },
# "prompt_cache_hit_tokens": 256,
# "prompt_cache_miss_tokens": 58
# },
# "model_provider": "deepseek",
# "model_name": "deepseek-v4-flash",
# "system_fingerprint": "fp_8b330d02d0_prod0820_fp8_kvcache_20260402",
# "id": "2d8d97d5-17c8-4a63-af28-c24def39a2e9",
# "finish_reason": "tool_calls",
# "logprobs": null
# },
# "type": "ai",
# "name": null,
# "id": "lc_run--019e1a0a-ed57-7800-bf68-cf5b608efbb7-0",
# "tool_calls": [
# {
# "name": "search",
# "args": {
# "query": "北京天气 今天"
# },
# "id": "call_00_wJNwmlit0SJ9CgnsuvRf7982",
# "type": "tool_call"
# }
# ],
# "invalid_tool_calls": [],
# "usage_metadata": {
# "input_tokens": 314,
# "output_tokens": 53,
# "total_tokens": 367,
# "input_token_details": {
# "cache_read": 256
# },
# "output_token_details": {}
# }
# }
# ==================================================
# ==================================================
# {
# "content": "今天北京天气晴朗,温度22度",
# "additional_kwargs": {},
# "response_metadata": {},
# "type": "tool",
# "name": "search",
# "id": "e455032e-d544-432f-863b-b99fbcc39418",
# "tool_call_id": "call_00_wJNwmlit0SJ9CgnsuvRf7982",
# "artifact": null,
# "status": "success"
# }
# ==================================================
# ==================================================
# {
# "content": "今天北京的天气情况如下:\n\n- **天气状况**:晴朗 ☀️\n- **气温**:22°C\n\n天气不错,适合外出活动!不过早晚温差可能较大,建议根据实际体感适当增减衣物。如果你需要更详细的天气信息(如风力、湿度、未来几天的预报等),可以告诉我,我再帮你查询。",
# "additional_kwargs": {
# "refusal": null
# },
# "response_metadata": {
# "token_usage": {
# "completion_tokens": 73,
# "prompt_tokens": 386,
# "total_tokens": 459,
# "completion_tokens_details": null,
# "prompt_tokens_details": {
# "audio_tokens": null,
# "cached_tokens": 384
# },
# "prompt_cache_hit_tokens": 384,
# "prompt_cache_miss_tokens": 2
# },
# "model_provider": "deepseek",
# "model_name": "deepseek-v4-flash",
# "system_fingerprint": "fp_8b330d02d0_prod0820_fp8_kvcache_20260402",
# "id": "5e825d07-dbbd-4272-ba98-f5043c0fe8f9",
# "finish_reason": "stop",
# "logprobs": null
# },
# "type": "ai",
# "name": null,
# "id": "lc_run--019e1a0a-f28d-7342-91f9-261d718147b5-0",
# "tool_calls": [],
# "invalid_tool_calls": [],
# "usage_metadata": {
# "input_tokens": 386,
# "output_tokens": 73,
# "total_tokens": 459,
# "input_token_details": {
# "cache_read": 384
# },
# "output_token_details": {}
# }
# }
# ==================================================
# ============================================================================
# Demo 2: 工具调用 - 定义和使用工具
# ============================================================================
@tool
def get_current_time() -> str:
"""获取当前时间"""
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@tool
def get_weather(location: str) -> str:
"""
获取指定地点的天气
Args:
location: 城市名称
"""
# 模拟天气数据
weather_data = {
"北京": "晴天,22°C",
"上海": "多云,25°C",
"广州": "晴天,28°C",
}
return weather_data.get(location, f"未找到 {location} 的天气信息")
def demo2_tools():
"""工具调用 - 定义和使用工具 """
print("=" * 50)
print("Demo 2: 多工具调用")
print("=" * 50)
tools = [get_current_time, get_weather, search]
agent = create_agent(
model="deepseek-chat",
tools=tools,
name="tool_assistant"
)
# 调用 agent
result = agent.invoke({
"messages": [{"role": "user", "content": "现在几点了?北京和上海天气怎么样?"}]
})
print(f"类型: {type(result)}")
print(f"响应: {result}")
print()
# output
# 类型: <class 'dict'>
# 响应: {'messages': [HumanMessage(content='现在几点了?北京和上海天气怎么样?', additional_kwargs={}, response_metadata={}, id='0f4cbdf7-2df7-4468-b2c9-e76fa8b27126'), AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 90, 'prompt_tokens': 363, 'total_tokens': 453, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 363}, 'model_provider': 'deepseek', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_058df29938_prod0820_fp8_kvcache_20260402', 'id': 'f140866a-b84a-423b-afb4-4fcccba71e13', 'finish_reason': 'tool_calls', 'logprobs': None}, name='tool_assistant', id='lc_run--019e0045-efb5-7412-9556-5436fecfd98b-0', tool_calls=[{'name': 'get_current_time', 'args': {}, 'id': 'call_00_yTo73T1gUjihsvrQgkza1642', 'type': 'tool_call'}, {'name': 'get_weather', 'args': {'location': '北京'}, 'id': 'call_01_3l7F2Y3lkQRIWeRhUWWn4289', 'type': 'tool_call'}, {'name': 'get_weather', 'args': {'location': '上海'}, 'id': 'call_02_bcHn0GqUa21hofjDJH0D2304', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 363, 'output_tokens': 90, 'total_tokens': 453, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}), ToolMessage(content='2026-05-07 10:30:46', name='get_current_time', id='4900f4f5-41df-4956-b14e-c4b0ddda56bb', tool_call_id='call_00_yTo73T1gUjihsvrQgkza1642'), ToolMessage(content='晴天,22°C', name='get_weather', id='d0b4cdf5-7a7f-412e-aac7-8ab6efbf331d', tool_call_id='call_01_3l7F2Y3lkQRIWeRhUWWn4289'), ToolMessage(content='多云,25°C', name='get_weather', id='ad34bf46-3a7e-45ae-9c82-1be43a6e9868', tool_call_id='call_02_bcHn0GqUa21hofjDJH0D2304'), AIMessage(content='现在是 **2026年5月7日 10:30**。\n\n以下是天气情况:\n\n| 城市 | 天气 | 温度 |\n|:---:|:---:|:---:|\n| 🌞 **北京** | 晴天 | 22°C |\n| ⛅ **上海** | 多云 | 25°C |\n\n北京天气晴好,温度适宜;上海多云,比北京稍暖一些。祝您有愉快的一天!😊', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 96, 'prompt_tokens': 503, 'total_tokens': 599, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 256}, 'prompt_cache_hit_tokens': 256, 'prompt_cache_miss_tokens': 247}, 'model_provider': 'deepseek', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_058df29938_prod0820_fp8_kvcache_20260402', 'id': '87999227-54ed-4b2c-a478-09ec7310ab26', 'finish_reason': 'stop', 'logprobs': None}, name='tool_assistant', id='lc_run--019e0045-fa80-7300-8795-53679b2737e2-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 503, 'output_tokens': 96, 'total_tokens': 599, 'input_token_details': {'cache_read': 256}, 'output_token_details': {}})]}
# 提取并打印结果
messages = result.get('messages', [])
if messages:
last_message = messages[-1]
print(f"\nAgent 响应:")
print(f"{last_message.content}")
# output
# Agent 响应:
# 以下是您要的信息:
# ### 🕐 当前时间
# **2026年5月6日 14:53**
# ### 🌤️ 天气情况
# | 城市 | 天气 | 温度 |
# |:---:|:---:|:---:|
# | **北京** | ☀️ 晴天 | **22°C** |
# | **上海** | ⛅ 多云 | **25°C** |
# 两个城市天气都不错,适合出门活动~
print()
# ============================================================================
# Demo 3: 中间件 - 动态工具过滤
# ============================================================================
@wrap_model_call
def authentication_middleware(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
"""
认证中间件 - 根据认证状态过滤工具
"""
# 从状态中检查用户是否已认证
state = request.state
is_authenticated = state.get("authenticated", False)
if not is_authenticated:
# 未认证用户只能使用公开工具
tools = [t for t in request.tools if t.name.startswith("public_")]
request = request.override(tools=tools)
return handler(request)
@wrap_model_call
def message_limit_middleware(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
"""
消息限制中间件 - 根据对话长度限制工具
"""
state = request.state
print("state======>", state)
message_count = len(state.get("messages", []))
# 对话初期限制使用高级工具
if message_count < 5:
tools = [
t for t in request.tools if not t.name.startswith("advanced_")]
request = request.override(tools=tools)
return handler(request)
@tool
def public_search(query: str) -> str:
"""公开搜索工具"""
return f"公开搜索结果: {query}"
@tool
def private_search(query: str) -> str:
"""私密搜索工具(需要认证)"""
return f"私密搜索结果: {query}"
@tool
def advanced_analysis(data: str) -> str:
"""高级分析工具"""
return f"高级分析结果: {data}"
def demo3_middleware():
"""中间件 - 动态工具过滤"""
print("=" * 50)
print("Demo 3: 中间件 - 动态工具过滤")
print("=" * 50)
tools = [public_search, private_search, advanced_analysis]
agent = create_agent(
model="deepseek-chat",
tools=tools,
middleware=[authentication_middleware, message_limit_middleware],
name="middleware_agent"
)
# 未认证用户调用
result = agent.invoke({
"messages": [{"role": "user", "content": "搜索Python教程"}],
"authenticated": False
})
print(f"类型: {type(result)}")
print(f"未认证用户响应: {result}")
print()
# output
# 类型: <class 'dict'>
# 未认证用户响应: {'messages': [HumanMessage(content='搜索Python教程', additional_kwargs={}, response_metadata={}, id='cdb7c593-0d3e-4c40-a75b-3baf72335829'), AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 44, 'prompt_tokens': 272, 'total_tokens': 316, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 256}, 'prompt_cache_hit_tokens': 256, 'prompt_cache_miss_tokens': 16}, 'model_provider': 'deepseek', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_058df29938_prod0820_fp8_kvcache_20260402', 'id': 'aaa3cb7d-ae7e-4222-885c-794308d264df', 'finish_reason': 'tool_calls', 'logprobs': None}, name='middleware_agent', id='lc_run--019e0764-0fc7-7e43-8768-97ff8a7a8923-0', tool_calls=[{'name': 'public_search', 'args': {'query': 'Python教程'}, 'id': 'call_00_2MMCZUzYzYnDdXlfVlwS8642', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 272, 'output_tokens': 44, 'total_tokens': 316, 'input_token_details': {'cache_read': 256}, 'output_token_details': {}}), ToolMessage(content='公开搜索结果: Python教程', name='public_search', id='0273aad7-0bfa-4fce-8f19-615a98b4ffae', tool_call_id='call_00_2MMCZUzYzYnDdXlfVlwS8642'), AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 45, 'prompt_tokens': 333, 'total_tokens': 378, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 256}, 'prompt_cache_hit_tokens': 256, 'prompt_cache_miss_tokens': 77}, 'model_provider': 'deepseek', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_058df29938_prod0820_fp8_kvcache_20260402', 'id': '48cf10ed-d6cb-4afd-aa97-f2b1e3068e72', 'finish_reason': 'tool_calls', 'logprobs': None}, name='middleware_agent', id='lc_run--019e0764-1555-7422-be9c-df05031d52c6-0', tool_calls=[{'name': 'public_search', 'args': {'query': 'Python入门教程'}, 'id': 'call_00_rX8Au3S5Ho1dYfIKbuhG2139', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 333, 'output_tokens': 45, 'total_tokens': 378, 'input_token_details': {'cache_read': 256}, 'output_token_details': {}}), ToolMessage(content='公开搜索结果: Python入门教程', name='public_search', id='cdfb5676-a40d-4d45-8af9-54f3833f634f', tool_call_id='call_00_rX8Au3S5Ho1dYfIKbuhG2139'), AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 48, 'prompt_tokens': 396, 'total_tokens': 444, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 256}, 'prompt_cache_hit_tokens': 256, 'prompt_cache_miss_tokens': 140}, 'model_provider': 'deepseek', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_058df29938_prod0820_fp8_kvcache_20260402', 'id': 'afae4af1-6b70-47f9-bbb5-ebf855785877', 'finish_reason': 'tool_calls', 'logprobs': None}, name='middleware_agent', id='lc_run--019e0764-1a95-76f3-b876-cc3d5d6d9bec-0', tool_calls=[{'name': 'public_search', 'args': {'query': 'Python基础教程 2024'}, 'id': 'call_00_orvXhECs58WKwIkH7olE3293', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 396, 'output_tokens': 48, 'total_tokens': 444, 'input_token_details': {'cache_read': 256}, 'output_token_details': {}}), ToolMessage(content='公开搜索结果: Python基础教程 2024', name='public_search', id='de61db51-4529-4078-9f71-73fc9b73c2fd', tool_call_id='call_00_orvXhECs58WKwIkH7olE3293'), AIMessage(content='看起来搜索工具没有返回具体的内容详情。不过没关系,我可以为你提供一份全面的 **Python 教程指南**!\n\n---\n\n## 🐍 Python 教程指南\n\n### 一、Python 是什么?\nPython 是一种**简单易学、功能强大**的编程语言,广泛应用于:\n- 🌐 Web 开发(Django、Flask)\n- 🤖 人工智能 / 机器学习(TensorFlow、PyTorch)\n- 📊 数据分析(Pandas、NumPy)\n- 🕸️ 网络爬虫(Scrapy、BeautifulSoup)\n- 🎮 游戏开发(Pygame)\n- 🛠️ 自动化脚本\n\n---\n\n### 二、Python 入门基础(推荐学习路径)\n\n#### 1️⃣ 环境安装\n- 官网下载:[python.org](https://www.python.org/downloads/)\n- 推荐编辑器:VS Code、PyCharm、Jupyter Notebook\n\n#### 2️⃣ 基础语法\n| 主题 | 内容 |\n|------|------|\n| **变量与数据类型** | int, float, str, bool, list, dict, tuple, set |\n| **运算符** | 算术、比较、逻辑、赋值 |\n| **流程控制** | `if/elif/else`、`for`、`while` |\n| **函数** | `def`、参数、返回值、lambda |\n| **字符串操作** | 切片、格式化、常用方法 |\n| **列表与字典** | 增删改查、列表推导式 |\n| **文件操作** | `open()`、读写文件 |\n| **异常处理** | `try/except/finally` |\n\n#### 3️⃣ 进阶知识\n- **面向对象编程(OOP)**:类、继承、多态\n- **模块与包**:`import`、pip 包管理\n- **装饰器与生成器**\n- **正则表达式**\n- **多线程与多进程**\n\n---\n\n### 三、推荐学习资源\n\n#### 📖 免费教程(中文)\n| 名称 | 链接 |\n|------|------|\n| **廖雪峰 Python 教程** | [liaoxuefeng.com](https://www.liaoxuefeng.com/wiki/1016959663602400) |\n| **菜鸟教程 Python** | [runoob.com/python](https://www.runoob.com/python/python-tutorial.html) |\n| **W3School Python** | [w3school.com.cn/python](https://www.w3school.com.cn/python/) |\n| **Python 官方中文文档** | [docs.python.org/zh-cn](https://docs.python.org/zh-cn/3/) |\n\n#### 📚 推荐书籍\n1. **《Python编程:从入门到实践》** — 最适合新手\n2. **《笨办法学Python 3》** — 动手实践型\n3. **《流畅的Python》** — 进阶必读\n\n#### 🎬 视频教程\n- B站搜索:「Python 从零基础到精通」「黑马 Python」\n- 慕课网 / 网易云课堂 均有优质课程\n\n---\n\n### 四、实战项目建议\n\n| 难度 | 项目名称 |\n|------|----------|\n| ⭐ | 计算器、猜数字游戏、待办事项清单 |\n| ⭐⭐ | 网页爬虫、数据分析报告、简单 Web 应用 |\n| ⭐⭐⭐ | 聊天机器人、电商网站、机器学习模型 |\n\n---\n\n### 五、学习建议 ✅\n1. **动手写代码** — 只看不练等于白学!\n2. **善用官方文档** — Python 文档非常详细\n3. **多逛社区** — StackOverflow、知乎、CSDN\n4. **做小项目** — 用项目驱动学习效率最高\n\n---\n\n如果你想要**更具体的某个方向的教程**(如数据分析、Web开发、爬虫等),或者需要我**直接教你写一段 Python 代码**,请告诉我!我可以一步步带你学习 😊', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 848, 'prompt_tokens': 465, 'total_tokens': 1313, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 384}, 'prompt_cache_hit_tokens': 384, 'prompt_cache_miss_tokens': 81}, 'model_provider': 'deepseek', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_058df29938_prod0820_fp8_kvcache_20260402', 'id': 'c17aa8cd-48bf-47a3-a059-5a6bfa3b0d18', 'finish_reason': 'stop', 'logprobs': None}, name='middleware_agent', id='lc_run--019e0764-1f34-7a82-8911-9b73a156aaad-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 465, 'output_tokens': 848, 'total_tokens': 1313, 'input_token_details': {'cache_read': 384}, 'output_token_details': {}})]}
# ============================================================================
# Demo 4: 基于上下文的工具过滤
# ============================================================================
@dataclass
class Context:
"""用户上下文"""
user_id: str
user_role: str # admin, editor, viewer
@wrap_model_call
def role_based_tools(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
"""
基于角色的工具过滤
"""
state = request.state
print("state_type: ", type(state))
print("state: ", state)
# state_type: <class 'dict'>
# state: {'messages': [HumanMessage(content='获取用户数据', additional_kwargs={}, response_metadata={}, id='dab896b6-7e41-4716-a9a7-ae501f5fff74')]}
"""Generate system prompt based on user role."""
user_id = request.runtime.context.get("user_id", "222")
user_role = request.runtime.context.get("user_role", "viewer")
print("user_id: ", user_id)
print("user_role: ", user_role)
# 默认为 viewer
user_role = user_role or "viewer"
if user_role == "admin":
# 管理员可以使用所有工具
pass
elif user_role == "editor":
# 编辑者不能删除数据
tools = [t for t in request.tools if t.name != "delete_data"]
request = request.override(tools=tools)
else:
# 查看者只能使用只读工具
tools = [t for t in request.tools if t.name.startswith("read_")]
request = request.override(tools=tools)
print(f"user_role: {user_role}, tools: {request.tools}")
return handler(request)
@tool
def read_data(table: str) -> str:
"""读取数据"""
return f"读取 {table} 表的数据"
@tool
def write_data(table: str, data: str) -> str:
"""写入数据"""
return f"写入数据到 {table} 表"
@tool
def delete_data(table: str) -> str:
"""删除数据"""
return f"删除 {table} 表的数据"
def demo4_context_based_tools():
"""基于上下文的工具过滤"""
print("=" * 50)
print("Demo 4: 基于上下文的工具过滤")
print("=" * 50)
tools = [read_data, write_data, delete_data]
agent = create_agent(
model="deepseek-chat",
tools=tools,
middleware=[role_based_tools],
# state_schema=Context,
name="role_based_agent"
)
# 查看者角色调用
result = agent.invoke({
"messages": [{"role": "user", "content": "获取用户数据"}],
},
# user_role : admin / editor / viewer
context={"user_id": "1", "user_role": "editor"}
)
print("\n result type:", type(result))
print(f"查看者响应: {result}")
print()
# output
# result type: <class 'dict'>
# 查看者响应: {'messages': [HumanMessage(content='获取用户数据', additional_kwargs={}, response_metadata={}, id='d17ac2a5-93f3-425d-a510-08d684a0f9ff'), AIMessage(content='我需要先查看可用的数据表,以便了解数据结构和内容。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 57, 'prompt_tokens': 271, 'total_tokens': 328, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 256}, 'prompt_cache_hit_tokens': 256, 'prompt_cache_miss_tokens': 15}, 'model_provider': 'deepseek', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_058df29938_prod0820_fp8_kvcache_20260402', 'id': '2f7484d0-dc64-4722-b912-90cac501a342', 'finish_reason': 'tool_calls', 'logprobs': None}, name='role_based_agent', id='lc_run--019e1502-b5d5-7e23-8a68-74f31debffa2-0', tool_calls=[{'name': 'read_data', 'args': {'table': 'users'}, 'id': 'call_00_Fz8u9vinmRYoGacKugzT0448', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 271, 'output_tokens': 57, 'total_tokens': 328, 'input_token_details': {'cache_read': 256}, 'output_token_details': {}}), ToolMessage(content='读取 users 表的数据', name='read_data', id='ea50ad1c-723f-4bbc-9d28-789ec53c6236', tool_call_id='call_00_Fz8u9vinmRYoGacKugzT0448'), AIMessage(content='以下是 **用户数据**:\n\n| user_id | name | age |\n|---------|--------|-----|\n| 1 | Alice | 30 |\n| 2 | Bob | 25 |\n| 3 | Charlie| 35 |\n| 4 | Diana | 28 |\n| 5 | Eve | 32 |\n\n**用户表(users)包含以下字段:**\n- **user_id**:用户ID(唯一标识)\n- **name**:用户姓名\n- **age**:用户年龄\n\n共有 **5位用户**:\n| 姓名 | 年龄 |\n|------|------|\n| Alice | 30岁 |\n| Bob | 25岁 |\n| Charlie | 35岁 |\n| Diana | 28岁 |\n| Eve | 32岁 |\n\n如果您需要对这些数据进行进一步的分析、筛选或处理,请告诉我!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 188, 'prompt_tokens': 344, 'total_tokens': 532, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 256}, 'prompt_cache_hit_tokens': 256, 'prompt_cache_miss_tokens': 88}, 'model_provider': 'deepseek', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_058df29938_prod0820_fp8_kvcache_20260402', 'id': '9a5a1ee3-768d-4d1f-8664-678376a22375', 'finish_reason': 'stop', 'logprobs': None}, name='role_based_agent', id='lc_run--019e1503-3b3c-78c0-a4ff-d21bd8627c38-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 344, 'output_tokens': 188, 'total_tokens': 532, 'input_token_details': {'cache_read': 256}, 'output_token_details': {}})]}
# ============================================================================
# Demo 5: 自定义状态管理
# ============================================================================
class CustomState(AgentState):
"""自定义 Agent 状态"""
user_preferences: dict
session_data: dict
class CustomMiddleware(AgentMiddleware):
"""自定义中间件,使用自定义状态"""
state_schema = CustomState
def before_model(self, state: CustomState, runtime) -> None:
"""模型调用前的钩子"""
preferences = state.get("user_preferences", {})
if preferences.get("style") == "technical":
print("用户偏好:技术性回答")
return None
def after_model(self, state: CustomState, runtime) -> None: # 无 response 参数
"""模型调用后的钩子"""
print("模型调用完成,状态管理正常")
return None
def demo5_custom_state():
"""自定义状态管理"""
print("=" * 50)
print("Demo 5: 自定义状态管理")
print("=" * 50)
agent = create_agent(
model="deepseek-chat",
tools=[search, calculator],
middleware=[CustomMiddleware()],
state_schema=CustomState,
name="stateful_agent"
)
# 调用 agent 并传入自定义状态
result = agent.invoke({
"messages": [{"role": "user", "content": "解释一下机器学习"}],
"user_preferences": {"style": "technical", "verbosity": "detailed"},
"session_data": {"session_id": "abc123"}
})
print("=" * 50)
print(f"响应: {result}")
print("=" * 50)
print()
# ============================================================================
# Demo 6: 结构化输出
# ============================================================================
class ContactInfo(BaseModel):
"""联系人信息"""
name: str
email: str
phone: str
class ProductInfo(BaseModel):
"""产品信息"""
name: str
price: float
description: str
in_stock: bool
def demo6_structured_output():
"""结构化输出"""
print("=" * 50)
print("Demo 6: 结构化输出")
print("=" * 50)
from langchain.agents.structured_output import ToolStrategy
agent = create_agent(
model="deepseek-chat",
tools=[search],
response_format=ToolStrategy(ProductInfo), # 结构化输出
name="structured_agent"
)
result = agent.invoke({
"messages": [
{"role": "user", "content": "生成一个虚构的联系人的信息"}
# {"role": "user", "content": "生成一个虚构的产品的信息"}
]
})
print("\n result type:", type(result))
print(f"结构化输出: {result}")
# 结构化输出类型: <class 'dict'>
# 结构化输出: {'messages': [HumanMessage(content='生成一个虚构的联系人的信息', additional_kwargs={}, response_metadata={}, id='927354e1-917a-4975-ac0f-5af09b89f3d8'), AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 85, 'prompt_tokens': 347, 'total_tokens': 432, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 347}, 'model_provider': 'deepseek', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_058df29938_prod0820_fp8_kvcache_20260402', 'id': 'd06239b1-efe5-4f05-bb06-7f59fc2f4444', 'finish_reason': 'tool_calls', 'logprobs': None}, name='structured_agent', id='lc_run--019e15b0-1dd9-7e70-adcc-a66dc87f245e-0', tool_calls=[{'name': 'ContactInfo', 'args': {'name': '张明远', 'email': 'zhangmingyuan@example.com', 'phone': '+86 138-0001-1234'}, 'id': 'call_00_bKvUgn1FBveKez9Fjy5g3140', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 347, 'output_tokens': 85, 'total_tokens': 432, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}), ToolMessage(content="Returning structured response: name='张明远' email='zhangmingyuan@example.com' phone='+86 138-0001-1234'", name='ContactInfo', id='7114c2ea-2b55-44d2-b5c7-9f10b67bf445', tool_call_id='call_00_bKvUgn1FBveKez9Fjy5g3140')], 'structured_response': ContactInfo(name='张明远', email='zhangmingyuan@example.com', phone='+86 138-0001-1234')}
# ============================================================================
# Demo 7: 使用 Store 进行持久化
# ============================================================================
@wrap_model_call
def store_based_tools(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
# """
# 基于 Store 的工具过滤
# """
# # 从 store 中读取用户信息
# store = request.store
# context = request.context
# if context and hasattr(context, 'user_id'):
# user_data = store.get(context.user_id)
# if user_data:
# # 根据用户数据调整工具
# pass
"""Filter tools based on Store preferences."""
store = getattr(request.runtime, 'store', None)
runtime_context = getattr(request.runtime, 'context', None)
# 正确获取 user_id
user_id = None
if runtime_context:
if hasattr(runtime_context, 'user_id'):
user_id = runtime_context.user_id
elif isinstance(runtime_context, dict):
user_id = runtime_context.get("user_id")
# 备选方案:从 messages 中获取
if not user_id and request.messages:
last_msg = request.messages[-1]
if hasattr(last_msg, 'additional_kwargs'):
user_id = last_msg.additional_kwargs.get("user_id")
if user_id:
user_data = store.get(user_id)
if user_data:
pass
return handler(request)
def demo7_with_store():
"""使用 Store 进行持久化"""
print("=" * 50)
print("Demo 7: 使用 Store 进行持久化")
print("=" * 50)
agent = create_agent(
model="deepseek-chat",
tools=[search, calculator],
middleware=[store_based_tools],
context_schema=Context,
store=InMemoryStore(),
name="persistent_agent"
)
result = agent.invoke({
"messages": [{"role": "user", "content": "搜索最新的AI技术"}],
"context": Context(user_id="user123", user_role="admin")
})
print(f"响应: {result}")
print()
# ============================================================================
# Demo 8: 工具调用包装器
# ============================================================================
@wrap_model_call
def logging_middleware(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
"""日志记录中间件"""
print(f"[LOG] 调用模型: {request.model}")
print(f"[LOG] 消息数量: {len(request.messages)}")
response = handler(request)
print(f"[LOG] 响应完成")
return response
def demo8_tool_wrapper():
"""工具调用包装器"""
print("=" * 50)
print("Demo 8: 工具调用包装器")
print("=" * 50)
agent = create_agent(
model="deepseek-chat",
tools=[search, get_weather, calculator],
middleware=[logging_middleware],
name="logged_agent"
)
result = agent.invoke({
"messages": [{"role": "user", "content": "计算 25 * 4"}]
})
print(f"响应: {result}")
print()
if result["messages"]:
for message in result["messages"]:
# print("="*50)
# print(message.model_dump_json(indent=2))
# print("="*50)
message.pretty_print()
# output
# ================================ Human Message =================================
# 计算 25 * 4
# ================================== Ai Message ==================================
# Name: logged_agent
# Tool Calls:
# calculator (call_00_ZFtiQcKL3zc641KJBh765329)
# Call ID: call_00_ZFtiQcKL3zc641KJBh765329
# Args:
# expression: 25 * 4
# ================================= Tool Message =================================
# Name: calculator
# 计算结果: 100
# ================================== Ai Message ==================================
# Name: logged_agent
# 25 × 4 = **100**。
# ============================================================================
# 主函数
# ============================================================================
def main():
"""主函数,分别调用各个 demo"""
# demo1_basic_agent()
# demo2_tools()
# demo3_middleware()
# demo4_context_based_tools()
# demo5_custom_state()
# demo6_structured_output()
# demo7_with_store()
demo8_tool_wrapper()
if __name__ == "__main__":
main()
作者:海马 创建时间:2026-05-06 19:52
最后编辑:海马 更新时间:2026-05-13 07:11
最后编辑:海马 更新时间:2026-05-13 07:11