DASD-4B-Thinking实操手册:Chainlit前端交互设计+后端vLLM服务协同部署 DASD-4B-Thinking实操手册Chainlit前端交互设计后端vLLM服务协同部署你是不是也遇到过这样的情况好不容易部署了一个强大的AI模型但每次调用都得敲命令行或者写一堆脚本体验特别不友好。想给模型做个漂亮的交互界面又觉得前端开发太复杂后端服务还得自己折腾。今天我就带你解决这个问题。咱们用DASD-4B-Thinking这个专门做复杂推理的模型搭配vLLM做后端推理服务再用Chainlit快速搭建一个美观实用的Web界面。整个过程就像搭积木一样简单不需要你懂前端框架也不用写复杂的HTTP接口30分钟就能搞定一个完整的AI应用。1. 项目概览为什么选择这个技术栈在开始动手之前我们先搞清楚为什么要用这套组合。了解背后的设计思路能帮你更好地理解每个环节的作用。1.1 DASD-4B-Thinking专为复杂推理而生DASD-4B-Thinking是个很有意思的模型。它只有40亿参数在现在动辄几百亿参数的大模型里算是“小个子”但它在数学推理、代码生成、科学问题解答这些需要多步思考的任务上表现很出色。这个模型是怎么做到的呢它用了“长链式思维推理”技术。简单来说就是让模型像人一样遇到复杂问题时不是直接给答案而是先想几步把思考过程展示出来最后再得出结论。这种思考方式特别适合解决那些需要逻辑推导的问题。举个例子你问它“一个水池进水管每小时进水10立方米出水管每小时出水8立方米如果两个水管同时开多久能装满100立方米的水池”普通模型可能直接给个答案但DASD-4B-Thinking会把计算过程一步步展示出来让你看到它是怎么想的。1.2 vLLM让推理速度飞起来vLLM是现在最流行的大模型推理框架之一。它的核心优势有两个内存管理特别聪明传统的推理框架在处理多个请求时每个请求都要单独分配内存。vLLM用了“PagedAttention”技术就像电脑的内存分页管理一样能更高效地利用显存同时处理更多请求。推理速度很快通过优化内存访问和计算流程vLLM能显著提升生成速度。对于DASD-4B-Thinking这种40亿参数的模型在合适的硬件上能达到每秒生成几十个token的速度。1.3 Chainlit零前端经验的交互界面Chainlit是我最近发现的一个宝藏工具。它专门为AI应用设计让你用Python就能快速搭建一个功能完整的Web界面。不用写HTML/CSS/JavaScript所有界面元素都用Python代码定义像写普通Python程序一样简单。内置对话管理自动处理消息历史、会话状态支持流式输出打字机效果。丰富的UI组件支持文本输入、文件上传、图片显示、代码高亮、Markdown渲染等。部署简单一行命令就能启动服务支持生产环境部署。2. 环境准备与快速部署好了理论部分讲完了现在咱们开始动手。我会带你一步步完成整个部署过程确保你能跟着做出来。2.1 检查模型服务状态首先我们需要确认vLLM服务已经正常启动。在CSDN星图镜像中模型服务通常是预部署好的我们只需要检查一下状态。打开终端输入以下命令cat /root/workspace/llm.log你会看到类似这样的输出INFO 07-10 14:30:15 llm_engine.py:73] Initializing an LLM engine with config: model/root/workspace/models/DASD-4B-Thinking, tokenizer/root/workspace/models/DASD-4B-Thinking, tokenizer_modeauto, trust_remote_codeTrue, dtypetorch.float16, max_seq_len4096, ... INFO 07-10 14:30:20 llm_engine.py:125] # GPU blocks: 1458, # CPU blocks: 512 INFO 07-10 14:30:25 llm_engine.py:130] KV cache usage: 0.0% INFO 07-10 14:30:25 llm_engine.py:131] Loading weights finished. Total time: 10.2s INFO 07-10 14:30:25 llm_engine.py:135] Model loaded successfully.看到“Model loaded successfully”就说明模型加载成功了。如果还没加载完可能需要等几分钟。模型加载时间取决于硬件配置一般1-3分钟就能完成。2.2 理解服务架构在开始写代码之前我们先看看整个系统的架构用户浏览器 ↓ Chainlit前端 (运行在8501端口) ↓ HTTP请求 ↓ vLLM后端服务 (运行在8000端口) ↓ DASD-4B-Thinking模型Chainlit作为前端接收用户的输入然后通过HTTP请求调用后端的vLLM服务vLLM服务加载模型进行推理再把结果返回给ChainlitChainlit把结果显示给用户。这个架构的好处是前后端分离。前端负责交互界面后端负责模型推理两者通过标准的HTTP接口通信。这样即使以后要换前端框架或者升级后端服务都不会影响另一边。3. Chainlit前端开发实战现在我们来创建Chainlit应用。我会带你写一个完整的应用包含所有必要的功能。3.1 创建应用文件首先在合适的位置创建一个Python文件比如叫app.py# app.py - Chainlit前端应用 import chainlit as cl import requests import json from typing import Optional # vLLM服务的地址 VLLM_API_URL http://localhost:8000/v1/completions # 系统提示词用于设定模型的角色和行为 SYSTEM_PROMPT 你是一个专业的AI助手擅长数学推理、代码生成和科学问题解答。 请用清晰、有条理的方式回答问题展示你的思考过程。 如果问题涉及计算请分步骤展示计算过程。 如果问题需要代码实现请提供可运行的代码并解释关键部分。 cl.on_chat_start async def on_chat_start(): 聊天开始时的初始化函数 这里可以设置会话的初始状态 # 设置欢迎消息 await cl.Message( content 你好我是基于DASD-4B-Thinking模型的AI助手。\n\n 我特别擅长需要多步推理的问题比如\n • 数学计算和证明\n • 编程问题解答\n • 科学概念解释\n • 逻辑推理题\n\n 请直接问我任何问题我会展示详细的思考过程。, author助手 ).send() # 初始化会话状态 cl.user_session.set(conversation_history, []) cl.on_message async def on_message(message: cl.Message): 处理用户消息的核心函数 每次用户发送消息时都会调用这个函数 # 显示“正在思考”的提示 msg cl.Message(content) await msg.send() # 获取会话历史 history cl.user_session.get(conversation_history, []) # 构建完整的提示词 full_prompt build_prompt(message.content, history) try: # 调用vLLM API response call_vllm_api(full_prompt) # 更新会话历史 history.append({role: user, content: message.content}) history.append({role: assistant, content: response}) cl.user_session.set(conversation_history, history) # 流式显示回复 await display_streaming_response(msg, response) except Exception as e: # 错误处理 error_msg f调用模型服务时出错{str(e)} await msg.update(contenterror_msg) def build_prompt(user_input: str, history: list) - str: 构建发送给模型的提示词 包含系统提示、历史对话和当前问题 prompt_parts [] # 添加系统提示 prompt_parts.append(fSystem: {SYSTEM_PROMPT}) # 添加历史对话 for item in history[-6:]: # 保留最近6轮对话 role User if item[role] user else Assistant prompt_parts.append(f{role}: {item[content]}) # 添加当前问题 prompt_parts.append(fUser: {user_input}) prompt_parts.append(Assistant:) return \n\n.join(prompt_parts) def call_vllm_api(prompt: str) - str: 调用vLLM的API接口 # 准备请求数据 payload { model: DASD-4B-Thinking, prompt: prompt, max_tokens: 1024, temperature: 0.7, top_p: 0.9, stop: [User:, System:], stream: False } # 发送请求 response requests.post( VLLM_API_URL, jsonpayload, headers{Content-Type: application/json}, timeout60 # 60秒超时 ) # 检查响应 if response.status_code 200: result response.json() return result[choices][0][text].strip() else: raise Exception(fAPI调用失败: {response.status_code} - {response.text}) async def display_streaming_response(msg: cl.Message, response: str): 模拟流式输出效果 让回复像打字一样逐个字符显示 # 清空初始消息 await msg.update(content) # 模拟打字效果 displayed_text for char in response: displayed_text char await msg.update(contentdisplayed_text) await cl.sleep(0.01) # 控制显示速度 # Chainlit应用配置 cl.set_chat_profiles async def chat_profile(): 设置聊天配置 可以在这里定义不同的对话模式 return [ cl.ChatProfile( name标准模式, markdown_description适合大多数问题的标准对话模式。 ), cl.ChatProfile( name代码专家, markdown_description专注于编程问题解答提供详细的代码示例。 ), cl.ChatProfile( name数学助手, markdown_description专门处理数学问题展示完整的计算过程。 ) ]这个文件包含了Chainlit应用的所有核心功能。我来解释几个关键部分cl.on_chat_start装饰器这个函数在每次新对话开始时执行用来初始化会话状态和显示欢迎消息。cl.on_message装饰器这是最重要的函数每次用户发送消息都会调用它。它负责处理用户输入、调用模型、显示回复。call_vllm_api函数这是连接vLLM后端的关键。它构造HTTP请求发送给vLLM服务然后处理返回的结果。流式输出display_streaming_response函数让回复像打字一样逐个显示这比一次性显示全部文字体验好很多。3.2 创建配置文件Chainlit需要一个配置文件来定义应用的基本信息。创建chainlit.md文件# 欢迎使用 DASD-4B-Thinking 智能助手 这是一个基于DASD-4B-Thinking模型的对话应用专门处理需要复杂推理的问题。 ## 主要功能 - **数学推理**解决数学问题展示计算过程 - **代码生成**编写和解释各种编程语言的代码 - **科学解答**解释科学概念和原理 - **逻辑推理**分析逻辑问题提供推理过程 ## 使用技巧 1. **问题要具体**描述越详细回答越准确 2. **分步骤提问**复杂问题可以拆分成几个小问题 3. **要求展示思考**可以说“请展示你的思考过程” 4. **提供上下文**如果是连续对话尽量提供之前的背景 ## ⚙️ 技术栈 - **后端模型**DASD-4B-Thinking (4B参数) - **推理引擎**vLLM (高性能推理框架) - **前端框架**Chainlit (Python Web界面) - **部署平台**CSDN星图镜像 --- 开始你的对话吧直接在下方的输入框提问即可。这个文件会在应用启动时显示在侧边栏给用户提供使用指南。3.3 启动Chainlit应用现在我们可以启动应用了。在终端中运行chainlit run app.py -w参数说明run app.py运行app.py文件-w启用自动重载修改代码后会自动重启启动成功后你会看到类似这样的输出Chainlit app is running at http://localhost:8501打开浏览器访问http://localhost:8501就能看到Chainlit的界面了。4. 与模型交互实战演示界面搭好了现在我们来实际测试一下。我会用几个不同类型的例子展示DASD-4B-Thinking的能力。4.1 数学推理测试在输入框中提问一个水池有进水管和出水管。进水管单独开4小时可以注满水池出水管单独开6小时可以放空水池。如果两个水管同时开需要多少小时才能注满水池看看模型的回复我模拟一下典型的思考过程让我一步步计算这个问题。 已知条件 1. 进水管单独开4小时注满 → 进水管每小时进水 1/4 水池 2. 出水管单独开6小时放空 → 出水管每小时出水 1/6 水池 两个水管同时开时 每小时净进水量 进水量 - 出水量 1/4 - 1/6 (3/12) - (2/12) 1/12 这意味着两个水管同时开时每小时能注入 1/12 的水池。 要注满整个水池即注入 1 个水池 所需时间 总水量 ÷ 每小时净进水量 1 ÷ (1/12) 12 小时 所以两个水管同时开需要 12 小时才能注满水池。 验证一下 12小时进水12 × (1/4) 3 个水池 12小时出水12 × (1/6) 2 个水池 净进水量3 - 2 1 个水池 ✓你看模型不仅给出了答案还展示了完整的计算过程。这对于学习数学的学生特别有用他们能看到解题思路而不只是结果。4.2 代码生成测试再试一个编程问题用Python写一个函数判断一个字符串是不是回文。要求忽略空格和标点不区分大小写。模型的回复def is_palindrome(s: str) - bool: 判断字符串是否为回文忽略空格、标点不区分大小写 参数: s: 输入的字符串 返回: bool: 如果是回文返回True否则返回False # 1. 转换为小写 s_lower s.lower() # 2. 移除非字母数字字符 cleaned_chars [] for char in s_lower: if char.isalnum(): # 只保留字母和数字 cleaned_chars.append(char) # 3. 连接清理后的字符 cleaned_str .join(cleaned_chars) # 4. 使用双指针法判断回文 left, right 0, len(cleaned_str) - 1 while left right: if cleaned_str[left] ! cleaned_str[right]: return False left 1 right - 1 return True # 测试用例 if __name__ __main__: test_cases [ A man, a plan, a canal: Panama, # 经典回文 race a car, # 不是回文 , # 空字符串或只有空格 12321, # 数字回文 hello, # 普通非回文 ] for test in test_cases: result is_palindrome(test) print(f{test} - {result}) # 输出解释 # 1. 首先处理大小写问题使用.lower()统一为小写 # 2. 清理字符串使用.isalnum()判断是否为字母或数字 # 3. 双指针法从两端向中间比较时间复杂度O(n)空间复杂度O(1) # 4. 提供了测试用例方便验证函数正确性这个回复有几个亮点提供了完整的、可运行的代码有详细的注释说明每部分的作用包含了测试用例解释了算法的时间复杂度和空间复杂度4.3 科学问题测试再来一个科学推理问题解释一下为什么天空是蓝色的而夕阳是红色的模型的思考过程这是一个关于光散射的经典物理问题。让我分步骤解释 1. **白光由多种颜色组成** 太阳光看起来是白色的但实际上它包含了彩虹的所有颜色红、橙、黄、绿、蓝、靛、紫。 2. **瑞利散射现象** 当光穿过大气层时会与空气中的分子主要是氮气和氧气发生碰撞这种现象叫做散射。 散射的强度与光的波长有关波长越短散射越强。 3. **不同颜色的波长** - 蓝光波长较短约450-495纳米 - 红光波长较长约620-750纳米 蓝光的波长大约是红光的一半所以蓝光被散射的程度是红光的16倍散射强度与波长的四次方成反比。 4. **白天为什么天空是蓝色的** 在白天太阳光直射地球。蓝光因为波长短被大气分子强烈散射到各个方向。 当我们仰望天空时看到的就是这些被散射的蓝光所以天空呈现蓝色。 5. **傍晚为什么夕阳是红色的** 在傍晚太阳接近地平线阳光需要穿过更厚的大气层才能到达我们的眼睛。 在这个过程中大部分蓝光已经被散射掉了只剩下波长较长的红光能够穿透。 所以我们看到的夕阳是红色的。 6. **附加思考** 这个原理也解释了 - 为什么正午的太阳看起来是白色的直射穿过的大气层较薄 - 为什么在太空看天空是黑色的没有大气散射 - 为什么雾天或污染严重时天空发白大颗粒散射所有波长的光 总结天空的蓝色和夕阳的红色都是瑞利散射的结果关键因素是光的波长和穿过大气层的距离。这种分步骤、有逻辑的解释特别适合教学场景。学生不仅能知道结论还能理解背后的原理。5. 高级功能与优化建议基础功能已经实现了但我们可以做得更好。下面是一些进阶功能和优化建议。5.1 添加文件上传功能Chainlit支持文件上传我们可以让用户上传代码文件、数学题目图片等。修改app.pycl.on_message async def on_message(message: cl.Message): 处理用户消息支持文本和文件 # 检查是否有文件上传 if message.elements: for element in message.elements: if element.type image: # 处理图片文件 await handle_image_file(element, msg) elif element.type file: # 处理文本文件 await handle_text_file(element, msg) # 处理文本消息 if message.content: # ... 原有的文本处理逻辑 ... async def handle_text_file(element, msg): 处理上传的文本文件 # 读取文件内容 file_content element.content.decode(utf-8) # 根据文件类型处理 if element.name.endswith(.py): prompt f请分析以下Python代码\n\n{file_content}\n\n请指出代码中的问题并提出改进建议。 elif element.name.endswith(.txt): prompt f请处理以下文本\n\n{file_content} else: prompt f请分析以下文件内容\n\n{file_content} # 调用模型分析 response call_vllm_api(prompt) await msg.update(contentresponse) async def handle_image_file(element, msg): 处理上传的图片文件 # 这里可以集成OCR功能识别图片中的文字 # 或者直接让用户描述图片内容 await msg.update(content图片上传功能已收到。请描述一下图片内容或者告诉我你想对这张图片做什么分析。)5.2 优化提示词工程提示词的质量直接影响模型的表现。我们可以根据不同的对话模式调整提示词# 不同的系统提示词模板 PROMPT_TEMPLATES { standard: { system: 你是一个专业的AI助手擅长数学推理、代码生成和科学问题解答。请用清晰、有条理的方式回答问题展示你的思考过程。, temperature: 0.7 }, code_expert: { system: 你是一个资深的编程专家精通多种编程语言。请提供高质量的代码示例包含详细的注释和解释。注重代码的可读性、效率和最佳实践。, temperature: 0.3 # 更低的温度代码生成更确定 }, math_tutor: { system: 你是一个耐心的数学老师。请分步骤解答数学问题展示完整的计算过程。用简单易懂的语言解释复杂概念并提供验证方法。, temperature: 0.5 } } def build_prompt(user_input: str, history: list, mode: str standard) - str: 根据模式构建提示词 template PROMPT_TEMPLATES.get(mode, PROMPT_TEMPLATES[standard]) prompt_parts [] prompt_parts.append(fSystem: {template[system]}) # 添加历史对话 for item in history[-6:]: role User if item[role] user else Assistant prompt_parts.append(f{role}: {item[content]}) prompt_parts.append(fUser: {user_input}) prompt_parts.append(Assistant:) return \n\n.join(prompt_parts)5.3 添加对话历史管理长时间对话时历史记录可能很长。我们可以添加历史管理功能cl.on_message async def on_message(message: cl.Message): 处理用户消息支持历史管理 # 检查是否是清除历史命令 if message.content.lower() in [/clear, /reset, 清除历史]: cl.user_session.set(conversation_history, []) await cl.Message(content对话历史已清除。).send() return # 检查是否是查看历史命令 if message.content.lower() in [/history, /历史, 查看历史]: history cl.user_session.get(conversation_history, []) if not history: await cl.Message(content当前没有对话历史。).send() else: history_text 最近的对话历史\n\n for i, item in enumerate(history[-5:], 1): # 显示最近5条 role 用户 if item[role] user else 助手 history_text f{i}. {role}: {item[content][:100]}...\n await cl.Message(contenthistory_text).send() return # 正常的消息处理...5.4 性能优化建议随着用户增多我们需要考虑性能优化1. 添加请求队列import asyncio from collections import deque class RequestQueue: def __init__(self, max_concurrent3): self.queue deque() self.semaphore asyncio.Semaphore(max_concurrent) async def add_request(self, prompt): async with self.semaphore: return await self._process_request(prompt) async def _process_request(self, prompt): # 实际的API调用 return call_vllm_api(prompt) # 在应用中初始化队列 request_queue RequestQueue(max_concurrent3)2. 添加缓存机制import hashlib from functools import lru_cache lru_cache(maxsize100) def get_cached_response(prompt_hash: str): 缓存频繁使用的回答 pass def call_vllm_api_with_cache(prompt: str): # 生成提示词的哈希值作为缓存键 prompt_hash hashlib.md5(prompt.encode()).hexdigest() # 检查缓存 cached get_cached_response(prompt_hash) if cached: return cached # 没有缓存调用API response call_vllm_api(prompt) # 存入缓存 cache_response(prompt_hash, response) return response3. 添加超时和重试import asyncio from tenacity import retry, stop_after_attempt, wait_exponential retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, min4, max10)) async def call_vllm_api_with_retry(prompt: str): 带重试机制的API调用 try: return await asyncio.wait_for( call_vllm_api(prompt), timeout30.0 # 30秒超时 ) except asyncio.TimeoutError: raise Exception(请求超时请稍后重试)6. 部署与维护开发完成后我们需要考虑如何部署和维护这个应用。6.1 生产环境部署对于生产环境建议使用以下配置使用Gunicorn运行Chainlit# 安装gunicorn pip install gunicorn # 启动服务 gunicorn -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8501 app:app添加Nginx反向代理如果需要server { listen 80; server_name your-domain.com; location / { proxy_pass http://localhost:8501; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }6.2 监控与日志添加监控和日志功能方便排查问题import logging from datetime import datetime # 配置日志 logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(app.log), logging.StreamHandler() ] ) logger logging.getLogger(__name__) cl.on_message async def on_message(message: cl.Message): 添加日志记录的消息处理 user_id cl.user_session.get(id, unknown) logger.info(f用户 {user_id} 发送消息: {message.content[:50]}...) start_time datetime.now() try: # 处理消息... response call_vllm_api(prompt) end_time datetime.now() duration (end_time - start_time).total_seconds() logger.info(f请求处理完成 - 用户: {user_id}, 耗时: {duration:.2f}秒) except Exception as e: logger.error(f处理消息时出错 - 用户: {user_id}, 错误: {str(e)}) raise6.3 安全考虑输入验证def validate_input(user_input: str) - bool: 验证用户输入的安全性 # 检查长度 if len(user_input) 5000: return False, 输入内容过长请控制在5000字符以内 # 检查是否有恶意内容简单示例 malicious_patterns [ system(, exec(, eval(, import os, rm -rf, drop table, delete from ] for pattern in malicious_patterns: if pattern in user_input.lower(): return False, 输入包含不安全内容 return True, 速率限制from collections import defaultdict import time class RateLimiter: def __init__(self, max_requests10, time_window60): self.max_requests max_requests self.time_window time_window self.user_requests defaultdict(list) def is_allowed(self, user_id): now time.time() requests self.user_requests[user_id] # 移除过期的请求记录 requests [req_time for req_time in requests if now - req_time self.time_window] self.user_requests[user_id] requests # 检查是否超过限制 if len(requests) self.max_requests: return False # 添加当前请求 requests.append(now) return True # 在应用中使用 rate_limiter RateLimiter(max_requests30, time_window60) cl.on_message async def on_message(message: cl.Message): user_id cl.user_session.get(id, unknown) if not rate_limiter.is_allowed(user_id): await cl.Message(content请求过于频繁请稍后再试。).send() return # 正常处理...7. 总结通过这个项目我们完成了一个完整的AI应用开发从模型部署到前端开发从基础功能到高级优化。7.1 关键收获技术栈选择合理vLLM Chainlit DASD-4B-Thinking 这个组合既保证了推理性能又提供了良好的用户体验开发效率也很高。前后端分离架构这种架构让系统更灵活。以后如果想换模型只需要改后端如果想换界面只需要改前端。两者通过HTTP接口通信互不影响。用户体验优先Chainlit提供的流式输出、文件上传、会话管理等功能让AI应用用起来更自然、更友好。用户不需要懂技术打开网页就能用。可扩展性强我们实现的系统很容易扩展。可以添加更多功能比如多模态支持可以优化性能比如缓存、队列可以增强安全比如输入验证、速率限制。7.2 实际应用场景这个系统可以用在很多地方教育领域作为数学、编程的智能助教帮助学生理解复杂概念。开发工具帮助程序员写代码、调试、学习新技术。内容创作辅助写作、翻译、总结文档。客服系统处理需要多步推理的复杂咨询。研究助手帮助研究人员分析问题、整理思路。7.3 下一步建议如果你想让这个系统更强大可以考虑集成更多模型除了DASD-4B-Thinking可以同时部署其他模型让用户选择。添加多模态支持集成图片识别、语音输入输出等功能。实现用户系统添加登录、个人历史、收藏等功能。优化移动端体验让界面在手机上也很好用。添加插件系统让其他开发者可以扩展功能。最重要的是这个项目展示了如何用现代AI工具快速构建实用的应用。你不必是前端专家也不必是AI科学家只要会Python就能做出很酷的东西。AI技术正在变得越来越易用像Chainlit这样的工具降低了开发门槛。希望这个教程能帮你快速上手做出自己的AI应用。在实际使用中你会遇到各种具体问题那时候再针对性地优化就好。记住最好的学习方式就是动手做。从这个小项目开始逐步添加功能慢慢你就会掌握整套技术栈。祝你开发顺利获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。