Agent开发理解
何为Agent开发
普通的大模型调用很简单,发起一次简单的LLM Api调用即可。Agent区分于普通的LLM Api调用,其关键在于让无状态的LLM Api调用能好用,不仅能够像有状态一样有记忆,还能分析思考,正确执行。
与之延伸而来就有所谓的「记忆(历史会话)管理」、「工具调用(Mcp)」,Agent开发范式(react、plan&excute等),让LLM Api调用能够拆解问题,多次请求,共享会话,最终实现目标。
想要模型能够结合实时知识,进而又出现RAG。基本能力开发好了,想节省token成本,需要利用好LLM 的qv cache,增大缓存占比,就要在「记忆管理」的基础上加上分层管理,区分长短期记忆,将系统提示词、工具列表、长期记忆(claude.md)等不改动的放到提示词前面,以便命中前缀缓存;还有后续在MCP的基础上提出的「Skill」概念,通过渐进式加载的方式减少工具调用token长度。
随着对LLM的进一步利用,单一的Agent可能无法完成协作任务,于是又开始聚焦于 Muti-Agent 开发,引申出了A2A协议以便多Agent间的通信,并基于此区分各种模式(主从、顺序、协作等)…
Agent开发框架核心抽象
Agent开发框架,就是方便开发者去实现上述Agent开发过程中涉及的相关场景的。从22年的chatGPT出现到现在的cursor、claude、codex广泛普及,AI的发展太迅速了,成熟的Agent开发框架也已经出现了不少,如langchain、langgraph、spring-AI等,作为一个站在巨人肩膀的AI应用开发,时常抱着学习态度去了解这些开发框架的实现代码,抛开不同框架间的语言差异不谈,会发现各个框架的核心抽象都基本一致。
Model
最基本的,一次LLM调用需要明确模型厂商的provider、baseUrl等信息,所以需要抽象出Model类,各家厂商模型协议不同,只在Model类中抽象出call方法,上层不关心具体实现。
Message / Prompt
模型调用需要输入,输入可以是提示词,也可以是模型上一轮输出,提示词又可以进行分层,因此对 Message / Prompt 抽象,包含
SystemMessage(系统提示词)UserMessage(用户提示词)AssistantMessage(模型回复)ToolMessage(工具执行结果)PromptTemplate(提示词组装scheme),
Prompt 不是一段文本,而是一组可组合、可追踪、可插入上下文的结构化消息。
Tool
模型不只是聊天,它要能做事,所以出现了 Tool,Mcp/Skill本质上就是一种Tool,模型通过 function calling / tool calling 选择工具,框架负责解析参数、执行工具、把结果塞回对话。
interface Tool {
name: string
description: string
schema: JSONSchema
call(input: unknown): Promise<unknown>
}
Chain / Runnable
实际场景往往不是一次模型调用就能解决问题的,通常需要多个步骤,所以需要把步骤串起来。于是出现了 Chain / Runnable,Runnable把各种执行组件都统一成:
input -> invoke/stream/batch -> output
把 AI 应用从“写业务代码调用模型”变成“声明一条数据处理管线”,类似于工作流,只适用于流程相对固定的场景,还算不上Agent。
Agent
Agent = Model + Prompt + Tools + Loop。Agent 的关键是一个循环:
观察当前状态
-> 让模型决定下一步
-> 如果要调用工具,执行工具
-> 把工具结果加入上下文
-> 再让模型判断
-> 直到完成
经典 ReAct 范式就是:Thought -> Action -> Observation -> Thought -> Final Answer:
class AgentExecutor {
model: ChatModel
tools: Tool[]
memory: Memory
async run(input) {
while (!done) {
const response = await model.call(messages)
if (response.toolCalls) {
const results = await runTools(response.toolCalls)
messages.push(results)
} else {
return response
}
}
}
}
RAG
接着为了引入实时外部知识实现RAG,又出现了 Document / Retriever / VectorStore,具体过程涉及文档加载(DocumentLoader)、分隔(TextSplitter)、向量化(EmbeddingModel)、向量存储(VectorStore)、向量检索(Retriever)、重排序(Reranker)等。其中的 Retriever 的抽象很重要,因为上层 Agent 不应该知道底层是 Milvus、PGVector、Elasticsearch、Redis、Chroma,还是普通 SQL。
interface Retriever {
retrieve(query: string): Promise<Document[]>
}
Graph/Node
在复杂场景下,可以将复杂任务拆解成子任务,由多个Agent串行/并行实现,因而基于此提出图(Graph)的概念。Graph每个节点(Node)是一个Agent(当然也可以是Agent之下更小的运行组件),如规划节点、检索节点、代码执行节点、审核节点、人类确认节点、总结节点、失败重试节点、分支路由节点等。
Checkpoint
每个节点的状态记录通过Checkpoint存储,Checkpoint 保存的是图执行状态快照,如当前 thread id、当前 graph step、当前 state values、各 channel version、下一步待执行节点、历史状态等,让每个具体节点状态都变得可观测。
State
节点间的共享元数据使用 State维护(所有节点共享同一份),每个节点读取 State,每返回 State 的增量更新。一个通用的State定义通常包含:
type AgentState = {
messages: Message[]
taskId: string
plan?: string[]
currentStep?: number
retrievedDocs?: Document[]
toolResults?: ToolResult[]
finalAnswer?: string
}
Memory
想要保存跨轮次、跨任务、跨会话的记忆信息,又引申出Memory,Memory可以细分出很多类型,如长期记忆、短期记忆、规则/流程记忆等。
Hooks/Advisor
同时节点执行前、执行后可以添加一下Hooks/Advisor拦截操作,例如通过这些Hooks来进行人工确认。
实际代码编写过程中,通常是先建出各个节点,然后再建图,添加边,把一个节点到下一个节点作为一条边,例如我参与开源项目spring-ai-aibaba中的一个例子:
/**
* 示例 1: 添加短期内存
*/
public static void addShortTermMemory(ChatClient.Builder chatClientBuilder) throws GraphStateException {
// 创建内存检查点器
MemorySaver checkpointer = new MemorySaver();
SaverConfig saverConfig = SaverConfig.builder()
.register(checkpointer)
.build();
// 定义状态策略
KeyStrategyFactory keyStrategyFactory = () -> {
Map<String, KeyStrategy> keyStrategyMap = new HashMap<>();
keyStrategyMap.put("messages", new AppendStrategy());
return keyStrategyMap;
};
// 创建聊天节点
var chatNode = node_async(state -> {
List<Map<String, String>> messages =
(List<Map<String, String>>) state.value("messages").orElse(List.of());
// 使用 ChatClient 调用 AI 模型
ChatClient chatClient = chatClientBuilder.build();
String response = chatClient.prompt()
.user(messages.get(messages.size() - 1).get("content"))
.call()
.content();
return Map.of("messages", List.of(
Map.of("role", "assistant", "content", response)
));
});
// 构建图
StateGraph stateGraph = new StateGraph(keyStrategyFactory)
.addNode("chat", chatNode)
.addEdge(START, "chat")
.addEdge("chat", END);
// 编译图
CompiledGraph graph = stateGraph.compile(
CompileConfig.builder()
.saverConfig(saverConfig)
.build()
);
// 第一轮对话
RunnableConfig config = RunnableConfig.builder()
.threadId("conversation-1")
.build();
graph.invoke(Map.of("messages", List.of(
Map.of("role", "user", "content", "你好!我是 Bob")
)), config);
// 第二轮对话(使用相同的 threadId)
graph.invoke(Map.of("messages", List.of(
Map.of("role", "user", "content", "我的名字是什么?")
)), config);
// AI 将能够记住之前的对话,回答 "Bob"
System.out.println("Short-term memory example executed");
}
A2A
如果说Tool 是 Agent 调用函数,那么A2A 是 Agent 调用另一个 Agent。一个具有多Agent节点的Graph可以理解为本地的A2A 调用,当然也可以通过http、rpc实现远程A2A 调用,例如配合nacos注册中心进行Agent级别的服务注册、发现。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)