外呼打电话全流程梳理 v2
本文基于 waihu_log 目录下的样本日志,以及 interactive-controller 当前代码整理。重点不是只描述模块,而是把一通电话里每个阶段的数据如何产生、如何存储、如何在下一轮 VAD/ASR 交互中被带回、如何传给 LLM/NLU/flow-agent/TTS 串起来。
1. 样本和关键文件
主要样本:
- 电话会话样本:
logid=3303326126,session=2_9ab7cf07579e4d6287358c420d643464_3808251032371200_2025090108,主日志在waihu_log/speech-controller.log、waihu_log/ts_debug.log、waihu_log/interactive-controller_debug.log。 - 单轮细节样本:
logid=2001844932,sn=3a6fd312-a998-4c16-a713-9221c7e9aa14_6,用户提供手机号15162115538,主日志在waihu_log/2001844932。 - 业务链路样本:
flow-agent.log/flow-agent_debug.log,能看到 flow-agent 收到 IC 注入的nluInfos、业务上下文、retrieval_info。
关键代码:
interactive-controller/task/llm/task_baisheng_llm.cppinteractive-controller/component/rpc_nlu_handler.cppinteractive-controller/component/rpc_flowagent_handler.cppinteractive-controller/component/llm/handler/llm_handler.cppinteractive-controller/util/bdvs_parser.cppinteractive-controller/util/bdvs_protocol.cppinteractive-controller/context/context.cpp
2. 一通电话里的三个上下文
当前链路里至少有三类上下文,不能混在一起看。
| 上下文 | 存储/来源 | 下一轮如何使用 | 典型字段 | | --- | --- | --- | --- | | TS 多轮识别/语义上下文 | ts-ex 写 Redis,key 形如 multi_round_session_{cuid}{pid} | 下一轮 TaskLongLlm::fill_controller_recog_result 放进 BDVS payload 的 nlu_session | finalSession、semantic_session、nluInfos、tts_text | | IC 历史 QA | TaskBaishengLLM 写 Redis,key 形如 baisheng_history_qa_prefix_akw3ob_{pid}{cuid}{sessionId} | 下一轮 IC 初始化读取,用于 RAG session_context、安全上下文、部分 LLM param | query/answer 成对保存 | | flow-agent/ICE 会话 | flow-agent 按 pid:cuid:session_id 维护,IC 的 RPCFlowagentHandler 初始化时读取 | 取上一轮 prefetch_rag,并在本轮请求里注入 session.ice_clarify_session | clarify_response、retrieval_info、业务 memory |
几个容易混的字段:
interactionOutputData.nluInfos:TS/IC/flow-agent 之间传 NLU JSON 的容器。IC 调 flow-agent 前会用 rpc_nlu 的结果覆盖/注入这里。nlu_session:下一轮从 TS Redis 读回来的历史会话数组,IC 会解析为ConversationSessionItem。raw_nluinfo:IC protobuf 里的原始 NLU JSON,来自interactionOutputData.nluInfos。nlu_txt:不是 BDVS 上行字段,是 LLM 回复 label 被LLMHandler解析后的内部字段,例如GENERAL_INFO#GET_ORDER_INFO#{phone_num},会传给 rpc_nlu。text_param.default_rag_data:TS 上行给 IC 的预取 RAG 大字符串,里面包含任务=预取和参考回复候选。
3. 总体时序
sequenceDiagram
participant Tel as 电话/ACG
participant SC as speech-controller
participant BTS as bdvs-ts
participant TS as ts-ex
participant IC as interactive-controller
participant LLM as LLM backend
participant NLU as rpc_nlu
participant FA as flow-agent
participant ICE as ICE/百胜接口
participant TTS as TTS
Tel->>SC: 建立 ASR/TTS 流,传入 pid/cuid/session/ext
SC->>Tel: prologue 事件
Tel-->>SC: play_prologue + tts_text + personalized_info
SC->>BTS: SynTextInput,播放开场白
BTS->>TTS: 合成开场白音频
SC->>TS: q1 开场白参考信息,写入多轮上下文
Tel->>SC: 用户音频/VAD
SC->>TS: 音频包 + vad_info
TS->>TS: ASR/ITGP/多轮 session 读写
TS->>IC: TextInput BDVS JSON,带 nlu_session/text_param/interactionOutputData
IC->>IC: 解析 nlu_session/raw_nluinfo,读取 IC 历史 QA
IC->>LLM: 第一次 LLM,预取/垫话/label
LLM-->>IC: padding 文本 + nlu_txt
IC->>NLU: asr_result + llm_answer + history + nlu_txt
NLU-->>IC: 结构化 nlu_result
IC->>FA: 注入 nluInfos,调用 flow-agent
FA->>ICE: 业务策略/接口
ICE-->>FA: 业务结果
FA-->>IC: polish_rag / prefetch_rag / flow session
IC->>LLM: 第二次 LLM,follow_up_content=polish_rag
LLM-->>IC: 润色结果
IC-->>TS: Speak directive 文本
TS->>TTS: 合成最终回复
TS-->>SC: TTS 音频
SC-->>Tel: 播放回复
SC->>Tel: dialog_record / client_silence 等事件
4. 建连和开场白
4.1 speech-controller 建立电话会话
电话侧会建立 ASR/TTS 两路流。样本 3303326126 的汇总日志显示:
pid=4144826pkey=com.baidu.appcuid=2_9ab7cf07579e4d6287358c420d643464_3808251032371200- ASR 流:
asr_stream_id=197568496309 - TTS 流:
tts_stream_id=197568496863 - 会话:
2_9ab7cf07579e4d6287358c420d643464_3808251032371200_2025090108
对应日志:waihu_log/speech-controller.log:46。
4.2 向 ACG 请求开场白
speech-controller 先向 ACG 发 prologue 事件。ACG 返回:
{
"action_type": "play_prologue",
"data": {
"tts_text": "喂,您好",
"silence_detect_ms": 10000,
"personalized_info": {
"sys_phonenum": "15162115538",
"sys_date": "2026-05-11",
"sys_time": "19:43:49"
},
"allow_interrupt": false
}
}
对应日志:waihu_log/speech-controller_debug.log:22,waihu_log/speech-controller.log:29。
这里有两条并行作用:
BdvsEventClient走事件链路,把SynTextInput发到bdvs-ts,用于真正播放开场白。BdvsClient走普通 TS 链路,发一个 q1 请求,文本是任务=开场白的参考信息,用于把开场白写入对话上下文。
样本中 speech-controller.log:30 是事件 TTS 完成,speech-controller.log:31 是 q1 开场白参考信息完成。
4.3 开场白也会写上下文
q1 进入 TS 后,TS 解析出 interaction_output_data.finalSession 和 nluInfos,然后写 Redis:
key=multi_round_session_2_9ab7..._38082510323712004144826
相同逻辑在另一个样本里能看到:
- q1 读 Redis 为空:
waihu_log/ts_debug.log:240 - q1 写 Redis:
waihu_log/ts_debug.log:274
IC 侧也会通过 TaskConversationInjection 把开场白作为 QA 写入自己的历史:
query=参考信息【...任务:开场白...】answer=喂,您好
对应日志:waihu_log/interactive-controller_debug.log:3121。
5. 用户说话、VAD、ASR 和 TS 上下文
5.1 speech-controller 发送音频和 VAD
电话侧音频进入后,speech-controller 把音频分块发给 TS。日志里能看到每个音频包带 vad_info,例如:
{
"audio_packet_index": 2,
"audio_len": 2560,
"vad_info": [0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0]
}
对应日志:waihu_log/speech-controller_debug.log:172 到 speech-controller_debug.log:180。
这一步还没有进入 IC,它只是把用户音频和 VAD 标记送到 TS,TS 后续根据 ASR/完整性/VAD 结果形成 TextInput。
5.2 TS 读取上一轮多轮 session
每个 query 进入 TS 后,会先读 Redis 中的多轮识别/语义上下文。第一次通常读不到:
GET multi_round_session_{cuid}{pid}
read_redis failed
对应日志:waihu_log/ts_debug.log:240、waihu_log/ts_debug.log:241。
后续轮次会读到上一轮写入的 session,并在 TaskLongLlm::fill_controller_recog_result 里塞进发给 IC 的 BDVS JSON,字段通常表现为:
event.payload.nlu_sessionevent.payload.interactionOutputDatainteractionOutputData.finalSessioninteractionOutputData.nluInfos
样本 2001844932 的第一条 IC 输入就带了上一轮的 nlu_session,里面已有:
我要修改订单是的- 对应的
tts_text - 对应的
nlu_json_res
对应日志:waihu_log/2001844932:3。
5.3 TS 写回本轮 interaction session
ASR/ITGP 产生本轮结果后,TS 会写:
TSContext::write_interaction_session_to_redis
key=multi_round_session_{cuid}{pid}
sessionsize=...
样本 今天天气怎么样 的写入在 waihu_log/ts_debug.log:10078。紧接着,TS 构造给 IC 的请求体,见 waihu_log/ts_debug.log:10113。
6. IC 收到 TS 请求后的初始化
6.1 Context 解析 nlu_session
IC 收到 BDVS JSON 后,Context::init_with_bdvs_param() 会提取 nlu_session:
- 如果
nlu_session里有上一轮llm_params.scene_id,会重置本轮prompt_scene_id。 - 如果
nlu_session里有workflow_stack,会恢复_workflow_stack。
代码:interactive-controller/context/context.cpp:256 到 context.cpp:273。
6.2 TaskBaishengLLM 解析 BDVS JSON
TaskBaishengLLM::process_task_req_param() 调用:
bdvs_json_to_pb_with_dialog(param_str, bdvs_pb_ptr, err_msg, _context->handshake_request())
核心解析在 bdvs_parser.cpp:
get_nlu_str()从interactionOutputData.nluInfos提取raw_nluinfo。nlu_json_to_pb()把raw_nluinfo转成 protobuf 里的nlu_info。get_nlu_session()把 payload 的nlu_session转成ConversationSessionItem。
代码:interactive-controller/task/llm/task_baisheng_llm.cpp:176,interactive-controller/util/bdvs_parser.cpp:84 到 bdvs_parser.cpp:145。
6.3 IC 构造 _history_dialog
如果 bdvs_pb_ptr->nlu_session_size() > 0,IC 会把每个 ConversationSessionItem 转 JSON,组成 _history_dialog。后续 rpc_nlu 请求会带这个字段。
代码:interactive-controller/task/llm/task_baisheng_llm.cpp:192 到 task_baisheng_llm.cpp:199。
样本 2001844932 中,rpc_nlu 请求里的 history 包含前两轮:
我要修改订单->您好!请问您需要修改送餐地址里的楼层或者房间号吗?...是的->请您稍等一下。麻烦您提供一下订单上收餐人的手机号哦...
对应日志:waihu_log/2001844932:250。
6.4 IC 读取自己的历史 QA
TaskBaishengLLM 初始化时生成:
baisheng_history_qa_prefix_akw3ob_{pid}{cuid}{sessionId}
然后 read_history_qa_from_redis() 读取历史 query/answer。样本中:
[我要修改订单][您好!请问您需要修改送餐地址里的楼层或者房间号吗?...]
[是的][请您稍等一下。麻烦您提供一下订单上收餐人的手机号哦...]
对应日志:waihu_log/2001844932:9、waihu_log/2001844932:15。代码:interactive-controller/task/llm/task_baisheng_llm.cpp:339 到 task_baisheng_llm.cpp:363。
这份历史会进入:
send_param_request_to_llm_handler()的 safety 上下文。fill_session_context_in_retrieve_request()的 RAGsession_context。- 当前轮结束后的历史续写。
7. IC 内部一轮用户输入的处理
以用户提供手机号 15162115538 为例,完整路径如下。
7.1 判断是否是 NLU 轮
IC 用:
has_nluinfo = bdvs_msg->is_speech_mode() && bdvs_msg->has_raw_nluinfo()
如果是 NLU 轮,process_task_req_param() 会:
push_bdvs_req_into_list()插入第一次 LLM 请求,is_follow_req=false,用于预取和垫话。push_followup_bdvs_req_into_list()再插入第二次 LLM 请求,is_follow_req=true,先挂起,等 flow-agent 返回polish_rag。process_nluinfo()发 RAG、安全请求,并记录_lastest_nlu_bdvs_req。process_completeness()在完整性满足后放行第一次 LLM。
代码:interactive-controller/task/llm/task_baisheng_llm.cpp:223 到 task_baisheng_llm.cpp:240,task_baisheng_llm.cpp:933 到 task_baisheng_llm.cpp:970。
7.2 预取 RAG 和意图压缩
TS 给 IC 的 text_param.default_rag_data 是一个大字符串,常见结构:
{
"retrieval_results": [
{
"content": {
"任务": "预取",
"参考回复": {
"提供手机号": {"候选回复": "...<placeholder no 436>..."},
"提供订单号": {"候选回复": "...<placeholder no 433>..."},
"其他": {"候选回复": "...<placeholder no 420>..."}
}
}
}
]
}
如果候选太多,IC 会做意图压缩:
fill_rag_data_str()从 flow-agent 上轮prefetch_rag或本轮text_param.default_rag_data取预取 RAG。need_compress_prefectch_rag_data()抽取参考回复的 key。send_intents_rag_request_to_rag_handler()把候选 key 作为intends发给 intents RAG。rebuild_retrieval_results()只保留召回到的候选,同时补一个其他fallback。
代码:interactive-controller/task/llm/task_baisheng_llm.cpp:2868 到 task_baisheng_llm.cpp:3175。
注意:意图压缩不调用 rpc_nlu。它只影响第一次 LLM 能看到哪些预取候选,进而影响 LLM 输出哪个 placeholder label。
7.3 第一次 LLM:预取 + 垫话 + label
第一次 LLM 请求由 send_data_request_to_llm_handler() 构造:
idx: decoder_idxquery: 当前用户文本raw_nluinfo: TS 侧原始 NLUnlu_info: TS 侧 NLU protobufrag_data: 预取候选interaction_ontput_data: TS 上行的interactionOutputDatais_follow_req=falseinput_mode=FLAGS_input_mode,样本里通常是4
代码:interactive-controller/task/llm/task_baisheng_llm.cpp:1487 到 task_baisheng_llm.cpp:1585。
LLM 回复文本里可能带 placeholder label,例如:
<|place▁holder▁no▁436|>请您稍等片刻。
LLMHandler::process_nlu_label() 会解析这个 label,查 label mapping,得到:
GENERAL_INFO#GET_ORDER_INFO#{phone_num}
并把去掉 label 后的文本作为垫话下发给 TS/TTS。样本中垫话是:
请您稍等片刻。
对应日志:waihu_log/2001844932:250 中 rpc_nlu 请求的 llm_answer 和 nlu_txt。
7.4 垫话完成后调用 rpc_nlu
触发条件在 process_llm_data_response():
resp.has_padding_is_complete() && resp.padding_is_complete() == 1
如果 FLAGS_need_current_answer_to_nlu=true,IC 会调用 send_input_to_rpc_nlu_handler(),请求字段是:
| 字段 | 来源 | 样例 | | --- | --- | --- | | asr_result | bdvs_msg->ori_query() | 幺五幺六二幺幺五五三八 | | llm_answer | 第一次 LLM 已输出垫话 | 请您稍等片刻。 | | history_dialog | BDVS nlu_session 转成的历史 | 前两轮用户/机器人记录 | | index | decoder_idx | 23 | | nlu_txt | LLM label 映射结果 | GENERAL_INFO#GET_ORDER_INFO#{phone_num} |
代码:interactive-controller/task/llm/task_baisheng_llm.cpp:1808 到 task_baisheng_llm.cpp:1818,task_baisheng_llm.cpp:2542 到 task_baisheng_llm.cpp:2568。
RPCNLUHandler 再转成后端 speech::NluRequest:
logidsnasr_resultllm_answerhistorynlu_vs_addrnlu_vs_trends_addrnlu_txt
代码:interactive-controller/component/rpc_nlu_handler.cpp:305 到 rpc_nlu_handler.cpp:324。协议:interactive-controller/protocol/decoder.proto。
样本请求和返回:
- 请求:
waihu_log/2001844932:250 - 返回:抽取
phone_num=15162115538,见waihu_log/2001844932:256
7.5 rpc_nlu 结果进入 flow-agent
TaskBaishengLLM::process_rpc_nlu_handler_output() 收到 nlu_result 后,调用:
send_input_to_rpc_flowagent_handler(nlu_out->index(), nlu_out->nlu_result(), nlu_out->is_last())
代码:interactive-controller/task/llm/task_baisheng_llm.cpp:2571 到 task_baisheng_llm.cpp:2585。
RPCFlowagentHandler::construct_flow_request() 会把 rpc_nlu 的结果注入回 BDVS JSON:
event.payload.interactionOutputData = {"nluInfos": "...", "tokenId": [...], "wordPiece": "..."}root["nlu_info"] = js_arr_nluroot["raw_nluinfo"] = input->nlu()root["session"]["ice_clarify_session"] = _history_flow_sessionroot["contexts"]注入个性化信息,例如sys_phonenumroot["product_line"]、root["appname"]
代码:interactive-controller/component/rpc_flowagent_handler.cpp:388 到 rpc_flowagent_handler.cpp:440。
flow-agent 日志里能看到请求已经包含:
interactionOutputData.nluInfoscontexts.payload.sys_phonenum=15162115538business_session_idsession_contextquery
例如 waihu_log/flow-agent.log:3。
7.6 flow-agent 返回待润色文本和下一轮预取信息
flow-agent 返回 FlowResponse.response,里面的 session.clarify_response.retrieval_info 是一个二元数组:
- 第一个元素:
polish_rag,给第二次 LLM 润色使用。 - 第二个元素:
prefetch_rag,给下一轮预取使用。
RPCFlowagentHandler::process_response() 会解析 clarify_response,再由 parse_flow_session() 拆出 polish_rag 和 prefetch_rag。
代码:interactive-controller/component/rpc_flowagent_handler.cpp:448 到 rpc_flowagent_handler.cpp:585。
样本 2001844932 中,flow-agent 返回的待润色文本是:
系统忙不过来了,请稍后重试
对应日志:waihu_log/2001844932:286。
7.7 第二次 LLM:润色
IC 收到 flow-agent 输出后,不直接把 flow-agent 的文本给用户,而是:
set_bdvs_req_ready_in_list(flowagent_out->index(), &flowagent_out->polish_rag());
process_bdvs_req_list();
这会唤醒之前挂起的 follow LLM 请求:
is_follow_req=trueinput_mode=0follow_up_content=polish_rag
代码:interactive-controller/task/llm/task_baisheng_llm.cpp:2685 到 task_baisheng_llm.cpp:2708,task_baisheng_llm.cpp:1119 到 task_baisheng_llm.cpp:1140。
样本第二次 LLM 请求中:
follow_up_content=任务:润色, 待润色文本:系统忙不过来了,请稍后重试
对应日志:waihu_log/2001844932:291。
最终输出:
请您稍等片刻。系统现在有点忙不过来啦,请您稍后再重试哦
对应日志:waihu_log/2001844932:533。
8. 数据如何传出到用户和外部系统
8.1 IC 到 TS
LLM 的流式结果会由 TaskBaishengLLM::generate_bdvs_speak_directive() 包成 BDVS Speak directive,然后通过:
_context->write_stream_to_client(CONTROLLER_RSP_TYPE_TTS, ...)
回给 TS。第一次 LLM 垫话和第二次 LLM 润色都会走这个路径。
代码:interactive-controller/task/llm/task_baisheng_llm.cpp:1833,task_baisheng_llm.cpp:2029,task_baisheng_llm.cpp:2332。
8.2 TS 到 TTS,再到 speech-controller
TS 的 TaskWenxinDialog 收到 IC 的 TTS 文本后,调用本地 TTS 服务 127.0.0.1:8853 合成音频。样本 今天天气怎么样 的 TS 汇总中能看到:
TaskLongLlm, timecost:756tts_text=请您直接提问哦。您好哦...StreamTTSHelper ... tts_svr:127.0.0.1:8853tts_audio_ms=8090
对应日志:waihu_log/ts_debug.log:10540。
8.3 speech-controller 到 ACG/电话侧
speech-controller 收到 TS/TTS 音频后回送电话侧,同时把每轮对话上报给 ACG:
{
"event_type": "dialog_record",
"trigger_event": "user_interaction",
"data": {
"records": [
{"role": "user", "type": "asr", "text": "..."},
{"role": "robot", "type": "tts", "text": "..."}
]
}
}
样本中每轮完成后都有 dialog_record:
我要修改订单:waihu_log/speech-controller.log:35是:waihu_log/speech-controller.log:3715162115538:waihu_log/speech-controller.log:39今天天气怎么样:waihu_log/speech-controller.log:54
如果用户静默,speech-controller 会向 ACG 发 client_silence。样本中:
event=client_silence
action=play_silence
对应日志:waihu_log/speech-controller.log:52。
9. 当前样本的完整轮次时间线
以 logid=3303326126 为主:
| 时间 | query_idx | sn/logid | 输入 | 关键处理 | 输出 | | --- | ---: | --- | --- | --- | --- | | 19:45:31 | - | 3303326126 | 呼入 | ACG prologue 返回 play_prologue | 喂,您好 | | 19:45:32 | 1/2 | 516246244 | 开场白参考信息/q1 | TS 写 multi_round_session;IC TaskConversationInjection 写开场白 QA | 开场白进入上下文 | | 19:45:41 | 3 | 1258119147 | 我要修改订单 | 预取 LLM -> rpc_nlu -> flow-agent -> 润色 LLM | 询问是否修改楼层/房间号 | | 19:45:51 | 4 | 1273919548 | 是 | LLM label <416> -> GENERAL_CONTROL#CONFIRM#{};flow-agent 要手机号;润色 | 要求提供订单手机号 | | 19:46:07 | 5 | 1289719949 | 15162115538 | rpc_nlu 抽 phone_num;flow-agent 业务查询异常;润色 | 系统忙,稍后重试 | | 19:46:15 | 6 | 1305520350 | 重试什么 | unknown#unknown;兜底 | 暂不会这项技能 | | 19:46:29 | 7 | 1321320751 | 你会什么 | flow-agent 业务链路耗时偏长/异常 | 系统忙,稍后重试 | | 19:46:37 | 9 | 1352921553 | 哎今天天气 | CHAT#GREET | 问候类回复 | | 19:46:49 | 10 | 1303045849 | 今天天气怎么样 | CHAT#GREET | 问候类回复 | | 19:47:29 | 8 | 1337121152 | 今天天气 | controller 等待 60s 后补结束 | 补发 dialog_record / 静默事件 |
注意 query_idx=8 晚于 9/10 结束,是因为该轮 TaskLongLlm timecost=60000,后续补报。对应日志:waihu_log/ts_debug.log:10585、waihu_log/speech-controller.log:49。
10. 一轮手机号输入的字段流转
样本:logid=2001844932,sn=3a6fd312-a998-4c16-a713-9221c7e9aa14_6。
10.1 TS -> IC
上行字段:
query=15162115538ori_query=幺五幺六二幺幺五五三八nlu_session: 前两轮历史text_param.default_rag_data: 预取候选interactionOutputData: 当前识别结果、finalSession、nluInfos
对应日志:waihu_log/2001844932:3、waihu_log/2001844932:126。
10.2 IC -> 第一次 LLM
第一次 LLM 请求:
is_follow_req=falseinput_mode=4任务=预取- 参考回复中有
提供手机号 -> <|place▁holder▁no▁436|>请稍等片刻
对应日志:waihu_log/2001844932:160。
10.3 第一次 LLM -> rpc_nlu
第一次 LLM 输出:
- 垫话:
请您稍等片刻。 nlu_txt=GENERAL_INFO#GET_ORDER_INFO#{phone_num}
rpc_nlu 请求:
{
"asr_result": "幺五幺六二幺幺五五三八",
"llm_answer": "请您稍等片刻。",
"nlu_txt": "GENERAL_INFO#GET_ORDER_INFO#{phone_num}"
}
对应日志:waihu_log/2001844932:250。
10.4 rpc_nlu -> flow-agent
rpc_nlu 返回:
{
"domain": "GENERAL_INFO",
"intent": "GET_ORDER_INFO",
"slots": {
"phone_num": [{"value": "15162115538"}]
}
}
对应日志:waihu_log/2001844932:256。
IC 注入 flow-agent 请求:
interactionOutputData.nluInfos = rpc_nlu.nlu_resulttokenId = syllable_idswordPiece = word_piececontexts里带personalized_info
对应代码:interactive-controller/component/rpc_flowagent_handler.cpp:388 到 rpc_flowagent_handler.cpp:440。
10.5 flow-agent -> 第二次 LLM
flow-agent 输出 polish_rag:
任务=润色
待润色文本=系统忙不过来了,请稍后重试
对应日志:waihu_log/2001844932:286。
第二次 LLM 请求把它放到 follow_up_content:
follow_up_content=任务:润色, 待润色文本:系统忙不过来了,请稍后重试
对应日志:waihu_log/2001844932:291。
10.6 最终输出和历史保存
最终文本:
请您稍等片刻。系统现在有点忙不过来啦,请您稍后再重试哦
对应日志:waihu_log/2001844932:533。
IC 结束时写历史 QA:
query=15162115538
answer=请您稍等片刻。系统现在有点忙不过来啦,请您稍后再重试哦
对应日志:waihu_log/2001844932:548。代码:interactive-controller/task/llm/task_baisheng_llm.cpp:2068 到 task_baisheng_llm.cpp:2106,结束回写在 task_baisheng_llm.cpp:2769 到 task_baisheng_llm.cpp:2797。
11. 对后续协议调整的关键影响点
当前流程里,LLM、rpc_nlu、flow-agent 不是简单串行文本调用,存在几处隐式契约:
- 第一次 LLM 不只是出垫话,还负责输出 label,IC 解析成
nlu_txt,再传给 rpc_nlu。 - rpc_nlu 不只看 ASR,还看
llm_answer、history_dialog、nlu_txt。 - flow-agent 不直接面向用户输出,至少在 TaskBaishengLLM 链路里,它输出的是
polish_rag,再由第二次 LLM 润色。 - 预取候选来自
text_param.default_rag_data或上一轮 flow-agent 的prefetch_rag,并可能被 IC 做意图压缩。 - TS 的
nlu_session和 IC 的baisheng_history_qa是两套上下文,后续如果改成 stateless HTTP/sglang,需要明确每次请求重新组装哪些上下文。 - 旧 brpc 流式 LLM 里有“新 NLU 到来后打断旧 LLM”的控制消息;如果改成每个 NLU 新建 HTTP 连接,需要明确旧连接废弃、结果丢弃、decoder_idx 乱序处理的规则。
padding_is_complete是触发 rpc_nlu 的关键开关。如果新协议没有等价字段,需要定义“垫话完成”的判断条件。follow_up_content是第二次 LLM 的润色输入。如果改 OpenAI/sglang 协议,需要把它显式组装成 message 或结构化字段。
12. 快速定位表
| 主题 | 日志/代码 | | --- | --- | | ACG prologue 返回 | waihu_log/speech-controller_debug.log:22 | | 开场白事件 TTS | waihu_log/speech-controller.log:30 | | q1 开场白参考信息 | waihu_log/speech-controller.log:31 | | TS 写 multi_round_session | waihu_log/ts_debug.log:274、waihu_log/ts_debug.log:10078 | | TS 构造发 IC 请求 | waihu_log/ts_debug.log:10113 | | IC 解析 nlu_session | interactive-controller/util/bdvs_parser.cpp:37、interactive-controller/util/bdvs_parser.cpp:137 | | IC 读取历史 QA | interactive-controller/task/llm/task_baisheng_llm.cpp:339 | | 第一次/第二次 LLM 队列 | interactive-controller/task/llm/task_baisheng_llm.cpp:933、interactive-controller/task/llm/task_baisheng_llm.cpp:957 | | 预取意图压缩 | interactive-controller/task/llm/task_baisheng_llm.cpp:2868 | | LLM label 解析 | interactive-controller/component/llm/handler/llm_handler.cpp:1718 | | rpc_nlu 请求组装 | interactive-controller/component/rpc_nlu_handler.cpp:305 | | flow-agent 请求注入 nluInfos | interactive-controller/component/rpc_flowagent_handler.cpp:388 | | flow-agent 解析 polish_rag | interactive-controller/component/rpc_flowagent_handler.cpp:448 | | IC 写历史 QA | interactive-controller/task/llm/task_baisheng_llm.cpp:2068 |