navi-go

3. 智能体设计

NaviGo 采用专业化多智能体模式,每个智能体负责单一规划领域。智能体通过共享的强类型状态通信,而非直接消息传递。这种设计解耦了智能体实现,支持独立测试,并确保主管路由器可以通过检查状态完整性来推理规划进度。

3.1 智能体分类

智能体 文件 职责 是否需要 LLM?
需求解析器 src/agents/requirement-parser.agent.ts 从自然语言提取结构化旅行字段
表单补全器 src/agents/form-completer.agent.ts 校验完整性;组装 UserRequest 或发起追问
风险守卫 src/agents/risk-guard.agent.ts 通过规则 + LLM 扫描输入/输出的提示注入与不安全内容
偏好 src/agents/preference.agent.ts 从自由文本请求提取结构化偏好
目的地 src/agents/destination.agent.ts 带推荐理由的目的地候选建议
行程 src/agents/itinerary.agent.ts 通过 LLM 构建按天行程,获取航班与天气
预算 src/agents/budget.agent.ts 通过 LLM 估算总费用并标记预算问题
打包 src/agents/packing.agent.ts 通过 LLM 生成天气/活动感知的打包清单
计划合成器 src/agents/plan-synthesizer.agent.ts 通过 LLM 组装最终计划产物并应用输出护栏

3.2 智能体契约

每个智能体遵循相同的函数签名:

async (state: PlannerState, deps?: AgentDependencies): Promise<Partial<PlannerState>>

3.3 需求解析器智能体

目的:将原始自然语言请求转换为部分结构化的旅行字段。

实现src/agents/requirement-parser.agent.ts):

输入:  state.naturalLanguage
输出:  parsedRequest, decisionLog

决策日志证据

3.4 表单补全器智能体

目的:校验提取的字段是否足以组装完整的 UserRequest,或生成澄清式问题。

实现src/agents/form-completer.agent.ts):

输入:  state.parsedRequest, state.naturalLanguage
输出:  userRequest(若完整), pendingQuestions(若不完整), decisionLog

决策日志证据

3.5 风险守卫智能体

目的:对抗性输入与不安全输入的第一道与最后一道防线。

实现src/agents/risk-guard.agent.ts):

输入:  state.userRequest.requestText, state.finalPlan
输出:  safetyFlags, decisionLog

检测到的模式(规则层):

不安全输出模式

3.6 偏好智能体

目的:将非结构化用户意图转换为结构化偏好画像。

实现src/agents/preference.agent.ts):

使用 model.withStructuredOutput(PreferencesSchema) 与提示词模板:

PreferencesSchema = z.object({
  travelStyle: z.enum(["relaxed", "balanced", "packed"]),
  prioritizedInterests: z.array(z.string().min(1)),
  preferredPace: z.enum(["slow", "normal", "fast"]),
  accommodationPreference: z.enum(["budget", "midrange", "premium"]),
});

若用户在请求中显式提供了 interests,则覆盖模型提取的兴趣,确保用户意图不会被静默改写。

决策日志证据

3.7 目的地智能体

目的:生成带支持理由的排序目的地候选。

实现src/agents/destination.agent.ts):

使用 model.withStructuredOutput(DestinationSuggestionsSchema),候选包含:

DestinationCandidateSchema = z.object({
  name: z.string(),
  country: z.string(),
  iataCode: z.string().regex(/^[A-Z]{3}$/).nullable(),
  cityCode: z.string().regex(/^[A-Z]{3}$/).nullable(),
  rationale: z.string(),
});

回退逻辑:若用户提供了 destinationHintdestinationCityCodedestinationIata,当该目的地未出现在生成列表中时,智能体会将其作为显式回退候选前置。最终候选列表限制为 3 条。

提示词上下文

3.8 行程智能体

目的:基于真实航班与天气数据构建具体的按天行程。

实现src/agents/itinerary.agent.ts):

航班搜索

originIata 与目的地 IATA 可用,调用 searchFlightOffers()(Duffel 集成)两次:

航班选项通过 pickRecommendedFlightOption()src/agents/flight-option-selection.ts)的 O(n) min-find reduce 进行排序,优先选择抵达时间不晚于旅行开始日期的航班,然后按更早抵达、更低价格、更早起飞排序。

天气风险评估

调用 fetchWeatherRiskSummary()(Open-Meteo 集成)获取每日预报与风险等级:

活动生成

使用 model.withStructuredOutput(ItineraryDraftSchema) 生成行程。提示词包含:

对 LLM 的指令:

决策日志证据

3.9 预算智能体

目的:估算总旅行费用并判断是否在用户预算范围内。

实现src/agents/budget.agent.ts):

使用 model.withStructuredOutput(BudgetAssessmentSchema),提示词包含:

LLM 返回:

BudgetAssessmentSchema = z.object({
  estimatedTotal: z.number().nonnegative(),
  budgetLimit: z.number().positive(),
  withinBudget: z.boolean(),
  optimizationTips: z.array(z.string()),
});

风险标记BUDGET_EXCEEDED,当 estimatedTotal > budgetLimit 时触发。

决策日志证据

3.10 打包智能体

目的:基于天气预报与计划活动生成上下文相关的打包清单。

实现src/agents/packing.agent.ts):

使用 model.withStructuredOutput(PackingListSchema),提示词包含:

LLM 返回简洁、去重的必备物品打包清单。

决策日志证据

3.11 计划合成器智能体

目的:将所有智能体输出组装为最终、已校验的计划产物。

实现src/agents/plan-synthesizer.agent.ts):

安全拒答路径

safetyFlags 中存在 BLOCKED_PROMPT_INJECTION,合成器生成拒答摘要而非旅行计划。

正常路径

使用 model.withStructuredOutput(PlanSynthesisSchema) 生成:

然后组装最终产物:

输出护栏

返回前,合成器对生成的摘要运行 detectUnsafeOutput()。任何不安全模式都会追加到 safetyFlags

最终计划通过 Zod 的 FinalPlanSchema 校验后才写入状态。

3.12 智能体依赖注入

智能体接受依赖以支持使用 fake 和 stub 进行测试:

智能体 依赖
requirement_parser { model: ChatOpenAI }
form_completer { model: ChatOpenAI }
risk_guard { model: ChatOpenAI }
preference_agent { model: ChatOpenAI }
destination_agent { model: ChatOpenAI }
itinerary_agent { model: ChatOpenAI, searchFlights, fetchWeather }
budget_agent { model: ChatOpenAI }
packing_agent { model: ChatOpenAI }
plan_synthesizer { model: ChatOpenAI }

默认依赖使用生产实现(真实 OpenAI 模型、真实 Duffel/Open-Meteo API),测试注入 FakeStructuredChatModel 和 stub 工具函数。

3.13 决策日志

每个智能体向 state.decisionLog 追加一条 DecisionLogEntry

{
  agent: string,
  inputSummary: string,
  keyEvidence: string[],
  outputSummary: string,
  riskFlags: string[],
  timestamp: string, // ISO 8601
}

这创建了一个不可变的、仅追加的审计追踪,记录每次规划决策、考虑的证据以及标记的风险。日志在 API 响应中返回,并在 CLI 输出中打印。