Java: 手动实现DeepSeek R1工具调用,基于ReAct与Spring AI的实践指南
1. 为什么我们需要手动实现DeepSeek R1的工具调用?
最近在捣鼓DeepSeek R1的时候,我发现了一个挺让人头疼的问题:这模型本身不支持原生的工具调用。官方在GitHub上倒是挺坦诚,说下一个大版本会支持,但具体啥时候能等到,谁也说不好。这就好比你买了一辆性能超强的跑车,结果发现它没有方向盘——动力是有了,但你想往哪儿开,完全使不上劲。
对于咱们做AI应用开发的来说,工具调用可不是什么锦上添花的功能,而是刚需。你想啊,一个AI模型再聪明,它知道的也只是训练数据截止日期之前的世界。让它查今天的天气、帮你订张机票、或者从数据库里拉点实时数据,它自己根本办不到。这时候,工具调用就派上用场了:让AI学会“使用工具”,把复杂任务拆解成“思考-行动-观察”的循环,最终调用外部API或者执行一段代码来解决问题。
我试过直接问DeepSeek R1:“今天北京天气怎么样?”它可能会根据历史数据给你编一个答案,或者干脆告诉你它不知道实时信息。这显然不是我们想要的。我们需要的是它能意识到“我需要调用一个天气查询工具”,然后生成规范的调用指令,我们收到指令后去执行真正的API调用,再把结果喂回给它,让它生成最终的人类可读答案。这个过程,就是ReAct(Reasoning + Acting)思想的核心。
所以,与其干等官方更新,不如咱们自己动手,丰衣足食。用Java,结合Spring AI这个越来越火的框架,再套上ReAct的“思考模板”,完全可以在DeepSeek R1上实现一套手工打造的工具调用流程。听起来有点复杂?别怕,我一步步带你走一遍,踩过的坑都给你标出来,保证你能跟着做出来。
2. 理解ReAct:让AI学会“动脑”和“动手”
在开始敲代码之前,咱们得先把ReAct这个思想吃透。你可以把它想象成教一个特别聪明但缺乏实践能力的孩子解决问题。比如,孩子想从高高的柜子上拿饼干。他不会直接跳起来抓(那不可能),而是会先“思考”:我需要一个垫脚的东西。然后“行动”:去搬一把椅子。接着“观察”:椅子够不够高?如果不够,再进入下一轮“思考”:也许还需要再垫几本书。如此循环,直到最终“行动”:站上去拿到饼干。
把这个逻辑搬到AI对话里,就形成了ReAct的标准流程。AI的每次回复不再是一锤子买卖,而是一个循环中的一步。它输出的内容被严格格式化成几个部分:Thought(我目前的分析和下一步计划)、Action(我要调用哪个工具,参数是什么)、Observation(上次工具调用的结果)。只有当它认为信息足够时,才会输出 Final Answer。
为什么非得用这个格式?因为DeepSeek R1不支持结构化输出(比如强制它返回一个固定的JSON字段)。我们只能通过精心设计的提示词(Prompt),去“引导”它按照我们规定的格式说话。这个提示词就像一份详细的“剧本”和“行为规范”,告诉AI:“你说话必须按这个套路来,先报告思考,再报告行动,行动必须包在特定的标记里。”
我最初尝试时,提示词写得比较松散,结果AI经常“演砸”——要么忘了输出Action标记,要么把思考和答案混在一起说,导致我的程序根本无法解析下一步该干什么。后来我把提示词打磨得非常严格,几乎到了“强迫症”的地步,效果才稳定下来。这份中文版的提示词模板,是我调试了很多次后的成果,你可以直接拿去用:
尽可能帮助用户,并提供准确的回答。
{{instruction}}
你可以使用以下工具:
{{tools}}
通过使用 JSON 对象来指定工具,提供 action(工具名称)和 action_input(工具输入)键。
有效的 “action” 值有:“Final Answer” 或 {{tool_names}}。
每个 $JSON_BLOB 只能指定一个动作,格式如下:
###
{
"action": $TOOL_NAME,
"action_input": $ACTION_INPUT
}
###
遵循以下格式:
问题(Question):要回答的输入问题
思考(Thought):考虑之前和接下来的步骤
行动(Action):
### $JSON_BLOB ###
观察(Observation):行动结果
… (重复“思考/行动/观察”多次,直至完成任务)
思考(Thought):我知道如何回答了
行动(Action):
###
{
"action": "Final Answer",
"action_input": "Final response to human"
}
###
开始!请务必始终以有效的 JSON 对象形式提供单个Action。
如果需要工具就使用工具。
如果适合,请直接回应用户。
格式为:###$JSON_BLOB###然后 Observation:.
这里有几个关键点你一定要注意:
{{instruction}}:这是你的系统指令,比如“你是一个有帮助的助手”。{{tools}}:这里要替换成你定义的所有工具的JSON Schema描述。AI需要知道每个工具叫什么、能干什么、需要什么参数。这部分描述越清晰,AI调用得越准。{{tool_names}}:就是所有工具名称的列表,用逗号隔开,比如"查询天气, 搜索新闻, 计算器"。这用来明确告诉AI有哪些选项。- 严格的格式和分隔符:我用了
###作为JSON块的开始和结束标记。这个标记要足够独特,避免和AI正常输出的内容混淆。同时,整个“思考-行动-观察”的循环结构必须反复强调,直到AI形成“肌肉记忆”。
3. 搭建Spring AI项目环境与核心依赖
理论讲完了,咱们动手搭环境。我选择Spring AI,是因为它和Spring Boot生态无缝集成,配置起来比直接用原生API省心很多,尤其是在管理对话历史、处理消息流这些方面。
首先,用你喜欢的IDE(比如IntelliJ IDEA)或者Spring Initializr创建一个新的Spring Boot项目。我这里假设你用Maven,Gradle的配置也类似。关键的依赖就下面这几个:
<dependencies>
<!-- Spring Boot Web (如果需要提供HTTP接口的话) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI 核心 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
<version>0.8.1</version> <!-- 请使用最新稳定版 -->
</dependency>
<!-- Spring AI 对DeepSeek的支持 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-deepseek</artifactId>
<version>0.8.1</version>
</dependency>
<!-- 工具类,比如JSON处理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
注意:Spring AI的版本迭代比较快,你一定要去官方文档查一下当前推荐的稳定版本。我写这篇文章时0.8.1是稳定的,但等你看到的时候可能已经更新了。
依赖加好后,在 application.yml 或 application.properties 里配置你的DeepSeek API密钥和基础URL。DeepSeek的API目前是免费的,但你需要去官网申请一个API Key。
spring:
ai:
deepseek:
base-url: https://api.deepseek.com
api-key: ${DEEPSEEK_API_KEY:你的API密钥}
chat:
options:
model: deepseek-r1
temperature: 0.1 # 温度调低点,让输出更稳定,更遵守格式
这里我把 temperature 调到了0.1,甚至0。在工具调用场景下,我们不需要AI有多大的“创意”,恰恰相反,我们需要它严格地、可预测地遵守我们规定的输出格式。低温度值能大大减少它“胡言乱语”、破坏格式的概率。
环境搭好,启动一下项目,确保没有报错。接下来,我们就要进入最核心的部分:定义工具,并实现那个驱动AI“思考-行动”循环的引擎。
4. 定义与封装AI可用的工具
工具是AI的“手”和“眼”。在Spring AI里,定义一个工具特别简单,就是一个实现了 Tool 接口的普通Spring Bean。这个接口主要就是一个 call 方法,接收一个参数(通常是字符串形式的JSON),返回一个字符串结果。
我们来定义两个最常用的工具当例子:一个天气查询,一个简单计算器。
import org.springframework.ai.tool.Tool;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.Map;
@Component
public class WeatherTool implements Tool {
@Override
public String getName() {
return "get_weather";
}
@Override
public String getDescription() {
return "根据城市名称查询该城市当前的天气情况。";
}
@Override
public Map<String, String> getParameters() {
// 定义输入参数的Schema,这里用简单的Map,也可以用JSON Schema对象
return Map.of(
"city", "要查询天气的城市名称,例如:北京、上海"
);
}
@Override
public String call(String input) {
try {
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = (ObjectNode) mapper.readTree(input);
String city = node.get("city").asText();
// 这里应该是调用真实天气API,比如和风、OpenWeatherMap等
// 为了演示,我们模拟一个返回
// 实战中,请在这里发起HTTP请求并解析结果
String mockResult = String.format("城市【%s】的天气情况:晴,温度25℃,湿度60%,东南风2级。", city);
return mockResult;
} catch (Exception e) {
return "查询天气时发生错误:" + e.getMessage();
}
}
}
@Component
public class CalculatorTool implements Tool {
@Override
public String getName() {
return "calculator";
}
@Override
public String getDescription() {
return "执行简单的数学计算,支持加(+)、减(-)、乘(*)、除(/)。";
}
@Override
public Map<String, String> getParameters() {
return Map.of(
"expression", "数学表达式,例如:'3 + 5 * 2'"
);
}
@Override
public String call(String input) {
try {
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = (ObjectNode) mapper.readTree(input);
String expression = node.get("expression").asText().replaceAll("\\s+", "");
// 警告:这里使用ScriptEngine仅作最简单演示,生产环境请使用更安全的表达式求值库!
javax.script.ScriptEngineManager mgr = new javax.script.ScriptEngineManager();
javax.script.ScriptEngine engine = mgr.getEngineByName("JavaScript");
Object result = engine.eval(expression);
return String.format("表达式 `%s` 的计算结果是:%s", expression, result.toString());
} catch (Exception e) {
return "计算表达式时发生错误:" + e.getMessage();
}
}
}
定义好工具后,Spring AI会自动发现它们。但是,我们手动实现的ReAct引擎需要自己来管理和调用这些工具。所以,我们还需要一个 ToolManager 来集中管理所有的工具,方便根据名称查找和执行。
import org.springframework.ai.tool.Tool;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ToolManager {
private final List<Tool> tools;
private final Map<String, Tool> toolMap = new HashMap<>();
// Spring会自动注入所有实现了Tool接口的Bean
public ToolManager(List<Tool> tools) {
this.tools = tools;
}
@PostConstruct
public void init() {
for (Tool tool : tools) {
toolMap.put(tool.getName(), tool);
}
}
public Tool getTool(String name) {
return toolMap.get(name);
}
public List<Tool> getAllTools() {
return tools;
}
// 这个方法用于生成ReAct提示词中的 {{tools}} 部分
public String generateToolsDescription() {
StringBuilder sb = new StringBuilder();
for (Tool tool : tools) {
sb.append("- 工具名称:").append(tool.getName()).append("\n");
sb.append(" 描述:").append(tool.getDescription()).append("\n");
sb.append(" 参数:").append(tool.getParameters()).append("\n\n");
}
return sb.toString();
}
// 这个方法用于生成ReAct提示词中的 {{tool_names}} 部分
public String generateToolNames() {
return String.join(", ", toolMap.keySet());
}
}
有了工具定义和管理器,我们就有了AI可以调用的“武器库”。下一步,就是构建那个最关键的“大脑”——ReAct提示词引擎,并实现循环调用逻辑。
5. 构建ReAct引擎:循环调用与结果解析
这是整个手动实现中最核心、也最需要耐心调试的部分。我们的目标很简单:写一个循环,每次循环都拿着构建好的对话历史(包含用户问题、AI的思考、行动、以及我们返回的观察结果)去调用DeepSeek R1 API,然后解析它的输出,判断是该执行工具,还是可以返回最终答案了。
首先,我们创建一个 ReActService 类。它会依赖Spring AI提供的 ChatClient 来与DeepSeek对话,以及我们刚才写的 ToolManager。
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.messages.*;
import org.springframework.ai.tool.Tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class ReActService {
@Autowired
private ChatClient chatClient;
@Autowired
private ToolManager toolManager;
// 系统指令,可以根据你的场景修改
private static final String SYSTEM_INSTRUCTION = "你是一个有帮助的AI助手,请严格遵循给定的格式进行思考和行动。";
public String executeWithTools(String userQuestion) {
// 1. 构建初始提示词
String toolsDesc = toolManager.generateToolsDescription();
String toolNames = toolManager.generateToolNames();
String reactPromptTemplate = """
尽可能帮助用户,并提供准确的回答。
%s
你可以使用以下工具:
%s
通过使用 JSON 对象来指定工具,提供 action(工具名称)和 action_input(工具输入)键。
有效的 “action” 值有:“Final Answer” 或 %s。
每个 $JSON_BLOB 只能指定一个动作,格式如下:
###
{
"action": $TOOL_NAME,
"action_input": $ACTION_INPUT
}
###
遵循以下格式:
问题(Question):要回答的输入问题
思考(Thought):考虑之前和接下来的步骤
行动(Action):
### $JSON_BLOB ###
观察(Observation):行动结果
… (重复“思考/行动/观察”多次,直至完成任务)
思考(Thought):我知道如何回答了
行动(Action):
###
{
"action": "Final Answer",
"action_input": "Final response to human"
}
###
开始!请务必始终以有效的 JSON 对象形式提供单个Action。
如果需要工具就使用工具。
如果适合,请直接回应用户。
格式为:###$JSON_BLOB###然后 Observation:.
""".formatted(SYSTEM_INSTRUCTION, toolsDesc, toolNames);
// 2. 初始化消息历史
List<Message> messageHistory = new ArrayList<>();
messageHistory.add(new SystemMessage(reactPromptTemplate));
messageHistory.add(new UserMessage("问题(Question):" + userQuestion));
// 3. 开始ReAct循环
int maxIterations = 10; // 防止无限循环
for (int i = 0; i < maxIterations; i++) {
// 构建本次对话的Prompt
Prompt prompt = new Prompt(messageHistory);
// 调用DeepSeek R1
ChatResponse response = chatClient.call(prompt);
String assistantReply = response.getResult().getOutput().getContent();
// 4. 解析AI的回复
ParsedResult parsedResult = parseAssistantReply(assistantReply);
System.out.println("第" + (i+1) + "轮回复解析结果:" + parsedResult);
if ("Final Answer".equals(parsedResult.action)) {
// 循环结束,返回最终答案
return parsedResult.actionInput;
}
// 5. 执行工具调用
Tool tool = toolManager.getTool(parsedResult.action);
if (tool == null) {
// 如果AI请求了一个不存在的工具,把错误信息作为Observation
String errorObservation = "Observation: 工具 '" + parsedResult.action + "' 不存在。";
messageHistory.add(new AssistantMessage(assistantReply));
messageHistory.add(new UserMessage(errorObservation));
continue;
}
String toolResult;
try {
toolResult = tool.call(parsedResult.actionInput);
} catch (Exception e) {
toolResult = "Observation: 调用工具 '" + parsedResult.action + "' 时出错: " + e.getMessage();
}
// 6. 将本次AI回复和工具执行结果加入历史,进行下一轮
messageHistory.add(new AssistantMessage(assistantReply));
messageHistory.add(new UserMessage("观察(Observation):" + toolResult));
}
return "经过多次尝试,未能得出最终答案。可能问题过于复杂或格式解析失败。";
}
// 解析AI回复的核心方法
private ParsedResult parseAssistantReply(String reply) {
ParsedResult result = new ParsedResult();
// 首先,尝试提取Thought部分(不一定总是有,但有助于调试)
int thoughtStart = reply.indexOf("思考(Thought):");
if (thoughtStart != -1) {
int thoughtEnd = reply.indexOf("\n", thoughtStart);
if (thoughtEnd != -1) {
result.thought = reply.substring(thoughtStart + "思考(Thought):".length(), thoughtEnd).trim();
}
}
// 关键:提取被###包裹的JSON块
String jsonBlockMarker = "###";
int blockStart = reply.indexOf(jsonBlockMarker);
if (blockStart == -1) {
// 如果没有找到标记,可能AI直接给出了最终答案(非标准情况)
result.action = "Final Answer";
result.actionInput = reply;
return result;
}
int blockEnd = reply.indexOf(jsonBlockMarker, blockStart + jsonBlockMarker.length());
if (blockEnd == -1) {
// 格式错误,标记不完整
result.action = "Final Answer";
result.actionInput = "错误:未找到匹配的JSON块结束标记。";
return result;
}
String jsonStr = reply.substring(blockStart + jsonBlockMarker.length(), blockEnd).trim();
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(jsonStr);
result.action = rootNode.get("action").asText();
JsonNode inputNode = rootNode.get("action_input");
result.actionInput = (inputNode != null && !inputNode.isNull()) ? inputNode.asText() : "";
} catch (Exception e) {
// JSON解析失败
result.action = "Final Answer";
result.actionInput = "错误:无法解析Action JSON - " + e.getMessage() + "。原始内容:" + jsonStr;
}
return result;
}
// 内部类,用于存储解析结果
private static class ParsedResult {
String thought;
String action;
String actionInput;
@Override
public String toString() {
return String.format("Thought: %s, Action: %s, Input: %s", thought, action, actionInput);
}
}
}
这段代码就是整个手动ReAct引擎的心脏。我来解释几个容易出错的点:
- 消息历史的构建:每一轮循环,我们都要把完整的对话历史(包括最初的系统提示、用户问题、AI的所有回复、我们所有的观察结果)传给模型。这样它才知道整个思考过程进行到哪一步了。Spring AI的
Message对象帮我们很好地管理了角色(System, User, Assistant)。 - 解析AI回复:这是最脆弱的一环。我们依赖
###作为JSON块的标记。如果AI的输出里偶然出现了这三个字符,就会导致解析混乱。所以我在提示词里反复强调这个格式。解析时,先找标记,再提取中间的字符串,最后用Jackson解析JSON。一定要做好异常处理,因为AI的输出是不可控的。 - 循环终止条件:两个条件会终止循环:一是AI的action是
"Final Answer",二是达到了最大循环次数(我设为10,防止复杂任务陷入死循环)。在实际使用中,你可能还需要根据token数量或者超时时间来设置终止条件。 - 错误处理:如果AI调用了一个不存在的工具,或者工具执行抛异常,我们要把这个错误信息包装成
Observation塞回对话历史。这样AI在下一轮思考时,就能知道上一步出错了,并调整策略。
写到这里,核心逻辑就完成了。你可以写一个简单的Controller来暴露一个HTTP接口,测试一下这个服务。
6. 实战测试与常见问题调试
理论完美,跑起来才是硬道理。我们写个测试Controller,然后看看实际效果。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/react")
public class ReActController {
@Autowired
private ReActService reActService;
@PostMapping("/chat")
public String chat(@RequestBody ChatRequest request) {
// 简单起见,直接返回结果。生产环境应考虑流式响应、异步处理等。
return reActService.executeWithTools(request.getQuestion());
}
// 简单的请求体
public static class ChatRequest {
private String question;
// getter and setter ...
}
}
启动应用,用Postman或者curl发个请求试试:
POST /api/react/chat
Content-Type: application/json
{
"question": "北京今天的天气怎么样?然后再计算一下(25加上今天气温的数值)乘以2等于多少?"
}
理想情况下,你应该能在控制台看到类似这样的日志输出:
第1轮回复解析结果:Thought: 用户问了两个问题,先查询天气,再计算。 Action: get_weather, Input: {"city":"北京"}
观察(Observation):城市【北京】的天气情况:晴,温度25℃,湿度60%,东南风2级。
第2轮回复解析结果:Thought: 已获取北京气温为25℃。现在需要计算 (25 + 25) * 2。 Action: calculator, Input: {"expression":"(25+25)*2"}
观察(Observation):表达式 `(25+25)*2` 的计算结果是:100.0
第3轮回复解析结果:Thought: 我已经获得了所有必要信息。 Action: Final Answer, Input: 北京今天天气晴朗,气温25℃。根据您的要求,(25 + 25) * 2 的计算结果是100。
最终返回给用户的,就是最后那个 Final Answer 里的内容。
当然,现实很骨感,你可能会遇到各种问题。下面是我踩过的一些坑和解决办法:
问题1:AI不按格式输出,解析失败。
- 原因:提示词不够严格,或者temperature参数太高。
- 解决:反复检查并强化提示词中的格式要求,特别是分隔符
###和关键词(Thought, Action, Observation)。将API调用的temperature参数设为0或接近0的值。你甚至可以在提示词开头加上“你必须严格遵守以下格式,任何偏离都将导致错误。”
问题2:AI在应该调用工具时,直接输出了最终答案。
- 原因:可能是工具描述不够清晰,AI觉得靠自己已有的知识就能回答。
- 解决:在系统指令(
{{instruction}})里明确强调“对于涉及实时信息、计算或外部数据的问题,你必须使用提供的工具。”同时,检查工具的描述是否准确说明了其能力边界。
问题3:循环卡住,一直在调用同一个工具。
- 原因:工具返回的观察结果可能没有提供足够的新信息,或者AI陷入了逻辑循环。
- 解决:在工具返回的观察结果中,提供更结构化、更清晰的信息。如果循环超过一定次数(比如5次)还没有得到Final Answer,可以主动终止并返回一个错误提示,或者将历史记录给用户看,进行人工干预。
问题4:JSON解析出错,因为AI输出的JSON里有换行或多余字符。
- 解决:在解析前,对提取出的JSON字符串进行清洗,比如去除首尾空白、删除JSON标记外的所有内容。使用健壮的JSON库(如Jackson)并做好
try-catch。
调试这个过程有点像训练一个遵守纪律的新手。你需要非常明确、甚至有点啰嗦地告诉它每一步该做什么、该怎么说。一旦它“上了道”,跑起来就会非常顺畅。
7. 进阶优化与生产环境考量
如果上面的基础版本你已经跑通了,恭喜你!但要想用到实际项目里,还有一些地方可以优化,让整个系统更健壮、更高效。
1. 提示词工程优化: 我们现在的提示词是硬编码在Java代码里的,维护起来不方便。更好的做法是把它放到外部配置文件(如YAML)或者数据库中。可以针对不同的任务类型(数据分析、客服、内容生成)准备不同的提示词模板,动态加载。
2. 支持流式输出: 用户不想等整个循环结束才看到答案。Spring AI的 ChatClient 支持流式响应(stream() 方法)。我们可以改造 ReActService,每次AI输出 Thought 或 Action 时,就通过Server-Sent Events (SSE) 或WebSocket推送给前端,让用户看到AI的“思考过程”,体验会好很多。
3. 工具调用的增强:
- 参数验证与转换:现在的工具
call方法直接接收字符串。最好能定义一个强类型的请求对象,在调用前进行验证和类型转换。 - 工具编排:有些复杂任务需要按特定顺序调用多个工具。可以在
ToolManager里加入简单的编排逻辑,或者利用更高级的框架(如LangChain4J)的Chain功能。 - 工具结果后处理:工具返回的原始数据(比如一大段JSON)直接塞给AI可能效果不好。可以写一个后处理器,将原始结果总结、提炼成更精炼的文本,再作为
Observation。
4. 对话历史管理与上下文长度: DeepSeek R1有上下文长度限制。我们的ReAct循环会在历史中附加很多轮消息,很容易超限。需要实现一个“摘要”功能,当历史消息太长时,将早期的“思考-行动-观察”循环压缩成一段简短的摘要,只保留最近几轮和关键的观察结果。
5. 加入Fallback机制: 如果我们的手动ReAct解析连续失败,或者AI始终不按格式输出,应该有一个降级方案。比如,直接回退到普通的ChatCompletion模式,告诉用户“暂时无法使用工具,但我可以尝试直接回答您的问题”。
6. 监控与日志: 在生产环境,详细的日志至关重要。要记录每一轮循环的输入输出、工具调用耗时、解析成功与否。这不仅能帮你快速定位问题,还能用来分析AI在哪些情况下容易“不听话”,从而进一步优化提示词。
手动实现DeepSeek R1的工具调用,一开始会觉得有点绕,但一旦你把整个流程串起来,会发现思路其实非常清晰。它让你对AI如何与外部世界交互有了更底层的理解。虽然等官方支持后,我们大概率会切换到更优雅的原生工具调用方式,但这段手动实现的经历,对于理解ReAct框架和LLM的工作机制,绝对是宝贵的实战经验。至少下次遇到类似需求,你心里有底了。
更多推荐

所有评论(0)