Initial commit: Fintec AI Framework with Agent, RAG, and MCP modules

This commit is contained in:
limqsh
2026-04-27 17:23:58 +08:00
parent a9a1441537
commit 69c5aacdc8
85 changed files with 7143 additions and 0 deletions

View File

@@ -0,0 +1,491 @@
# Spring AI 企业级应用开发培训
## 📋 大纲
1. [AI 开发基础概念](#1-ai-开发基础概念)
2. [Spring AI 框架介绍](#2-spring-ai-框架介绍)
3. [实战演练](#3-实战演练)
**前置知识**: Spring Boot 基础、RESTful API
---
## 1. AI 开发基础概念
### 1.1 大语言模型(LLM)是什么?
```
传统编程: 输入 + 规则 = 输出
AI 编程: 输入 + 示例 + 指令 = 输出
```
**核心能力**:
- 自然语言理解
- 文本生成
- 逻辑推理
- 代码生成
### 1.2 Prompt Engineering(提示工程)
**Prompt 的组成**:
```
系统提示(System): 定义角色和行为规则
用户消息(User): 具体问题或任务
上下文(Context): 历史对话或相关知识
```
**最佳实践**:
```java
// ❌ 差的 Prompt
"帮我写个排序算法"
// ✅ 好的 Prompt
"""
你是一位资深Java工程师。请实现一个快速排序算法,要求:
1. 使用泛型支持任意Comparable类型
2. 添加详细注释
3. 包含单元测试示例
4. 分析时间复杂度
"""
```
### 1.3 RAG (检索增强生成)
**为什么需要 RAG?**
- LLM 的知识有截止时间
- 企业内部数据不在训练集中
- 减少幻觉(Hallucination)
**工作原理**:
```
用户问题 → 向量化 → 检索相关文档 → 拼接Prompt → LLM生成答案
```
### 1.4 Function Calling (工具调用)
让 AI 能够调用外部工具:
- 查询数据库
- 调用 API
- 执行计算
---
## 2. Spring AI 框架介绍
### 2.1 为什么选择 Spring AI?
**优势**:
- ✅ 与 Spring 生态无缝集成
- ✅ 统一的模型抽象(支持多厂商)
- ✅ 企业级特性(安全、限流、重试)
- ✅ 熟悉的编程模型
**对比其他框架**:
| 特性 | Spring AI | LangChain4j | LlamaIndex |
|------|-----------|-------------|------------|
| Spring 集成 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| 学习曲线 | 平缓 | 中等 | 陡峭 |
### 2.2 架构设计
```
┌──────────────────────────────────────┐
│ 业务代码 (Your Service) │
├──────────────────────────────────────┤
│ AgentTemplate / RagTemplate │ ← 我们封装的模板
├──────────────────────────────────────┤
│ ChatClient (Spring AI) │ ← 统一客户端
├──────────────────────────────────────┤
│ OpenAI / Ollama / DashScope │ ← 具体模型
└──────────────────────────────────────┘
```
### 2.3 核心模块概览
#### Agent Starter - 对话能力
```java
// 一行代码实现对话
String answer = agentTemplate.ask("什么是Spring AI?");
```
#### RAG Starter - 知识库问答
```java
// 基于企业知识库回答
String answer = ragTemplate.ask("公司的报销流程是什么?");
```
#### MCP Starter - 工具调用
```java
@Tool(description = "查询天气")
public String getWeather(String city) {
return weatherService.query(city);
}
```
#### Graph Starter - 工作流编排
```java
// 顺序执行多个步骤
var result = GraphTemplate.sequential(
node1, node2, node3
).execute(input);
```
---
## 3. 实战演练
### 3.1 场景一: 智能客服机器人
**需求**:
- 回答常见问题
- 支持多轮对话
- 能查询订单状态
**实现**:
```java
@Service
public class CustomerBotService {
@Autowired
private AgentTemplate agentTemplate;
@Autowired
private OrderService orderService;
/**
* 处理用户咨询
*/
public String chat(String sessionId, String message) {
// 1. 尝试从知识库回答
String knowledgeAnswer = ragTemplate.ask(message);
// 2. 如果知识库没有,使用通用对话
if (isNotConfident(knowledgeAnswer)) {
return agentTemplate.askWithMemory(sessionId, message);
}
return knowledgeAnswer;
}
/**
* 查询订单(工具调用示例)
*/
@Tool(description = "查询订单状态")
public String queryOrder(@ToolParam(description = "订单号") String orderId) {
Order order = orderService.findById(orderId);
return "订单状态: " + order.getStatus();
}
}
```
**配置**:
```yaml
spring:
ai:
openai:
chat:
options:
model: gpt-4
temperature: 0.7 # 创造性: 0-1,越高越有创意
app:
ai:
safety:
block-keywords:
- 密码
- 身份证号
```
---
### 3.2 场景二: 智能文档助手
**需求**:
- 上传 PDF/Word 文档
- 基于文档内容问答
- 提取关键信息
**实现**:
```java
@Service
public class DocumentAssistantService {
@Autowired
private VectorStore vectorStore;
@Autowired
private RagTemplate ragTemplate;
/**
* 导入文档到知识库
*/
public void importDocument(MultipartFile file) {
// 1. 解析文档
List<Document> documents = documentParser.parse(file);
// 2. 切片(Chunking)
List<Document> chunks = documentSplitter.split(documents);
// 3. 向量化并存储
vectorStore.add(chunks);
}
/**
* 基于文档问答
*/
public String askAboutDocument(String question, String docCategory) {
// 添加过滤条件,只搜索特定类别的文档
return ragTemplate.askWithConfig(
question,
0.75, // 相似度阈值
3 // TopK
);
}
/**
* 提取文档摘要
*/
public String extractSummary(String documentId) {
String content = documentRepository.findById(documentId);
return agentTemplate.askForObject(
"请总结以下文档的核心要点,以列表形式返回:\n" + content,
DocumentSummary.class
);
}
}
```
---
### 3.3 场景三: 代码审查助手
**需求**:
- 自动审查提交的代码
- 发现潜在问题
- 给出改进建议
**实现**:
```java
@Service
public class CodeReviewService {
@Autowired
private AgentTemplate agentTemplate;
/**
* 审查代码
*/
public CodeReviewResult review(String code, String language) {
String prompt = """
你是一位资深的%s工程师,请审查以下代码:
检查项:
1. 代码规范
2. 潜在bug
3. 性能问题
4. 安全漏洞
5. 可维护性
代码:
```%s
%s
```
请以JSON格式返回审查结果。
""".formatted(language, language, code);
return agentTemplate.askForObject(prompt, CodeReviewResult.class);
}
/**
* 批量审查(并行处理)
*/
public List<CodeReviewResult> batchReview(List<CodeFile> files) {
var nodes = files.stream()
.map(file -> GraphTemplate.node(
"审查_" + file.getName(),
f -> review(f.getContent(), f.getLanguage())
))
.toArray(Node[]::new);
var workflow = GraphTemplate.parallel(nodes);
return workflow.execute(files);
}
}
```
---
## 4. 高级主题
### 4.1 工作流编排模式
#### 模式一: 链式处理 (Sequential)
```
输入 → 节点1 → 节点2 → 节点3 → 输出
```
**适用场景**: 数据清洗流水线、ETL 流程
#### 模式二: 分支路由 (Routing)
```
┌→ 节点A →┐
输入 → 路由 → 合并 → 输出
└→ 节点B →┘
```
**适用场景**: 根据问题类型选择不同处理逻辑
#### 模式三: 并行聚合 (Parallel)
```
输入 → 节点1 ─┐
输入 → 节点2 ─┼→ 聚合 → 输出
输入 → 节点3 ─┘
```
**适用场景**: 多维度分析、批量处理
#### 模式四: 循环优化 (Loop)
```
输入 → 节点 → 评估 → 不满足 → 回到节点
满足 → 输出
```
**适用场景**: 迭代优化、自我修正
---
### 4.2 性能优化技巧
#### 技巧一: 缓存策略
```java
@Cacheable(value = "ai-responses", key = "#prompt", unless = "#result.length() > 1000")
public String cachedAsk(String prompt) {
return agentTemplate.ask(prompt);
}
```
#### 技巧二: 流式响应
```java
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamChat(String message) {
return agentTemplate.stream(message)
.map(chunk -> ServerSentEvent.builder(chunk).build());
}
```
#### 技巧三: 异步处理
```java
@Async
public CompletableFuture<String> asyncAsk(String question) {
return CompletableFuture.completedFuture(
agentTemplate.ask(question)
);
}
```
---
## 4 安全与合规
### 4.1 数据安全
```yaml
app:
ai:
safety:
enabled: true
block-keywords:
- 密码
- token
- secret
```
### 4.2 审计日志
```java
@Component
public class AiAuditAdvisor implements CallAroundAdvisor {
@Override
public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) {
// 记录请求
auditLog.info("AI请求: user={}, prompt={}",
getCurrentUser(), request.prompt());
AdvisedResponse response = chain.nextAroundCall(request);
// 记录响应
auditLog.info("AI响应: user={}, tokens={}",
getCurrentUser(), response.metadata().getTokensUsage());
return response;
}
}
```
---
## 5. 常见问题解答
### Q1: 如何选择合适的模型?
**A**:
- **GPT-4**: 复杂任务、需要高质量输出
- **GPT-3.5**: 日常对话、成本敏感场景
- **Ollama(本地)**: 数据敏感、离线环境
- **通义千问**: 中文场景、国内部署
### Q2: Token 费用如何控制?
**A**:
1. 设置 `max-tokens-per-request`
2. 优化 Prompt,避免冗余
3. 使用缓存减少重复调用
4. 监控用量,设置告警
### Q3: 如何处理 AI 的错误输出?
**A**:
1. 添加验证逻辑
2. 使用结构化输出(JSON Schema)
3. 设置重试机制
4. 人工审核关键环节
### Q4: 生产环境需要注意什么?
**A**:
1. ✅ 配置限流和熔断
2. ✅ 启用监控和告警
3. ✅ 做好异常处理
4. ✅ 定期清理会话数据
5. ✅ 备份向量数据库
---
## 6. 总结
### 核心要点回顾
1. **Spring AI 简化了 AI 应用开发**
- 统一的抽象层
- 开箱即用的 Starter
- 与 Spring 生态完美集成
2. **四大核心能力**
- Agent: 对话交互
- RAG: 知识增强
- MCP: 工具调用
- Graph: 工作流编排
3. **企业级特性**
- 安全防护
- 限流降级
- 可观测性
- 多模型支持

656
docs/API参考.md Normal file
View File

@@ -0,0 +1,656 @@
# API 参考文档
## 📚 模块索引
- [AgentTemplate API](#agenttemplate-api)
- [RagTemplate API](#ragtemplate-api)
- [GraphTemplate API](#graphtemplate-api)
- [配置项说明](#配置项说明)
---
## AgentTemplate API
### 类说明
`com.ccb.fintec.agent.template.AgentTemplate`
封装 Spring AI ChatClient 的常用调用模式,提供简洁的对话能力。
### 构造方法
```java
public AgentTemplate(ChatClient chatClient)
```
**参数**:
- `chatClient`: Spring AI 聊天客户端(由自动配置提供)
---
### 方法列表
#### 1. ask(String question)
简单问答
**签名**:
```java
public String ask(String question)
```
**参数**:
- `question`: 用户问题
**返回**: AI 的回答
**示例**:
```java
String answer = agentTemplate.ask("什么是Spring AI?");
```
---
#### 2. ask(String systemPrompt, String question)
带系统提示的问答
**签名**:
```java
public String ask(String systemPrompt, String question)
```
**参数**:
- `systemPrompt`: 系统提示词,定义 AI 角色和行为
- `question`: 用户问题
**返回**: AI 的回答
**示例**:
```java
String answer = agentTemplate.ask(
"你是一位专业的Java工程师",
"如何优化Stream性能?"
);
```
---
#### 3. askWithTools(String question, Object... toolObjects)
带工具调用的问答
**签名**:
```java
public String askWithTools(String question, Object... toolObjects)
```
**参数**:
- `question`: 用户问题
- `toolObjects`: 包含 `@Tool` 注解的对象
**返回**: AI 的回答(可能包含工具调用结果)
**示例**:
```java
@Service
public class WeatherService {
@Tool(description = "查询天气")
public String getWeather(String city) {
return weatherApi.query(city);
}
}
// 使用
String answer = agentTemplate.askWithTools(
"北京今天天气怎么样?",
new WeatherService()
);
```
---
#### 4. askForObject(String question, Class<T> responseType)
结构化输出
**签名**:
```java
public <T> T askForObject(String question, Class<T> responseType)
```
**参数**:
- `question`: 用户问题
- `responseType`: 期望的返回类型
**返回**: 解析后的 Java 对象
**示例**:
```java
@Data
public class PersonInfo {
private String name;
private Integer age;
private String email;
}
PersonInfo info = agentTemplate.askForObject(
"从以下文本提取个人信息: 我叫张三,今年25岁,邮箱zhangsan@example.com",
PersonInfo.class
);
```
---
#### 5. stream(String question)
流式输出
**签名**:
```java
public Flux<String> stream(String question)
```
**参数**:
- `question`: 用户问题
**返回**: 响应式流,逐块返回 AI 回答
**示例**:
```java
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamChat(String message) {
return agentTemplate.stream(message)
.map(chunk -> ServerSentEvent.builder(chunk).build());
}
```
---
#### 6. streamWithMemory(String conversationId, String question)
带记忆的流式输出
**签名**:
```java
public Flux<String> streamWithMemory(String conversationId, String question)
```
**参数**:
- `conversationId`: 会话ID
- `question`: 用户问题
**返回**: 响应式流
**示例**:
```java
Flux<String> response = agentTemplate.streamWithMemory(
"user_123_session_456",
"我刚才问了什么?"
);
```
---
#### 7. askWithMemory(String conversationId, String question)
带记忆的多轮对话
**签名**:
```java
public String askWithMemory(String conversationId, String question)
```
**参数**:
- `conversationId`: 会话ID
- `question`: 用户问题
**返回**: AI 的回答
**示例**:
```java
// 第一轮
String answer1 = agentTemplate.askWithMemory("session_001", "我叫张三");
// 第二轮(AI 记得用户的名字)
String answer2 = agentTemplate.askWithMemory("session_001", "我叫什么名字?");
// 回答: "你叫张三"
```
---
## RagTemplate API
### 类说明
`com.ccb.fintec.rag.template.RagTemplate`
封装检索增强生成(RAG)能力,基于向量数据库实现知识库问答。
### 构造方法
```java
public RagTemplate(ChatClient chatClient, VectorStore vectorStore)
```
**参数**:
- `chatClient`: Spring AI 聊天客户端
- `vectorStore`: 向量数据库
---
### 方法列表
#### 1. ask(String question)
基于知识库问答
**签名**:
```java
public String ask(String question)
```
**参数**:
- `question`: 用户问题
**返回**: 基于知识库的回答
**示例**:
```java
String answer = ragTemplate.ask("公司的报销流程是什么?");
```
---
#### 2. stream(String question)
基于知识库的流式问答
**签名**:
```java
public Flux<String> stream(String question)
```
**参数**:
- `question`: 用户问题
**返回**: 响应式流
**示例**:
```java
@GetMapping(value = "/rag/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamRag(String question) {
return ragTemplate.stream(question)
.map(chunk -> ServerSentEvent.builder(chunk).build());
}
```
---
#### 3. askWithMemory(String conversationId, String question)
带记忆的 RAG 问答
**签名**:
```java
public String askWithMemory(String conversationId, String question)
```
**参数**:
- `conversationId`: 会话ID
- `question`: 用户问题
**返回**: 基于知识库的回答
**示例**:
```java
String answer = ragTemplate.askWithMemory("session_001",
"结合我之前的问题,再详细解释一下");
```
---
#### 4. askWithConfig(String question, double similarityThreshold, int topK)
自定义检索参数的 RAG 问答
**签名**:
```java
public String askWithConfig(String question, double similarityThreshold, int topK)
```
**参数**:
- `question`: 用户问题
- `similarityThreshold`: 相似度阈值(0.0-1.0),越高越严格
- `topK`: 返回最相关的 K 个文档片段
**返回**: 基于知识库的回答
**示例**:
```java
// 高精度模式: 只返回非常相关的内容
String answer = ragTemplate.askWithConfig(question, 0.9, 3);
// 召回模式: 返回更多相关内容
String answer = ragTemplate.askWithConfig(question, 0.6, 10);
```
---
## GraphTemplate API
### 类说明
`com.ccb.fintec.graph.template.GraphTemplate`
提供工作流编排能力,支持顺序、并行、路由、循环等模式。
### 静态方法
#### 1. sequential(Node<?, ?>... nodes)
创建顺序工作流
**签名**:
```java
public static SequentialWorkflow sequential(Node<?, ?>... nodes)
```
**参数**:
- `nodes`: 按顺序执行的节点数组
**返回**: 顺序工作流实例
**示例**:
```java
SequentialWorkflow workflow = GraphTemplate.sequential(
GraphTemplate.node("清洗", text -> text.trim()),
GraphTemplate.node("翻译", text -> translate(text)),
GraphTemplate.node("总结", text -> summarize(text))
);
String result = workflow.execute(input);
```
---
#### 2. parallel(Node<?, ?>... nodes)
创建并行工作流
**签名**:
```java
public static ParallelWorkflow parallel(Node<?, ?>... nodes)
```
**参数**:
- `nodes`: 并行执行的节点数组
**返回**: 并行工作流实例
**示例**:
```java
ParallelWorkflow workflow = GraphTemplate.parallel(
GraphTemplate.node("情感分析", text -> sentiment(text)),
GraphTemplate.node("关键词提取", text -> keywords(text)),
GraphTemplate.node("分类", text -> classify(text))
);
List<String> results = workflow.execute(input);
```
---
#### 3. routing()
创建路由工作流构建器
**签名**:
```java
public static RoutingWorkflow routing()
```
**返回**: 路由工作流构建器
**示例**:
```java
RoutingWorkflow workflow = GraphTemplate.routing()
.addBranch("技术",
Condition.of(ctx -> ((String)ctx).contains("技术")),
GraphTemplate.node("技术回答", q -> techAnswer(q)))
.addBranch("业务",
Condition.of(ctx -> ((String)ctx).contains("业务")),
GraphTemplate.node("业务回答", q -> businessAnswer(q)))
.setDefault(GraphTemplate.node("通用回答", q -> generalAnswer(q)));
String result = workflow.execute(question);
```
---
#### 4. loop(Node<?, ?> node, Condition condition, int maxIterations)
创建循环工作流
**签名**:
```java
public static LoopWorkflow loop(Node<?, ?> node, Condition condition, int maxIterations)
```
**参数**:
- `node`: 要循环执行的节点
- `condition`: 继续循环的条件(返回 true 则继续)
- `maxIterations`: 最大迭代次数(防止死循环)
**返回**: 循环工作流实例
**示例**:
```java
LoopWorkflow workflow = GraphTemplate.loop(
GraphTemplate.node("优化", text -> optimize(text)),
Condition.of(result -> !isSatisfactory((String)result)),
5 // 最多迭代5次
);
String result = workflow.execute(initialText);
```
---
#### 5. node(String name, Function<I, O> function)
创建工作流节点
**签名**:
```java
public static <I, O> Node<I, O> node(String name, Function<I, O> function)
```
**参数**:
- `name`: 节点名称(用于调试和日志)
- `function`: 节点执行逻辑
**返回**: 节点实例
**示例**:
```java
Node<String, String> node = GraphTemplate.node(
"数据转换",
data -> data.toUpperCase()
);
```
---
## 配置项说明
### 核心配置 (app.ai.*)
#### 安全防护 (app.ai.safety.*)
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `enabled` | Boolean | true | 是否启用安全防护 |
| `block-keywords` | List<String> | [] | 敏感词列表 |
**示例**:
```yaml
app:
ai:
safety:
enabled: true
block-keywords:
- 暴力
- 色情
- 赌博
```
**功能说明**:
- 双向检查:拦截请求和响应中的敏感内容
- 大小写不敏感:自动转小写匹配
- 发现敏感词时抛出 `AiException` 异常
---
#### 限流配置 (app.ai.rate-limit.*)
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `max-requests-per-minute` | Integer | 60 | 每分钟最大请求数 |
**示例**:
```yaml
app:
ai:
rate-limit:
max-requests-per-minute: 100
```
---
#### 重试策略 (app.ai.retry.*)
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `enabled` | Boolean | true | 是否启用重试 |
| `max-attempts` | Integer | 3 | 最大重试次数 |
| `backoff-ms` | Long | 1000 | 重试间隔(毫秒) |
**示例**:
```yaml
app:
ai:
retry:
enabled: true
max-attempts: 5
backoff-ms: 2000
```
**功能说明**:
- 自动处理网络波动、临时故障
- 记录重试日志,方便问题排查
- 支持固定退避时间
---
### Spring AI 配置 (spring.ai.*)
#### OpenAI 配置
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `spring.ai.openai.api-key` | String | - | API Key |
| `spring.ai.openai.base-url` | String | https://api.openai.com/v1 | API 地址 |
| `spring.ai.openai.chat.options.model` | String | gpt-3.5-turbo | 模型名称 |
| `spring.ai.openai.chat.options.temperature` | Double | 0.7 | 温度参数(0-1) |
| `spring.ai.openai.chat.options.max-tokens` | Integer | - | 最大输出 Token 数 |
**示例**:
```yaml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://api.openai.com/v1
chat:
options:
model: gpt-4-turbo
temperature: 0.8
max-tokens: 2048
```
---
#### Ollama 配置(本地模型)
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `spring.ai.ollama.base-url` | String | http://localhost:11434 | Ollama 地址 |
| `spring.ai.ollama.chat.options.model` | String | llama3 | 模型名称 |
**示例**:
```yaml
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: qwen:7b
```
---
#### 通义千问配置
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `spring.ai.dashscope.api-key` | String | - | API Key |
| `spring.ai.dashscope.chat.options.model` | String | qwen-plus | 模型名称 |
**示例**:
```yaml
spring:
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen-max
```
---
## 数据类型参考
### Node 接口
```java
@FunctionalInterface
public interface Node<I, O> {
O execute(I input);
default String getName() {
return this.getClass().getSimpleName();
}
static <I, O> Node<I, O> of(String name, Function<I, O> function) {
return new SimpleNode<>(name, function);
}
}
```
---
### Condition 接口
```java
@FunctionalInterface
public interface Condition {
boolean evaluate(Object context);
static Condition of(Predicate<Object> predicate) {
return predicate::test;
}
}
```

View File

@@ -0,0 +1,152 @@
<svg width="100%" viewBox="0 0 680 700" role="img" xmlns="http://www.w3.org/2000/svg">
<title style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">Spring AI 分层架构图</title>
<desc style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">从业务应用层到模型厂商层的六层 Spring AI 架构示意图</desc>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</marker>
</defs>
<!-- Layer 1: 业务应用层 -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="20" width="600" height="82" rx="10" stroke-width="0.5" style="fill:rgb(238, 237, 254);stroke:rgb(83, 74, 183);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="40" text-anchor="middle" dominant-baseline="central" style="fill:rgb(60, 52, 137);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">业务应用层</text>
</g>
<g onclick="sendPrompt('智能客服业务应用怎么集成 Spring AI')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="56" y="50" width="120" height="38" rx="6" stroke-width="0.5" style="fill:rgb(238, 237, 254);stroke:rgb(83, 74, 183);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="116" y="69" text-anchor="middle" dominant-baseline="central" style="fill:rgb(83, 74, 183);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">智能客服</text>
</g>
<g onclick="sendPrompt('文档助手业务应用怎么集成 Spring AI')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="192" y="50" width="120" height="38" rx="6" stroke-width="0.5" style="fill:rgb(238, 237, 254);stroke:rgb(83, 74, 183);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="252" y="69" text-anchor="middle" dominant-baseline="central" style="fill:rgb(83, 74, 183);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">文档助手</text>
</g>
<g onclick="sendPrompt('代码助手业务应用怎么集成 Spring AI')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="328" y="50" width="120" height="38" rx="6" stroke-width="0.5" style="fill:rgb(238, 237, 254);stroke:rgb(83, 74, 183);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="388" y="69" text-anchor="middle" dominant-baseline="central" style="fill:rgb(83, 74, 183);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">代码助手</text>
</g>
<g onclick="sendPrompt('AI绘画业务应用怎么集成 Spring AI')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="464" y="50" width="120" height="38" rx="6" stroke-width="0.5" style="fill:rgb(238, 237, 254);stroke:rgb(83, 74, 183);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="524" y="69" text-anchor="middle" dominant-baseline="central" style="fill:rgb(83, 74, 183);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">AI 绘画</text>
</g>
<!-- Arrow 1->2 -->
<line x1="340" y1="102" x2="340" y2="118" marker-end="url(#arrow)" style="fill:none;stroke:rgb(115, 114, 108);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<!-- Layer 2: Starter 能力层 -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="120" width="600" height="82" rx="10" stroke-width="0.5" style="fill:rgb(225, 245, 238);stroke:rgb(15, 110, 86);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="140" text-anchor="middle" dominant-baseline="central" style="fill:rgb(8, 80, 65);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Starter 能力层(纯 POM 聚合)</text>
</g>
<g onclick="sendPrompt('Agent Starter 的作用是什么?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="56" y="150" width="176" height="38" rx="6" stroke-width="0.5" style="fill:rgb(225, 245, 238);stroke:rgb(15, 110, 86);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="144" y="169" text-anchor="middle" dominant-baseline="central" style="fill:rgb(15, 110, 86);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Agent Starter</text>
</g>
<g onclick="sendPrompt('RAG Starter 的作用是什么?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="252" y="150" width="176" height="38" rx="6" stroke-width="0.5" style="fill:rgb(225, 245, 238);stroke:rgb(15, 110, 86);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="169" text-anchor="middle" dominant-baseline="central" style="fill:rgb(15, 110, 86);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">RAG Starter</text>
</g>
<g onclick="sendPrompt('MCP Server Starter 的作用是什么?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="448" y="150" width="176" height="38" rx="6" stroke-width="0.5" style="fill:rgb(225, 245, 238);stroke:rgb(15, 110, 86);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="536" y="169" text-anchor="middle" dominant-baseline="central" style="fill:rgb(15, 110, 86);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">MCP Server Starter</text>
</g>
<!-- Arrow 2->3 -->
<line x1="340" y1="202" x2="340" y2="218" marker-end="url(#arrow)" style="fill:none;stroke:rgb(115, 114, 108);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<!-- Layer 3: AutoConfigure 层 -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="220" width="600" height="82" rx="10" stroke-width="0.5" style="fill:rgb(230, 241, 251);stroke:rgb(24, 95, 165);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="240" text-anchor="middle" dominant-baseline="central" style="fill:rgb(12, 68, 124);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">AutoConfigure 层(自动配置逻辑)</text>
</g>
<g onclick="sendPrompt('Agent AutoConfig 做了哪些自动配置?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="56" y="250" width="176" height="38" rx="6" stroke-width="0.5" style="fill:rgb(230, 241, 251);stroke:rgb(24, 95, 165);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="144" y="269" text-anchor="middle" dominant-baseline="central" style="fill:rgb(24, 95, 165);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Agent AutoConfig</text>
</g>
<g onclick="sendPrompt('RAG AutoConfig 做了哪些自动配置?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="252" y="250" width="176" height="38" rx="6" stroke-width="0.5" style="fill:rgb(230, 241, 251);stroke:rgb(24, 95, 165);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="269" text-anchor="middle" dominant-baseline="central" style="fill:rgb(24, 95, 165);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">RAG AutoConfig</text>
</g>
<g onclick="sendPrompt('MCP Server AutoConfig 做了哪些自动配置?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="448" y="250" width="176" height="38" rx="6" stroke-width="0.5" style="fill:rgb(230, 241, 251);stroke:rgb(24, 95, 165);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="536" y="269" text-anchor="middle" dominant-baseline="central" style="fill:rgb(24, 95, 165);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">MCP Server AutoConfig</text>
</g>
<!-- Arrow 3->4 -->
<line x1="340" y1="302" x2="340" y2="318" marker-end="url(#arrow)" style="fill:none;stroke:rgb(115, 114, 108);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<!-- Layer 4: Core 核心层 -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="320" width="600" height="118" rx="10" stroke-width="0.5" style="fill:rgb(241, 239, 232);stroke:rgb(95, 94, 90);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="342" text-anchor="middle" dominant-baseline="central" style="fill:rgb(68, 68, 65);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Core 核心层(纯 JAR — 契约与模型)</text>
</g>
<g onclick="sendPrompt('AiCoreProperties 统一配置的结构是什么?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="60" y="354" width="170" height="34" rx="6" stroke-width="0.5" style="fill:rgb(241, 239, 232);stroke:rgb(95, 94, 90);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="145" y="371" text-anchor="middle" dominant-baseline="central" style="fill:rgb(95, 94, 90);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">AiCoreProperties 统一配置</text>
</g>
<g onclick="sendPrompt('AiResponse 统一响应模型包含哪些字段?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="246" y="354" width="170" height="34" rx="6" stroke-width="0.5" style="fill:rgb(241, 239, 232);stroke:rgb(95, 94, 90);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="331" y="371" text-anchor="middle" dominant-baseline="central" style="fill:rgb(95, 94, 90);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">AiResponse 统一响应模型</text>
</g>
<g onclick="sendPrompt('AiException 统一异常体系怎么设计?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="432" y="354" width="170" height="34" rx="6" stroke-width="0.5" style="fill:rgb(241, 239, 232);stroke:rgb(95, 94, 90);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="517" y="371" text-anchor="middle" dominant-baseline="central" style="fill:rgb(95, 94, 90);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">AiException 统一异常</text>
</g>
<g onclick="sendPrompt('Node/Condition Graph 核心接口怎么实现?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="60" y="396" width="170" height="34" rx="6" stroke-width="0.5" style="fill:rgb(241, 239, 232);stroke:rgb(95, 94, 90);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="145" y="413" text-anchor="middle" dominant-baseline="central" style="fill:rgb(95, 94, 90);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Node/Condition Graph 接口</text>
</g>
<g onclick="sendPrompt('MetricConstants 指标常量有哪些?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="246" y="396" width="170" height="34" rx="6" stroke-width="0.5" style="fill:rgb(241, 239, 232);stroke:rgb(95, 94, 90);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="331" y="413" text-anchor="middle" dominant-baseline="central" style="fill:rgb(95, 94, 90);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">MetricConstants 指标常量</text>
</g>
<!-- Arrow 4->5 -->
<line x1="340" y1="438" x2="340" y2="454" marker-end="url(#arrow)" style="fill:none;stroke:rgb(115, 114, 108);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<!-- Layer 5: Spring AI 适配层 -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="456" width="600" height="82" rx="10" stroke-width="0.5" style="fill:rgb(250, 236, 231);stroke:rgb(153, 60, 29);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="476" text-anchor="middle" dominant-baseline="central" style="fill:rgb(113, 43, 19);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Spring AI 适配层(模型抽象)</text>
</g>
<g onclick="sendPrompt('ChatClient 的使用方式是什么?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="56" y="486" width="176" height="38" rx="6" stroke-width="0.5" style="fill:rgb(250, 236, 231);stroke:rgb(153, 60, 29);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="144" y="505" text-anchor="middle" dominant-baseline="central" style="fill:rgb(153, 60, 29);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">ChatClient</text>
</g>
<g onclick="sendPrompt('VectorStore 如何在 RAG 中使用?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="252" y="486" width="176" height="38" rx="6" stroke-width="0.5" style="fill:rgb(250, 236, 231);stroke:rgb(153, 60, 29);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="505" text-anchor="middle" dominant-baseline="central" style="fill:rgb(153, 60, 29);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">VectorStore</text>
</g>
<g onclick="sendPrompt('Image/Audio Model 支持哪些厂商?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="448" y="486" width="176" height="38" rx="6" stroke-width="0.5" style="fill:rgb(250, 236, 231);stroke:rgb(153, 60, 29);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="536" y="505" text-anchor="middle" dominant-baseline="central" style="fill:rgb(153, 60, 29);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Image / Audio Model</text>
</g>
<!-- Arrow 5->6 -->
<line x1="340" y1="538" x2="340" y2="554" marker-end="url(#arrow)" style="fill:none;stroke:rgb(115, 114, 108);color:rgb(0, 0, 0);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<!-- Layer 6: 模型厂商层 -->
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="40" y="556" width="600" height="82" rx="10" stroke-width="0.5" style="fill:rgb(250, 238, 218);stroke:rgb(133, 79, 11);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="340" y="576" text-anchor="middle" dominant-baseline="central" style="fill:rgb(99, 56, 6);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">模型厂商层(可插拔)</text>
</g>
<g onclick="sendPrompt('Spring AI 如何接入 OpenAI')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="56" y="586" width="88" height="38" rx="6" stroke-width="0.5" style="fill:rgb(250, 238, 218);stroke:rgb(133, 79, 11);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="100" y="605" text-anchor="middle" dominant-baseline="central" style="fill:rgb(133, 79, 11);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">OpenAI</text>
</g>
<g onclick="sendPrompt('Spring AI 如何接入 Ollama 本地模型?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="158" y="586" width="88" height="38" rx="6" stroke-width="0.5" style="fill:rgb(250, 238, 218);stroke:rgb(133, 79, 11);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="202" y="605" text-anchor="middle" dominant-baseline="central" style="fill:rgb(133, 79, 11);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Ollama</text>
</g>
<g onclick="sendPrompt('Spring AI 如何接入 DashScope 通义千问?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="260" y="586" width="108" height="38" rx="6" stroke-width="0.5" style="fill:rgb(250, 238, 218);stroke:rgb(133, 79, 11);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="314" y="605" text-anchor="middle" dominant-baseline="central" style="fill:rgb(133, 79, 11);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">DashScope</text>
</g>
<g onclick="sendPrompt('Spring AI 如何接入 Azure OpenAI')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="382" y="586" width="88" height="38" rx="6" stroke-width="0.5" style="fill:rgb(250, 238, 218);stroke:rgb(133, 79, 11);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="426" y="605" text-anchor="middle" dominant-baseline="central" style="fill:rgb(133, 79, 11);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Azure</text>
</g>
<g onclick="sendPrompt('Spring AI 如何接入其他自定义厂商?')" style="fill:rgb(0, 0, 0);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
<rect x="484" y="586" width="140" height="38" rx="6" stroke-width="0.5" style="fill:rgb(250, 238, 218);stroke:rgb(133, 79, 11);color:rgb(0, 0, 0);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
<text x="554" y="605" text-anchor="middle" dominant-baseline="central" style="fill:rgb(133, 79, 11);stroke:none;color:rgb(0, 0, 0);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:&quot;Anthropic Sans&quot;, -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">其他厂商(可扩展)</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 37 KiB

546
docs/开发指南.md Normal file
View File

@@ -0,0 +1,546 @@
# Fintec AI Framework 开发指南
## 📚 文档目录
- [快速开始](#快速开始)
- [架构概览](#架构概览)
- [模块说明](#模块说明)
- [使用示例](#使用示例)
- [最佳实践](#最佳实践)
- [常见问题](#常见问题)
---
## 快速开始
### 环境要求
- JDK 17+
- Spring Boot 3.4.6+
- Spring AI 1.1.1+
- Maven 3.6+
### 引入依赖
在项目的 `pom.xml` 中添加依赖:
```xml
<!-- Agent Starter(对话能力) -->
<dependency>
<groupId>com.ccb.fintec</groupId>
<artifactId>fintec-framework-agent-spring-boot-starter</artifactId>
</dependency>
<!-- RAG Starter(知识库问答) -->
<dependency>
<groupId>com.ccb.fintec</groupId>
<artifactId>fintec-framework-rag-spring-boot-starter</artifactId>
</dependency>
<!-- MCP Server Starter(工具暴露) -->
<dependency>
<groupId>com.ccb.fintec</groupId>
<artifactId>fintec-framework-mcp-server-spring-boot-starter</artifactId>
</dependency>
```
### 基础配置
`application.yml` 中配置 AI 模型:
```yaml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://api.openai.com/v1
chat:
options:
model: gpt-4
temperature: 0.7
app:
ai:
# 安全防护
safety:
enabled: true
block-keywords:
- 暴力
- 色情
- 赌博
# 限流配置
rate-limit:
max-requests-per-minute: 60
# 重试策略(自动处理网络波动)
retry:
enabled: true
max-attempts: 3
backoff-ms: 1000
```
---
## 架构概览
### 整体架构
```
┌─────────────────────────────────────────────┐
│ 业务应用层 (Business App) │
├─────────────────────────────────────────────┤
│ Agent+Graph+MCP Client │ RAG │
│ Starter │ Starter │
├─────────────────────────────────────────────┤
│ Core 核心层 (统一抽象) │
├─────────────────────────────────────────────┤
│ Spring AI (模型适配层) │
├─────────────────────────────────────────────┤
│ OpenAI │ Ollama │ Anthropic │ 其他模型 │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ MCP Server (独立部署) │
│ SSE + STDIO + Streamable-HTTP │
└─────────────────────────────────────────────┘
```
### 设计原则
1. **分层解耦**: 技术实现与业务编排分离
2. **多模型支持**: 通过 Spring AI 抽象,支持切换不同模型厂商
3. **开箱即用**: 自动配置,零样板代码
4. **可扩展性**: 基于 Spring Boot Starter 机制,易于扩展
---
## 模块说明
### 1. fintec-framework-ai-core (核心模块)
**职责**: 提供统一的 AI 配置和能力抽象
**功能**:
- 全局 AI 配置管理 (`AiCoreProperties`)
- 公共的接口和DTO公共回复等解耦...
---
### 2. fintec-framework-agent-spring-boot-starter (Agent 模块)
**职责**: 提供对话式 AI、Graph 工作流编排和 MCP Client 能力
**核心组件**:
- `AgentTemplate`: 封装常用对话模式(内置重试)
- `MultimodalTemplate`: 多模态能力(图片/文档理解)
- `ChatClient`: 统一的聊天客户端
- `ChatMemory`: 对话记忆管理
- Graph 工作流: `Node`, `Condition`, `SequentialWorkflow`, `ParallelWorkflow`, `RoutingWorkflow`, `LoopWorkflow`
- `McpClientAutoConfiguration`: MCP Client 自动配置
- `RetryAutoConfiguration`: 自动重试机制
- `SafetyAspect`: 敏感词过滤切面
**主要功能**:
- 简单问答
- 带系统提示的对话
- Function Calling(工具调用)
- 结构化输出
- 流式响应
- 对话记忆
- **图片理解**: 分析图片内容、OCR识别
- **Graph 工作流**: Sequential/Parallel/Routing/Loop
- **MCP Client**: 连接外部 MCP Server
- **安全防护**: 敏感词过滤(请求+响应双向检查)
- **自动重试**: 处理网络波动、临时故障
**典型场景**: 智能客服、AI助手、代码生成、多模态应用、复杂工作流编排
---
### 3. fintec-framework-rag-spring-boot-starter (RAG 模块)
**职责**: 提供检索增强生成、ETL 和图片生成能力
**核心组件**:
- `RagTemplate`: 封装 RAG 调用模式
- `ImageGenerationTemplate`: 图片生成能力
- `QuestionAnswerAdvisor`: 向量检索顾问
**主要功能**:
- **ETL**: 文档读取和解析PDF、Word、TXT等
- **Embedding**: 文本向量化
- **VectorStore**: 向量存储和检索
- 基于知识库的问答
- 自定义相似度阈值和 TopK
- 流式输出
- 结合对话记忆
- **图片生成**: 根据文本描述生成图片
**典型场景**: 企业知识库问答、文档检索、智能搜索、AI绘画
---
### 4. fintec-framework-mcp-server-spring-boot-starter (MCP Server 模块)
**职责**: 提供 Model Context Protocol Server 支持,将工具暴露给外部 Agent
**核心组件**:
- `McpAutoConfiguration`: MCP Server 自动配置
- `ToolCallbackProvider`: 自动扫描 `@Tool` 注解
**主要功能**:
- 自动注册工具函数
- **SSE**: Server-Sent Events 传输
- **STDIO**: 标准输入输出传输
- **Streamable-HTTP**: 流式 HTTP 传输
- 工具发现与调用
**典型场景**: AI 调用外部 API、数据库查询、业务系统集成独立部署
---
## 使用示例
### Agent 模块示例(包含 Graph 工作流)
```java
@Service
public class CustomerService {
@Autowired
private AgentTemplate agentTemplate;
// 简单问答
public String answer(String question) {
return agentTemplate.ask(question);
}
// 带系统提示
public String answerWithRole(String question) {
return agentTemplate.ask(
"你是一位专业的客服代表,请用礼貌的语气回答",
question
);
}
// 流式输出
public Flux<String> streamAnswer(String question) {
return agentTemplate.stream(question);
}
// 带对话记忆
public String chat(String conversationId, String message) {
return agentTemplate.askWithMemory(conversationId, message);
}
// 结构化输出
public OrderInfo extractOrder(String text) {
return agentTemplate.askForObject(
"从以下文本中提取订单信息: " + text,
OrderInfo.class
);
}
}
```
### Graph 工作流示例
```java
@Service
public class WorkflowService {
@Autowired
private GraphTemplate graphTemplate;
// 顺序工作流: 数据清洗 -> 翻译 -> 总结
public String processText(String text) {
var workflow = GraphTemplate.sequential(
GraphTemplate.node("清洗", t -> t.trim()),
GraphTemplate.node("翻译", t -> translate(t)),
GraphTemplate.node("总结", t -> summarize(t))
);
return workflow.execute(text);
}
// 并行工作流: 多维度分析
public List<String> analyzeText(String text) {
var workflow = GraphTemplate.parallel(
GraphTemplate.node("情感分析", t -> sentiment(t)),
GraphTemplate.node("关键词提取", t -> keywords(t)),
GraphTemplate.node("分类", t -> classify(t))
);
return workflow.execute(text);
}
// 路由工作流: 根据问题类型选择处理逻辑
public String routeQuestion(String question) {
var workflow = GraphTemplate.routing()
.addBranch("技术",
q -> ((String)q).contains("技术"),
GraphTemplate.node("技术回答", q -> techAnswer(q)))
.addBranch("业务",
q -> ((String)q).contains("业务"),
GraphTemplate.node("业务回答", q -> businessAnswer(q)))
.setDefault(GraphTemplate.node("通用回答", q -> generalAnswer(q)));
return workflow.execute(question);
}
}
```
```java
@Service
public class KnowledgeService {
@Autowired
private RagTemplate ragTemplate;
// 基于知识库问答
public String askKnowledge(String question) {
return ragTemplate.ask(question);
}
// 自定义检索参数
public String askWithConfig(String question) {
return ragTemplate.askWithConfig(
question,
0.8, // 相似度阈值
5 // TopK
);
}
// 流式输出
public Flux<String> streamAsk(String question) {
return ragTemplate.stream(question);
}
}
```
### Graph 模块示例
```java
@Service
public class WorkflowService {
@Autowired
private GraphTemplate graphTemplate;
// 顺序工作流: 数据清洗 -> 翻译 -> 总结
public String processText(String text) {
var workflow = GraphTemplate.sequential(
GraphTemplate.node("清洗", t -> t.trim()),
GraphTemplate.node("翻译", t -> translate(t)),
GraphTemplate.node("总结", t -> summarize(t))
);
return workflow.execute(text);
}
// 并行工作流: 多维度分析
public List<String> analyzeText(String text) {
var workflow = GraphTemplate.parallel(
GraphTemplate.node("情感分析", t -> sentiment(t)),
GraphTemplate.node("关键词提取", t -> keywords(t)),
GraphTemplate.node("分类", t -> classify(t))
);
return workflow.execute(text);
}
// 路由工作流: 根据问题类型选择处理逻辑
public String routeQuestion(String question) {
var workflow = GraphTemplate.routing()
.addBranch("技术",
q -> ((String)q).contains("技术"),
GraphTemplate.node("技术回答", q -> techAnswer(q)))
.addBranch("业务",
q -> ((String)q).contains("业务"),
GraphTemplate.node("业务回答", q -> businessAnswer(q)))
.setDefault(GraphTemplate.node("通用回答", q -> generalAnswer(q)));
return workflow.execute(question);
}
}
```
---
## 最佳实践
### 1. 对话记忆管理
**推荐做法**:
```java
// 使用业务ID作为会话ID
String conversationId = "user_" + userId + "_session_" + sessionId;
agentTemplate.askWithMemory(conversationId, message);
// 定期清理过期会话
@Component
public class MemoryCleanupTask {
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
public void cleanup() {
// 清理7天前的会话
}
}
```
**避免做法**:
- 使用 UUID 作为会话ID(每次都是新会话)
- 不清理会话数据(内存泄漏)
- 不同用户共用同一会话ID
---
### 3. RAG 优化
**推荐做法**:
```java
// 调整相似度阈值(根据业务场景)
ragTemplate.askWithConfig(question, 0.75, 3);
// 文档切片策略
// - 技术文档: 500-800 tokens/chunk
// - FAQ: 按问题拆分
// - 合同: 按条款拆分
// 添加元数据过滤
SearchRequest.builder()
.similarityThreshold(0.8)
.topK(5)
.filterExpression("category == 'technical'")
.build();
```
**避免做法**:
- TopK 设置过大(增加成本)
- 相似度阈值过低(返回不相关内容)
- 不处理文档切片重叠
---
### 4. 工作流设计
**推荐做法**:
```java
// 为节点命名(便于调试)
GraphTemplate.node("数据验证", data -> validate(data));
// 设置合理的循环上限
GraphTemplate.loop(node, condition, 5); // 最多5次迭代
// 异常处理
try {
return workflow.execute(input);
} catch (Exception e) {
log.error("工作流执行失败", e);
return fallbackResult;
}
```
**避免做法**:
- 循环不设上限(死循环风险)
- 节点逻辑过于复杂(应拆分为多个节点)
- 不记录工作流执行日志
---
### 5. 性能优化
**推荐做法**:
```java
// 使用流式输出提升用户体验
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamChat(String message) {
return agentTemplate.stream(message);
}
// 并行执行独立任务
var results = GraphTemplate.parallel(
node1, node2, node3
).execute(input);
// 缓存常见问题的答案
@Cacheable(value = "faq", key = "#question")
public String getFaqAnswer(String question) {
return ragTemplate.ask(question);
}
```
**避免做法**:
- 同步等待大模型响应(超时风险)
- 串行执行可并行的任务
- 重复调用相同的 Prompt
---
## 常见问题
### Q1: 如何切换不同的 AI 模型?
**A**: 修改 `application.yml` 配置即可:
```yaml
# 切换到 Ollama
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: llama3
# 切换到通义千问
spring:
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen-max
```
---
### Q2: 如何实现多轮对话?
**A**: 使用 `askWithMemory` 方法:
```java
// 第一次对话
String answer1 = agentTemplate.askWithMemory("session_001", "你好");
// 第二次对话(能记住上下文)
String answer2 = agentTemplate.askWithMemory("session_001", "我刚才说了什么?");
```
---
### Q3: RAG 检索不到相关内容怎么办?
**A**: 检查以下几点:
1. 降低相似度阈值(如从 0.8 降到 0.6)
2. 增加 TopK 数量
3. 优化文档切片策略
4. 检查 Embedding 模型质量
5. 添加更多相关文档
---
### Q4: 如何调试工作流?
**A**: 启用 DEBUG 日志:
```yaml
logging:
level:
com.ccb.fintec.graph: DEBUG
```
查看每个节点的输入输出和执行时间。
---
### Q5: 如何保证 AI 输出的安全性?
**A**: 多层防护:
1. 配置敏感词过滤 (`app.ai.safety.block-keywords`)
2. 设置输出长度限制 (`max-tokens-per-request`)
3. 人工审核关键业务输出

173
docs/架构设计.md Normal file
View File

@@ -0,0 +1,173 @@
# Fintec AI Framework 架构设计文档
## 📋 目录
- [整体架构](#整体架构)
- [模块职责](#模块职责)
- [技术栈](#技术栈)
---
## 整体架构
### 架构图
![spring_ai_architecture](./pic/spring_ai_architecture.svg)
## 模块职责
### 1. fintec-framework-ai-core (核心模块)
**定位**: 纯 JAR - 契约、模型与常量定义
**职责**:
- 统一的 AI 配置管理 (`AiCoreProperties`)
- 统一响应模型 (`AiResponse` + `TokenUsage` + `ErrorInfo`)
- 统一异常体系 (`AiException`)
- Graph 核心接口 (`Node`, `Condition`)
**设计原则**:
- 无 Spring 依赖(纯 POJO/接口)
- 不包含自动配置
- 被所有 autoconfigure 模块依赖
**关键类**:
```
com.ccb.fintec.core.properties.AiCoreProperties
com.ccb.fintec.core.dto.AiResponse
com.ccb.fintec.core.exception.AiException
com.ccb.fintec.core.graph.Node
com.ccb.fintec.core.graph.Condition
```
---
### 2. fintec-framework-agent-spring-boot-starter (Agent 模块)
**定位**: 对话式 AI 和多模态能力中心
**职责**:
- **基础对话**: ChatClient 封装,同步/流式响应
- **Tool Calling**: @Tool 自动注册和调用
- **对话记忆**: ChatMemory + JDBC/Redis 持久化
- **Advisor 链**: Memory/RAG/Log/Guardrails
- **结构化输出**: .entity(Class) JSON解析
- **多模态输入**: 图片理解、文档理解
- **语音合成**: TTS (Text-to-Speech) - 暂不支持
- **语音识别**: STT (Speech-to-Text) - 暂不支持
**核心组件**:
```
AgentTemplate - 对话模板
MultimodalTemplate - 多模态模板
```
**典型场景**:
- 智能客服机器人
- AI 编程助手
- 多模态交互应用
---
### 3. fintec-framework-rag-spring-boot-starter (RAG 模块)
**定位**: 检索增强生成和图片生成中心
**职责**:
- **RAG 检索增强**: RetrievalAugmentationAdvisor
- **ETL 文档管道**: PDF/Word/网页读取分块
- **Embedding**: 向量化接口
- **Vector Store**: PGVector/Redis/Chroma 等
- **图片生成**: ImageModel (DALL-E等)
**核心组件**:
```
RagTemplate - RAG模板
ImageGenerationTemplate - 图片生成模板
```
**典型场景**:
- 企业知识库问答
- 智能文档检索
- AI 绘画应用
---
### 4. fintec-framework-agent-spring-boot-starter (Graph 模块)
**定位**: 工作流编排引擎
**职责**:
- **Sequential**: 顺序链式执行
- **Parallel**: 并行执行聚合
- **Routing**: 条件分支路由
- **Loop + Recursive**: 循环和递归
- **Human in the Loop**: 人工干预节点
**核心组件**:
```
Node - 工作流节点
Condition - 条件边
SequentialWorkflow - 顺序工作流
ParallelWorkflow - 并行工作流
RoutingWorkflow - 路由工作流
LoopWorkflow - 循环工作流
GraphTemplate - 工作流模板
```
**典型场景**:
- 复杂业务流程编排
- 多步骤 AI 任务
- 决策树系统
---
### 5. fintec-framework-mcp-server-spring-boot-starter (MCP 模块)
**定位**: Model Context Protocol 支持
**职责**:
- **MCP Server**: SSE + STDIO + Streamable-HTTP
- **MCP Client**: 连接远程 MCP Server
- **工具发现**: 自动扫描 @Tool 注解
- **协议适配**: 多种传输协议支持
**核心组件**:
```
ToolCallbackProvider - 工具回调提供者
McpAutoConfiguration - MCP自动配置
```
**典型场景**:
- AI 调用外部 API
- 跨系统集成
- 微服务编排
---
## 技术栈
### 基础框架
- **JDK**: 17+
- **Spring Boot**: 3.4.6+
- **Spring AI**: 1.1.0+
- **Maven**: 3.6+
### AI 模型支持
- **LLM**: OpenAI GPT、Ollama、通义千问、Anthropic Claude
- **Embedding**: OpenAI Embedding、本地 Embedding
- **Image**: DALL-E 3、Stable Diffusion
- **Audio**: OpenAI Whisper/TTS
### 向量数据库
- **PGVector**: PostgreSQL 向量扩展
- **Redis**: Redis Stack
- **Chroma**: 原生向量数据库
- **Milvus**: 大规模向量检索
### 数据存储
- **JDBC**: MySQL/PostgreSQL/Oracle
- **Redis**: 会话缓存
- **Caffeine**: 本地缓存