Commit 9e67a5c4 authored by Gavin's avatar Gavin

完善ReAct Agent功能:1. 添加enable_react字段到数据库 2. 更新前端界面支持ReAct配置 3. 完善工具调用实现 4. 添加时间工具用于测试

parents
package pangea.hiagent.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import pangea.hiagent.model.Agent;
import pangea.hiagent.model.Tool;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* ReAct Agent服务类
* 负责实现ReAct Agent的核心逻辑
*/
@Slf4j
@Service
public class ReActAgentService {
@Autowired
private ChatModel chatModel;
@Autowired
private ToolService toolService;
@Autowired
private AgentService agentService;
/**
* 处理用户请求的主方法
*
* @param agent Agent对象
* @param userMessage 用户消息
* @return 处理结果
*/
public String processRequest(Agent agent, String userMessage) {
log.info("开始处理ReAct Agent请求,Agent ID: {}, 用户消息: {}", agent.getId(), userMessage);
// 初始化对话历史
List<Message> conversationHistory = new ArrayList<>();
// 添加系统提示词
String systemPrompt = agent.getSystemPrompt() != null ? agent.getSystemPrompt() :
"You are a helpful AI assistant that can use tools to help answer questions. " +
"Use the following format to think through problems step by step:\n\n" +
"Thought: Think about what to do.\n" +
"Action: Call a tool with parameters.\n" +
"Observation: Observe the result of the tool call.\n\n" +
"Continue this process until you have enough information to answer the question.\n" +
"Finally, provide your answer in the format:\n" +
"Final Answer: [Your answer here]";
conversationHistory.add(new SystemMessage(systemPrompt));
// 添加用户消息
conversationHistory.add(new UserMessage(userMessage));
// 执行ReAct循环
for (int i = 0; i < 5; i++) { // 最多执行5轮
log.info("执行ReAct循环第 {} 轮", i + 1);
// 构建Prompt
Prompt prompt = new Prompt(conversationHistory);
// 调用模型
ChatResponse response = chatModel.call(prompt);
String aiResponse = response.getResult().getOutput().getText();
log.info("模型响应: {}", aiResponse);
// 检查是否需要工具调用
if (needsToolCall(aiResponse)) {
log.info("检测到需要工具调用");
// 解析工具调用
ToolCall toolCall = parseToolCall(aiResponse);
if (toolCall != null) {
log.info("解析到工具调用: 工具名称={}, 参数={}", toolCall.getToolName(), toolCall.getParameters());
// 执行工具调用
String toolResult = toolService.executeTool(toolCall.getToolName(), toolCall.getParameters());
log.info("工具执行结果: {}", toolResult);
// 将工具结果添加到对话历史
conversationHistory.add(new AssistantMessage(aiResponse));
conversationHistory.add(new UserMessage("Observation: " + toolResult));
} else {
log.warn("工具调用解析失败,结束ReAct循环");
break;
}
} else {
log.info("无需工具调用,返回最终答案");
// 返回最终结果
String finalAnswer = extractFinalAnswer(aiResponse);
log.info("最终答案: {}", finalAnswer);
return finalAnswer;
}
}
log.warn("达到最大执行轮次限制");
return "达到最大执行轮次限制";
}
/**
* 判断是否需要工具调用
*
* @param response 模型响应
* @return 是否需要工具调用
*/
private boolean needsToolCall(String response) {
return response.contains("Action:");
}
/**
* 解析工具调用
*
* @param response 模型响应
* @return 工具调用对象
*/
private ToolCall parseToolCall(String response) {
// 使用正则表达式解析Action行
// 支持多种格式:Action: tool_name(param1=value1, param2=value2) 或 Action: tool_name()
Pattern actionPattern = Pattern.compile("Action:\\s*(\\w+)\\s*\\(([^)]*)\\)");
Matcher matcher = actionPattern.matcher(response);
if (matcher.find()) {
String toolName = matcher.group(1);
String paramsStr = matcher.group(2);
// 解析参数
Map<String, Object> parameters = parseParameters(paramsStr);
return new ToolCall(toolName, parameters);
}
return null;
}
/**
* 解析参数字符串
*
* @param paramsStr 参数字符串
* @return 参数Map
*/
private Map<String, Object> parseParameters(String paramsStr) {
Map<String, Object> parameters = new HashMap<>();
// 如果参数字符串为空,直接返回空Map
if (paramsStr == null || paramsStr.trim().isEmpty()) {
return parameters;
}
// 解析参数,支持带引号和不带引号的值
// 格式: key1=value1, key2="value with spaces", key3='another value'
String[] paramPairs = paramsStr.split(","); // 简化处理,按逗号分割
for (String paramPair : paramPairs) {
String[] parts = paramPair.split("=");
if (parts.length == 2) {
String key = parts[0].trim();
String value = parts[1].trim();
// 移除引号(如果存在)
if ((value.startsWith("\"") && value.endsWith("\"")) ||
(value.startsWith("'") && value.endsWith("'"))) {
value = value.substring(1, value.length() - 1);
}
parameters.put(key, value);
}
}
return parameters;
}
/**
* 提取最终答案
*
* @param response 模型响应
* @return 最终答案
*/
private String extractFinalAnswer(String response) {
// 查找Final Answer:后面的文本
Pattern answerPattern = Pattern.compile("Final Answer:\\s*(.*)", Pattern.DOTALL);
Matcher matcher = answerPattern.matcher(response);
if (matcher.find()) {
return matcher.group(1).trim();
}
// 如果没有找到Final Answer格式,返回整个响应
return response;
}
/**
* 工具调用内部类
*/
private static class ToolCall {
private final String toolName;
private final Map<String, Object> parameters;
public ToolCall(String toolName, Map<String, Object> parameters) {
this.toolName = toolName;
this.parameters = parameters;
}
public String getToolName() {
return toolName;
}
public Map<String, Object> getParameters() {
return parameters;
}
}
}
\ No newline at end of file
package pangea.hiagent.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import pangea.hiagent.model.Tool;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
/**
* 时间工具服务类
* 提供获取当前时间的功能
*/
@Slf4j
@Service
public class TimeToolService {
/**
* 执行时间工具调用
*
* @param tool 工具信息
* @param parameters 工具参数
* @return 当前时间的字符串表示
*/
public String executeTimeTool(Tool tool, Map<String, Object> parameters) {
log.info("执行时间工具调用: {}", tool.getName());
try {
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
// 格式化时间
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedTime = now.format(formatter);
// 构造返回结果
return "{\n" +
" \"currentTime\": \"" + formattedTime + "\",\n" +
" \"timestamp\": " + System.currentTimeMillis() + "\n" +
"}";
} catch (Exception e) {
log.error("时间工具调用失败: {}", tool.getName(), e);
return "{\n" +
" \"error\": \"时间工具调用失败\",\n" +
" \"message\": \"" + e.getMessage() + "\"\n" +
"}";
}
}
}
\ No newline at end of file
package pangea.hiagent.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import com.fasterxml.jackson.databind.ObjectMapper;
import pangea.hiagent.model.Tool;
import pangea.hiagent.repository.ToolRepository;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.net.URI;
/**
* Tool服务类
* 负责工具的注册、管理和调用
*/
@Slf4j
@Service
public class ToolService {
private final ToolRepository toolRepository;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
private final TimeToolService timeToolService;
public ToolService(ToolRepository toolRepository, TimeToolService timeToolService) {
this.toolRepository = toolRepository;
this.restTemplate = new RestTemplate();
this.objectMapper = new ObjectMapper();
this.timeToolService = timeToolService;
}
/**
* 注册工具
*/
@Transactional
public Tool registerTool(Tool tool) {
log.info("注册工具: {}", tool.getName());
toolRepository.insert(tool);
return tool;
}
/**
* 更新工具
*/
@Transactional
public Tool updateTool(Tool tool) {
log.info("更新工具: {}", tool.getId());
toolRepository.updateById(tool);
return tool;
}
/**
* 删除工具
*/
@Transactional
public void deleteTool(String id) {
log.info("删除工具: {}", id);
toolRepository.deleteById(id);
}
/**
* 获取工具详情
*/
public Tool getTool(String id) {
return toolRepository.selectById(id);
}
/**
* 获取工具列表
*/
public List<Tool> listTools() {
log.debug("开始获取工具列表");
try {
LambdaQueryWrapper<Tool> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Tool::getStatus, "active");
List<Tool> tools = toolRepository.selectList(wrapper);
log.debug("成功获取工具列表,共 {} 条记录", tools.size());
return tools;
} catch (Exception e) {
log.error("获取工具列表时发生异常", e);
throw e;
}
}
/**
* 分页获取工具列表
*/
public IPage<Tool> pageTools(Long current, Long size, String name, String category) {
Page<Tool> page = new Page<>(current, size);
LambdaQueryWrapper<Tool> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Tool::getStatus, "active");
if (name != null) {
wrapper.like(Tool::getName, name);
}
if (category != null) {
wrapper.eq(Tool::getCategory, category);
}
return toolRepository.selectPage(page, wrapper);
}
/**
* 按分类获取工具列表
*/
public List<Tool> getToolsByCategory(String category) {
LambdaQueryWrapper<Tool> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Tool::getCategory, category);
wrapper.eq(Tool::getStatus, "active");
return toolRepository.selectList(wrapper);
}
/**
* 执行工具调用
*
* @param toolName 工具名称
* @param parameters 工具参数
* @return 工具执行结果
*/
public String executeTool(String toolName, Map<String, Object> parameters) {
log.info("执行工具调用: {}, 参数: {}", toolName, parameters);
// 根据工具名称获取工具信息
LambdaQueryWrapper<Tool> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Tool::getName, toolName);
wrapper.eq(Tool::getStatus, "active");
Tool tool = toolRepository.selectOne(wrapper);
if (tool == null) {
log.warn("工具未找到或未激活: {}", toolName);
return "工具未找到或未激活: " + toolName;
}
// 根据工具类型执行不同的调用逻辑
// 这里根据工具的具体类型来执行不同的逻辑
switch (tool.getCategory()) {
case "API":
return executeApiTool(tool, parameters);
case "FUNCTION":
// 检查是否为特殊的时间工具
if ("get_current_time".equals(tool.getName())) {
return timeToolService.executeTimeTool(tool, parameters);
}
return executeFunctionTool(tool, parameters);
default:
return executeDefaultTool(tool, parameters);
}
}
/**
* 执行API工具调用
*/
private String executeApiTool(Tool tool, Map<String, Object> parameters) {
log.info("执行API工具调用: {}", tool.getName());
try {
// 构建请求URL
String apiUrl = tool.getApiEndpoint();
if (apiUrl == null || apiUrl.isEmpty()) {
return "工具配置错误:API端点未设置";
}
// 解析HTTP方法
HttpMethod httpMethod = HttpMethod.GET;
if (tool.getHttpMethod() != null) {
try {
httpMethod = HttpMethod.valueOf(tool.getHttpMethod().toUpperCase());
} catch (IllegalArgumentException e) {
log.warn("无效的HTTP方法: {}, 使用GET作为默认方法", tool.getHttpMethod());
}
}
// 构建请求头
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
headers.set("User-Agent", "HiAgent/1.0");
// 构建请求体
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(parameters, headers);
// 发送HTTP请求
ResponseEntity<String> response = restTemplate.exchange(
apiUrl,
httpMethod,
requestEntity,
String.class
);
// 检查响应状态
if (response.getStatusCode() == HttpStatus.OK) {
return response.getBody();
} else {
return "API调用失败,状态码: " + response.getStatusCode();
}
} catch (Exception e) {
log.error("API工具调用失败: {}", tool.getName(), e);
return "API工具调用失败: " + e.getMessage();
}
}
/**
* 执行函数工具调用
*/
private String executeFunctionTool(Tool tool, Map<String, Object> parameters) {
log.info("执行函数工具调用: {}", tool.getName());
// 这里应该实现实际的函数调用逻辑
// 例如通过反射调用指定的Java方法
// 目前返回模拟结果
Map<String, Object> result = new HashMap<>();
result.put("tool", tool.getName());
result.put("status", "success");
result.put("message", "函数工具调用成功");
result.put("parameters", parameters);
result.put("result", "这是函数执行的结果");
try {
return objectMapper.writeValueAsString(result);
} catch (Exception e) {
log.error("序列化函数调用结果失败", e);
return result.toString();
}
}
/**
* 执行默认工具调用
*/
private String executeDefaultTool(Tool tool, Map<String, Object> parameters) {
log.info("执行默认工具调用: {}", tool.getName());
// 默认的工具执行逻辑
Map<String, Object> result = new HashMap<>();
result.put("tool", tool.getName());
result.put("status", "success");
result.put("message", "默认工具调用成功");
result.put("parameters", parameters);
result.put("result", "这是默认工具执行的结果");
try {
return objectMapper.writeValueAsString(result);
} catch (Exception e) {
log.error("序列化默认工具调用结果失败", e);
return result.toString();
}
}
}
\ No newline at end of file
-- 插入默认数据
INSERT INTO sys_user (id, username, password, email, nickname, status, role) VALUES
('default-user-id', 'admin', '$2a$10$N.zmdr9k7uOCQb0bta/OauRxaOKSr.QhqyD2R5FKvMQjmHoLkm5Sy', 'admin@hiagent.com', 'Admin', 'active', 'admin');
INSERT INTO agent (id, name, description, status, default_model, owner) VALUES
('default-agent-1', '客服助手', '处理客户咨询的AI助手', 'active', 'deepseek-chat', 'default-user-id'),
('default-agent-2', '技术支持', '提供技术支持服务的AI助手', 'active', 'gpt-3.5-turbo', 'default-user-id');
INSERT INTO tool (id, name, display_name, description, category, status, timeout, http_method) VALUES
('default-tool-1', 'search', '搜索工具', '进行网络搜索查询', 'API', 'active', 30000, 'GET'),
('default-tool-2', 'calculator', '计算器', '进行数学计算', 'FUNCTION', 'active', 5000, 'POST'),
('default-tool-3', 'weather', '天气查询', '查询天气信息', 'API', 'active', 10000, 'GET'),
('default-tool-4', 'get_current_time', '获取当前时间', '获取当前系统时间', 'FUNCTION', 'active', 1000, 'GET');
\ No newline at end of file
-- HiAgent数据库表结构初始化脚本
-- 用户表
CREATE TABLE IF NOT EXISTS sys_user (
id varchar(36) NOT NULL,
username varchar(50) NOT NULL UNIQUE,
password varchar(255) NOT NULL,
email varchar(100),
nickname varchar(100),
status varchar(20) DEFAULT 'active',
role varchar(50) DEFAULT 'user',
avatar varchar(255),
last_login_time bigint,
api_key varchar(255),
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by varchar(36),
updated_by varchar(36),
deleted int DEFAULT 0,
remark text,
PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_username ON sys_user (username);
-- Agent表
CREATE TABLE IF NOT EXISTS agent (
id varchar(36) NOT NULL,
name varchar(100) NOT NULL,
description text,
status varchar(20) DEFAULT 'active',
default_model varchar(50),
system_prompt text,
prompt_template text,
temperature decimal(3,2) DEFAULT 0.7,
max_tokens int DEFAULT 4096,
top_p decimal(3,2) DEFAULT 0.9,
top_k int DEFAULT 50,
presence_penalty decimal(3,2) DEFAULT 0,
frequency_penalty decimal(3,2) DEFAULT 0,
history_length int DEFAULT 10,
tools json,
rag_collection_id varchar(36),
enable_rag tinyint DEFAULT 0,
enable_react tinyint DEFAULT 0,
owner varchar(36),
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by varchar(36),
updated_by varchar(36),
deleted int DEFAULT 0,
remark text,
PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_owner ON agent (owner);
-- 工具表
CREATE TABLE IF NOT EXISTS tool (
id varchar(36) NOT NULL,
name varchar(100) NOT NULL,
display_name varchar(100),
description text,
category varchar(50),
status varchar(20) DEFAULT 'active',
parameters json,
return_type varchar(50),
return_schema json,
implementation text,
timeout bigint,
api_endpoint varchar(255),
http_method varchar(20),
owner varchar(36),
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by varchar(36),
updated_by varchar(36),
deleted int DEFAULT 0,
remark text,
PRIMARY KEY (id)
);
-- 文档表
CREATE TABLE IF NOT EXISTS document (
id varchar(36) NOT NULL,
name varchar(255) NOT NULL,
type varchar(20),
size bigint,
status varchar(20) DEFAULT 'uploading',
chunks int DEFAULT 0,
collection_id varchar(36),
file_path varchar(255),
author varchar(100),
source varchar(100),
tags json,
metadata json,
embedding_model varchar(100),
error_message text,
owner varchar(36),
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by varchar(36),
updated_by varchar(36),
deleted int DEFAULT 0,
remark text,
PRIMARY KEY (id)
);
-- 文档片段表
CREATE TABLE IF NOT EXISTS document_chunk (
id varchar(36) NOT NULL,
document_id varchar(36) NOT NULL,
content longtext,
page_number int,
score decimal(3,2),
sequence int,
vector_id bigint,
metadata json,
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by varchar(36),
updated_by varchar(36),
deleted int DEFAULT 0,
remark text,
PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_document_id ON document_chunk (document_id);
-- Agent对话表
CREATE TABLE IF NOT EXISTS agent_dialogue (
id varchar(36) NOT NULL,
agent_id varchar(36) NOT NULL,
context_id varchar(36),
user_message longtext,
agent_response longtext,
prompt_tokens int,
completion_tokens int,
total_tokens int,
processing_time bigint,
finish_reason varchar(50),
tool_calls json,
user_id varchar(36),
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by varchar(36),
updated_by varchar(36),
deleted int DEFAULT 0,
remark text,
PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_agent_id ON agent_dialogue (agent_id);
CREATE INDEX IF NOT EXISTS idx_user_id ON agent_dialogue (user_id);
-- 系统日志表
CREATE TABLE IF NOT EXISTS sys_log (
id varchar(36) NOT NULL,
operation_type varchar(50),
resource_type varchar(50),
resource_id varchar(36),
user_id varchar(36),
description text,
request_params json,
response_result json,
success tinyint DEFAULT 1,
error_message text,
ip_address varchar(50),
execution_time bigint,
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by varchar(36),
updated_by varchar(36),
deleted int DEFAULT 0,
remark text,
PRIMARY KEY (id)
);
-- LLM配置表
CREATE TABLE IF NOT EXISTS llm_config (
id varchar(36) NOT NULL,
name varchar(100) NOT NULL UNIQUE,
description text,
provider varchar(50),
model_name varchar(100),
api_key varchar(255),
base_url varchar(255),
temperature decimal(3,2) DEFAULT 0.7,
max_tokens int DEFAULT 4096,
top_p decimal(3,2) DEFAULT 0.9,
enabled tinyint DEFAULT 1,
owner varchar(36),
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
created_by varchar(36),
updated_by varchar(36),
deleted int DEFAULT 0,
remark text,
PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_user_id ON sys_log (user_id);
CREATE INDEX IF NOT EXISTS idx_created_at ON sys_log (created_at);
\ No newline at end of file
<template>
<div class="management-page">
<h2>Agent管理</h2>
<el-button type="primary" @click="dialogVisible = true" style="margin-bottom: 20px">
创建Agent
</el-button>
<el-table :data="agents" stripe style="width: 100%">
<el-table-column prop="name" label="名称" />
<el-table-column prop="description" label="描述" />
<el-table-column prop="defaultModel" label="默认模型" />
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-tag :type="row.status === 'active' ? 'success' : 'warning'">
{{ row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<el-button link type="primary" @click="editAgent(row)">编辑</el-button>
<el-button link type="danger" @click="deleteAgent(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑Agent' : '创建Agent'">
<el-form :model="form">
<el-form-item label="名称">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" type="textarea" />
</el-form-item>
<el-form-item label="默认模型">
<el-select v-model="form.defaultModel" placeholder="请选择默认模型">
<el-option label="DeepSeek-Chat" value="deepseek-chat" />
<el-option label="GPT-3.5 Turbo" value="gpt-3.5-turbo" />
<el-option label="Llama2" value="llama2" />
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="form.status">
<el-option label="活跃" value="active" />
<el-option label="非活跃" value="inactive" />
<el-option label="草稿" value="draft" />
</el-select>
</el-form-item>
<el-form-item label="启用ReAct模式">
<el-switch v-model="form.enableReAct" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveAgent">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useAuthStore } from '@/stores/auth'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '@/utils/request'
import { handleGlobalError, withErrorHandling } from '@/utils/errorHandler'
const authStore = useAuthStore()
const agents = ref([])
const dialogVisible = ref(false)
const isEdit = ref(false)
const form = ref({
name: '',
description: '',
defaultModel: 'deepseek-chat',
status: 'active',
enableReAct: false
})
onMounted(() => {
loadAgents()
})
const loadAgents = async () => {
try {
const res = await authStore.get('/agent')
agents.value = res.data.data || []
} catch (error) {
console.error('获取Agent列表失败:', error)
console.error('错误详情:', {
message: error.message,
response: error.response,
status: error.response?.status,
statusText: error.response?.statusText,
data: error.response?.data
})
ElMessage.error('获取Agent列表失败: ' + (error.response?.data?.message || error.message || '未知错误'))
}
}
const editAgent = (agent) => {
isEdit.value = true
form.value = { ...agent }
dialogVisible.value = true
}
const saveAgent = async () => {
try {
if (isEdit.value) {
// 使用更安全的方式调用 PUT 请求
await authStore.put(`/agent/${form.value.id}`, form.value)
ElMessage.success('更新成功')
} else {
// 使用更安全的方式调用 POST 请求
await authStore.post('/agent', form.value)
ElMessage.success('创建成功')
}
dialogVisible.value = false
loadAgents()
} catch (error) {
console.error('保存Agent失败:', error)
let errorMessage = '操作失败'
if (error.response && error.response.data && error.response.data.message) {
errorMessage += `: ${error.response.data.message}`
}
ElMessage.error(errorMessage)
}
}
const deleteAgent = (agent) => {
ElMessageBox.confirm('确认删除该Agent吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
// 使用更安全的方式调用 DELETE 请求
await authStore.del(`/agent/${agent.id}`)
ElMessage.success('删除成功')
loadAgents()
} catch (error) {
console.error('删除Agent失败:', error)
let errorMessage = '删除失败'
if (error.response && error.response.data && error.response.data.message) {
errorMessage += `: ${error.response.data.message}`
}
ElMessage.error(errorMessage)
}
}).catch(() => {})
}
</script>
<style scoped>
.management-page {
padding: 20px;
}
</style>
\ No newline at end of file
<template>
<div class="management-page">
<h2>工具管理</h2>
<el-button type="primary" @click="openDialog()" style="margin-bottom: 20px">
注册工具
</el-button>
<el-table :data="tools" stripe style="width: 100%">
<el-table-column prop="name" label="工具名称" />
<el-table-column prop="displayName" label="显示名称" />
<el-table-column prop="description" label="描述" />
<el-table-column prop="category" label="分类" />
<el-table-column prop="status" label="状态">
<template #default="{ row }">
<el-tag :type="row.status === 'active' ? 'success' : 'warning'">
{{ row.status === 'active' ? '活跃' : '非活跃' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="{ row }">
<el-button link type="primary" @click="openDialog(row)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑工具' : '注册工具'" width="600px">
<el-form :model="form" label-width="120px">
<el-form-item label="工具名称">
<el-input v-model="form.name" placeholder="请输入工具名称" />
</el-form-item>
<el-form-item label="显示名称">
<el-input v-model="form.displayName" placeholder="请输入显示名称" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="form.description" type="textarea" placeholder="请输入工具描述" />
</el-form-item>
<el-form-item label="分类">
<el-select v-model="form.category" placeholder="请选择分类">
<el-option label="API" value="API" />
<el-option label="函数" value="FUNCTION" />
<el-option label="默认" value="DEFAULT" />
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="form.status" placeholder="请选择状态">
<el-option label="活跃" value="active" />
<el-option label="非活跃" value="inactive" />
</el-select>
</el-form-item>
<el-form-item label="API端点">
<el-input v-model="form.apiEndpoint" placeholder="请输入API端点URL" />
</el-form-item>
<el-form-item label="请求方式">
<el-select v-model="form.httpMethod" placeholder="请选择请求方式">
<el-option label="GET" value="GET" />
<el-option label="POST" value="POST" />
<el-option label="PUT" value="PUT" />
<el-option label="DELETE" value="DELETE" />
</el-select>
</el-form-item>
<el-form-item label="超时时间(毫秒)">
<el-input-number v-model="form.timeout" :min="1000" :max="60000" placeholder="请输入超时时间" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveTool">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useAuthStore } from '@/stores/auth'
import { ElMessage, ElMessageBox } from 'element-plus'
const authStore = useAuthStore()
const tools = ref([])
const dialogVisible = ref(false)
const isEdit = ref(false)
const form = ref({
name: '',
displayName: '',
description: '',
category: 'API',
status: 'active',
apiEndpoint: '',
httpMethod: 'GET',
timeout: 10000
})
const toolId = ref('')
onMounted(async () => {
await loadTools()
})
const loadTools = async () => {
try {
const res = await authStore.get('/tools')
tools.value = res.data.data || []
} catch (error) {
console.error('获取工具列表失败:', error)
ElMessage.error('获取工具列表失败: ' + (error.response?.data?.message || error.message || '未知错误'))
}
}
const openDialog = (tool) => {
if (tool) {
isEdit.value = true
toolId.value = tool.id
form.value = { ...tool }
} else {
isEdit.value = false
toolId.value = ''
form.value = {
name: '',
displayName: '',
description: '',
category: 'API',
status: 'active',
apiEndpoint: '',
httpMethod: 'GET',
timeout: 10000
}
}
dialogVisible.value = true
}
const saveTool = async () => {
try {
if (isEdit.value) {
await authStore.put(`/tools/${toolId.value}`, form.value)
ElMessage.success('工具更新成功')
} else {
await authStore.post('/tools', form.value)
ElMessage.success('工具注册成功')
}
dialogVisible.value = false
await loadTools()
} catch (error) {
console.error('保存工具失败:', error)
ElMessage.error('保存工具失败: ' + (error.response?.data?.message || error.message || '未知错误'))
}
}
const handleDelete = (tool) => {
ElMessageBox.confirm(`确认删除工具 "${tool.name}" 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await authStore.del(`/tools/${tool.id}`)
ElMessage.success('工具删除成功')
await loadTools()
} catch (error) {
console.error('删除工具失败:', error)
ElMessage.error('删除工具失败: ' + (error.response?.data?.message || error.message || '未知错误'))
}
}).catch(() => {
ElMessage.info('已取消删除')
})
}
</script>
<style scoped>
.management-page {
padding: 20px;
}
</style>
\ No newline at end of file
-- HiAgent数据库初始化脚本
USE hiagent;
-- 用户表
CREATE TABLE IF NOT EXISTS `sys_user` (
`id` varchar(36) NOT NULL,
`username` varchar(50) NOT NULL UNIQUE,
`password` varchar(255) NOT NULL,
`email` varchar(100),
`nickname` varchar(100),
`status` varchar(20) DEFAULT 'active',
`role` varchar(50) DEFAULT 'user',
`avatar` varchar(255),
`last_login_time` bigint,
`api_key` varchar(255),
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` varchar(36),
`updated_by` varchar(36),
`deleted` int DEFAULT 0,
`remark` text,
PRIMARY KEY (`id`),
KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Agent表
CREATE TABLE IF NOT EXISTS `agent` (
`id` varchar(36) NOT NULL,
`name` varchar(100) NOT NULL,
`description` text,
`status` varchar(20) DEFAULT 'active',
`default_model` varchar(50),
`system_prompt` text,
`prompt_template` text,
`temperature` decimal(3,2) DEFAULT 0.7,
`max_tokens` int DEFAULT 4096,
`top_p` decimal(3,2) DEFAULT 0.9,
`top_k` int DEFAULT 50,
`presence_penalty` decimal(3,2) DEFAULT 0,
`frequency_penalty` decimal(3,2) DEFAULT 0,
`history_length` int DEFAULT 10,
`tools` json,
`rag_collection_id` varchar(36),
`enable_rag` tinyint DEFAULT 0,
`enable_react` tinyint DEFAULT 0,
`owner` varchar(36),
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` varchar(36),
`updated_by` varchar(36),
`deleted` int DEFAULT 0,
`remark` text,
PRIMARY KEY (`id`),
KEY `idx_owner` (`owner`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 工具表
CREATE TABLE IF NOT EXISTS `tool` (
`id` varchar(36) NOT NULL,
`name` varchar(100) NOT NULL,
`display_name` varchar(100),
`description` text,
`category` varchar(50),
`status` varchar(20) DEFAULT 'active',
`parameters` json,
`return_type` varchar(50),
`return_schema` json,
`implementation` text,
`timeout` bigint,
`api_endpoint` varchar(255),
`http_method` varchar(20),
`owner` varchar(36),
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` varchar(36),
`updated_by` varchar(36),
`deleted` int DEFAULT 0,
`remark` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 文档表
CREATE TABLE IF NOT EXISTS `document` (
`id` varchar(36) NOT NULL,
`name` varchar(255) NOT NULL,
`type` varchar(20),
`size` bigint,
`status` varchar(20) DEFAULT 'uploading',
`chunks` int DEFAULT 0,
`collection_id` varchar(36),
`file_path` varchar(255),
`author` varchar(100),
`source` varchar(100),
`tags` json,
`metadata` json,
`embedding_model` varchar(100),
`error_message` text,
`owner` varchar(36),
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` varchar(36),
`updated_by` varchar(36),
`deleted` int DEFAULT 0,
`remark` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 文档片段表
CREATE TABLE IF NOT EXISTS `document_chunk` (
`id` varchar(36) NOT NULL,
`document_id` varchar(36) NOT NULL,
`content` longtext,
`page_number` int,
`score` decimal(3,2),
`sequence` int,
`vector_id` bigint,
`metadata` json,
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` varchar(36),
`updated_by` varchar(36),
`deleted` int DEFAULT 0,
`remark` text,
PRIMARY KEY (`id`),
KEY `idx_document_id` (`document_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Agent对话表
CREATE TABLE IF NOT EXISTS `agent_dialogue` (
`id` varchar(36) NOT NULL,
`agent_id` varchar(36) NOT NULL,
`context_id` varchar(36),
`user_message` longtext,
`agent_response` longtext,
`prompt_tokens` int,
`completion_tokens` int,
`total_tokens` int,
`processing_time` bigint,
`finish_reason` varchar(50),
`tool_calls` json,
`user_id` varchar(36),
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` varchar(36),
`updated_by` varchar(36),
`deleted` int DEFAULT 0,
`remark` text,
PRIMARY KEY (`id`),
KEY `idx_agent_id` (`agent_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 系统日志表
CREATE TABLE IF NOT EXISTS `sys_log` (
`id` varchar(36) NOT NULL,
`operation_type` varchar(50),
`resource_type` varchar(50),
`resource_id` varchar(36),
`user_id` varchar(36),
`description` text,
`request_params` json,
`response_result` json,
`success` tinyint DEFAULT 1,
`error_message` text,
`ip_address` varchar(50),
`execution_time` bigint,
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_by` varchar(36),
`updated_by` varchar(36),
`deleted` int DEFAULT 0,
`remark` text,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 插入默认数据
INSERT INTO `sys_user` (id, username, password, email, nickname, status, role) VALUES
('default-user-id', 'admin', '$2a$10$N.zmdr9k7uOCQb0bta/OauRxaOKSr.QhqyD2R5FKvMQjmHoLkm5Sy', 'admin@hiagent.com', 'Admin', 'active', 'admin');
INSERT INTO `agent` (id, name, description, status, default_model, owner) VALUES
('default-agent-1', '客服助手', '处理客户咨询的AI助手', 'active', 'deepseek-v2', 'default-user-id'),
('default-agent-2', '技术支持', '提供技术支持服务的AI助手', 'active', 'gpt-3.5-turbo', 'default-user-id');
INSERT INTO `tool` (id, name, display_name, description, category, status, timeout, http_method) VALUES
('default-tool-1', 'search', '搜索工具', '进行网络搜索查询', 'API', 'active', 30000, 'GET'),
('default-tool-2', 'calculator', '计算器', '进行数学计算', 'FUNCTION', 'active', 5000, 'POST'),
('default-tool-3', 'weather', '天气查询', '查询天气信息', 'API', 'active', 10000, 'GET'),
('default-tool-4', 'get_current_time', '获取当前时间', '获取当前系统时间', 'FUNCTION', 'active', 1000, 'GET');
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