项目5 无Function Call的结构化通用Agent知识点结构化Agent、Prompt工程、不支持Function Call模型适配、自定义工具功能自己写 Prompt 约束格式Action:xxx Action Input:xxx不用模型原生Function Call纯提示词实现工具调用接入计算器、时间查询、本地文档检索工具落地结构化Agent不靠模型靠Prompt实现Agent。项目拆解核心定位纯 Prompt 工程实现 Agent 工具调用零模型原生函数调用能力。阶段一项目基础协议定义前置基建无代码步骤 1定义 Agent 结构化交互协议项目核心标准实践统一模型输出字段、工具调用格式、问答分支规则配套理论无 Function Call Agent 的结构化解析原理阶段二Prompt 工程实现Agent 大脑规则层步骤 2手写完整版结构化约束 System Prompt实践编写角色规则、工具清单、判断逻辑、正负样例、输出规约配套理论Prompt 工程如何替代原生 Function Call 能力阶段三代码基建核心底座Python步骤 3搭建项目基础代码结构初始化全局配置实践创建项目文件结构、导入依赖、定义全局常量配套理论模块化 Agent 工程的分层设计思想阶段四核心能力开发工具层步骤 4开发 3 个自定义原生工具实践逐一对接实现 calculator、time_query、doc_search 工具函数配套理论自定义工具的入参、出参标准化设计规范阶段五Agent 核心逻辑解析 调度层步骤 5开发文本格式解析器实践代码解析模型输出提取 Thought / Action / Action Input配套理论结构化输出的正则匹配解析原理步骤 6开发工具路由调度器实践根据解析出的 Action 名称自动分发执行对应工具配套理论Agent 工具调度的路由机制阶段六Agent 闭环能力多轮迭代步骤 7实现单轮 Agent 调用链路实践用户提问→模型推理→工具调用→结果返回步骤 8实现多轮 Agent 思考闭环实践工具结果回传模型支持多次连续调用工具直至问题解决配套理论Agent ReAct 思考迭代框架核心逻辑阶段七测试落地步骤 9全场景功能测试、异常兼容优化实践测试计算、时间、文档检索、无需工具直接回答、格式异常场景本项目属于独立可复用的工具调度模块。整体层级关系上层大模型负责思考决策、输出规范格式文本中层咱们写的解析 调度逻辑拆解指令、匹配工具底层计算器、文档查询这类具体功能函数本项目承接模型指令落地实际操作是完整智能体里的执行环节。1 项目基础协议放弃模型原生函数调用接口依靠约定文本格式做调用标识程序端解析文本完成工具触发实现等效 Agent 工具调用能力。1.1 应答分支规则无需调用工具直接输出最终答案不出现指定格式字段需要调用工具严格遵循固定三段式格式输出字段顺序不可调换1.2 标准输出格式Thought:思考判断内容 Action:工具标识名 Action Input:工具传入参数1.3 工具名册与使用规范工具标识功能用途入参要求calculator数学运算计算填写完整数学表达式time_query获取当前系统时间无参数填空即可doc_search本地文档关键词检索填写检索关键词1.4 格式约束细则字段名称固定大小写、文字不可修改删减思考内容简洁说明调用工具的原因参数严格匹配对应工具格式不额外附加多余描述2 结构化约束 System Prompt通过提示词强制约束模型输出范式、决策逻辑与工具使用规则替代原生 Function Call 接口让模型按约定格式产出可解析调用指令。2.1 完整 Prompt 文案你是结构化智能Agent无法使用模型原生函数调用能力仅按既定格式完成交互作答。 可用工具列表1.calculator执行数学运算入参填写标准数学表达式2.time_query查询当前系统时间无需传入参数3.doc_search检索本地文档内容入参填写检索关键词 作答规则1.简单问题无需调用工具直接给出最终答案即可2.涉及计算、查时间、查文档的问题必须严格按下方固定格式输出不得增减内容、更改字段顺序 标准调用格式 Thought:简述调用工具的原因 Action:填写对应工具标识 Action Input:填写对应所需参数 参考示例 示例1调用计算 Thought:需要计算数值结果调用计算器工具 Action:calculator Action Input:1236*2示例2查询时间 Thought:需要获取当前时间调用时间查询工具 Action:time_query Action Input:示例3文档检索 Thought:需要查找相关文档信息调用文档检索工具 Action:doc_search Action Input:项目开发规范2.2 实操要点文案明确区分直接作答、工具调用两种场景绑定前期约定的工具名与入参规范保证前后统一附上实操样例降低模型格式出错概率3 项目基础代码结构采用分层模块化架构拆分规则、工具、解析、调度模块降低耦合便于后续迭代扩展。3.1 规划目录文件结构agent_project/├── main.py# 程序入口、对话主逻辑├── tools.py# 计算器/时间/文档检索工具函数└── parser.py# 模型输出格式解析逻辑3.2 初始化各基础文件本次基础功能无需额外第三方库仅使用 Python 内置模块即可开发无需额外安装包。简单问题文字释义、词语理解这类属于模型自身知识库内容不属于原生函数调用无需动用工具直接作答即可。内置知识库 vs 联网搜索 边界总结内置知识库不触发 invoke低消耗模型训练时录入的存量知识本地直接推理作答适用词语释义、常识典故、基础理论、历史固定内容、文本分析、常规问答联网搜索 / 外部工具触发 invoke额外耗资源向外请求外部数据、实时内容、本地文件、运算服务适用实时时间、最新资讯、动态数据、数学计算、文档查询、当下变动信息核心分界信息固定不变→用内置知识信息实时可变 / 外部独有→走调用检索4 自定义工具函数工具单独封装成独立单元统一输入输出形式和调度逻辑拆分改动互不影响。4.1 明确文件与整体思路操作文件tools.py整体规划分别编写三个工具方法最后用字典建立工具名和方法的对应关系4.2 逐个梳理工具开发要点计算器工具接收参数数学表达式字符串核心逻辑解析表达式算出结果容错处理捕获计算出错的情况返回异常提示时间查询工具接收参数无实际有效参数核心逻辑读取系统当前时间规范日期时间展示格式返回内容格式化后的时间文本本地文档检索工具接收参数检索关键词核心逻辑内置模拟文档数据根据关键词匹配内容判定分支匹配到则返回对应内容无匹配则给出未查询到提示4.3 构建工具映射关系定义映射容器把之前约定的 action 标识名一一对应绑定到各自工具方法后续调度时直接通过名称就能调用对应功能。# 工具映射表TOOL_MAP{calculator:compute,time_query:get_time,doc_search:search_document}5 文本格式解析器拆分模型返回文本提取 Thought、Action、Input 三段内容为后续工具调度提供有效数据。5.1 核心思路按固定字段标识切割文本分别取出思考内容、工具名、入参校验字段完整性异常则判定无需调用工具5.2 关键操作要点匹配Thought:、Action:、Action Input:前缀拆分内容去除首尾多余空格换行规整数据解析成功返回三段数据解析失败返回空标识6 工具路由调度器承接解析后的指令匹配对应工具函数完成调用并返回执行结果。执行步骤调用解析器拆分出思考内容、工具标识、传入参数判断工具标识是否为空无调用指令则仅返回思考信息检索工具映射表校验当前工具是否合法可用匹配到对应工具函数传入参数执行运算查询汇总思考过程与工具执行结果统一对外输出标准格式测试输入文本Thought:查询项目相关说明 Action:doc_search Action Input:项目介绍核心代码fromparserimportparserfromtoolsimportTOOL_MAPif__name____main__:test_inputThought: 查询项目相关说明 Action: doc_search Action Input: 项目介绍# 调用解析器拆分出思考内容、工具标识、传入参数input_parserparser(test_input)print(input_parser)res# 判断工具标识是否为空无调用指令则仅返回思考信息ifinput_parser[action]:resfthought:{input_parser[thought]}else:# 检索工具映射表校验当前工具是否合法可用ifinput_parser[action]notinTOOL_MAP:print(当前工具不可用)else:# 匹配到对应工具函数传入参数执行运算查询toolTOOL_MAP[input_parser[action]]# 汇总思考过程与工具执行结果统一对外输出outputtool(input_parser[action_input])resfthought:{input_parser[thought]}\n output:{output}print(res)运行结果{thought:查询项目相关说明,action:doc_search,action_input:项目介绍}thought:查询项目相关说明 output:这是一个无FunctionCall的结构化Agent7 单轮 Agent 调用链路完整流转用户提问 → 模型推理生成结构化指令 → 解析拆解指令 → 路由匹配工具执行 → 整合结果反馈。执行步骤定义接收用户问题的入口编写模拟模型逻辑依据问题内容产出规范格式的思考、动作、参数文本调用原有解析模块拆分出思考内容、工具标识、入参走工具调度逻辑判断是否调用工具并执行拼接信息输出最终答复核心代码先用 mock 模拟输出现阶段只模拟推理结果格式暂不接入真实大模型接口fromparserimportparserfromtoolsimportTOOL_MAPdefinvoke(input):print(input)# 编写模拟模型逻辑依据问题内容产出规范格式的思考、动作、参数文本ifinput.find(计算)!-1:test_inputThought: 11结果是什么 Action: calculator Action Input: 11elifinput.find(查询)!-1:test_inputThought: 查询项目相关说明 Action: doc_search Action Input: 项目介绍elifinput.find(时间)!-1:test_inputThought: 现在是什么时间 Action: time_query Action Input: returntest_inputif__name____main__:# 定义接收用户问题的入口llm_outputinvoke(现在是什么时间)# 调用解析器拆分出思考内容、工具标识、传入参数input_parserparser(llm_output)res# 判断工具标识是否为空无调用指令则仅返回思考信息ifinput_parser[action]:resfthought:{input_parser[thought]}else:# 检索工具映射表校验当前工具是否合法可用ifinput_parser[action]notinTOOL_MAP:print(当前工具不可用)else:# 匹配到对应工具函数传入参数执行运算查询toolTOOL_MAP[input_parser[action]]# 汇总思考过程与工具执行结果统一对外输出outputtool(input_parser[action_input])resfthought:{input_parser[thought]}\n output:{output}print(res)8 ReAct 多轮思考闭环8.1 ReActReAct Reason推理思考 Act行动调用工具是 Agent 经典迭代框架边思考边行动循环往复解决问题。8.2 核心运作流程思考Reason模型分析问题 已有信息判断是否需要调用工具、用什么工具→ 对应你代码里的 invoke 函数生成结构化指令行动Act解析指令 → 匹配工具 → 执行函数 → 拿到结果→ 对应你的 parser 工具调度逻辑复盘反馈把工具执行结果回传给模型让模型重新判断问题是否解决循环迭代未解决 → 继续思考 调用工具已解决 → 停止循环给出最终答案核心规则工具结果回传给模型循环判断是否继续调用工具直到问题办结。8.3 实操分步思路定义对话上下文变量用来存放用户问题、每一轮的思考、工具执行结果持续传给模型用循环包裹单轮执行逻辑让 Agent 可以自动重复思考→行动→复盘而不是只跑一次每轮结束拼接上下文把本轮的思考 工具结果追加到上下文里给下一轮模型使用模型根据上下文决策读取历史信息继续生成新指令或判断任务完成设置终止条件当模型输出空工具action 为空时结束循环输出最终回答流程图8.4 核心代码fromparserimportparserfromtoolsimportTOOL_MAPdefinvoke(input):print(input)# 编写模拟模型逻辑依据问题内容产出规范格式的思考、动作、参数文本ifinput.find(计算)!-1:test_inputThought: 11结果是什么 Action: calculator Action Input: 11elifinput.find(查询)!-1:test_inputThought: 查询项目相关说明 Action: doc_search Action Input: 项目介绍elifinput.find(时间)!-1:test_inputThought: 现在是什么时间 Action: time_query Action Input: else:test_inputThought: 问题已解决 Action: Action Input:returntest_inputif__name____main__:# 对话上下文context{user_query:,history:[]}# 思考义接收用户问题的入口llm_outputinvoke(现在是什么时间)context[user_query]现在是什么时间whileTrue:# 调用解析器拆分出思考内容、工具标识、传入参数input_parserparser(llm_output)# 当 action 为空时退出循环ifinput_parser[action]:breakres# 检索工具映射表校验当前工具是否合法可用ifinput_parser[action]notinTOOL_MAP:print(当前工具不可用)break# 跳出循环结束Agentelse:# 行动匹配到对应工具函数传入参数执行运算查询toolTOOL_MAP[input_parser[action]]# 汇总思考过程与工具执行结果统一对外输出outputtool(input_parser[action_input])resfthought:{input_parser[thought]}\n output:{output}context[history].append(res)print(---)print(context)# 复盘# 拼接上下文用户问题 历史结果context_strf用户问题{context[user_query]}历史结果{str(context[history])}llm_outputinvoke(context_str)运行结果局部9 全场景测试 异常兼容优化9.1 测试场景清单常规计算帮我计算 11资料查询帮我查询项目介绍时间查询现在是什么时间无需工具闲聊你好呀无效工具请求帮我查询天气9.2 代码优化要点问题抽取成变量方便切换测试增加异常捕获规避执行报错输出规整最终总结答复9.3 验证标准合法请求正常调用工具、循环迭代后自动收尾无效工具、程序报错均可稳妥终止不会死循环各类意图场景都能按逻辑分支正确响应项目6 Function Call 智能工具助手项目核心功能实现 3 个工具查天气工具查当前时间工具简单知识库检索工具自动调用工具用户问问题 → 模型自己判断要不要调用工具、调用哪个、传什么参数自定义解析器自己写解析函数控制 Agent 什么时候停止思考、停止调用工具最后必须输出结构化 JSON不管中间调用多少次工具最终回答强制输出标准 JSON不能随便说话Function Call核心本质就是大模型主动调用外部工具。核心逻辑模型本身知识有边界、没法实时数据、没法运算检索就靠 Function Call 向外求助模型不再只输出文本而是生成规范的工具调用指令包含工具名、入参程序解析指令执行对应工具拿到结果再回传给模型模型结合工具返回内容继续推理或是给出最终答复项目里的查天气、时间、知识库检索全都是靠这套机制实现调度开发步骤阶段 1环境准备 基础配置安装必要依赖langchain、langchain-openai 等配置大模型GPT / 通义千问 / 文心一言 都行阶段 2编写 3 个自定义工具写一个 “查天气” 工具简单模拟返回写一个 “查当前时间” 工具写一个 “知识库检索” 工具模拟本地知识阶段 3学习并使用 bind_tools把工具绑定给大模型理解 Function Call 原理模型什么时候决定调用工具阶段 4搭建 AgentExecutor 执行器搭建 Agent 核心运行流程理解用户提问 → 思考 → 调用工具 → 获取结果 → 再思考阶段 5编写自定义解析器自己写 parse 函数控制什么时候停止调用工具、进入最终回答阶段 6强制输出 JSON 格式Response 伪装让模型最后必须调用 Response 工具输出固定结构 JSON而不是自然语言阶段 7完整测试测试各种问题只需要查时间需要查天气需要查知识库混合问题看 Agent 能不能自动调度工具 最后输出 JSON1 环境准备 基础配置先引入环境变量、模型、工具相关基础模块读取密钥接口地址实例化大模型温度设 0 保证推理稳定2 自定义工具LangChain 自定义工具的固定规则用 tool 装饰器修饰一个函数函数名 工具名函数文档字符串注释 给大模型看的工具说明非常重要函数参数 工具需要的入参函数返回值 工具执行结果核心代码fromdatetimeimportdatetimefromlangchain_core.toolsimporttooltooldefget_current_time():returndatetime.now().strftime(%Y-%m-%d %H:%M:%S)tooldefget_weather(city):returnf{city}市 晴天25℃documents{项目介绍:这是一个无FunctionCall的结构化Agent,工具列表:计算器、时间查询、文档检索,使用规则:必须按指定格式输出调用指令}tooldefknowledge_base_search(query):ifqueryindocuments:returndocuments[query]else:return未找到相关文档3 bind_tools什么是 bind_tools将自定义的工具 “告诉” 大模型让模型知道这里有几个工具可以用需要做的事情把你定义的 3 个工具放进一个列表用模型的 .bind_tools() 方法把这个列表传进去得到一个绑定了工具的新模型对象规则工具列表 [工具 1, 工具 2, 工具 3]直接调用 chat_model.bind_tools(工具列表)赋值给一个新变量比如 llm_with_tools核心代码tools[get_current_time,get_weather,knowledge_base_search]chat_model_with_toolschat_model.bind_tools(tools)4 AgentExecutor 执行器必须先创建一个 Agentcreate_tool_calling_agent绑定好工具的模型chat_model_with_tools工具列表tools一个 prompt 提示词模板系统提示 用户输入创建 AgentExecutor —— Agent 的 “运行器”第一步创建的 agent工具列表tools可以加verboseTrue 看思考过程强烈建议开executor.invoke ()传入用户问题即可自动调用工具核心代码fromlangchain_classic.agentsimportcreate_tool_calling_agent,AgentExecutorfromlangchain_core.promptsimportChatPromptTemplate,MessagesPlaceholder chat_prompt_templateChatPromptTemplate.from_messages([(system,你是工具助手必须调用工具回答),(user,{user_input}),MessagesPlaceholder(agent_scratchpad)])agentcreate_tool_calling_agent(chat_model_with_tools,tools,chat_prompt_template)executorAgentExecutor(agentagent,toolstools,verboseTrue)resexecutor.invoke({user_input:南京天气如何})print(res)运行结果5 自定义解析器脱离默认 AgentExecutor 黑盒手动解析工具调用、自主控制流程启停。执行流程步骤 1梳理核心依赖与前置准备现有资源已绑定工具的模型、工具列表、提示词模板直接复用。核心对象模型返回结果里的 tool_calls 字段用来判断是否要调用工具。步骤 2实现工具匹配执行逻辑定义一个独立函数入参设计为工具名、工具参数。遍历全局工具列表根据工具名匹配对应工具。匹配成功就执行工具并返回结果匹配失败返回提示文本。步骤 3构造首轮对话上下文用提示词模板组装请求内容传入用户问题。agent_scratchpad 首轮传空列表用来存放后续交互记录。步骤 4自定义解析 流程控制核心调用模型得到响应结果。判断响应是否存在 tool_calls存在提取工具名、入参调用上面的工具执行函数拿到结果手动终止本轮流程。不存在直接取模型文本内容作为最终回答流程结束。自定义解析器的自定义体现在自己判断工具、自己拆字段、自己找工具、自己控制终止核心代码defexecute_action(res):fortoolintools:iftool.nameres.tool_calls[0][name]:restool.invoke(res.tool_calls[0][args])returnresreturn未找到对应的工具请重试chainchat_prompt_template|chat_model_with_tools reschain.invoke({user_input:我在胡言乱语啊啊啊啊啊,agent_scratchpad:[]})print(res)ifres.tool_calls:res1execute_action(res)print(res1)else:# 直接提取文本内容作为最终回复流程结束。print(res.content)运行结果正常情况南京市 晴天25℃异常情况content哈哈听起来你是在释放压力或者玩文字游戏呢如果你有任何问题、需要帮助或者只是想聊点什么我都很乐意陪你 \n要不要试试问点有趣的、实用的或者来点脑洞大开的问题additional_kwargs{}response_metadata{finish_reason:stop,request_id:c75a6e27-08dc-9e7b-8461-c769b09fd5b6,token_usage:{input_tokens:258,output_tokens:53,total_tokens:311,prompt_tokens_details:{cached_tokens:0}}}idlc_run--019e64d3-f637-7820-b912-8f95b0aeb04ftool_calls[]invalid_tool_calls[]哈哈听起来你是在释放压力或者玩文字游戏呢如果你有任何问题、需要帮助或者只是想聊点什么我都很乐意陪你 要不要试试问点有趣的、实用的或者来点脑洞大开的问题6 强制结构化输出本节主要内容为约束模型输出格式 自定义解析 JSON 内容。提示词修改为chat_prompt_templateChatPromptTemplate.from_messages([(system,你是工具助手必须调用工具回答。只输出 JSON不要说任何多余的话。如果是纯文本信息那么回复的内容有type值为text)和content(值为回答的内容\n属性如果是工具调用相关那么是type(值为tool和tool_name要调用的工具名称和 args工具函数的入参),(user,{user_input}),MessagesPlaceholder(agent_scratchpad)])chat_model不再需要绑定tools。在上述提示词模板下输出如下。content{type: tool, tool_name: get_weather, args: {city: 南京}}additional_kwargs{}response_metadata{finish_reason:stop,request_id:7838f54d-ec09-9226-922e-961fc962beb1,token_usage:{input_tokens:96,output_tokens:23,total_tokens:119,prompt_tokens_details:{cached_tokens:0}}}idlc_run--019e64e6-1126-7210-8933-0a5d91a01577tool_calls[]invalid_tool_calls[]对输出进行解析修改解析器defexecute_action(res):fortoolintools:iftool.nameres[tool_name]:restool.invoke(res[args])returnresreturn未找到对应的工具请重试chainchat_prompt_template|chat_model reschain.invoke({user_input:南京天气如何,agent_scratchpad:[]})print(res)contentjson.loads(res.content)ifcontent[type]tool:res1execute_action(content)print(res1)else:# 直接提取文本内容作为最终回复流程结束。print(content)运行结果南京市 晴天25℃本节目的在不使用模型原生工具调用不 bind_tools的前提下通过提示词强制模型输出【自定义结构化 JSON】手动解析格式、手动匹配工具、手动执行调用最终拿到结果