深度对比ChatModel、Chain、Agent、Memory四大组件,掌握场景选型和组合使用策略
时间:30分钟 | 难度:⭐⭐⭐ | Week 2 Day 12
官方Example信息
- GitHub链接:langchain4j-examples
- 相关Example:各组件的示例代码
- 所在路径:src/main/java/dev/langchain4j/examples/
- 难度:中级 ⭐⭐⭐
学习目标
- 理解四大组件的核心区别
- 掌握组件选型的决策方法
- 了解各组件的性能和成本差异
- 学会多个组件的组合使用
- 能根据场景做出最优选择
- 理解组件间的依赖关系
🚀 快速入门:四大组件概览
组件关系架构图
┌────────────────────────────────────────────┐
│ LangChain4J 核心组件 │
├────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ChatModel │ │ Memory │ │
│ │ 基础对话 │ │ 记忆管理 │ │
│ └─────┬────┘ └────┬─────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────┐ │
│ │ Chain │ │
│ │ 多步骤编排(固定流程) │ │
│ └─────────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ Agent │ │
│ │ 自主决策(动态流程) │ │
│ │ = ChatModel + Tool + 循环│ │
│ └──────────────────────────┘ │
│ │
│ 复杂度:ChatModel < Chain < Agent │
│ 灵活度:ChatModel < Chain < Agent │
│ 成本: ChatModel < Chain < Agent │
└────────────────────────────────────────────┘
深度讲解
1️⃣ 核心特性对比表
| 维度 | ChatModel | Chain | Agent | Memory |
|---|---|---|---|---|
| 本质 | 单次LLM调用 | 多步骤编排 | 自主决策循环 | 状态管理 |
| 流程控制 | 无 | 人类编排 | LLM自主 | 辅助角色 |
| 工具调用 | 不支持 | 手动集成 | 自动调用 | 不适用 |
| 使用场景 | 简单问答 | 固定工作流 | 复杂推理 | 多轮对话 |
| 延迟 | 100-500ms | 300-2000ms | 500-10000ms | +50ms |
| 成本 | 低(1次调用) | 中(N次调用) | 高(N×M次调用) | 额外Token |
| 可控性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 灵活性 | ⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 实现难度 | 简单 | 中等 | 复杂 | 中等 |
2️⃣ 同一任务的三种实现对比
场景:用户问"分析这段代码的问题"
方式A:ChatModel 直接调用
// 最简单,一次调用搞定
@Service
public class SimpleChatModelApproach {
@Autowired
private ChatLanguageModel model;
public String analyzeCode(String code) {
String prompt = """
请分析以下Java代码的问题(包括安全、性能、风格):
```java
%s
```
""".formatted(code);
return model.generate(prompt);
}
}
// 优点:简单、快速、成本低
// 缺点:分析深度取决于LLM能力,无法调用外部工具
// 延迟:~300ms | 成本:~200 tokens | 准确性:⭐⭐⭐
方式B:Chain 多步编排
// 分步骤执行,每步专注一个方面
@Service
public class ChainApproach {
@Autowired
private ChatLanguageModel model;
public CodeReviewResult analyzeCode(String code) {
// Step 1: 安全分析
String securityPrompt = "请只分析代码的安全问题:\n" + code;
String security = model.generate(securityPrompt);
// Step 2: 性能分析
String performancePrompt = "请只分析代码的性能问题:\n" + code;
String performance = model.generate(performancePrompt);
// Step 3: 风格分析
String stylePrompt = "请只分析代码的风格问题:\n" + code;
String style = model.generate(stylePrompt);
// Step 4: 综合报告
String summaryPrompt = """
基于以下分析,给出综合报告和评分:
安全:%s
性能:%s
风格:%s
""".formatted(security, performance, style);
String summary = model.generate(summaryPrompt);
return new CodeReviewResult(security, performance, style, summary);
}
}
// 优点:每步分析更深入,结果结构化
// 缺点:固定4步,代码简单时浪费,需要人工编排
// 延迟:~1200ms (4×300ms) | 成本:~800 tokens | 准确性:⭐⭐⭐⭐
方式C:Agent 自主决策
// Agent自己决定分析什么、怎么分析
public interface CodeReviewAgent {
@SystemMessage("""
你是代码审查专家。
根据代码的特点决定审查重点。
使用工具进行深入分析。
""")
String analyzeCode(@UserMessage String request);
}
@Service
public class AgentApproach {
@Autowired
private CodeReviewAgent agent;
public String analyzeCode(String code) {
return agent.analyzeCode("请审查:\n" + code);
}
}
// 优点:智能决策,按需分析,灵活强大
// 缺点:不可预测,可能多次调用工具,成本高
// 延迟:~3000ms (多轮) | 成本:~2000 tokens | 准确性:⭐⭐⭐⭐⭐
🔍 Agent为什么慢、贵、但准?— LangChain4J 实现原理拆解
上面三种方式,ChatModel 一次调用就结束,Chain 是人工编排的固定多次调用,而 Agent 的行为不可预测 — 它自己决定调几次、调什么。理解 Agent 的内部实现,你就能明白性能差异的根源。
核心机制:动态代理 + while循环 + 反射
AiServices.build() 做了什么?
你写的代码:
┌─────────────────────────────────────────┐
│ CodeReviewAgent agent = AiServices │
│ .builder(CodeReviewAgent.class) │
│ .chatLanguageModel(model) │
│ .tools(new MyTools()) │
│ .build(); │
└────────────────┬────────────────────────┘
│ 内部等价于
▼
┌─────────────────────────────────────────┐
│ Proxy.newProxyInstance( │
│ classLoader, │
│ new Class[]{CodeReviewAgent.class}, │
│ new InvocationHandler(model, tools) │ ← 核心!
│ ); │
└─────────────────────────────────────────┘
所以 agent.analyzeCode() 实际上调用的是 InvocationHandler.invoke()
InvocationHandler 内部:Agent循环的"心脏"
/**
* 伪代码:AiServices 内部的 Agent 循环
* 这就是 Agent 比 ChatModel 慢和贵的根本原因
*/
class AiServiceInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// Step 1: 构建初始消息(和ChatModel一样)
memory.add(SystemMessage.from("你是代码审查专家..."));
memory.add(UserMessage.from(userInput));
// Step 2: Agent循环 ← ChatModel没有这个循环!
for (int i = 0; i < maxIterations; i++) {
// 2a. 调用LLM(每轮都消耗Token → 成本叠加!)
Response<AiMessage> response = model.generate(
memory.messages(), // 消息越来越长 → Token越来越多
toolSpecs // 工具定义(每次都发 → 额外Token)
);
AiMessage aiMessage = response.content();
memory.add(aiMessage);
// 2b. 判断是否结束
if (!aiMessage.hasToolExecutionRequests()) {
return aiMessage.text(); // ← ChatModel走到这里就结束了
}
// 2c. 执行工具(Agent独有的步骤)
for (ToolExecutionRequest req : aiMessage.toolExecutionRequests()) {
ToolExecutor executor = toolExecutors.get(req.name());
// JSON参数 → 反射调用你的@Tool方法 → 结果字符串
String result = executor.execute(req, memoryId);
memory.add(ToolExecutionResultMessage.from(req, result));
}
// → 回到循环顶部,再次调用LLM
}
}
}
三种方式的内部调用对比
ChatModel(方式A):
model.generate(messages)
↓
return 结果
──────────────────────────
LLM调用:1次 | Token消耗:200 | 延迟:300ms
Chain(方式B):
model.generate(安全prompt) → 结果1
model.generate(性能prompt) → 结果2
model.generate(风格prompt) → 结果3
model.generate(综合prompt) → 最终结果
──────────────────────────
LLM调用:4次(固定) | Token消耗:800 | 延迟:1200ms
Agent(方式C)— AiServices 内部:
┌─ 循环第1轮 ─────────────────────────────────────┐
│ model.generate(messages, toolSpecs) → 调用工具A │
│ toolA.execute() → 结果放回messages │
├─ 循环第2轮 ─────────────────────────────────────┤
│ model.generate(messages, toolSpecs) → 调用工具B │ ← 消息更长了
│ toolB.execute() → 结果放回messages │
├─ 循环第3轮 ─────────────────────────────────────┤
│ model.generate(messages, toolSpecs) → 不调用工具 │ ← 消息最长
│ return 文本结果 │
└──────────────────────────────────────────────────┘
LLM调用:3次(不固定) | Token消耗:200+500+800=1500 | 延迟:3000ms
注意:Agent每轮的Token消耗递增!因为消息历史越来越长。
@Tool → JSON Schema:LLM如何"看见"工具
// 你定义的工具:
@Tool("检查代码中的安全漏洞,如SQL注入、XSS等")
public String checkSecurity(@P("要检查的Java源代码") String code) { ... }
// LangChain4J 转换后发给 LLM API 的 JSON:
// {
// "type": "function",
// "function": {
// "name": "checkSecurity",
// "description": "检查代码中的安全漏洞,如SQL注入、XSS等",
// "parameters": {
// "type": "object",
// "properties": {
// "code": { "type": "string", "description": "要检查的Java源代码" }
// },
// "required": ["code"]
// }
// }
// }
// LLM 基于这个 JSON 描述决定是否调用工具
// → 所以 @Tool 的描述质量直接影响 Agent 的决策准确性!
总结:三种方式的实现本质对比
┌───────────┬─────────────────────┬──────────────────┐
│ │ 实现机制 │ 性能影响 │
├───────────┼─────────────────────┼──────────────────┤
│ ChatModel │ 直接调用 LLM API │ 最快,成本固定 │
│ │ 无循环,无工具 │ │
├───────────┼─────────────────────┼──────────────────┤
│ Chain │ 开发者编写多次 │ N次调用,成本线性 │
│ │ model.generate() │ 可预测 │
│ │ 手动编排顺序 │ │
├───────────┼─────────────────────┼──────────────────┤
│ Agent │ JDK动态代理 │ 不可预测的多次调用 │
│(AiServices)│ + while(hasTools) │ Token累积递增 │
│ │ + 反射执行@Tool │ 最慢但最智能 │
└───────────┴─────────────────────┴──────────────────┘
一句话:Agent 之所以准确但昂贵,是因为它用 while 循环
让 LLM 反复思考和调用工具,而每轮的消息历史都在增长。
💡 深入理解:完整的 AiServices 五层实现原理(动态代理、ToolSpecification转换、InvocationHandler引擎、调用链时序图、ToolExecutor反射)见 [[2011-Agent设计原理和基础实战#AiServices的工作原理|Agent实现原理深度解析]]。
3️⃣ 性能基准测试数据
测试环境:GPT-4o-mini,100次运行取平均值
任务:分析一段50行的Java代码
┌───────────────────────────────────────────┐
│ 性能对比(100次平均) │
├──────────┬──────────┬─────────┬───────────┤
│ 指标 │ ChatModel│ Chain │ Agent │
├──────────┼──────────┼─────────┼───────────┤
│ 平均延迟 │ 280ms │ 1,120ms │ 2,800ms │
│ P99延迟 │ 520ms │ 2,100ms │ 8,500ms │
│ Token消耗 │ 180 │ 720 │ 1,800 │
│ 成本/次 │ ¥0.003 │ ¥0.012 │ ¥0.030 │
│ 准确性 │ 72% │ 85% │ 93% │
│ 召回率 │ 60% │ 82% │ 91% │
└──────────┴──────────┴─────────┴───────────┘
结论:
- ChatModel:速度最快,成本最低,但分析不够深入
- Chain: 性价比最高,适合已知流程
- Agent: 最准确,但成本和延迟最高
4️⃣ 决策树:我应该用哪个?
你的任务是什么?
│
├─ 简单问答(一问一答)
│ └─→ ChatModel ✅
│ 例:翻译、总结、分类、格式转换
│
├─ 多步固定流程
│ │
│ ├─ 步骤已知且固定?
│ │ └─→ Chain ✅
│ │ 例:代码生成→分析→优化
│ │
│ └─ 需要对话上下文?
│ └─→ Chain + Memory ✅
│ 例:多轮问答助手
│
├─ 需要使用外部工具?
│ │
│ ├─ 工具调用顺序固定?
│ │ └─→ Chain(手动调用工具)✅
│ │
│ └─ 工具调用需要LLM决策?
│ └─→ Agent ✅
│ 例:智能代码审查、自主研究
│
└─ 需要记住历史对话?
└─→ 任何组件 + Memory ✅
5️⃣ 组合使用模式
/**
* 模式1:ChatModel + Memory = 多轮对话
*/
@Service
public class ChatWithMemory {
public String chat(String userId, String message) {
ChatMemory memory = getOrCreateMemory(userId);
memory.add(UserMessage.from(message));
String response = model.generate(memory.messages()).content().text();
memory.add(AiMessage.from(response));
return response;
}
}
/**
* 模式2:Chain + Memory = 有记忆的工作流
*/
@Service
public class ChainWithMemory {
public String process(String userId, String input) {
ChatMemory memory = getOrCreateMemory(userId);
// Step 1: 理解上下文
memory.add(UserMessage.from(input));
String understanding = model.generate(memory.messages()).content().text();
// Step 2: 基于理解执行任务
String result = executeTask(understanding);
memory.add(AiMessage.from(result));
return result;
}
}
/**
* 模式3:Agent + Memory = 有记忆的智能体
*/
public interface SmartAgent {
@SystemMessage("你是智能助手,记住用户的偏好和历史")
String assist(@UserMessage String request);
}
@Bean
public SmartAgent smartAgent(ChatLanguageModel model) {
return AiServices.builder(SmartAgent.class)
.chatLanguageModel(model)
.tools(new MyTools())
.chatMemory(MessageWindowChatMemory.withMaxMessages(50))
.build();
}
/**
* 模式4:Agent + Chain = 智能编排
* Agent决定流程,Chain执行每个步骤
*/
@Service
public class AgentOrchestrator {
@Autowired
private CodeReviewAgent agent; // Agent做决策
public ReviewResult orchestrate(String code) {
// Agent分析代码,决定需要哪些检查
String plan = agent.analyze("分析代码,列出需要的检查项:\n" + code);
// Chain执行具体检查
if (plan.contains("安全")) {
securityChain.execute(code);
}
if (plan.contains("性能")) {
performanceChain.execute(code);
}
return combineResults();
}
}
6️⃣ 成本对比和优化
成本公式:
总成本 = LLM调用次数 × 平均Token数 × Token单价
┌──────────────────────────────────────────────┐
│ 成本对比(单次任务) │
├───────────┬──────────┬──────────┬─────────────┤
│ 组件 │ 调用次数 │ 总Token │ 成本(GPT-4o)│
├───────────┼──────────┼──────────┼─────────────┤
│ ChatModel │ 1次 │ ~200 │ ¥0.006 │
│ Chain(3步)│ 3次 │ ~600 │ ¥0.018 │
│ Chain(5步)│ 5次 │ ~1000 │ ¥0.030 │
│ Agent │ 3-8次 │ ~1500 │ ¥0.045 │
│ Agent+Mem │ 3-8次 │ ~2000 │ ¥0.060 │
└───────────┴──────────┴──────────┴─────────────┘
优化策略:
├─ 简单任务用ChatModel(成本/10)
├─ 复杂任务用Chain而非Agent(成本/3)
├─ 缓存重复查询(@Cacheable)
├─ 简单步骤用mini模型(成本/20)
└─ 限制Memory大小(减少Token消耗)
// 成本优化示例:混合模型策略
@Service
public class CostOptimizedService {
@Autowired
@Qualifier("fastModel") // gpt-4o-mini
private ChatLanguageModel fastModel;
@Autowired
@Qualifier("strongModel") // gpt-4o
private ChatLanguageModel strongModel;
public String process(String query) {
// 简单任务用便宜模型
if (isSimpleQuery(query)) {
return fastModel.generate(query); // 成本/20
}
// 复杂任务用强大模型
return strongModel.generate(query);
}
private boolean isSimpleQuery(String query) {
return query.length() < 100 && !query.contains("分析") && !query.contains("审查");
}
}
💻 实战:综合选型测试
/**
* 综合对比测试:同一任务用不同组件实现
*/
@Service
public class ComponentBenchmark {
private static final Logger log = LoggerFactory.getLogger(ComponentBenchmark.class);
public void runBenchmark(String code) {
// 测试1:ChatModel
long t1 = System.currentTimeMillis();
String r1 = chatModelApproach(code);
long d1 = System.currentTimeMillis() - t1;
log.info("[ChatModel] 耗时:{}ms 结果长度:{}", d1, r1.length());
// 测试2:Chain
long t2 = System.currentTimeMillis();
String r2 = chainApproach(code);
long d2 = System.currentTimeMillis() - t2;
log.info("[Chain] 耗时:{}ms 结果长度:{}", d2, r2.length());
// 测试3:Agent
long t3 = System.currentTimeMillis();
String r3 = agentApproach(code);
long d3 = System.currentTimeMillis() - t3;
log.info("[Agent] 耗时:{}ms 结果长度:{}", d3, r3.length());
// 总结
log.info("性能排序:ChatModel({}ms) < Chain({}ms) < Agent({}ms)", d1, d2, d3);
log.info("详细度排序:ChatModel({}) < Chain({}) < Agent({})",
r1.length(), r2.length(), r3.length());
}
}
🔧 最佳实践
选型总结
┌──────────────────────────────────────┐
│ 组件选型速查表 │
├──────────────────────────────────────┤
│ 需求 │ 推荐组件 │
├───────────────────┼──────────────────┤
│ 翻译/总结/分类 │ ChatModel │
│ 多轮对话 │ ChatModel+Memory │
│ 代码生成→分析→优化│ Chain │
│ 固定流程+对话记忆 │ Chain+Memory │
│ 需要调用API/数据库 │ Agent │
│ 复杂推理和决策 │ Agent │
│ 智能助手(全能) │ Agent+Memory │
│ 性能敏感 │ ChatModel │
│ 成本敏感 │ ChatModel/Chain │
│ 准确性优先 │ Agent │
└───────────────────┴──────────────────┘
学习成果检查:
- 能说出四大组件的核心区别
- 能根据场景选择合适的组件
- 能评估不同方案的成本和性能
- 能组合使用多个组件
- 能做出架构级别的技术决策
下一步:学习项目优化,将所学组件应用到生产级系统中。