内容参考于:图灵AI大模型全栈
后面会有专门的Agent详细玩法,这里只是做一个了解
1.0的调用方式比0.3版本简单很多
运行下方的代码,要用1.0.2版本,否则会报错
pip install langgraph==1.0.2 langgraph-prebuilt==1.0.2效果图:
# 导入LangChain智能体创建函数:用于构建AI智能客服代理 # 来源:langchain 核心库 from langchain.agents import create_agent # 导入Tavily在线搜索工具:用于联网搜索实时信息 # 来源:langchain_tavily 第三方工具库 from langchain_tavily import TavilySearch # 导入大模型调用类:对接阿里通义千问大模型 # 来源:langchain_openai 官方库 from langchain_core.tools import Tool # 导入LangChain工具类:用于封装自定义函数为AI可调用工具 # 来源:langchain_core 核心库 import os # 导入Python内置模块:用于读取环境变量、文件操作 # 来源:Python官方内置库 from dotenv import load_dotenv # 导入环境变量加载工具:读取.env文件中的API密钥 # 来源:第三方库 python-dotenv # 作用:加载项目根目录下的.env环境变量文件 # 入参:无 # 可传值:无 # 值来源:固定调用 load_dotenv() # 定义查询订单状态的函数 # 作用:根据订单ID查询订单状态(自定义业务函数) # 入参:order_id -> 字符串/数字,订单编号 # 可传值:任意订单号(仅1024有预设结果) # 值来源:用户提问中提取的订单ID # 返回值:订单状态描述字符串 def query_order_status(order_id): if order_id == "1024": return "订单 1024 的状态是:已发货,预计送达时间是 3-5 个工作日。" else: return f"未找到订单 {order_id} 的信息,请检查订单号是否正确。" # 定义退款政策说明函数 # 作用:根据公司名称查询对应退款政策(自定义业务函数) # 入参:company_name -> 字符串,公司名称 # 可传值:任意公司名称(仅tom有预设结果) # 值来源:用户提问中提取的公司名称 # 返回值:退款政策描述字符串 def company_refund_policy(company_name): print(company_name) if company_name == "tom": return "tom公司的退款政策是:在购买后7天内可以申请全额退款,需提供购买凭证。" else: print('输入有误') # 查询年龄 # 作用:根据姓名查询年龄(自定义业务函数) # 入参:name -> 字符串,姓名 # 可传值:任意姓名(仅tom有预设结果) # 值来源:用户提问中提取的姓名 # 返回值:年龄描述字符串 def get_age(name): if name == "tom": print(name) return "我的年龄是56岁!" else: print('输入有误') # 初始化工具列表:将在线搜索+自定义函数封装为AI工具 # 作用:给智能体提供可调用的工具集合 # 入参:列表格式,包含Tool对象和TavilySearch对象 # 值来源:自定义编写的工具+官方搜索工具 tools = [ # Tavily联网搜索工具 # 作用:实时联网搜索互联网信息 # max_results:搜索返回结果数量,int # 可传值:1-5 # 值来源:自定义设置 # tavily_api_key:Tavily平台的API密钥 # 可传值:Tavily官网申请的密钥 # 值来源:.env文件中的TAVILY_API_KEY TavilySearch(max_results=1, tavily_api_key=os.getenv("TAVILY_API_KEY")), # 自定义工具1:查询订单状态 Tool( name="queryOrderStatus", # 工具名称:唯一标识,智能体通过名称调用 func=query_order_status, # 工具绑定的函数:调用后执行的自定义函数 description="根据订单ID查询订单状态", # 工具描述:告诉智能体该工具的用途 args={"order_id": "订单的ID"} # 工具参数:参数名+参数说明 ), # 自定义工具2:查询公司退款政策 Tool( name="companyRefundPolicy", # 工具唯一名称 func=company_refund_policy, # 绑定退款政策函数 description="查询某某公司退款政策详细内容", # 工具用途描述 args={"company_name": "公司名称"} # 工具入参说明 ), # 自定义工具3:查询年龄 Tool( name="getAge", # 工具唯一名称 func=get_age, # 绑定年龄查询函数 description="查询tom年龄大小", # 工具用途描述 args={"name": "查询tom年龄大小"} # 工具入参说明 ), ] # 选择将驱动代理的LLM # 作用:初始化阿里通义千问大模型,作为智能体的大脑 # api_key:阿里通义API密钥 # 可传值:阿里云百炼平台申请的API Key # 值来源:.env文件中的DASHSCOPE_API_KEY # base_url:阿里通义兼容OpenAI格式的接口地址 # 可传值:官方固定地址 # 值来源:阿里云官方文档 # model:模型名称 # 可传值:qwen-plus、qwen-turbo、qwen-long等 # 值来源:阿里云官方模型列表 llm = ChatOpenAI(api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", model='qwen-plus') # 作用:创建AI智能客服代理 # model:绑定大模型(大脑) # 可传值:初始化好的llm对象 # 值来源:上方定义的llm变量 # tools:绑定可调用的工具集合 # 可传值:上方定义的tools列表 # 值来源:自定义工具列表 # system_prompt:系统提示词,约束智能体行为 # 核心规则:必须用工具回答,参数传递准确,不篡改字符串 # 值来源:自定义编写的约束规则 agent = create_agent( model=llm, tools=tools, system_prompt="你是一个客服助手,使用工具回答问题。传递给工具的内容必须是准确的json数据不结尾的括号多加一个,如果是字符串数据必须和输入的保持一致要完整,不是篡改**重要规则**, ", ) # 定义一些测试询问 # 作用:测试智能体的提问列表 # 入参:列表格式,字符串元素 # 可传值:任意自定义问题 # 值来源:自定义测试用例 queries = [ "请问订单1024的状态是什么?", "请问tom公司退款政策是什么?", "2024年谁胜出了美国总统的选举" ] # 运行代理并输出结果 # 作用:循环遍历测试问题,调用智能体并打印回答 # input:单个用户问题 # inputs:标准化输入格式,固定messages结构 # result:智能体返回的结果 # 输出:打印客户问题+智能体最终回答 for input in queries: print('客户提问:' + input) inputs = {"messages": [{"role": "user", "content": input}]} result = agent.invoke(inputs) print(result['messages'][-1].content)
中间件的作用
我们现在要访问国外的网站,我们在国内是没办法直接访问国外的网站,我们就需要先找一个可以访问国外网络的服务器,这个服务器就是一个中间件,我们通过访问中间件,然后中间件根据我们的请求,去访问国外的数据,然后中间件把国外的数据再发送给我们
LangChain的中间件
| class | 描述 |
|---|---|
| SummarizationMiddleware | 接近令牌限制时自动总结对话历史记录 |
| HumanInTheLoopMiddleware | 暂停执行,等待人工批准工具调用 |
| ModelCallLimitMiddleware | 限制模型调用次数,以防止成本过高。 |
| ToolCallLimitMiddleware | 通过限制调用次数来控制工具执行。 |
| ModelFallbackMiddleware | 主模型故障时自动回退到备用模型 |
| PIIMiddleware | 检测和处理个人身份信息 |
| TodoListMiddleware | 为代理人配备任务规划和跟踪功能 |
| LLMToolSelectorMiddleware | 在调用主模型之前,使用 LLM 选择相关工具。 |
| ToolRetryMiddleware | 使用指数退避算法自动重试失败的工具调用 |
| LLMToolEmulator | 使用LLM模拟工具执行以进行测试 |
| ContextEditingMiddleware | 通过精简或清除工具使用情况来管理对话上下文 |
| ShellToolMiddleware | 向代理公开持久 shell 会话以执行命令 |
| FilesystemFileSearchMiddleware | 提供对文件系统文件的 Glob 和 Grep 搜索工具 |
| AgentMiddleware | 用于创建自定义中间件的基础中间件类 |
使用中间,这里使用HumanInTheLoopMiddleware拦截Tavily搜索引擎
HumanInTheLoopMiddleware需要传递一个工具的名字,Tavily搜索引擎工具名字查看方式,按着CTRL鼠标左键单击下图红框
然后如下图红框,就可以看到名字了
然后HumanInTheLoopMiddleware里有三个值,按着CTRL鼠标左键单击下图红框
如下图红框,可以看到"approve", "edit", "reject",分别是允许、编辑、拒绝,意思是我们人工可以做什么,上图中只使用了运行和拒绝
如下图:ai回答的问题是空的,这是因为这个问题是需要调用工具,我们并没有同意它使用工具,所以回答的是空的
上图使用的代码
from langchain.agents import create_agent from langchain_tavily import TavilySearch from langchain_openai import ChatOpenAI from langchain_core.tools import Tool import os from dotenv import load_dotenv from langchain.agents.middleware import HumanInTheLoopMiddleware from langgraph.checkpoint.memory import InMemorySaver from langgraph.types import Command load_dotenv() # 定义查询订单状态的函数 def query_order_status(order_id): if order_id == "1024": return "订单 1024 的状态是:已发货,预计送达时间是 3-5 个工作日。" else: return f"未找到订单 {order_id} 的信息,请检查订单号是否正确。" # 定义退款政策说明函数 def company_refund_policy(company_name): print(company_name) if company_name == "tom": return "tom公司的退款政策是:在购买后7天内可以申请全额退款,需提供购买凭证。" else: print('输入有误') # 查询年龄 def get_age(name): if name == "tom": print(name) return "我的年龄是56岁!" else: print('输入有误') # 初始化工具 tools = [ TavilySearch(max_results=1, tavily_api_key=os.getenv("TAVILY_API_KEY")), Tool( name="queryOrderStatus", func=query_order_status, description="根据订单ID查询订单状态", args={"order_id": "订单的ID"} ), Tool( name="companyRefundPolicy", func=company_refund_policy, description="查询某某公司退款政策详细内容", args={"company_name": "公司名称"} ), Tool( name="getAge", func=get_age, description="查询tom年龄大小", args={"name": "查询tom年龄大小"} ), ] # 选择将驱动代理的LLM llm = ChatOpenAI(api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", model='qwen-plus') agent = create_agent( model=llm, tools=tools, system_prompt="你是一个客服助手,使用工具回答问题。传递给工具的内容必须是准确的json数据不结尾的括号多加一个,如果是字符串数据必须和输入的保持一致要完整,不是篡改**重要规则**, ", # 使用中间件 middleware=[ # 用于指定需要人工判断工具是否可使用的 HumanInTheLoopMiddleware( interrupt_on={ 'tavily_search':{'allowed_decisions':["approve", "reject"]}, "queryOrderStatus": {'allowed_decisions': ['approve', 'reject']}, "companyRefundPolicy": {'allowed_decisions': ['approve', 'reject']}, "getAge": {'allowed_decisions': ['approve', 'reject']}, } ) ], # 让 agent 代理具备“记住对话历史 + 支持中断后继续”的能力 checkpointer=InMemorySaver(), ) # 定义一些测试询问 queries = [ "请问订单1024的状态是什么?", "请问tom公司退款政策是什么?", "2024年谁胜出了美国总统的选举" ] # 为当前会话设置唯一的 thread_id # 同一个 thread_id 会保留完整的对话状态和中断历史 # 可以理解为一个ai工具运行一个会话,就跟我们使用ai时创建的新窗口一样 config = {"configurable": {"thread_id": "some_other_id_123"}} # 运行代理并输出结果 for que in queries: print('客户提问:' + que) inputs = {"messages": [{"role": "user", "content": que}]} # # 执行代理,直到遇到中断或完成 result = agent.invoke(inputs, config=config) print(result)如下图红框__interrupt__这里面的是需要审批的工具,它里面会有用到的工具信息,还有用工具的原因
上图里json的说明
# 智能体执行返回的完整结果字典 | 逐值注释,格式无任何改动 { # 键:messages 作用:存储对话所有消息 类型:列表 值来源:agent执行生成 'messages': [ # 用户消息对象 类型:HumanMessage 值来源:用户输入 HumanMessage( content='请问订单1024的状态是什么?', # 内容:用户提问 类型:str 值来源:自定义输入 additional_kwargs={}, # 附加参数 类型:dict 值来源:默认空 response_metadata={}, # 响应元数据 类型:dict 值来源:默认空 id='1afd3b77-e1cd-4417-827a-746bab834aed' # 唯一ID 类型:str 值来源:系统自动生成 ), # AI消息对象 类型:AIMessage 值来源:大模型返回 AIMessage( content='', # AI回答内容 类型:str 空值=调用工具 值来源:大模型返回 additional_kwargs={'refusal': None}, # 附加配置 类型:dict refusal=无拒绝 值来源:大模型返回 response_metadata={ # 响应元数据 类型:dict 值来源:大模型接口返回 'token_usage': { # token消耗 类型:dict 值来源:计费统计 'completion_tokens': 25, # 输出token数 类型:int 值来源:模型计算 'prompt_tokens': 1948, # 输入token数 类型:int 值来源:模型计算 'total_tokens': 1973, # 总token数 类型:int 值来源:模型计算 'completion_tokens_details': None, # 输出token详情 类型:None 值来源:无数据 'prompt_tokens_details': { # 输入token详情 类型:dict 值来源:模型返回 'audio_tokens': None, # 音频token 类型:None 值来源:无音频 'cached_tokens': 0 # 缓存token 类型:int 值来源:无缓存 } }, 'model_provider': 'openai', # 模型厂商 类型:str 值来源:兼容协议标识 'model_name': 'qwen-plus', # 模型名称 类型:str 值来源:自定义配置 'system_fingerprint': None, # 系统指纹 类型:None 值来源:无数据 'id': 'chatcmpl-53c20f3e-9b99-9b50-ad4f-843bfa7e21cc', # 对话ID 类型:str 值来源:接口生成 'finish_reason': 'tool_calls', # 结束原因 类型:str tool_calls=调用工具 值来源:模型返回 'logprobs': None # 日志概率 类型:None 值来源:无数据 }, id='lc_run--019e9cd3-b281-77d2-8c95-4933cfeafde7-0', # 运行ID 类型:str 值来源:系统生成 tool_calls=[ # 工具调用列表 类型:list 值来源:大模型指令 { 'name': 'queryOrderStatus', # 工具名称 类型:str 值来源:自定义工具名 'args': {'__arg1': '1024'}, # 工具参数 类型:dict 值来源:大模型解析生成 'id': 'call_d5306434579d4e93b3ad37', # 调用ID 类型:str 值来源:系统生成 'type': 'tool_call' # 类型标识 类型:str 固定值tool_call } ], invalid_tool_calls=[], # 无效工具调用 类型:list 空=无无效调用 值来源:无错误 usage_metadata={ # 使用元数据 类型:dict 值来源:统计生成 'input_tokens': 1948, # 输入token 类型:int 值来源:模型统计 'output_tokens': 25, # 输出token 类型:int 值来源:模型统计 'total_tokens': 1973, # 总token 类型:int 值来源:模型统计 'input_token_details': {'cache_read': 0}, # 输入详情 类型:dict 值来源:无缓存 'output_token_details': {} # 输出详情 类型:dict 值来源:无数据 } ) ], # 键:__interrupt__ 作用:工具调用中断 类型:list 值来源:LangChain安全机制 '__interrupt__': [ Interrupt( # 中断对象 类型:Interrupt 值来源:需要审批工具调用 value={ # 中断值 类型:dict 值来源:系统生成 'action_requests': [ # 操作请求 类型:list 值来源:工具调用申请 { 'name': 'queryOrderStatus', # 工具名 类型:str 值来源:自定义工具 'args': {'__arg1': '1024'}, # 参数 类型:dict 值来源:模型解析 'description': 'Tool execution requires approval\n\nTool: queryOrderStatus\nArgs: {\'__arg1\': \'1024\'}' # 描述 类型:str 固定提示 } ], 'review_configs': [ # 审批配置 类型:list 值来源:系统设置 { 'action_name': 'queryOrderStatus', # 操作名 类型:str 值来源:工具名 'allowed_decisions': ['approve', 'reject'] # 允许操作 类型:list 固定值:同意/拒绝 } ] }, id='5ce1059b62115668f1e1ea4c98a83ca9' # 中断ID 类型:str 值来源:系统生成 ) ] }
增加输入同意或拒绝功能
效果图:如果输入reject拒绝了,大模型也会回答,只不过它没有使用工具自己乱编的答案
# 作用:创建智能代理对象 # 来源:langchain.agents模块 from langchain.agents import create_agent # 作用:Tavily联网搜索工具 # 来源:langchain_tavily第三方库 from langchain_tavily import TavilySearch # 作用:兼容openai协议调用大模型 # 来源:langchain_openai库 from langchain_openai import ChatOpenAI # 作用:自定义函数封装为工具 # 来源:langchain_core.tools from langchain_core.tools import Tool # 作用:系统内置,环境变量操作 # 来源:python内置os import os # 作用:读取.env配置文件 # 来源:python-dotenv库 from dotenv import load_dotenv # 作用:人工审批中间件,控制工具调用中断 # 来源:langchain.agents.middleware from langchain.agents.middleware import HumanInTheLoopMiddleware # 作用:内存存储会话历史、断点数据 # 来源:langgraph.checkpoint.memory from langgraph.checkpoint.memory import InMemorySaver # 作用:断点恢复指令对象 # 来源:langgraph.types from langgraph.types import Command # 作用:加载项目.env环境配置 # 入参:无 # 可传:无 # 值来源:固定调用 load_dotenv() # 作用:自定义函数,根据订单编号查订单状态 # 入参:order_id 字符串 # 可传:任意订单编号,仅"1024"命中数据 # 值来源:大模型从用户问题提取参数 def query_order_status(order_id): if order_id == "1024": return "订单 1024 的状态是:已发货,预计送达时间是 3-5 个工作日。" else: return f"未找到订单 {order_id} 的信息,请检查订单号是否正确。" # 作用:自定义函数,查询指定公司退款规则 # 入参:company_name 字符串 # 可传:任意公司名,仅"tom"有返回内容 # 值来源:大模型从用户问题提取参数 def company_refund_policy(company_name): print(company_name) if company_name == "tom": return "tom公司的退款政策是:在购买后7天内可以申请全额退款,需提供购买凭证。" else: print('输入有误') # 作用:自定义函数,查询tom年龄 # 入参:name 字符串 # 可传:任意姓名,仅"tom"正常返回年龄 # 值来源:大模型从用户问题提取参数 def get_age(name): if name == "tom": print(name) return "我的年龄是56岁!" else: print('输入有误') # 作用:组装全部可用工具列表,提供给agent调用 # 入参:工具实例集合 # 可传:搜索工具、Tool封装自定义函数 # 值来源:下方逐个定义工具 tools = [ # 作用:在线联网搜索工具 # max_results:int,单次返回结果条数,可传1~5,来源自定义 # tavily_api_key:str,搜索密钥,可传官网申请key,来源.env里TAVILY_API_KEY TavilySearch(max_results=1, tavily_api_key=os.getenv("TAVILY_API_KEY")), # 作用:订单查询工具封装 # name:str,工具标识名,自定义命名 # func:绑定执行函数,来源上方query_order_status # description:str,工具用途说明,给大模型识别 # args:字典,参数释义 Tool( name="queryOrderStatus", func=query_order_status, description="根据订单ID查询订单状态", args={"order_id": "订单的ID"} ), # 作用:退款政策查询工具封装 Tool( name="companyRefundPolicy", func=company_refund_policy, description="查询某某公司退款政策详细内容", args={"company_name": "公司名称"} ), # 作用:年龄查询工具封装 Tool( name="getAge", func=get_age, description="查询tom年龄大小", args={"name": "查询tom年龄大小"} ), ] # 作用:初始化通义千问大模型实例 # api_key:str,阿里密钥,来源.env DASHSCOPE_API_KEY # base_url:str,兼容接口地址,固定官方地址 # model:str,模型名称,可选qwen-plus/qwen-turbo等,来源官方模型名 llm = ChatOpenAI(api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", model='qwen-plus') # 作用:实例化智能代理 # model:大模型对象,来源上方llm变量 # tools:工具列表,来源上方tools变量 # system_prompt:str,系统约束提示词,来源自定义配置 # middleware:列表,存放审批中间件 # checkpointer:会话存储器实例,InMemorySaver()内存存储 agent = create_agent( model=llm, tools=tools, system_prompt="你是一个客服助手,使用工具回答问题。传递给工具的内容必须是准确的json数据不结尾的括号多加一个,如果是字符串数据必须和输入的保持一致要完整,不是篡改**重要规则**, ", # 使用中间件 middleware=[ # 作用:人工审批中间件,所有工具调用触发人工确认 # interrupt_on:字典,key=工具名,value=审批选项,固定approve/reject HumanInTheLoopMiddleware( interrupt_on={ 'tavily_search':{'allowed_decisions':["approve", "reject"]}, "queryOrderStatus": {'allowed_decisions': ['approve', 'reject']}, "companyRefundPolicy": {'allowed_decisions': ['approve', 'reject']}, "getAge": {'allowed_decisions': ['approve', 'reject']}, } ) ], # 让 agent 代理具备“记住对话历史 + 支持中断后继续”的能力 checkpointer=InMemorySaver(), ) # 作用:测试提问集合 # 入参:字符串问题 # 可传:任意用户问题 # 值来源:自定义测试用例 queries = [ "请问订单1024的状态是什么?", "请问tom公司退款政策是什么?", "2024年谁胜出了美国总统的选举" ] # 作用:会话配置,thread_id标记独立会话,同一个id保留上下文 # configurable:固定key,thread_id自定义字符串 # 可传:任意自定义id字符串 # 值来源:自定义设置 config = {"configurable": {"thread_id": "some_other_id_123"}} # 作用:循环逐个执行用户提问 for que in queries: print('客户提问:' + que) # 作用:组装agent规定输入格式 inputs = {"messages": [{"role": "user", "content": que}]} # 作用:首次调用agent,触发工具审批中断 result = agent.invoke(inputs, config=config) # 作用:从中断结果取出待审批工具名称 tool_name = result['__interrupt__'][0].value['action_requests'][0]['name'] # 作用:控制台手动输入审批指令,只能approve/reject app_or_reject = input("请确认调用{}是否同意(approve or reject):".format(tool_name)) # 作用:构造恢复指令,携带审批结果继续运行agent # Command:恢复对象,resume传入审批结果字典 res = agent.invoke( Command(resume={'decisions': [{'type': app_or_reject}]}), config=config ) # 作用:打印最终大模型回答 print(res['messages'][-1].content)