news 2026/9/26 2:43:46

别以为只有大模型能调用工具!手把手教你用提示工程“骗”AI干活_通过提示词的方式告诉大模型调用某个java函数-CSDN博客

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
别以为只有大模型能调用工具!手把手教你用提示工程“骗”AI干活_通过提示词的方式告诉大模型调用某个java函数-CSDN博客

首屏导读 · 本教程配套付费专栏: 大模型工程师修炼手记19.9 元(AI 编程 / Agent 实战 | 本文同主题系统课程)· AI时代程序员的自我提升49.9 元(AI 时代成长方法论)。

单篇不过瘾?订阅解锁全量源码、实战与答疑;文末附资料包领取方式 ↓

引言:AI真的会“用工具”吗?

当你在聊天框里输入“北京今天天气怎么样?”时,你是否好奇过:AI是怎么知道实时天气的?它自己上网查的吗?其实,AI本身并不具备实时获取信息的能力——它只是通过训练学到了“如何调用工具”这一技能。

所谓“工具调用”,就是让大模型在需要时,输出一个结构化的指令(比如“查询天气,城市=北京”),然后由外部程序执行这个指令,再把结果返回给模型,最终生成自然语言回答。整个过程就像你指挥一个助手去办事:你动嘴,助手动手。

目前实现工具调用的主流方式有三种:

  1. 提示工程模拟:通过精心设计的prompt,让模型输出固定格式的JSON,外部解析执行。
  2. 原生Tool Calling:大模型原生支持的标准接口,输出结构化的tool_calls请求。
  3. MCP协议:新兴的开放协议,将工具封装为独立服务,实现跨语言、动态扩展。

本篇我们将从最基础的提示工程模拟入手,手把手带你实现一个完整的工具调用流程。这种方式不依赖任何高级功能,适用于任何大模型,也是理解工具调用原理的最佳起点。


一、核心思想:让模型当“编剧”,程序当“演员”

在没有原生函数调用支持的情况下,我们需要通过提示词“教会”模型如何表达调用工具的意图。核心思路如下:

  1. 告诉模型有哪些工具可用(名称、功能、参数)。
  2. 要求模型在需要调用工具时,输出一个特定格式的JSON(包含工具名和参数)。
  3. 外部程序解析这个JSON,执行对应的工具函数。
  4. (可选)将工具执行结果再次交给模型,生成自然语言回答。

这个过程的巧妙之处在于:模型并不真正执行任何代码,它只是“纸上谈兵”——输出调用指令。真正的“动手”由我们的Java程序完成。这样既利用了模型的智能,又保证了安全性和可控性。


二、实战准备:Spring Boot + Spring AI 环境搭建

我们将在Spring Boot项目中集成Spring AI,以OpenAI为例(其他模型类似)。首先创建Spring Boot项目(3.2+),添加依赖:

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId><version>1.0.0.M6</version></dependency></dependencies>

在application.yml中配置OpenAI API密钥:

spring:ai:openai:api-key:${OPENAI_API_KEY}chat:options:model:gpt-3.5-turbotemperature:0.0# 降低随机性,保证输出稳定

三、定义可执行的工具函数

我们实现两个简单工具:天气查询和计算器。注意:这些是真实的Java方法,之后将由我们的程序调用。

@ComponentpublicclassWeatherToolExecutor{// 模拟天气查询(实际可调用真实API)publicStringgetWeather(Stringcity){Map<String,String>weatherMap=Map.of("北京","晴,10-22℃","上海","多云,15-25℃","广州","雨,20-28℃");Stringweather=weatherMap.get(city);returnweather!=null?weather:"未找到"+city+"的天气信息";}// 模拟计算器(使用JavaScript引擎,生产环境建议使用安全计算库)publicStringcalculate(Stringexpression){try{ScriptEngineengine=newScriptEngineManager().getEngineByName("JavaScript");Objectresult=engine.eval(expression);returnString.valueOf(result);}catch(Exceptione){return"表达式错误:"+e.getMessage();}}}

这里我们使用@Component将工具类注册为Spring Bean,方便后续注入。


四、设计提示词:让模型学会输出JSON

提示词是整个流程的关键。我们需要:

  • 清晰描述每个工具的功能和参数。
  • 规定输出格式(必须是一个JSON对象)。
  • 提供几个示例(few-shot),帮助模型理解。

下面是我们设计的系统提示词:

你是一个智能助手,可以根据用户问题调用以下工具: 1. get_weather:获取城市天气 参数:{"city": "城市名称"} 2. calculate:计算数学表达式 参数:{"expression": "数学表达式,如 2+2"} 如果用户问题需要调用工具,请只输出一个JSON对象,格式为: { "tool": "工具名称", "args": {"参数名": "参数值"} } 不要输出其他任何内容。 如果不需要调用工具,直接回答用户问题。 示例: 用户:北京今天热吗? 输出:{"tool": "get_weather", "args": {"city": "北京"}} 用户:计算 3.5 * 2 输出:{"tool": "calculate", "args": {"expression": "3.5 * 2"}} 用户:你好 输出:你好!有什么可以帮你的吗?

注意:我们将这个提示作为system消息发送,这样模型会在每次对话中遵循这些规则。


五、编写服务类:解析JSON并执行工具

接下来我们创建一个服务类,整合以上逻辑:

@ServicepublicclassPromptToolService{privatefinalChatClientchatClient;privatefinalWeatherToolExecutortoolExecutor;privatefinalObjectMapperobjectMapper=newObjectMapper();publicPromptToolService(ChatClient.Builderbuilder,WeatherToolExecutortoolExecutor){this.chatClient=builder.build();this.toolExecutor=toolExecutor;}publicStringprocess(StringuserInput){// 1. 调用模型获取响应Stringresponse=chatClient.prompt().system(SYSTEM_PROMPT)// 上一步定义的提示词.user(userInput).call().content();// 2. 尝试解析为JSONtry{// 清理可能的多余字符(如Markdown代码块)Stringcleaned=response.replaceAll("```json|```","").trim();if(cleaned.startsWith("{")&&cleaned.endsWith("}")){JsonNodejson=objectMapper.readTree(cleaned);Stringtool=json.get("tool").asText();JsonNodeargs=json.get("args");// 3. 根据工具名执行对应方法Stringresult=switch(tool){case"get_weather"->toolExecutor.getWeather(args.get("city").asText());case"calculate"->toolExecutor.calculate(args.get("expression").asText());default->"未知工具:"+tool;};// 4. 返回执行结果(也可以再次调用模型生成自然语言回答)return"工具返回:"+result;}}catch(Exceptione){// 解析失败,记录日志System.err.println("JSON解析失败: "+e.getMessage());}// 不是JSON,直接返回模型原始回答returnresponse;}// 将SYSTEM_PROMPT定义为常量privatestaticfinalStringSYSTEM_PROMPT=""" 你是一个智能助手,可以根据用户问题调用以下工具: 1. get_weather:获取城市天气 参数:{"city": "城市名称"} 2. calculate:计算数学表达式 参数:{"expression": "数学表达式,如 2+2"} 如果用户问题需要调用工具,请只输出一个JSON对象,格式为: { "tool": "工具名称", "args": {"参数名": "参数值"} } 不要输出其他任何内容。 如果不需要调用工具,直接回答用户问题。 示例: 用户:北京今天热吗? 输出:{"tool": "get_weather", "args": {"city": "北京"}} 用户:计算 3.5 * 2 输出:{"tool": "calculate", "args": {"expression": "3.5 * 2"}} 用户:你好 输出:你好!有什么可以帮你的吗? """;}

六、测试一下

我们写一个简单的Controller来测试:

@RestControllerpublicclassChatController{@AutowiredprivatePromptToolServicepromptToolService;@GetMapping("/chat")publicStringchat(@RequestParamStringmessage){returnpromptToolService.process(message);}}

启动应用,用浏览器或curl测试:

GET /chat?message=北京今天天气怎么样? 响应:工具返回:晴,10-22℃
GET /chat?message=计算 25*4+10 响应:工具返回:110
GET /chat?message=你好 响应:你好!有什么可以帮你的吗?

完美!模型成功输出了JSON,程序正确解析并执行了工具。


七、进阶:将工具结果再次交给模型生成自然语言

目前我们直接返回了工具的执行结果,虽然清晰,但不够自然。比如用户问“北京今天热吗?”,工具返回“晴,10-22℃”,如果直接回复这句,显得生硬。更好的做法是:把工具结果作为上下文,再次调用模型,让它生成一个完整的回答。

修改process方法,在执行工具后,将结果组装成新的用户消息,再次调用模型:

publicStringprocess(StringuserInput){// 第一步:获取模型响应(可能包含JSON)StringfirstResponse=chatClient.prompt().system(SYSTEM_PROMPT).user(userInput).call().content();// 尝试解析JSONtry{Stringcleaned=firstResponse.replaceAll("```json|```","").trim();if(cleaned.startsWith("{")&&cleaned.endsWith("}")){JsonNodejson=objectMapper.readTree(cleaned);Stringtool=json.get("tool").asText();JsonNodeargs=json.get("args");// 执行工具StringtoolResult=switch(tool){case"get_weather"->toolExecutor.getWeather(args.get("city").asText());case"calculate"->toolExecutor.calculate(args.get("expression").asText());default->"未知工具";};// 将工具结果作为上下文,再次调用模型生成自然语言回答StringfinalAnswer=chatClient.prompt().system("你是一个智能助手,请根据工具返回的结果,用自然的语言回答用户。").user("用户问题:"+userInput+"\n工具返回:"+toolResult).call().content();returnfinalAnswer;}}catch(Exceptione){// 解析失败,忽略}returnfirstResponse;}

测试:

GET /chat?message=北京今天天气怎么样? 响应:北京今天天气晴朗,气温在10到22摄氏度之间,适合外出活动。

这样回答更加自然。


八、处理多轮对话:简单的记忆机制

在上面的实现中,每次对话都是独立的,模型不记得之前说过什么。如果用户接着问“那上海呢?”,模型无法关联上下文。我们可以通过维护一个会话记忆来改善。

Spring AI提供了ChatMemory接口和MessageChatMemoryAdvisor,可以方便地添加记忆功能。但为了保持本篇的独立性,我们先用一个简单的手动实现:

@ServicepublicclassPromptToolWithMemoryService{privatefinalChatClientchatClient;privatefinalWeatherToolExecutortoolExecutor;privatefinalObjectMapperobjectMapper=newObjectMapper();// 简单内存存储,key为sessionId,value为历史消息列表privatefinalMap<String,List<Message>>sessionMemory=newConcurrentHashMap<>();// ... 构造函数等publicStringprocess(StringuserInput,StringsessionId){// 获取或创建历史记录List<Message>history=sessionMemory.computeIfAbsent(sessionId,k->newArrayList<>());// 将用户消息加入历史history.add(newUserMessage(userInput));// 构建包含历史的promptPromptprompt=newPrompt(history,ChatOptions.builder().model("gpt-3.5-turbo").temperature(0.0).build());// 调用模型Stringresponse=chatClient.prompt(prompt).system(SYSTEM_PROMPT).call().content();// 处理工具调用(省略,与之前类似)// ...// 将助手回答加入历史history.add(newAssistantMessage(finalAnswer));returnfinalAnswer;}}

这样,同一sessionId的对话会累积记忆,模型就能理解上下文了。


九、优缺点与适用场景

优点
  • 通用性强:任何大模型都可以使用,不依赖厂商特定功能。
  • 完全可控:输出格式、工具列表、执行逻辑都由自己定义。
  • 易于理解:适合教学和快速原型,帮助理解工具调用的本质。
缺点
  • 格式不稳定:模型可能输出格式错误的JSON,或编造不存在的工具。
  • 解析繁琐:需要编写额外的解析和异常处理代码。
  • 无法并行调用:一次只能请求一个工具(除非设计数组格式)。
  • 多轮记忆需要手动维护。
适用场景
  • 使用不支持原生Tool Calling的模型(如某些开源模型)。
  • 需要对工具调用进行高度定制(如特殊的安全校验)。
  • 教学演示,帮助新人理解原理。

十、小结

本篇我们通过提示工程的方式,成功模拟了函数调用,实现了天气查询和计算器功能。虽然这种方法有一些局限性,但它让我们深入理解了工具调用的核心思想:模型负责“想”,程序负责“做”。

下一篇文章,我们将介绍Spring AI原生的Tool Calling功能,看看它是如何优雅地解决上述问题的,让代码简洁10倍!

href="https://csdnimg.cn/release/blogv2/dist/mdeditor/css/editerView/markdown_views-e1cc28b339.css" rel="stylesheet"> href="https://csdnimg.cn/release/blogv2/dist/mdeditor/css/style-d1e89cace4.css" rel="stylesheet">

内容来源:csdn.net

作者昵称:Tom·Ge

原文链接:https://blog.csdn.net/gedonshen/article/details/158463020

作者主页:https://blog.csdn.net/gedonshen

标签
#人工智能 #AI编程
确定要放弃本次机会?
福利倒计时
::

立减 ¥

普通VIP年卡可用
立即使用

延伸阅读 · 我的付费专栏

觉得这篇文章对你有帮助?我把同类主题的系统化内容沉淀成了付费专栏,欢迎订阅支持持续输出:

专栏定价内容
大模型工程师修炼手记19.9 元AI 编程 / Agent 深度实战
AI时代程序员的自我提升49.9 元AI 时代成长方法论

本文配套代码 / 资料包:欢迎在评论区留言「求代码」,我会私信发送完整资源!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 2:43:37

EasyDataAI课程笔记 TASK4

1.任务要求 任务信息截止时间任务明细Task 4&#xff1a; - 开发者篇 D2&#xff1a;统一 AI Native 数据层实战 - 产业应用篇 I3&#xff1a;SQL AI —— AI Functions 的设计与执行截止时间 09 月 26 日 03:00任务&#xff1a; 1. 通过阅读并跑通 code/D2 目录下的 d2_1…

作者头像 李华
网站建设 2026/9/26 2:43:33

Chronos协变量预测:三步把外部特征喂给模型

Chronos协变量预测&#xff1a;三步把外部特征喂给模型 【免费下载链接】chronos-forecasting Chronos: Pretrained Models for Time Series Forecasting 项目地址: https://gitcode.com/GitHub_Trending/ch/chronos-forecasting 只拿历史电价去预测明天的电价&#xff…

作者头像 李华
网站建设 2026/9/26 2:43:32

八字排盘App开发实战:离线本地存储、真太阳时校正与节气算法全解析

排盘 App 做了整整半年&#xff0c;期间推翻了三次数据结构和 UI 方案&#xff0c;最后沉淀下来的版本让我在朋友圈里被问爆了。它没有广告、不申请网络权限、所有命盘数据都锁在手机本地&#xff0c;哪怕在飞机上打开&#xff0c;也能一秒排出完整的壬午年柱八字。这篇文章把我…

作者头像 李华
网站建设 2026/9/26 2:42:32

SpringBoot项目Maven配置实战:从依赖管理到常见踩坑排查

新手用SpringBoot搞项目&#xff0c;十个里有八个第一关就卡在Maven上。不是依赖下载慢得让人抓狂&#xff0c;就是本地明明有包却报红&#xff0c;再不然就是IDEA里那个“Cannot resolve symbol”怎么都消不掉。很多教程默认你懂Maven&#xff0c;结果你照着敲代码&#xff0c…

作者头像 李华
网站建设 2026/9/26 2:42:06

倒V天线DIY全攻略:从选材、绕巴伦到架设调试的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 2:39:45

Delta金手指实用指南:在iPhone上四步启用你的第一个作弊码

Delta金手指实用指南&#xff1a;在iPhone上四步启用你的第一个作弊码 【免费下载链接】Delta Delta is an all-in-one classic video game emulator for non-jailbroken iOS devices. 项目地址: https://gitcode.com/GitHub_Trending/delt/Delta Delta 是一款运行在 iO…

作者头像 李华