从 Go Agent 实战到 Spring AI:Java 工程师的知识迁移指南
💡 适合人群:学习了 AI Agent 原理(Go/Python),想用 Spring AI 快速落地
🎯 阅读收获:理解底层原理如何映射到 Spring AI 框架,掌握核心组件对照
📦 背景知识:ReAct、Tool Calling、RAG、Memory、MCP 协议
核心概念速查:
- Embedding:把文字变成数字向量,让计算机理解语义相似度("苹果"和"iPhone"向量相似,能搜到)
- Advisor:拦截器模式,在 ChatClient 调用前后插入自定义逻辑(记忆管理、RAG、日志等)
一、为什么先学原理再学框架?
很多人直接上手 Spring AI,遇到问题就卡住了:
现象:Spring AI 调用工具报错,不知道哪里出了问题
原因:不理解 Tool Calling 的底层交互机制
结果:只能复制粘贴报错信息去搜答案
我的学习路径:
Go 手写 Agent → 理解 ReAct 循环、Tool 协议、RAG 管道
↓
Spring AI → 知道每个组件在干什么,遇到问题能定位
比喻:
|
学习方式 |
类比 |
理解深度 |
|
直接学 Spring AI |
自动挡汽车 |
会开,但不会修 |
|
先学原理再看框架 |
手动挡 → 自动挡 |
懂原理,遇事不慌 |
二、核心概念对照表
2.1 架构层级映射
┌─────────────────────────────────────────────────────────────┐
│ 应用层 │
│ 客服机器人 / 代码助手 / 数据分析 │
├─────────────────────────────────────────────────────────────┤
│ Agent 编排层 │
│ Go: 手写循环调度 ←→ Spring AI: Advisor 链式编排 │
├─────────────────────────────────────────────────────────────┤
│ Agent 核心层 │
│ Go: 手写实现 ←→ Spring AI: ChatClient 封装 │
│ - ReAct Loop ←→ 内置循环(自动处理) │
│ - Tool System ←→ @Tool 注解 │
│ - Memory ←→ ChatMemory 接口 │
│ - RAG Pipeline ←→ VectorStore + Advisor │
├─────────────────────────────────────────────────────────────┤
│ 模型接入层 │
│ Go: HTTP Client ←→ Spring AI: ChatModel 接口 │
│ OpenAI / 本地模型 ←→ 统一抽象,支持多厂商 │
├─────────────────────────────────────────────────────────────┤
│ 基础设施层 │
│ Go: 自行选择 ←→ Spring AI: 开箱即用 │
│ 向量库 / Redis ←→ VectorStore 实现 / ChatMemory │
└─────────────────────────────────────────────────────────────┘
2.2 组件详细对照
|
概念 |
Go 手写实现 |
Spring AI 组件 |
封装程度 |
|
ReAct 循环 |
手写 for 循环 + 状态判断 |
|
完全封装 |
|
Tool Calling |
|
|
声明式 |
|
MCP 协议 |
WebSocket 连接 + JSON-RPC |
|
完全封装 |
|
RAG 检索 |
手写 Embedding + 向量搜索 |
|
开箱即用 |
|
对话记忆 |
手写消息列表管理 |
|
多策略支持 |
|
Prompt 模板 |
字符串拼接 / 模板引擎 |
|
变量注入 |
|
Embedding |
HTTP 调用 Embedding API |
|
统一抽象 |
|
向量存储 |
调用向量库 SDK |
|
多实现支持 |
三、核心模块对比详解
3.1 Agent Loop:从手写循环到一行调用
Go 手写实现(理解原理)
// AgentLoop Agent 执行循环
func AgentLoop(ctx context.Context, task Task) Result {
maxSteps := 10
for i := 0; i < maxSteps; i++ {
// 1. 思考:分析当前状态,决定下一步
thought := llm.Think(ctx, task)
log.Printf("Step %d - Thought: %s", i, thought)
// 2. 决策:选择动作
action := llm.DecideAction(ctx, thought)
// 3. 判断:是否完成?
if action.Type == "FINISH" {
return Result{Answer: action.Output}
}
// 4. 执行:调用工具
observation := executor.Execute(ctx, action)
// 5. 更新状态
task.History = append(task.History, HistoryItem{
Thought: thought,
Action: action,
Observation: observation,
})
}
return Result{Error: "exceeded max steps"}
}
核心要点:
- 手写循环,每一步都清晰可见
- 需要自己管理
History(消息历史) - 需要处理
maxSteps(防止死循环) - 需要判断
FINISH(终止条件)
Spring AI 封装(开箱即用)
@Service
public class AgentService {
private final ChatClient chatClient;
public AgentService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultTools(logParserTool, errorAnalyzerTool) // 注册工具
.build();
}
public String analyze(String logContent) {
// 一行代码,自动完成循环
return chatClient.prompt()
.user("帮我分析这段日志:" + logContent)
.call() // 内部自动执行 ReAct 循环
.content();
}
}
Spring AI 做了什么:
- 自动管理消息历史(
ChatMemory) - 自动判断是否调用工具
- 自动拼接工具结果到下一轮对话
- 自动处理终止条件
迁移价值:你知道它内部在干什么,遇到问题能定位:
问题:Agent 无限循环
原因:工具返回结果没有被正确解析
定位:Spring AI 的 ToolResult 处理逻辑
解决:检查 Tool 的 output schema 定义
3.2 Tool Calling:从结构体到注解
Go 手写实现
// Tool 定义
type Tool struct {
Name string
Description string
Parameters map[string]Parameter
Execute func(ctx context.Context, input map[string]any) (string, error)
}
type Parameter struct {
Type string
Description string
Required bool
}
// 工具注册
var tools = map[string]Tool{
"log_parser": {
Name: "log_parser",
Description: "解析日志格式,提取关键字段",
Parameters: map[string]Parameter{
"log_content": {Type: "string", Description: "日志内容", Required: true},
},
Execute: func(ctx context.Context, input map[string]any) (string, error) {
content := input["log_content"].(string)
return parseLog(content), nil
},
},
}
// LLM 调用时需要手动构建 JSON Schema
func buildToolSchema(tools map[string]Tool) []byte {
schema := make([]map[string]any, 0)
for _, tool := range tools {
schema = append(schema, map[string]any{
"type": "function",
"function": map[string]any{
"name": tool.Name,
"description": tool.Description,
"parameters": map[string]any{
"type": "object",
"properties": tool.Parameters,
},
},
})
}
return schema
}
痛点:
- 需要手写 JSON Schema
- 参数类型转换容易出错
- 没有编译时检查
Spring AI 声明式
// 方式 1:注解声明(推荐)
@Component
public class LogTools {
@Tool(name = "log_parser", description = "解析日志格式,提取关键字段")
public String parseLog(
@ToolParam(description = "日志内容") String logContent,
@ToolParam(description = "日志类型", required = false) String logType) {
// 业务逻辑
return LogParser.parse(logContent, logType);
}
}
// 方式 2:函数式构建
@Bean
public Tool logParserTool() {
return Tool.builder()
.name("log_parser")
.description("解析日志格式,提取关键字段")
.inputType(LogInput.class) // 自动生成 Schema
.execution(this::parseLog)
.build();
}
// 输入类型定义
public record LogInput(
@Description("日志内容") String logContent,
@Description("日志类型") @Nullable String logType
) {}
Spring AI 优势:
- 自动生成 JSON Schema
- 编译时类型检查
- 参数验证(JSR-303)
- 支持复杂对象
迁移价值:理解 Tool Calling 协议后,你能:
// 问题:工具调用失败
// 定位:检查 LLM 返回的 tool_calls 格式
{
"tool_calls": [{
"function": {
"name": "log_parser", // 工具名
"arguments": "{\"logContent\": \"...\"}" // JSON 字符串
}
}]
}
// Spring AI 内部做了什么:
// 1. 解析 tool_calls
// 2. 反序列化 arguments
// 3. 调用方法
// 4. 返回结果
3.3 RAG:从手动管道到 Advisor 模式
Go 手写 RAG 管道
// ===== 离线索引阶段 =====
func IndexDocuments(docs []Document) error {
for _, doc := range docs {
// 1. 文档分块
chunks := splitter.Split(doc.Content, ChunkSize, Overlap)
// 2. 批量 Embedding
embeddings := make([][]float64, len(chunks))
for i, chunk := range chunks {
embedding, err := embedder.Embed(ctx, chunk)
if err != nil {
return err
}
embeddings[i] = embedding
}
// 3. 写入向量库
vectors := make([]Vector, len(chunks))
for i, chunk := range chunks {
vectors[i] = Vector{
ID: fmt.Sprintf("%s-%d", doc.ID, i),
Content: chunk,
Embedding: embeddings[i],
Metadata: doc.Metadata,
}
}
if err := vectorStore.Save(ctx, vectors); err != nil {
return err
}
}
return nil
}
// ===== 在线问答阶段 =====
func Ask(ctx context.Context, query string) (string, error) {
// 1. 问题 Embedding
queryEmbedding, err := embedder.Embed(ctx, query)
if err != nil {
return "", err
}
// 2. 向量检索
docs, err := vectorStore.Search(ctx, queryEmbedding, TopK)
if err != nil {
return "", err
}
// 3. 构建 Prompt
prompt := buildPrompt(query, docs)
// 4. 调用 LLM
answer, err := llm.Generate(ctx, prompt)
return answer, err
}
func buildPrompt(query string, docs []Document) string {
sb := strings.Builder{}
sb.WriteString("基于以下文档回答问题:\n\n")
for i, doc := range docs {
sb.WriteString(fmt.Sprintf("【文档 %d】\n%s\n\n", i+1, doc.Content))
}
sb.WriteString(fmt.Sprintf("问题:%s\n\n回答:", query))
return sb.String()
}
手写痛点:
- 离线/在线流程分离,代码分散
- Prompt 模板硬编码
- 检索参数(TopK、阈值)分散
Spring AI 一体化方案
@Service
public class RagService {
private final ChatClient chatClient;
private final VectorStore vectorStore;
private final EmbeddingModel embeddingModel;
// ===== 离线索引:一行代码 =====
public void indexDocuments(List<Document> documents) {
// 自动:分块 + Embedding + 存储
vectorStore.accept(embeddingModel.embed(documents));
}
// ===== 在线问答:Advisor 自动注入 =====
public String ask(String query) {
return chatClient.prompt()
.user(query)
.advisors(QuestionAnswerAdvisor.builder()
.vectorStore(vectorStore)
.topK(5)
.similarityThreshold(0.7)
.build())
.call()
.content();
}
}
Spring AI 内部流程:
用户输入 → Advisor 拦截
↓
问题 Embedding → 向量检索 → TopK 文档
↓
自动拼接 Prompt:
┌─────────────────────────────────┐
│ Context: │
│ 【文档 1】... │
│ 【文档 2】... │
│ │
│ Question: {用户问题} │
│ Answer: │
└─────────────────────────────────┘
↓
调用 LLM → 返回答案
迁移价值:理解 RAG 本质后,你能优化:
// 问题:检索结果不相关
// 定位:检查 Embedding 质量和检索参数
// 优化 1:调整分块策略
vectorStore.accept(
new TokenTextSplitter(500, 50, 5, 10000, true)
.split(documents)
.stream()
.map(embeddingModel::embed)
.toList()
);
// 优化 2:混合检索(向量 + 关键词)
.advisors(QuestionAnswerAdvisor.builder()
.vectorStore(vectorStore)
.searchType(SearchType.HYBRID) // 混合检索
.topK(10)
.build())
// 优化 3:重排序
.advisors(RerankingAdvisor.builder()
.rerankingModel(rerankingModel)
.topN(5) // 从 10 篇重排序取 Top 5
.build())
3.4 Memory:从手写管理到多策略支持
Go 手写 Memory
// 消息历史
type Conversation struct {
ID string
Messages []Message
}
type Message struct {
Role string // "user", "assistant", "system"
Content string
}
// 手动管理历史
func Chat(ctx context.Context, convID, userInput string) (string, error) {
// 1. 加载历史
conv := loadConversation(convID)
// 2. 添加用户消息
conv.Messages = append(conv.Messages, Message{
Role: "user",
Content: userInput,
})
// 3. 调用 LLM
response, err := llm.Generate(ctx, conv.Messages)
if err != nil {
return "", err
}
// 4. 添加助手消息
conv.Messages = append(conv.Messages, Message{
Role: "assistant",
Content: response,
})
// 5. 保存历史
saveConversation(conv)
return response, nil
}
// 窗口截断(防止 Context 过长)
func truncateHistory(messages []Message, maxTokens int) []Message {
// 手动计算 token 数
totalTokens := 0
result := make([]Message, 0)
// 倒序添加,保留最近的
for i := len(messages) - 1; i >= 0; i-- {
tokens := countTokens(messages[i].Content)
if totalTokens + tokens > maxTokens {
break
}
result = append([]Message{messages[i]}, result...)
totalTokens += tokens
}
return result
}
Spring AI 多策略 Memory
// 策略 1:窗口记忆(保留最近 N 条)
@Bean
public ChatMemory windowMemory() {
return new InMemoryChatMemory(); // 默认窗口策略
}
// 策略 2:Token 限制(自动截断)
@Bean
public ChatMemory tokenLimitedMemory() {
return new TokenLimitChatMemory(4000); // 限制 4000 tokens
}
// 策略 3:摘要记忆(长对话压缩)
@Bean
public ChatMemory summaryMemory(ChatModel model) {
return new SummarizingChatMemory(model, 10); // 每 10 轮摘要
}
// 使用
@Service
public class ChatService {
private final ChatClient chatClient;
public ChatService(ChatClient.Builder builder, ChatMemory memory) {
this.chatClient = builder
.advisors(new MessageChatMemoryAdvisor(memory, "conversation-001"))
.build();
}
public String chat(String userInput) {
return chatClient.prompt()
.user(userInput)
.call()
.content();
// 自动管理历史,自动截断/摘要
}
}
迁移价值:理解 Memory 本质后,你能选择合适策略:
场景:客服对话,需要保留订单号等关键信息
问题:窗口记忆可能截断关键信息
方案:摘要记忆 + 关键信息提取
实现:
├── 基础:SummarizingChatMemory
├── 增强:EntityMemoryAdvisor(提取实体)
└── 持久化:RedisChatMemory(分布式)
3.5 MCP 协议:从 WebSocket 到客户端
Go 手写 MCP Client
// MCP 客户端
type McpClient struct {
conn *websocket.Conn
tools map[string]Tool
pending map[string]chan Response
}
// 连接服务器
func (c *McpClient) Connect(url string) error {
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
return err
}
c.conn = conn
// 握手
c.sendRequest("initialize", map[string]any{
"protocolVersion": "2024-11-05",
"clientInfo": map[string]any{
"name": "go-mcp-client",
"version": "1.0.0",
},
})
// 启动消息循环
go c.messageLoop()
return nil
}
// 消息循环
func (c *McpClient) messageLoop() {
for {
_, msg, err := c.conn.ReadMessage()
if err != nil {
return
}
var response Response
json.Unmarshal(msg, &response)
// 处理响应
if ch, ok := c.pending[response.ID]; ok {
ch <- response
delete(c.pending, response.ID)
}
// 处理通知(工具列表更新等)
if response.Method != "" {
c.handleNotification(response)
}
}
}
// 调用工具
func (c *McpClient) CallTool(ctx context.Context, name string, args map[string]any) (string, error) {
// 发送请求
id := uuid.New().String()
ch := make(chan Response, 1)
c.pending[id] = ch
c.sendRequest("tools/call", map[string]any{
"name": name,
"arguments": args,
})
// 等待响应
select {
case resp := <-ch:
return resp.Result, nil
case <-ctx.Done():
return "", ctx.Err()
}
}
Spring AI MCP 集成
@Configuration
public class McpConfig {
@Bean
public McpClient mcpClient() {
return McpClient.builder()
.url("ws://localhost:8081/mcp")
.protocolVersion("2024-11-05")
.build();
}
@Bean
public ChatClient chatClient(ChatClient.Builder builder, McpClient mcpClient) {
return builder
.mcpTools(mcpClient) // 自动注册 MCP 工具
.build();
}
}
// 使用:和本地工具一样
String result = chatClient.prompt()
.user("帮我查询航班信息")
.call()
.content();
// 自动调用 MCP 服务器的 flight_search 工具
迁移价值:理解 MCP 协议后,你能:
问题:MCP 工具调用超时
定位:检查 WebSocket 连接状态和消息格式
排查步骤:
1. 检查 WebSocket 连接是否正常
2. 查看 MCP 服务器日志
3. 验证 tools/call 请求格式
4. 检查响应是否正确返回
四、架构设计:从手写组件到 Advisor 模式
4.1 Go 手写架构
┌─────────────────────────────────────┐
│ Agent Service │
│ ┌─────────────────────────────┐ │
│ │ AgentLoop(手写循环) │ │
│ │ ┌───────┐ ┌───────┐ │ │
│ │ │Think │→ │Act │ │ │
│ │ └───────┘ └───────┘ │ │
│ │ ↓ ↓ │ │
│ │ ┌───────┐ ┌───────┐ │ │
│ │ │Memory │ │Tools │ │ │
│ │ └───────┘ └───────┘ │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
问题:
- 组件耦合度高
- 扩展需要修改核心代码
- 难以组合不同能力
4.2 Spring AI Advisor 模式
┌─────────────────────────────────────────────┐
│ ChatClient │
│ ┌─────────────────────────────────────┐ │
│ │ Advisor Chain │ │
│ │ ┌──────────┐ ┌──────────┐ ┌─────┐│ │
│ │ │Memory │→ │RAG │→ │Tool ││ │
│ │ │Advisor │ │Advisor │ │Advisor│ │ │
│ │ └──────────┘ └──────────┘ └─────┘│ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
优势:
- 责任链模式,解耦清晰
- 可插拔组合
- 扩展只需新增 Advisor
自定义 Advisor 示例
// 自定义:实体记忆 Advisor(提取关键实体)
public class EntityMemoryAdvisor implements RequestResponseAdvisor {
private final ChatModel model;
@Override
public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
// 提取实体
String entities = model.call("从以下文本提取关键实体:\n" + request.userText());
// 注入到 Prompt
String enhancedPrompt = String.format(
"关键实体:%s\n\n用户问题:%s",
entities, request.userText()
);
return AdvisedRequest.from(request)
.userText(enhancedPrompt)
.build();
}
@Override
public int getOrder() {
return 100; // 执行顺序
}
}
// 使用
ChatClient client = ChatClient.builder()
.advisors(
new MessageChatMemoryAdvisor(memory), // 记忆
new EntityMemoryAdvisor(model), // 实体提取
new QuestionAnswerAdvisor(vectorStore) // RAG
)
.build();
五、实战案例:差标查询 Agent
5.1 需求
用户:"技术部高级工程师出差上海,差标是多少?"
Agent:
1. 识别部门(技术部)、职级(高级工程师)、城市(上海)
2. 查询差标数据库
3. 返回具体标准
5.2 Go 手写实现
func main() {
// 1. 初始化组件
llm := openai.NewClient()
embedder := openai.NewEmbedder()
vectorStore := chromadb.NewClient()
toolRegistry := NewToolRegistry()
// 2. 注册工具
toolRegistry.Register(Tool{
Name: "query_standard",
Description: "查询差旅标准",
Parameters: map[string]Parameter{
"department": {Type: "string", Description: "部门"},
"level": {Type: "string", Description: "职级"},
"city": {Type: "string", Description: "城市"},
},
Execute: func(ctx context.Context, input map[string]any) (string, error) {
return queryStandard(
input["department"].(string),
input["level"].(string),
input["city"].(string),
), nil
},
})
// 3. RAG 索引
docs := loadDocuments()
for _, doc := range docs {
chunks := splitter.Split(doc.Content, 500, 50)
embeddings := embedder.EmbedBatch(chunks)
vectorStore.Save(embeddings)
}
// 4. Agent 循环
for {
// 用户输入
input := getUserInput()
// RAG 检索
queryEmb := embedder.Embed(input)
docs := vectorStore.Search(queryEmb, 5)
// 构建 Prompt
prompt := buildPrompt(input, docs)
// LLM 推理
response := llm.Generate(prompt)
// 判断是否调用工具
if response.ToolCall != nil {
result := toolRegistry.Execute(response.ToolCall)
// 继续循环
continue
}
// 返回结果
fmt.Println(response.Content)
break
}
}
代码量:约 200 行
5.3 Spring AI 实现
@SpringBootApplication
public class StandardQueryAgent {
public static void main(String[] args) {
SpringApplication.run(StandardQueryAgent.class, args);
}
@Bean
public ChatClient chatClient(
ChatClient.Builder builder,
VectorStore vectorStore,
ChatMemory memory) {
return builder
.defaultTools(queryStandardTool())
.advisors(
new MessageChatMemoryAdvisor(memory),
new QuestionAnswerAdvisor(vectorStore)
)
.build();
}
@Bean
public Tool queryStandardTool(StandardService service) {
return Tool.builder()
.name("query_standard")
.description("查询差旅标准")
.inputType(StandardQuery.class)
.execution(service::query)
.build();
}
@Bean
public CommandLineRunner indexDocuments(
VectorStore vectorStore,
EmbeddingModel embeddingModel) {
return args -> {
List<Document> docs = loadDocuments();
vectorStore.accept(embeddingModel.embed(docs));
};
}
}
@RestController
class AgentController {
private final ChatClient chatClient;
@PostMapping("/ask")
public String ask(@RequestBody String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
代码量:约 60 行
六、常见问题定位能力对比
场景 1:Agent 无限循环
|
定位能力 |
Go 手写 |
Spring AI |
|
发现问题 |
日志输出循环 |
日志输出循环 |
|
定位原因 |
检查终止条件判断 |
检查 Tool 返回值 |
|
解决方案 |
修改循环逻辑 |
调整 Tool output schema |
Spring AI 定位思路:
// 问题:Agent 一直调用同一个工具
// 原因:工具返回结果格式不符合预期
// 排查步骤:
// 1. 开启 Debug 日志
logging.level.org.springframework.ai=DEBUG
// 2. 查看 LLM 响应
[DEBUG] LLM Response: {
"tool_calls": [{
"function": {
"name": "query_standard",
"arguments": "{\"department\": \"技术部\"}"
}
}]
}
// 3. 查看工具返回
[DEBUG] Tool Result: "查询成功" // 问题:返回太简单
// 4. 修复:返回结构化结果
@Bean
public Tool queryStandardTool() {
return Tool.builder()
.name("query_standard")
.outputType(StandardResult.class) // 定义输出类型
.execution(input -> new StandardResult(
"技术部高级工程师差标:机票经济舱,酒店500元/晚"
))
.build();
}
场景 2:RAG 检索结果不相关
|
定位能力 |
Go 手写 |
Spring AI |
|
发现问题 |
输出内容错误 |
输出内容错误 |
|
定位原因 |
检查向量检索结果 |
检查 VectorStore 配置 |
|
解决方案 |
调整 Chunk 大小 |
调整 Advisor 参数 |
Spring AI 定位思路:
// 问题:检索结果不相关
// 原因:分块太大,语义不清晰
// 排查步骤:
// 1. 检查检索日志
[DEBUG] VectorStore search results:
- Doc 1 (score: 0.3): "技术部差标规定..." // 分数太低
- Doc 2 (score: 0.2): "销售部差标规定..."
// 2. 调整参数
.advisors(QuestionAnswerAdvisor.builder()
.vectorStore(vectorStore)
.topK(10) // 增加候选数
.similarityThreshold(0.5) // 降低阈值
.build())
// 3. 优化分块
@Bean
public VectorStore vectorStore() {
return SimpleVectorStore.builder()
.embeddingModel(embeddingModel)
.textSplitter(new TokenTextSplitter(
300, // 更小的分块
30, // 重叠
5, 10000, true
))
.build();
}
场景 3:Memory 丢失关键信息
|
定位能力 |
Go 手写 |
Spring AI |
|
发现问题 |
后续对话忘记订单号 |
后续对话忘记订单号 |
|
定位原因 |
检查窗口截断逻辑 |
检查 Memory 策略 |
|
解决方案 |
手动保留关键字段 |
使用摘要策略 |
Spring AI 定位思路:
// 问题:用户说订单号后,Agent 忘记了
// 原因:窗口记忆截断了
// 排查步骤:
// 1. 检查消息历史
[DEBUG] ChatMemory messages:
- User: 订单号是多少?
- Assistant: 订单号是 12345
- User: 帮我查询物流 // 此时可能截断
// 2. 切换策略
@Bean
public ChatMemory chatMemory(ChatModel model) {
// 从窗口记忆 → 摘要记忆
return new SummarizingChatMemory(model, 5);
}
// 3. 或使用实体记忆
.advisors(new EntityMemoryAdvisor(model))
// 自动提取并保留:订单号=12345
七、学习路径建议
7.1 如果你是 Go 工程师
第 1 步:手写 Agent 核心
├── 实现 ReAct 循环
├── 实现 Tool 系统
└── 实现 RAG 管道
第 2 步:理解协议
├── MCP 协议(工具互联)
├── Tool Calling(LLM 工具调用)
└── Prompt 格式
第 3 步:迁移 Spring AI
├── 理解 Advisor 模式
├── 掌握开箱即用组件
└── 学会问题定位
7.2 如果你是 Java 工程师
第 1 步:先用 Spring AI
├── 跑通官方示例
├── 理解 ChatClient API
└── 掌握 Advisor 模式
第 2 步:阅读源码
├── ChatClient 如何处理 Tool Call
├── Advisor 如何拦截请求
└── Memory 如何管理历史
第 3 步:深入原理
├── 学习 ReAct 论文
├── 理解 MCP 协议
└── 掌握 RAG 本质
八、总结
核心价值
Go 手写代码 → 理解"第一性原理"
Spring AI → 工程化"快速落地"
比喻:
├── Go 代码 = 手动挡汽车(理解每个齿轮)
├── Spring AI = 自动挡汽车(踩油门就走)
└── 学原理 = 遇到故障能自己修
能力对照
|
能力 |
只学 Spring AI |
先学原理 |
|
快速开发 |
✅ 能做 |
✅ 能做 |
|
问题定位 |
❌ 只能搜答案 |
✅ 能定位根因 |
|
架构设计 |
❌ 不理解底层 |
✅ 能合理设计 |
|
协议对接 |
❌ 依赖框架 |
✅ 能手写适配 |
学习资源
原理学习:
├── [ReAct 论文](https://arxiv.org/abs/2210.03629)
├── [MCP 协议规范](https://modelcontextprotocol.io/)
├── 本项目 Go 实战代码
Spring AI 学习:
├── [官方文档](https://docs.spring.io/spring-ai/reference/)
├── [官方示例](https://github.com/spring-projects/spring-ai-examples)
└── 本博客对照表
附录:关键代码仓库
Go 手写 Agent:
├── 后端工程师如何理解AI-Agent-附Go实战代码.md
├── ReAct-Loop-实战.md
├── rag-core.md
├── memory-system-guide.md
└── mcp-protocol-guide.md
Spring AI 示例:
├── spring-ai-chatclient-demo
├── spring-ai-rag-demo
├── spring-ai-mcp-demo
└── spring-ai-memory-demo
📝 作者注:本文基于 Go 手写 Agent 的学习经验,总结与 Spring AI 的知识迁移。核心观点:先学原理,再学框架,才能真正掌握 AI Agent 开发。
相关文章:
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)