构建 Linda:从零打造 AI 助手
工程
深入解析 Linda Assistant 背后的架构决策——从 SwiftUI 到服务端 AI 智能体。

从零开始创建一个全栈 AI 助手是一个复杂而耗时的过程。尤其是在市场上已经有多个现成的助手——ChatGPT、Claude、Gemini——由模型提供商打造,功能强大到足以吸引用户。ChatGPT 在 iOS、Android 和 macOS 上拥有原生应用体验,提供从编码到图像生成、从深度研究到日常对话、从写作辅助到学习伙伴的全方位功能。那我们为什么还要再造一个助手?
发起方式的差异
传统 AI 助手是用户发起型的——用户触发第一条消息,助手作出回应。这适用于问答和基于任务的交互,但本质上是被动和响应式的。在大多数科幻电影和剧集中,我们想象的 AI 助手是主动的、"智能体化"的——能够自主处理信息并做出决策,及时且数据驱动。
我们希望智能体能主动监控外部世界的数据,为用户总结信息,并代表用户采取行动——预订餐厅、发送邮件,甚至根据用户的日程和收件箱生成每日简报。
这种发起方式的根本差异驱动了我们所有的架构决策。

系统架构概览
Linda 由六个可独立部署的服务组成,在 Kubernetes 上编排:
Next.js 后端作为中心枢纽——处理 API 路由、SSE 流式传输、身份验证和数据库访问。当任务到达时,它将繁重的工作委托给独立运行智能体循环的 Worker 进程,Worker 从 RabbitMQ 消费任务并通过同一消息代理发布流式事件。Celery 调度器(基于 Python,由 RedBeat 提供持久化)处理定期任务,如每日简报。
在基础设施层面,Redis 管理流式数据块缓存、序列编号和活跃会话跟踪,而 Mem0 通过 Upstash Vector 提供基于向量搜索的长期记忆。
流式传输问题
为什么 HTTP 流式传输还不够
HTTP 流式传输(Server-Sent Events)是实时传递 AI 响应的标准方式。当用户发送消息时,后端打开到模型提供商的流并实时转发数据块给客户端。这对传统的请求-响应模式运作良好。
但当智能体是发起者时会发生什么?用户并没有在等待一个打开的 HTTP 连接——他们可能已经关闭了应用。当没有人在监听时,我们不能简单地打开一个流。
此外,在传统设计中,智能体循环运行在与 HTTP 端点相同的进程中。但由于我们将智能体分离到独立的 Worker 进程中,我们需要一种方式来桥接数据块的生产端(Worker)和传输端(SSE 端点)。
使用 RabbitMQ 和 Redis 的解耦流式传输
我们的解决方案使用消息队列将数据块生产与数据块传输分离:

每个数据块通过 Redis INCR 获得单调递增的序列号,客户端可以检测间隔并请求重放。所有数据块也缓存在 Redis 中,TTL 为 1 小时,这意味着后加入的客户端可以从缓存重放完整响应。活跃会话标志防止重复处理——如果 Worker 已经在处理某个会话,新任务会被直接跳过。当智能体完成但用户没有活跃流式连接时,推送通知确保主动响应仍能到达用户。
流重放确保可靠性
网络中断和应用后台化在移动端是常态。当 iOS 应用重新连接时,它发送最后接收的序列号,只接收缺失的数据块。这为我们提供了至少一次传递保证,而无需 WebSocket 的复杂性。
智能体循环
Linda 的核心是智能体循环——由 Vercel AI SDK 的 streamText 和工具调用驱动的多步 AI 推理过程。
工作原理
当 Worker 从 RabbitMQ 接收任务时,它从数据库加载对话历史并根据用户权限构建工具集。然后运行 streamText,最多 20 个推理步骤,每个步骤可以涉及工具调用、用户确认请求或文本生成。所有步骤完成(或用户中止)后,最终消息被持久化回数据库。
上下文管理
对话可能跨越数百条消息和工具调用,因此上下文窗口管理至关重要。Linda 使用 75,000 token 的上下文窗口,在所有模型间共享。当预估 token 超过窗口的 75% 时,压缩流程启动——将较旧的消息总结为简洁的摘要,同时保留最近的 6 条消息原文。冗长的工具结果也会被压缩以回收空间。这允许对话无限期运行而不会达到上下文限制。
工具系统与权限
Linda 内置 20+ 工具,涵盖多个类别:

| 类别 | 工具 |
|---|---|
| 通讯 | send_email、search_emails、send_notification |
| 任务管理 | create_task、update_task |
| 文档 | create_document、update_document、search_documents |
| 创意 | create_slides、update_slides、create_drawing、generate_image |
| 信息 | get_current_time、get_location、search_history |
| 交互 | ask_question、request_upload、read_uploaded_file |
| 简报 | create_briefing |
权限感知执行
并非所有工具都应自动执行。代表用户发送邮件需要明确批准,但搜索文档不需要。Linda 使用三级权限模型:工具可以是自动确认(立即执行,如 search_documents)、手动确认(智能体暂停并发送推送通知供用户审查和批准)或自动拒绝(从智能体工具集中完全移除)。
权限可按受派人和任务进行配置,甚至可以有条件地自动确认——例如,仅当收件人在公司域内时自动批准 send_email。
通过 MCP 实现可扩展性
除了内置工具,Linda 还支持 Model Context Protocol (MCP) 服务器作为扩展。用户可以连接外部服务——日历、发票系统、家居自动化——智能体动态发现其工具。
为避免预先加载所有扩展工具(这会膨胀上下文),我们使用三个元工具实现延迟加载。智能体首先调用 search_tools 使用嵌入对所有可用扩展工具进行语义搜索,然后调用 read_tool 获取所需工具的完整 schema,最后调用 use_tool 在 MCP 服务器上调用它。这样智能体只在实际需要时加载工具详情,即使连接了数十个扩展,基础提示词也保持精简。
触发器:让智能体主动化
在传统助手中,只有用户消息触发智能体循环。Linda 支持四种触发类型。
最直接的是用户消息——标准路径,消息作为 AgentTask 发布到 RabbitMQ。但更有趣的是自主触发器。
定时任务由 Celery 调度器管理,使用 RedBeat 实现持久化。当定时任务触发时,它回调后端的 /api/tasks/{id}/execute 端点,将任务发布到 Worker 队列。这驱动每日简报、定期数据检查和定时报告。
时区处理在注册时完成——cron 表达式从用户本地时区转换为 UTC,因此 Celery 调度器始终在 UTC 下运行。
Webhook 事件允许外部系统直接触发智能体。当 Webhook 到达时,后端在任务的会话中创建新消息并将任务发布到队列。类似地,邮件事件让 Linda 监控收件箱,在相关邮件到达时触发智能体运行,实现自主分类、总结或回复。
iOS 应用:原生 SwiftUI 体验
iOS 客户端使用纯 SwiftUI 构建,采用模块化包架构。

核心是 AssistantCore,一个包含 API 客户端、SSE 客户端和数据模型的共享 Swift 包。视图按功能组织——聊天、任务、文档、幻灯片、简报、扩展、设置等。
iOS 上的 SSE
SSEClient 实现为 Swift actor 以确保线程安全。它使用 URLSession.bytes 流式传输 SSE 事件,具有自动重试和指数退避(最长 30 秒,最多 10 次重试)。事件以 AsyncThrowingStream<SSEEvent, Error> 形式传递,自然契合 Swift 的结构化并发模型。
Kubernetes 部署
所有服务使用 Kustomize 部署到 Kubernetes:

每个服务可以独立扩展。Worker Pod 是无状态的——它们从共享的 RabbitMQ 队列拉取任务,因此增加更多 Worker 可线性提升吞吐量。Celery 调度器作为单实例运行,RedBeat 确保持久化。
记忆:让助手学会记住
Linda 使用 Mem0 实现长期记忆——一个向量存储支持的记忆服务,允许智能体跨对话记住用户偏好、过去的决策和上下文。
Mem0 服务作为独立微服务运行,提供 REST API,由 Upstash Vector 支持语义搜索。当智能体学到值得记住的内容(用户偏好、项目上下文)时,它将其存储为记忆。在未来的对话中,通过语义相似度检索相关记忆并注入智能体的上下文。
这与对话历史不同——记忆是提炼后的事实和偏好,跨会话和任务持久化。
幻灯片生成
Linda 更具创意的功能之一是 AI 驱动的幻灯片生成。当用户要求制作演示文稿时,专门的子智能体使用受 NotebookLM 风格启发的详细设计提示为每张幻灯片生成 KonvaJS 场景 JSON。这些场景由集群中运行的 Headless Chrome Pod 渲染为 PNG,然后连同缩略图上传到 S3。整个幻灯片组还可以导出为 PDF。五种不同的布局——封面页、章节页、内容页、图表页和图文页——使演示文稿视觉上丰富多样且专业。
我们学到了什么
构建一个主动式 AI 助手让我们认识到解耦就是一切。将智能体循环从 HTTP 层分离是最重要的决策——它使主动触发器、独立扩展和协议灵活性成为可能。没有它,定时任务、Webhook 或邮件触发器都不切实际。
权限必须是第一优先级。 当智能体能代表用户发送邮件和创建任务时,权限系统需要从第一天起就健壮且细粒度,而不是后来才补上。
在客户端,可靠性需要重放机制。网络中断在移动端是常态,带序列号的数据块配合 Redis 缓存让我们的流式层具备了韧性,而无需增加 WebSocket 的复杂性。
我们还学到延迟加载工具至关重要。有了 MCP 扩展,可用工具可以增长到数百个。将它们全部加载到提示词中会耗尽上下文窗口;对工具描述的语义搜索使事情保持可控。
最后,记忆不是历史。对话历史是所说内容的原始记录。长期记忆是提炼后的知识——用户偏好、过去的决策、习得的上下文。两者对于一个真正有用的助手都是必需的,且必须以不同方式存储和检索。
想查看项目概览?请访问 Linda Assistant 项目页面。