Skip to content

perf(realtime): 精简会话上下文并按体量重建,降低 audio_text_input_token 成本 #249

Description

@LUPENGHAN

背景

Realtime 会话(intelligence/realtime/agent.py)为了让通话能跨多轮保持上下文,一个会话会被持续复用,直到用完 SESSION_MAX_TURNS=40 轮或 SESSION_MAX_AGE_SECONDS=240 秒的预算才重建(agent.py:46-47_spent())。这套复用机制 push_to_talk 和 continuous 两种语音模式都会走到——_session_for()/_Held(account_id, conversation_id) 复用同一个会话(agent.py:198 held.turns += 1),不是 continuous 独有的问题。但这两个预算只按轮次数时间计量,不看会话里实际攒了多少上下文——工具调用结果一旦进入会话历史,会在同一个会话存活期内被反复带上下文重新处理,直接推高 audio_text_input_token 消耗。

schedule_tools.py 里返回给模型的工具结果目前没有做过任何裁剪:

  • _find()list_schedules)对每条匹配日程调用 _for_model(),逐条 asdict(schedule)ScheduleSnapshot 全部 21 个字段(business/calendar/contracts.py:128-153:id、account_id、schedule_type、schedule_kind、title、is_all_day、timezone、status、revision、created_at、updated_at、start_time、end_time、recurrence_rule、location_name、latitude、longitude、reminder_type、reminder_trigger_at、reminder_offset_minutes、reminder_strength、reminder_disposition_state、deleted_at)原样吐给模型,外加一个 starts_at_localschedule_tools.py:478-491)。
  • _mutation_result()(create/update/delete 共用)同样通过 _for_model_dict() 把完整快照塞进 outputschedule_tools.py:463-476)。

模型确认一次日程创建、或者报一次"今天有哪些日程",实际只需要说出标题、时间、地点这几项,但现在整条 21 字段的快照连同没用的审计字段都进了会话上下文,且会在这个会话存活的每一轮里被重新计入 audio_text_input_token

同时,阿里云 Realtime API 的 response.done 事件本身带用量信息,但 infrastructure/external/realtime/qwen_audio.py 里两处 response.done 处理分支(qwen_audio.py:271qwen_audio.py:331)都只用它判断响应是否结束,从没读取或记录过用量字段。对照 infrastructure/external/llm/openai_compatible.py:97-172 里非 realtime 那条 LLM 管线,_parse_usage()/LlmUsage 早就在做同样的事——realtime 这条路径缺的是同一套实践,不是要新发明一套。

这三点合起来是 audio_text_input_token 占到约 70% 总成本的直接原因:上下文本身该精简的没精简,攒起来的量既不可见(没有用量日志),会话重建的时机也不看这个量(只看轮次/时间)。

建议方案

  1. 记录 response.done 用量:解析阿里云 Realtime API response.done 事件里的 usage 字段(若字段名/结构与协议不确定,需先对照实际线上事件确认),按 account_id/conversation_id 打日志或指标,参照 openai_compatible.pyLlmUsage 模式定义等价结构。没有这一步,后面两项改完有没有效果全靠猜。
  2. 精简工具结果_for_model()/_for_model_dict() 只回传模型确认/播报实际需要的字段(标题、时间、地点、日程类型等),account_id/created_at/updated_at/deleted_at/revision 这类审计字段模型不需要开口读出来,没必要进上下文(_snapshot_for_client() 已经在给客户端的那条路上做了同样的字段过滤,schedule_tools.py:493-500,工具结果这条路可以照抄同一个思路)。_find() 列表场景数量一多要单独考虑是否需要摘要/截断上限。
  3. 按上下文体量重建会话_spent() 除了轮次和时间,加一个基于累计 token(或输入字节数的粗略代理,如果拿不到精确 token 数)的第三个预算阈值,上下文变大到一定程度就主动重建,不等到轮次或时间耗尽。具体阈值需要基于第 1 步拿到的真实用量数据来定,不能拍脑袋。

验收标准

  • response.done 用量被解析并记录(日志或指标),可以在真实通话里观察到逐轮累积的 audio_text_input_token 走势
  • schedule_tools.pylist_schedules/create/update/delete 的工具结果只包含模型确认/播报所需字段,审计字段被过滤
  • agent.py 的会话重建条件新增一个上下文体量维度(token 数或字节数近似),并有单测覆盖"上下文变大但轮次/时间预算未耗尽"时触发重建的分支
  • 有前后对比数据(真实通话或可复现的测试场景)证明 audio_text_input_token 消耗下降

不做

  • 不动 Composed Agent 那条工具注册表(conversation/schedule_tools.py)——目前没有接入 /ws,不产生真实 token 成本

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions