智能体原生架构:从外挂到原生的企业软件设计实践
1. 从“外挂”到“原生”企业软件架构正在经历什么过去两年我参与过几个企业级智能体项目的落地从最初的“给现有系统加一个对话入口”到后来把智能体能力直接嵌入业务流的底层这个过程中最深的感受是大多数企业软件对智能体的支持还停留在“外挂”阶段。所谓外挂就是在原有的表单、流程、报表体系之外单独开一个聊天窗口用户问一句系统调一次接口返回一段文字。这种模式能跑通Demo但一旦进入真实业务场景问题就全暴露出来了——上下文断裂、权限混乱、状态无法持久化、多智能体之间无法协作。“智能体原生”这个概念正是冲着这些痛点来的。它不是简单地在软件里加一个AI助手而是从架构设计的第一天起就把智能体当作系统的一等公民。什么意思就像当年“云原生”不是把应用搬到云上就完事而是要求应用从设计之初就考虑弹性伸缩、服务发现、容错恢复一样智能体原生要求企业软件的每一个核心模块——数据层、逻辑层、交互层、权限层——都围绕智能体的运行范式来重新组织。这篇文章适合谁看如果你是企业架构师、技术负责人或者正在负责把智能体能力落地到实际业务系统中的开发者那接下来的内容应该能帮你少走一些弯路。我会从架构设计的底层逻辑讲起拆解智能体原生系统的核心组件、实操中的关键决策点以及我在实际项目中踩过的坑和总结出的排查技巧。全文基于常见的工程实践展开不涉及任何特定平台或工具的商业推广你可以直接把这些思路套用到自己的技术栈里。2. 智能体原生架构的核心设计思路2.1 为什么“外挂式智能体”走不远先说说外挂式智能体的典型架构。大多数团队的第一版方案是这样的前端加一个对话框组件后端加一个路由层把用户输入转发给大模型接口再把返回结果渲染出来。如果业务需要查数据就再写一个函数调用让模型去调内部API。这个方案在单轮问答场景下没问题但一旦涉及多轮任务比如“帮我查一下上个月的销售数据然后按区域拆分再生成一份对比报告”系统就开始力不从心了。根本原因在于外挂式架构里智能体没有“状态”。每一轮对话都是独立的请求-响应周期模型不知道上一轮做了什么也不知道当前处于任务的哪个阶段。你可能会说可以把历史对话塞进上下文里。但上下文窗口是有限的而且随着任务复杂度上升塞进去的历史信息会迅速膨胀既增加成本又降低模型注意力。更麻烦的是当任务需要调用多个内部系统时外挂式架构无法保证事务一致性——比如扣了库存但订单没生成或者生成了报告但数据源已经更新了。另一个致命问题是权限。外挂式智能体通常用一个统一的“服务账号”去访问所有内部API这意味着智能体拥有的权限远大于当前用户实际应有的权限。一个普通员工通过对话入口可能间接访问到本不该他看到的数据。这在企业环境里是绝对不能接受的。2.2 智能体原生的三个核心原则智能体原生架构要解决的就是上述问题。我把它归纳为三个核心原则这三个原则决定了后续所有技术选型和设计决策。第一个原则是状态内聚。智能体的执行状态——包括当前任务阶段、已完成的步骤、待处理的子任务、中间结果——必须作为系统的一等数据被持久化和管理而不是塞在对话历史里。这意味着你需要一个专门的状态管理层它独立于对话上下文能够被查询、恢复和审计。在实际项目中我通常会用一张任务状态表来记录每个智能体任务的完整生命周期每个步骤的输入输出都落库这样即使服务重启任务也能从断点继续。第二个原则是权限穿透。智能体在执行任何操作时必须以当前用户的身份和权限去执行而不是用一个超级账号。这要求智能体运行时能够获取到用户的身份凭证并且在调用内部服务时传递这个凭证。具体实现上可以采用令牌传递的方式让智能体在调用下游服务时携带用户令牌下游服务按照正常权限校验逻辑处理。这样智能体本质上成了用户的一个“代理”它能看到和操作的严格受限于用户本身的权限。第三个原则是能力原子化。智能体需要调用的每一个业务能力都应该被封装成独立的、可组合的原子服务。这些原子服务有明确的输入输出契约不依赖对话上下文可以独立测试和复用。比如“查询订单状态”是一个原子能力“生成对账单”是另一个智能体负责编排这些原子能力来完成复杂任务。这样做的好处是智能体的编排逻辑和业务逻辑解耦业务逻辑的变更不会影响智能体的核心框架反之亦然。2.3 架构分层与组件拆解基于这三个原则一个典型的智能体原生架构可以分成四层。我用一个实际项目中的分层方案来说明你可以根据自己的技术栈做调整。交互层负责接收用户输入但它不仅仅是对话框。在智能体原生架构里交互层需要支持多种输入形式——文本、语音、结构化表单、甚至来自其他系统的事件触发。交互层的核心职责是把用户意图转化为一个“任务请求”并附带用户身份信息然后交给下一层。编排层是智能体的“大脑”。它接收任务请求根据任务类型选择合适的执行策略。简单任务可能直接调用一个原子能力就完成了复杂任务则需要拆解成多个步骤按顺序或并行执行。编排层还需要处理异常——某个步骤失败了是重试、跳过还是回滚这些决策逻辑都在编排层实现。我通常会用状态机来建模任务流程每个状态对应一个执行步骤状态之间的迁移由执行结果驱动。能力层由一系列原子服务组成。每个原子服务封装一个具体的业务操作比如“查询客户信息”、“创建工单”、“发送通知”。这些服务不关心是谁在调用它们只关心输入参数是否合法、执行结果是什么。能力层的服务应该是无状态的每次调用都是独立的这样便于水平扩展和故障恢复。数据层除了传统的业务数据存储还需要增加智能体运行时的状态存储。这包括任务状态表、步骤执行日志、中间结果缓存等。这些数据的读写模式与业务数据不同——写入频繁、查询模式固定、生命周期较短所以通常会用独立的存储方案比如键值存储或文档数据库而不是和业务数据混在一起。3. 核心组件实现与关键技术决策3.1 任务状态机的设计与落地任务状态机是编排层的核心。我以“生成月度销售报告”这个任务为例说明状态机的设计过程。这个任务可以拆解为几个步骤验证用户权限、确定报告范围时间、区域、查询销售数据、计算汇总指标、生成报告文档、通知用户。每个步骤都是一个状态状态之间的迁移条件由执行结果决定。在实现上我会用一张任务表来记录每个任务实例的当前状态和上下文数据。表结构大致包含任务ID、任务类型、当前状态、上下文JSON、创建时间、更新时间、创建用户等字段。上下文JSON里存放任务执行过程中产生的中间数据比如查询到的销售数据、计算出的汇总结果等。每次状态迁移时更新任务表的当前状态和上下文并写入一条步骤执行日志。这里有个关键决策状态迁移是同步还是异步。同步方式下用户发起任务后请求会一直阻塞直到任务完成。这对于简单任务没问题但对于耗时较长的任务比如查询大量数据并生成报告用户体验会很差。异步方式下用户发起任务后立即返回一个任务ID后续通过轮询或推送获取任务进度。我倾向于异步方式但需要处理好任务进度的查询接口和超时机制。另一个决策点是状态机的持久化粒度。是每个步骤完成后持久化一次还是每个步骤执行前和执行后都持久化我建议在执行前和执行后都持久化。执行前持久化是为了记录“即将执行什么”这样如果服务在步骤执行过程中崩溃重启后可以知道上次执行到哪一步从而决定是重试还是标记为失败。执行后持久化是为了记录“执行结果是什么”供后续步骤使用。3.2 权限穿透的具体实现方案权限穿透是智能体原生架构里最容易出问题的环节。我见过不少项目智能体跑起来之后发现它能看到所有数据因为开发时为了方便直接用了一个高权限账号。上线前才发现这个问题再改就伤筋动骨了。正确的做法是从第一天起就设计好权限传递机制。具体来说当用户发起一个任务请求时交互层需要获取用户的身份令牌并把这个令牌附加到任务请求上。编排层在调用能力层的原子服务时需要把用户令牌透传下去。能力层的服务在接收到请求后用用户令牌去调用下游的业务系统而不是用自己的服务账号。这里的技术难点在于令牌的格式和传递方式可能因系统而异。如果企业内部有统一的身份认证体系那最好不过直接复用即可。如果没有就需要设计一个轻量的令牌封装方案。我通常会在任务请求中携带一个“用户上下文”对象包含用户ID、租户ID、角色列表等信息能力层在调用下游服务时根据下游服务的要求把这个用户上下文转换成相应的认证凭证。还有一个容易被忽略的点智能体自身的权限边界。即使以用户身份执行智能体也不应该拥有比用户本人更大的操作范围。比如用户本人不能删除订单那智能体也不能通过任何方式执行删除操作。这需要在能力层做一层额外的校验确保智能体调用的原子服务都在用户权限范围内。3.3 原子能力的封装规范与复用策略原子能力的封装质量直接决定了智能体的可扩展性。我总结了几条封装规范在实际项目中效果不错。规范一输入输出必须结构化。每个原子能力的输入参数和返回值都应该是明确的JSON结构而不是自然语言。比如“查询订单”这个能力输入应该是{orderId: string}输出应该是{orderId, status, amount, createdAt}。这样智能体在编排时可以精确地知道每个步骤需要什么参数、会产生什么结果而不需要去解析自然语言。规范二能力粒度要适中。太粗的粒度会导致智能体编排不灵活比如一个“处理订单”的能力包含了查询、修改、通知所有逻辑那智能体就没法只查询不修改。太细的粒度又会导致编排复杂度过高比如把“查询订单”拆成“连接数据库”、“执行SQL”、“解析结果”三个能力那智能体需要管理太多步骤。我的经验是一个原子能力对应一个业务操作这个操作在业务上有明确的意义且可以独立测试。规范三能力要幂等。智能体在执行任务时可能会因为重试而多次调用同一个能力。如果能力不是幂等的就会产生重复数据或重复操作。比如“创建工单”这个能力如果被调用两次就会创建两个工单。解决方法是让能力支持幂等键调用方传入一个唯一标识能力层根据这个标识判断是否已经执行过。规范四能力要可观测。每个能力的调用都应该记录日志包括调用时间、输入参数、输出结果、执行耗时、是否成功。这些日志不仅用于排查问题还可以用于分析智能体的行为模式比如哪些能力被调用得最频繁、哪些能力的失败率最高。3.4 多智能体协作的通信机制当企业软件中的智能体数量增多时它们之间需要协作。比如一个“销售智能体”需要调用“库存智能体”来确认库存再调用“物流智能体”来安排发货。这种协作不能靠硬编码的接口调用而需要一套通信机制。我目前采用的方案是基于消息的异步通信。每个智能体有一个独立的“信箱”其他智能体可以向这个信箱发送消息。消息包含发送者、接收者、消息类型、消息内容等字段。接收方智能体在空闲时从信箱中读取消息根据消息类型决定如何处理。这种方式的优点是解耦彻底智能体之间不需要知道对方的具体实现只需要知道对方能处理什么类型的消息。但异步通信也带来了新的问题如何保证消息的顺序和一致性。比如销售智能体先发送“锁定库存”消息再发送“创建发货单”消息如果这两条消息被乱序处理就会出问题。解决方案是在消息中携带序列号接收方按照序列号顺序处理。或者更简单的方式是把需要保证顺序的多个操作合并成一个消息让接收方一次性处理。另一个问题是超时和重试。如果库存智能体长时间不回复销售智能体应该怎么办我的做法是设置超时时间超时后发送一条“取消锁定”的消息并记录异常。如果库存智能体后来回复了销售智能体需要根据当前状态决定是忽略还是处理。这要求每个智能体都能处理“迟到”的消息。4. 实操落地从零搭建一个智能体原生模块4.1 环境准备与技术栈选型假设我们要在一个现有的企业系统中新增一个“智能采购助手”模块。这个模块需要能够理解用户的采购需求查询供应商信息比价生成采购申请并跟踪审批状态。我们从头开始搭建这个模块看看智能体原生架构怎么落地。技术栈方面我选择以下组合你可以根据团队熟悉度调整后端用Python因为生态丰富适合快速开发状态存储用PostgreSQL因为需要事务支持消息队列用Redis的Stream结构轻量且够用原子能力用FastAPI封装成HTTP服务编排层用状态机库来管理任务流程。前端不需要特别复杂一个简单的Web界面即可重点是后端逻辑。环境准备的第一步是定义任务类型和状态。对于采购助手我定义以下任务类型purchase_request采购申请、supplier_query供应商查询、price_comparison比价。每个任务类型有自己的状态集合。以purchase_request为例状态包括created已创建、validating验证中、querying_suppliers查询供应商中、comparing_prices比价中、generating_request生成申请中、pending_approval待审批、approved已批准、rejected已拒绝、failed失败。第二步是定义原子能力清单。采购助手需要以下原子能力validate_user_permission验证用户权限、extract_purchase_intent提取采购意图、query_suppliers查询供应商、get_supplier_quote获取供应商报价、compare_quotes比价、create_purchase_request创建采购申请、submit_for_approval提交审批、get_approval_status查询审批状态。每个能力都封装成独立的HTTP接口有明确的输入输出。4.2 任务状态机的代码实现我用Python实现一个简化的状态机。核心是一个TaskStateMachine类它负责加载任务状态、执行状态迁移、持久化状态。class TaskStateMachine: def __init__(self, task_id, db_conn): self.task_id task_id self.db db_conn self.state None self.context {} def load(self): row self.db.query( SELECT current_state, context FROM tasks WHERE task_id %s, (self.task_id,) ) self.state row[current_state] self.context json.loads(row[context]) def transition(self, event, payloadNone): # 根据当前状态和事件决定下一个状态 next_state self._resolve_next_state(self.state, event) if next_state is None: raise InvalidTransition(fCannot handle {event} in state {self.state}) # 执行前持久化 self.db.execute( UPDATE tasks SET current_state %s, updated_at NOW() WHERE task_id %s, (next_state, self.task_id) ) # 执行状态对应的动作 result self._execute_action(next_state, payload) # 更新上下文并持久化 self.context.update(result) self.db.execute( UPDATE tasks SET context %s WHERE task_id %s, (json.dumps(self.context), self.task_id) ) self.state next_state return result这个实现的关键点在于每次状态迁移都先持久化再执行。这样即使执行过程中服务崩溃重启后也能知道上次执行到哪个状态从而决定是重试还是标记失败。另外上下文数据在每次迁移后都更新确保后续步骤能拿到最新的中间结果。4.3 原子能力的封装示例以query_suppliers这个能力为例展示封装规范。from fastapi import FastAPI, Depends, HTTPException from pydantic import BaseModel app FastAPI() class QuerySuppliersRequest(BaseModel): category: str region: str None min_rating: float 0.0 class SupplierInfo(BaseModel): supplier_id: str name: str rating: float region: str class QuerySuppliersResponse(BaseModel): suppliers: list[SupplierInfo] total: int app.post(/capabilities/query_suppliers) async def query_suppliers( req: QuerySuppliersRequest, user_context: dict Depends(get_user_context) ): # 权限校验只有采购角色才能查询供应商 if procurement not in user_context.get(roles, []): raise HTTPException(status_code403, detailPermission denied) # 执行查询逻辑 suppliers db.query_suppliers( categoryreq.category, regionreq.region, min_ratingreq.min_rating ) return QuerySuppliersResponse( suppliers[SupplierInfo(**s) for s in suppliers], totallen(suppliers) )这个能力有几个特点输入输出都是结构化的Pydantic模型通过依赖注入获取用户上下文并做权限校验返回结果包含总数方便调用方判断是否需要分页。这些细节在智能体编排时非常有用因为智能体可以根据总数决定是否需要进一步筛选。4.4 编排逻辑的编写与调试编排逻辑的核心是根据任务类型和当前状态决定下一步执行什么。我用一个简单的规则引擎来实现。class PurchaseOrchestrator: def __init__(self, state_machine): self.sm state_machine def run(self): while self.sm.state not in TERMINAL_STATES: if self.sm.state created: self.sm.transition(validate, {}) elif self.sm.state validating: result call_capability(validate_user_permission, { user_id: self.sm.context[user_id] }) if result[allowed]: self.sm.transition(validated, result) else: self.sm.transition(rejected, result) elif self.sm.state querying_suppliers: result call_capability(query_suppliers, { category: self.sm.context[category], region: self.sm.context.get(region) }) self.sm.transition(suppliers_queried, result) # ... 其他状态的处理调试编排逻辑时我建议先用手动方式跑通整个流程。也就是说不启动智能体而是手动调用每个能力把结果填入上下文然后手动触发状态迁移。这样可以验证每个能力的输入输出是否符合预期状态迁移条件是否正确。等手动流程跑通后再接入智能体自动执行。4.5 与现有系统的集成方式智能体原生模块最终要集成到现有企业系统中。集成方式取决于现有系统的架构。如果现有系统是微服务架构那智能体模块可以作为一个独立的微服务通过API网关暴露接口。如果现有系统是单体架构那智能体模块可以作为单体中的一个模块通过内部方法调用。我倾向于独立微服务的方式因为智能体模块的负载特征和传统业务模块不同——它可能长时间运行、需要独立的扩缩容策略、需要独立的监控指标。独立部署可以让智能体模块的迭代不影响现有系统。集成时的关键点是数据一致性。智能体在执行任务时可能会修改业务数据。比如创建采购申请后需要更新供应商的“最近合作时间”。如果智能体模块和业务模块使用不同的数据库就需要考虑分布式事务问题。我的做法是尽量避免跨库事务而是通过事件驱动的方式实现最终一致性。智能体模块在完成操作后发布一个事件业务模块订阅这个事件并执行相应的更新。5. 常见问题与排查技巧实录5.1 状态迁移异常与恢复策略问题现象任务卡在某个状态既不继续也不失败。查看日志发现状态迁移的请求已经发出但执行动作时服务崩溃了重启后任务状态还是旧状态但动作已经部分执行。排查思路首先检查任务表的current_state和updated_at字段。如果updated_at是很久以前说明状态迁移后没有更新。然后检查步骤执行日志看最后一次执行的动作是什么。如果动作已经执行但状态没更新说明是在“执行动作”和“更新状态”之间崩溃了。解决方案在状态迁移的实现中采用“先记录意图再执行再确认”的模式。具体来说在状态迁移前先写入一条“迁移意图”记录包含目标状态和将要执行的动作。执行完动作后再更新任务状态并标记意图为已完成。如果服务崩溃重启后检查是否有未完成的迁移意图如果有根据意图记录决定是重试动作还是回滚。注意重试动作时必须确保动作是幂等的。如果动作不是幂等的需要先检查动作是否已经执行过比如查询目标数据是否已经存在。5.2 权限校验失败的典型场景场景一令牌过期。用户发起任务后任务执行时间较长用户令牌在任务执行过程中过期了。后续步骤调用能力时权限校验失败。解决方案在任务创建时记录用户令牌的过期时间。如果任务执行时间可能超过令牌有效期需要在任务执行过程中刷新令牌。或者在任务创建时生成一个专门用于该任务的“任务令牌”这个令牌的有效期与任务的生命周期一致但权限范围严格限制在任务所需的范围内。场景二权限继承错误。智能体以用户身份调用能力A能力A又调用能力B但能力B没有接收到用户上下文导致权限校验失败。解决方案在能力调用链中强制要求每个能力在调用下游能力时必须透传用户上下文。可以在框架层面做统一处理比如用一个装饰器自动从当前请求中提取用户上下文并注入到下游调用中。场景三角色映射不一致。用户在企业系统中的角色是“采购专员”但智能体模块中定义的角色是“procurement”两者没有映射关系导致权限校验失败。解决方案在智能体模块的入口处做一次角色映射把企业系统的角色转换成智能体模块内部使用的角色。这个映射关系应该配置化方便调整。5.3 能力调用超时与重试机制问题现象某个能力调用耗时过长导致整个任务超时失败。或者能力调用失败后重试但重试次数过多导致任务执行时间过长。排查思路首先看能力调用的日志确认是能力本身执行慢还是网络延迟。如果是能力本身执行慢需要优化能力实现比如加缓存、优化查询。如果是网络延迟需要检查网络配置。解决方案为每个能力设置合理的超时时间。超时时间应该根据能力的正常执行时间来定一般是正常执行时间的2-3倍。超时后根据任务的重要性决定是重试还是失败。对于关键步骤可以设置重试次数和重试间隔。重试时使用指数退避策略避免频繁重试加重下游负担。实操心得我通常会在能力层实现一个“熔断器”当某个能力的失败率超过阈值时自动熔断不再调用该能力而是直接返回失败。这样可以防止一个能力的故障拖垮整个任务。5.4 上下文膨胀与性能优化问题现象随着任务执行步骤增多上下文数据越来越大导致状态持久化和读取变慢甚至超出存储限制。排查思路检查上下文JSON的大小看哪些数据占用了大量空间。通常是查询结果集、中间计算结果等。解决方案上下文只存必要信息不存原始数据。比如查询供应商后上下文里只存供应商ID列表不存完整的供应商信息。后续步骤需要详细信息时再根据ID去查询。另外对于大文本结果比如生成的报告可以存到对象存储上下文里只存引用地址。另一个优化点是定期清理已完成任务的上下文。已完成的任务其上下文数据可以归档到冷存储或者直接删除只保留任务元数据用于审计。5.5 常见问题速查表问题类型典型现象排查方向解决措施状态卡死任务长时间停留在某状态检查步骤执行日志和迁移意图记录实现迁移意图机制支持断点恢复权限失败能力调用返回403检查令牌有效期和角色映射使用任务令牌配置角色映射调用超时任务执行时间过长检查能力执行耗时和网络延迟设置超时和重试实现熔断上下文膨胀状态读写变慢检查上下文JSON大小只存必要信息大对象外置数据不一致业务数据与智能体状态不符检查事件发布和订阅采用事件驱动实现最终一致性6. 智能体原生架构的演进方向6.1 从单智能体到多智能体协作目前大多数企业软件中的智能体还是单打独斗。一个智能体负责一个任务任务完成后就结束了。但在实际业务中很多任务需要多个智能体协作。比如采购任务需要采购智能体、库存智能体、财务智能体共同完成。多智能体协作的关键是通信协议和协商机制。通信协议解决“怎么说话”的问题协商机制解决“听谁的”的问题。我目前的做法是定义一个轻量的消息协议包含消息类型、发送者、接收者、优先级、超时时间等字段。协商机制则采用“主从模式”一个主智能体负责拆解任务和分配子任务从智能体负责执行子任务并返回结果。主智能体根据从智能体的返回结果决定下一步。这种模式实现简单但主智能体可能成为瓶颈。更复杂的场景可以用“市场模式”智能体之间通过竞价来分配任务但这需要更复杂的经济机制设计。6.2 智能体行为的可观测性与审计企业环境对智能体的行为有严格的审计要求。你不仅要知道智能体做了什么还要知道它为什么这么做。这要求智能体在每一步决策时都记录决策依据。比如智能体选择供应商A而不是供应商B是因为A的评分更高还是因为A的交期更短这些决策依据需要被记录下来供后续审计。实现上我会在状态机的每次迁移中增加一个“决策日志”字段记录本次迁移的触发条件、考虑的选项、最终选择及理由。这些日志可以用于事后分析也可以用于训练更好的智能体。6.3 面向未来的架构扩展性考虑智能体原生架构不是一成不变的。随着业务变化和技术进步架构需要能够平滑演进。我在设计时会预留几个扩展点能力注册中心新的原子能力可以动态注册智能体自动发现策略插件化编排策略可以替换比如从规则引擎换成强化学习模型多模型支持底层大模型可以切换不影响上层编排逻辑。这些扩展点的实现不需要一步到位但在架构设计时要预留接口。比如能力注册中心初期可以用配置文件实现后期再换成服务发现。策略插件化初期可以用简单的策略模式后期再引入更复杂的决策引擎。6.4 组织与团队的能力建设最后说一点技术之外的东西。智能体原生架构的落地不仅是技术问题更是组织问题。我见过不少团队技术方案很先进但落地效果不好原因是开发和运维的协作模式没有跟上。传统模式下开发负责写代码运维负责部署和监控。但智能体系统的行为是动态的它的执行路径不是固定的运维需要理解智能体的决策逻辑才能有效地监控和排查问题。我的建议是在团队中培养“智能体运维”角色这个角色既懂业务又懂技术能够分析智能体的行为日志发现异常模式并反馈给开发团队优化。同时开发和运维要共同制定智能体的SLA明确哪些指标需要监控异常时如何响应。这些组织层面的准备往往比技术选型更能决定项目的成败。我在实际项目中的体会是智能体原生架构的落地是一个迭代过程。不要试图一步到位先从一个小场景开始把状态管理、权限穿透、能力封装这三个核心机制跑通然后再逐步扩展。每扩展一个场景都会发现新的问题也会对架构有新的理解。这个过程急不得但方向对了每一步都算数。