[{"data":1,"prerenderedAt":4020},["ShallowReactive",2],{"note:\u002Fagent\u002F01-functioncall函数调用":3},{"id":4,"title":5,"body":6,"date":4000,"description":4001,"draft":4002,"extension":4003,"featured":4002,"kind":4004,"lastmod":4000,"meta":4005,"navigation":104,"path":4009,"planned":4010,"section":4011,"seo":4012,"source":4013,"stem":4014,"tags":4015,"toc":104,"weight":352,"__hash__":4019},"notes\u002Fagent\u002F01-FunctionCall函数调用.md","Function Call：让大模型学会调用外部工具",{"type":7,"value":8,"toc":3983},"minimark",[9,16,21,26,29,32,39,45,148,156,160,167,249,362,369,376,380,383,386,442,448,462,575,582,638,642,645,648,651,655,658,665,677,683,697,702,766,773,781,786,875,894,902,909,913,933,937,940,945,953,988,992,1077,1081,1084,1219,1262,1269,1359,1362,1705,1709,1716,1723,1753,1760,1800,1804,1807,1889,1907,2049,2053,2502,2509,2517,2521,2537,2550,2555,2569,2581,2584,2591,2868,2887,2891,2894,2911,2925,2936,3007,3025,3033,3035,3042,3344,3348,3501,3520,3528,3603,3607,3613,3618,3621,3712,3730,3735,3737,3744,3932,3947,3951,3979],[10,11,12],"blockquote",{},[13,14,15],"p",{},"一句话总结：Function Call 让大模型从「只会聊天」变成「懂得求助」——模型自己不执行任何函数，只输出调用所需的参数，由应用程序去调用真实的 API，再把结果交回模型组织成自然语言回复。",[17,18,20],"h2",{"id":19},"_1-什么是-function-call","1. 什么是 Function Call",[22,23,25],"h3",{"id":24},"_11-从一个生活场景说起","1.1 从一个生活场景说起",[13,27,28],{},"想象你在一家电商公司做售后客服，客户发来消息：\"我上周买的那台咖啡机，快递现在到哪了？\"",[13,30,31],{},"你会怎么做？你不会凭空编造一个物流节点——你会打开物流后台，输入运单号查一下，看到\"快件已到达杭州转运中心，预计明天上午派送\"，然后回复客户：\"您的咖啡机今天凌晨已到达杭州转运中心，预计明天上午送达。\"",[13,33,34,38],{},[35,36,37],"strong",{},"大模型也面临同样的处境","。用户问它\"我的快递到哪了\"，但大模型的知识截止于训练数据的时间点，它既不知道这串运单号存在，也看不到任何物流轨迹。如果硬让它回答，它要么瞎猜（产生\"幻觉\"），要么无奈地说\"我不知道\"。",[13,40,41,44],{},[35,42,43],{},"Function Call 就是让大模型学会\"查后台\"的能力","——模型自己不查询，但它能判断\"这个问题需要调用物流工具\"，然后告诉你：\"请帮我查一下运单号=SF1234567890 的物流轨迹\"。应用程序拿到这些参数后，去调用真实的物流 API，把结果交回给模型，模型再把这些数据组织成一句通顺的回复。",[46,47,52],"pre",{"className":48,"code":49,"language":50,"meta":51,"style":51},"language-mermaid shiki shiki-themes github-light github-dark","flowchart LR\n    A[\"① 用户提问\u003Cbr\u002F>我的快递到哪了？\"] --> B[\"② 大模型思考\u003Cbr\u002F>需要调用物流工具\"]\n    B --> C[\"③ 模型输出参数\u003Cbr\u002F>tracking_no=SF1234567890\"]\n    C --> D[\"④ 应用程序调用 API\u003Cbr\u002F>物流 API → 已到达杭州转运中心\"]\n    D --> E[\"⑤ 返回真实数据\u003Cbr\u002F>已到达杭州转运中心，预计明天上午派送\"]\n    E --> F[\"⑥ 模型组织语言\u003Cbr\u002F>您的包裹已经到杭州转运中心了\"]\n    F --> G[\"⑦ 用户收到回答\u003Cbr\u002F>预计明天上午送达，请留意签收\"]\n\n    style A fill:#F8FAFC,stroke:#64748B,stroke-width:2px\n    style B fill:#ECFDF5,stroke:#10B981,stroke-width:2px\n    style C fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n    style D fill:#FFF7ED,stroke:#EA580C,stroke-width:2px\n    style E fill:#ECFDF5,stroke:#10B981,stroke-width:2px\n    style F fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n    style G fill:#F8FAFC,stroke:#64748B,stroke-width:2px\n","mermaid","",[53,54,55,63,69,75,81,87,93,99,106,112,118,124,130,136,142],"code",{"__ignoreMap":51},[56,57,60],"span",{"class":58,"line":59},"line",1,[56,61,62],{},"flowchart LR\n",[56,64,66],{"class":58,"line":65},2,[56,67,68],{},"    A[\"① 用户提问\u003Cbr\u002F>我的快递到哪了？\"] --> B[\"② 大模型思考\u003Cbr\u002F>需要调用物流工具\"]\n",[56,70,72],{"class":58,"line":71},3,[56,73,74],{},"    B --> C[\"③ 模型输出参数\u003Cbr\u002F>tracking_no=SF1234567890\"]\n",[56,76,78],{"class":58,"line":77},4,[56,79,80],{},"    C --> D[\"④ 应用程序调用 API\u003Cbr\u002F>物流 API → 已到达杭州转运中心\"]\n",[56,82,84],{"class":58,"line":83},5,[56,85,86],{},"    D --> E[\"⑤ 返回真实数据\u003Cbr\u002F>已到达杭州转运中心，预计明天上午派送\"]\n",[56,88,90],{"class":58,"line":89},6,[56,91,92],{},"    E --> F[\"⑥ 模型组织语言\u003Cbr\u002F>您的包裹已经到杭州转运中心了\"]\n",[56,94,96],{"class":58,"line":95},7,[56,97,98],{},"    F --> G[\"⑦ 用户收到回答\u003Cbr\u002F>预计明天上午送达，请留意签收\"]\n",[56,100,102],{"class":58,"line":101},8,[56,103,105],{"emptyLinePlaceholder":104},true,"\n",[56,107,109],{"class":58,"line":108},9,[56,110,111],{},"    style A fill:#F8FAFC,stroke:#64748B,stroke-width:2px\n",[56,113,115],{"class":58,"line":114},10,[56,116,117],{},"    style B fill:#ECFDF5,stroke:#10B981,stroke-width:2px\n",[56,119,121],{"class":58,"line":120},11,[56,122,123],{},"    style C fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n",[56,125,127],{"class":58,"line":126},12,[56,128,129],{},"    style D fill:#FFF7ED,stroke:#EA580C,stroke-width:2px\n",[56,131,133],{"class":58,"line":132},13,[56,134,135],{},"    style E fill:#ECFDF5,stroke:#10B981,stroke-width:2px\n",[56,137,139],{"class":58,"line":138},14,[56,140,141],{},"    style F fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n",[56,143,145],{"class":58,"line":144},15,[56,146,147],{},"    style G fill:#F8FAFC,stroke:#64748B,stroke-width:2px\n",[10,149,150],{},[13,151,152,155],{},[35,153,154],{},"图示解读："," 一次完整的 Function Call 共七步——用户提问 → 大模型判断需要调用物流工具 → 模型只输出结构化参数（不是答案）→ 应用程序拿参数调用真实物流 API → 拿到真实数据 → 交回模型组织语言 → 用户收到自然语言回答。注意第 ③ 步与第 ④ 步的分工：模型负责\"想清楚要什么参数\"，应用程序负责\"真正把事办了\"。",[22,157,159],{"id":158},"_12-更多生活中的-function-call","1.2 更多生活中的 Function Call",[13,161,162,163,166],{},"Function Call 的思路其实很朴素——",[35,164,165],{},"让\"聪明但不联网\"的大脑，学会\"调用外部工具获取实时信息\"","。类似的场景在生活中随处可见：",[168,169,170,189],"table",{},[171,172,173],"thead",{},[174,175,176,180,183,186],"tr",{},[177,178,179],"th",{},"场景",[177,181,182],{},"大脑（大模型）",[177,184,185],{},"工具（外部 API）",[177,187,188],{},"用户提问",[190,191,192,207,221,235],"tbody",{},[174,193,194,198,201,204],{},[195,196,197],"td",{},"查快递",[195,199,200],{},"知道\"跨省一般 3 天到\"的经验，但不知道你这单走到哪了",[195,202,203],{},"物流 API → 返回\"已到达杭州转运中心\"",[195,205,206],{},"\"我的快递到哪了？\"",[174,208,209,212,215,218],{},[195,210,211],{},"查余票",[195,213,214],{},"知道京沪高铁大概 4 个多小时，但不知道明天的余票",[195,216,217],{},"票务 API → 返回\"二等座剩余 36 张\"",[195,219,220],{},"\"明天上午还有去上海的高铁吗？\"",[174,222,223,226,229,232],{},[195,224,225],{},"查餐厅等位",[195,227,228],{},"知道这家店人均 80 元、招牌是酸菜鱼，但不知道现在要等多久",[195,230,231],{},"排队 API → 返回\"前面还有 6 桌，预计 25 分钟\"",[195,233,234],{},"\"这家店现在要排队吗？\"",[174,236,237,240,243,246],{},[195,238,239],{},"点咖啡",[195,241,242],{},"理解\"帮我点杯冰美式\"的意思，但没有权限操作门店系统",[195,244,245],{},"点单 API → 返回\"订单已提交，取餐号 A027\"",[195,247,248],{},"\"帮我点一杯冰美式\"",[46,250,252],{"className":48,"code":251,"language":50,"meta":51,"style":51},"flowchart LR\n    W[\"🚚 物流 API\u003Cbr\u002F>我的快递到哪了？\u003Cbr\u002F>→ 已到达杭州转运中心\u003Cbr\u002F>tracking_no=SF1234567890\"]\n    E[\"🚄 票务 API\u003Cbr\u002F>明天还有去上海的高铁吗？\u003Cbr\u002F>→ 二等座剩余 36 张\u003Cbr\u002F>from=北京、to=上海、date=2026-09-16\"]\n    S[\"🍽 排队 API\u003Cbr\u002F>这家店现在要排队吗？\u003Cbr\u002F>→ 前面还有 6 桌\u003Cbr\u002F>shop=外婆家·西湖店\"]\n    O[\"☕ 点单 API\u003Cbr\u002F>帮我点一杯冰美式\u003Cbr\u002F>→ 取餐号 A027\u003Cbr\u002F>item=冰美式、qty=1\"]\n    LLM[\"🧠 大模型（LLM）\u003Cbr\u002F>判断需要调用什么工具\u003Cbr\u002F>提取参数 → 返回结构化调用指令\"]\n\n    LLM -->|调用工具| W\n    W -.->|返回结果| LLM\n    LLM -->|调用工具| E\n    E -.->|返回结果| LLM\n    LLM -->|调用工具| S\n    S -.->|返回结果| LLM\n    LLM -->|调用工具| O\n    O -.->|返回结果| LLM\n\n    style LLM fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n    style W fill:#FFF7ED,stroke:#EA580C,stroke-width:2px\n    style E fill:#ECFDF5,stroke:#059669,stroke-width:2px\n    style S fill:#F5F3FF,stroke:#7C3AED,stroke-width:2px\n    style O fill:#FEF2F2,stroke:#DC2626,stroke-width:2px\n",[53,253,254,258,263,268,273,278,283,287,292,297,302,307,312,317,322,327,332,338,344,350,356],{"__ignoreMap":51},[56,255,256],{"class":58,"line":59},[56,257,62],{},[56,259,260],{"class":58,"line":65},[56,261,262],{},"    W[\"🚚 物流 API\u003Cbr\u002F>我的快递到哪了？\u003Cbr\u002F>→ 已到达杭州转运中心\u003Cbr\u002F>tracking_no=SF1234567890\"]\n",[56,264,265],{"class":58,"line":71},[56,266,267],{},"    E[\"🚄 票务 API\u003Cbr\u002F>明天还有去上海的高铁吗？\u003Cbr\u002F>→ 二等座剩余 36 张\u003Cbr\u002F>from=北京、to=上海、date=2026-09-16\"]\n",[56,269,270],{"class":58,"line":77},[56,271,272],{},"    S[\"🍽 排队 API\u003Cbr\u002F>这家店现在要排队吗？\u003Cbr\u002F>→ 前面还有 6 桌\u003Cbr\u002F>shop=外婆家·西湖店\"]\n",[56,274,275],{"class":58,"line":83},[56,276,277],{},"    O[\"☕ 点单 API\u003Cbr\u002F>帮我点一杯冰美式\u003Cbr\u002F>→ 取餐号 A027\u003Cbr\u002F>item=冰美式、qty=1\"]\n",[56,279,280],{"class":58,"line":89},[56,281,282],{},"    LLM[\"🧠 大模型（LLM）\u003Cbr\u002F>判断需要调用什么工具\u003Cbr\u002F>提取参数 → 返回结构化调用指令\"]\n",[56,284,285],{"class":58,"line":95},[56,286,105],{"emptyLinePlaceholder":104},[56,288,289],{"class":58,"line":101},[56,290,291],{},"    LLM -->|调用工具| W\n",[56,293,294],{"class":58,"line":108},[56,295,296],{},"    W -.->|返回结果| LLM\n",[56,298,299],{"class":58,"line":114},[56,300,301],{},"    LLM -->|调用工具| E\n",[56,303,304],{"class":58,"line":120},[56,305,306],{},"    E -.->|返回结果| LLM\n",[56,308,309],{"class":58,"line":126},[56,310,311],{},"    LLM -->|调用工具| S\n",[56,313,314],{"class":58,"line":132},[56,315,316],{},"    S -.->|返回结果| LLM\n",[56,318,319],{"class":58,"line":138},[56,320,321],{},"    LLM -->|调用工具| O\n",[56,323,324],{"class":58,"line":144},[56,325,326],{},"    O -.->|返回结果| LLM\n",[56,328,330],{"class":58,"line":329},16,[56,331,105],{"emptyLinePlaceholder":104},[56,333,335],{"class":58,"line":334},17,[56,336,337],{},"    style LLM fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n",[56,339,341],{"class":58,"line":340},18,[56,342,343],{},"    style W fill:#FFF7ED,stroke:#EA580C,stroke-width:2px\n",[56,345,347],{"class":58,"line":346},19,[56,348,349],{},"    style E fill:#ECFDF5,stroke:#059669,stroke-width:2px\n",[56,351,353],{"class":58,"line":352},20,[56,354,355],{},"    style S fill:#F5F3FF,stroke:#7C3AED,stroke-width:2px\n",[56,357,359],{"class":58,"line":358},21,[56,360,361],{},"    style O fill:#FEF2F2,stroke:#DC2626,stroke-width:2px\n",[10,363,364],{},[13,365,366,368],{},[35,367,154],{}," 四个场景都以大模型为调度中枢：模型判断\"该调哪个工具\"→ 提取参数 → 应用执行 → 结果回传 → 组织自然语言回复。差别只在于挂载的工具不同，主流程完全一致——这正是 Function Call 能被抽象成一套标准协议的前提。",[13,370,371,372,375],{},"可以看到，每个场景都遵循相同的模式：",[35,373,374],{},"大模型判断\"我需要调用某个工具\"→ 提取工具所需的参数 → 应用程序替它执行 → 把结果交回大模型 → 大模型组织成自然语言回复用户","。这就是 Function Call 的核心流程。",[22,377,379],{"id":378},"_13-function-call-解决了什么问题","1.3 Function Call 解决了什么问题",[13,381,382],{},"在 Function Call 出现之前，要让大模型调用外部工具是一件非常困难的事——开发者需要自己写复杂的正则表达式或提示词工程，从用户输入中硬提取参数，再手动调用 API。这种方式不仅脆弱（用户换个说法就可能失效），而且无法处理多工具选择。",[13,384,385],{},"Function Call 的出现在三个层面解决了大模型的瓶颈：",[168,387,388,401],{},[171,389,390],{},[174,391,392,395,398],{},[177,393,394],{},"问题维度",[177,396,397],{},"没有 Function Call",[177,399,400],{},"有了 Function Call",[190,402,403,416,429],{},[174,404,405,410,413],{},[195,406,407],{},[35,408,409],{},"信息实时性",[195,411,412],{},"只能用训练截止日期前的知识，问实时问题只能回答\"我不知道\"",[195,414,415],{},"模型自主判断何时需要调用物流、票务、排队等实时 API，获取最新数据",[174,417,418,423,426],{},[195,419,420],{},[35,421,422],{},"数据局限性",[195,424,425],{},"无法覆盖医学、法律、企业内部数据等专业领域",[195,427,428],{},"模型可调用外部数据库或 API，获取任意领域的详细信息",[174,430,431,436,439],{},[195,432,433],{},[35,434,435],{},"功能扩展性",[195,437,438],{},"复杂计算、数据分析等能力需要全部内建",[195,440,441],{},"只需注册新的工具函数，模型即可按需调用，能力无限扩展",[13,443,444,447],{},[35,445,446],{},"一个直观的对比","：假设用户问\"帮我看看明天上午还有没有去上海的高铁票，顺便查一下我那个快递到哪了\"。",[449,450,451,455],"ul",{},[452,453,454],"li",{},"没有 Function Call：大模型只能输出一段文字——\"我无法查询实时票务和物流信息，建议您使用 12306 和快递 App。\"",[452,456,457,458,461],{},"有了 Function Call：大模型先输出 ",[53,459,460],{},"{\"name\": \"search_train\", \"args\": {\"from\": \"北京\", \"to\": \"上海\", \"date\": \"2026-09-16\"}}","，应用程序调用票务 API 拿到结果；再把结果和物流查询一起返回给模型，模型整合成一段通顺的回复：\"查到 5 趟高铁可选……您的快递已到杭州转运中心，预计明天上午送达。\"",[46,463,465],{"className":48,"code":464,"language":50,"meta":51,"style":51},"flowchart TB\n    subgraph NoFC[\"❌ 没有 Function Call\"]\n        N1[\"👤 用户\"] -->|提问| N2[\"大模型\"]\n        N2 --> N3[\"无法查询实时信息\u003Cbr\u002F>知识截止于训练数据\"]\n        N3 -->|无法回答| N1\n    end\n\n    subgraph HasFC[\"✅ 有了 Function Call\"]\n        H1[\"👤 用户\"] -->|提问| H2[\"大模型\"]\n        H2 -->|工具调用| H3[\"应用程序\"]\n        H3 --> H4[\"调用 API\"]\n        H4 -.->|返回结果| H2\n        H2 -.->|生成回复| H1\n    end\n\n    style NoFC fill:#FEF2F2,stroke:#DC2626,stroke-width:2px\n    style HasFC fill:#ECFDF5,stroke:#059669,stroke-width:2px\n    style N2 fill:#FFFFFF,stroke:#DC2626\n    style N3 fill:#FFFFFF,stroke:#DC2626\n    style H2 fill:#FFFFFF,stroke:#059669\n    style H3 fill:#FFFFFF,stroke:#059669\n    style H4 fill:#FFFFFF,stroke:#059669\n",[53,466,467,472,477,482,487,492,497,501,506,511,516,521,526,531,535,539,544,549,554,559,564,569],{"__ignoreMap":51},[56,468,469],{"class":58,"line":59},[56,470,471],{},"flowchart TB\n",[56,473,474],{"class":58,"line":65},[56,475,476],{},"    subgraph NoFC[\"❌ 没有 Function Call\"]\n",[56,478,479],{"class":58,"line":71},[56,480,481],{},"        N1[\"👤 用户\"] -->|提问| N2[\"大模型\"]\n",[56,483,484],{"class":58,"line":77},[56,485,486],{},"        N2 --> N3[\"无法查询实时信息\u003Cbr\u002F>知识截止于训练数据\"]\n",[56,488,489],{"class":58,"line":83},[56,490,491],{},"        N3 -->|无法回答| N1\n",[56,493,494],{"class":58,"line":89},[56,495,496],{},"    end\n",[56,498,499],{"class":58,"line":95},[56,500,105],{"emptyLinePlaceholder":104},[56,502,503],{"class":58,"line":101},[56,504,505],{},"    subgraph HasFC[\"✅ 有了 Function Call\"]\n",[56,507,508],{"class":58,"line":108},[56,509,510],{},"        H1[\"👤 用户\"] -->|提问| H2[\"大模型\"]\n",[56,512,513],{"class":58,"line":114},[56,514,515],{},"        H2 -->|工具调用| H3[\"应用程序\"]\n",[56,517,518],{"class":58,"line":120},[56,519,520],{},"        H3 --> H4[\"调用 API\"]\n",[56,522,523],{"class":58,"line":126},[56,524,525],{},"        H4 -.->|返回结果| H2\n",[56,527,528],{"class":58,"line":132},[56,529,530],{},"        H2 -.->|生成回复| H1\n",[56,532,533],{"class":58,"line":138},[56,534,496],{},[56,536,537],{"class":58,"line":144},[56,538,105],{"emptyLinePlaceholder":104},[56,540,541],{"class":58,"line":329},[56,542,543],{},"    style NoFC fill:#FEF2F2,stroke:#DC2626,stroke-width:2px\n",[56,545,546],{"class":58,"line":334},[56,547,548],{},"    style HasFC fill:#ECFDF5,stroke:#059669,stroke-width:2px\n",[56,550,551],{"class":58,"line":340},[56,552,553],{},"    style N2 fill:#FFFFFF,stroke:#DC2626\n",[56,555,556],{"class":58,"line":346},[56,557,558],{},"    style N3 fill:#FFFFFF,stroke:#DC2626\n",[56,560,561],{"class":58,"line":352},[56,562,563],{},"    style H2 fill:#FFFFFF,stroke:#059669\n",[56,565,566],{"class":58,"line":358},[56,567,568],{},"    style H3 fill:#FFFFFF,stroke:#059669\n",[56,570,572],{"class":58,"line":571},22,[56,573,574],{},"    style H4 fill:#FFFFFF,stroke:#059669\n",[10,576,577],{},[13,578,579,581],{},[35,580,154],{}," 左边是\"死路\"：模型只能基于训练数据作答，遇到实时问题就退回\"我无法查询\"。右边多了「应用程序」与「调用 API」两个环节，模型把执行权外包出去，自己只负责决策与表达——链路变长了，但答案从\"编造\"变成了\"有据可查\"。",[168,583,584,594],{},[171,585,586],{},[174,587,588,590,592],{},[177,589],{},[177,591,397],{},[177,593,400],{},[190,595,596,607,617,627],{},[174,597,598,601,604],{},[195,599,600],{},"三大问题 \u002F 优势",[195,602,603],{},"信息不实时：只能用训练截止日期的知识",[195,605,606],{},"信息实时：自主调用物流\u002F票务\u002F排队等 API",[174,608,609,611,614],{},[195,610],{},[195,612,613],{},"数据有局限：无法覆盖医学\u002F法律\u002F企业等专业领域",[195,615,616],{},"数据无限：可接入任意领域的数据库\u002FAPI",[174,618,619,621,624],{},[195,620],{},[195,622,623],{},"功能难扩展：复杂能力需要全部内建到模型中",[195,625,626],{},"功能可扩展：只需注册新工具函数即可",[174,628,629,632,635],{},[195,630,631],{},"示例回复",[195,633,634],{},"无法查询票务和物流信息，建议您使用 12306 和快递 App。",[195,636,637],{},"查到 5 趟高铁可选，最快 4h18m……您的快递已到杭州转运中心，预计明天上午送达。",[22,639,641],{"id":640},"_14-function-call-的正式定义","1.4 Function Call 的正式定义",[13,643,644],{},"2023 年 6 月 13 日 OpenAI 公布了 Function Call（函数调用）功能：它允许开发者向 GPT-4 和 GPT-3.5-turbo 模型描述函数，模型会智能地选择输出一个包含调用这些函数参数的 JSON 对象。这是一种更可靠地将 GPT 的能力与外部工具和 API 相连接的新方法。",[13,646,647],{},"GPT-4 及 GPT-3.5-turbo 之所以能使用 Function Call，是因为这些模型经过训练，不仅可以检测到何时需要调用函数（根据用户的输入），还可以回复符合函数参数的 JSON 对象，而不是直接返回常规的文本。",[13,649,650],{},"目前支持 Function Call 功能的模型除了 GPT 系列外，国内的模型也支持，如：百度文心一言、ChatGLM3-6B、讯飞星火 3.0 等。",[17,652,654],{"id":653},"_2-function-call-工作原理","2. Function Call 工作原理",[13,656,657],{},"接下来，我们对比有无 Function Call 功能时 GPT 模型工作流程的差异。",[13,659,660,661,664],{},"当",[35,662,663],{},"没有","函数调用（Function Call）时，我们调用 GPT 构建 AI 应用的模式非常简单：",[666,667,668,671,674],"ol",{},[452,669,670],{},"用户（client）发请求给我们的服务（chat server）",[452,672,673],{},"我们的服务（chat server）给 GPT 提示词",[452,675,676],{},"重复执行",[13,678,660,679,682],{},[35,680,681],{},"有","函数调用（Function Call）时，模式比之前要复杂一些：",[666,684,685,688,691,694],{},[452,686,687],{},"用户发请求提示词，chat server 将提示词和可以调用的函数发送给大模型",[452,689,690],{},"GPT 模型根据用户的提示词，判断是用普通文本还是函数调用的格式响应我们的服务",[452,692,693],{},"如果是函数调用格式，那么 chat server 就会执行这个函数，并将结果返回给 GPT",[452,695,696],{},"然后模型使用提供的数据，用连贯的文本回答",[13,698,699],{},[35,700,701],{},"无 Function Call 时的工作流程：",[46,703,705],{"className":48,"code":704,"language":50,"meta":51,"style":51},"sequenceDiagram\n    participant U as 用户\n    participant S as Chat Server\n    participant L as 大模型\n\n    U->>S: ① 发送问题\n    Note over U,S: 我的快递到哪了？\n    S->>L: ② 发送提示词\n    Note over S,L: 仅包含用户问题（无工具描述）\n    L-->>S: ③ 返回纯文本回答\n    Note over S,L: 我不知道，训练数据中没有……\n    S-->>U: ④ 返回回复\n",[53,706,707,712,717,722,727,731,736,741,746,751,756,761],{"__ignoreMap":51},[56,708,709],{"class":58,"line":59},[56,710,711],{},"sequenceDiagram\n",[56,713,714],{"class":58,"line":65},[56,715,716],{},"    participant U as 用户\n",[56,718,719],{"class":58,"line":71},[56,720,721],{},"    participant S as Chat Server\n",[56,723,724],{"class":58,"line":77},[56,725,726],{},"    participant L as 大模型\n",[56,728,729],{"class":58,"line":83},[56,730,105],{"emptyLinePlaceholder":104},[56,732,733],{"class":58,"line":89},[56,734,735],{},"    U->>S: ① 发送问题\n",[56,737,738],{"class":58,"line":95},[56,739,740],{},"    Note over U,S: 我的快递到哪了？\n",[56,742,743],{"class":58,"line":101},[56,744,745],{},"    S->>L: ② 发送提示词\n",[56,747,748],{"class":58,"line":108},[56,749,750],{},"    Note over S,L: 仅包含用户问题（无工具描述）\n",[56,752,753],{"class":58,"line":114},[56,754,755],{},"    L-->>S: ③ 返回纯文本回答\n",[56,757,758],{"class":58,"line":120},[56,759,760],{},"    Note over S,L: 我不知道，训练数据中没有……\n",[56,762,763],{"class":58,"line":126},[56,764,765],{},"    S-->>U: ④ 返回回复\n",[10,767,768],{},[13,769,770,772],{},[35,771,154],{}," 整条链路只有一次模型交互：Chat Server 把用户问题原样转给大模型，模型只能\"掏记忆\"。由于提示词里没有任何工具描述，模型连\"可以调用物流 API\"这件事都不知道，最终只能返回一段纯文本。",[10,774,775],{},[13,776,777,780],{},[35,778,779],{},"局限："," 大模型只能基于训练数据回答，无法获取实时信息，遇到知识截止后的问题只能猜测或拒绝回答。",[13,782,783],{},[35,784,785],{},"有 Function Call 时的工作流程：",[46,787,789],{"className":48,"code":788,"language":50,"meta":51,"style":51},"sequenceDiagram\n    participant U as 用户\n    participant S as Chat Server\n    participant L as 大模型\n    participant T as 外部工具\u002FAPI\n\n    U->>S: ① 发送问题\n    S->>L: ② 提示词 + 可用函数列表\n    Note over S,L: get_package_track(tracking_no)\n    L-->>S: ③ 返回函数调用请求\n    Note over S,L: get_package_track(tracking_no=SF1234567890)\n    S->>T: ④ 应用程序执行函数调用\n    T-->>S: ⑤ 返回函数执行结果\n    Note over S,T: status=运输中、node=杭州转运中心、eta=明天上午\n    S->>L: ⑥ 将结果传回模型\n    L-->>S: ⑦ 生成自然语言回答\n    Note over S,L: 您的快递已到达杭州转运中心……\n    S-->>U: ⑧ 返回最终回复\n",[53,790,791,795,799,803,807,812,816,820,825,830,835,840,845,850,855,860,865,870],{"__ignoreMap":51},[56,792,793],{"class":58,"line":59},[56,794,711],{},[56,796,797],{"class":58,"line":65},[56,798,716],{},[56,800,801],{"class":58,"line":71},[56,802,721],{},[56,804,805],{"class":58,"line":77},[56,806,726],{},[56,808,809],{"class":58,"line":83},[56,810,811],{},"    participant T as 外部工具\u002FAPI\n",[56,813,814],{"class":58,"line":89},[56,815,105],{"emptyLinePlaceholder":104},[56,817,818],{"class":58,"line":95},[56,819,735],{},[56,821,822],{"class":58,"line":101},[56,823,824],{},"    S->>L: ② 提示词 + 可用函数列表\n",[56,826,827],{"class":58,"line":108},[56,828,829],{},"    Note over S,L: get_package_track(tracking_no)\n",[56,831,832],{"class":58,"line":114},[56,833,834],{},"    L-->>S: ③ 返回函数调用请求\n",[56,836,837],{"class":58,"line":120},[56,838,839],{},"    Note over S,L: get_package_track(tracking_no=SF1234567890)\n",[56,841,842],{"class":58,"line":126},[56,843,844],{},"    S->>T: ④ 应用程序执行函数调用\n",[56,846,847],{"class":58,"line":132},[56,848,849],{},"    T-->>S: ⑤ 返回函数执行结果\n",[56,851,852],{"class":58,"line":138},[56,853,854],{},"    Note over S,T: status=运输中、node=杭州转运中心、eta=明天上午\n",[56,856,857],{"class":58,"line":144},[56,858,859],{},"    S->>L: ⑥ 将结果传回模型\n",[56,861,862],{"class":58,"line":329},[56,863,864],{},"    L-->>S: ⑦ 生成自然语言回答\n",[56,866,867],{"class":58,"line":334},[56,868,869],{},"    Note over S,L: 您的快递已到达杭州转运中心……\n",[56,871,872],{"class":58,"line":340},[56,873,874],{},"    S-->>U: ⑧ 返回最终回复\n",[10,876,877],{},[13,878,879,881,882,885,886,889,890,893],{},[35,880,154],{}," 相比左图，这里多了三条关键信息流：② 提示词里携带了",[35,883,884],{},"可用函数列表","（让模型知道有哪些工具），③ 模型返回的是",[35,887,888],{},"函数调用请求","而非答案，⑤ 外部工具返回",[35,891,892],{},"真实执行结果","。模型在第 ④ 步之后再次被调用（第 ⑥ 步），才把结构化数据翻译成人话。",[10,895,896],{},[13,897,898,901],{},[35,899,900],{},"核心："," 模型不执行函数，只输出参数 → 应用程序执行 → 结果返回模型 → 模型组织回答。",[13,903,904,905,908],{},"需要注意的是，大模型的 Function Call ",[35,906,907],{},"不会执行任何函数调用，仅返回调用函数所需要的参数","。开发者可以利用模型输出的参数在应用中执行函数调用。",[17,910,912],{"id":911},"_3-function-call-使用方式","3. Function Call 使用方式",[10,914,915],{},[13,916,917,920,921,924,925,928,929,932],{},[35,918,919],{},"为什么用文本工具示例？"," Function Call 最典型的应用是查询实时数据（如快递物流、车票余量）——模型只返回函数参数，由应用调用外部 API 获取结果。但为了聚焦\"函数调用\"本身、降低学习门槛，本节用两个最朴素的",[35,922,923],{},"文本小工具","（",[53,926,927],{},"text_length"," \u002F ",[53,930,931],{},"text_repeat","）作为示例：它们不依赖网络和第三方库，结果一眼可验，能让你看清\"定义函数 → 描述函数 → 模型返回参数 → 应用执行\"的完整链路。学会后，把文本工具换成真实业务函数（如查询快递物流）即可，逻辑完全一致。",[22,934,936],{"id":935},"_31-自定义-tool-结构","3.1 自定义 tool 结构",[13,938,939],{},"以下代码通过自定义 JSON 格式的工具 schema 来定义工具。",[941,942,944],"h4",{"id":943},"_311-导包","3.1.1 导包",[10,946,947],{},[13,948,949,950],{},"代码位置：",[53,951,952],{},"agent_learn\u002Ffunction_call\u002FC01_define_tool.py",[46,954,958],{"className":955,"code":956,"language":957,"meta":51,"style":51},"language-python shiki shiki-themes github-light github-dark","from langchain_openai import ChatOpenAI\nfrom langchain_core.messages import HumanMessage, ToolMessage\n\nfrom agent_learn.config import Config\n\nconf = Config()\n","python",[53,959,960,965,970,974,979,983],{"__ignoreMap":51},[56,961,962],{"class":58,"line":59},[56,963,964],{},"from langchain_openai import ChatOpenAI\n",[56,966,967],{"class":58,"line":65},[56,968,969],{},"from langchain_core.messages import HumanMessage, ToolMessage\n",[56,971,972],{"class":58,"line":71},[56,973,105],{"emptyLinePlaceholder":104},[56,975,976],{"class":58,"line":77},[56,977,978],{},"from agent_learn.config import Config\n",[56,980,981],{"class":58,"line":83},[56,982,105],{"emptyLinePlaceholder":104},[56,984,985],{"class":58,"line":89},[56,986,987],{},"conf = Config()\n",[941,989,991],{"id":990},"_312-定义外部函数","3.1.2 定义外部函数",[46,993,995],{"className":955,"code":994,"language":957,"meta":51,"style":51},"# todo: 第一步：定义工具函数\ndef text_length(text: str) -> int:\n    \"\"\"\n    统计一段文本的字符个数\n    Args:\n        text: 待统计的文本\n    \"\"\"\n    return len(text)\n\ndef text_repeat(text: str, times: int) -> str:\n    \"\"\"\n    把文本重复拼接指定次数\n    Args:\n        text: 待重复的文本\n        times: 重复次数\n    \"\"\"\n    return text * times\n",[53,996,997,1002,1007,1012,1017,1022,1027,1031,1036,1040,1045,1049,1054,1058,1063,1068,1072],{"__ignoreMap":51},[56,998,999],{"class":58,"line":59},[56,1000,1001],{},"# todo: 第一步：定义工具函数\n",[56,1003,1004],{"class":58,"line":65},[56,1005,1006],{},"def text_length(text: str) -> int:\n",[56,1008,1009],{"class":58,"line":71},[56,1010,1011],{},"    \"\"\"\n",[56,1013,1014],{"class":58,"line":77},[56,1015,1016],{},"    统计一段文本的字符个数\n",[56,1018,1019],{"class":58,"line":83},[56,1020,1021],{},"    Args:\n",[56,1023,1024],{"class":58,"line":89},[56,1025,1026],{},"        text: 待统计的文本\n",[56,1028,1029],{"class":58,"line":95},[56,1030,1011],{},[56,1032,1033],{"class":58,"line":101},[56,1034,1035],{},"    return len(text)\n",[56,1037,1038],{"class":58,"line":108},[56,1039,105],{"emptyLinePlaceholder":104},[56,1041,1042],{"class":58,"line":114},[56,1043,1044],{},"def text_repeat(text: str, times: int) -> str:\n",[56,1046,1047],{"class":58,"line":120},[56,1048,1011],{},[56,1050,1051],{"class":58,"line":126},[56,1052,1053],{},"    把文本重复拼接指定次数\n",[56,1055,1056],{"class":58,"line":132},[56,1057,1021],{},[56,1059,1060],{"class":58,"line":138},[56,1061,1062],{},"        text: 待重复的文本\n",[56,1064,1065],{"class":58,"line":144},[56,1066,1067],{},"        times: 重复次数\n",[56,1069,1070],{"class":58,"line":329},[56,1071,1011],{},[56,1073,1074],{"class":58,"line":334},[56,1075,1076],{},"    return text * times\n",[941,1078,1080],{"id":1079},"_313-描述函数功能","3.1.3 描述函数功能",[13,1082,1083],{},"光有函数还不够，模型并不知道它存在。我们要用一份 JSON Schema 把工具的\"名字、用途、参数\"描述给模型：",[46,1085,1087],{"className":48,"code":1086,"language":50,"meta":51,"style":51},"flowchart TD\n    ROOT[\"tools[]\u003Cbr\u002F>数组：可定义多个工具\"]\n    ROOT --> T[\"type\u003Cbr\u002F>固定为 function\"]\n    ROOT --> F[\"function\u003Cbr\u002F>对象：函数详细信息\"]\n\n    F --> N[\"name\u003Cbr\u002F>text_length \u002F text_repeat \u002F track_package\"]\n    F --> D[\"description\u003Cbr\u002F>统计一段文本的字符个数\u003Cbr\u002F>模型根据此描述决定是否调用\"]\n    F --> P[\"parameters\u003Cbr\u002F>JSON Schema 对象\"]\n\n    P --> PT[\"type\u003Cbr\u002F>object\"]\n    P --> PP[\"properties\u003Cbr\u002F>text: type=string、description=待统计的文本\u003Cbr\u002F>times: type=integer、description=重复次数\"]\n    P --> PR[\"required\u003Cbr\u002F>[text, times]：哪些参数必须传入\"]\n\n    WARN[\"⚠ 模型不会执行函数，只返回参数\"]\n    PR -.-> WARN\n\n    style ROOT fill:#F8FAFC,stroke:#64748B,stroke-width:2px\n    style T fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n    style F fill:#FFFBEB,stroke:#D97706,stroke-width:2px\n    style N fill:#ECFDF5,stroke:#059669,stroke-width:2px\n    style D fill:#FDF2F8,stroke:#DB2777,stroke-width:2px\n    style P fill:#EEF2FF,stroke:#4F46E5,stroke-width:2px\n    style PT fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n    style PP fill:#FFFBEB,stroke:#D97706,stroke-width:2px\n    style PR fill:#FFFBEB,stroke:#D97706,stroke-width:2px\n    style WARN fill:#FEF2F2,stroke:#DC2626,stroke-width:2px\n",[53,1088,1089,1094,1099,1104,1109,1113,1118,1123,1128,1132,1137,1142,1147,1151,1156,1161,1165,1170,1175,1180,1185,1190,1195,1201,1207,1213],{"__ignoreMap":51},[56,1090,1091],{"class":58,"line":59},[56,1092,1093],{},"flowchart TD\n",[56,1095,1096],{"class":58,"line":65},[56,1097,1098],{},"    ROOT[\"tools[]\u003Cbr\u002F>数组：可定义多个工具\"]\n",[56,1100,1101],{"class":58,"line":71},[56,1102,1103],{},"    ROOT --> T[\"type\u003Cbr\u002F>固定为 function\"]\n",[56,1105,1106],{"class":58,"line":77},[56,1107,1108],{},"    ROOT --> F[\"function\u003Cbr\u002F>对象：函数详细信息\"]\n",[56,1110,1111],{"class":58,"line":83},[56,1112,105],{"emptyLinePlaceholder":104},[56,1114,1115],{"class":58,"line":89},[56,1116,1117],{},"    F --> N[\"name\u003Cbr\u002F>text_length \u002F text_repeat \u002F track_package\"]\n",[56,1119,1120],{"class":58,"line":95},[56,1121,1122],{},"    F --> D[\"description\u003Cbr\u002F>统计一段文本的字符个数\u003Cbr\u002F>模型根据此描述决定是否调用\"]\n",[56,1124,1125],{"class":58,"line":101},[56,1126,1127],{},"    F --> P[\"parameters\u003Cbr\u002F>JSON Schema 对象\"]\n",[56,1129,1130],{"class":58,"line":108},[56,1131,105],{"emptyLinePlaceholder":104},[56,1133,1134],{"class":58,"line":114},[56,1135,1136],{},"    P --> PT[\"type\u003Cbr\u002F>object\"]\n",[56,1138,1139],{"class":58,"line":120},[56,1140,1141],{},"    P --> PP[\"properties\u003Cbr\u002F>text: type=string、description=待统计的文本\u003Cbr\u002F>times: type=integer、description=重复次数\"]\n",[56,1143,1144],{"class":58,"line":126},[56,1145,1146],{},"    P --> PR[\"required\u003Cbr\u002F>[text, times]：哪些参数必须传入\"]\n",[56,1148,1149],{"class":58,"line":132},[56,1150,105],{"emptyLinePlaceholder":104},[56,1152,1153],{"class":58,"line":138},[56,1154,1155],{},"    WARN[\"⚠ 模型不会执行函数，只返回参数\"]\n",[56,1157,1158],{"class":58,"line":144},[56,1159,1160],{},"    PR -.-> WARN\n",[56,1162,1163],{"class":58,"line":329},[56,1164,105],{"emptyLinePlaceholder":104},[56,1166,1167],{"class":58,"line":334},[56,1168,1169],{},"    style ROOT fill:#F8FAFC,stroke:#64748B,stroke-width:2px\n",[56,1171,1172],{"class":58,"line":340},[56,1173,1174],{},"    style T fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n",[56,1176,1177],{"class":58,"line":346},[56,1178,1179],{},"    style F fill:#FFFBEB,stroke:#D97706,stroke-width:2px\n",[56,1181,1182],{"class":58,"line":352},[56,1183,1184],{},"    style N fill:#ECFDF5,stroke:#059669,stroke-width:2px\n",[56,1186,1187],{"class":58,"line":358},[56,1188,1189],{},"    style D fill:#FDF2F8,stroke:#DB2777,stroke-width:2px\n",[56,1191,1192],{"class":58,"line":571},[56,1193,1194],{},"    style P fill:#EEF2FF,stroke:#4F46E5,stroke-width:2px\n",[56,1196,1198],{"class":58,"line":1197},23,[56,1199,1200],{},"    style PT fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n",[56,1202,1204],{"class":58,"line":1203},24,[56,1205,1206],{},"    style PP fill:#FFFBEB,stroke:#D97706,stroke-width:2px\n",[56,1208,1210],{"class":58,"line":1209},25,[56,1211,1212],{},"    style PR fill:#FFFBEB,stroke:#D97706,stroke-width:2px\n",[56,1214,1216],{"class":58,"line":1215},26,[56,1217,1218],{},"    style WARN fill:#FEF2F2,stroke:#DC2626,stroke-width:2px\n",[10,1220,1221],{},[13,1222,1223,1225,1226,1229,1230,1233,1234,1237,1238,1240,1241,1244,1245,1248,1249,1252,1253,1256,1257],{},[35,1224,154],{}," 一份工具定义分三层：最外层 ",[53,1227,1228],{},"tools[]"," 是数组，可以一次注册多个工具；第二层 ",[53,1231,1232],{},"type"," 固定为 ",[53,1235,1236],{},"function","（用于区分未来的其他工具类型），",[53,1239,1236],{}," 对象里装真正的元信息；第三层是 ",[53,1242,1243],{},"name","（模型靠它选择工具）、",[53,1246,1247],{},"description","（模型靠它判断\"该不该调用\"）和 ",[53,1250,1251],{},"parameters","（用 JSON Schema 描述每个参数的类型与含义，",[53,1254,1255],{},"required"," 声明哪些必填）。",[35,1258,1259,1261],{},[53,1260,1247],{}," 是模型决策的唯一依据，写得含糊模型就选错工具。",[13,1263,1264,1265,1268],{},"为了向模型描述外部函数库，需要向 ",[53,1266,1267],{},"tools"," 字段传入可以调用的函数列表。参数如下表：",[168,1270,1271,1287],{},[171,1272,1273],{},[174,1274,1275,1278,1281,1284],{},[177,1276,1277],{},"参数名称",[177,1279,1280],{},"类型",[177,1282,1283],{},"是否必填",[177,1285,1286],{},"参数说明",[190,1288,1289,1302,1314,1325,1336,1347],{},[174,1290,1291,1293,1296,1299],{},[195,1292,1232],{},[195,1294,1295],{},"String",[195,1297,1298],{},"是",[195,1300,1301],{},"设置为 function",[174,1303,1304,1306,1309,1311],{},[195,1305,1236],{},[195,1307,1308],{},"Object",[195,1310,1298],{},[195,1312,1313],{},"函数详细信息对象",[174,1315,1316,1318,1320,1322],{},[195,1317,1243],{},[195,1319,1295],{},[195,1321,1298],{},[195,1323,1324],{},"函数名称",[174,1326,1327,1329,1331,1333],{},[195,1328,1247],{},[195,1330,1295],{},[195,1332,1298],{},[195,1334,1335],{},"用于描述函数功能，模型会根据这段描述决定函数调用方式。",[174,1337,1338,1340,1342,1344],{},[195,1339,1251],{},[195,1341,1308],{},[195,1343,1298],{},[195,1345,1346],{},"需要传入一个 JSON Schema 对象，以准确地定义函数所接受的参数。若调用函数时不需要传入参数，省略该参数即可。",[174,1348,1349,1351,1353,1356],{},[195,1350,1255],{},[195,1352],{},[195,1354,1355],{},"否",[195,1357,1358],{},"指定哪些属性在数据中必须被包含。",[13,1360,1361],{},"说明如下：",[46,1363,1367],{"className":1364,"code":1365,"language":1366,"meta":51,"style":51},"language-json shiki shiki-themes github-light github-dark","\u002F\u002F 定义 JSON 格式的工具 schema\ntools = [\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"text_length\",\n            \"description\": \"统计一段文本的字符个数\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"text\": {\n                        \"type\": \"string\",\n                        \"description\": \"待统计的文本\"\n                    }\n                },\n                \"required\": [\"text\"]\n            }\n        }\n    },\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"text_repeat\",\n            \"description\": \"把文本重复拼接指定次数\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"text\": {\n                        \"type\": \"string\",\n                        \"description\": \"待重复的文本\"\n                    },\n                    \"times\": {\n                        \"type\": \"integer\",\n                        \"description\": \"重复次数\"\n                    }\n                },\n                \"required\": [\"text\", \"times\"]\n            }\n        }\n    }\n]\n","json",[53,1368,1369,1375,1381,1386,1402,1410,1422,1434,1441,1453,1460,1467,1479,1489,1494,1499,1513,1518,1523,1528,1532,1542,1548,1559,1570,1576,1586,1593,1600,1611,1621,1627,1635,1647,1657,1662,1667,1684,1689,1694,1700],{"__ignoreMap":51},[56,1370,1371],{"class":58,"line":59},[56,1372,1374],{"class":1373},"sJ8bj","\u002F\u002F 定义 JSON 格式的工具 schema\n",[56,1376,1377],{"class":58,"line":65},[56,1378,1380],{"class":1379},"sVt8B","tools = [\n",[56,1382,1383],{"class":58,"line":71},[56,1384,1385],{"class":1379},"    {\n",[56,1387,1388,1392,1395,1399],{"class":58,"line":77},[56,1389,1391],{"class":1390},"sj4cs","        \"type\"",[56,1393,1394],{"class":1379},": ",[56,1396,1398],{"class":1397},"sZZnC","\"function\"",[56,1400,1401],{"class":1379},",\n",[56,1403,1404,1407],{"class":58,"line":83},[56,1405,1406],{"class":1390},"        \"function\"",[56,1408,1409],{"class":1379},": {\n",[56,1411,1412,1415,1417,1420],{"class":58,"line":89},[56,1413,1414],{"class":1390},"            \"name\"",[56,1416,1394],{"class":1379},[56,1418,1419],{"class":1397},"\"text_length\"",[56,1421,1401],{"class":1379},[56,1423,1424,1427,1429,1432],{"class":58,"line":95},[56,1425,1426],{"class":1390},"            \"description\"",[56,1428,1394],{"class":1379},[56,1430,1431],{"class":1397},"\"统计一段文本的字符个数\"",[56,1433,1401],{"class":1379},[56,1435,1436,1439],{"class":58,"line":101},[56,1437,1438],{"class":1390},"            \"parameters\"",[56,1440,1409],{"class":1379},[56,1442,1443,1446,1448,1451],{"class":58,"line":108},[56,1444,1445],{"class":1390},"                \"type\"",[56,1447,1394],{"class":1379},[56,1449,1450],{"class":1397},"\"object\"",[56,1452,1401],{"class":1379},[56,1454,1455,1458],{"class":58,"line":114},[56,1456,1457],{"class":1390},"                \"properties\"",[56,1459,1409],{"class":1379},[56,1461,1462,1465],{"class":58,"line":120},[56,1463,1464],{"class":1390},"                    \"text\"",[56,1466,1409],{"class":1379},[56,1468,1469,1472,1474,1477],{"class":58,"line":126},[56,1470,1471],{"class":1390},"                        \"type\"",[56,1473,1394],{"class":1379},[56,1475,1476],{"class":1397},"\"string\"",[56,1478,1401],{"class":1379},[56,1480,1481,1484,1486],{"class":58,"line":132},[56,1482,1483],{"class":1390},"                        \"description\"",[56,1485,1394],{"class":1379},[56,1487,1488],{"class":1397},"\"待统计的文本\"\n",[56,1490,1491],{"class":58,"line":138},[56,1492,1493],{"class":1379},"                    }\n",[56,1495,1496],{"class":58,"line":144},[56,1497,1498],{"class":1379},"                },\n",[56,1500,1501,1504,1507,1510],{"class":58,"line":329},[56,1502,1503],{"class":1390},"                \"required\"",[56,1505,1506],{"class":1379},": [",[56,1508,1509],{"class":1397},"\"text\"",[56,1511,1512],{"class":1379},"]\n",[56,1514,1515],{"class":58,"line":334},[56,1516,1517],{"class":1379},"            }\n",[56,1519,1520],{"class":58,"line":340},[56,1521,1522],{"class":1379},"        }\n",[56,1524,1525],{"class":58,"line":346},[56,1526,1527],{"class":1379},"    },\n",[56,1529,1530],{"class":58,"line":352},[56,1531,1385],{"class":1379},[56,1533,1534,1536,1538,1540],{"class":58,"line":358},[56,1535,1391],{"class":1390},[56,1537,1394],{"class":1379},[56,1539,1398],{"class":1397},[56,1541,1401],{"class":1379},[56,1543,1544,1546],{"class":58,"line":571},[56,1545,1406],{"class":1390},[56,1547,1409],{"class":1379},[56,1549,1550,1552,1554,1557],{"class":58,"line":1197},[56,1551,1414],{"class":1390},[56,1553,1394],{"class":1379},[56,1555,1556],{"class":1397},"\"text_repeat\"",[56,1558,1401],{"class":1379},[56,1560,1561,1563,1565,1568],{"class":58,"line":1203},[56,1562,1426],{"class":1390},[56,1564,1394],{"class":1379},[56,1566,1567],{"class":1397},"\"把文本重复拼接指定次数\"",[56,1569,1401],{"class":1379},[56,1571,1572,1574],{"class":58,"line":1209},[56,1573,1438],{"class":1390},[56,1575,1409],{"class":1379},[56,1577,1578,1580,1582,1584],{"class":58,"line":1215},[56,1579,1445],{"class":1390},[56,1581,1394],{"class":1379},[56,1583,1450],{"class":1397},[56,1585,1401],{"class":1379},[56,1587,1589,1591],{"class":58,"line":1588},27,[56,1590,1457],{"class":1390},[56,1592,1409],{"class":1379},[56,1594,1596,1598],{"class":58,"line":1595},28,[56,1597,1464],{"class":1390},[56,1599,1409],{"class":1379},[56,1601,1603,1605,1607,1609],{"class":58,"line":1602},29,[56,1604,1471],{"class":1390},[56,1606,1394],{"class":1379},[56,1608,1476],{"class":1397},[56,1610,1401],{"class":1379},[56,1612,1614,1616,1618],{"class":58,"line":1613},30,[56,1615,1483],{"class":1390},[56,1617,1394],{"class":1379},[56,1619,1620],{"class":1397},"\"待重复的文本\"\n",[56,1622,1624],{"class":58,"line":1623},31,[56,1625,1626],{"class":1379},"                    },\n",[56,1628,1630,1633],{"class":58,"line":1629},32,[56,1631,1632],{"class":1390},"                    \"times\"",[56,1634,1409],{"class":1379},[56,1636,1638,1640,1642,1645],{"class":58,"line":1637},33,[56,1639,1471],{"class":1390},[56,1641,1394],{"class":1379},[56,1643,1644],{"class":1397},"\"integer\"",[56,1646,1401],{"class":1379},[56,1648,1650,1652,1654],{"class":58,"line":1649},34,[56,1651,1483],{"class":1390},[56,1653,1394],{"class":1379},[56,1655,1656],{"class":1397},"\"重复次数\"\n",[56,1658,1660],{"class":58,"line":1659},35,[56,1661,1493],{"class":1379},[56,1663,1665],{"class":58,"line":1664},36,[56,1666,1498],{"class":1379},[56,1668,1670,1672,1674,1676,1679,1682],{"class":58,"line":1669},37,[56,1671,1503],{"class":1390},[56,1673,1506],{"class":1379},[56,1675,1509],{"class":1397},[56,1677,1678],{"class":1379},", ",[56,1680,1681],{"class":1397},"\"times\"",[56,1683,1512],{"class":1379},[56,1685,1687],{"class":58,"line":1686},38,[56,1688,1517],{"class":1379},[56,1690,1692],{"class":58,"line":1691},39,[56,1693,1522],{"class":1379},[56,1695,1697],{"class":58,"line":1696},40,[56,1698,1699],{"class":1379},"    }\n",[56,1701,1703],{"class":58,"line":1702},41,[56,1704,1512],{"class":1379},[941,1706,1708],{"id":1707},"_314-模型实例化","3.1.4 模型实例化",[13,1710,1711,1712,1715],{},"为方便使用配置，需要创建 ",[53,1713,1714],{},"Config"," 类。",[10,1717,1718],{},[13,1719,949,1720],{},[53,1721,1722],{},"agent_learn\u002Fconfig.py",[46,1724,1726],{"className":955,"code":1725,"language":957,"meta":51,"style":51},"class Config:\n    def __init__(self):\n        self.base_url = 'https:\u002F\u002Fdashscope.aliyuncs.com\u002Fcompatible-mode\u002Fv1'\n        self.api_key = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx'\n        self.model_name = 'qwen-plus'\n",[53,1727,1728,1733,1738,1743,1748],{"__ignoreMap":51},[56,1729,1730],{"class":58,"line":59},[56,1731,1732],{},"class Config:\n",[56,1734,1735],{"class":58,"line":65},[56,1736,1737],{},"    def __init__(self):\n",[56,1739,1740],{"class":58,"line":71},[56,1741,1742],{},"        self.base_url = 'https:\u002F\u002Fdashscope.aliyuncs.com\u002Fcompatible-mode\u002Fv1'\n",[56,1744,1745],{"class":58,"line":77},[56,1746,1747],{},"        self.api_key = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx'\n",[56,1749,1750],{"class":58,"line":83},[56,1751,1752],{},"        self.model_name = 'qwen-plus'\n",[13,1754,1755,1756,1759],{},"在 ",[53,1757,1758],{},"C01_define_tool.py"," 中实例化模型的代码如下：",[46,1761,1763],{"className":955,"code":1762,"language":957,"meta":51,"style":51},"# todo: 第二步：初始化模型\nllm = ChatOpenAI(base_url=conf.base_url,\n                 api_key=conf.api_key,\n                 model=conf.model_name,\n                 temperature=0.1)\n# 绑定工具，允许模型自动选择工具\nllm_with_tools = llm.bind_tools(tools, tool_choice=\"auto\")\n",[53,1764,1765,1770,1775,1780,1785,1790,1795],{"__ignoreMap":51},[56,1766,1767],{"class":58,"line":59},[56,1768,1769],{},"# todo: 第二步：初始化模型\n",[56,1771,1772],{"class":58,"line":65},[56,1773,1774],{},"llm = ChatOpenAI(base_url=conf.base_url,\n",[56,1776,1777],{"class":58,"line":71},[56,1778,1779],{},"                 api_key=conf.api_key,\n",[56,1781,1782],{"class":58,"line":77},[56,1783,1784],{},"                 model=conf.model_name,\n",[56,1786,1787],{"class":58,"line":83},[56,1788,1789],{},"                 temperature=0.1)\n",[56,1791,1792],{"class":58,"line":89},[56,1793,1794],{},"# 绑定工具，允许模型自动选择工具\n",[56,1796,1797],{"class":58,"line":95},[56,1798,1799],{},"llm_with_tools = llm.bind_tools(tools, tool_choice=\"auto\")\n",[941,1801,1803],{"id":1802},"_315-模型调用","3.1.5 模型调用",[13,1805,1806],{},"绑定完工具后，一次完整的问答要走两轮模型调用：",[46,1808,1810],{"className":48,"code":1809,"language":50,"meta":51,"style":51},"sequenceDiagram\n    participant U as 用户\n    participant A as 应用程序\n    participant L as LLM 大模型\n    participant T as 外部工具\n\n    U->>A: ① 用户提问\n    Note over U,A: 「你好，Agent」这句话有多少个字符？\n    A->>L: ② 调用 LLM（messages + tools）\n    L-->>A: ③ 返回 tool_calls\n    Note over L,A: name=text_length、args=text:「你好，Agent」\n    A->>T: ④ 执行工具 text_length(\"你好，Agent\") → 8\n    T-->>A: ⑤ 返回结果 tool_output = 8\n    A->>L: ⑥ 再次调用 LLM（messages + tool_output）\n    L-->>A: ⑦ 生成回复：「你好，Agent」共 8 个字符\n    A-->>U: ⑧ 返回给用户\n",[53,1811,1812,1816,1820,1825,1830,1835,1839,1844,1849,1854,1859,1864,1869,1874,1879,1884],{"__ignoreMap":51},[56,1813,1814],{"class":58,"line":59},[56,1815,711],{},[56,1817,1818],{"class":58,"line":65},[56,1819,716],{},[56,1821,1822],{"class":58,"line":71},[56,1823,1824],{},"    participant A as 应用程序\n",[56,1826,1827],{"class":58,"line":77},[56,1828,1829],{},"    participant L as LLM 大模型\n",[56,1831,1832],{"class":58,"line":83},[56,1833,1834],{},"    participant T as 外部工具\n",[56,1836,1837],{"class":58,"line":89},[56,1838,105],{"emptyLinePlaceholder":104},[56,1840,1841],{"class":58,"line":95},[56,1842,1843],{},"    U->>A: ① 用户提问\n",[56,1845,1846],{"class":58,"line":101},[56,1847,1848],{},"    Note over U,A: 「你好，Agent」这句话有多少个字符？\n",[56,1850,1851],{"class":58,"line":108},[56,1852,1853],{},"    A->>L: ② 调用 LLM（messages + tools）\n",[56,1855,1856],{"class":58,"line":114},[56,1857,1858],{},"    L-->>A: ③ 返回 tool_calls\n",[56,1860,1861],{"class":58,"line":120},[56,1862,1863],{},"    Note over L,A: name=text_length、args=text:「你好，Agent」\n",[56,1865,1866],{"class":58,"line":126},[56,1867,1868],{},"    A->>T: ④ 执行工具 text_length(\"你好，Agent\") → 8\n",[56,1870,1871],{"class":58,"line":132},[56,1872,1873],{},"    T-->>A: ⑤ 返回结果 tool_output = 8\n",[56,1875,1876],{"class":58,"line":138},[56,1877,1878],{},"    A->>L: ⑥ 再次调用 LLM（messages + tool_output）\n",[56,1880,1881],{"class":58,"line":144},[56,1882,1883],{},"    L-->>A: ⑦ 生成回复：「你好，Agent」共 8 个字符\n",[56,1885,1886],{"class":58,"line":329},[56,1887,1888],{},"    A-->>U: ⑧ 返回给用户\n",[10,1890,1891],{},[13,1892,1893,1895,1896,1899,1900,1902,1903,1906],{},[35,1894,154],{}," 关键在第 ② 与第 ⑥ 步——",[35,1897,1898],{},"同一个模型被调用了两次","。第一次带上 ",[53,1901,1267],{},"，模型只决定\"调哪个工具、传什么参数\"；第 ④ 步由应用程序真正执行本地函数；第 ⑥ 步把执行结果作为 ",[53,1904,1905],{},"ToolMessage"," 追加进消息列表再问一次，模型才产出面向用户的自然语言答案。少了任何一轮，用户都拿不到最终回复。",[46,1908,1910],{"className":955,"code":1909,"language":957,"meta":51,"style":51},"# todo: 第三步：调用回复\nquery = \"「你好，Agent」这句话有多少个字符？\"\nmessages = [HumanMessage(query)]\n\ntry:\n    # todo: 第一次调用\n    ai_msg = llm_with_tools.invoke(messages)\n    messages.append(ai_msg)\n    print(f\"\\n第一轮调用后结果：\\n{messages}\")\n\n    # 处理工具调用\n    # 判断消息中是否有tool_calls，以判断工具是否被调用\n    if hasattr(ai_msg, 'tool_calls') and ai_msg.tool_calls:\n        for tool_call in ai_msg.tool_calls:\n            # todo: 处理工具调用\n            selected_tool = {\"text_length\": text_length, \"text_repeat\": text_repeat}[tool_call[\"name\"].lower()]\n            tool_output = selected_tool(**tool_call[\"args\"])\n            messages.append(ToolMessage(content=tool_output, tool_call_id=tool_call[\"id\"]))\n        print(f\"\\n第二轮  message中增加tool_output 之后：\\n{messages}\")\n\n        # todo: 第二次调用，将工具结果传回模型以生成最终回答\n        final_response = llm_with_tools.invoke(messages)\n        print(f\"\\n最终模型响应：\\n{final_response.content}\")\n    else:\n        print(\"模型未生成工具调用，直接返回文本:\")\n        print(ai_msg.content)\nexcept Exception as e:\n    print(f\"模型调用失败: {str(e)}\")\n",[53,1911,1912,1917,1922,1927,1931,1936,1941,1946,1951,1956,1960,1965,1970,1975,1980,1985,1990,1995,2000,2005,2009,2014,2019,2024,2029,2034,2039,2044],{"__ignoreMap":51},[56,1913,1914],{"class":58,"line":59},[56,1915,1916],{},"# todo: 第三步：调用回复\n",[56,1918,1919],{"class":58,"line":65},[56,1920,1921],{},"query = \"「你好，Agent」这句话有多少个字符？\"\n",[56,1923,1924],{"class":58,"line":71},[56,1925,1926],{},"messages = [HumanMessage(query)]\n",[56,1928,1929],{"class":58,"line":77},[56,1930,105],{"emptyLinePlaceholder":104},[56,1932,1933],{"class":58,"line":83},[56,1934,1935],{},"try:\n",[56,1937,1938],{"class":58,"line":89},[56,1939,1940],{},"    # todo: 第一次调用\n",[56,1942,1943],{"class":58,"line":95},[56,1944,1945],{},"    ai_msg = llm_with_tools.invoke(messages)\n",[56,1947,1948],{"class":58,"line":101},[56,1949,1950],{},"    messages.append(ai_msg)\n",[56,1952,1953],{"class":58,"line":108},[56,1954,1955],{},"    print(f\"\\n第一轮调用后结果：\\n{messages}\")\n",[56,1957,1958],{"class":58,"line":114},[56,1959,105],{"emptyLinePlaceholder":104},[56,1961,1962],{"class":58,"line":120},[56,1963,1964],{},"    # 处理工具调用\n",[56,1966,1967],{"class":58,"line":126},[56,1968,1969],{},"    # 判断消息中是否有tool_calls，以判断工具是否被调用\n",[56,1971,1972],{"class":58,"line":132},[56,1973,1974],{},"    if hasattr(ai_msg, 'tool_calls') and ai_msg.tool_calls:\n",[56,1976,1977],{"class":58,"line":138},[56,1978,1979],{},"        for tool_call in ai_msg.tool_calls:\n",[56,1981,1982],{"class":58,"line":144},[56,1983,1984],{},"            # todo: 处理工具调用\n",[56,1986,1987],{"class":58,"line":329},[56,1988,1989],{},"            selected_tool = {\"text_length\": text_length, \"text_repeat\": text_repeat}[tool_call[\"name\"].lower()]\n",[56,1991,1992],{"class":58,"line":334},[56,1993,1994],{},"            tool_output = selected_tool(**tool_call[\"args\"])\n",[56,1996,1997],{"class":58,"line":340},[56,1998,1999],{},"            messages.append(ToolMessage(content=tool_output, tool_call_id=tool_call[\"id\"]))\n",[56,2001,2002],{"class":58,"line":346},[56,2003,2004],{},"        print(f\"\\n第二轮  message中增加tool_output 之后：\\n{messages}\")\n",[56,2006,2007],{"class":58,"line":352},[56,2008,105],{"emptyLinePlaceholder":104},[56,2010,2011],{"class":58,"line":358},[56,2012,2013],{},"        # todo: 第二次调用，将工具结果传回模型以生成最终回答\n",[56,2015,2016],{"class":58,"line":571},[56,2017,2018],{},"        final_response = llm_with_tools.invoke(messages)\n",[56,2020,2021],{"class":58,"line":1197},[56,2022,2023],{},"        print(f\"\\n最终模型响应：\\n{final_response.content}\")\n",[56,2025,2026],{"class":58,"line":1203},[56,2027,2028],{},"    else:\n",[56,2030,2031],{"class":58,"line":1209},[56,2032,2033],{},"        print(\"模型未生成工具调用，直接返回文本:\")\n",[56,2035,2036],{"class":58,"line":1215},[56,2037,2038],{},"        print(ai_msg.content)\n",[56,2040,2041],{"class":58,"line":1588},[56,2042,2043],{},"except Exception as e:\n",[56,2045,2046],{"class":58,"line":1595},[56,2047,2048],{},"    print(f\"模型调用失败: {str(e)}\")\n",[941,2050,2052],{"id":2051},"_316-完整代码","3.1.6 完整代码",[46,2054,2056],{"className":955,"code":2055,"language":957,"meta":51,"style":51},"from langchain_openai import ChatOpenAI\nfrom langchain_core.messages import HumanMessage, ToolMessage\n\nfrom agent_learn.config import Config\n\nconf = Config()\n\n# todo: 第一步：定义工具函数\ndef text_length(text: str) -> int:\n    \"\"\"\n    统计一段文本的字符个数\n    Args:\n        text: 待统计的文本\n    \"\"\"\n    return len(text)\n\ndef text_repeat(text: str, times: int) -> str:\n    \"\"\"\n    把文本重复拼接指定次数\n    Args:\n        text: 待重复的文本\n        times: 重复次数\n    \"\"\"\n    return text * times\n\n# 定义 JSON 格式的工具 schema\ntools = [\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"text_length\",\n            \"description\": \"统计一段文本的字符个数\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"text\": {\"type\": \"string\", \"description\": \"待统计的文本\"}\n                },\n                \"required\": [\"text\"]\n            }\n        }\n    },\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"text_repeat\",\n            \"description\": \"把文本重复拼接指定次数\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"text\": {\"type\": \"string\", \"description\": \"待重复的文本\"},\n                    \"times\": {\"type\": \"integer\", \"description\": \"重复次数\"}\n                },\n                \"required\": [\"text\", \"times\"]\n            }\n        }\n    }\n]\n\n# todo: 第二步：初始化模型\nllm = ChatOpenAI(base_url=conf.base_url,\n                 api_key=conf.api_key,\n                 model=conf.model_name,\n                 temperature=0.1)\n# 绑定工具，允许模型自动选择工具\nllm_with_tools = llm.bind_tools(tools, tool_choice=\"auto\")\n\n# todo: 第三步：调用回复\nquery = \"「你好，Agent」这句话有多少个字符？\"\nmessages = [HumanMessage(query)]\n\ntry:\n    # todo: 第一次调用\n    ai_msg = llm_with_tools.invoke(messages)\n    messages.append(ai_msg)\n    print(f\"\\n第一轮调用后结果：\\n{messages}\")\n\n    # 处理工具调用\n    # 判断消息中是否有tool_calls，以判断工具是否被调用\n    if hasattr(ai_msg, 'tool_calls') and ai_msg.tool_calls:\n        for tool_call in ai_msg.tool_calls:\n            # todo: 处理工具调用\n            selected_tool = {\"text_length\": text_length, \"text_repeat\": text_repeat}[tool_call[\"name\"].lower()]\n            tool_output = selected_tool(**tool_call[\"args\"])\n            messages.append(ToolMessage(content=tool_output, tool_call_id=tool_call[\"id\"]))\n        print(f\"\\n第二轮  message中增加tool_output 之后：\\n{messages}\")\n\n        # todo: 第二次调用，将工具结果传回模型以生成最终回答\n        final_response = llm_with_tools.invoke(messages)\n        print(f\"\\n最终模型响应：\\n{final_response.content}\")\n    else:\n        print(\"模型未生成工具调用，直接返回文本:\")\n        print(ai_msg.content)\nexcept Exception as e:\n    print(f\"模型调用失败: {str(e)}\")\n",[53,2057,2058,2062,2066,2070,2074,2078,2082,2086,2090,2094,2098,2102,2106,2110,2114,2118,2122,2126,2130,2134,2138,2142,2146,2150,2154,2158,2163,2167,2171,2176,2181,2186,2191,2196,2201,2206,2211,2215,2220,2224,2228,2232,2237,2242,2247,2253,2259,2264,2269,2274,2280,2286,2291,2297,2302,2307,2312,2317,2322,2327,2332,2337,2342,2347,2352,2357,2362,2367,2372,2377,2382,2387,2392,2397,2402,2407,2412,2417,2422,2427,2432,2437,2442,2447,2452,2457,2462,2467,2472,2477,2482,2487,2492,2497],{"__ignoreMap":51},[56,2059,2060],{"class":58,"line":59},[56,2061,964],{},[56,2063,2064],{"class":58,"line":65},[56,2065,969],{},[56,2067,2068],{"class":58,"line":71},[56,2069,105],{"emptyLinePlaceholder":104},[56,2071,2072],{"class":58,"line":77},[56,2073,978],{},[56,2075,2076],{"class":58,"line":83},[56,2077,105],{"emptyLinePlaceholder":104},[56,2079,2080],{"class":58,"line":89},[56,2081,987],{},[56,2083,2084],{"class":58,"line":95},[56,2085,105],{"emptyLinePlaceholder":104},[56,2087,2088],{"class":58,"line":101},[56,2089,1001],{},[56,2091,2092],{"class":58,"line":108},[56,2093,1006],{},[56,2095,2096],{"class":58,"line":114},[56,2097,1011],{},[56,2099,2100],{"class":58,"line":120},[56,2101,1016],{},[56,2103,2104],{"class":58,"line":126},[56,2105,1021],{},[56,2107,2108],{"class":58,"line":132},[56,2109,1026],{},[56,2111,2112],{"class":58,"line":138},[56,2113,1011],{},[56,2115,2116],{"class":58,"line":144},[56,2117,1035],{},[56,2119,2120],{"class":58,"line":329},[56,2121,105],{"emptyLinePlaceholder":104},[56,2123,2124],{"class":58,"line":334},[56,2125,1044],{},[56,2127,2128],{"class":58,"line":340},[56,2129,1011],{},[56,2131,2132],{"class":58,"line":346},[56,2133,1053],{},[56,2135,2136],{"class":58,"line":352},[56,2137,1021],{},[56,2139,2140],{"class":58,"line":358},[56,2141,1062],{},[56,2143,2144],{"class":58,"line":571},[56,2145,1067],{},[56,2147,2148],{"class":58,"line":1197},[56,2149,1011],{},[56,2151,2152],{"class":58,"line":1203},[56,2153,1076],{},[56,2155,2156],{"class":58,"line":1209},[56,2157,105],{"emptyLinePlaceholder":104},[56,2159,2160],{"class":58,"line":1215},[56,2161,2162],{},"# 定义 JSON 格式的工具 schema\n",[56,2164,2165],{"class":58,"line":1588},[56,2166,1380],{},[56,2168,2169],{"class":58,"line":1595},[56,2170,1385],{},[56,2172,2173],{"class":58,"line":1602},[56,2174,2175],{},"        \"type\": \"function\",\n",[56,2177,2178],{"class":58,"line":1613},[56,2179,2180],{},"        \"function\": {\n",[56,2182,2183],{"class":58,"line":1623},[56,2184,2185],{},"            \"name\": \"text_length\",\n",[56,2187,2188],{"class":58,"line":1629},[56,2189,2190],{},"            \"description\": \"统计一段文本的字符个数\",\n",[56,2192,2193],{"class":58,"line":1637},[56,2194,2195],{},"            \"parameters\": {\n",[56,2197,2198],{"class":58,"line":1649},[56,2199,2200],{},"                \"type\": \"object\",\n",[56,2202,2203],{"class":58,"line":1659},[56,2204,2205],{},"                \"properties\": {\n",[56,2207,2208],{"class":58,"line":1664},[56,2209,2210],{},"                    \"text\": {\"type\": \"string\", \"description\": \"待统计的文本\"}\n",[56,2212,2213],{"class":58,"line":1669},[56,2214,1498],{},[56,2216,2217],{"class":58,"line":1686},[56,2218,2219],{},"                \"required\": [\"text\"]\n",[56,2221,2222],{"class":58,"line":1691},[56,2223,1517],{},[56,2225,2226],{"class":58,"line":1696},[56,2227,1522],{},[56,2229,2230],{"class":58,"line":1702},[56,2231,1527],{},[56,2233,2235],{"class":58,"line":2234},42,[56,2236,1385],{},[56,2238,2240],{"class":58,"line":2239},43,[56,2241,2175],{},[56,2243,2245],{"class":58,"line":2244},44,[56,2246,2180],{},[56,2248,2250],{"class":58,"line":2249},45,[56,2251,2252],{},"            \"name\": \"text_repeat\",\n",[56,2254,2256],{"class":58,"line":2255},46,[56,2257,2258],{},"            \"description\": \"把文本重复拼接指定次数\",\n",[56,2260,2262],{"class":58,"line":2261},47,[56,2263,2195],{},[56,2265,2267],{"class":58,"line":2266},48,[56,2268,2200],{},[56,2270,2272],{"class":58,"line":2271},49,[56,2273,2205],{},[56,2275,2277],{"class":58,"line":2276},50,[56,2278,2279],{},"                    \"text\": {\"type\": \"string\", \"description\": \"待重复的文本\"},\n",[56,2281,2283],{"class":58,"line":2282},51,[56,2284,2285],{},"                    \"times\": {\"type\": \"integer\", \"description\": \"重复次数\"}\n",[56,2287,2289],{"class":58,"line":2288},52,[56,2290,1498],{},[56,2292,2294],{"class":58,"line":2293},53,[56,2295,2296],{},"                \"required\": [\"text\", \"times\"]\n",[56,2298,2300],{"class":58,"line":2299},54,[56,2301,1517],{},[56,2303,2305],{"class":58,"line":2304},55,[56,2306,1522],{},[56,2308,2310],{"class":58,"line":2309},56,[56,2311,1699],{},[56,2313,2315],{"class":58,"line":2314},57,[56,2316,1512],{},[56,2318,2320],{"class":58,"line":2319},58,[56,2321,105],{"emptyLinePlaceholder":104},[56,2323,2325],{"class":58,"line":2324},59,[56,2326,1769],{},[56,2328,2330],{"class":58,"line":2329},60,[56,2331,1774],{},[56,2333,2335],{"class":58,"line":2334},61,[56,2336,1779],{},[56,2338,2340],{"class":58,"line":2339},62,[56,2341,1784],{},[56,2343,2345],{"class":58,"line":2344},63,[56,2346,1789],{},[56,2348,2350],{"class":58,"line":2349},64,[56,2351,1794],{},[56,2353,2355],{"class":58,"line":2354},65,[56,2356,1799],{},[56,2358,2360],{"class":58,"line":2359},66,[56,2361,105],{"emptyLinePlaceholder":104},[56,2363,2365],{"class":58,"line":2364},67,[56,2366,1916],{},[56,2368,2370],{"class":58,"line":2369},68,[56,2371,1921],{},[56,2373,2375],{"class":58,"line":2374},69,[56,2376,1926],{},[56,2378,2380],{"class":58,"line":2379},70,[56,2381,105],{"emptyLinePlaceholder":104},[56,2383,2385],{"class":58,"line":2384},71,[56,2386,1935],{},[56,2388,2390],{"class":58,"line":2389},72,[56,2391,1940],{},[56,2393,2395],{"class":58,"line":2394},73,[56,2396,1945],{},[56,2398,2400],{"class":58,"line":2399},74,[56,2401,1950],{},[56,2403,2405],{"class":58,"line":2404},75,[56,2406,1955],{},[56,2408,2410],{"class":58,"line":2409},76,[56,2411,105],{"emptyLinePlaceholder":104},[56,2413,2415],{"class":58,"line":2414},77,[56,2416,1964],{},[56,2418,2420],{"class":58,"line":2419},78,[56,2421,1969],{},[56,2423,2425],{"class":58,"line":2424},79,[56,2426,1974],{},[56,2428,2430],{"class":58,"line":2429},80,[56,2431,1979],{},[56,2433,2435],{"class":58,"line":2434},81,[56,2436,1984],{},[56,2438,2440],{"class":58,"line":2439},82,[56,2441,1989],{},[56,2443,2445],{"class":58,"line":2444},83,[56,2446,1994],{},[56,2448,2450],{"class":58,"line":2449},84,[56,2451,1999],{},[56,2453,2455],{"class":58,"line":2454},85,[56,2456,2004],{},[56,2458,2460],{"class":58,"line":2459},86,[56,2461,105],{"emptyLinePlaceholder":104},[56,2463,2465],{"class":58,"line":2464},87,[56,2466,2013],{},[56,2468,2470],{"class":58,"line":2469},88,[56,2471,2018],{},[56,2473,2475],{"class":58,"line":2474},89,[56,2476,2023],{},[56,2478,2480],{"class":58,"line":2479},90,[56,2481,2028],{},[56,2483,2485],{"class":58,"line":2484},91,[56,2486,2033],{},[56,2488,2490],{"class":58,"line":2489},92,[56,2491,2038],{},[56,2493,2495],{"class":58,"line":2494},93,[56,2496,2043],{},[56,2498,2500],{"class":58,"line":2499},94,[56,2501,2048],{},[13,2503,2504,2505,2508],{},"注意 ",[53,2506,2507],{},"bind_tools"," 与临时传参的区别：",[46,2510,2515],{"className":2511,"code":2513,"language":2514,"meta":51},[2512],"language-text","llm.invoke(messages, tools=tools, ...):\n绑定方式: 直接在 .invoke() 调用中传入 tools 参数。这是一种临时、一次性的绑定方式，仅对本次调用有效。\n调用方式: 如果你想再次调用模型并使用工具，必须在下一次 .invoke() 调用中再次传递 tools 参数。\n适用场景: 适用于简单、单次的工具调用需求。\n","text",[53,2516,2513],{"__ignoreMap":51},[22,2518,2520],{"id":2519},"_32-装饰器-tool-方式","3.2 装饰器 tool 方式",[13,2522,2523,2526,2527,2530,2531,2533,2534,2536],{},[35,2524,2525],{},"定义方式","：通过 ",[53,2528,2529],{},"@tool"," 装饰器直接装饰一个普通的 Python 函数，比如 ",[53,2532,927],{}," 和 ",[53,2535,931],{},"。",[13,2538,2539,2542,2543,2545,2546,2549],{},[35,2540,2541],{},"工作原理","：",[53,2544,2529],{}," 装饰器会自动根据函数签名（如 ",[53,2547,2548],{},"text: str, times: int","）和文档字符串生成一个完整的工具定义（schema），包括工具名称、描述和参数结构。",[13,2551,2552,2542],{},[35,2553,2554],{},"优势",[449,2556,2557,2563],{},[452,2558,2559,2562],{},[35,2560,2561],{},"简洁高效","：这是最简单、最 Pythonic 的方式，几乎不需要额外的样板代码。你只需编写核心函数逻辑，工具定义部分由框架自动处理。",[452,2564,2565,2568],{},[35,2566,2567],{},"自动化","：LangChain 的工具系统会自动处理工具的封装和调用，包括基本的参数类型验证。",[10,2570,2571],{},[13,2572,2573,2574,2577,2578,2580],{},"类比：给函数\"穿上工具外套\"。普通函数就像\"便装\"，只能自己调用 ",[53,2575,2576],{},"text_length(\"你好\")","；",[53,2579,2529],{}," 装饰器就像\"工具外套\"，穿上后函数变成了标准工具，LLM 可以识别和调用；外套自动适配尺寸（生成 Schema），你只需关注函数本身的业务逻辑。优势是代码简洁、自动化程度高、适合快速开发和简单工具。",[13,2582,2583],{},"代码如下：",[10,2585,2586],{},[13,2587,949,2588],{},[53,2589,2590],{},"agent_learn\u002Ffunction_call\u002FC02_by_annotation.py",[46,2592,2594],{"className":955,"code":2593,"language":957,"meta":51,"style":51},"from langchain_openai import ChatOpenAI\nfrom langchain_core.messages import HumanMessage, ToolMessage\nfrom langchain_core.tools import tool\n\nfrom agent_learn.config import Config\n\nconf = Config()\n\n# todo: 第一步：定义工具函数\n@tool\ndef text_length(text: str) -> int:\n    \"\"\"\n    统计一段文本的字符个数\n    Args:\n        text: 待统计的文本\n    \"\"\"\n    return len(text)\n\n@tool\ndef text_repeat(text: str, times: int) -> str:\n    \"\"\"\n    把文本重复拼接指定次数\n    Args:\n        text: 待重复的文本\n        times: 重复次数\n    \"\"\"\n    return text * times\n\n# 定义 JSON 格式的工具 schema\ntools = [text_length, text_repeat]\n\n# todo: 第二步：初始化模型\nllm = ChatOpenAI(base_url=conf.base_url,\n                 api_key=conf.api_key,\n                 model=conf.model_name,\n                 temperature=0.1)\n# 绑定工具，允许模型自动选择工具\nllm_with_tools = llm.bind_tools(tools, tool_choice=\"auto\")\n\n# todo: 第三步：调用回复\nquery = \"「你好，Agent」这句话有多少个字符？\"\nmessages = [HumanMessage(query)]\n\ntry:\n    # todo: 第一次调用\n    ai_msg = llm_with_tools.invoke(messages)\n    messages.append(ai_msg)\n    print(f\"\\n第一轮调用后结果：\\n{messages}\")\n\n    # 处理工具调用\n    # 判断消息中是否有tool_calls，以判断工具是否被调用\n    if hasattr(ai_msg, 'tool_calls') and ai_msg.tool_calls:\n        for tool_call in ai_msg.tool_calls:\n            # todo: 处理工具调用\n            selected_tool = {\"text_length\": text_length, \"text_repeat\": text_repeat}[tool_call[\"name\"].lower()]\n            tool_output = selected_tool.invoke(tool_call[\"args\"])  # 需要使用invoke进行调用\n            messages.append(ToolMessage(content=tool_output, tool_call_id=tool_call[\"id\"]))\n        print(f\"\\n第二轮  message中增加tool_output 之后：\\n{messages}\")\n\n        # todo: 第二次调用，将工具结果传回模型以生成最终回答\n        final_response = llm_with_tools.invoke(messages)\n        print(f\"\\n最终模型响应：\\n{final_response.content}\")\n    else:\n        print(\"模型未生成工具调用，直接返回文本:\")\n        print(ai_msg.content)\nexcept Exception as e:\n    print(f\"模型调用失败: {str(e)}\")\n",[53,2595,2596,2600,2604,2609,2613,2617,2621,2625,2629,2633,2638,2642,2646,2650,2654,2658,2662,2666,2670,2674,2678,2682,2686,2690,2694,2698,2702,2706,2710,2714,2719,2723,2727,2731,2735,2739,2743,2747,2751,2755,2759,2763,2767,2771,2775,2779,2783,2787,2791,2795,2799,2803,2807,2811,2815,2819,2824,2828,2832,2836,2840,2844,2848,2852,2856,2860,2864],{"__ignoreMap":51},[56,2597,2598],{"class":58,"line":59},[56,2599,964],{},[56,2601,2602],{"class":58,"line":65},[56,2603,969],{},[56,2605,2606],{"class":58,"line":71},[56,2607,2608],{},"from langchain_core.tools import tool\n",[56,2610,2611],{"class":58,"line":77},[56,2612,105],{"emptyLinePlaceholder":104},[56,2614,2615],{"class":58,"line":83},[56,2616,978],{},[56,2618,2619],{"class":58,"line":89},[56,2620,105],{"emptyLinePlaceholder":104},[56,2622,2623],{"class":58,"line":95},[56,2624,987],{},[56,2626,2627],{"class":58,"line":101},[56,2628,105],{"emptyLinePlaceholder":104},[56,2630,2631],{"class":58,"line":108},[56,2632,1001],{},[56,2634,2635],{"class":58,"line":114},[56,2636,2637],{},"@tool\n",[56,2639,2640],{"class":58,"line":120},[56,2641,1006],{},[56,2643,2644],{"class":58,"line":126},[56,2645,1011],{},[56,2647,2648],{"class":58,"line":132},[56,2649,1016],{},[56,2651,2652],{"class":58,"line":138},[56,2653,1021],{},[56,2655,2656],{"class":58,"line":144},[56,2657,1026],{},[56,2659,2660],{"class":58,"line":329},[56,2661,1011],{},[56,2663,2664],{"class":58,"line":334},[56,2665,1035],{},[56,2667,2668],{"class":58,"line":340},[56,2669,105],{"emptyLinePlaceholder":104},[56,2671,2672],{"class":58,"line":346},[56,2673,2637],{},[56,2675,2676],{"class":58,"line":352},[56,2677,1044],{},[56,2679,2680],{"class":58,"line":358},[56,2681,1011],{},[56,2683,2684],{"class":58,"line":571},[56,2685,1053],{},[56,2687,2688],{"class":58,"line":1197},[56,2689,1021],{},[56,2691,2692],{"class":58,"line":1203},[56,2693,1062],{},[56,2695,2696],{"class":58,"line":1209},[56,2697,1067],{},[56,2699,2700],{"class":58,"line":1215},[56,2701,1011],{},[56,2703,2704],{"class":58,"line":1588},[56,2705,1076],{},[56,2707,2708],{"class":58,"line":1595},[56,2709,105],{"emptyLinePlaceholder":104},[56,2711,2712],{"class":58,"line":1602},[56,2713,2162],{},[56,2715,2716],{"class":58,"line":1613},[56,2717,2718],{},"tools = [text_length, text_repeat]\n",[56,2720,2721],{"class":58,"line":1623},[56,2722,105],{"emptyLinePlaceholder":104},[56,2724,2725],{"class":58,"line":1629},[56,2726,1769],{},[56,2728,2729],{"class":58,"line":1637},[56,2730,1774],{},[56,2732,2733],{"class":58,"line":1649},[56,2734,1779],{},[56,2736,2737],{"class":58,"line":1659},[56,2738,1784],{},[56,2740,2741],{"class":58,"line":1664},[56,2742,1789],{},[56,2744,2745],{"class":58,"line":1669},[56,2746,1794],{},[56,2748,2749],{"class":58,"line":1686},[56,2750,1799],{},[56,2752,2753],{"class":58,"line":1691},[56,2754,105],{"emptyLinePlaceholder":104},[56,2756,2757],{"class":58,"line":1696},[56,2758,1916],{},[56,2760,2761],{"class":58,"line":1702},[56,2762,1921],{},[56,2764,2765],{"class":58,"line":2234},[56,2766,1926],{},[56,2768,2769],{"class":58,"line":2239},[56,2770,105],{"emptyLinePlaceholder":104},[56,2772,2773],{"class":58,"line":2244},[56,2774,1935],{},[56,2776,2777],{"class":58,"line":2249},[56,2778,1940],{},[56,2780,2781],{"class":58,"line":2255},[56,2782,1945],{},[56,2784,2785],{"class":58,"line":2261},[56,2786,1950],{},[56,2788,2789],{"class":58,"line":2266},[56,2790,1955],{},[56,2792,2793],{"class":58,"line":2271},[56,2794,105],{"emptyLinePlaceholder":104},[56,2796,2797],{"class":58,"line":2276},[56,2798,1964],{},[56,2800,2801],{"class":58,"line":2282},[56,2802,1969],{},[56,2804,2805],{"class":58,"line":2288},[56,2806,1974],{},[56,2808,2809],{"class":58,"line":2293},[56,2810,1979],{},[56,2812,2813],{"class":58,"line":2299},[56,2814,1984],{},[56,2816,2817],{"class":58,"line":2304},[56,2818,1989],{},[56,2820,2821],{"class":58,"line":2309},[56,2822,2823],{},"            tool_output = selected_tool.invoke(tool_call[\"args\"])  # 需要使用invoke进行调用\n",[56,2825,2826],{"class":58,"line":2314},[56,2827,1999],{},[56,2829,2830],{"class":58,"line":2319},[56,2831,2004],{},[56,2833,2834],{"class":58,"line":2324},[56,2835,105],{"emptyLinePlaceholder":104},[56,2837,2838],{"class":58,"line":2329},[56,2839,2013],{},[56,2841,2842],{"class":58,"line":2334},[56,2843,2018],{},[56,2845,2846],{"class":58,"line":2339},[56,2847,2023],{},[56,2849,2850],{"class":58,"line":2344},[56,2851,2028],{},[56,2853,2854],{"class":58,"line":2349},[56,2855,2033],{},[56,2857,2858],{"class":58,"line":2354},[56,2859,2038],{},[56,2861,2862],{"class":58,"line":2359},[56,2863,2043],{},[56,2865,2866],{"class":58,"line":2364},[56,2867,2048],{},[10,2869,2870],{},[13,2871,2872,2873,2875,2876,2879,2880,2883,2884,2536],{},"与 JSON Schema 方式的两点差异：",[53,2874,1267],{}," 里直接放",[35,2877,2878],{},"函数对象","而不是字典；执行时要用 ",[53,2881,2882],{},"selected_tool.invoke(...)","，而不是 ",[53,2885,2886],{},"selected_tool(...)",[22,2888,2890],{"id":2889},"_33-pydantic-的-tool-方式","3.3 Pydantic 的 tool 方式",[13,2892,2893],{},"通过严格数据校验 Pydantic 进行工具定义：",[13,2895,2896,2898,2899,2902,2903,2906,2907,2910],{},[35,2897,2525],{},"：创建一个继承自 ",[53,2900,2901],{},"BaseModel"," 的类，用类型注解和 ",[53,2904,2905],{},"Field"," 定义工具参数，并在类中手动实现 ",[53,2908,2909],{},"invoke"," 方法包含执行逻辑。",[13,2912,2913,2542,2919,2921,2922,2924],{},[35,2914,2915,2916,2918],{},"与 ",[53,2917,2529],{}," 的区别",[53,2920,2529],{}," 自动生成 Schema 和调用逻辑；Pydantic 方式则需手动实现 ",[53,2923,2909],{},"，但换来更强的数据验证（自动校验参数类型与约束）和更清晰的结构。",[10,2926,2927],{},[13,2928,2929,2932,2933,2935],{},[35,2930,2931],{},"说明","：Pydantic 方式在实际项目中用得相对较少，多数场景用 ",[53,2934,2529],{}," 更省事。这里介绍它，是为了让你了解三种定义方式的差异（见下方对比表）。",[46,2937,2939],{"className":48,"code":2938,"language":50,"meta":51,"style":51},"flowchart LR\n    A[\"① 定义 BaseModel 子类\u003Cbr\u002F>class TextRepeat(BaseModel):\u003Cbr\u002F>text: str = Field(…)\u003Cbr\u002F>times: int = Field(…)\"]\n    B[\"② 实现 invoke 方法\u003Cbr\u002F>def invoke(self, args):\u003Cbr\u002F>tool_instance = self.__class__(**args)\u003Cbr\u002F>return tool_instance.text * tool_instance.times\"]\n    C[\"③ 实例化并调用\u003Cbr\u002F>tool_instance = TextRepeat(**args)\u003Cbr\u002F>自动验证 text、times 类型\u003Cbr\u002F>output = tool_instance.invoke(args)\"]\n    V[\"🔒 数据验证：传入 args 时自动检查类型和约束，不符合则报错\"]\n\n    A -->|定义参数| B\n    B -->|手动实现| C\n    V -.-> C\n\n    style A fill:#F8FAFC,stroke:#64748B,stroke-width:2px\n    style B fill:#F5F3FF,stroke:#7C3AED,stroke-width:2px\n    style C fill:#ECFDF5,stroke:#059669,stroke-width:2px\n    style V fill:#FFFBEB,stroke:#D97706,stroke-width:2px\n",[53,2940,2941,2945,2950,2955,2960,2965,2969,2974,2979,2984,2988,2992,2997,3002],{"__ignoreMap":51},[56,2942,2943],{"class":58,"line":59},[56,2944,62],{},[56,2946,2947],{"class":58,"line":65},[56,2948,2949],{},"    A[\"① 定义 BaseModel 子类\u003Cbr\u002F>class TextRepeat(BaseModel):\u003Cbr\u002F>text: str = Field(…)\u003Cbr\u002F>times: int = Field(…)\"]\n",[56,2951,2952],{"class":58,"line":71},[56,2953,2954],{},"    B[\"② 实现 invoke 方法\u003Cbr\u002F>def invoke(self, args):\u003Cbr\u002F>tool_instance = self.__class__(**args)\u003Cbr\u002F>return tool_instance.text * tool_instance.times\"]\n",[56,2956,2957],{"class":58,"line":77},[56,2958,2959],{},"    C[\"③ 实例化并调用\u003Cbr\u002F>tool_instance = TextRepeat(**args)\u003Cbr\u002F>自动验证 text、times 类型\u003Cbr\u002F>output = tool_instance.invoke(args)\"]\n",[56,2961,2962],{"class":58,"line":83},[56,2963,2964],{},"    V[\"🔒 数据验证：传入 args 时自动检查类型和约束，不符合则报错\"]\n",[56,2966,2967],{"class":58,"line":89},[56,2968,105],{"emptyLinePlaceholder":104},[56,2970,2971],{"class":58,"line":95},[56,2972,2973],{},"    A -->|定义参数| B\n",[56,2975,2976],{"class":58,"line":101},[56,2977,2978],{},"    B -->|手动实现| C\n",[56,2980,2981],{"class":58,"line":108},[56,2982,2983],{},"    V -.-> C\n",[56,2985,2986],{"class":58,"line":114},[56,2987,105],{"emptyLinePlaceholder":104},[56,2989,2990],{"class":58,"line":120},[56,2991,111],{},[56,2993,2994],{"class":58,"line":126},[56,2995,2996],{},"    style B fill:#F5F3FF,stroke:#7C3AED,stroke-width:2px\n",[56,2998,2999],{"class":58,"line":132},[56,3000,3001],{},"    style C fill:#ECFDF5,stroke:#059669,stroke-width:2px\n",[56,3003,3004],{"class":58,"line":138},[56,3005,3006],{},"    style V fill:#FFFBEB,stroke:#D97706,stroke-width:2px\n",[10,3008,3009],{},[13,3010,3011,3013,3014,3016,3017,3020,3021,3024],{},[35,3012,154],{}," 三步里框架只帮你做了第一步的\"参数声明\"，第二步的 ",[53,3015,2909],{}," 要自己写。代价是多写几行代码，收益是每次实例化 ",[53,3018,3019],{},"TextRepeat(**args)"," 时 Pydantic 都会强制校验类型——传 ",[53,3022,3023],{},"times=\"abc\""," 会立刻报错，而不是等到运行时才算错。",[10,3026,3027],{},[13,3028,3029,3030,3032],{},"类比：Pydantic 就像\"带质检的生产线\"，每个参数都要经过严格检查；如果传入 ",[53,3031,3023],{},"（应该是 int），Pydantic 会立即报错，而不是等到运行时才发现问题；就像工厂质检员，不合格的产品（参数）直接拦截，保证进入生产线的都是合格品。适用场景是需要复杂数据验证、清晰结构和自定义逻辑的场景。",[13,3034,2583],{},[10,3036,3037],{},[13,3038,949,3039],{},[53,3040,3041],{},"agent_learn\u002Ffunction_call\u002FC03_by_pydantic.py",[46,3043,3045],{"className":955,"code":3044,"language":957,"meta":51,"style":51},"from langchain_openai import ChatOpenAI\nfrom langchain_core.messages import HumanMessage, ToolMessage\nfrom pydantic.v1 import BaseModel, Field\n\nfrom agent_learn.config import Config\n\nconf = Config()\n\n# todo: 第一步：定义工具类\nclass TextLength(BaseModel):\n    \"\"\"统计一段文本的字符个数\"\"\"\n    text: str = Field(..., description=\"待统计的文本\")\n\n    def invoke(self, args):\n        # 验证参数\n        tool_instance = self.__class__(**args)  # 自动验证 text\n        return len(tool_instance.text)\n\nclass TextRepeat(BaseModel):\n    \"\"\"把文本重复拼接指定次数\"\"\"\n    text: str = Field(..., description=\"待重复的文本\")\n    times: int = Field(..., description=\"重复次数\")\n\n    def invoke(self, args):\n        # 验证参数\n        tool_instance = self.__class__(**args)  # 自动验证 text 和 times\n        return tool_instance.text * tool_instance.times\n\n# 使用类名作为工具\ntools = [TextLength, TextRepeat]\n\n# todo: 第二步：初始化模型\nllm = ChatOpenAI(base_url=conf.base_url,\n                 api_key=conf.api_key,\n                 model=conf.model_name,\n                 temperature=0.1)\n# 绑定工具，允许模型自动选择工具\nllm_with_tools = llm.bind_tools(tools, tool_choice=\"auto\")\n\n# todo: 第三步：调用回复\nquery = \"「你好，Agent」这句话有多少个字符？\"\nmessages = [HumanMessage(query)]\n\ntry:\n    # todo: 第一次调用\n    ai_msg = llm_with_tools.invoke(messages)\n    messages.append(ai_msg)\n    print(f\"\\n第一轮调用后结果：\\n{messages}\")\n\n    # 处理工具调用\n    # 判断消息中是否有tool_calls，以判断工具是否被调用\n    if hasattr(ai_msg, 'tool_calls') and ai_msg.tool_calls:\n        for tool_call in ai_msg.tool_calls:\n            # todo: 处理工具调用\n            selected_tool = {\"text_length\": TextLength, \"text_repeat\": TextRepeat}[tool_call[\"name\"].lower()]\n            # 实例化工具类并调用 invoke\n            tool_instance = selected_tool(**tool_call[\"args\"])\n            tool_output = tool_instance.invoke(tool_call[\"args\"])\n            messages.append(ToolMessage(content=tool_output, tool_call_id=tool_call[\"id\"]))\n        print(f\"\\n第二轮  message中增加tool_output 之后：\\n{messages}\")\n\n        # todo: 第二次调用，将工具结果传回模型以生成最终回答\n        final_response = llm_with_tools.invoke(messages)\n        print(f\"\\n最终模型响应：\\n{final_response.content}\")\n    else:\n        print(\"模型未生成工具调用，直接返回文本:\")\n        print(ai_msg.content)\nexcept Exception as e:\n    print(f\"模型调用失败: {str(e)}\")\n",[53,3046,3047,3051,3055,3060,3064,3068,3072,3076,3080,3085,3090,3095,3100,3104,3109,3114,3119,3124,3128,3133,3138,3143,3148,3152,3156,3160,3165,3170,3174,3179,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3280,3285,3290,3295,3300,3304,3308,3312,3316,3320,3324,3328,3332,3336,3340],{"__ignoreMap":51},[56,3048,3049],{"class":58,"line":59},[56,3050,964],{},[56,3052,3053],{"class":58,"line":65},[56,3054,969],{},[56,3056,3057],{"class":58,"line":71},[56,3058,3059],{},"from pydantic.v1 import BaseModel, Field\n",[56,3061,3062],{"class":58,"line":77},[56,3063,105],{"emptyLinePlaceholder":104},[56,3065,3066],{"class":58,"line":83},[56,3067,978],{},[56,3069,3070],{"class":58,"line":89},[56,3071,105],{"emptyLinePlaceholder":104},[56,3073,3074],{"class":58,"line":95},[56,3075,987],{},[56,3077,3078],{"class":58,"line":101},[56,3079,105],{"emptyLinePlaceholder":104},[56,3081,3082],{"class":58,"line":108},[56,3083,3084],{},"# todo: 第一步：定义工具类\n",[56,3086,3087],{"class":58,"line":114},[56,3088,3089],{},"class TextLength(BaseModel):\n",[56,3091,3092],{"class":58,"line":120},[56,3093,3094],{},"    \"\"\"统计一段文本的字符个数\"\"\"\n",[56,3096,3097],{"class":58,"line":126},[56,3098,3099],{},"    text: str = Field(..., description=\"待统计的文本\")\n",[56,3101,3102],{"class":58,"line":132},[56,3103,105],{"emptyLinePlaceholder":104},[56,3105,3106],{"class":58,"line":138},[56,3107,3108],{},"    def invoke(self, args):\n",[56,3110,3111],{"class":58,"line":144},[56,3112,3113],{},"        # 验证参数\n",[56,3115,3116],{"class":58,"line":329},[56,3117,3118],{},"        tool_instance = self.__class__(**args)  # 自动验证 text\n",[56,3120,3121],{"class":58,"line":334},[56,3122,3123],{},"        return len(tool_instance.text)\n",[56,3125,3126],{"class":58,"line":340},[56,3127,105],{"emptyLinePlaceholder":104},[56,3129,3130],{"class":58,"line":346},[56,3131,3132],{},"class TextRepeat(BaseModel):\n",[56,3134,3135],{"class":58,"line":352},[56,3136,3137],{},"    \"\"\"把文本重复拼接指定次数\"\"\"\n",[56,3139,3140],{"class":58,"line":358},[56,3141,3142],{},"    text: str = Field(..., description=\"待重复的文本\")\n",[56,3144,3145],{"class":58,"line":571},[56,3146,3147],{},"    times: int = Field(..., description=\"重复次数\")\n",[56,3149,3150],{"class":58,"line":1197},[56,3151,105],{"emptyLinePlaceholder":104},[56,3153,3154],{"class":58,"line":1203},[56,3155,3108],{},[56,3157,3158],{"class":58,"line":1209},[56,3159,3113],{},[56,3161,3162],{"class":58,"line":1215},[56,3163,3164],{},"        tool_instance = self.__class__(**args)  # 自动验证 text 和 times\n",[56,3166,3167],{"class":58,"line":1588},[56,3168,3169],{},"        return tool_instance.text * tool_instance.times\n",[56,3171,3172],{"class":58,"line":1595},[56,3173,105],{"emptyLinePlaceholder":104},[56,3175,3176],{"class":58,"line":1602},[56,3177,3178],{},"# 使用类名作为工具\n",[56,3180,3181],{"class":58,"line":1613},[56,3182,3183],{},"tools = [TextLength, TextRepeat]\n",[56,3185,3186],{"class":58,"line":1623},[56,3187,105],{"emptyLinePlaceholder":104},[56,3189,3190],{"class":58,"line":1629},[56,3191,1769],{},[56,3193,3194],{"class":58,"line":1637},[56,3195,1774],{},[56,3197,3198],{"class":58,"line":1649},[56,3199,1779],{},[56,3201,3202],{"class":58,"line":1659},[56,3203,1784],{},[56,3205,3206],{"class":58,"line":1664},[56,3207,1789],{},[56,3209,3210],{"class":58,"line":1669},[56,3211,1794],{},[56,3213,3214],{"class":58,"line":1686},[56,3215,1799],{},[56,3217,3218],{"class":58,"line":1691},[56,3219,105],{"emptyLinePlaceholder":104},[56,3221,3222],{"class":58,"line":1696},[56,3223,1916],{},[56,3225,3226],{"class":58,"line":1702},[56,3227,1921],{},[56,3229,3230],{"class":58,"line":2234},[56,3231,1926],{},[56,3233,3234],{"class":58,"line":2239},[56,3235,105],{"emptyLinePlaceholder":104},[56,3237,3238],{"class":58,"line":2244},[56,3239,1935],{},[56,3241,3242],{"class":58,"line":2249},[56,3243,1940],{},[56,3245,3246],{"class":58,"line":2255},[56,3247,1945],{},[56,3249,3250],{"class":58,"line":2261},[56,3251,1950],{},[56,3253,3254],{"class":58,"line":2266},[56,3255,1955],{},[56,3257,3258],{"class":58,"line":2271},[56,3259,105],{"emptyLinePlaceholder":104},[56,3261,3262],{"class":58,"line":2276},[56,3263,1964],{},[56,3265,3266],{"class":58,"line":2282},[56,3267,1969],{},[56,3269,3270],{"class":58,"line":2288},[56,3271,1974],{},[56,3273,3274],{"class":58,"line":2293},[56,3275,1979],{},[56,3277,3278],{"class":58,"line":2299},[56,3279,1984],{},[56,3281,3282],{"class":58,"line":2304},[56,3283,3284],{},"            selected_tool = {\"text_length\": TextLength, \"text_repeat\": TextRepeat}[tool_call[\"name\"].lower()]\n",[56,3286,3287],{"class":58,"line":2309},[56,3288,3289],{},"            # 实例化工具类并调用 invoke\n",[56,3291,3292],{"class":58,"line":2314},[56,3293,3294],{},"            tool_instance = selected_tool(**tool_call[\"args\"])\n",[56,3296,3297],{"class":58,"line":2319},[56,3298,3299],{},"            tool_output = tool_instance.invoke(tool_call[\"args\"])\n",[56,3301,3302],{"class":58,"line":2324},[56,3303,1999],{},[56,3305,3306],{"class":58,"line":2329},[56,3307,2004],{},[56,3309,3310],{"class":58,"line":2334},[56,3311,105],{"emptyLinePlaceholder":104},[56,3313,3314],{"class":58,"line":2339},[56,3315,2013],{},[56,3317,3318],{"class":58,"line":2344},[56,3319,2018],{},[56,3321,3322],{"class":58,"line":2349},[56,3323,2023],{},[56,3325,3326],{"class":58,"line":2354},[56,3327,2028],{},[56,3329,3330],{"class":58,"line":2359},[56,3331,2033],{},[56,3333,3334],{"class":58,"line":2364},[56,3335,2038],{},[56,3337,3338],{"class":58,"line":2369},[56,3339,2043],{},[56,3341,3342],{"class":58,"line":2374},[56,3343,2048],{},[22,3345,3347],{"id":3346},"_34-三种定义方式对比","3.4 三种定义方式对比",[46,3349,3351],{"className":48,"code":3350,"language":50,"meta":51,"style":51},"flowchart LR\n    subgraph W1[\"方式一：JSON Schema\"]\n        W1A[\"定义方式\u003Cbr\u002F>手动编写 Python 字典\"]\n        W1B[\"自动化程度\u003Cbr\u002F>低：完全手动定义，需手写 JSON Schema\"]\n        W1C[\"数据验证\u003Cbr\u002F>需手动验证或依赖外部库\"]\n        W1D[\"优势\u003Cbr\u002F>灵活性最高 \u002F 可与其他系统集成 \u002F 通用性强\"]\n        W1E[\"适用场景\u003Cbr\u002F>通用接口定义、需最大灵活性\"]\n        W1F[\"推荐度 ★★\"]\n    end\n\n    subgraph W2[\"方式二：@tool 装饰器\"]\n        W2A[\"定义方式\u003Cbr\u002F>@tool 装饰 Python 函数\"]\n        W2B[\"自动化程度\u003Cbr\u002F>高：自动生成 Schema 和调用逻辑\"]\n        W2C[\"数据验证\u003Cbr\u002F>基础类型检查\"]\n        W2D[\"优势\u003Cbr\u002F>代码简洁 Pythonic \u002F 自动生成 Schema \u002F 快速开发\"]\n        W2E[\"适用场景\u003Cbr\u002F>快速开发、简单工具、日常项目\"]\n        W2F[\"推荐度 ★★★★★\"]\n    end\n\n    subgraph W3[\"方式三：Pydantic\"]\n        W3A[\"定义方式\u003Cbr\u002F>class TextRepeat(BaseModel) + Field\"]\n        W3B[\"自动化程度\u003Cbr\u002F>中等：自动验证数据，但需手动实现 invoke\"]\n        W3C[\"数据验证\u003Cbr\u002F>强大：丰富的验证功能\"]\n        W3D[\"优势\u003Cbr\u002F>强类型验证 \u002F 结构清晰 \u002F 可自定义逻辑\"]\n        W3E[\"适用场景\u003Cbr\u002F>复杂数据验证、需清晰结构\"]\n        W3F[\"推荐度 ★★★\"]\n    end\n\n    style W1 fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n    style W2 fill:#ECFDF5,stroke:#059669,stroke-width:2px\n    style W3 fill:#F5F3FF,stroke:#7C3AED,stroke-width:2px\n",[53,3352,3353,3357,3362,3367,3372,3377,3382,3387,3392,3396,3400,3405,3410,3415,3420,3425,3430,3435,3439,3443,3448,3453,3458,3463,3468,3473,3478,3482,3486,3491,3496],{"__ignoreMap":51},[56,3354,3355],{"class":58,"line":59},[56,3356,62],{},[56,3358,3359],{"class":58,"line":65},[56,3360,3361],{},"    subgraph W1[\"方式一：JSON Schema\"]\n",[56,3363,3364],{"class":58,"line":71},[56,3365,3366],{},"        W1A[\"定义方式\u003Cbr\u002F>手动编写 Python 字典\"]\n",[56,3368,3369],{"class":58,"line":77},[56,3370,3371],{},"        W1B[\"自动化程度\u003Cbr\u002F>低：完全手动定义，需手写 JSON Schema\"]\n",[56,3373,3374],{"class":58,"line":83},[56,3375,3376],{},"        W1C[\"数据验证\u003Cbr\u002F>需手动验证或依赖外部库\"]\n",[56,3378,3379],{"class":58,"line":89},[56,3380,3381],{},"        W1D[\"优势\u003Cbr\u002F>灵活性最高 \u002F 可与其他系统集成 \u002F 通用性强\"]\n",[56,3383,3384],{"class":58,"line":95},[56,3385,3386],{},"        W1E[\"适用场景\u003Cbr\u002F>通用接口定义、需最大灵活性\"]\n",[56,3388,3389],{"class":58,"line":101},[56,3390,3391],{},"        W1F[\"推荐度 ★★\"]\n",[56,3393,3394],{"class":58,"line":108},[56,3395,496],{},[56,3397,3398],{"class":58,"line":114},[56,3399,105],{"emptyLinePlaceholder":104},[56,3401,3402],{"class":58,"line":120},[56,3403,3404],{},"    subgraph W2[\"方式二：@tool 装饰器\"]\n",[56,3406,3407],{"class":58,"line":126},[56,3408,3409],{},"        W2A[\"定义方式\u003Cbr\u002F>@tool 装饰 Python 函数\"]\n",[56,3411,3412],{"class":58,"line":132},[56,3413,3414],{},"        W2B[\"自动化程度\u003Cbr\u002F>高：自动生成 Schema 和调用逻辑\"]\n",[56,3416,3417],{"class":58,"line":138},[56,3418,3419],{},"        W2C[\"数据验证\u003Cbr\u002F>基础类型检查\"]\n",[56,3421,3422],{"class":58,"line":144},[56,3423,3424],{},"        W2D[\"优势\u003Cbr\u002F>代码简洁 Pythonic \u002F 自动生成 Schema \u002F 快速开发\"]\n",[56,3426,3427],{"class":58,"line":329},[56,3428,3429],{},"        W2E[\"适用场景\u003Cbr\u002F>快速开发、简单工具、日常项目\"]\n",[56,3431,3432],{"class":58,"line":334},[56,3433,3434],{},"        W2F[\"推荐度 ★★★★★\"]\n",[56,3436,3437],{"class":58,"line":340},[56,3438,496],{},[56,3440,3441],{"class":58,"line":346},[56,3442,105],{"emptyLinePlaceholder":104},[56,3444,3445],{"class":58,"line":352},[56,3446,3447],{},"    subgraph W3[\"方式三：Pydantic\"]\n",[56,3449,3450],{"class":58,"line":358},[56,3451,3452],{},"        W3A[\"定义方式\u003Cbr\u002F>class TextRepeat(BaseModel) + Field\"]\n",[56,3454,3455],{"class":58,"line":571},[56,3456,3457],{},"        W3B[\"自动化程度\u003Cbr\u002F>中等：自动验证数据，但需手动实现 invoke\"]\n",[56,3459,3460],{"class":58,"line":1197},[56,3461,3462],{},"        W3C[\"数据验证\u003Cbr\u002F>强大：丰富的验证功能\"]\n",[56,3464,3465],{"class":58,"line":1203},[56,3466,3467],{},"        W3D[\"优势\u003Cbr\u002F>强类型验证 \u002F 结构清晰 \u002F 可自定义逻辑\"]\n",[56,3469,3470],{"class":58,"line":1209},[56,3471,3472],{},"        W3E[\"适用场景\u003Cbr\u002F>复杂数据验证、需清晰结构\"]\n",[56,3474,3475],{"class":58,"line":1215},[56,3476,3477],{},"        W3F[\"推荐度 ★★★\"]\n",[56,3479,3480],{"class":58,"line":1588},[56,3481,496],{},[56,3483,3484],{"class":58,"line":1595},[56,3485,105],{"emptyLinePlaceholder":104},[56,3487,3488],{"class":58,"line":1602},[56,3489,3490],{},"    style W1 fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n",[56,3492,3493],{"class":58,"line":1613},[56,3494,3495],{},"    style W2 fill:#ECFDF5,stroke:#059669,stroke-width:2px\n",[56,3497,3498],{"class":58,"line":1623},[56,3499,3500],{},"    style W3 fill:#F5F3FF,stroke:#7C3AED,stroke-width:2px\n",[10,3502,3503],{},[13,3504,3505,3507,3508,3510,3511,3513,3514],{},[35,3506,154],{}," 三种方式的差异集中在\"自动化程度\"与\"数据验证强度\"两个维度上，并且是反向的——",[53,3509,2529],{}," 自动化最高但只做基础类型检查；Pydantic 要手写 ",[53,3512,2909],{},"，却换来最强的校验能力。",[35,3515,3516,3517,3519],{},"日常开发首选 ",[53,3518,2529],{},"，需要严格验证或复杂参数结构时再用 Pydantic，需要与其他系统做通用集成时才回到手写 JSON Schema。",[13,3521,3522],{},[35,3523,3524,3525,3527],{},"推荐：日常开发使用 ",[53,3526,2529],{}," 装饰器，需要严格验证时使用 Pydantic。",[168,3529,3530,3546],{},[171,3531,3532],{},[174,3533,3534,3537,3540,3543],{},[177,3535,3536],{},"特性",[177,3538,3539],{},"JSON Schema",[177,3541,3542],{},"@tool 装饰器",[177,3544,3545],{},"Pydantic",[190,3547,3548,3561,3575,3589],{},[174,3549,3550,3552,3555,3558],{},[195,3551,2525],{},[195,3553,3554],{},"手动编写 Python 字典（JSON Schema）",[195,3556,3557],{},"装饰 Python 函数",[195,3559,3560],{},"继承 Pydantic BaseModel",[174,3562,3563,3566,3569,3572],{},[195,3564,3565],{},"自动化程度",[195,3567,3568],{},"低：完全手动定义和分发",[195,3570,3571],{},"高：自动生成 Schema 和调用逻辑",[195,3573,3574],{},"中等：自动验证数据，但需手动实现 invoke",[174,3576,3577,3580,3583,3586],{},[195,3578,3579],{},"数据验证",[195,3581,3582],{},"需要手动验证或依赖外部库",[195,3584,3585],{},"基础类型检查",[195,3587,3588],{},"强大：提供丰富的验证功能",[174,3590,3591,3594,3597,3600],{},[195,3592,3593],{},"适用场景",[195,3595,3596],{},"需要与其他系统集成、通用性和最大灵活性的场景",[195,3598,3599],{},"快速开发、简单工具、原型验证",[195,3601,3602],{},"需要复杂数据验证、清晰结构和自定义逻辑的场景",[17,3604,3606],{"id":3605},"_4-agent-调用-tool","4. Agent 调用 tool",[13,3608,3609,3610,2536],{},"Agent（智能体）是一种能够感知环境、进行决策和执行动作的智能实体。从大模型的角度来看，",[35,3611,3612],{},"Agent 其实就是基于大模型的语义理解和推理能力，让大模型拥有解决复杂问题时的任务规划能力，并调用外部工具来执行各种任务，并且能够保留\"记忆\"的一个智能体",[10,3614,3615],{},[13,3616,3617],{},"Agent = 大模型 + 任务规划（Planning） + 使用外部工具执行任务（Tools & Action） + 记忆（Memory）",[13,3619,3620],{},"Agent 的核心就是大模型，它调用工具的方式通常通过 Function Call 实现，不过很多 Agent 框架对内部的调用过程进行了封装，所以更易使用。",[46,3622,3624],{"className":48,"code":3623,"language":50,"meta":51,"style":51},"flowchart LR\n    A[\"👤 用户输入\u003Cbr\u002F>「你好，Agent」有几个字符？\"]\n    B[\"🤖 Agent（智能体）\u003Cbr\u002F>分析意图 → 选择工具 → 执行 → 返回\u003Cbr\u002F>封装了 Function Call 的完整流程\"]\n    C[\"🧰 工具列表\u003Cbr\u002F>text_length、text_repeat\u003Cbr\u002F>自动选择 text_length\"]\n    D[\"📤 返回结果\u003Cbr\u002F>result: 8\"]\n    E[\"📥 最终输出\u003Cbr\u002F>「你好，Agent」一共 8 个字符\"]\n\n    A --> B\n    B -->|调用工具| C\n    C -->|执行 text_length| D\n    D -->|结果返回| B\n    B --> E\n\n    style A fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n    style B fill:#FFFBEB,stroke:#D97706,stroke-width:2px\n    style C fill:#ECFDF5,stroke:#059669,stroke-width:2px\n    style D fill:#F8FAFC,stroke:#64748B,stroke-width:2px\n    style E fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n",[53,3625,3626,3630,3635,3640,3645,3650,3655,3659,3664,3669,3674,3679,3684,3688,3693,3698,3702,3707],{"__ignoreMap":51},[56,3627,3628],{"class":58,"line":59},[56,3629,62],{},[56,3631,3632],{"class":58,"line":65},[56,3633,3634],{},"    A[\"👤 用户输入\u003Cbr\u002F>「你好，Agent」有几个字符？\"]\n",[56,3636,3637],{"class":58,"line":71},[56,3638,3639],{},"    B[\"🤖 Agent（智能体）\u003Cbr\u002F>分析意图 → 选择工具 → 执行 → 返回\u003Cbr\u002F>封装了 Function Call 的完整流程\"]\n",[56,3641,3642],{"class":58,"line":77},[56,3643,3644],{},"    C[\"🧰 工具列表\u003Cbr\u002F>text_length、text_repeat\u003Cbr\u002F>自动选择 text_length\"]\n",[56,3646,3647],{"class":58,"line":83},[56,3648,3649],{},"    D[\"📤 返回结果\u003Cbr\u002F>result: 8\"]\n",[56,3651,3652],{"class":58,"line":89},[56,3653,3654],{},"    E[\"📥 最终输出\u003Cbr\u002F>「你好，Agent」一共 8 个字符\"]\n",[56,3656,3657],{"class":58,"line":95},[56,3658,105],{"emptyLinePlaceholder":104},[56,3660,3661],{"class":58,"line":101},[56,3662,3663],{},"    A --> B\n",[56,3665,3666],{"class":58,"line":108},[56,3667,3668],{},"    B -->|调用工具| C\n",[56,3670,3671],{"class":58,"line":114},[56,3672,3673],{},"    C -->|执行 text_length| D\n",[56,3675,3676],{"class":58,"line":120},[56,3677,3678],{},"    D -->|结果返回| B\n",[56,3680,3681],{"class":58,"line":126},[56,3682,3683],{},"    B --> E\n",[56,3685,3686],{"class":58,"line":132},[56,3687,105],{"emptyLinePlaceholder":104},[56,3689,3690],{"class":58,"line":138},[56,3691,3692],{},"    style A fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n",[56,3694,3695],{"class":58,"line":144},[56,3696,3697],{},"    style B fill:#FFFBEB,stroke:#D97706,stroke-width:2px\n",[56,3699,3700],{"class":58,"line":329},[56,3701,3001],{},[56,3703,3704],{"class":58,"line":334},[56,3705,3706],{},"    style D fill:#F8FAFC,stroke:#64748B,stroke-width:2px\n",[56,3708,3709],{"class":58,"line":340},[56,3710,3711],{},"    style E fill:#EFF6FF,stroke:#2563EB,stroke-width:2px\n",[10,3713,3714],{},[13,3715,3716,3718,3719,3722,3723,3726,3727,3729],{},[35,3717,154],{}," 与第 3 节的\"手动版\"相比，Agent 把「分析意图 → 选工具 → 执行 → 把结果喂回模型 → 再要一次回答」这一整套循环收进了自己内部。外部只需要一次 ",[53,3720,3721],{},"agent.invoke(\"「你好，Agent」有几个字符？\")","，中间的两轮模型调用、",[53,3724,3725],{},"tool_calls"," 解析、",[53,3728,1905],{}," 拼装都由框架完成。",[10,3731,3732],{},[13,3733,3734],{},"类比：手动 Function Call 像\"自己做饭\"——你要自己判断用什么工具、怎么调用、怎么处理结果；Agent 像\"智能管家\"——你只需说\"帮我数数这句话有几个字\"，管家自动挑出字数统计工具、执行、再把结果告诉你。Agent = 大模型 + 任务规划 + 工具调用 + 记忆，是更高级的 Function Call 封装。适用场景是复杂任务、多步骤推理、需要自动选择工具的场景。",[13,3736,2583],{},[10,3738,3739],{},[13,3740,949,3741],{},[53,3742,3743],{},"agent_learn\u002Ffunction_call\u002FC04_by_agent.py",[46,3745,3747],{"className":955,"code":3746,"language":957,"meta":51,"style":51},"from langchain.agents import initialize_agent, AgentType\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.tools import tool\n\nfrom agent_learn.config import Config\n\nconf = Config()\n\n# todo: 第一步：定义工具函数\n@tool\ndef text_length(text: str) -> int:\n    \"\"\"\n    统计一段文本的字符个数\n    Args:\n        text: 待统计的文本\n    \"\"\"\n    return len(text)\n\n@tool\ndef text_repeat(text: str, times: int) -> str:\n    \"\"\"\n    把文本重复拼接指定次数\n    Args:\n        text: 待重复的文本\n        times: 重复次数\n    \"\"\"\n    return text * times\n\n# 加载工具\ntools = [text_length, text_repeat]\n\n# todo: 第二步：初始化模型\nllm = ChatOpenAI(base_url=conf.base_url,\n                 api_key=conf.api_key,\n                 model=conf.model_name,\n                 temperature=0.1)\n\n# todo: 第三步：创建Agent\nagent = initialize_agent(tools, llm, AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True)\n\n# todo: 第四步：调用Agent\nquery = \"「你好，Agent」这句话有多少个字符？\"\nresult = agent.invoke(query)\nprint(f'result: {result[\"output\"]}')\n",[53,3748,3749,3754,3758,3762,3766,3770,3774,3778,3782,3786,3790,3794,3798,3802,3806,3810,3814,3818,3822,3826,3830,3834,3838,3842,3846,3850,3854,3858,3862,3867,3871,3875,3879,3883,3887,3891,3895,3899,3904,3909,3913,3918,3922,3927],{"__ignoreMap":51},[56,3750,3751],{"class":58,"line":59},[56,3752,3753],{},"from langchain.agents import initialize_agent, AgentType\n",[56,3755,3756],{"class":58,"line":65},[56,3757,964],{},[56,3759,3760],{"class":58,"line":71},[56,3761,2608],{},[56,3763,3764],{"class":58,"line":77},[56,3765,105],{"emptyLinePlaceholder":104},[56,3767,3768],{"class":58,"line":83},[56,3769,978],{},[56,3771,3772],{"class":58,"line":89},[56,3773,105],{"emptyLinePlaceholder":104},[56,3775,3776],{"class":58,"line":95},[56,3777,987],{},[56,3779,3780],{"class":58,"line":101},[56,3781,105],{"emptyLinePlaceholder":104},[56,3783,3784],{"class":58,"line":108},[56,3785,1001],{},[56,3787,3788],{"class":58,"line":114},[56,3789,2637],{},[56,3791,3792],{"class":58,"line":120},[56,3793,1006],{},[56,3795,3796],{"class":58,"line":126},[56,3797,1011],{},[56,3799,3800],{"class":58,"line":132},[56,3801,1016],{},[56,3803,3804],{"class":58,"line":138},[56,3805,1021],{},[56,3807,3808],{"class":58,"line":144},[56,3809,1026],{},[56,3811,3812],{"class":58,"line":329},[56,3813,1011],{},[56,3815,3816],{"class":58,"line":334},[56,3817,1035],{},[56,3819,3820],{"class":58,"line":340},[56,3821,105],{"emptyLinePlaceholder":104},[56,3823,3824],{"class":58,"line":346},[56,3825,2637],{},[56,3827,3828],{"class":58,"line":352},[56,3829,1044],{},[56,3831,3832],{"class":58,"line":358},[56,3833,1011],{},[56,3835,3836],{"class":58,"line":571},[56,3837,1053],{},[56,3839,3840],{"class":58,"line":1197},[56,3841,1021],{},[56,3843,3844],{"class":58,"line":1203},[56,3845,1062],{},[56,3847,3848],{"class":58,"line":1209},[56,3849,1067],{},[56,3851,3852],{"class":58,"line":1215},[56,3853,1011],{},[56,3855,3856],{"class":58,"line":1588},[56,3857,1076],{},[56,3859,3860],{"class":58,"line":1595},[56,3861,105],{"emptyLinePlaceholder":104},[56,3863,3864],{"class":58,"line":1602},[56,3865,3866],{},"# 加载工具\n",[56,3868,3869],{"class":58,"line":1613},[56,3870,2718],{},[56,3872,3873],{"class":58,"line":1623},[56,3874,105],{"emptyLinePlaceholder":104},[56,3876,3877],{"class":58,"line":1629},[56,3878,1769],{},[56,3880,3881],{"class":58,"line":1637},[56,3882,1774],{},[56,3884,3885],{"class":58,"line":1649},[56,3886,1779],{},[56,3888,3889],{"class":58,"line":1659},[56,3890,1784],{},[56,3892,3893],{"class":58,"line":1664},[56,3894,1789],{},[56,3896,3897],{"class":58,"line":1669},[56,3898,105],{"emptyLinePlaceholder":104},[56,3900,3901],{"class":58,"line":1686},[56,3902,3903],{},"# todo: 第三步：创建Agent\n",[56,3905,3906],{"class":58,"line":1691},[56,3907,3908],{},"agent = initialize_agent(tools, llm, AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True)\n",[56,3910,3911],{"class":58,"line":1696},[56,3912,105],{"emptyLinePlaceholder":104},[56,3914,3915],{"class":58,"line":1702},[56,3916,3917],{},"# todo: 第四步：调用Agent\n",[56,3919,3920],{"class":58,"line":2234},[56,3921,1921],{},[56,3923,3924],{"class":58,"line":2239},[56,3925,3926],{},"result = agent.invoke(query)\n",[56,3928,3929],{"class":58,"line":2244},[56,3930,3931],{},"print(f'result: {result[\"output\"]}')\n",[13,3933,3934,3935,3937,3938,3937,3940,3942,3943,3946],{},"核心代码只有 4 行（创建 Agent + 调用），对比手动 Function Call 需要自己处理 ",[53,3936,3725],{},"、",[53,3939,2909],{},[53,3941,1905],{},"，Agent 一行 ",[53,3944,3945],{},"invoke()"," 搞定，框架自动处理所有细节。",[17,3948,3950],{"id":3949},"_5-本节小结","5. 本节小结",[449,3952,3953,3960,3970,3976],{},[452,3954,3955,3956,3959],{},"Function Call 的本质是",[35,3957,3958],{},"模型只输出参数、不执行函数","，执行权留在应用程序手里",[452,3961,3962,3963,3966,3967,3969],{},"一次完整调用需要",[35,3964,3965],{},"两轮模型交互","：第一轮拿到 ",[53,3968,3725],{},"，第二轮拿到自然语言答案",[452,3971,3972,3973,3975],{},"工具定义的三种方式中，",[53,3974,2529],{}," 装饰器日常最省事，Pydantic 适合需要严格校验的场景",[452,3977,3978],{},"Agent 是对 Function Call 流程的封装，把\"多轮调用 + 工具执行\"隐藏起来，让调用方只写一行",[3980,3981,3982],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":51,"searchDepth":65,"depth":65,"links":3984},[3985,3991,3992,3998,3999],{"id":19,"depth":65,"text":20,"children":3986},[3987,3988,3989,3990],{"id":24,"depth":71,"text":25},{"id":158,"depth":71,"text":159},{"id":378,"depth":71,"text":379},{"id":640,"depth":71,"text":641},{"id":653,"depth":65,"text":654},{"id":911,"depth":65,"text":912,"children":3993},[3994,3995,3996,3997],{"id":935,"depth":71,"text":936},{"id":2519,"depth":71,"text":2520},{"id":2889,"depth":71,"text":2890},{"id":3346,"depth":71,"text":3347},{"id":3605,"depth":65,"text":3606},{"id":3949,"depth":65,"text":3950},"2026-06-02","从“客服查快递”讲清 Function Call 的工作机制，再用文本小工具示例对比 JSON Schema、@tool 装饰器与 Pydantic 三种工具定义方式。",false,"md","article",{"migration":4006},{"generator":4007,"sourceSha256":4008},"nuxt-site\u002Fscripts\u002Fmigrate-content.mjs","ff07d393cdc6d46c1a3f80e2b3f159870a07c3f1c7d56054d865fb65695d12ce","\u002Fagent\u002F01-functioncall函数调用",null,"agent",{"title":5,"description":4001},"content\u002Fagent\u002F01-FunctionCall函数调用.md","agent\u002F01-FunctionCall函数调用",[4016,4017,4018],"Agent","Function Call","工具调用","Pzj8O08SN9lYP8ZFVBMPOQfqVMGCicZH8qBENvWfTsg",1791279145023]