当前位置: 首页 > news >正文

接入工具代码讲解


❓ 疑问一:AI 是一定会调用工具,还是会直接生成代码?

答案是:全看 AI 自己觉得需不需要!它拥有完全的“自主决定权”。

在传统的编程里,代码写了if就走分支,写了requests.get就一定发请求。但在 Agent 的世界里,coder_llm_with_tools.invoke(messages)这一行代码,就像是把一份**“开卷考试的考卷”和一部“能上网的手机(工具)”**同时放在了 Claude 面前。

  • 场景 A:闭眼作答(直接生成代码)

假设用户的需求是:“写一个计算斐波那契数列的函数”。

Claude 看到这个需求,心里想:“这太简单了,我闭着眼睛都能写,根本不需要查资料。”

于是,它不会输出任何工具调用指令,而是直接老老实实地输出一段带有<code_block>的纯文本

我们的路由器(Router)看到它没用工具,就会直接把它送去 Tester 节点。

  • 场景 B:翻书作答(调用工具)

假设用户的需求是:“使用最新的 LangGraph 0.3 版本 API 写一个状态图”。

Claude 看到后,心里一惊:“我的训练数据只到 2024 年,我不知道 0.3 版本长什么样。”

这时候,它就会主动选择**“求助”**。它会输出一个ToolCall指令,要求使用search_web工具去搜“LangGraph 0.3 API”。

路由器发现它求助了,就会把它送到 Tools 节点去查资料。

💡总结:我们通过bind_tools只是给了它“查资料的能力和权利”,但绝对不强制它必须查。这就是所谓“智能”的体现——它会根据自己掌握知识的边界,动态决定要不要使用工具。


❓ 疑问二:正则表达式(Regex)到底是怎么提取代码的?

正则表达式(Regular Expression,简称 Regex)就像是给文本做的一台“高精度外科手术”。大模型往往很啰嗦,可能会说:“好的老板,这是您要的代码:\n<code_block>def test(): pass</code_block>\n 希望您满意!”

我们需要把废话剔除,只精准地把尖括号中间的代码“抠”出来。

让我们彻底解剖这行代码:

code_match = re.search(r'<code_block>(.*?)</code_block>', ai_text, re.DOTALL)

1. 拆解匹配规则r'<code_block>(.*?)</code_block>'
  • r'...':前面的r代表“原始字符串”(Raw string),意思是告诉 Python 忽略转义字符,里面写啥就是啥。
  • <code_block></code_block>:这就像是两个“定位锚点”。告诉正则引擎:“你先给我找到左边这个词,再找到右边这个词。”
  • (.*?)(这是最核心的魔法)
    • .(点):代表**“匹配任何单个字符”**(字母、数字、标点都可以)。
    • *(星号):代表**“重复前面的字符 0 次或无数次”**。所以.*连起来就是“匹配中间的任意一长串内容”。
    • ?(问号,非贪婪模式):如果没有这个问号,正则会非常“贪婪”,它会从第一个<code_block>一直匹配到整篇文章最后一个</code_block>(如果文中有好几个块就全乱套了)。加了?就是告诉它:“只要遇到第一个</code_block>,你就赶紧停下来!”
    • ()(括号,捕获组):圆括号的意思是“提取”。虽然我们匹配了整个<code_block>...内容...</code_block>,但我只想要括号里面的“内容”,不想要两边的标签。这叫捕获组 1
2. 为什么要加re.DOTALL标志?

这是一个极其容易踩坑的地方!

在正则表达式的默认规则里,刚才说的那个神奇的.(点),唯独不能匹配一种东西:换行符(\n

但是,我们提取的 Python 代码通常是有十几行的,中间全是换行符!如果不加这个参数,匹配会在第一行结束时直接断掉报错。

加上re.DOTALL,就是赋予了.特权:“从现在起,连换行符你也给我统统匹配进去!”

3. 如何拿到提取的结果?
if code_match: # group(0) 会返回完整的:<code_block>def a(): pass</code_block> # group(1) 只会返回括号里捕获的纯净代码:def a(): pass pure_code = code_match.group(1).strip() # strip() 用于去首尾的空格和空行

❓ 疑问三:last_message到底是个什么对象?

当代码执行完response = coder_llm_with_tools.invoke(messages)时,返回的response(也就是我们存进状态里的last_message),并不是一个普通的字符串,而是 LangChain 框架定义的一个对象(Object),叫做AIMessage

这个AIMessage对象身上,有两个极其重要的属性(字段):

  1. content:用来存放 AI 说的普通文本内容(比如写好的代码、或者聊天的废话)。
  2. tool_calls:这是一个列表(List),专门用来存放 AI 发出的工具调用请求

🎬 场景还原:两种情况下的内存快照

让我们看看在你刚才提问的两种情况下,这个对象在内存里长什么样。

情况 A:AI 决定直接写代码(不查资料)

当 AI 觉得题目太简单,直接输出带<code_block>的代码时。

这个AIMessage对象的内部结构是这样的:

AIMessage( content="好的,这是代码:\n<code_block>def test(): pass</code_block>", tool_calls=[], # <--- 注意看这里!列表是空的! # ... 其他属性忽略 )

此时,路由器执行到if last_message.tool_calls:

在 Python 中,一个空的列表[]会被当成False。所以if条件不成立,代码不会进入Tools节点,而是顺理成章地走向了Tester节点。

情况 B:AI 决定求助(调用search_web

当 AI 遇到“LangGraph 0.3 API”这种知识盲区时,由于我们用bind_tools提前告诉过它“你有工具可用”,它就会在底层触发一个特殊的生成模式。

这时候,生成的AIMessage对象结构变了:

AIMessage( content="", # <--- 注意!通常文本内容是空的,因为它没在说话,而在按按钮。 tool_calls=[ # <--- 核心魔法在这里!列表里多了一个字典! { "name": "search_web", # 它想要调用的工具名称 "args": {"query": "LangGraph 0.3 API"}, # 它自动提取的搜索关键字 "id": "call_abc123xyz" # 这次调用的唯一流水号 } ], # ... 其他属性忽略 )

此时,路由器再次执行到if last_message.tool_calls:

这次,列表里有东西了!在 Python 中,非空列表会被当成True

于是if条件成立!程序立刻打印[Router] 决策:Coder 需要查资料...,并把流程导向了Tools节点。


💡 为什么需要那个特殊的id字段?(进阶思考)

你可能会注意到上面那个call_abc123xyz。这在微服务通信里叫“请求溯源 ID”。

当我们的Tools节点去百度搜完结果后,我们要把结果发回给大模型。大模型怎么知道这个结果对应的是它哪一次的提问呢?

所以在Tools节点里,我们必须把这个 ID 抄下来,连同搜索结果一起打包成一个ToolMessage发给它:

ToolMessage( content="搜索结果:LangGraph 0.3 引入了新的...", tool_call_id="call_abc123xyz" # <--- 拿着单号去交差 )

大模型一看到这个单号对上了,就会恍然大悟:“哦!这是我刚才查的资料结果回来了!我现在可以开始写代码了!”


🔬 代码逻辑深度拆解

在这个十字路口,路由器拿到了last_message(也就是 AI 刚刚在coder_node里吐出来的那个对象)。

第一道关卡:hasattr(last_message, "tool_calls")
  • 这是什么?这是 Python 中的内置函数,意思是:“查一下last_message这个对象身上,有没有一个叫做tool_calls的属性(字段)?”
  • 为什么要这么写(防御性编程)?在 LangChain 的世界里,消息有很多种类型:HumanMessage(人类说的)、SystemMessage(系统设定的)、以及AIMessage(AI 说的)。

通常情况下,last_message肯定是个AIMessage。但作为严谨的工程师,我们要防患于未然:万一系统出了 Bug,传过来一个根本没有tool_calls属性的奇怪对象,如果你直接写if last_message.tool_calls:,Python 解释器会立刻原地爆炸,抛出AttributeError导致整个微服务宕机。

所以,先礼后兵。先问一句:“兄弟,你身上有这个口袋吗?”

第二道关卡:and last_message.tool_calls
  • 这是什么?只有当第一道关卡通过(它确实有这个口袋)时,程序才会执行这半句。这半句的意思是:“既然你有这个口袋,你把口袋翻开,里面有东西吗?
  • 底层逻辑

就像我们上节课“抓包”看到的:

    • 如果 AI 决定直接写代码,这个口袋是个空列表[]。在 Python 的if语句中,空列表等同于False。条件不成立,继续往下走(去测代码)。
    • 如果 AI 决定用search_web,这个口袋里就会有一个字典[{'name': 'search_web', ...}]非空列表等同于True。条件成立,立刻返回"tools",把流程拐进工具节点!

http://www.jsqmd.com/news/636448/

相关文章:

  • 2026贵阳车牌识别系统官方联系方式与智慧停车品牌深度横评|无人值守停车解决方案对标指南 - 精选优质企业推荐榜
  • 2026物联网终端产业观察:AI端侧算力、5G RedCap与渠道下沉的规模化落地 - 格行官方招商总部
  • 经典算法:打家劫舍(动态规划 + 回溯求最优解)C++ 超详细解析
  • AIAgent架构中的多目标优化难题(工业级Agent系统92%失败源于此)
  • 如何用c# 做 mcp/ChatGPT app挂
  • 通过 C# 复制 Word 文档、指定段落、指定节庇
  • 抖音广告批量制作神器:用MATLAB+FFmpeg实现自动视频拼接(附完整源码)
  • Ansible模块
  • 2026年贵阳智慧停车车牌识别系统与安防门禁一体化解决方案深度横评 - 精选优质企业推荐榜
  • 保姆级教程:手把手教你为RTA-OS硬件Counter写那4个要命的回调函数(含避坑指南)
  • 【Redis工具类实战】SpringBoot中静态工具类的配置与多场景应用
  • Freertos中队列头尾指针及读写指针工作机制
  • fMRI(4-1)统计分析报告生成器说明
  • D11 15. 三数之和 18. 四数之和
  • 2026贵阳车牌识别系统与无人值守停车场完全指南:5大本土品牌深度横评+官方直达联系方式 - 精选优质企业推荐榜
  • EtherCAT:工业自动化中的实时通信引擎
  • 别再乱用配合了!SolidWorks装配体设计中‘重合’、‘同轴’、‘距离’三大核心关系的深度解析与实战技巧
  • ESPS USB MSC 调试全过程记录范
  • 璀璨星河Starry Night应用场景:儿童绘本AI辅助创作落地案例
  • 深度解析猫抓扩展:从资源嗅探到流媒体下载的全面实战指南
  • 零基础快速上手:CodeFormer AI人脸修复开源工具完全指南
  • 别再数据线了!用FastAPI 分钟搭个局域网文件+剪贴板神器刭
  • 5分钟掌握模糊PID控制器:让机器人控制像人脑一样智能思考
  • C语言_数组_题3
  • 从CTF赛题到实战:利用phar伪协议绕过上传限制的攻防演练
  • CSS如何保证移动端顶部Fixed头部的安全区域
  • 打通智能体孤岛:用 AgentRun 构建生产级 AA 多 Agent 管理协作系统僦
  • 别再迷信仿真!实测STM32的3.3V PWM也能驱动IR2104(附完整代码与波形分析)
  • PubTest_1775973795700
  • 大学思政课高分通关秘籍:我用思维导图搞定马原期末考试(附全套复习资料)