1,定义长期会话管理类
- package com.jinhei;
-
- import java.io.*;
- import java.nio.file.Files;
- import java.nio.file.Paths;
- import java.time.LocalDateTime;
- import java.time.format.DateTimeFormatter;
- import java.util.ArrayList;
- import java.util.List;
-
- /**
- * 长期记忆管理类
- * 用于存储和读取 AI 的对话历史与工具调用记录
- * 就像给 AI 配备了一个"笔记本",让它能记住之前发生过的事情
- */
- public class MemoryManager {
-
- // 记忆文件路径 - 记忆保存在这里
- private static final String MEMORY_FILE = "agent_memory.json";
-
- // 内存中的记忆列表
- private List<MemoryEntry> memories;
-
- /**
- * 记忆条目 - 每一条记忆的结构
- * 包含:角色(用户/AI)、内容、时间戳
- */
- public static class MemoryEntry {
- private String role; // 角色:user(用户)或 assistant(AI)
- private String content; // 对话内容
- private String timestamp; // 时间戳
- private String toolName; // 使用的工具名称(如果有)
- private String toolResult; // 工具执行结果(如果有)
-
- public MemoryEntry(String role, String content) {
- this.role = role;
- this.content = content;
- this.timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
- }
-
- // Getter 和 Setter 方法
- public String getRole() { return role; }
- public void setRole(String role) { this.role = role; }
-
- public String getContent() { return content; }
- public void setContent(String content) { this.content = content; }
-
- public String getTimestamp() { return timestamp; }
- public void setTimestamp(String timestamp) { this.timestamp = timestamp; }
-
- public String getToolName() { return toolName; }
- public void setToolName(String toolName) { this.toolName = toolName; }
-
- public String getToolResult() { return toolResult; }
- public void setToolResult(String toolResult) { this.toolResult = toolResult; }
- }
-
- /**
- * 构造函数 - 创建记忆管理器时自动加载已有记忆
- */
- public MemoryManager() {
- this.memories = new ArrayList<>();
- loadMemories(); // 从文件加载记忆
- }
-
- /**
- * 从文件加载记忆
- */
- public void loadMemories() {
- File file = new File(MEMORY_FILE);
- if (!file.exists()) {
- System.out.println("[记忆] 未发现历史记忆文件,从空开始");
- return;
- }
-
- try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
- StringBuilder content = new StringBuilder();
- String line;
- while ((line = reader.readLine()) != null) {
- content.append(line);
- }
-
- // 简单的 JSON 解析(不使用额外库)
- parseMemoriesFromJson(content.toString());
- System.out.println("[记忆] 已加载 " + memories.size() + " 条历史记忆");
- } catch (IOException e) {
- System.err.println("[记忆] 加载失败:" + e.getMessage());
- }
- }
-
- /**
- * 从 JSON 字符串解析记忆
- * @param json JSON 格式的记忆数据
- */
- private void parseMemoriesFromJson(String json) {
- memories.clear();
-
- // 简单的 JSON 数组解析
- json = json.trim();
- if (!json.startsWith("[") || !json.endsWith("]")) {
- return;
- }
-
- // 移除首尾的 [ ]
- json = json.substring(1, json.length() - 1).trim();
-
- // 分割每个对象
- String[] objects = json.split("\\},\\s*\\{");
-
- for (String obj : objects) {
- obj = obj.trim();
- if (!obj.startsWith("{")) {
- obj = "{" + obj;
- }
- if (!obj.endsWith("}")) {
- obj = obj + "}";
- }
-
- // 提取各个字段
- String role = extractJsonValue(obj, "role");
- String content = extractJsonValue(obj, "content");
- String timestamp = extractJsonValue(obj, "timestamp");
- String toolName = extractJsonValue(obj, "toolName");
- String toolResult = extractJsonValue(obj, "toolResult");
-
- if (role != null && content != null) {
- MemoryEntry entry = new MemoryEntry(role, content);
- if (timestamp != null) entry.setTimestamp(timestamp);
- if (toolName != null) entry.setToolName(toolName);
- if (toolResult != null) entry.setToolResult(toolResult);
- memories.add(entry);
- }
- }
- }
-
- /**
- * 添加用户消息到记忆中
- * @param message 用户说的话
- */
- public void addUserMessage(String message) {
- memories.add(new MemoryEntry("user", message));
- System.out.println("[记忆] 已记录用户消息:" + message);
- }
-
- /**
- * 添加 AI 回复到记忆中
- * @param message AI 的回答
- * @param toolName 使用的工具名称(如果没有则为 null)
- * @param toolResult 工具执行结果(如果没有则为 null)
- */
- public void addAssistantMessage(String message, String toolName, String toolResult) {
- MemoryEntry entry = new MemoryEntry("assistant", message);
- if (toolName != null) {
- entry.setToolName(toolName);
- }
- if (toolResult != null) {
- entry.setToolResult(toolResult);
- }
- memories.add(entry);
- System.out.println("[记忆] 已记录 AI 回复:" + message);
- }
-
- /**
- * 获取最近的 N 条记忆
- * @param limit 限制数量
- * @return 最近的记忆列表
- */
- public List<MemoryEntry> getRecentMemories(int limit) {
- if (memories.size() <= limit) {
- return memories;
- }
- return memories.subList(memories.size() - limit, memories.size());
- }
-
- /**
- * 将记忆保存到文件
- * 使用 JSON 格式存储,方便读取和管理
- */
- public void saveMemories() {
- try (FileWriter writer = new FileWriter(MEMORY_FILE)) {
- StringBuilder jsonBuilder = new StringBuilder("[\n");
-
- for (int i = 0; i < memories.size(); i++) {
- MemoryEntry entry = memories.get(i);
- jsonBuilder.append(" {\n");
- jsonBuilder.append(" "role": "").append(escapeJson(entry.getRole())).append("",\n");
- jsonBuilder.append(" "content": "").append(escapeJson(entry.getContent())).append("",\n");
- jsonBuilder.append(" "timestamp": "").append(escapeJson(entry.getTimestamp())).append("",\n");
-
- if (entry.getToolName() != null) {
- jsonBuilder.append(" "toolName": "").append(escapeJson(entry.getToolName())).append("",\n");
- }
- if (entry.getToolResult() != null) {
- jsonBuilder.append(" "toolResult": "").append(escapeJson(entry.getToolResult())).append("",\n");
- }
-
- // 移除最后一个逗号
- String lastLine = jsonBuilder.substring(jsonBuilder.lastIndexOf(","));
- jsonBuilder.delete(jsonBuilder.length() - 2, jsonBuilder.length());
- jsonBuilder.append("\n }");
-
- if (i < memories.size() - 1) {
- jsonBuilder.append(",");
- }
- jsonBuilder.append("\n");
- }
-
- jsonBuilder.append("]");
- writer.write(jsonBuilder.toString());
- System.out.println("[记忆] 已保存 " + memories.size() + " 条记忆到文件");
- } catch (IOException e) {
- System.err.println("[记忆] 保存失败:" + e.getMessage());
- }
- }
-
- /**
- * 从 JSON 对象中提取指定字段的值
- * @param json JSON 对象字符串
- * @param key 字段名
- * @return 字段值
- */
- private String extractJsonValue(String json, String key) {
- String searchKey = """ + key + """;
- int keyIndex = json.indexOf(searchKey);
- if (keyIndex == -1) {
- return null;
- }
-
- int colonIndex = json.indexOf(":", keyIndex);
- if (colonIndex == -1) {
- return null;
- }
-
- // 跳过冒号后的空格
- int startIndex = colonIndex + 1;
- while (startIndex < json.length() && Character.isWhitespace(json.charAt(startIndex))) {
- startIndex++;
- }
-
- if (startIndex >= json.length()) {
- return null;
- }
-
- // 检查是否是字符串值
- if (json.charAt(startIndex) == '"') {
- int endIndex = json.indexOf(""", startIndex + 1);
- if (endIndex == -1) {
- return null;
- }
- return json.substring(startIndex + 1, endIndex);
- }
-
- return null;
- }
-
- /**
- * 转义 JSON 特殊字符
- * @param text 原始文本
- * @return 转义后的文本
- */
- private String escapeJson(String text) {
- if (text == null) return "";
- return text.replace("\", "\\\")
- .replace(""", "\\"")
- .replace("\n", "\\n")
- .replace("\r", "\\r")
- .replace("\t", "\\t");
- }
-
- /**
- * 获取记忆统计信息
- * @return 统计信息字符串
- */
- public String getMemoryStats() {
- int userMessages = 0;
- int assistantMessages = 0;
- int toolCalls = 0;
-
- for (MemoryEntry entry : memories) {
- if ("user".equals(entry.getRole())) {
- userMessages++;
- } else if ("assistant".equals(entry.getRole())) {
- assistantMessages++;
- if (entry.getToolName() != null) {
- toolCalls++;
- }
- }
- }
-
- return String.format("记忆统计:共 %d 条,用户消息 %d 条,AI 回复 %d 条,工具调用 %d 次",
- memories.size(), userMessages, assistantMessages, toolCalls);
- }
- }
复制代码 2,实现会话记忆
- package com.jinhei;
-
- import com.alibaba.fastjson2.JSONObject;
- import com.openai.client.OpenAIClient;
- import com.openai.client.okhttp.OpenAIOkHttpClient;
- import com.openai.models.chat.completions.ChatCompletion;
- import com.openai.models.chat.completions.ChatCompletionCreateParams;
-
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.HashMap;
- import java.util.List;
-
- /**
- * 调用模型对话
- */
- public class AiChat {
-
- private static final String TEMPLATE = """
- 你是一位能力强大的 AI 助手,擅长通过逻辑推理与调用工具来解决问题。
-
- 你可以使用的工具如下:
- {tools}
-
- **重要:你必须严格按照以下 JSON 格式返回工具调用请求:**
- ```json
- {
- "toolName": "工具名称",
- "params": "{参数 JSON 字符串}"
- }
- ```
-
- 注意:
- 1. 只能返回上述 JSON 格式,不要添加任何解释文字
- 2. params 字段必须是字符串格式的 JSON
- 3. 不要使用代码块标记
-
- 对话历史:
- {memory}
-
- 当前用户的问题是:
- {input}
- """;
-
- public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
-
- // 第0步:准备工作 - 注册可用的工具
- // 创建一个工具注册表(就像一个工具箱),记录每个工具的名称和对应的方法
- // key: 工具名称(AI调用时使用)
- // value: 实际执行的Java方法(反射调用)
- // 通俗理解:建立一张"工具对照表"
- // 当AI说"我要用putFile工具"时,程序就知道要调用AgentTools类的putFile方法
- HashMap<String, Method> tools = new HashMap<>();
- tools.put("putFile", AgentTools.class.getMethod("putFile", String.class));
- tools.put("getFile", AgentTools.class.getMethod("getFile", String.class));
- tools.put("batchPutFile", AgentTools.class.getMethod("batchPutFile", String.class));
-
- // 第 0.5 步:创建记忆管理器 - 加载长期记忆
- MemoryManager memoryManager = new MemoryManager();
- System.out.println(memoryManager.getMemoryStats());
-
- //第1步:创建AI客户端 - 建立与AI服务的连接通道
- OpenAIClient aiClient = OpenAIOkHttpClient.builder()
- .apiKey(AiConfig.API_KEY) // 设置身份证(API密钥)
- .baseUrl(AiConfig.BASE_URL) // 设置对方地址(服务地址)
- .build(); // 建造完成,电话接通
-
- //第 2 步:准备要问的问题 - 编写聊天内容
- // 2.1 用户的实际问题(原始输入)
- // String promptString = "批量创建 3 个文件到 D:\\ 中,分别是 a.txt 内容为 Hello A, b.txt 内容为 Hello B, c.txt 内容为 Hello C";
- // String promptString = "读取 D:\\a.txt 文件的内容"; // 这个问题 AI 会直接回答,不需要调用工具
- String promptString = "我之前都让你干了哪些事情"; // 这个问题 AI 会直接回答,不需要调用工具
-
- // 2.1.5 记录用户消息到记忆中
- memoryManager.addUserMessage(promptString);
-
- // 2.2 替换模板中的工具占位符
- // ToolUtil.getToolDescription(AgentTools.class) 会返回 AgentTools 类中所有工具的说明
- // 就像把“工具箱清单”填写到工作流程说明书中
- // 告诉 AI:你能用的工具有“写文件”、“读文件”等等...
- String prompt = TEMPLATE.replace("{tools}", ToolUtil.getToolDescription(AgentTools.class));
-
- // 2.2.5 获取最近的记忆并替换到模板中
- // 这样 AI 就能记住之前的对话内容
- String memoryContext = buildMemoryContext(memoryManager.getRecentMemories(10));
- prompt = prompt.replace("{memory}", memoryContext);
-
- // 2.3 替换模板中的用户问题占位符
- // 把用户的具体问题填进去
- // 就像告诉AI:"这是你要完成的具体任务"
- prompt = prompt.replace("{input}", promptString);
-
- //第3步:构建请求参数 - 把问题打包成AI能理解的格式
- ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
- .addUserMessage(prompt) // 添加用户消息:你说的话
- .model(AiConfig.LLM_NAME) // 指定用哪个AI大脑来处理
- .build(); // 打包完成,准备发送
-
- //第4步:发送请求并获取回复 - 把信寄出去,等待回信
- ChatCompletion chatCompletion = aiClient.chat() // 打开聊天功能
- .completions() // 开启补全模式(AI回复)
- .create(params); // 发送请求并等待响应
-
- // 第5步:提取AI的回答 - 拆开回信,取出内容
- // 5.1 获取AI返回的原始消息
- String message = chatCompletion.choices().get(0).message().content().get();
-
- // 5.2 清理消息格式 - 移除 markdown 代码块标记和多余文字
- // AI 可能把工具调用放在代码块中,我们需要提取出纯 JSON
- // 例如:将 ```tool_code {...} ``` 转换成 {...}
- message = message.replace("```tool_code", "");
- message = message.replace("```json", "");
- message = message.replace("```", "");
-
- // 尝试提取第一个 { 到最后一个 } 之间的内容(处理 AI 添加解释文字的情况)
- int firstBrace = message.indexOf('{');
- int lastBrace = message.lastIndexOf('}');
- if (firstBrace != -1 && lastBrace != -1 && lastBrace > firstBrace) {
- message = message.substring(firstBrace, lastBrace + 1);
- }
-
- // 5.3 解析 JSON - 理解 AI 想要调用哪个工具
- // 将清理后的字符串解析成 JSON 对象,提取工具名称和参数
- // 就像读懂 AI 写的"工具使用申请单"
- JSONObject jsonObject = null;
- try {
- jsonObject = JSONObject.parseObject(message);
-
- // 检查是否是有效的工具调用格式(必须包含 toolName 和 params)
- String toolName = jsonObject.getString("toolName");
- String toolParams = jsonObject.getString("params");
-
- if (toolName == null || toolParams == null) {
- // 格式不对,说明不是工具调用
- jsonObject = null;
- }
- } catch (Exception e) {
- // 解析失败,说明 AI 返回的不是 JSON,而是直接回答
- System.out.println("\n[AI 直接回复]\n" + message);
-
- // 记录 AI 的回复到记忆中(没有工具调用)
- memoryManager.addAssistantMessage(message, null, null);
- memoryManager.saveMemories();
- System.out.println(memoryManager.getMemoryStats());
- return; // 直接结束,不执行工具调用
- }
-
- // 能到这里说明是有效的工具调用
- String toolName = jsonObject.getString("toolName");
- String toolParams = jsonObject.getString("params");
-
- // 第 6 步:执行工具调用 - 根据 AI 的指令实际操作
- // 6.1 从工具注册表中找到对应的方法
- // 就像根据工具名称从工具箱里取出对应的工具
- Method toolMethod = tools.get(toolName);
-
- // 6.2 创建工具实例并执行方法
- // 通过反射调用工具方法,就像启动机器开始干活
- // new AgentTools():创建工具对象(准备干活)
- // toolMethod.invoke():执行具体方法(开始干活)
- // toolParams:传入参数(告诉工具要怎么做)
- Object result = toolMethod.invoke(new AgentTools(), toolParams);
-
- // 第 7 步:显示结果 - 把工具执行的结果打印出来
- System.out.println(result);
-
- // 第 8 步:保存记忆 - 将本次对话记录保存到长期记忆中
- // 重要:记录自然语言总结,而不是工具调用 JSON
- String assistantSummary = buildAssistantSummary(toolName, result.toString());
- memoryManager.addAssistantMessage(assistantSummary, toolName, result.toString());
- memoryManager.saveMemories();
- System.out.println(memoryManager.getMemoryStats());
- }
-
- /**
- * 构建 AI 回复的自然语言总结
- * @param toolName 工具名称
- * @param toolResult 工具执行结果
- * @return 自然语言总结
- */
- private static String buildAssistantSummary(String toolName, String toolResult) {
- if ("putFile".equals(toolName)) {
- return "使用写文件工具创建了文件";
- } else if ("getFile".equals(toolName)) {
- return "使用读文件工具读取了文件内容";
- } else if ("batchPutFile".equals(toolName)) {
- // 从结果中提取文件信息
- StringBuilder summary = new StringBuilder("批量创建了以下文件:");
- String[] lines = toolResult.split("\\n");
- for (String line : lines) {
- if (line.contains("成功写入")) {
- // 提取文件名
- int start = line.indexOf("D:");
- if (start != -1) {
- String filePath = line.substring(start).trim();
- summary.append(filePath).append("、");
- }
- }
- }
- // 移除最后一个顿号
- if (summary.length() > 0 && summary.charAt(summary.length() - 1) == '、') {
- summary.setLength(summary.length() - 1);
- }
- return summary.toString();
- }
- return "调用了工具:" + toolName;
- }
-
- /**
- * 构建记忆上下文字符串
- * @param memories 记忆列表
- * @return 格式化的记忆文本
- */
- private static String buildMemoryContext(List<MemoryManager.MemoryEntry> memories) {
- if (memories.isEmpty()) {
- return "(暂无历史对话)";
- }
-
- StringBuilder context = new StringBuilder();
- for (MemoryManager.MemoryEntry entry : memories) {
- String roleText = "user".equals(entry.getRole()) ? "用户" : "AI";
- context.append(String.format("[%s] %s: %s\n",
- entry.getTimestamp(), roleText, entry.getContent()));
-
- // 如果有工具调用信息,也加上
- if (entry.getToolName() != null) {
- context.append(String.format(" → 使用工具:%s\n", entry.getToolName()));
- }
- if (entry.getToolResult() != null) {
- context.append(String.format(" → 工具结果:%s\n", entry.getToolResult()));
- }
- }
- return context.toString();
- }
-
- }
复制代码
aiagent.zip
(19.04 KB, 下载次数: 0, 售价: 50 金豆)
|