1. 立项从「一个窗口」开始:WeClaw 要解决的问题和第一版形态
WeClaw 这个名字最早不是正儿八经的产品代号,是我在白板上随手画的一只丑猫。但立项理由非常朴素:我做技术咨询和独立开发,每天要在代码编辑器、浏览器、PDF 阅读器和聊天软件之间来回切换,光是「把问题描述清楚给 AI」这件事就消耗了大量精力。问代码要复制报错、问翻译要粘贴文本、问文档要手动把关键段落丢进去,上下文一换就全断。市面上已有不少 Chat 类网页、也有各种聚合客户端,但在「我本机上的文件、剪贴板、当前选中的代码」这个语境里,它们始终隔着一层。
所谓跨平台 AI 助手,在我这边的定义不是「把同一个聊天界面搬到 Windows、macOS 和 Linux」,而是让 AI 能力长在系统里:无论我在编辑器选中一段报错,在浏览器看到一段外文,还是在终端里遇到一条看不懂的日志,都能通过同一个快捷键唤起 WeClaw,让它带着当前上下文回答问题。这个目标决定了后面的架构演进方向。WeClaw 不管底层接的是云端大模型还是本地小模型,对用户来说应该只是一个可以随时呼出的系统级助手。
如果你正在做 AI 应用集成、桌面端工具,或者想把一个小工具慢慢做成长线产品,这篇文章会比较对味。我不会只讲最终架构图,而是把中间反复推翻、重写、留坑、填坑的过程一起讲出来,因为架构演进真正值钱的不是最后的漂亮分层,而是那些「当时为什么这么选」的判断。
1.1 需求收敛:五个高频场景,砍掉三个伪需求
第一版之前,我列了八个候选场景:代码问答、文档摘要、划词翻译、周报生成、会议记录整理、图片 OCR、情绪陪伴、像 Siri 一样闲聊。后来跟几个真实用户聊完,砍掉了一半。
留下来的是三个:
- 划词解释和翻译:在任何桌面应用里选中文本,按快捷键直接得到结果,不需要先把文本复制到网页。
- 终端/报错代码分析:把剪贴板里的一段报错丢给模型,结合对话历史判断修复方案。
- 本地文档问答:把 PDF、Markdown、TXT 放进一个目录,AI 能基于这些资料回答「我们架构文档里对权限是怎么设计的」。
被砍掉的情绪陪伴和闲聊,不是技术不能做,而是它们对延迟、上下文连续性和系统权限的要求完全不一样。如果一开始就做八爪鱼式的全能助手,架构八成会被需求拉扯到散架。这个收敛过程给整个项目定了基调:WeClaw 要做的是「帮助用户处理信息」,不是「陪用户聊天」。后面所有架构决策,都以「能不能更低延迟、更快拿到上下文、更可靠地执行工具」为标准来判断。
1.2 v0.1 架构选型:为什么先做网页应用而不是桌面程序
很多人听到「跨平台 AI 助手」会直接选 Electron 或者 Flutter,但我第一版反而做成了 Web 应用。原因非常现实:我不确定用户愿意在多大程度上接受一个需要常驻的桌面程序,先用浏览器验证需求最省钱。
v0.1 的架构非常简单:前端是 React + Vite 的单页应用,后端是 Python FastAPI,里面封装了对大模型服务的 HTTP 调用。用户打开浏览器,粘贴文本,点提问,后端转发请求,流式返回结果。这个版本把「能不能在一个页面里完成问答」验证通了,但也很快暴露出两个问题:
第一,浏览器里拿不到系统级的上下文。划词文本、剪贴板变化、当前前台应用,这些能力在浏览器里都受严格限制,很多需要通过本地代理或者浏览器插件才能实现,体验支离破碎。
第二,用户不愿意为了一个工具去记忆 URL、打开浏览器、找到书签。他们要的是「按一下快捷键,它就在那里」。Web 版本的用户留存率低得惊人,上线两周只有不到 10% 的人会第二次主动打开。
v0.1 的结论是:需求真实存在,但使用形态必须是桌面客户端,且最好支持全局快捷键。于是第一次架构转折开始了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 第一次架构转折:从浏览器页面到可常驻的系统托盘程序
严格来说,WeClaw 真正从 0 到 1 是这一阶段开始的。网页版推倒之后,我花了一周时间做技术选型。当时摆在面前的是 Electron、Qt、Tauri、Flutter Desktop 四个方向,网上争论很多,但放到 WeClaw 的具体场景里,比较维度会变得非常清晰。
2.1 用户「挂后台」的需求出来后,Web 架构的短板立刻暴露
网页版验证期间,有个用户反馈让我印象深刻:「我希望能像输入法一样呼出它。」也就是不管当前在哪个应用里,按快捷键就能弹出一个悬浮输入框,输入问题后它变成一个小窗展示答案,再按一下 Esc 就消失。这个需求需要程序常驻后台、监听全局快捷键、能够在其他窗口之上绘制自己的 UI,同时保持很低的内存占用。
网页方案做这些事要么得靠浏览器插件,要么得再套一层本地代理,链路一长延迟和 bug 数量都会成倍增加。与此同时,AI 对话是典型的「高频短交互」,如果每次呼出要等 3 秒,用户会直接放弃。技术选型的核心矛盾变成:什么方案能在提供完整系统能力的同时,把启动路径和常驻成本降到最低?
2.2 Tauri、Electron、Qt、Flutter 的取舍
列一张当时做的对比表,可能对你也有参考价值。
| 方案 | 安装包体 | 常驻内存 | 系统能力 | 移动端覆盖 | 团队熟悉度 | 结论 |
|---|---|---|---|---|---|---|
| Electron | 70-150MB | 200MB 以上 | 强,通过 Node 生态 | 无直接方案 | 高,前端友好 | 太重,做系统级常驻体验不够好 |
| Qt / C++ | 10-40MB | 50-80MB | 强 | 可做但成本高 | 低 | 开发效率不足,迭代快不起来 |
| Tauri | 5-15MB | 50-100MB | 强,通过 Rust 侧插件 | 有移动端路线 | 中,需补 Rust | 最终选择 |
| Flutter Desktop | 20-60MB | 100MB 上下 | 中,系统 API 依赖插件 | 强 | 中 | UI 一致性好但系统集成生态偏弱 |
最后选了 Tauri,核心原因有两个。第一,WeClaw 的大量逻辑其实不在 UI,而在系统能力:监听剪贴板、读取选中文本、索引本地文件、接入大模型 SDK。Tauri 的后端是 Rust,让我可以用同一套语言写系统相关代码,并暴露成前端可调的接口,性能比走 Node 桥接好很多。第二,包体和内存直接决定「常驻软件」的舒适度,一个要占 300MB 内存的助手,用户嘴上不说,但会在某个时刻默默卸载。
2.3 抽 Rust core:UI 做薄壳,能力层独立成库
第一版桌面架构,我用的是比较朴素的三层结构:
- UI 层:Tauri 内置 WebView 渲染,负责悬浮窗、对话列表、设置页面。
- Bridge 层:Tauri 的 command 机制,前端通过
invoke调用 Rust 函数。 - Core 层:Rust crate,包含全局快捷键、剪贴板监听、模型请求转发、对话存储。
初期这个结构跑得很顺,直到我开始加「划词唤起」和「文档知识库」后,发现如果把所有 AI 逻辑都写在 Core 里,Core 会慢慢膨胀成一个没人敢动的 God Module。于是我在 crate 内部又做了模块化分层:
rust复制weclaw_core/
├── events/ // 全局事件:快捷键、剪切板、系统唤醒
├── context/ // 上下文收集与组装:选中文本、前台应用、文件类型
├── llm/ // 大模型调用、流式管道、工具调用
├── memory/ // 会话记忆、本地向量存储、摘要管理
├── tools/ // 本地工具注册表与执行沙箱
└── ui_bridge/ // 提供给 Tauri command 的薄接口
这一层拆分在早期看似过度,后来被证明救了大命。因为 AI 领域变化极快,模型供应商、调用协议、上下文管理策略几乎每季度都在变。如果这些逻辑写在一个 5000 行的 main.rs 里,每一次协议调整都会引发连锁崩溃。拆成独立模块后,替换模型供应商只需要改 llm 内部实现,UI 和记忆层完全不用动。这个决策形成了 WeClaw 后续所有迭代的地基。
3. 对话链路从「一问一答」进化为「多模型网关 + 流式管道」
桌面壳子搭好后,最核心的 AI 调用链路开始成为主要矛盾。早期版本直接在前端点击按钮后,由 Rust 后端向模型服务发一个同步 HTTP 请求,等全部内容返回后再一次性渲染到界面上。这个方案在模型回答比较短时还能用,一旦涉及长文、代码生成或者工具调用,体验就崩了。
3.1 最初的直连模型代码问题出在哪
当时的链路简化成伪代码大概是这个样子:
rust复制async fn ask_question(question: String, history: Vec<Message>) -> String {
let client = reqwest::Client::new();
let resp = client.post(model_url)
.json(&build_payload(question, history))
.send()
.await?;
let full_text = resp.text().await?;
Ok(full_text)
}
这种同步等待有几个致命问题:
- 如果模型服务端要思考 10 秒才开始返回,用户界面就干等 10 秒,没有逐字输出,也没有取消按钮。
- 如果用户在中间发现问错了想停止,前端没有办法中断这次请求,只能在 UI 上假装停止,后端还在偷偷耗 token。
- 一旦模型支持「工具调用」,即需要先让 AI 决定调用某个本地函数再返回结果,这种一问一答的结构根本没法承载多轮工具交互。
所以第二次大重构,就是把这条链路从「请求-响应」模型改成「流式管道 + 事件总线」。
3.2 流式增量、工具调用、中断抢占,三件事合成一条管道
改造后的核心思路是:前端和后端都不再理解「完整回答」这个概念,只处理一个个流式事件。事件类型包括文本增量、消息开始、消息结束、工具调用请求、工具执行结果、错误信号等。
用 Rust 枚举定义事件大概是这样的:
rust复制pub enum LlmEvent {
TextDelta { content: String },
ToolCall { id: String, name: String, arguments: serde_json::Value },
ToolResult { id: String, result: Result<serde_json::Value, String> },
MessageEnd { finish_reason: FinishReason },
Error { kind: ErrorKind, message: String },
}
UI 层只做一件事:把收到的事件渲染成交互界面。收到 TextDelta 就在打字机效果里追加文字,收到 ToolCall 就显示一个「正在调用本地工具」的卡片,收到 ToolResult 就把工具输出折叠在卡片里。
这条管道最大的受益者是「中断」。由于连接是基于 SSE 或 WebSocket 的长连接,前端发一个 cancel 请求,后端会立刻终止上游连接,并通知模型服务停止生成。用户感知是「终于能真正停止了」,不是假按钮。省下的 token 在长期使用中相当可观,而且极大降低了对界面响应性的伤害。
3.3 多模型网关与失败降级
对话链路稳定之后,另一个需求浮出水面:不同任务应该用不同模型。划词翻译用轻量快速的小模型,代码重构用推理能力强的大模型,本地敏感文档问答用完全离线的模型。WeClaw 不能只绑定一家模型服务商,于是我在 llm 模块里做了一个模型网关层。
网关层负责三件事:
- 路由:根据当前任务类型,决定请求发给哪个供应商、哪个模型。
- 归一化:不同供应商的请求格式、流式协议、工具调用格式不一样,网关层统一转换成 WeClaw 内部事件,避免上层代码被供应商 SDK 绑架。
- 降级:如果主模型超时或返回 5xx,自动切换到备选模型,同时补一条提示说明「当前使用备用模型回答」。
降级逻辑是最容易被忽视但非常影响口碑的部分。用户不会关心你接入了多少家模型,他只知道上一次回答失败后,下一次能不能自动恢复。网关层配合健康检查,让 WeClaw 在真实使用中即使遇到某个上游服务不稳定,也能基本维持可用。
4. 记忆与知识:从「把聊天记录存 JSON」到「本地向量库 + 会话摘要」
对话链路顺畅以后,我开始做记忆系统。最原始的方案特别傻:每次对话结束,把整个消息数组序列化成 JSON,存到一个文件里。下次启动时读进内存,作为历史消息全量发给模型。这个方案在小体量测试时没问题,但聊到第三天就发现两个问题:一是历史一长,token 费用飞速上涨;二是模型并不会真正「记得」两周前聊过的一个关键结论,因为全量塞进上下文,重点反而被淹没了。
4.1 JSON 聊天记录方案是怎么被淘汰的
淘汰它的不是存储本身,而是上下文构建方式的改变。WeClaw 需要让 AI 理解三类信息:
- 当前会话的近期对话,按顺序排列。
- 跨会话的项目背景,比如「用户上次让我提炼的那份架构评审结论」。
- 用户长期偏好,比如「代码回答要带完整示例,不要只讲思路」。
这三种信息的更新频率、检索方式和重要性完全不同,混在同一份 JSON 数组里只会让每次请求越来越贵、越来越慢。真正的转折点是当我想做「让 AI 读取本地文档」时,向量库和分层记忆开始变得不可回避。
4.2 本地向量库引入:不是为 RAG 而 RAG
本地文档问答在 WeClaw 里的实现路径是:把用户指定的目录(比如 ~/Documents/WeClaw/Knowledge)里的 Markdown、TXT、PDF 解析成文本块,分块之后做 embedding,写入本地向量库。每次提问时,先根据问题在向量库检索最相关的若干片段,拼进 prompt,再让模型基于这些片段回答。
这个方案很多人叫它 RAG,但我不太喜欢给架构贴术语标签。对 WeClaw 来说,引入向量库的真实动机是:模型训练语料里根本没有用户本机的私有文档,如果不做检索增强,模型就只能瞎编。WeClaw 定位是「能干活」,瞎编的答案比不回答更糟。
检索链路初期用的方案比较土:
text复制问题文本 -> embedding -> 向量相似度检索 -> 取 top-k 片段 -> 拼进 system prompt -> 模型回答
后来发现 embedding 模型的本地推理在低配电脑上有可感知的延迟(每次 200-800ms),于是做了一个小优化:对文档做增量索引,内容不变就不重新 embedding;同时对问题做缓存,如果 30 分钟内有人问过相似度超过阈值的问题,直接走缓存。这个优化让 80% 的重复提问延迟降到了几十毫秒。
4.3 分层记忆:短期会话、项目记忆、全局偏好
在存储层面,我把记忆拆成三层:
- 短期会话层:存最近一次对话的原始消息,保留在本地 SQLite 中,过期自动清理。
- 项目记忆层:每次会话结束后,后台对这次对话做摘要,提取结论、待办、关键事实,写入项目级记忆表。
- 用户偏好层:用户主动设置的偏好,例如回答语言、代码风格、是否默认联网搜索等。
为什么要这么做?因为模型调用时我不需要把所有历史都塞进去。日常提问,只带短期会话;跨会话引用,用对话摘要;只有当用户明确问「我上次让你记的那个结论」,才去项目记忆里检索。
这样做还有一个好处:用户可以在设置里逐层查看记忆内容。AI 应用最怕「用户不知道你记住了什么」,分层后至少能把记忆边界清楚地展示出来。我在这块踩过坑,早期所有历史都全量发出,结果有一次把用户完全无关的隐私内容带到了上下文里,虽然没有外发,但已经足够让人警觉。从那以后,默认策略改成「最小必要记忆」,跨会话记忆默认关闭,用户主动开启才启用。
4.4 本地为主、加密云同步为辅
多端使用是跨平台的隐藏需求:我在公司 Windows 上聊到一半,回家想在 Linux 上继续。全本地存储无法满足这个场景。我的选择是:所有数据默认先写本地,SQLite 作为主存储;同步功能做成可选,用户开启后,把 SQLite 变更记录按事务加密上传到自己的对象存储或 WebDAV,其他端拉取后解密合并。
不自己做账号系统的原因很简单:对于一个助手工具,账号体系是巨大的维护成本。支持 WebDAV 等通用协议,反而能放进用户已有的私有存储体系里,信任成本更低。
5. 能力边界:插件系统和本地工具调用的安全设计
AI 助手要真正的「有用」,绕不开工具调用。WeClaw 已经内置了几个本地工具:读写剪贴板、读取选中文本、执行终端命令(受限)、操作本地文件、搜索本地文档。但当第三方插件出现时,单纯的功能叠加会变成安全灾难。这里的安全设计是整个架构里最需要谨慎的部分。
5.1 工具注册表:把函数变成能被模型调用的接口层
每个工具的暴露方式,不是直接让模型执行代码,而是把工具的元信息注册到一个表里,包括名称、描述、参数 JSON Schema、执行函数和权限等级。
以「读取选中文本」为例,注册表里类似这样:
rust复制ToolRegistration {
name: "get_selected_text",
description: "读取当前前台应用选中的文本",
parameters: Schema::empty(),
permission_level: PermissionLevel::UserGesture,
handler: get_selected_text_handler,
}
模型返回一个 ToolCall,请求调用 get_selected_text,网关层解析参数后,根据权限等级决定是直接执行、等待用户确认还是拒绝执行。这个中间判断层是安全的关键,它把「模型想要做什么」和「实际允许做什么」彻底分开。
5.2 危险操作清单与显式确认机制
执行终端命令、删除文件、访问浏览器历史这类操作,全部被标记为高危权限。模型可以建议执行,但真正执行前必须弹窗让用户点击确认。确认框里不能只写「是否允许执行」,必须显示完整命令和后果说明,比如:
text复制WeClaw 想要执行以下命令:
rm -rf /tmp/weclaw_cache
风险等级:高
请确认是否允许?
有人觉得这样很繁琐,会打断 AI 助手的流畅感。但从实际经验看,如果一个 AI 助手能不加确认地执行终端命令,它一次幻觉产生的影响可能是不可逆的。WeClaw 的目标不是做到「最流畅」,而是做到「用户敢让它做更多事」。
5.3 进程隔离:把插件放得远远的
内置工具跑在 Core 进程内可以接受,因为代码是自己维护的。但第三方插件如果也能在 Core 内执行,就等于给每个插件发了系统级通行证,这个风险不能接受。所以插件系统从设计一开始就要求:
- 每个插件跑在独立子进程中,拥有独立的权限 token。
- 插件只能通过协议定义的接口与 Core 通信,不能直接访问文件系统或网络。
- 插件需要访问外部资源时,必须申请 scope 权限,由用户审批。
这个设计牺牲了一定的开发便利性,但让 WeClaw 可以在真实环境里放心地开放插件生态。后来看,如果当时图省事直接在主进程里动态加载插件脚本,出一次安全事故可能整个项目就没了。
6. 多平台交付踩坑:签名、自动更新、系统服务这些「最后一公里」
架构说得再好,到多平台发布时还是会发现,「最后一公里」才是真正磨人的地方。WeClaw 的目标平台是 Windows、macOS 和 Linux,三个平台在交付环节各有各的坑,单独每个平台的坑细说能写一万字,我把最核心的几条整理出来。
6.1 Windows/macOS 的签名公证与自动更新
Tauri 自带的 updater 机制很好用,但前提是发布版本必须签名。
- macOS 上要申请 Developer ID 证书,用
codesign签名后还要走notarytool公证。没有公证的应用,在别的机器上首次运行会被 Gatekeeper 拦截,用户得去「系统设置-隐私与安全性」里手动允许,这一步会劝退大量普通用户。 - Windows 上需要代码签名证书,没有签名的话 SmartScreen 会弹红色警告。很多独立开发者在这里选择不签名,想着「让用户点一下更多信息-仍要运行」就行,但真实转化率会掉一大截。
自动更新服务我用的是 Tauri 内置的更新端点加静态 JSON 文件方案。每次发版只需要把新安装包上传到对象存储,更新 update.json 里的版本号和下载地址。这里有个容易被忽略的细节:下载地址必须带版本目录,否则旧版本客户端会基于浏览器缓存拿到旧文件,造成「显示有新版本,但下载后还是旧版本」的诡异 bug。
6.2 Linux 分发:你以为的「跨平台」在 Linux 上会碎一地
Linux 的桌面生态碎片化,是跨平台开发里最容易被低估的部分。WeClaw 在 Linux 上遇到的问题包括:不同发行版缺不同的系统依赖库,托盘图标在某些桌面环境不显示,全局快捷键在 Wayland 会话下根本拿不到全局键盘事件,因为 Wayland 出于安全原因不允许应用随便监听全局快捷键。
我们的应对策略是:
- 用 AppImage 作为主力分发格式,同时提供 deb 和 rpm 包给不同用户。但 AppImage 也有自身问题,在部分发行版上 FUSE 没装就运行不了,这个只能靠文档说明兜底。
- 全局快捷键检测到当前是 Wayland 时,降级为「从系统托盘菜单唤起」和「自定义命令唤起」,至少保留一条可用路径。
- 把 Linux 报障的优先级放低。不是不重视,而是投入产出比决定了大部分用户集中在 Windows/macOS,Linux 用户通常更愿意自己看日志、报细节,反而不太需要频繁发版。
6.3 本地日志与崩溃上报
跨端开发最难排查的是「用户说他那边崩了,但我这儿复现不了」。WeClaw 的处理方式分两层:
- 本地日志:Rust 侧用
tracing输出结构化日志,前端 console 也会统一转发到本地日志文件。日志文件轮转策略按 5 天或 20MB 上限清理,避免长期占用用户磁盘。 - 崩溃上报:不做全自动上报,而是把崩溃现场的信息(堆栈、版本号、最近 50 条日志)打包,在下一次启动时提示用户可自愿上传。
这比强制自动上报温和得多,也更符合本地优先工具的定位。用户愿意上传,是因为他遇到了问题;不愿意上传,是他的权利。强制上报看似能拿到更多崩溃数据,但会让产品在口碑上付出代价。
6.4 发版节奏与灰度顺序
Tauri 的更新机制天然支持按版本灰度:在 update.json 里先把新版本指向一部分流量,观察监控指标后再全量。如果直接用现成的静态 JSON,做不到真正的灰度,因为所有客户端拉到的都是同一份 JSON。我后来用了一个很简单的方案:更新接口不直接返回 JSON 文件,而是一个轻量服务端接口,可以根据请求参数里的设备 ID 决定返回哪个版本。
灰度顺序基本固定:先 5% 内部用户,再 20% 日常活跃用户,最后全量。AI 应用的特殊之处在于,模型供应商的接口变更可能让某类功能突然不可用,灰度能尽早发现问题。
7. 复盘:哪些架构决策让我庆幸,哪些是过度设计
WeClaw 走到今天,回头看的架构决策大概可以分为两类:有些是当时顶着压力做的,效果超出了预期;有些是当时觉得有必要,后来发现确实做早了。
7.1 值得庆幸的三个决策
第一,Rust Core 几乎纯粹为了「稳定」。AI 应用会频繁重试、中断、解析流式输出,如果这些放在 GC 语言里,内存和延迟的不确定性会放大。Rust 在内存安全和性能之间的平衡,让常驻程序在跑了一周之后也能保持稳定的内存占用。
第二,事件驱动的对话管道,让「多模型」「多模态」「工具调用」的扩展没有伤筋动骨。新接一个模型供应商时,只需要写适配层把它的协议翻译成内部 LlmEvent,不需要改 UI 和业务逻辑。
第三,把权限和工具执行做成了显式框架。虽然前期开发要多写不少代码,但后来所有插件和新工具都安全地归置在框架里,不需要每次为单个工具单独设计安全策略。这可能是 WeClaw 能保持开放性而不翻车的最重要原因。
7.2 过度设计的地方
坦诚讲,插件系统在最开始就是过度设计。第一版桌面应用还在验证期时,我花了大量时间设计插件进程隔离、跨进程通信、权限 token 体系。但当时真正需要的只是先把三个内置工具跑通。结果插件系统草稿在仓库里躺了四个月,直到需求端真正出现才重新翻出来适配。
如果重新来一次,我会先把工具调用和权限框架做成最小可用版本,比如用枚举标记内置工具权限,等第三方需求明确后再升级成完整插件体系。过度设计不只是浪费时间,更危险的是它会让你产生「系统已经很完善了」的错觉,忽略了核心体验的粗糙。
7.3 给同类项目的一个血泪经验
如果你也在做跨平台 AI 助手,我最大的建议是:把「上下文收集」当成一级架构模块来设计。很多 AI 应用把上下文只理解成聊天历史,但真正的系统级 AI 助手,上下文是当前文件路径、选中文本、剪贴板、环境变量、最近操作记录和用户长期偏好的组合。这个模块能否优雅地扩展,直接决定了助手能有多「懂你」。为此值得单独抽象一个 Context Builder 接口,而不是在调用大模型时临时拼参数。
WeClaw 的架构还在继续演进,但经过这几轮重构,我已经能比较有底气地面对下一个未知需求了。架构演进不是把代码写得越来越抽象,而是每一次改动之后,都能让系统在新增能力时不再需要推倒重来。
