Skip to main content
Glama
Varma904

Agentic Travel Recommendations Service

by Varma904

智能体旅行推荐服务

本项目是一个基于 TypeScript 和 Node.js 的多租户旅行推荐服务概念验证。它通过 REST API、Streamable HTTP MCP 端点和命令行接口(CLI)暴露共享的推荐能力。

主要特性

  • 用于健康检查、会员资料和推荐的 REST API

  • Streamable HTTP MCP 端点

  • MCP 工具:get_member_profile

  • MCP 工具:get_recommendations

  • 权威的、从会员推导出的租户解析

  • 合作伙伴特定的推荐上限

  • 合作伙伴特定的类别排除

  • 确定性推荐生成

  • 合作伙伴配置的故障关闭行为

  • 请求 ID 和结构化 JSON 日志

  • 最小化 CLI 演示

  • 多阶段 Docker 构建

  • 自动化测试

Related MCP server: Agentic Travel Recommendations API

架构概览

REST / MCP / CLI
       |
       v
RecommendationService
       |
       v
MemberDataService
       |
       | member.partnerId
       v
PartnerConfigurationService
       |
       v
CandidateGenerator
       |
       v
RecommendationPolicy
       |
       | exclusions then cap
       v
Final Recommendations

调用者仅提供 memberId;他们不选择权威的 partnerId。会员资料决定合作伙伴配置,REST、MCP 和 CLI 都复用同一个业务层。

快速开始

npm ci
npm run dev

服务默认在 http://localhost:3000 可用。

类型检查

npm run typecheck
npm run typecheck:test
npm run typecheck:all

测试

npm test

当前已验证的基线是 6 个文件中的 46 个通过测试。

生产构建

npm run build
npm start

REST API

GET /health
GET /api/members/:memberId
GET /api/recommendations/:memberId

示例请求:

curl http://localhost:3000/api/members/MEMBER-001
curl http://localhost:3000/api/recommendations/MEMBER-001

MCP

MCP 服务器通过以下方式暴露:

POST /mcp

它提供以下工具:

  • get_member_profile

  • get_recommendations

两个工具都只接受会员标识符:

{
  "memberId": "MEMBER-001"
}

该实现使用官方的 @modelcontextprotocol/sdk Streamable HTTP 传输。调用者不提供 partnerId;它从权威会员资料中解析。

CLI

npm run cli -- MEMBER-001

演示会员:

  • MEMBER-001 → BANK_A

  • MEMBER-002 → BANK_B

  • MEMBER-003 → CREDIT_UNION_C

Docker

docker build -t agentic-travel-recommendations .
docker run --rm -p 3000:3000 agentic-travel-recommendations

该镜像使用多阶段构建、Node 24 运行时和非 root 运行时用户。HTTP 服务器处理优雅关闭信号。

章节 A — 架构与权衡

架构概述

该服务是一个无状态的 TypeScript 和 Node.js 应用,通过 REST、Streamable HTTP MCP 和 CLI 暴露相同的推荐工作流。每个传输都验证其输入并委托给共享的 RecommendationService;传输处理器本身不实现合作伙伴策略。

权威租户流程如下:

memberId
→ MemberDataService
→ MemberProfile.partnerId
→ PartnerConfigurationService
→ CandidateGenerator
→ RecommendationPolicy
→ final recommendations

调用者提供 memberId,从不选择权威的 partnerId。由 MemberDataService 返回的会员资料决定检索哪个合作伙伴配置。两个上游服务还会将响应中嵌入的身份与请求的身份相关联,并且 RecommendationService 在生成前执行额外的合作伙伴身份检查。

候选生成有意独立于合作伙伴策略。确定性生成器首先根据会员资料生成原始候选;然后通用策略层按顺序移除 excludedCategories 中的候选并应用 recommendationCap。只返回最终推荐结果。在此概念验证中,会员数据服务和合作伙伴配置服务是模拟的,并且合作伙伴配置访问是只读的。

设计权衡

正确性优先于可用性。 如果权威合作伙伴配置缺失、不可用、模式无效或身份不匹配,请求将故障关闭(fail closed)。服务不会替换为宽松的默认值或返回不受限制的推荐。这可能会在上游故障期间降低可用性,但可防止推荐偏离正确的租户策略。

新配置优先于缓存。 首个版本为每个推荐请求检索合作伙伴配置,而不是增加缓存基础设施。这使行为保持简单,并确保每个成功的请求都使用当前策略。它接受额外的上游延迟和负载;只有在实测性能证明一致性权衡合理时,短期缓存才适合后续引入。

确定性生成优先于外部 LLM。 候选生成是可复现、可测试、零成本且运维可预测的。这限制了个性化的复杂程度,但使策略行为和评估结果易于验证。未来的 LLM 或排序组件可以替换候选生成,而无需更改确定性策略执行。

处理合作伙伴配置变更

合作伙伴配置服务是一个只读依赖。如果合作伙伴将其推荐上限从无限改为 3,或将 cruise 添加到 excludedCategories,推荐服务无需更改代码,也无需特定于租户的分支。下一个成功的请求会读取当前配置,通用策略逻辑会应用新的排除项和上限值。

只要配置仍然与现有模式兼容,应用程序就不需要重新部署。如果后续引入配置缓存,它必须具有刻意设置的短 TTL 或可靠的失效策略,因为过期的配置可能暂时违反合作伙伴的当前策略。

章节 B — 生产就绪与事件响应

事件处理手册条目

场景: 一位会员报告称,AI 礼宾服务显示了一条邮轮推荐,尽管其合作伙伴排除了邮轮。

  1. 识别并关联。 从报告中获取请求或关联 ID(如果可用),并找到相应的结构化日志。记录 operation、memberId、解析后的权威 partnerId、resultCode 以及适用的 HTTP 状态。旅行历史和推荐负载故意不记录,因此使用标识符和结果元数据进行关联。

  2. 验证权威租户。 通过 MemberDataService 检索受影响的会员,并确认请求的 memberId 等于返回的 member.memberId。仅从 member.partnerId 推导租户。不要信任由前端、MCP 调用者、查询参数或支持报告提供的合作伙伴 ID。

  3. 验证合作伙伴配置。 使用 member.partnerId 检索配置,然后确认 configuration.partnerId === member.partnerId。检查 excludedCategories 和 recommendationCap,并确定 cruise 是否在当前权威配置中被排除。缺失、不可用、格式错误或身份不匹配的配置必须使服务故障关闭,而不是使用宽松的默认值。

  4. 重现流水线。 让该会员经历相同的推荐工作流。原始 CandidateGenerator 输出中出现邮轮本身不是缺陷,因为生成过程有意忽略合作伙伴策略。验证 RecommendationPolicy 处理 原始候选 → 移除排除类别 → 应用推荐上限 → 最终推荐,并确认最终结果中没有邮轮。

  5. 隔离故障位置。 如果邮轮出现在原始候选中但没有出现在最终推荐中,则策略运行正常。调查过时的客户端响应、与错误会员关联的响应、绕过预期工作流的其他消费者或端点,或者报告时间与当前配置之间的差异。如果邮轮通过了 RecommendationPolicy,请检查类别比较或规范化、权威配置内容和身份,以及最近的策略更改或回归。

  6. 遏制。 如果无法建立或安全重现正确的合作伙伴策略,则应故障关闭,而不是返回可能不合规的推荐。不要尝试从此应用程序修改只读的合作伙伴配置服务。

  7. 修复并验证。 在负责的层中纠正缺陷,并添加重现确切失败的回归测试。运行:

    npm run typecheck:all
    npm test
    npm run build

    验证受影响的合作伙伴、至少一个不受影响的租户、REST 行为以及相关的 MCP 行为。

  8. 跟进。 记录根本原因、受影响的合作伙伴和会员范围、影响窗口、补救措施、回归覆盖范围和预防措施。

部分 B2 — 必需推理问题

AI 编码助手可能合理地生成一个实现,该实现使用 Zod 验证上游会员和合作伙伴记录,但从不将返回的身份与请求的身份相关联。代码将是类型安全的,模式验证和快乐路径测试会通过,表面审查会看到合理的防御性验证。缺失的跨租户不变量仍然会带来严重的策略风险。

例如,MEMBER-001 属于 BANK_A。RecommendationService 请求 BANK_A 的配置,但有缺陷或路由错误的上游服务返回了一个完全模式有效的 BANK_B 配置,具有无限上限且无类别排除。Zod 正确接受其形状,但将该策略应用于 MEMBER-001 可能会绕过 BANK_A 的限制。

我会通过对抗性回归测试来捕获这个问题:在故意故障的配置服务替身返回有效的 BANK_B 配置时请求 BANK_A。我会期望 InvalidUpstreamDataError,断言没有产生推荐结果,并明确验证 CandidateGenerator.generate 未被调用。我会添加相应的会员数据测试,证明请求的 memberId 必须等于返回的 member.memberId。

在采用 AI 生成的代码之前,我会追踪权威性和执行顺序,而不是仅仅依赖类型。我会验证选择租户的是 member.partnerId 而不是调用者输入;两个返回的身份都与其权威请求匹配;缺失、不可用或不匹配的配置在不提供宽松回退的情况下故障关闭。我还会确认在策略身份安全建立之前无法开始生成,并且负面的对抗性测试与正常的快乐路径一起覆盖这些情况。

章节 C — AI 使用日志

交互 1 — 架构审查

我问了什么

我请 AI 编码助手审查该挑战,并帮助设计一个可由一名工程师实际实施的最小架构。请求的范围包括领域模型、模拟的上游服务、推荐逻辑、REST、MCP、CLI、自动化测试和容器化,同时避免概念验证不需要的基础设施。

AI 提供了什么

它提议分离领域模型、服务契约、模拟的上游服务、候选生成、合作伙伴策略、编排和传输适配器。它最初建议将 stdio 作为最简单的 MCP 传输。

我保留、更改或拒绝的内容

我保留了分层分离,因为它让 REST、MCP 和 CLI 调用一个业务层,而不是独立实现规则。我拒绝了将 stdio 作为主要 MCP 传输,并将设计重定向到官方 MCP SDK 在 POST /mcp 的 Streamable HTTP 传输。任务描述了一个代理应发现和调用的内部 API,而 HTTP 适合容器化服务架构。我在将提议与任务的集成要求进行比较后做出了这一选择,而不是自动接受最简单的选项。

交互 2 — 增量实现

我问了什么

我没有在一个提示中要求整个应用程序。我将实现划分为有界的步骤:领域模型、模式和错误、上游契约和模拟、候选生成、推荐策略、编排、REST、MCP、CLI、可观测性和 Docker。每一步之后,我都会审查报告的行为,并要求在继续之前进行类型检查和测试。

AI 提供了什么

助手实现了每个有界的组件,并附有针对性的测试,报告了更改的文件和验证结果。这使得单个设计选择可见且可审查,而不是隐藏在大型生成补丁中。

我保留、更改或拒绝的内容

我保留了共享的 RecommendationService、确定性的 CandidateGenerator、独立的 RecommendationPolicy、成员派生的租户解析、只读配置契约,以及共享的 REST/MCP/CLI 业务逻辑。这种结构使租户策略可以独立测试,并防止特定传输的规则实现。我还特意保留了确定性生成,而不是添加外部 LLM 依赖。评估聚焦于服务设计和策略执行,可复现的输出更容易测试、调试和演示。每个增量只有在行为符合架构不变量且检查通过后才被接受。

交互 3 — 生产与安全审计

我的要求

应用程序正常工作后,我要求 AI 停止添加功能,并从资深工程师、多租户安全审查者、值班生产负责人以及 REST/MCP API 审查者的角度对仓库进行审计。

AI 提供的内容

审计发现,通过模式校验的上游响应最初并未与所请求的会员或合作伙伴身份进行关联。审计还发现,格式错误的 JSON 可能在请求 ID 中间件建立请求上下文之前就导致失败。此外还提出了一些较低优先级的改进建议。

我保留、修改或拒绝的内容

我接受了两项高价值发现,因为它们影响租户正确性和安全运营。对于身份关联,实现现在会验证返回的 memberId 是否与所请求的会员匹配,configuration.partnerId 是否与权威的 member.partnerId 匹配,并且 RecommendationService 会防御性地重复配置身份检查。任何不匹配都会失败关闭,对抗性回归测试验证了在无法建立权威配置时,候选生成永远不会启动。

对于格式错误的 JSON,现在会在解析之前建立请求上下文和请求 ID。无效的请求体会收到安全的、结构化的 400 响应,其中不包含解析器细节、堆栈跟踪、文件系统路径或原始请求内容。

我推迟了较低优先级的想法,例如持久化 MCP 会话、额外的分布式基础设施以及更高级的可观测性,因为这些对于为期四周的概念验证并非必要,而且会增加运营范围。我根据作业要求、租户正确性、可测试性、运营风险和交付范围对每项建议进行了评估。助手提供了选项和实现帮助,但我审查了推理过程,选择了改动方案,并通过重点测试和端到端检查进行了验证。

四周计划的第一步

首先交付什么

四周目标是一个可交付的首个内部概念验证。它展示了所需的工作流程,包括安全的租户执行和基本运营能力;这并不是声称广泛生产上线所需的所有能力都已完备。

第 1 周 — 服务基础

  • 建立 TypeScript 和 Node.js 服务基础。

  • 定义领域模型、严格的 Zod 边界验证和类型化错误。

  • 添加 MemberDataService 契约和模拟实现。

  • 添加只读的 PartnerConfigurationService 契约和模拟实现。

  • 建立权威的、成员派生的租户解析机制和初始单元测试基础。

目标: 在实现推荐逻辑之前,建立安全的服务边界和租户权威。

第 2 周 — 推荐工作流

  • 独立于合作伙伴规则实现确定性的 CandidateGenerator。

  • 实现 RecommendationPolicy,包括类别排除和推荐上限。

  • 执行要求的顺序:先排除,再上限。

  • 添加 RecommendationService 编排和配置失败即关闭的行为。

  • 用重点单元测试覆盖策略和编排。

目标: 证明契约性合作伙伴规则是确定性的,并且独立于候选生成。

第 3 周 — 接口与端到端流程

  • 暴露 REST 端点和 Streamable HTTP MCP 端点。

  • 提供 MCP 工具 get_member_profile 和 get_recommendations。

  • 添加 CLI 演示。

  • 将 REST、MCP 和 CLI 路由到共享业务层。

  • 添加 REST/MCP 集成测试和租户覆盖测试。

目标: 通过作业要求的接口展示完整的推荐工作流。

第 4 周 — 生产就绪与交付

  • 添加请求 ID、关联字段、结构化 JSON 日志和安全错误处理。

  • 安全地处理格式错误的 JSON,并实现优雅关闭。

  • 添加 Docker 多阶段构建和非 root 运行时。

  • 分别对生产源代码和测试进行类型检查。

  • 执行生产/安全审计,并添加对抗性身份关联测试。

  • 完成最终的端到端和容器验证。

  • 准备 README、事件运行手册和演示视频。

目标: 使概念验证能够由其所属团队在值班时提供支持。

后续工作

以下工作被特意推迟到四周首版发布之后:

  1. 真实上游集成。 用真实的 arrivia REST 客户端替换模拟的 MemberDataService 和 PartnerConfigurationService 实现,同时保留现有的服务契约和身份关联不变量。

  2. 现有认证和授权集成。 与 arrivia 现有的身份和网关机制集成,而不是引入新的身份平台。授权必须保留成员派生的租户权威。

  3. 网络韧性。 对于真实上游 HTTP 依赖,验证并配置请求和连接超时、在操作可安全重试时设置有限重试次数,并明确失败行为。配置不确定性必须继续失败关闭。

  4. 性能验证。 在优化之前先运行真实的负载和性能测试。只有在测量结果证明有必要时才考虑短期合作伙伴配置缓存。过期的策略会带来正确性风险,因此任何缓存都需要清晰的时效性和失效策略。

  5. 推荐智能。 可能用 LLM、排序模型或更丰富的个性化来替换或增强确定性的 CandidateGenerator。RecommendationPolicy 必须保持确定性,并且独立于模型之外,这样生成的输出就不能覆盖合作伙伴规则。

  6. 生产可观测性。 将现有的结构化事件和关联 ID 连接到 arrivia 批准的指标、追踪、告警和运维工具中。

  7. MCP 演进。 仅在具体产品需求需要跨请求状态时,才考虑有状态或可恢复的 MCP 行为。当前无状态的 Streamable HTTP 实现是出于该服务的有意设计。

Related MCP Connectors

Related MCP Servers