Commit 3534a7fe authored by ligaowei's avatar ligaowei

refactor(react): 简化并优化ReAct执行器及回调实现

- 修改ReActAgentProcessor调用接口,简化执行逻辑,去除带Agent参数的方法调用
- 重构DefaultReactCallback,完善工作面板记录逻辑,增加日志记录,区分思考、动作、观察、最终答案步骤
- 优化异常处理,确保工作面板异常时仍记录错误工具调用信息
- 简化DefaultReactExecutor实现,去除冗余注释和过时代码,增强流式执行的日志和错误处理
- 改进流处理过程,基于完整响应解析Thought、Action、Observation和Final Answer段落,避免重复触发步骤
- 增加辅助方法提取工具名称和参数,支持多格式解析
- 优化智能体工具管理,确保DateTimeTools始终存在,移除多余日志
- 提升响应内容存储逻辑,确保最终答案正确触发并保存到历史记忆
- 总体提升代码可读性和维护性,增强日志信息输出便于调试和监控
parent 90b3874a
# Pangea-Agent Timeline方案分析文档
## 1. 概述
Pangea-Agent的Timeline功能是一个实时展示AI代理执行过程的组件,采用前后端分离架构,通过SSE(Server-Sent Events)实现实时事件推送。该功能允许用户实时观察AI代理的思考过程、工具调用和执行结果,增强了AI执行过程的可观察性和透明度。
## 2. 整体架构
### 2.1 前端架构
- **TimelineService**:负责SSE连接管理和事件处理
- **TimelineContainer**:事件容器组件,管理事件数据状态
- **TimelinePanel**:UI展示组件,负责事件的可视化呈现
- **事件类型定义**:定义了多种事件类型(思考、工具调用、工具结果、嵌入内容等)
### 2.2 后端架构
- **TimelineEventController**:提供SSE端点,处理前端订阅请求
- **UserSseService**:管理SSE连接的生命周期和事件广播
- **EventService**:事件构建和发送服务
- **TimelineEventFactory**:事件工厂,负责创建不同类型事件对象
### 2.3 数据流设计
```
Agent执行过程 → EventService → UserSseService → SSE流 → TimelineService → TimelineContainer → TimelinePanel
```
## 3. 前端Timeline组件结构和实现
### 3.1 TimelineService (src/services/TimelineService.ts)
- **职责**:管理SSE连接、处理事件流、实现重连机制
- **核心功能**
- 建立带认证的SSE连接
- 实现消息队列批处理机制,限制队列大小避免内存泄漏
- 实现重连机制,最多重试5次,重试间隔递增
- 处理SSE连接的开/关/错误事件
- 自定义EventSource实现,支持Authorization请求头
### 3.2 TimelineContainer (src/components/TimelineContainer.vue)
- **职责**:事件数据管理、状态维护
- **核心功能**
- 维护事件列表状态
- 提供添加、清除事件方法
- 实现事件监听器(监听window的timeline-event事件)
- 提供性能统计功能(统计各类事件数量和工具执行时间)
- 暴露API供父组件调用
### 3.3 TimelinePanel (src/components/TimelinePanel.vue)
- **职责**:事件UI展示和交互
- **核心功能**
- 可视化展示各类时间轴事件
- 支持内容展开/收起功能
- 针对工具事件显示输入/输出详情
- 支持元数据展示
- 响应式设计适配不同屏幕尺寸
### 3.4 事件类型定义 (src/types/timeline.ts)
定义了完整的事件类型系统,包括:
- `BaseTimelineEvent`:基础事件接口
- `ThoughtEvent`:思考事件
- `ToolCallEvent`:工具调用事件
- `ToolResultEvent`:工具结果事件
- `ToolErrorEvent`:工具错误事件
- `EmbedEvent`:嵌入内容事件
## 4. 后端Timeline事件处理机制
### 4.1 TimelineEventController
- **职责**:提供SSE端点,处理前端订阅请求
- **功能**
- 提供`/api/v1/agent/timeline-events`端点
- 验证用户身份,获取当前用户ID
- 创建并注册SSE连接
- 返回SseEmitter实例
### 4.2 UserSseService
- **职责**:管理SSE连接生命周期和事件广播
- **核心功能**
- 创建和注册SSE连接
- 实现连接回调(完成、错误、超时)
- 管理用户会话映射
- 实现心跳机制,保持连接活跃
- 提供事件发送功能
- 管理连接有效性验证
### 4.3 EventService
- **职责**:事件构建、发送和管理
- **核心功能**
- 构建工作面板事件数据
- 发送事件到SSE连接
- 集成事件去重逻辑
- 事件数据预处理
### 4.4 TimelineEventFactory
- **职责**:根据事件类型创建相应的事件DTO对象
- **功能**
- 支持多种事件类型(thought, tool_call, tool_result, tool_error, embed, log, result等)
- 类型安全的事件对象创建
- 数据类型转换和验证
- 统一的事件创建接口
### 4.5 SseEventBroadcaster
- **职责**:广播事件给所有订阅者
- **功能**
- 批量向所有活跃连接发送事件
- 连接有效性检查
- 异常处理和日志记录
## 5. SSE(Server-Sent Events)流式传输实现
### 5.1 后端SSE实现
**SSE连接管理**
- 使用Spring的`SseEmitter`实现SSE连接
- 设置超时时间为2分钟(120000毫秒),提高连接稳定性
- 实现连接注册和管理机制,将用户ID与SSE连接关联
- 提供连接有效性检查机制
**回调处理**
- **完成回调**:从连接池中移除完成的连接
- **错误回调**:从连接池中移除错误的连接
- **超时回调**:从连接池中移除超时的连接
**心跳机制**
- 使用`ScheduledExecutorService`实现心跳机制
- 定期向客户端发送心跳事件,保持连接活跃
- 防止网络代理或浏览器断开空闲连接
**事件发送**
- 统一的事件发送方法,支持不同类型事件
- 参数验证,确保发送的数据完整有效
- 异常处理,防止连接异常导致服务中断
### 5.2 前端SSE实现
**自定义EventSource**
- 由于标准EventSource不支持Authorization头部,项目实现了自定义的`EventSourceWithAuth`
- 使用XMLHttpRequest实现,支持添加认证头部
- 实现了完整的EventSource接口,包括CONNECTING、OPEN、CLOSED状态
**连接管理**
- 实现重连机制,最多重试5次,重试间隔递增
- 处理连接开/关/错误事件
- 添加连接成功/失败事件到时间轴
**消息处理**
- 实现消息队列批处理机制,限制队列大小为100条
- 批量处理消息以提高性能,每批处理10条
- 消息解析和错误处理
### 5.3 认证集成
- 前端从localStorage获取token
- 将token通过Authorization头部传递
- 后端通过`UserUtils.getCurrentUserId()`获取当前用户ID
- 确保只有认证用户才能订阅时间轴事件
### 5.4 性能优化
- 消息批处理:避免频繁更新UI,提高性能
- 连接池管理:有效管理多个用户连接
- 心跳机制:保持连接活跃,减少重连开销
- 队列大小限制:防止内存泄漏
## 6. Timeline事件类型和数据结构
### 6.1 前端事件类型定义
**基础事件接口**
```typescript
interface BaseTimelineEvent {
type: string;
title: string;
timestamp: number;
metadata?: Record<string, any>;
}
```
**具体事件类型**
1. **ThoughtEvent(思考事件)**
```typescript
interface ThoughtEvent extends BaseTimelineEvent {
content: string;
thinkingType?: string;
}
```
2. **ToolCallEvent(工具调用事件)**:
```typescript
interface ToolCallEvent extends BaseTimelineEvent {
toolName: string;
toolAction?: string;
toolInput?: any;
toolStatus: string;
}
```
3. **ToolResultEvent(工具结果事件)**:
```typescript
interface ToolResultEvent extends BaseTimelineEvent {
toolName: string;
toolAction?: string;
toolOutput?: any;
toolStatus: string;
executionTime?: number;
}
```
4. **ToolErrorEvent(工具错误事件)**:
```typescript
interface ToolErrorEvent extends BaseTimelineEvent {
toolName: string;
errorMessage: string;
errorCode?: string;
}
```
5. **EmbedEvent(嵌入内容事件)**:
```typescript
interface EmbedEvent extends BaseTimelineEvent {
embedUrl: string;
embedType?: string;
embedTitle: string;
embedHtmlContent?: string;
}
```
**事件类型标签映射**:
```typescript
export const eventTypeLabels: Record<string, string> = {
thought: '🧠 思考',
tool_call: '🔧 工具调用',
tool_result: '✅ 工具结果',
tool_error: '❌ 工具错误',
embed: '🌐 网页预览',
log: '📝 日志',
result: '🎯 最终答案',
observation: '🔍 观察'
};
```
### 6.2 后端事件数据结构
**事件工厂模式**
- `TimelineEventFactory`:使用工厂模式创建不同类型的事件对象
- 支持多种事件类型:thought, tool_call, tool_result, tool_error, embed, log, result等
**后端事件DTO**
- `WorkPanelEvent`:基础事件类
- `ThoughtEvent`:思考事件
- `ToolEvent`:工具事件(统一处理调用、结果、错误)
- `EmbedEvent`:嵌入事件
- `LogEvent`:日志事件
- `ResultEvent`:结果事件
**数据转换**
- 前端事件格式通过`ChatArea`组件发送到`TimelineContainer`组件
- 事件数据通过SSE传输,使用JSON格式
- 后端接收Map格式数据,通过事件工厂转换为相应DTO对象
### 6.3 事件数据流向
1. **Agent执行过程** → 产生各种事件数据
2. **ChatArea组件** → 收集事件数据并封装为前端事件格式
3. **TimelineContainer组件** → 通过CustomEvent接收事件
4. **SSE传输** → 后端接收事件,通过事件工厂创建DTO
5. **SSE广播** → 后端将事件推送到前端
6. **TimelineService** → 接收SSE事件并处理
7. **TimelinePanel** → 展示事件到用户界面
### 6.4 事件处理机制
- **类型守卫**:前端使用类型守卫函数验证事件类型
- **事件分发**:通过CustomEvent机制在组件间传递事件
- **数据验证**:后端事件工厂对输入数据进行验证和类型转换
- **元数据支持**:支持携带额外的元数据信息
## 7. 方案优缺点分析
### 7.1 优点
**1. 实时性**
- 采用SSE技术实现服务器到客户端的实时事件推送
- 用户可以实时查看AI代理的执行过程,包括思考、工具调用等步骤
- 提供良好的用户体验,增强对AI执行过程的可观察性
**2. 架构清晰**
- 前后端分离架构,职责明确
- 采用工厂模式创建事件对象,代码结构清晰
- 组件化设计,便于维护和扩展
**3. 容错性强**
- 实现了重连机制,网络异常时可自动重连
- 连接超时处理,避免长时间无效连接
- 消息队列批处理,防止内存泄漏
**4. 用户体验良好**
- 丰富的事件类型,包括思考、工具调用、结果等
- 可视化时间轴展示,直观展现执行过程
- 支持内容展开/收起,便于查看详细信息
- 不同事件类型有不同颜色标识,便于区分
**5. 认证安全**
- 集成用户认证,确保只有认证用户可订阅事件
- 通过Authorization头部传递认证信息
**6. 性能优化**
- 消息批处理机制,提高性能
- 心跳机制保持连接活跃
- 队列大小限制,防止内存问题
### 7.2 缺点
**1. 复杂性较高**
- SSE连接管理相对复杂,需要处理连接状态、心跳、重连等
- 自定义EventSource实现增加了前端复杂度
- 多层服务架构可能导致调试困难
**2. 浏览器兼容性**
- SSE在某些旧版浏览器中可能不被支持
- 需要额外的polyfill或降级方案
**3. 连接资源消耗**
- 每个用户建立独立的SSE连接,大量用户时服务器资源消耗较大
- 长连接可能增加服务器负载
**4. 代码冗余**
- 根据文档显示,系统中存在一些冗余的服务类,如WorkPanelEventSubscriber、SseEventSender等
- 服务层次可能过于复杂,增加了维护难度
**5. 错误处理**
- 在某些异常情况下,SSE连接可能无法正常关闭
- 错误处理逻辑可能需要进一步完善
**6. 扩展性限制**
- 当前设计主要针对单一用户连接,多用户共享场景下可能需要额外设计
- 事件去重逻辑可能影响性能
## 8. 总体评价
Pangea-Agent的Timeline方案在实现AI代理执行过程可视化方面表现出色,提供了实时、直观的执行过程展示。虽然存在一些架构复杂性和资源消耗方面的挑战,但整体方案设计合理,功能完善,为用户提供了良好的AI执行过程观察体验。该方案成功地将AI代理的复杂执行过程转化为用户友好的可视化界面,增强了系统的透明度和可用性。
\ No newline at end of file
package pangea.hiagent.web.repository; package pangea.hiagent;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
......
...@@ -88,7 +88,7 @@ public class ReActAgentProcessor extends BaseAgentProcessor { ...@@ -88,7 +88,7 @@ public class ReActAgentProcessor extends BaseAgentProcessor {
} }
// 使用ReAct执行器执行流程,传递Agent对象以支持记忆功能 // 使用ReAct执行器执行流程,传递Agent对象以支持记忆功能
String finalAnswer = defaultReactExecutor.executeWithAgent(client, userMessage, tools, agent); String finalAnswer = defaultReactExecutor.execute(client, userMessage, tools, agent);
// 将助理回复添加到ChatMemory // 将助理回复添加到ChatMemory
String sessionId = generateSessionId(agent, userId); String sessionId = generateSessionId(agent, userId);
...@@ -139,7 +139,7 @@ public class ReActAgentProcessor extends BaseAgentProcessor { ...@@ -139,7 +139,7 @@ public class ReActAgentProcessor extends BaseAgentProcessor {
} }
// 使用ReAct执行器流式执行流程,传递Agent对象以支持记忆功能 // 使用ReAct执行器流式执行流程,传递Agent对象以支持记忆功能
defaultReactExecutor.executeStreamWithAgent(client, userMessage, tools, tokenConsumer, agent); defaultReactExecutor.executeStream(client, userMessage, tools, tokenConsumer, agent);
} catch (Exception e) { } catch (Exception e) {
agentErrorHandler.handleStreamError(e, tokenConsumer, "流式处理ReAct请求时发生错误"); agentErrorHandler.handleStreamError(e, tokenConsumer, "流式处理ReAct请求时发生错误");
agentErrorHandler.ensureCompletionCallback(tokenConsumer, "处理请求时发生错误: " + e.getMessage()); agentErrorHandler.ensureCompletionCallback(tokenConsumer, "处理请求时发生错误: " + e.getMessage());
......
...@@ -6,78 +6,96 @@ import lombok.extern.slf4j.Slf4j; ...@@ -6,78 +6,96 @@ import lombok.extern.slf4j.Slf4j;
import pangea.hiagent.workpanel.IWorkPanelDataCollector; import pangea.hiagent.workpanel.IWorkPanelDataCollector;
/** /**
* 自定义 ReAct 回调类,用于捕获并处理 ReAct 的每一步思维过程 * 简化的ReAct回调类
* 适配项目现有的 ReAct 实现方式
*/ */
@Slf4j @Slf4j
@Component // 注册为 Spring 组件,方便注入 @Component
public class DefaultReactCallback implements ReactCallback { public class DefaultReactCallback implements ReactCallback {
@Autowired @Autowired
private IWorkPanelDataCollector workPanelCollector; private IWorkPanelDataCollector workPanelCollector;
/**
* ReAct 每执行一个步骤,该方法会被触发
* @param reactStep ReAct 步骤对象,包含步骤的所有核心信息
*/
@Override @Override
public void onStep(ReactStep reactStep) { public void onStep(ReactStep reactStep) {
// 将信息记录到工作面板 log.info("ReAct步骤触发: 类型={}, 内容摘要={}",
reactStep.getStepType(),
reactStep.getContent() != null ?
reactStep.getContent().substring(0, Math.min(50, reactStep.getContent().length())) : "null");
recordReactStepToWorkPanel(reactStep); recordReactStepToWorkPanel(reactStep);
} }
/**
* 处理 ReAct 最终答案步骤
* @param finalAnswer 最终答案
*/
@Override @Override
public void onFinalAnswer(String finalAnswer) { public void onFinalAnswer(String finalAnswer) {
// 创建一个FINAL_ANSWER类型的ReactStep并处理
ReactStep finalStep = new ReactStep(0, ReactStepType.FINAL_ANSWER, finalAnswer); ReactStep finalStep = new ReactStep(0, ReactStepType.FINAL_ANSWER, finalAnswer);
recordReactStepToWorkPanel(finalStep); recordReactStepToWorkPanel(finalStep);
} }
/**
* 将ReAct步骤记录到工作面板
* @param reactStep ReAct步骤
*/
private void recordReactStepToWorkPanel(ReactStep reactStep) { private void recordReactStepToWorkPanel(ReactStep reactStep) {
if (workPanelCollector == null) { if (workPanelCollector == null) {
log.debug("无法记录到工作面板:collector为null");
return; return;
} }
try { try {
switch (reactStep.getStepType()) { switch (reactStep.getStepType()) {
case THOUGHT: case THOUGHT:
workPanelCollector.recordThinking(reactStep.getContent(), "reasoning"); workPanelCollector.recordThinking(reactStep.getContent(), "thought");
log.info("[WorkPanel] 记录思考步骤: {}",
reactStep.getContent().substring(0, Math.min(100, reactStep.getContent().length())));
break; break;
case ACTION: case ACTION:
if (reactStep.getAction() != null) { if (reactStep.getAction() != null) {
// 使用recordToolCallAction记录工具调用开始,状态为pending // 记录工具调用动作
String toolName = reactStep.getAction().getToolName();
Object parameters = reactStep.getAction().getParameters();
// 记录工具调用,初始状态为pending
workPanelCollector.recordToolCallAction( workPanelCollector.recordToolCallAction(
reactStep.getAction().getToolName(), toolName,
reactStep.getAction().getParameters(), parameters,
null, null, // 结果为空
"pending", "pending", // 状态为pending
null null // 错误信息为空
); );
// 同时记录工具调用信息到日志
log.info("[WorkPanel] 记录工具调用: 工具={} 参数={}", toolName, parameters);
} else {
// 如果没有具体的工具信息,记录为一般动作
workPanelCollector.recordThinking(reactStep.getContent(), "action");
log.info("[WorkPanel] 记录动作步骤: {}",
reactStep.getContent().substring(0, Math.min(100, reactStep.getContent().length())));
} }
break; break;
case OBSERVATION: case OBSERVATION:
if (reactStep.getObservation() != null && reactStep.getAction() != null) { if (reactStep.getObservation() != null) {
// 使用recordToolCallAction记录工具调用完成,状态为success // 检查是否有对应的动作信息
if (reactStep.getAction() != null) {
// 使用动作信息更新工具调用结果
workPanelCollector.recordToolCallAction( workPanelCollector.recordToolCallAction(
reactStep.getAction().getToolName(), reactStep.getAction().getToolName(),
reactStep.getAction().getParameters(), reactStep.getAction().getParameters(),
reactStep.getObservation().getContent(), reactStep.getObservation().getContent(),
"success", "success", // 状态为success
null null // 无错误信息
); );
log.info("[WorkPanel] 更新工具调用结果: 工具={} 结果摘要={}",
reactStep.getAction().getToolName(),
reactStep.getObservation().getContent().substring(0, Math.min(50, reactStep.getObservation().getContent().length())));
} else {
// 如果没有动作信息,记录为观察结果
workPanelCollector.recordThinking(reactStep.getContent(), "observation");
log.info("[WorkPanel] 记录观察步骤: {}",
reactStep.getContent().substring(0, Math.min(100, reactStep.getContent().length())));
}
} }
break; break;
case FINAL_ANSWER: case FINAL_ANSWER:
workPanelCollector.recordFinalAnswer(reactStep.getContent()); workPanelCollector.recordFinalAnswer(reactStep.getContent());
// 记录最终答案到日志
log.info("[WorkPanel] 记录最终答案: {}",
reactStep.getContent().substring(0, Math.min(100, reactStep.getContent().length())));
break; break;
default: default:
log.warn("未知的ReAct步骤类型: {}", reactStep.getStepType()); log.warn("未知的ReAct步骤类型: {}", reactStep.getStepType());
...@@ -85,6 +103,20 @@ public class DefaultReactCallback implements ReactCallback { ...@@ -85,6 +103,20 @@ public class DefaultReactCallback implements ReactCallback {
} }
} catch (Exception e) { } catch (Exception e) {
log.error("记录ReAct步骤到工作面板失败", e); log.error("记录ReAct步骤到工作面板失败", e);
// 即使发生异常,也尝试记录错误信息到工作面板
try {
if (reactStep != null && reactStep.getAction() != null) {
workPanelCollector.recordToolCallAction(
reactStep.getAction().getToolName(),
reactStep.getAction().getParameters(),
"记录失败: " + e.getMessage(),
"error",
System.currentTimeMillis() // 使用当前时间戳作为执行时间
);
}
} catch (Exception ex) {
log.error("记录错误信息到工作面板也失败", ex);
}
} }
} }
} }
\ No newline at end of file
...@@ -7,8 +7,6 @@ import org.springframework.ai.chat.model.ChatResponse; ...@@ -7,8 +7,6 @@ import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.context.annotation.Lazy;
import pangea.hiagent.workpanel.IWorkPanelDataCollector;
import pangea.hiagent.agent.service.ErrorHandlerService; import pangea.hiagent.agent.service.ErrorHandlerService;
import pangea.hiagent.agent.service.TokenConsumerWithCompletion; import pangea.hiagent.agent.service.TokenConsumerWithCompletion;
import pangea.hiagent.memory.MemoryService; import pangea.hiagent.memory.MemoryService;
...@@ -21,38 +19,81 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -21,38 +19,81 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
* 重构后的默认ReAct执行器实现 - 支持真正的流式输出和完整的ReAct流程可观测性 * 简化的默认ReAct执行器实现
*/ */
@Slf4j @Slf4j
@Service @Service
public class DefaultReactExecutor implements ReactExecutor { public class DefaultReactExecutor implements ReactExecutor {
// 默认系统提示词 - 遵循Spring AI工具调用规范
private static final String DEFAULT_SYSTEM_PROMPT = private static final String DEFAULT_SYSTEM_PROMPT =
"You are a helpful AI assistant with access to tools. \n\n" + "You are a powerful AI assistant powered by the ReAct (Reasoning + Acting) framework. Your task is to solve complex user queries by:\n" +
"IMPORTANT: You have the following tools available:\n" + "1. Breaking down problems into clear thinking steps\n" +
"1. getCurrentDateTime - 获取当前日期和时间,格式为 'yyyy-MM-dd HH:mm:ss'\n" + "2. Intelligently selecting and combining the right tools to solve each sub-problem\n" +
"2. getCurrentDate - 获取当前日期,格式为 'yyyy-MM-dd'\n" + "3. Processing tool results and iterating until you reach the final answer\n\n" +
"3. getCurrentTime - 获取当前时间,格式为 'HH:mm:ss'\n" + "=== TOOL SYNERGY STRATEGY ===\n" +
"4. getCurrentTimeMillis - 获取当前时间戳(毫秒数)\n\n" + "You have access to multiple specialized tools. You should automatically think about tool combinations that create value:\n" +
"TOOL CALLING RULES:\n" + "- Chain tools together when one tool's output feeds into another tool's input\n" +
"- When a user asks 'what time is it?', 'what's the current time?', 'what's the time?', '现在几点了?', etc., YOU MUST use getCurrentDateTime or getCurrentTime tool\n" + "- Use text processing tools to prepare data before saving with file tools\n" +
"- When a user asks about the current date, use getCurrentDate tool\n" + "- Use calculation tools within larger workflows to perform computations\n" +
"- When other questions require external information, think about which tool would be most appropriate and use it\n" + "- Combine multiple tools to enrich data and provide better insights\n" +
"- Always provide your final response directly to the user in natural language\n" + "- Use data extraction tools with analysis tools for comprehensive results\n\n" +
"- Do not output special format markers - just think internally, use tools as needed, and respond naturally to the user\n" + "Examples of tool synergy:\n" +
"- Tool results are automatically available to you after execution\n\n" + "1. Process a file → analyze content with text tools → save results\n" +
"RESPONSE FORMAT:\n" + "2. Get current date/time → combine with formatting tools → format for display\n" +
"Simply answer the user's question using the tool results. Always be helpful and direct."; "3. Extract web content → process text → calculate statistics → generate charts\n" +
"4. Perform calculations → convert to specific format → store results\n\n" +
"=== ReAct THINKING PROCESS ===\n" +
"Follow this step-by-step process for every problem:\n\n" +
"Step 1 - THOUGHT: Analyze the user's query\n" +
" - Break down the problem into sub-tasks\n" +
" - Identify which tools or tool combinations could solve each sub-task\n" +
" - Consider tool synergies: can output from one tool feed into another?\n" +
" - Plan the sequence of tool calls if multiple tools are needed\n\n" +
"Step 2 - ACTION: Execute the planned tools\n" +
" - Call the most appropriate tool(s) based on your analysis\n" +
" - Tools are executed automatically by the Spring AI framework\n" +
" - Multiple tools can be invoked in sequence if needed\n" +
" - Wait for tool execution results before proceeding\n\n" +
"Step 3 - OBSERVATION: Analyze tool results\n" +
" - Examine the data returned from tools\n" +
" - Verify accuracy and completeness\n" +
" - Consider if additional tools are needed to enrich or validate the data\n" +
" - Identify patterns or insights from the results\n\n" +
"Step 4 - FINAL ANSWER: Synthesize and respond\n" +
" - Combine all tool results into a coherent answer\n" +
" - Present information clearly and logically\n" +
" - If a tool combination provided better insights, explain how the tools worked together\n" +
" - Provide actionable conclusions based on the gathered information\n\n" +
"=== RESPONSE FORMAT ===\n" +
"When responding, follow this structure:\n\n" +
"1. Thought: Explain your problem analysis and tool selection strategy\n" +
" - What sub-problems did you identify?\n" +
" - Which tools address each sub-problem?\n" +
" - What is the tool execution sequence?\n\n" +
"2. Action: Describe which tools you're calling and why\n" +
" - Tool_Call: 1.[tool name] → Purpose: [why you're using it]\n" +
" - Tool_Call: 2.[tool name] → Purpose: [how it complements Tool 1]\n" +
" - Tool_Call: N.[tool name] → Purpose: [final enrichment or validation]\n\n" +
"3. Observation: Interpret the results\n" +
" - What did each tool reveal?\n" +
" - How do the tool results relate to each other?\n" +
" - What patterns or insights emerged from combining tools?\n\n" +
"4. Final_Answer: Provide the solution\n" +
" - Clear, natural language answer to the user's question\n" +
" - Highlight insights gained from tool combinations\n" +
" - Offer follow-up insights or recommendations\n\n" +
"=== CRITICAL RULES ===\n" +
"- ALWAYS think about tool combinations first - complex problems usually need multiple tools\n" +
"- DO NOT limit yourself to single tools - maximize tool synergy\n" +
"- ALWAYS wait for actual tool execution results, never make up data\n" +
"- Tools are called automatically by Spring AI - focus on selecting the right tools\n" +
"- Be explicit about your tool selection strategy in the Thought section\n" +
"- If a tool is unavailable, explain what additional capabilities you would need\n" +
"- Maintain conversational tone in Final Answer, not technical tool details";
private final List<ReactCallback> reactCallbacks = new ArrayList<>(); private final List<ReactCallback> reactCallbacks = new ArrayList<>();
private final AtomicInteger stepCounter = new AtomicInteger(0); private final AtomicInteger stepCounter = new AtomicInteger(0);
@Autowired
@Lazy
private IWorkPanelDataCollector workPanelCollector;
@Autowired @Autowired
private DateTimeTools dateTimeTools; private DateTimeTools dateTimeTools;
...@@ -62,16 +103,15 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -62,16 +103,15 @@ public class DefaultReactExecutor implements ReactExecutor {
@Autowired @Autowired
private ErrorHandlerService errorHandlerService; private ErrorHandlerService errorHandlerService;
@Autowired
private TokenTextSegmenter tokenTextSegmenter;
private final AgentToolManager agentToolManager; private final AgentToolManager agentToolManager;
public DefaultReactExecutor(AgentToolManager agentToolManager) { public DefaultReactExecutor(AgentToolManager agentToolManager) {
this.agentToolManager = agentToolManager; this.agentToolManager = agentToolManager;
} }
/**
* 添加ReAct回调
* @param callback ReAct回调
*/
@Override @Override
public void addReactCallback(ReactCallback callback) { public void addReactCallback(ReactCallback callback) {
if (callback != null) { if (callback != null) {
...@@ -79,61 +119,30 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -79,61 +119,30 @@ public class DefaultReactExecutor implements ReactExecutor {
} }
} }
/**
* 执行ReAct流程(同步方式)
* @param chatClient ChatClient实例
* @param userInput 用户输入
* @param tools 工具列表(已弃用,现在通过Agent获取工具)
* @return 最终答案
*/
@Override @Override
public String execute(ChatClient chatClient, String userInput, List<Object> tools) { public String execute(ChatClient chatClient, String userInput, List<Object> tools, Agent agent) {
return executeWithAgent(chatClient, userInput, tools, null);
}
/**
* 执行ReAct流程(同步方式)- 支持Agent配置
* @param chatClient ChatClient实例
* @param userInput 用户输入
* @param tools 工具列表(已弃用,现在通过Agent获取工具)
* @param agent Agent对象(可选)
* @return 最终答案
*/
public String executeWithAgent(ChatClient chatClient, String userInput, List<Object> tools, Agent agent) {
log.info("开始执行ReAct流程,用户输入: {}", userInput); log.info("开始执行ReAct流程,用户输入: {}", userInput);
// 重置步骤计数器
stepCounter.set(0); stepCounter.set(0);
// 获取Agent关联的工具实例
List<Object> agentTools = getAgentTools(agent); List<Object> agentTools = getAgentTools(agent);
try { try {
// 触发思考步骤
triggerThinkStep("开始处理用户请求: " + userInput); triggerThinkStep("开始处理用户请求: " + userInput);
// 构建Prompt,包含历史对话记录
Prompt prompt = buildPromptWithHistory(DEFAULT_SYSTEM_PROMPT, userInput, agent); Prompt prompt = buildPromptWithHistory(DEFAULT_SYSTEM_PROMPT, userInput, agent);
// 使用call()获取完整的LLM响应
// 这会阻塞直到完成整个ReAct循环(思考→行動→观察→...→最终答案)
log.info("使用call()方法处理ReAct流程,确保完整的工具调用循环");
ChatResponse response = chatClient.prompt(prompt) ChatResponse response = chatClient.prompt(prompt)
.tools(agentTools.toArray()) .tools(agentTools.toArray())
.call() .call()
.chatResponse(); .chatResponse();
// 获取响应文本
String responseText = response.getResult().getOutput().getText(); String responseText = response.getResult().getOutput().getText();
// 触发观察步骤
triggerObservationStep(responseText); triggerObservationStep(responseText);
// 返回最终结果
log.info("最终答案: {}", responseText); log.info("最终答案: {}", responseText);
// 触发最终答案步骤
triggerFinalAnswerStep(responseText); triggerFinalAnswerStep(responseText);
return responseText; return responseText;
...@@ -144,98 +153,65 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -144,98 +153,65 @@ public class DefaultReactExecutor implements ReactExecutor {
} }
/** /**
* 处理ReAct执行错误 * 处理ReAct执行过程中发生的错误
* @param e 异常 *
* @return 错误信息 * @param e 发生的异常
* @return 错误处理结果
*/ */
private String handleReActError(Exception e) { private String handleReActError(Exception e) {
return errorHandlerService.handleSyncError(e, "处理ReAct请求时发生错误"); return errorHandlerService.handleSyncError(e, "处理ReAct请求时发生错误");
} }
/** /**
* 构建包含历史对话记录的Prompt * 构建带有历史记录的提示词
*
* @param systemPrompt 系统提示词 * @param systemPrompt 系统提示词
* @param userInput 用户输入 * @param userInput 用户输入
* @param agent Agent对象(可选) * @param agent 智能体对象
* @return 构建的Prompt * @return 构建好的提示词对象
*/ */
private Prompt buildPromptWithHistory(String systemPrompt, String userInput, Agent agent) { private Prompt buildPromptWithHistory(String systemPrompt, String userInput, Agent agent) {
List<org.springframework.ai.chat.messages.Message> messages = new ArrayList<>(); List<org.springframework.ai.chat.messages.Message> messages = new ArrayList<>();
// 添加系统消息
messages.add(new SystemMessage(systemPrompt)); messages.add(new SystemMessage(systemPrompt));
// 如果提供了Agent,添加历史对话记录
if (agent != null) { if (agent != null) {
try { try {
// 生成会话ID
String sessionId = memoryService.generateSessionId(agent); String sessionId = memoryService.generateSessionId(agent);
// 获取历史记录长度配置,默认为10
int historyLength = agent.getHistoryLength() != null ? agent.getHistoryLength() : 10; int historyLength = agent.getHistoryLength() != null ? agent.getHistoryLength() : 10;
// 获取历史消息
List<org.springframework.ai.chat.messages.Message> historyMessages = List<org.springframework.ai.chat.messages.Message> historyMessages =
memoryService.getHistoryMessages(sessionId, historyLength); memoryService.getHistoryMessages(sessionId, historyLength);
// 添加历史消息到Prompt
messages.addAll(historyMessages); messages.addAll(historyMessages);
// 将当前用户消息添加到内存中,以便下次对话使用
memoryService.addUserMessageToMemory(sessionId, userInput); memoryService.addUserMessageToMemory(sessionId, userInput);
} catch (Exception e) { } catch (Exception e) {
log.warn("获取历史对话记录时发生错误: {}", e.getMessage()); log.warn("获取历史对话记录时发生错误: {}", e.getMessage());
} }
} }
// 添加当前用户消息到Prompt
messages.add(new UserMessage(userInput)); messages.add(new UserMessage(userInput));
return new Prompt(messages); return new Prompt(messages);
} }
/**
* 流式执行ReAct流程 - 使用真正的流式处理机制
*
* @param chatClient ChatClient实例
* @param userInput 用户输入
* @param tools 工具列表(已弃用,现在通过Agent获取工具)
* @param tokenConsumer token处理回调函数
*/
@Override @Override
public void executeStream(ChatClient chatClient, String userInput, List<Object> tools, Consumer<String> tokenConsumer) { public void executeStream(ChatClient chatClient, String userInput, List<Object> tools, Consumer<String> tokenConsumer, Agent agent) {
executeStreamWithAgent(chatClient, userInput, tools, tokenConsumer, null);
}
/**
* 流式执行ReAct流程 - 使用真正的流式处理机制(支持Agent配置)
*
* @param chatClient ChatClient实例
* @param userInput 用户输入
* @param tools 工具列表(已弃用,现在通过Agent获取工具)
* @param tokenConsumer token处理回调函数
* @param agent Agent对象(可选)
*/
public void executeStreamWithAgent(ChatClient chatClient, String userInput, List<Object> tools, Consumer<String> tokenConsumer, Agent agent) {
log.info("使用stream()方法处理ReAct流程,支持真正的流式输出"); log.info("使用stream()方法处理ReAct流程,支持真正的流式输出");
// 重置步骤计数器
stepCounter.set(0); stepCounter.set(0);
// 获取Agent关联的工具实例
List<Object> agentTools = getAgentTools(agent); List<Object> agentTools = getAgentTools(agent);
// 使用StringBuilder累积完整响应
StringBuilder fullResponse = new StringBuilder(); StringBuilder fullResponse = new StringBuilder();
try { try {
// 触发思考步骤
triggerThinkStep("开始处理用户请求: " + userInput); triggerThinkStep("开始处理用户请求: " + userInput);
// 构建Prompt,包含历史对话记录
Prompt prompt = buildPromptWithHistory(DEFAULT_SYSTEM_PROMPT, userInput, agent); Prompt prompt = buildPromptWithHistory(DEFAULT_SYSTEM_PROMPT, userInput, agent);
// 订阅流式响应
chatClient.prompt(prompt) chatClient.prompt(prompt)
.tools(agentTools.toArray()) .tools(agentTools.toArray())
.stream() .stream()
...@@ -253,32 +229,29 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -253,32 +229,29 @@ public class DefaultReactExecutor implements ReactExecutor {
} }
/** /**
* 处理token响应 * 处理流式响应中的单个token
* *
* @param chatResponse 聊天响应 * @param chatResponse 聊天响应对象
* @param tokenConsumer token处理回调函数 * @param tokenConsumer token消费者
* @param fullResponse 完整响应构建器 * @param fullResponse 完整响应构建器
*/ */
private void handleTokenResponse(org.springframework.ai.chat.model.ChatResponse chatResponse, Consumer<String> tokenConsumer, StringBuilder fullResponse) { private void handleTokenResponse(org.springframework.ai.chat.model.ChatResponse chatResponse, Consumer<String> tokenConsumer, StringBuilder fullResponse) {
try { try {
// 获取token
String token = chatResponse.getResult().getOutput().getText(); String token = chatResponse.getResult().getOutput().getText();
// 验证token是否有效
if (isValidToken(token)) { if (isValidToken(token)) {
// 累积完整响应
fullResponse.append(token); fullResponse.append(token);
// 分析token内容,识别工具调用和结果 // analyzeAndRecordToolEvents(token, fullResponse.toString());
analyzeAndRecordToolEvents(token, fullResponse.toString());
// 实时发送token给客户端
if (tokenConsumer != null) { if (tokenConsumer != null) {
tokenConsumer.accept(token); tokenConsumer.accept(token);
} }
// 记录思考过程 tokenTextSegmenter.inputChar(token);
processTokenForSteps(token);
// 改进:在流式处理过程中实时解析关键词
// processTokenForStepsWithFullResponse(token, fullResponse.toString());
} }
} catch (Exception e) { } catch (Exception e) {
log.error("处理token时发生错误", e); log.error("处理token时发生错误", e);
...@@ -286,35 +259,52 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -286,35 +259,52 @@ public class DefaultReactExecutor implements ReactExecutor {
} }
/** /**
* 处理流式完成 * 处理流式响应完成事件
* *
* @param tokenConsumer token处理回调函数 * @param tokenConsumer token消费者
* @param fullResponse 完整响应构建器 * @param fullResponse 完整响应内容
* @param agent Agent对象 * @param agent 智能体对象
*/ */
private void handleStreamCompletion(Consumer<String> tokenConsumer, StringBuilder fullResponse, Agent agent) { private void handleStreamCompletion(Consumer<String> tokenConsumer, StringBuilder fullResponse, Agent agent) {
try { try {
log.info("流式处理完成"); log.info("流式处理完成");
// 触发最终答案步骤
triggerFinalAnswerStep(fullResponse.toString());
// 将助理回复添加到ChatMemory // 检查是否已经处理了Final Answer,如果没有,则将整个响应作为最终答案
saveAssistantResponseToMemory(agent, fullResponse.toString()); String responseStr = fullResponse.toString();
if (!hasFinalAnswerBeenTriggered(responseStr)) {
triggerFinalAnswerStep(responseStr);
}
saveAssistantResponseToMemory(agent, responseStr);
// 发送完成事件,包含完整内容 sendCompletionEvent(tokenConsumer, responseStr);
sendCompletionEvent(tokenConsumer, fullResponse.toString());
} catch (Exception e) { } catch (Exception e) {
log.error("处理流式完成回调时发生错误", e); log.error("处理流式完成回调时发生错误", e);
// 即使在完成回调中出现错误,也要确保标记完成
handleCompletionError(tokenConsumer, e); handleCompletionError(tokenConsumer, e);
} }
} }
/** /**
* 保存助理回复到内存 * 检查是否已经触发了Final Answer步骤
* *
* @param agent Agent对象 * @param fullResponse 完整响应内容
* @param response 助理回复 * @return 如果已经触发了Final Answer则返回true,否则返回false
*/
private boolean hasFinalAnswerBeenTriggered(String fullResponse) {
String[] finalAnswerPatterns = {"Final Answer:", "final answer:", "FINAL ANSWER:", "Final_Answer:", "final_answer:", "FINAL_ANSWER:", "最终答案:"};
for (String pattern : finalAnswerPatterns) {
if (fullResponse.toLowerCase().contains(pattern.toLowerCase())) {
return true;
}
}
return false;
}
/**
* 将助手的回复保存到内存中
*
* @param agent 智能体对象
* @param response 助手的回复内容
*/ */
private void saveAssistantResponseToMemory(Agent agent, String response) { private void saveAssistantResponseToMemory(Agent agent, String response) {
if (agent != null) { if (agent != null) {
...@@ -328,10 +318,10 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -328,10 +318,10 @@ public class DefaultReactExecutor implements ReactExecutor {
} }
/** /**
* 处理完成错误 * 处理完成事件时发生的错误
* *
* @param tokenConsumer token处理回调函数 * @param tokenConsumer token消费者
* @param e 异常 * @param e 发生的异常
*/ */
private void handleCompletionError(Consumer<String> tokenConsumer, Exception e) { private void handleCompletionError(Consumer<String> tokenConsumer, Exception e) {
if (tokenConsumer instanceof TokenConsumerWithCompletion) { if (tokenConsumer instanceof TokenConsumerWithCompletion) {
...@@ -346,37 +336,182 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -346,37 +336,182 @@ public class DefaultReactExecutor implements ReactExecutor {
} }
/** /**
* 检查token是否有效 * 验证token是否有效
* @param token token字符串 *
* @return 是否有效 * @param token 待验证的token
* @return 如果token有效则返回true,否则返回false
*/ */
private boolean isValidToken(String token) { private boolean isValidToken(String token) {
return token != null && !token.isEmpty(); return token != null && !token.isEmpty();
} }
/** /**
* 处理token以识别不同的步骤类型 * 基于完整响应进行关键词解析
* 由于新的提示词不再使用格式化前缀,此方法主要用于触发思考和最终答案步骤 *
* @param token token字符串 * @param token 当前token
* @param fullResponse 完整响应内容
*/ */
private void processTokenForSteps(String token) { private void processTokenForStepsWithFullResponse(String token, String fullResponse) {
// 参数验证 if (fullResponse == null || fullResponse.isEmpty()) {
if (token == null || token.isEmpty()) {
log.debug("接收到空的token,跳过处理");
return; return;
} }
// 由于新提示词不使用格式化前缀,这里只检测是否包含最终答案的关键词 // 检查并处理Thought部分
if (token.toLowerCase().contains("final answer") || token.toLowerCase().contains("最终答案")) { processThoughtSection(fullResponse);
triggerFinalAnswerStep(token);
// 检查并处理Action部分
processActionSection(fullResponse);
// 检查并处理Observation部分
processObservationSection(fullResponse);
// 检查并处理Final Answer部分
processFinalAnswerSection(fullResponse);
}
/**
* 处理Thought部分
*
* @param fullResponse 完整响应内容
*/
private void processThoughtSection(String fullResponse) {
// 检查是否包含Thought标记
String[] thoughtPatterns = {"Thought:", "thought:", "THOUGHT:"};
for (String pattern : thoughtPatterns) {
if (fullResponse.toLowerCase().contains(pattern.toLowerCase())) {
// 检查是否已经有对应的步骤被触发,避免重复触发
if (!isStepAlreadyTriggered(ReactStepType.THOUGHT, fullResponse, pattern)) {
String content = extractContentAfterKeyword(fullResponse, pattern);
// 如果内容中包含下一个步骤的关键词,则只提取到下一个关键词之前的内容
String[] nextKeywords = {"Action:", "Observation:", "Final Answer:"};
for (String nextKeyword : nextKeywords) {
int nextKeywordIndex = content.toLowerCase().indexOf(nextKeyword.toLowerCase());
if (nextKeywordIndex != -1) {
content = content.substring(0, nextKeywordIndex).trim();
break;
}
}
if (!content.isEmpty()) {
triggerThinkStep(content);
}
}
break; // 找到一个就跳出
}
}
}
/**
* 处理Action部分
*
* @param fullResponse 完整响应内容
*/
private void processActionSection(String fullResponse) {
// 检查是否包含Action标记
String[] actionPatterns = {"Action:", "action:", "ACTION:"};
for (String pattern : actionPatterns) {
if (fullResponse.toLowerCase().contains(pattern.toLowerCase())) {
if (!isStepAlreadyTriggered(ReactStepType.ACTION, fullResponse, pattern)) {
String content = extractContentAfterKeyword(fullResponse, pattern);
String[] nextKeywords = {"Observation:", "Thought:", "Final Answer:"};
for (String nextKeyword : nextKeywords) {
int nextKeywordIndex = content.toLowerCase().indexOf(nextKeyword.toLowerCase());
if (nextKeywordIndex != -1) {
content = content.substring(0, nextKeywordIndex).trim();
break;
}
}
// 尝试提取工具名称和参数
String toolName = extractToolName(fullResponse); // 从整个响应中提取,因为可能在Action后有详细信息
Object toolArgs = extractToolArgs(fullResponse);
if (toolName != null && !toolName.isEmpty()) {
triggerActionStep(toolName, "tool_call", toolArgs);
} else { } else {
// 对于其他情况,触发思考步骤 triggerActionStep("unknown", "tool_call", toolArgs);
triggerThinkStep(token); }
}
break;
}
}
}
/**
* 处理Observation部分
*
* @param fullResponse 完整响应内容
*/
private void processObservationSection(String fullResponse) {
// 检查是否包含Observation标记
String[] observationPatterns = {"Observation:", "observation:", "OBSERVATION:"};
for (String pattern : observationPatterns) {
if (fullResponse.toLowerCase().contains(pattern.toLowerCase())) {
if (!isStepAlreadyTriggered(ReactStepType.OBSERVATION, fullResponse, pattern)) {
String content = extractContentAfterKeyword(fullResponse, pattern);
String[] nextKeywords = {"Thought:", "Action:", "Final Answer:"};
for (String nextKeyword : nextKeywords) {
int nextKeywordIndex = content.toLowerCase().indexOf(nextKeyword.toLowerCase());
if (nextKeywordIndex != -1) {
content = content.substring(0, nextKeywordIndex).trim();
break;
}
}
if (!content.isEmpty()) {
triggerObservationStep(content);
}
}
break;
}
}
}
/**
* 处理Final Answer部分
*
* @param fullResponse 完整响应内容
*/
private void processFinalAnswerSection(String fullResponse) {
// 检查是否包含Final Answer标记(包括带下划线和不带下划线的变体)
String[] finalAnswerPatterns = {"Final Answer:", "final answer:", "FINAL ANSWER:", "Final_Answer:", "final_answer:", "FINAL_ANSWER:", "最终答案:"};
for (String pattern : finalAnswerPatterns) {
if (fullResponse.toLowerCase().contains(pattern.toLowerCase())) {
if (!isStepAlreadyTriggered(ReactStepType.FINAL_ANSWER, fullResponse, pattern)) {
String content = extractContentAfterKeyword(fullResponse, pattern);
// Final Answer后的内容即为最终答案,不需要再查找下一个关键词
if (!content.isEmpty()) {
triggerFinalAnswerStep(content);
}
}
break;
}
} }
} }
/** /**
* 处理流式处理错误 * 检查某个步骤是否已经触发过,避免重复触发
*
* @param stepType 步骤类型
* @param fullResponse 完整响应内容
* @param keyword 关键词
* @return 如果步骤已经触发过则返回true,否则返回false
*/
private boolean isStepAlreadyTriggered(ReactStepType stepType, String fullResponse, String keyword) {
// 这里可以根据已有的步骤计数或内容来判断是否已经触发过
// 为了简化,我们使用一个简单的检查:如果关键词在响应中出现多次,但只处理第一次
int firstIndex = fullResponse.toLowerCase().indexOf(keyword.toLowerCase());
int nextIndex = fullResponse.toLowerCase().indexOf(keyword.toLowerCase(), firstIndex + 1);
// 如果没有找到关键词,返回true(无需触发)
if (firstIndex == -1) {
return true; // 没有关键词,无需触发
}
// 简单的检查:如果当前响应中包含关键词,且我们正在处理它,则认为未重复
// 在这个简化版本中,我们假设每次检测到关键词时都处理它
return false;
}
/**
* 处理流式响应中的错误
* *
* @param throwable 异常对象 * @param throwable 异常对象
* @param tokenConsumer token消费者 * @param tokenConsumer token消费者
...@@ -387,66 +522,62 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -387,66 +522,62 @@ public class DefaultReactExecutor implements ReactExecutor {
/** /**
* 发送完成事件 * 发送完成事件
*
* @param tokenConsumer token消费者 * @param tokenConsumer token消费者
* @param fullResponse 完整响应 * @param fullResponse 完整响应内容
*/ */
private void sendCompletionEvent(Consumer<String> tokenConsumer, String fullResponse) { private void sendCompletionEvent(Consumer<String> tokenConsumer, String fullResponse) {
// 参数验证
if (fullResponse == null) { if (fullResponse == null) {
fullResponse = ""; fullResponse = "";
} }
if (tokenConsumer instanceof TokenConsumerWithCompletion) { if (tokenConsumer instanceof TokenConsumerWithCompletion) {
log.debug("调用onComplete,内容长度: {}", fullResponse.length());
((TokenConsumerWithCompletion) tokenConsumer).onComplete(fullResponse); ((TokenConsumerWithCompletion) tokenConsumer).onComplete(fullResponse);
} else if (tokenConsumer != null) { } else if (tokenConsumer != null) {
log.warn("tokenConsumer不是TokenConsumerWithCompletion实例");
tokenConsumer.accept(""); tokenConsumer.accept("");
} }
} }
/** /**
* 分析token内容,识别工具调用和结果 * 分析并记录工具事件
* 由于新的提示词让Spring AI自动处理工具调用,此方法主要记录完整的响应
* *
* @param token 当前token * @param token 当前token
* @param fullResponse 完整响应 * @param fullResponse 完整响应内容
*/ */
private void analyzeAndRecordToolEvents(String token, String fullResponse) { // private void analyzeAndRecordToolEvents(String token, String fullResponse) {
// 由于Spring AI自动处理工具调用,这里可以留空或记录通用信息 // if (token == null || token.isEmpty()) {
// 如果需要记录工具调用,应该在工具实际执行时记录,而不是在响应中解析 // return;
} // }
/** // String lowerToken = token.toLowerCase();
* 检查是否为错误信息
* @param token token字符串
* @return 是否为错误信息
*/
private boolean isError(String token) {
return token != null && (token.contains("Error:") || token.contains("error:") ||
token.contains("Exception:") || token.contains("exception:"));
}
/** // if (lowerToken.startsWith("action:") || (lowerToken.contains("action:") && !fullResponse.contains("Observation:"))) {
* 检查工具名称是否有效 // String toolName = extractToolName(token);
* @param toolName 工具名称 // Object toolArgs = extractToolArgs(token);
* @return 是否有效
*/ // if (toolName != null && !toolName.isEmpty()) {
private boolean isValidToolName(String toolName) { // log.info("[ReAct Action] 检测到工具调用: {} 参数: {}", toolName, toolArgs);
return toolName != null && !toolName.isEmpty(); // }
} // }
// if (lowerToken.startsWith("observation:") || (lowerToken.contains("observation:") && fullResponse.contains("Observation:"))) {
// log.info("[ReAct Observation] 检测到工具结果: {}", token.substring(0, Math.min(100, token.length())));
// }
// if (lowerToken.startsWith("final answer:") || lowerToken.contains("final answer:")) {
// log.info("[ReAct Final Answer] 检测到最终答案");
// }
// }
/** /**
* 从文本中提取工具名称 * 从文本中提取工具名称
* *
* @param text 输入文本 * @param text 待解析的文本
* @return 工具名称,如果未找到则返回null * @return 工具名称,如果未找到则返回null
*/ */
private String extractToolName(String text) { private String extractToolName(String text) {
if (text == null) return null; if (text == null) return null;
// 尝试从ReAct格式中提取工具名称,例如:Action: getCurrentTime 或 Action Input: {}
// 或者从JSON格式中提取,以防万一
String[] patterns = { String[] patterns = {
"Action:\\s*(\\w+)(?:\\(.*?\\))?$", // 匹配Action: toolName格式 "Action:\\s*(\\w+)(?:\\(.*?\\))?$", // 匹配Action: toolName格式
"Action:\\s*(\\w+)$", // 匹配Action: toolName格式 "Action:\\s*(\\w+)$", // 匹配Action: toolName格式
...@@ -467,48 +598,30 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -467,48 +598,30 @@ public class DefaultReactExecutor implements ReactExecutor {
} }
} }
// 尝试从JSON格式中提取
try {
// 查找类似 {"name": "toolName"} 或 {"action": {"name": "toolName"}} 的格式
java.util.regex.Pattern jsonPattern = java.util.regex.Pattern.compile("(?:\"name\"\\s*:\\s*\"(\\w+)|\"action\"\\s*:\\s*\\{[^}]*\"name\"\\s*:\\s*\"(\\w+)\")");
java.util.regex.Matcher jsonMatcher = jsonPattern.matcher(text);
if (jsonMatcher.find()) {
// 返回第一个捕获组,如果为空则返回第二个捕获组
String group1 = jsonMatcher.group(1);
return group1 != null ? group1 : jsonMatcher.group(2);
}
} catch (Exception e) {
log.debug("解析JSON格式工具名称时出错: {}", e.getMessage());
}
return null; return null;
} }
/** /**
* 从文本中提取工具参数 * 从文本中提取工具参数
* *
* @param text 输入文本 * @param text 待解析的文本
* @return 工具参数,如果未找到则返回null * @return 工具参数,如果未找到则返回null
*/ */
private Object extractToolArgs(String text) { private Object extractToolArgs(String text) {
if (text == null) return null; if (text == null) return null;
// 尝试从ReAct格式中提取参数,例如:Action Input: {"parameter": "value"}
try { try {
// 查找Action Input行 java.util.regex.Pattern actionInputPattern = java.util.regex.Pattern.compile("Action Input:\s*(.*)");
java.util.regex.Pattern actionInputPattern = java.util.regex.Pattern.compile("Action Input:\\s*(.*)");
java.util.regex.Matcher actionInputMatcher = actionInputPattern.matcher(text); java.util.regex.Matcher actionInputMatcher = actionInputPattern.matcher(text);
if (actionInputMatcher.find()) { if (actionInputMatcher.find()) {
String inputStr = actionInputMatcher.group(1).trim(); String inputStr = actionInputMatcher.group(1).trim();
if (!inputStr.isEmpty() && inputStr.startsWith("{")) { if (!inputStr.isEmpty() && inputStr.startsWith("{")) {
// 尝试解析为JSON
return parseJsonToMap(inputStr); return parseJsonToMap(inputStr);
} }
} }
// 从JSON格式中提取参数 java.util.regex.Pattern jsonPattern = java.util.regex.Pattern.compile("\\\"arguments\\\"\\s*:\\s*(\\{[^}]*\\})");
java.util.regex.Pattern jsonPattern = java.util.regex.Pattern.compile("\"arguments\"\\s*:\\s*(\\{[^}]+\\})");
java.util.regex.Matcher jsonMatcher = jsonPattern.matcher(text); java.util.regex.Matcher jsonMatcher = jsonPattern.matcher(text);
if (jsonMatcher.find()) { if (jsonMatcher.find()) {
return parseJsonToMap(jsonMatcher.group(1)); return parseJsonToMap(jsonMatcher.group(1));
...@@ -518,23 +631,21 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -518,23 +631,21 @@ public class DefaultReactExecutor implements ReactExecutor {
log.debug("解析工具参数时出错: {}", e.getMessage()); log.debug("解析工具参数时出错: {}", e.getMessage());
} }
// 如果没有找到ReAct格式或JSON格式,返回空参数
java.util.Map<String, Object> args = new java.util.HashMap<>(); java.util.Map<String, Object> args = new java.util.HashMap<>();
return args.isEmpty() ? null : args; return args.isEmpty() ? null : args;
} }
/** /**
* 将JSON字符串解析为Map * 将JSON字符串解析为Map
*
* @param jsonString JSON字符串 * @param jsonString JSON字符串
* @return 解析后的Map * @return 解析后的Map对象
*/ */
private java.util.Map<String, Object> parseJsonToMap(String jsonString) { private java.util.Map<String, Object> parseJsonToMap(String jsonString) {
try { try {
// 简单的JSON解析,实际项目中建议使用Jackson或Gson
jsonString = jsonString.trim().replaceAll("^\\{|\\}$", "").trim(); jsonString = jsonString.trim().replaceAll("^\\{|\\}$", "").trim();
java.util.Map<String, Object> map = new java.util.HashMap<>(); java.util.Map<String, Object> map = new java.util.HashMap<>();
// 简单分割键值对(仅适用于简单情况)
if (!jsonString.isEmpty()) { if (!jsonString.isEmpty()) {
String[] pairs = jsonString.split(","); String[] pairs = jsonString.split(",");
for (String pair : pairs) { for (String pair : pairs) {
...@@ -556,26 +667,47 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -556,26 +667,47 @@ public class DefaultReactExecutor implements ReactExecutor {
} }
/** /**
* 从token中提取工具结果 * 提取关键词后的内容
* *
* @param token 输入token * @param text 原始文本
* @return 工具结果 * @param keyword 关键词
* @return 关键词后的内容
*/ */
private String extractToolResult(String token) { private String extractContentAfterKeyword(String text, String keyword) {
if (token == null) return ""; if (text == null || keyword == null) {
return "";
}
// 找到关键词的位置
int keywordIndex = -1;
String lowerText = text.toLowerCase();
String lowerKeyword = keyword.toLowerCase();
// 首先尝试精确匹配
keywordIndex = lowerText.indexOf(lowerKeyword);
if (keywordIndex == -1) {
// 如果没找到,尝试匹配带空格的版本
keywordIndex = lowerText.indexOf(lowerKeyword.trim());
}
// 简单的结果提取逻辑 if (keywordIndex != -1) {
// 提取冒号后的内容 // 提取关键词后的部分
int colonIndex = token.lastIndexOf(':'); String content = text.substring(keywordIndex + keyword.length()).trim();
if (colonIndex >= 0 && colonIndex < token.length() - 1) {
return token.substring(colonIndex + 1).trim(); // 如果内容以冒号开头,去掉它
if (content.startsWith(":")) {
content = content.substring(1).trim();
}
return content;
} }
return token.trim(); return text.trim();
} }
/** /**
* 触发思考步骤 * 触发思考步骤
*
* @param content 思考内容 * @param content 思考内容
*/ */
private void triggerThinkStep(String content) { private void triggerThinkStep(String content) {
...@@ -586,8 +718,9 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -586,8 +718,9 @@ public class DefaultReactExecutor implements ReactExecutor {
/** /**
* 触发行动步骤 * 触发行动步骤
*
* @param toolName 工具名称 * @param toolName 工具名称
* @param toolAction 工具行动 * @param toolAction 工具操作
* @param toolArgs 工具参数 * @param toolArgs 工具参数
*/ */
private void triggerActionStep(String toolName, String toolAction, Object toolArgs) { private void triggerActionStep(String toolName, String toolAction, Object toolArgs) {
...@@ -602,6 +735,7 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -602,6 +735,7 @@ public class DefaultReactExecutor implements ReactExecutor {
/** /**
* 触发观察步骤 * 触发观察步骤
*
* @param observation 观察内容 * @param observation 观察内容
*/ */
private void triggerObservationStep(String observation) { private void triggerObservationStep(String observation) {
...@@ -616,6 +750,7 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -616,6 +750,7 @@ public class DefaultReactExecutor implements ReactExecutor {
/** /**
* 触发最终答案步骤 * 触发最终答案步骤
*
* @param finalAnswer 最终答案 * @param finalAnswer 最终答案
*/ */
private void triggerFinalAnswerStep(String finalAnswer) { private void triggerFinalAnswerStep(String finalAnswer) {
...@@ -626,6 +761,7 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -626,6 +761,7 @@ public class DefaultReactExecutor implements ReactExecutor {
/** /**
* 通知所有回调 * 通知所有回调
*
* @param reactStep ReAct步骤 * @param reactStep ReAct步骤
*/ */
private void notifyCallbacks(ReactStep reactStep) { private void notifyCallbacks(ReactStep reactStep) {
...@@ -639,57 +775,30 @@ public class DefaultReactExecutor implements ReactExecutor { ...@@ -639,57 +775,30 @@ public class DefaultReactExecutor implements ReactExecutor {
} }
/** /**
* 获取Agent关联的工具实例 * 获取智能体工具
* @param agent Agent对象 *
* @return 工具实例列表 * @param agent 智能体对象
* @return 智能体可用的工具列表
*/ */
private List<Object> getAgentTools(Agent agent) { private List<Object> getAgentTools(Agent agent) {
if (agent == null) { if (agent == null) {
log.warn("Agent is null, returning default tools with DateTimeTools only");
List<Object> defaultTools = new ArrayList<>(); List<Object> defaultTools = new ArrayList<>();
defaultTools.add(dateTimeTools); defaultTools.add(dateTimeTools);
log.info("Agent is null, using default datetime tools. Current tool instance count: {}", defaultTools.size());
return defaultTools; return defaultTools;
} }
try { try {
log.info("======== Start loading tools for Agent '{}' ========", agent.getName());
// Get tool instances associated with Agent
List<Object> tools = agentToolManager.getAvailableToolInstances(agent); List<Object> tools = agentToolManager.getAvailableToolInstances(agent);
log.info("[Tool Manager] Retrieved {} tool instances from Agent", tools.size());
// Must add datetime tools (very important!) if (dateTimeTools != null && !tools.contains(dateTimeTools)) {
if (dateTimeTools != null) {
if (!tools.contains(dateTimeTools)) {
tools.add(dateTimeTools); tools.add(dateTimeTools);
log.info("[DateTime Tool] Successfully added DateTimeTools to tool list (total tools: {})", tools.size());
} else {
log.debug("[DateTime Tool] DateTimeTools already exists in tool list, skip adding");
}
} else {
log.error("[DateTime Tool] DateTimeTools Bean is null, failed to inject. Please check Spring configuration");
}
log.info("======== Final tool instance count: {} ========", tools.size());
// Print details of each tool
for (int i = 0; i < tools.size(); i++) {
Object tool = tools.get(i);
String toolClassName = tool.getClass().getSimpleName();
String toolFullName = tool.getClass().getName();
log.debug(" [Tool #{}] Class name: {} ({})", i + 1, toolClassName, toolFullName);
} }
return tools; return tools;
} catch (Exception e) { } catch (Exception e) {
log.error("[ERROR] Failed to get tool instances for Agent '{}'. Error: {} \nStack Trace:", log.error("获取工具实例时发生错误: {}", e.getMessage());
agent.getName(), e.getMessage(), e);
// On error, at least return datetime tools as fallback
List<Object> fallbackTools = new ArrayList<>(); List<Object> fallbackTools = new ArrayList<>();
fallbackTools.add(dateTimeTools); fallbackTools.add(dateTimeTools);
log.warn("[FALLBACK] Returning only datetime tools due to error. Tool count: {}", fallbackTools.size());
return fallbackTools; return fallbackTools;
} }
} }
......
...@@ -17,19 +17,7 @@ public interface ReactExecutor { ...@@ -17,19 +17,7 @@ public interface ReactExecutor {
* @param tools 工具列表 * @param tools 工具列表
* @return 最终答案 * @return 最终答案
*/ */
String execute(ChatClient chatClient, String userInput, List<Object> tools); String execute(ChatClient chatClient, String userInput, List<Object> tools, Agent agent);
/**
* 执行ReAct流程(同步方式)- 支持Agent配置
* @param chatClient ChatClient实例
* @param userInput 用户输入
* @param tools 工具列表
* @param agent Agent对象(可选)
* @return 最终答案
*/
default String executeWithAgent(ChatClient chatClient, String userInput, List<Object> tools, Agent agent) {
return execute(chatClient, userInput, tools);
}
/** /**
* 流式执行ReAct流程 * 流式执行ReAct流程
...@@ -38,19 +26,7 @@ public interface ReactExecutor { ...@@ -38,19 +26,7 @@ public interface ReactExecutor {
* @param tools 工具列表 * @param tools 工具列表
* @param tokenConsumer token处理回调函数 * @param tokenConsumer token处理回调函数
*/ */
void executeStream(ChatClient chatClient, String userInput, List<Object> tools, Consumer<String> tokenConsumer); void executeStream(ChatClient chatClient, String userInput, List<Object> tools, Consumer<String> tokenConsumer, Agent agent);
/**
* 流式执行ReAct流程 - 支持Agent配置
* @param chatClient ChatClient实例
* @param userInput 用户输入
* @param tools 工具列表
* @param tokenConsumer token处理回调函数
* @param agent Agent对象(可选)
*/
default void executeStreamWithAgent(ChatClient chatClient, String userInput, List<Object> tools, Consumer<String> tokenConsumer, Agent agent) {
executeStream(chatClient, userInput, tools, tokenConsumer);
}
/** /**
* 添加ReAct回调 * 添加ReAct回调
......
package pangea.hiagent.agent.react;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import pangea.hiagent.workpanel.IWorkPanelDataCollector;
@Slf4j
@Component
public class TokenTextSegmenter {
@Autowired
private IWorkPanelDataCollector workPanelCollector;
// 定义分段标识(按出现优先级排序)
private static final String[] SEGMENT_MARKERS = {
"Thought:",
"Action:",
"Observation:",
"Final_Answer:"
};
// 当前缓存的输入字符
private StringBuilder currentInputBuffer;
// 已匹配到的分段标识
private String matchedMarker;
// 分段内容起始索引
private int segmentContentStartIndex;
public TokenTextSegmenter() {
currentInputBuffer = new StringBuilder();
matchedMarker = null;
segmentContentStartIndex = 0;
}
/**
* 逐字输入字符并处理分段
*
* @param inputChar 单个输入字符
* @return 当分割出有效文本段时返回该段内容,否则返回null
*/
public void inputChar(String inputChar) {
// 将字符加入缓存
currentInputBuffer.append(inputChar);
String currentBufferStr = currentInputBuffer.toString();
// 1. 未匹配到标识时,检测是否出现分段标识
if (matchedMarker == null) {
for (String marker : SEGMENT_MARKERS) {
if (currentBufferStr.endsWith(marker)) {
// 匹配到标识,记录信息
matchedMarker = marker;
segmentContentStartIndex = currentBufferStr.length();
// 输出标识本身(可选,根据需求决定是否包含标识)
log.info("【识别到分段标识】: {}", matchedMarker);
}
}
}
// 2. 已匹配到标识,检测是否出现下一个标识(或文本结束)
else {
for (String marker : SEGMENT_MARKERS) {
if (!marker.equals(matchedMarker) && currentBufferStr.contains(marker)) {
// 找到下一个标识,截取当前分段内容
int nextMarkerStartIndex = currentBufferStr.indexOf(marker);
String segmentContent = currentBufferStr.substring(segmentContentStartIndex, nextMarkerStartIndex)
.trim();
// 重置状态,准备处理下一个分段
resetSegmentState(nextMarkerStartIndex);
// 输出当前分段内容
outputSegment(matchedMarker, segmentContent);
}
}
}
}
/**
* 文本输入结束时,处理最后一个分段
*
* @return 最后一个分段的内容,无则返回null
*/
public void finishInput() {
if (matchedMarker != null && segmentContentStartIndex < currentInputBuffer.length()) {
String lastSegmentContent = currentInputBuffer.substring(segmentContentStartIndex).trim();
resetSegmentState(currentInputBuffer.length());
outputSegment(matchedMarker, lastSegmentContent);
}
}
/**
* 重置分段状态
*
* @param newStartIndex 新的起始索引
*/
private void resetSegmentState(int newStartIndex) {
// 保留未处理的部分,用于下一个分段
String remainingStr = currentInputBuffer.substring(newStartIndex);
currentInputBuffer = new StringBuilder(remainingStr);
matchedMarker = null;
segmentContentStartIndex = 0;
}
/**
* 输出分段内容(封装输出逻辑)
*
* @param marker 分段标识
* @param content 分段内容
* @return 格式化后的分段内容
*/
private void outputSegment(String marker, String content) {
log.info("【分段内容】{}: {}", marker, content);
workPanelCollector.addEvent(null);
}
}
...@@ -21,19 +21,18 @@ MERGE INTO agent (id, name, description, status, default_model, owner, system_pr ...@@ -21,19 +21,18 @@ MERGE INTO agent (id, name, description, status, default_model, owner, system_pr
-- 插入Agent和Tool的关联关系 -- 插入Agent和Tool的关联关系
MERGE INTO agent_tool_relation (id, agent_id, tool_id) VALUES MERGE INTO agent_tool_relation (id, agent_id, tool_id) VALUES
('relation-4', 'agent-2', 'tool-1'),
('relation-5', 'agent-2', 'tool-2'), ('relation-5', 'agent-2', 'tool-2'),
('relation-6', 'agent-2', 'tool-5'), ('relation-6', 'agent-2', 'tool-5'),
('relation-7', 'agent-2', 'tool-6'), ('relation-7', 'agent-2', 'tool-6'),
('relation-8', 'agent-3', 'tool-2'), ('relation-8', 'agent-3', 'tool-2'),
('relation-9', 'agent-3', 'tool-7'), ('relation-9', 'agent-3', 'tool-7'),
('relation-10', 'agent-3', 'tool-8'), ('relation-10', 'agent-3', 'tool-8'),
('relation-11', 'agent-4', 'tool-1'),
('relation-12', 'agent-4', 'tool-9'), ('relation-12', 'agent-4', 'tool-9'),
('relation-13', 'agent-4', 'tool-10'), ('relation-13', 'agent-4', 'tool-10'),
('relation-14', 'agent-5', 'tool-1'),
('relation-15', 'agent-5', 'tool-11'), ('relation-15', 'agent-5', 'tool-11'),
('relation-16', 'agent-5', 'tool-12'); ('relation-17', 'agent-2', 'tool-3'),
('relation-18', 'agent-4', 'tool-3'),
('relation-19', 'agent-5', 'tool-3');
-- 插入默认工具数据 -- 插入默认工具数据
MERGE INTO tool (id, name, display_name, description, category, status, bean_name, owner, timeout, http_method, parameters, return_type, return_schema, implementation, api_endpoint, headers, auth_type, auth_config) VALUES MERGE INTO tool (id, name, display_name, description, category, status, bean_name, owner, timeout, http_method, parameters, return_type, return_schema, implementation, api_endpoint, headers, auth_type, auth_config) VALUES
...@@ -48,9 +47,26 @@ MERGE INTO tool (id, name, display_name, description, category, status, bean_nam ...@@ -48,9 +47,26 @@ MERGE INTO tool (id, name, display_name, description, category, status, bean_nam
('tool-9', 'writingStyleReference', '创作风格参考', '提供各种写作风格的参考和指导', 'FUNCTION', 'active', 'writingStyleReferenceTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'), ('tool-9', 'writingStyleReference', '创作风格参考', '提供各种写作风格的参考和指导', 'FUNCTION', 'active', 'writingStyleReferenceTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-10', 'documentTemplate', '文档模板', '提供各种类型的文档模板', 'FUNCTION', 'active', 'documentTemplateTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'), ('tool-10', 'documentTemplate', '文档模板', '提供各种类型的文档模板', 'FUNCTION', 'active', 'documentTemplateTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-11', 'studyPlanGeneration', '学习计划制定', '根据学习目标和时间安排制定个性化的学习计划', 'FUNCTION', 'active', 'studyPlanGenerationTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'), ('tool-11', 'studyPlanGeneration', '学习计划制定', '根据学习目标和时间安排制定个性化的学习计划', 'FUNCTION', 'active', 'studyPlanGenerationTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-12', 'courseMaterialRetrieval', '课程资料检索', '检索和查询相关课程资料', 'FUNCTION', 'active', 'courseMaterialRetrievalTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'); ('tool-12', 'courseMaterialRetrieval', '课程资料检索', '检索和查询相关课程资料', 'FUNCTION', 'active', 'courseMaterialRetrievalTool', 'user-001', 10000, 'GET', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-13', 'emailTools', '邮件工具', '提供基于POP3协议的邮件访问功能', 'FUNCTION', 'active', 'emailTools', 'user-001', 30000, 'POST', '{"defaultPop3Port": 995, "defaultAttachmentPath": "attachments", "pop3SslEnable": true, "pop3SocketFactoryClass": "javax.net.ssl.SSLSocketFactory"}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-14', 'fileProcessing', '文件处理工具', '提供文件读写和管理功能,支持多种文本格式文件', 'FUNCTION', 'active', 'fileProcessingTools', 'user-001', 10000, 'POST', '{"textFileExtensions": ".txt,.md,.java,.html,.htm,.css,.js,.json,.xml,.yaml,.yml,.properties,.sql,.py,.cpp,.c,.h,.cs,.php,.rb,.go,.rs,.swift,.kt,.scala,.sh,.bat,.cmd,.ps1,.log,.csv,.ts,.jsx,.tsx,.vue,.scss,.sass,.less", "imageFileExtensions": ".jpg,.jpeg,.png,.gif,.bmp,.svg,.webp,.ico", "defaultStorageDir": "storage"}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-15', 'hisenseSsoAuth', '海信SSO认证工具', '用于访问需要SSO认证的海信业务系统,自动完成登录并提取页面内容', 'FUNCTION', 'active', 'hisenseSsoAuthTool', 'user-001', 60000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-16', 'oauth2Authorization', 'OAuth2.0授权工具', '支持标准OAuth2.0认证流程,包括密码凭证流、令牌刷新和受保护资源访问', 'FUNCTION', 'active', 'oauth2AuthorizationTool', 'user-001', 30000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-17', 'orderQuery', '订单查询工具', '用于查询客户订单信息', 'FUNCTION', 'active', 'orderQueryTool', 'user-001', 10000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-18', 'playwrightWeb', 'Playwright网页自动化工具', '提供基于Playwright的网页内容抓取、交互操作、截图等功能', 'FUNCTION', 'active', 'playwrightWebTools', 'user-001', 30000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-19', 'refundProcessing', '退款处理工具', '用于处理客户退款申请', 'FUNCTION', 'active', 'refundProcessingTool', 'user-001', 10000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-20', 'storageFileAccess', '存储文件访问工具', '提供访问服务器后端 storage 目录下文件的功能', 'FUNCTION', 'active', 'storageFileAccessTool', 'user-001', 10000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-21', 'stringProcessing', '字符串处理工具', '提供字符串处理和转换功能', 'FUNCTION', 'active', 'stringProcessingTools', 'user-001', 5000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}'),
('tool-22', 'webPageAccess', '网页访问工具', '提供根据网站名称或URL地址访问网页并在工作面板中预览的功能', 'FUNCTION', 'active', 'webPageAccessTools', 'user-001', 30000, 'POST', '{}', 'object', '{}', '', '', '{}', '', '{}');
-- 插入默认工具配置数据 -- 插入默认工具配置数据
MERGE INTO tool_configs (id, tool_name, param_name, param_value, description, default_value, type, required, group_name) VALUES MERGE INTO tool_configs (id, tool_name, param_name, param_value, description, default_value, type, required, group_name) VALUES
('config-1', 'search', 'apiKey', 'test-key-123', '搜索引擎API密钥', '', 'string', 1, 'auth'), ('config-1', 'search', 'apiKey', 'test-key-123', '搜索引擎API密钥', '', 'string', 1, 'auth'),
('config-2', 'search', 'endpoint', 'https://api.search.com/v1/search', '搜索引擎API端点', 'https://api.search.com/v1/search', 'string', 1, 'connection'); ('config-2', 'search', 'endpoint', 'https://api.search.com/v1/search', '搜索引擎API端点', 'https://api.search.com/v1/search', 'string', 1, 'connection'),
\ No newline at end of file ('config-3', 'emailTools', 'defaultPop3Port', '995', '默认POP3服务器端口', '995', 'integer', 1, 'email'),
('config-4', 'emailTools', 'defaultAttachmentPath', 'attachments', '默认附件保存路径', 'attachments', 'string', 1, 'email'),
('config-5', 'emailTools', 'pop3SslEnable', 'true', '是否启用POP3 SSL', 'true', 'boolean', 1, 'email'),
('config-6', 'emailTools', 'pop3SocketFactoryClass', 'javax.net.ssl.SSLSocketFactory', 'POP3 SSL套接字工厂类', 'javax.net.ssl.SSLSocketFactory', 'string', 1, 'email'),
('config-7', 'fileProcessing', 'textFileExtensions', '.txt,.md,.java,.html,.htm,.css,.js,.json,.xml,.yaml,.yml,.properties,.sql,.py,.cpp,.c,.h,.cs,.php,.rb,.go,.rs,.swift,.kt,.scala,.sh,.bat,.cmd,.ps1,.log,.csv,.ts,.jsx,.tsx,.vue,.scss,.sass,.less', '支持的文本文件扩展名,逗号分隔', '.txt,.md,.java,.html,.htm,.css,.js,.json,.xml,.yaml,.yml,.properties,.sql,.py,.cpp,.c,.h,.cs,.php,.rb,.go,.rs,.swift,.kt,.scala,.sh,.bat,.cmd,.ps1,.log,.csv,.ts,.jsx,.tsx,.vue,.scss,.sass,.less', 'string', 1, 'file'),
('config-8', 'fileProcessing', 'imageFileExtensions', '.jpg,.jpeg,.png,.gif,.bmp,.svg,.webp,.ico', '支持的图片文件扩展名,逗号分隔', '.jpg,.jpeg,.png,.gif,.bmp,.svg,.webp,.ico', 'string', 1, 'file'),
('config-9', 'fileProcessing', 'defaultStorageDir', 'storage', '默认文件存储目录', 'storage', 'string', 1, 'file');
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment