创建时间: 2026-08-12最后更新: 2026-08-12

1. 演变

这个章节,不是为了让大家学会案例代码怎么编写。

而是让大家感受 Agent 从简单对话案例,逐步演变为复杂任务、节点图、多 Agent 协作的完整过程。从而帮助大家对 Agent 开发祛魅。作为初学者,能够直观的感受到 Agent 开发在做什么事情,并且根据需求的变化,多 Agent 还可以变得更加复杂,如下案例所示

出行建议 Agent
偏好、近期对话和天气结果会在本地浏览器中协同工作
尚未保存偏好或对话
LangGraph 多 Agent 运行轨迹
0 / 9 个步骤完成
等待用户消息
  1. 意图识别
    IntentSchema
  2. 信息抽取
    开放事实模型
  3. 动态澄清
    场景缺口
  4. 偏好学习
    候选偏好
  5. Supervisor
    任务分派
  6. 天气 Agent
    天气子图
  7. 偏好 Agent
    IndexedDB 偏好
  8. 记忆 Agent
    IndexedDB 对话
  9. 建议汇总
    Supervisor
正在读取本地记忆...
graph.ts
graph-state.ts
understanding.ts
travel-schema.ts
travel-prompt.ts
specialist-agents.ts
memory.ts
preference-memory.ts
model.ts
track-node.ts
status.ts
types.ts
chat.tsx
preferences.tsx
floating-label-field.tsx
001
import { END, START, StateGraph } from '@langchain/langgraph'
002
import { createSubagentModel, createTravelModel } from './model'
003
import { filterSafePreferenceCandidates } from './preference-memory'
004
import {
005
createTravelChannels,
006
createTravelInput,
007
formatCompleteTravelInput,
008
type TravelGraphState,
009
} from './graph-state'
010
import {
011
clarifyInformation,
012
extractInformation,
013
recognizeIntent,
014
} from './understanding'
015
import {
016
runMemoryAgent,
017
runPreferenceAgent,
018
runSynthesisAgent,
019
runWeatherAgent,
020
} from './specialist-agents'
021
import { trackNode } from './track-node'
022
import type { TravelStepEvent } from './status'
023
import type {
024
TravelAgentInput,
025
TravelPreferenceCandidate,
026
TravelSession,
027
} from './types'
028
029
export interface TravelGraphResult {
030
reply: string
031
awaitingUserInput: boolean
032
session: TravelSession | null
033
learnedPreferences: TravelPreferenceCandidate[]
034
}
035
036
interface RunTravelGraphOptions {
037
onStepEvent: (event: TravelStepEvent) => void
038
abortSignal?: AbortSignal
039
}
040
041
export async function runTravelGraph(
042
input: TravelAgentInput,
043
{ onStepEvent, abortSignal }: RunTravelGraphOptions,
044
): Promise<TravelGraphResult> {
045
const { model, structuredOutputMethod } = await createTravelModel()
046
const specialistModel = await createSubagentModel()
047
const graph = new StateGraph<TravelGraphState>({
048
channels: createTravelChannels(input),
049
})
050
.addNode('recognizeIntentNode', trackNode('intent', onStepEvent, async (state, signal) => {
051
if (state.intent) return { intent: state.intent }
052
return {
053
intent: await recognizeIntent(
054
state.input,
055
model,
056
structuredOutputMethod,
057
signal,
058
),
059
}
060
}))
061
.addNode('extractInformationNode', trackNode('extract', onStepEvent, async (state, signal) => {
062
if (state.information) {
063
return {
064
information: state.information,
065
preferenceCandidates: state.preferenceCandidates,
066
}
067
}
068
return extractInformation(
069
state.input,
070
state.intent,
071
model,
072
structuredOutputMethod,
073
signal,
074
)
075
}))
076
.addNode('clarifyInformationNode', trackNode('clarify', onStepEvent, async (state, signal) => {
077
const clarification = await clarifyInformation(
078
state,
079
model,
080
structuredOutputMethod,
081
signal,
082
)
083
const awaitingUserInput = clarification.askUserFor.length > 0
084
return {
085
information: clarification.information,
086
preferenceCandidates: clarification.preferenceCandidates,
087
awaitingUserInput,
088
clarificationQuestion: clarification.nextQuestion ?? '',
089
pendingSession: awaitingUserInput ? {
090
id: 'default',
091
intent: state.intent!,
092
information: clarification.information,
093
preferenceCandidates: clarification.preferenceCandidates,
094
updatedAt: Date.now(),
095
} : null,
096
}
097
}))
098
.addNode('learnPreferencesNode', trackNode('learn', onStepEvent, async state => ({
099
preferenceCandidates: filterSafePreferenceCandidates(
100
state.preferenceCandidates,
101
),
102
})))
103
.addNode('returnClarificationNode', async () => ({}))
104
.addNode('readyForDelegationNode', async () => ({}))
105
.addNode('supervisor', trackNode('supervisor', onStepEvent, async state => ({
106
awaitingUserInput: false,
107
clarificationQuestion: '',
108
pendingSession: null,
109
input: formatCompleteTravelInput(state),
110
})))
111
.addNode('weather', trackNode('weather', onStepEvent, async (state, signal) => ({
112
weatherAdvice: await runWeatherAgent(
113
formatCompleteTravelInput(state),
114
specialistModel,
115
onStepEvent,
116
signal,
117
),
118
})))
119
.addNode('preference', trackNode('preference', onStepEvent, async (state, signal) => ({
120
preferenceAdvice: await runPreferenceAgent(
121
formatCompleteTravelInput(state),
122
state.preferences,
123
specialistModel,
124
signal,
125
),
126
})))
127
.addNode('memory', trackNode('memory', onStepEvent, async (state, signal) => ({
128
memoryAdvice: await runMemoryAgent(
129
formatCompleteTravelInput(state),
130
state.conversations,
131
specialistModel,
132
signal,
133
),
134
})))
135
.addNode('synthesis', trackNode('synthesis', onStepEvent, async (state, signal) => ({
136
finalReply: await runSynthesisAgent(state, specialistModel, signal),
137
})))
138
.addEdge('clarifyInformationNode', 'learnPreferencesNode')
139
.addConditionalEdges(
140
'learnPreferencesNode',
141
state => state.awaitingUserInput
142
? 'returnClarificationNode'
143
: 'readyForDelegationNode',
144
['returnClarificationNode', 'readyForDelegationNode'],
145
)
146
.addEdge(START, 'recognizeIntentNode')
147
.addEdge('recognizeIntentNode', 'extractInformationNode')
148
.addEdge('extractInformationNode', 'clarifyInformationNode')
149
.addEdge('supervisor', 'weather')
150
.addEdge('supervisor', 'preference')
151
.addEdge('supervisor', 'memory')
152
.addEdge(['weather', 'preference', 'memory'], 'synthesis')
153
.addEdge('synthesis', END)
154
.addEdge('returnClarificationNode', END)
155
.addEdge('readyForDelegationNode', 'supervisor')
156
.compile({ name: 'travel_advice_multi_agent' })
157
158
const result = await graph.invoke(createTravelInput(input), {
159
signal: abortSignal,
160
}) as unknown as TravelGraphState
161
162
if (result.awaitingUserInput) {
163
return {
164
reply: `为了给出更合适的建议,想再确认一下:${result.clarificationQuestion}`,
165
awaitingUserInput: true,
166
session: result.pendingSession,
167
learnedPreferences: result.preferenceCandidates,
168
}
169
}
170
if (!result.finalReply) throw new Error('出行建议 Agent 没有生成最终结果')
171
return {
172
reply: result.finalReply,
173
awaitingUserInput: false,
174
session: null,
175
learnedPreferences: result.preferenceCandidates,
176
}
177
}
178

这条路线可以压缩成一句话:把「一次对话」逐步变成「可验证的任务」,再把任务变成「可观察的工作流」,最后把工作流拆成「可以协作的专业角色」。

正在加载图示...

图中的每一步都对应前面文章里解决的一类具体问题:信息不完整时先澄清,步骤太多时先规划,流程变长时拆节点,有等待依赖时做串行或并行编排,领域边界稳定后再抽成子图和子 Agent,最后由 Supervisor 负责委派与汇总。

不要把最后的多 Agent 误解成学习的起点。它只是前面所有边界都已经变得清楚之后,系统自然演变出来的组织方式。

订阅后可阅读剩余内容
AI 电子伴侣企业级项目实战
已发布239计划发布120目标已完成199%