Initial commit: Fintec AI Framework with Agent, RAG, and MCP modules
This commit is contained in:
491
docs/AI应用开发培训.md
Normal file
491
docs/AI应用开发培训.md
Normal 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
656
docs/API参考.md
Normal 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;
|
||||
}
|
||||
}
|
||||
```
|
||||
152
docs/pic/spring_ai_architecture.svg
Normal file
152
docs/pic/spring_ai_architecture.svg
Normal 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", 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
546
docs/开发指南.md
Normal 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
173
docs/架构设计.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# Fintec AI Framework 架构设计文档
|
||||
|
||||
## 📋 目录
|
||||
|
||||
- [整体架构](#整体架构)
|
||||
- [模块职责](#模块职责)
|
||||
- [技术栈](#技术栈)
|
||||
|
||||
---
|
||||
|
||||
## 整体架构
|
||||
|
||||
### 架构图
|
||||
|
||||

|
||||
|
||||
## 模块职责
|
||||
|
||||
### 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**: 本地缓存
|
||||
Reference in New Issue
Block a user