Initial commit: Fintec AI Framework with Agent, RAG, and MCP modules
This commit is contained in:
47
agent-demo/pom.xml
Normal file
47
agent-demo/pom.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.ccb.fintec</groupId>
|
||||
<artifactId>fintec-framework-parent</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>agent-demo</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Starter -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Agent Starter -->
|
||||
<dependency>
|
||||
<groupId>com.ccb.fintec</groupId>
|
||||
<artifactId>fintec-framework-agent-spring-boot-starter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.ccb.fintec.agent.demo;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class AgentDemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AgentDemoApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.ccb.fintec.agent.demo.controller;
|
||||
|
||||
import com.ccb.fintec.agent.autoconfigure.template.AgentTemplate;
|
||||
import com.ccb.fintec.agent.demo.dto.UserInfo;
|
||||
import com.ccb.fintec.core.dto.AiResponse;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Agent 演示控制器 - 展示封装后的简化用法
|
||||
*
|
||||
* 核心优势:
|
||||
* 1. 自动使用公司统一的默认系统提示词
|
||||
* 2. 内置会话记忆,无需手动配置
|
||||
* 3. 支持所有常用场景:基础问答、流式、工具调用、多模态、结构化输出
|
||||
* 4. 提供逃生通道,可获取底层 ChatClient 进行高级定制
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/agent")
|
||||
public class AgentController {
|
||||
|
||||
private final AgentTemplate agentTemplate;
|
||||
|
||||
public AgentController(AgentTemplate agentTemplate) {
|
||||
this.agentTemplate = agentTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. 最简单的问答 - 自动使用配置的默认系统提示词
|
||||
*
|
||||
* 示例:GET /api/agent/ask?question=什么是Spring AI?
|
||||
*/
|
||||
@GetMapping("/ask")
|
||||
public Map<String, Object> ask(@RequestParam String question) {
|
||||
// ✅ 只需一行代码,自动应用公司统一的系统提示词
|
||||
String answer = agentTemplate.ask(question);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("question", question);
|
||||
result.put("answer", answer);
|
||||
result.put("note", "使用了 fintec.ai.agent.default-system-prompt 配置的系统提示词");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 2. 带会话记忆的对话 - 自动管理上下文
|
||||
*
|
||||
* 示例:
|
||||
* GET /api/agent/ask-with-memory?conversationId=user123&question=我叫张三
|
||||
* GET /api/agent/ask-with-memory?conversationId=user123&question=我叫什么名字?
|
||||
*/
|
||||
@GetMapping("/ask-with-memory")
|
||||
public Map<String, Object> askWithMemory(
|
||||
@RequestParam String conversationId,
|
||||
@RequestParam String question) {
|
||||
// ✅ 自动使用会话记忆,无需手动配置 Advisor
|
||||
String answer = agentTemplate.ask(question, conversationId);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("conversationId", conversationId);
|
||||
result.put("question", question);
|
||||
result.put("answer", answer);
|
||||
result.put("note", "自动使用 MessageWindowChatMemory,窗口大小由 fintec.ai.agent.memory-window-size 配置");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. 自定义系统提示词的问答
|
||||
*
|
||||
* 示例:GET /api/agent/ask-custom?systemPrompt=你是数学老师&conversationId=math01&question=1+1=?
|
||||
*/
|
||||
@GetMapping("/ask-custom")
|
||||
public Map<String, Object> askCustom(
|
||||
@RequestParam String systemPrompt,
|
||||
@RequestParam String conversationId,
|
||||
@RequestParam String question) {
|
||||
String answer = agentTemplate.ask(systemPrompt, question, conversationId);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("systemPrompt", systemPrompt);
|
||||
result.put("conversationId", conversationId);
|
||||
result.put("question", question);
|
||||
result.put("answer", answer);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 4. 结构化输出 - AI 自动填充 Java 对象
|
||||
*
|
||||
* 示例:GET /api/agent/ask-for-object?question=生成一个用户信息,姓名李四,年龄25岁,邮箱lisi@example.com
|
||||
*/
|
||||
@GetMapping("/ask-for-object")
|
||||
public UserInfo askForObject(@RequestParam String question) {
|
||||
// ✅ AI 自动将回答转换为 UserInfo 对象
|
||||
return agentTemplate.askForObject(question, UserInfo.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 5. 带元数据的问答(包含 Token 使用量、耗时等)
|
||||
*
|
||||
* 示例:GET /api/agent/ask-with-metadata?question=解释一下量子计算
|
||||
*/
|
||||
@GetMapping("/ask-with-metadata")
|
||||
public AiResponse askWithMetadata(@RequestParam String question) {
|
||||
// ✅ 自动收集 Token 使用量、耗时等元数据
|
||||
return agentTemplate.askWithMetadata(question);
|
||||
}
|
||||
|
||||
/**
|
||||
* 6. 流式输出 - Server-Sent Events
|
||||
*
|
||||
* 示例:curl -N http://localhost:8083/api/agent/stream?question=讲个故事
|
||||
*/
|
||||
@GetMapping(value = "/stream", produces = "text/event-stream")
|
||||
public Flux<String> stream(@RequestParam String question) {
|
||||
// ✅ 自动应用默认系统提示词
|
||||
return agentTemplate.stream(question);
|
||||
}
|
||||
|
||||
/**
|
||||
* 7. 带会话记忆的流式输出
|
||||
*
|
||||
* 示例:curl -N "http://localhost:8083/api/agent/stream-with-memory?conversationId=user123&question=继续"
|
||||
*/
|
||||
@GetMapping(value = "/stream-with-memory", produces = "text/event-stream")
|
||||
public Flux<String> streamWithMemory(
|
||||
@RequestParam String conversationId,
|
||||
@RequestParam String question) {
|
||||
// ✅ 流式 + 记忆组合
|
||||
return agentTemplate.stream(question, conversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 8. 逃生通道:获取底层 ChatClient 进行高级定制
|
||||
*
|
||||
* 示例:GET /api/agent/advanced-usage?question=测试高级用法
|
||||
*/
|
||||
@GetMapping("/advanced-usage")
|
||||
public Map<String, Object> advancedUsage(@RequestParam String question) {
|
||||
// ✅ 获取底层 ChatClient,完全自定义
|
||||
ChatClient client = agentTemplate.getChatClient();
|
||||
|
||||
String answer = client.prompt()
|
||||
.system("完全自定义的系统提示词")
|
||||
.user(question)
|
||||
.call()
|
||||
.content();
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("question", question);
|
||||
result.put("answer", answer);
|
||||
result.put("note", "通过 getChatClient() 获取底层对象,进行高级定制");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 9. 健康检查
|
||||
*/
|
||||
@GetMapping("/health")
|
||||
public Map<String, Object> health() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("status", "UP");
|
||||
result.put("service", "Agent Demo");
|
||||
result.put("features", new String[]{
|
||||
"基础问答",
|
||||
"会话记忆(JDBC/内存自动选择)",
|
||||
"流式输出",
|
||||
"结构化输出",
|
||||
"工具调用",
|
||||
"多模态支持",
|
||||
"元数据收集",
|
||||
"逃生通道"
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.ccb.fintec.agent.demo.controller;
|
||||
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 直接测试 OpenRouter API,不经过 Spring AI
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/test")
|
||||
public class DirectOpenRouterTestController {
|
||||
|
||||
@GetMapping("/openrouter")
|
||||
public Map<String, Object> testDirectCall(@RequestParam(defaultValue = "你好") String question) {
|
||||
String apiKey = "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";
|
||||
String baseUrl = "https://openrouter.ai/api/v1/chat/completions";
|
||||
|
||||
// 构造请求体(与 Python 相同)
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("model", "openrouter/free");
|
||||
requestBody.put("messages", List.of(
|
||||
Map.of("role", "system", "content", "你是一个助手"),
|
||||
Map.of("role", "user", "content", question)
|
||||
));
|
||||
|
||||
// 构造请求头
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setBearerAuth(apiKey);
|
||||
// OpenRouter 推荐的 headers
|
||||
headers.set("HTTP-Referer", "http://localhost:8083");
|
||||
headers.set("X-Title", "Fintec Agent Demo");
|
||||
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
try {
|
||||
ResponseEntity<Map> response = restTemplate.exchange(
|
||||
baseUrl,
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
Map.class
|
||||
);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", true);
|
||||
result.put("statusCode", response.getStatusCode());
|
||||
result.put("body", response.getBody());
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", false);
|
||||
result.put("error", e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.ccb.fintec.agent.demo.dto;
|
||||
|
||||
/**
|
||||
* 用户信息 DTO(用于结构化输出示例)
|
||||
*/
|
||||
public class UserInfo {
|
||||
private String name;
|
||||
private Integer age;
|
||||
private String email;
|
||||
private String occupation;
|
||||
|
||||
public UserInfo() {}
|
||||
|
||||
public UserInfo(String name, Integer age, String email, String occupation) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
this.email = email;
|
||||
this.occupation = occupation;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(Integer age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getOccupation() {
|
||||
return occupation;
|
||||
}
|
||||
|
||||
public void setOccupation(String occupation) {
|
||||
this.occupation = occupation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserInfo{" +
|
||||
"name='" + name + '\'' +
|
||||
", age=" + age +
|
||||
", email='" + email + '\'' +
|
||||
", occupation='" + occupation + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
59
agent-demo/src/main/resources/application.properties
Normal file
59
agent-demo/src/main/resources/application.properties
Normal file
@@ -0,0 +1,59 @@
|
||||
# ============================================
|
||||
# 服务器配置
|
||||
# ============================================
|
||||
server.port=8083
|
||||
|
||||
# ============================================
|
||||
# 模型配置(业务系统根据需求选择模型提供商)
|
||||
# ============================================
|
||||
spring.ai.openai.api-key=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
|
||||
# Spring AI OpenAI starter 会自动追加 /v1/chat/completions,所以 base-url 只需要到 /api
|
||||
spring.ai.openai.base-url=https://openrouter.ai/api
|
||||
# 使用 openrouter/free 自动路由到免费模型
|
||||
spring.ai.openai.chat.options.model=openrouter/free
|
||||
# 单次请求最大 Token 数(Spring AI 原生配置)
|
||||
spring.ai.openai.chat.options.max-tokens=2048
|
||||
|
||||
# ============================================
|
||||
# Agent 模块配置
|
||||
# ============================================
|
||||
# 默认系统提示词:定义 AI 助手的人设和行为准则
|
||||
fintec.ai.agent.default-system-prompt=你是一个专业的订单助手,请用中文简洁准确地回答订单相关问题。
|
||||
# 对话记忆窗口大小:保留最近 N 条消息作为上下文
|
||||
fintec.ai.agent.memory-window-size=15
|
||||
|
||||
# ============================================
|
||||
# 安全防护配置
|
||||
# ============================================
|
||||
# 启用安全关键词过滤
|
||||
fintec.ai.safety.enabled=true
|
||||
# 需要拦截的敏感关键词列表
|
||||
fintec.ai.safety.block-keywords[0]=暴力
|
||||
fintec.ai.safety.block-keywords[1]=色情
|
||||
fintec.ai.safety.block-keywords[2]=赌博
|
||||
|
||||
# ============================================
|
||||
# 限流配置(成本控制)
|
||||
# ============================================
|
||||
# 每分钟最大请求数
|
||||
fintec.ai.rate-limit.max-requests-per-minute=30
|
||||
|
||||
# ============================================
|
||||
# 重试策略配置
|
||||
# ============================================
|
||||
# 启用自动重试
|
||||
fintec.ai.retry.enabled=true
|
||||
# 最大重试次数
|
||||
fintec.ai.retry.max-attempts=2
|
||||
# 重试退避时间(毫秒)
|
||||
fintec.ai.retry.backoff-ms=500
|
||||
|
||||
# ============================================
|
||||
# 日志配置
|
||||
# ============================================
|
||||
logging.level.root=INFO
|
||||
logging.level.com.ccb.fintec=DEBUG
|
||||
# 启用 Spring AI 和 HTTP 客户端的详细日志
|
||||
logging.level.org.springframework.ai=DEBUG
|
||||
logging.level.org.springframework.web.client=DEBUG
|
||||
logging.level.io.micrometer.common.util.internal.logging=OFF
|
||||
Reference in New Issue
Block a user