1. 项目概述:构建一个真正懂你的AI伙伴
如果你和我一样,对市面上那些“金鱼记忆”的AI助手感到厌倦——每次对话都像初次见面,需要你反复交代背景,更别提主动帮你留意什么了——那么Jossie2的出现,绝对会让你眼前一亮。这不仅仅是一个聊天机器人,它是一个用Rust精心打造的、具备长期记忆和主动意识的AI个人伴侣。它的核心目标不是简单地执行一个指令,而是成为你数字生活的延伸,一个真正“懂你”的伙伴。
想象一下,你告诉Jossie:“下周我和王总有个关于‘北极星项目’的会议,帮我找找我们之前的邮件往来。”一个普通的AI可能会直接去搜索“王总”和“北极星”。但Jossie不同,它的大脑里有一个为你构建的知识图谱。它知道“王总”是你的重要客户,你们在三个月前通过邮件讨论过“项目A”的初步方案,而上周的日历事件显示“与王总电话沟通‘北极星’需求”。它会将这些点自动关联起来,不仅找到历史邮件,还可能提醒你:“根据之前的沟通记录,王总特别关注交付时间,需要我在会议材料里重点标注吗?”这种基于上下文和关系的深度理解,才是智能助手的未来。
Jossie2脱胎于对现有AI代理框架(如OpenClaw)局限性的深度思考。OpenClaw这类工具擅长执行一次性的、离散的任务,比如“总结这个网页”或“运行这个脚本”,但它们本质上是“失忆”的。每次交互都是孤岛,缺乏对用户长期偏好、人际关系和项目脉络的积累。Jossie2的设计哲学恰恰相反:记忆与关系优先。它通过SQLite数据库持久化存储一切,并利用知识图谱技术结构化地记录实体(人、项目、事件)及其之间的复杂关系。这使得它的每一次回应都建立在对你日益丰富的了解之上。
更重要的是,Jossie2是主动的。它不是一个只会等待命令的仆人,而是一个会观察、会提醒的伙伴。通过集成的邮箱和日历监控,它能在新邮件到达、会议即将开始时,主动向你推送通知,甚至提前准备好相关的背景信息。你可以让它“每天上午9点检查我的日程,并简要汇报”,它就会像一个尽责的私人秘书一样准时工作。这种“设定后不管”的自主性,极大地解放了用户的认知负担。
在技术选型上,团队选择了Rust而非Python作为主语言,这是一个值得玩味且关键的决定。Rust带来的内存安全、零成本抽象和高并发性能,使得Jossie2作为一个需要7x24小时运行、处理多种外部服务集成(网络请求、浏览器自动化)的后台服务,具备了极高的稳定性和资源效率。它编译成一个独立的二进制文件,部署极其简单,避免了Python项目常见的依赖地狱和环境配置问题。当然,这也意味着开发门槛稍高,但换来的则是长期运行的安心和卓越的性能表现。
2. 核心架构与设计哲学解析
2.1 模块化集成系统:可插拔的能力引擎
Jossie2的核心是一个高度模块化的集成(Integration)系统。这不同于许多AI项目将功能硬编码在核心逻辑中的做法。在Jossie2中,每一个核心能力——记忆、知识图谱、邮箱、浏览器——都是一个独立的、实现了标准Integrationtrait的Rust crate。
#[async_trait] pub trait Integration: Send + Sync { fn name(&self) -> &str; fn tools(&self) -> Vec<ToolDefinition>; async fn execute(&self, tool_name: &str, arguments: &str) -> Result<String>; }这个设计精妙之处在于:
- 解耦与复用:每个集成只关心自己的领域逻辑。
email集成负责IMAP/SMTP协议,browser集成管理Headless Chrome实例。它们之间没有直接依赖,通过核心的IntegrationRegistry进行协调。 - 动态工具暴露:每个集成通过
tools()方法,向大语言模型(LLM)声明自己提供了哪些“工具”(即函数)。这些工具的定义遵循OpenAI的Function Calling格式。当LLM在思考过程中认为需要调用某个工具(比如“搜索邮件”)时,它会生成一个结构化的调用请求。 - 统一执行入口:
execute方法是集成的唯一对外接口。核心的Agent Loop(代理循环)在收到LLM的工具调用请求后,会根据工具名找到对应的集成,并转发参数。集成执行完毕后,将结果(字符串)返回,再由核心附加上下文,送回给LLM进行下一轮思考。
这种架构让扩展Jossie2变得异常清晰。如果你想添加一个“天气预报”功能,只需新建一个jossie-integration-weathercrate,实现上述trait,并在主函数中注册即可。LLM会自动获得新的get_weather工具,并在合适的对话场景中使用它。
2.2 双引擎记忆系统:关键词与图谱的融合
记忆是Jossie2的“灵魂”,它采用了一种双引擎混合模式,兼顾了快速检索和深度关联。
FTS5全文搜索记忆:这是第一层,基于SQLite的FTS5(全文搜索)扩展实现。它就像一个高速的、基于关键词的“便签本”。当Jossie在对话中捕捉到重要信息(如“我女儿小米的生日是5月20日”),它会自动调用
memory_save工具,将这条信息以“键-值”或“内容-标签”的形式存入数据库。当后续对话提到“小米”或“生日”时,LLM可以快速调用memory_search进行模糊匹配,瞬间回忆起相关信息。这种记忆速度快,适合存储零散的、事实性的信息。知识图谱记忆:这是第二层,也是更强大的“关系型大脑”。它不仅在SQLite中存储实体(节点)和关系(边),更重要的是,它具备自动抽取能力。在每一轮对话结束后,Jossie的后台任务会启动一个专用的、成本更低的LLM(如
gpt-4o-mini),对刚发生的对话内容进行实体和关系抽取。例如,从句子“我明天要和同事张三讨论Q3的预算报告”中,它能自动识别出:- 实体:
人物:张三,文档:Q3预算报告,时间:明天 - 关系:
张三-同事->我;我-讨论->Q3预算报告;讨论-时间->明天这些节点和边会被存入图谱。此后,当你问“我下周要和谁讨论预算?”,Jossie可以通过图谱的graph_explore_connections工具进行图谱遍历和推理,找到“张三”和“Q3预算报告”,并结合时间上下文给出精准答案。图谱记忆使得Jossie的理解超越了文本匹配,进入了语义关联的层面。
- 实体:
实操心得:系统提示词(System Prompt)的塑造Jossie的“人格”和行为模式,几乎完全由
config.toml中的llm.system_prompt定义。这是你“训练”Jossie性格的最重要杠杆。一个优秀的提示词不仅要定义角色(“你是一个贴心、主动的助手”),更要明确其行为准则和记忆使用规范。例如,你必须明确指示它:“在对话中,如果用户提到了关于人、地点、事件或项目的新的重要信息,你应该主动调用memory_save工具将其保存。”以及“在回答用户问题前,优先考虑使用memory_search或graph_search工具来查询相关记忆。”没有这些明确的指令,LLM很可能只会进行普通对话,而不会主动利用你精心搭建的记忆系统。
2.3 安全至上的主动代理循环
Jossie2的“主动”特性,通过其代理循环和调度器集成实现,并且始终贯穿着“安全第一”的设计哲学。
反应式循环:用户发送消息后,核心服务会加载该对话的历史消息和所有可用工具定义,调用LLM。LLM可能返回纯文本,也可能返回一个或多个工具调用请求。对于每个工具调用,核心会安全地执行并收集结果,然后将“用户消息+工具调用+工具结果”这一整套上下文再次发送给LLM,形成多轮思考,直到LLM认为可以给出最终答复。这个过程是标准的ReAct(Reasoning and Acting)模式。
主动式调度:
scheduler集成是主动性的发动机。它允许你(或Jossie自己)通过schedule_task或schedule_recurring_task来安排未来任务。例如,你可以直接命令Jossie:“每天上午10点,检查我的Gmail收件箱,如果有‘紧急’标签的邮件,摘要后通知我。”Jossie会解析这个命令,调用调度器工具设定一个每天触发的重复任务。到了指定时间,调度器会唤醒Jossie的代理循环,并注入一个虚拟的“系统消息”作为起点,触发它去执行检查邮件的流程。安全边界:与OpenClaw等提供直接系统shell访问的框架不同,Jossie2默认将所有集成设计为与在线API服务交互(Gmail、Calendar、Web搜索)。它没有直接访问本地文件系统或执行任意命令的权限。浏览器集成也是在一个受控的沙盒环境中运行。这种设计极大地减少了恶意指令或LLM幻觉可能造成的损害。如果你需要本地文件访问,必须显式地、谨慎地开发或集成一个具有明确权限范围的插件。
3. 从零开始部署与深度配置指南
3.1 环境准备与初次运行
部署Jossie2的过程体现了Rust项目的优雅。你不需要复杂的Python虚拟环境或一堆系统依赖。
第一步:获取代码与基础构建
# 克隆仓库 git clone https://github.com/robinp7720/Jossie2.git cd Jossie2 # 使用Rust 2024 Edition进行编译。确保你的Rust工具链是最新的。 cargo build --release这个过程可能会花费一些时间,因为需要编译Rust编译器本身、所有依赖项以及Chrome的headless shell(用于浏览器集成)。首次构建请保持网络通畅。
第二步:核心配置 - config.toml配置文件是Jossie2的大脑连接器。config.sample.toml是一个完整的模板,你必须复制并修改它。
cp config.sample.toml config.toml用文本编辑器打开config.toml,以下几个部分是必须关注的:
[llm]部分:这是项目的命脉。你需要一个OpenAI格式的API密钥。api_url可以是OpenAI官方端点,也可以是任何兼容的第三方代理(如Azure OpenAI或本地部署的Ollama)。model决定了核心智能,gpt-4或gpt-4o在复杂推理上表现更好,而kg_model可以设置为更便宜的gpt-3.5-turbo或gpt-4o-mini来专门处理知识图谱抽取,以节省成本。[llm] api_url = "https://api.openai.com/v1" api_key = "sk-..." # 你的API密钥 model = "gpt-4o" kg_model = "gpt-4o-mini" # 知识图谱专用轻量模型 system_prompt = """ 你是Jossie,一个热情、细心、主动的AI个人助手。你的目标是成为用户值得信赖的伙伴。 核心行为准则: 1. 长期记忆:主动保存对话中关于用户、联系人、项目、事件的关键信息到记忆系统。 2. 知识关联:使用知识图谱来理解和连接不同信息点。 3. 主动协助:在获得用户许可或通过预设任务的情况下,主动检查邮件、日历,并提供提醒。 4. 工具优先:在回答涉及外部信息的问题时,优先考虑使用搜索、查询等工具获取实时数据。 保持对话自然、友好。 """[server]部分:设置服务监听的端口和认证令牌。auth_token是保护你API的简单密码,务必修改。[server] host = "0.0.0.0" port = 3000 auth_token = "your-super-secret-token-here"[database]部分:默认的SQLite路径即可。数据库文件会在首次运行时自动创建。
第三步:运行与验证
# 在项目根目录下运行 cargo run --release如果一切顺利,终端会显示服务器启动日志。此时,打开浏览器访问http://localhost:3000。你会看到一个简洁的Web聊天界面。在登录框输入你在config.toml中设置的auth_token,即可开始与Jossie对话。
注意事项:初次对话的“冷启动”第一次和Jossie聊天时,你可能会觉得它和普通ChatGPT区别不大。这是因为它的记忆数据库和知识图谱还是空的。你需要有意识地“喂养”它信息。例如,主动介绍你自己:“我叫李雷,是一名软件工程师,目前在‘智慧城市’项目组工作,我的经理是韩梅梅。” Jossie会调用记忆和图谱工具保存这些信息。几次交互后,你再问“我的经理是谁?”,它就能从记忆中准确调取答案了。这个过程类似于培养一个数字伙伴,初期需要一些引导。
3.2 关键集成配置详解
邮箱集成配置邮箱是Jossie主动性的重要数据源。配置IMAP(收信)和SMTP(发信):
[email] imap_host = "imap.gmail.com" # 以Gmail为例 imap_port = 993 smtp_host = "smtp.gmail.com" smtp_port = 587 username = "your-email@gmail.com" password = "your-app-password" # 注意:不要用邮箱登录密码,要用应用专用密码重要警告:对于Gmail等现代邮箱服务,务必在账户安全设置中开启“两步验证”,然后生成一个“应用专用密码”填入此处。直接使用登录密码通常会因安全策略导致认证失败。
谷歌服务集成配置这是实现Gmail、Calendar、Drive功能的核心。你需要前往 Google Cloud Console 创建一个项目,并配置OAuth 2.0凭据。
- 创建OAuth 2.0客户端ID,应用类型选择“桌面应用”。
- 将获得的
client_id和client_secret填入配置。 - 在Jossie运行后,访问
http://localhost:3000/setup/google,会引导你完成OAuth授权流程,将访问令牌存入数据库。
[google] client_id = "xxxxxx.apps.googleusercontent.com" client_secret = "GOCSPX-xxxxxx"此集成支持多个谷歌账户,授权后即可在对话中通过google_list_accounts工具管理。
浏览器集成配置此集成无需额外配置,但首次运行时会自动下载一个特定版本的Chromium。请确保网络环境允许访问Google的存储服务。如果下载失败,你也可以手动指定本地Chrome或Chromium的路径(需查阅相关环境变量配置)。
3.3 多前端接入:Web与Telegram
Jossie2默认提供了Web UI,这是一个基于React的简洁聊天界面,位于frontend/目录。运行cargo run时,后端会自动服务这些静态文件。
Telegram机器人配置将Jossie接入Telegram,可以让你随时随地通过手机与它交互。
- 在Telegram中搜索
@BotFather,创建一个新的Bot,获取bot_token。 - 将token填入配置:
[telegram] bot_token = "1234567890:AAHxQ4xxxxxxxxxxxx" # allowed_user_id = 987654321 # 可选:限制只允许特定用户ID使用 - 重启Jossie。现在,你可以在Telegram中与你创建的Bot聊天,所有对话历史会同步到同一个后端数据库中。
API直接调用对于开发者,直接调用HTTP API或WebSocket接口是更灵活的方式。所有接口都需要Bearer Token认证。
# 示例:通过cURL发送消息 curl -X POST http://localhost:3000/api/chat \ -H "Authorization: Bearer your-super-secret-token-here" \ -H "Content-Type: application/json" \ -d '{ "message": "你好,Jossie", "conversation_id": null }' # 示例:使用WebSocket进行流式对话(使用wscat等工具) # 连接地址: ws://localhost:3000/api/chat/stream?token=your-super-secret-token-hereWebSocket接口支持流式响应,你可以实时看到LLM的思考过程和工具调用情况,体验更佳。
4. 高级使用模式与场景化实战
4.1 构建个人知识库:从对话到图谱
Jossie最强大的能力在于将零散的对话转化为结构化的知识。以下是一个实战工作流:
- 信息注入阶段:在初期,像和朋友聊天一样向Jossie介绍你的世界。
- “我最近在忙‘家庭智能花园’项目,主要用Raspberry Pi和传感器。”
- “我的合作伙伴是Alex,他的邮箱是alex@example.com。”
- “我们计划下周五下午两点进行一次项目评审会。”
- 自动图谱构建:你不需要显式命令。Jossie会在后台自动运行知识图谱抽取模型,将上述对话转化为:
- 节点:
项目:家庭智能花园,技术:Raspberry Pi,技术:传感器,人:Alex,事件:项目评审会。 - 边:
你-忙于->家庭智能花园项目;家庭智能花园项目-使用->Raspberry Pi;家庭智能花园项目-合作伙伴->Alex;项目评审会-属于->家庭智能花园项目。
- 节点:
- 知识查询与推理:几天后,你可以进行复杂查询。
- 你问:“我下周和Alex要干嘛?”
- Jossie的思考链:调用
graph_search查找“Alex” -> 发现与“你”和“家庭智能花园项目”的关系 -> 调用graph_explore_connections查找与“Alex”和“项目”相关的事件 -> 定位到“项目评审会”节点及其时间属性 -> 返回答案:“你与Alex计划在下周五下午两点进行‘家庭智能花园项目’的评审会。”
- 可视化查看:访问
http://localhost:3000/graph,你可以看到一个动态的、可交互的知识图谱可视化界面,直观地看到所有实体如何相互关联。
4.2 实现自动化工作流:调度器与工具链
Jossie的调度器允许你创建复杂的自动化工作流,将多个工具串联起来。
场景:每日晨报机器人目标:每天早晨8点,让Jossie自动检查日历中的当日会议,搜索相关项目的未读邮件,并生成一份摘要发送到Telegram。
你可以通过一次对话完成设置:
你:“Jossie,请创建一个每天上午8点运行的任务。任务内容是:1. 查看我今天的日历事件。2. 搜索我的Gmail收件箱中,过去24小时内来自‘项目组’标签且未读的邮件。3. 将日历事件和邮件摘要整合成一份简洁的晨报。4. 使用
send_user_message工具,将这份晨报发送给我。”
Jossie会解析你的指令,并可能通过多轮工具调用来确认细节(比如你有哪些日历账户,项目组是标签还是发件人),最终调用schedule_recurring_task工具,设定一个interval_seconds为86400(24小时)的重复任务。
内部执行流程:
- 每天8点,调度器触发任务,启动一个独立的Jossie代理实例。
- 该实例的“系统消息”被设置为执行上述任务指令。
- LLM开始规划:首先调用
calendar_list_events获取今日日程。 - 接着调用
gmail_search,使用查询语句label:项目组 is:unread newer_than:1d。 - 然后调用
gmail_read阅读关键的邮件内容。 - LLM综合以上信息,生成一份文本摘要。
- 最后,调用
send_user_message工具(这是调度器集成提供的特殊工具,用于在非对话上下文中主动向用户发送消息),将摘要推送出去。 - 整个流程完全自动化,无需你每日手动操作。
4.3 外部系统集成:HTTP集成作为万能胶水
http集成是连接Jossie与外部世界的“万能胶水”。它允许LLM直接调用任意的HTTP API,极大地扩展了其能力边界。
场景:连接内部任务管理系统假设你公司内部有一个RESTful API的任务管理系统(例如自建的Jira或禅道)。
- 配置:在
config.toml的[http]部分,将你的内部API域名加入allowed_domains列表,或者谨慎地使用["*"](生产环境不推荐)。 - 描述工具:虽然
http集成只提供一个通用的http_request工具,但你可以通过系统提示词(System Prompt)来“教”Jossie如何使用它。在提示词中添加:“当用户提到‘任务’、‘工单’或‘Ticket’时,你可以使用
http_request工具与我们的内部任务系统交互。系统的API端点是https://api.internal-company.com。获取所有任务的接口是GET /api/tickets,需要添加认证头Authorization: Bearer internal-token-123。创建任务的接口是POST /api/tickets,请求体格式为JSON:{"title": string, "description": string, "assignee": string}。” - 使用:现在你可以直接对Jossie说:“帮我创建一个新任务,标题是‘修复登录页面的CSS错误’,分配给前端组的张三,描述就用我们刚才讨论的。” Jossie会理解你的意图,构造出合适的HTTP请求并发送。
实操心得:为HTTP集成编写“使用说明书”由于
http_request工具非常通用,LLM需要明确的指引才能正确使用。最佳实践是在系统提示词中,为你希望集成的每一个外部API编写一个简短的“自然语言说明书”。说明书应包括:1) 什么情况下使用这个API;2) API的基础URL;3) 必要的认证信息;4) 主要端点(Endpoint)和对应的HTTP方法、参数格式示例。这相当于为LLM编写了一套内部API的SDK文档,能显著提高工具调用的准确率。
5. 运维、问题排查与性能调优
5.1 部署方案选择
- 开发/测试:直接使用
cargo run是最简单的。配合RUST_LOG=debug环境变量可以输出详细日志。 - 生产环境 - systemd:对于Linux服务器,项目提供的
contrib/systemd/jossie2.service单元文件是首选。它提供了进程守护、自动重启、日志管理等功能。你需要:- 将编译好的
target/release/jossie2二进制文件、config.toml以及构建好的前端静态文件(frontend/dist)放置到服务器固定目录(如/opt/jossie)。 - 修改service文件中的路径和环境变量。
- 使用
systemctl enable --now jossie2启动并设为开机自启。
- 将编译好的
- 生产环境 - Docker:Docker提供了更好的环境隔离和一致性。项目根目录的
Dockerfile定义了多阶段构建,能生成一个包含所有依赖的镜像。使用Docker Compose可以更方便地管理数据库文件卷和配置。
确保在Docker中正确挂载存储docker-compose up -dconfig.toml和SQLite数据库文件的持久化卷。
5.2 常见问题与排查技巧
问题1:LLM不调用工具,总是直接回答。
- 排查:这是最常见的问题。首先检查
RUST_LOG=debug下的日志,看LLM的响应中是否包含tool_calls字段。如果没有,问题出在LLM侧。 - 解决:
- 强化系统提示词:在提示词中反复强调“你必须使用工具来获取信息”、“在回答关于用户个人信息、邮件、日历等问题前,务必先搜索记忆或知识图谱”。
- 检查工具描述:每个集成返回的
ToolDefinition包含了工具的名称和描述。确保工具描述清晰、准确,能让LLM理解何时该调用它。你可以修改集成代码中的工具描述。 - 使用更强的模型:
gpt-3.5-turbo在复杂工具调用规划上可能力不从心,尝试切换到gpt-4或gpt-4o。
问题2:知识图谱自动抽取不工作或抽取不准。
- 排查:检查日志中是否有
kg_model调用的记录或错误。查看数据库graph_nodes和graph_edges表是否为空或有乱码。 - 解决:
- 确认配置:确保
config.toml中的kg_model已设置,并且该模型在你的API端点可用。 - 优化抽取提示词:知识图谱抽取的质量取决于内置的提示词模板。如果发现实体识别错误(如把“明天”识别为人名),你可能需要根据你的语言环境(中文)微调
jossie-integration-graphcrate中的提示词。 - 分批处理:如果单次对话内容很长,抽取效果可能变差。可以考虑在代码层面将长对话分割成多个片段进行抽取。
- 确认配置:确保
问题3:邮箱或谷歌集成认证失败。
- 排查:日志中通常会明确显示IMAP、SMTP或OAuth的错误信息。
- 解决:
- 邮箱:99%的问题源于密码/应用专用密码错误,或邮箱服务商的安全设置(如“允许不够安全的应用”未开启,对于Gmail已基本废弃,必须使用应用专用密码)。
- 谷歌:检查Google Cloud Console中OAuth同意屏幕是否已发布(测试环境可只添加测试用户),以及API是否已启用(Gmail API, Calendar API, Drive API)。确保重定向URI配置正确(本地开发通常为
http://localhost:3000/setup/google/callback)。
问题4:内存占用或响应速度随时间变慢。
- 排查:SQLite数据库文件(
jossie.db)可能随着记忆和图谱的增长而变大。检查数据库大小。 - 解决:
- SQLite维护:定期对数据库执行
VACUUM;命令(可通过sqlite3 jossie.db连接后执行),以整理碎片、回收空间。 - 对话历史清理:
config.toml中的max_context_messages控制了每次对话加载的历史消息数量。适当调低(如30)可以减少LLM的上下文长度,提升速度并降低API成本。更早的历史记录仍保存在数据库,只是不参与当前推理。 - 日志级别:在生产环境将
RUST_LOG设置为info或warn,减少debug/trace级别的大量日志输出。
- SQLite维护:定期对数据库执行
5.3 成本控制与优化建议
Jossie2的运行成本主要来自LLM API调用,尤其是长期运行且主动任务多的情况下。
- 模型分级使用:充分利用
kg_model配置。知识图谱抽取是后台任务,对推理能力要求相对较低,使用gpt-4o-mini或gpt-3.5-turbo可以大幅降低成本,而将gpt-4o留给需要复杂规划和对话的主循环。 - 控制上下文长度:
max_context_messages和max_agent_iterations是两个重要的控制阀。前者限制单次请求携带的历史消息量,后者限制LLM“思考-行动”循环的最大轮数,防止陷入死循环消耗大量token。 - 精简系统提示词:系统提示词会随着每次请求发送给LLM。在满足功能需求的前提下,尽量使其简洁明了,避免冗长的叙述。
- 审慎使用主动任务:每个由调度器触发的主动任务,都是一次完整的、无用户消息的API调用。评估每个周期性任务的必要性和执行频率。例如,检查邮件的任务可以设为每小时一次,而非每分钟一次。
- 监控与审计:定期查看数据库中的
messages表,了解对话和工具调用的频率。许多LLM API提供商也提供了详细的使用量仪表盘,帮助你分析消耗点。
Jossie2代表了一种更人性化、更深入的AI交互范式。它不再是一个简单的问答机,而是一个通过持续学习不断进化的数字存在。部署和调优它的过程,本身就是在塑造一个专属的智能伙伴。从简单的记忆查询,到复杂的自动化工作流,再到与外部系统的深度集成,它的能力边界很大程度上取决于你的想象力和配置技巧。