一句话定位
callout-server + callout-webpage 是平台的智能外呼子系统:管理”外呼活动 →
联系人名单 → 拨号节奏 → 拨打结果”的完整生命周期,把名单批量、可控、可重试地交给
FreeSWITCH 发起呼叫,接通后复用 callflow-esl 的业务编排能力。
分工记一句话:callout 负责”什么时候打、打给谁、结果如何”,callflow-esl 负责”接通之后说什么”。
除这条核心主线外,callout-server 已经是一个运营级外呼平台:角色鉴权与坐席工作台、
外呼策略中心与 DNC 免打扰、外呼线路与两层并发、异步名单导入、媒体资产与分角色转写、
业务结果模板、运行事件告警,以及通话后情绪分析。下文先把主线讲清,再逐块展开这些平台能力。
它和其它组件的关系
1 | ┌────────────────┐ HTTP ┌────────────────────┐ |
- callout-server 不通过 HTTP 调用 callflow-esl,而是直接连 FreeSWITCH inbound ESL
下发originate {business_code=...,...} <dialString> &socket(<callbackHost:callbackPort> async full); - originate 时把
business_code与联系人 / 活动级variables写进 channel variable,FreeSWITCH
接通后把这条腿通过 dialplan 的 outbound socket 送回 callflow-esl,由business_code选中业务; - 通话结束后,由 FreeSWITCH 事件监听 / callflow-esl / 业务侧把结果
POST /api/call-results
回写给 callout-server(生产建议带X-Callout-Internal-Token)。
核心概念
| 概念 | 表 | 说明 |
|---|---|---|
| 外呼活动 Campaign | campaigns |
一次外呼任务:绑定业务、主叫号码、并发、重试、工作时段、计划窗口等策略 |
| 联系人 Contact | contacts |
活动下的一个被叫号码及其当前状态、重试计数、最近结果快照 |
| 拨打尝试 Attempt | call_attempts |
一次 originate 的提交结果 + 整通通话生命周期 + 业务结果(含情绪字段) |
| 派发批次 Batch | callout_batches |
一次手动或调度派发的统计(请求/认领/提交/失败数) |
| 外呼线路 Line | lines |
拨号串模板 + 主叫号码 + 共享并发上限,多活动可共用一条线路 |
| 外呼策略 Policy | callout_policies |
策略中心:禁呼日期 / 节假日 / 时段 / 号码规则 / 频控 / 重试 / 并发,可全局或任务级 |
| DNC 免打扰 | dnc_numbers |
全局免打扰名单,派发前独立拦截 |
数据表都在独立的 callout schema(与 callflow 的业务路由表分开),定义见apps/callout-server/src/db/schema.ts,部署或本地初始化统一执行 bun run db:push。完整数据表见
下文「数据表总览」。
活动与联系人生命周期
活动状态机:
1 | draft ──启动──► running ──暂停──► paused ──恢复──► running |
running是唯一会被后台调度器自动派发的状态;- 当活动下没有
pending也没有”到期可重试”的联系人时,自动标记为completed。
联系人状态机:
1 | pending ──派发──► dialing ──提交成功──► submitted ──结果回写──┬──► completed |
failed联系人在retryCount < maxRetries且nextRetryAt到期后变为可重试;- 手动重试通过
POST /api/campaigns/:id/retry把匹配联系人重置为pending; - 运行中可用
POST /api/campaigns/:campaignId/contacts/pause暂停指定联系人。
调度与节奏
外呼最怕”一拥而上打爆线路”或”在不该打的时间骚扰用户”。callout-server 用以下几个
活动级参数控制节奏:
| 参数 | 字段 | 作用 |
|---|---|---|
| 最大并发 | maxConcurrency |
同时处于 dialing + submitted 的联系人上限;派发时严格不超 |
| 拨号间隔 | callIntervalMs |
自动调度中两次 originate 提交之间的最小间隔,平滑外呼速率 |
| 工作时段 | workdayStart / workdayEnd |
HH:mm;都设置时仅在窗口内自动派发,留空表示不限 |
| 计划窗口 | scheduledStartAt / scheduledEndAt |
活动整体的计划起止时间 |
| 最大重试 | maxRetries |
每个联系人的自动重试次数上限(默认 0,不自动重试) |
| 重试间隔 | retryIntervalSeconds |
失败后多久变为可重试(写入 nextRetryAt) |
两种派发方式:
- 手动派发
POST /api/campaigns/:id/dispatch:一次最多认领 100 个pending
联系人(limit夹紧在1..100),立即提交。适合小批量测试或人工触发。 - 自动调度:后台定时器(
scheduler.intervalMs,默认 5s)扫描所有running
活动,逐个检查”是否在工作时段、是否到拨号间隔、是否有并发余量”,每轮最多认领min(scheduler.batchSize, campaign.maxConcurrency)个联系人派发,并更新lastScheduledAt控制节奏。
启动前可调用 GET /api/campaigns/:id/preflight 预检任务状态、计划窗口、待呼名单、线路与策略;
运行中可用 POST /api/campaigns/:id/batches/:batchId/cancel 取消仍在 running 的调度批次。
并发模型与多实例
callout-server 派发联系人时使用两层并发限制:
- 任务并发
campaigns.maxConcurrency:限制同一活动内同时活跃的联系人数; - 线路并发
lines.maxConcurrency:限制共享同一外呼线路的所有活动的同时活跃联系人数。
活跃 = 状态为 dialing 或 submitted。绑定线路的活动,一次批次实际派发数为:
1 | min(请求批次大小, 活动剩余槽位, 线路剩余槽位) |
未绑定线路时只用前两项;线路被禁用 / 删除时,派发会在提交 originate 前失败。
多实例部署用两层协调:
- Redis 锁(跨实例):一个全局调度器锁保证每轮只有一个实例扫描运行中活动;一个活动锁保证
手动派发与调度器派发不会同时提交同一活动; - PostgreSQL 行锁(最终一致):派发事务先锁活动与绑定线路,再算可用槽位,然后用
FOR UPDATE SKIP LOCKED占用联系人——即使 Redis 配置错误或锁过期,同一联系人也不会被重复占用。
数据库锁刻意保持短生命周期(只覆盖批次创建、并发计算、联系人占用、lastScheduledAt 更新);
FreeSWITCH originate 提交发生在事务提交之后,网络调用期间不持有数据库锁。
角色与鉴权
启用鉴权后(auth.enabled,默认开启;本地联调可 CALLOUT_AUTH_ENABLED=false 关闭),
管理 API 按角色授权。POST /api/auth/login 返回 Bearer Token,GET /api/auth/me 下发当前用户的permissions 列表,前端据此做菜单 / 路由 / 按钮门控(单一事实来源)。启用后会自动创建引导管理员账号。
| 角色 | 说明 | 关键权限 |
|---|---|---|
admin |
系统管理员 | 全部权限(含用户、审计、线路) |
supervisor |
运营主管 | 活动 / 策略 / 联系人 / 导入 / 合规 / 坐席管理 / 坐席操作;不含 admin、line:write、audit:read |
agent |
话务坐席 | contact:write + agent:operate(更新自身状态、软电话记录、领取 / 呼出 / 回拨 / 转人工);不能创建坐席 |
agent:write(创建坐席 / 坐席组)与 agent:operate(坐席自助操作)刻意分离:坐席本人可操作
软电话工作流,但不能管理坐席花名册。敏感操作(用户/角色管理、创建/更新/删除、派发、批量暂停、
导入错误 CSV 导出、媒体下载等)都会写入 audit_logs。
坐席工作台与转人工
callout 不只做机器外呼,也支持人工坐席接管:
- 坐席与坐席组
agents/agent_groups:坐席在线 / 忙碌 / 示闲 / 离线状态由PATCH /api/agents/:id/status维护; - 领取线索
POST /api/agents/:id/claim-contact:坐席领取联系人并生成跟进记录(follow_up_records); - 人工回拨
POST /api/agents/:id/callbacks:通过 FreeSWITCH originate 先呼坐席分机、再桥接客户; - 人工外呼
POST /api/agents/:id/manual-calls:坐席直接呼出客户号码; - AI 转人工
POST /api/agents/:id/transfers:把 AI 通话线索转人工,生成软电话桥接呼叫。
坐席呼出 / 回拨 / 转人工都会在 originate 变量里写入 callout_softphone_call_id,记录在softphone_calls。启用 freeswitchEvents.enabled 后,FreeSWITCH 的 CHANNEL_ANSWER /CHANNEL_HANGUP / CHANNEL_HANGUP_COMPLETE 会回写软电话的接听 / 结束时间、时长与挂机原因,
避免记录长期停留在 submitted。坐席分机拨号串模板由 freeswitch.agentDialStringTemplate
(默认 user/{{agentExtension}})控制。
外呼策略中心与 DNC
活动 / 联系人级的”能不能打、什么时候打”由策略中心 callout_policies 统一管理,可在
全局或任务级生效,执行语义为更严格优先:全局策略和任务策略都必须通过,任务级不能绕过全局禁呼。POST /api/policies/evaluate 可试算某次派发是否被允许。策略类型:
| 类型 | 作用 |
|---|---|
blocked_dates |
命中 YYYY-MM-DD 日期时阻断派发 |
holiday_blocklist |
按节假日阻断,支持指定年份日期、每年重复日期与补班放行,如 { "dates": ["2026-02-17"], "recurringDates": ["01-01"], "workdayOverrides": ["2026-02-14"], "region": "CN" } |
daily_window / weekly_window |
仅允许指定每日 / 星期时段派发 |
phone_blacklist / phone_prefix_blocklist |
按号码或前缀阻断单个联系人 |
contact_frequency_limit |
限制同一号码最近 N 天最大触达次数,如 { "days": 7, "maxAttempts": 2 } |
rate_limit / retry_rule / concurrency_limit |
收紧呼叫间隔、重试与并发配置 |
旧版独立的
blocked_dates表 //api/blocked-dates接口已并入策略中心:禁呼日期现在是blocked_dates这一策略类型,通过/api/policies维护。
DNC 免打扰名单 dnc_numbers 是全局的,派发前独立于策略表先行拦截。当 POST /api/call-results
回写 businessStatus 为 opt_out / unsubscribe / do_not_call / dnc / rejected,或businessResult 含 optOut / unsubscribe / doNotCall / dnc / rejectCall:true 时,平台会
自动把该号码沉淀到 DNC,后续派发直接拦截。
回铃音检测(可选)
普通外呼只能区分”接通 / 没接通”。开启回铃音检测后,callout-server 在彩铃/回铃阶段做两类检测:
- 运营商提示音:把早媒体音频 fork 到一个 ASR WebSocket(可直接复用 sherpa-asr-online-server 的
/audio),用识别文本匹配运营商提示音关键词,从而在接人之前就判断号码状态:
| 提示音类别 | 典型话术 |
|---|---|
| 空号 empty_number | “您拨打的号码是空号” |
| 关机 powered_off | “您拨打的电话已关机” |
| 停机 out_of_service | “您拨打的电话已停机” |
| 无法接通 unreachable | “您拨打的用户暂时无法接通” |
| 忙 busy | “正在通话中” |
| 无人接听 no_answer | 久振未接 |
- 回铃嘟声:在 B-leg 早媒体上启动 FreeSWITCH
tone_detect(依赖mod_spandsp),默认检测 450Hz,
累计 5 次后判定为无人接听。
该功能由 config.json 的 ringbackDetection 配置块控制,也可用 CALLOUT_RINGBACK_DETECTION_*
环境变量覆盖:enabled(代码默认关闭,但仓库内 config.json 默认开启)、wsUrl、bugName、mixType、sampleRate、detectionTimeoutMs(默认 20000),以及嘟声检测的 toneDetectionEnabled
(默认随回铃检测启用)、toneFrequency(默认 450)、toneHits(默认 5)、toneTimeoutMs。开启后派发走submitOutboundCallWithRingbackDetection(),检测结果写回对应 attempt,把”无人接听”和
“空号/停机”区分开,提升名单清洗与重试策略的准确性。实现见 src/freeswitch-ringback-client.ts。
通话后情绪分析(可选)
启用 emotionAnalysis.enabled=true 并配置 endpoint(默认 http://127.0.0.1:9090/analyze)后,
callout-server 的后台 worker 会把已完成的呼叫尝试排队,把 attemptId、尽力提取的转写文本、recordingUrl 与 transcriptUrl 发到 情绪分析服务 的 POST /analyze,
再把融合后的情绪极性 / 分数保存到 call_attempts.emotion_* 字段,供按客户情绪筛选线索。
worker 的轮询间隔、并发与重试由 emotionAnalysis.intervalMs / maxConcurrency /retryIntervalMs / maxRetries 控制。文本 BERT + 音频 wav2vec2 的推理细节见
情绪分析服务。
异步名单导入
大名单导入走服务端异步任务,避免一次性请求超时:
POST /api/campaigns/:campaignId/import-files:上传 CSV/TSV,创建服务端暂存行异步导入任务;POST /api/campaigns/:campaignId/import-jobs:以 JSON 联系人行创建异步导入任务;- 导入支持号码归一化、CN 地区格式校验、运营商限制、固话开关;失败行进入错误明细 / CSV;
- 进度与结果:
GET /api/import-jobs、/api/import-jobs/:id/errors、/api/import-jobs/:id/errors.csv。
任务相关数据在 import_jobs / import_job_rows / import_job_errors,由 importJobs worker 处理。
坐席间流转与转人工会议
AI 外呼通话「转人工」不是简单挂断重拨,而是把客户腿放进一个 FreeSWITCH 会议室,再把坐席
拉进同一个会议室,从而支持盲转、协商转(三方)和主管旁听。callout-server 用 conference_sessions
表登记每个会议会话(会议室 ID、关联 attempt/contact、客户腿 UUID、当前主坐席及其软电话腿、状态)。
1 | AI 通话中 ──转人工──► callflow-esl 把客户腿移入会议室 |
会议成员 API(成员动态进出的原语):
| 操作 | 端点 | 说明 |
|---|---|---|
| 加成员 | POST /api/conference-sessions/:id/participants |
role=agent(默认)拉坐席:带 agentId 指定、否则空闲池预占;replacePrimary=true 为盲转(加新坐席后踢旧主坐席并切 primary),否则为协商转 / 三方(primary 不变)。role=supervisor 为主管介入:必须带 agentId,不占坐席池,mode=monitor 静默监听(mute)、mode=barge 可发言 |
| 减成员 | DELETE /api/conference-sessions/:id/participants/:softphoneCallId |
对成员腿 uuid_kill:协商转完成(移除旧主坐席)/ 取消(移除新坐席)/ 主管退出;移除主坐席时自动把 primary 提升为剩余坐席腿 |
| 监控 | GET /api/conference-sessions、GET /api/conference-sessions/:id |
列表 / 详情,附 FreeSWITCH 实时成员数与 live / ended / unknown 状态(只读不写库) |
坐席入会通过 inbound ESL originate 坐席分机、业务编码固定 agent-conference-guest。AI 业务侧发起
转人工时,先由 callflow-esl 调用公开的 POST /api/transfers/route 预占空闲坐席并拿到坐席分机。
僵尸会话兜底:conference_sessions.status 只在坐席腿挂机事件命中收尾路径时才翻 ended,
客户先挂断、事件丢失或进程重启会留下「FS 里会议已没、库里仍 active」的僵尸行。后台conferenceReconcile worker(默认每 30s、60s 宽限期)用实时成员数校准:成员数为 0 且过宽限期即
回收卡住的坐席腿、挂断残留客户腿、关闭会话并记 conference_session_reconciled 事件;FreeSWITCH
不可达时整轮跳过,绝不误关。
前端 callout-webpage 的实时作战大屏与转人工会议监控面板即基于这组 API。
结果回写
callout-server 只把 call_attempts.status 当作 originate 提交结果
(accepted / rejected / error)。整通通话的生命周期与业务结果由外部回写:
1 | POST /api/call-results |
1 | { |
- 可用
attemptId、requestId或campaignId + contactId定位 attempt; intentLevel/intentCategory/resultSchemaCode作为标准筛选字段保存(模板由/api/result-schemas维护,可定义意向等级、意向分类与字段元数据);businessResult以
不透明 JSON 继续保存具体业务的扩展数据;- callout 据
callStatus+businessStatus推导联系人最终状态(completed / failed),并决定是否
安排重试、是否把活动标记completed,以及是否把号码沉淀到 DNC。
录音 / 转写等媒体由 media_assets / transcript_segments 管理:GET/POST /api/media-assets、GET /api/media-assets/:id/download(过期返回 410)、POST /api/media-assets/cleanup-expired,
以及分角色(机器人 / 客户 / 坐席)转写片段 GET/POST /api/media-assets/:id/transcript-segments。
录音文件本身仍由 FreeSWITCH / callflow 侧生成并提供访问,callout 只保存并返回 URL。
运行事件与告警
runtime_events 记录调度、生命周期补偿、导入、会议对账等后台运行事件。管理端通过GET /api/runtime/overview 与 GET /api/runtime/events(SSE 实时流)展示,PATCH /api/runtime/events/:id/acknowledge 确认告警。配置 notifications.webhookUrl 后,达到notifications.minLevel 的事件会同步发送 webhook。常见事件:调度器未捕获异常 →scheduler_failed;生命周期超时补偿 → lifecycle_timeout_reconciled;导入完成 / 失败 →import_job_completed / import_job_failed;情绪分析 worker 异常 →emotion_analysis_worker_failed;会议对账收尾僵尸会话 → conference_session_reconciled。
服务启动时按配置拉起 5 个后台 worker(均 setInterval 驱动并带重入保护):调度器scheduler、生命周期对账 lifecycleReconcile、会议对账 conferenceReconcile、异步导入importJobs、通话后情绪分析 emotionAnalysis,外加一个 FreeSWITCH inbound ESL 事件监听器
(freeswitchEvents,回写接听 / 挂机兜底)。
HTTP API 一览
所有响应统一用 { ok, data } / { ok, error } 信封。下表按功能分组列出主要端点,完整清单
以 apps/callout-server/src/router.ts 与该项目 README.md 为准。
| 分组 | 方法 路径 |
|---|---|
| 健康 / 鉴权 | GET /health · POST /api/auth/login · POST /api/auth/logout · GET /api/auth/me · GET /api/roles/permissions |
| 看板 | GET /api/overview · GET /api/overview/trend |
| 业务来源 | GET /api/businesses(来自 callflow.call_businesses) |
| 活动 | GET/POST /api/campaigns · GET/PATCH/DELETE /api/campaigns/:id · GET /api/campaigns/:id/preflight |
| 联系人 | GET/POST /api/campaigns/:id/contacts · PATCH/DELETE …/:contactId · POST …/contacts/pause |
| 派发 | POST /api/campaigns/:id/dispatch · POST /api/campaigns/:id/retry · POST /api/campaigns/:id/batches/:batchId/cancel |
| 呼叫明细 | GET /api/call-attempts(支持按 intentLevel/intentCategory/resultSchemaCode 筛选) · POST /api/call-results |
| 线路 | GET/POST /api/lines · PATCH/DELETE /api/lines/:id |
| 策略 / DNC | GET/POST /api/policies · PATCH/DELETE /api/policies/:id · POST /api/policies/evaluate · GET/POST /api/dnc · DELETE /api/dnc/:id |
| 坐席 | GET /api/agents · POST /api/agents · POST /api/agent-groups · PATCH /api/agents/:id/status · POST /api/agents/:id/claim-contact · …/callbacks · …/manual-calls · …/transfers · GET /api/softphone-calls · GET/POST /api/follow-ups |
| 导入 | POST …/import-files · POST …/import-jobs · GET /api/import-jobs · /api/import-jobs/:id/errors[.csv] |
| 媒体 / 结果模板 | GET/POST /api/media-assets · …/:id/download · …/cleanup-expired · …/:id/transcript-segments · GET/POST /api/result-schemas · PATCH /api/result-schemas/:id |
| 转人工会议 | POST /api/transfers/route(公开回调) · GET /api/conference-sessions[/:id] · POST /api/conference-sessions/:id/participants · DELETE /api/conference-sessions/:id/participants/:softphoneCallId |
| 运行 / 审计 | GET /api/runtime/overview · GET /api/runtime/events(SSE) · PATCH /api/runtime/events/:id/acknowledge · GET /api/audit-logs |
| 用户 | GET/POST /api/users · PATCH/DELETE /api/users/:id |
数据表总览
表都在 callout schema,定义见 src/db/schema.ts:
| 表 | 作用 |
|---|---|
campaigns |
外呼任务:业务、主叫号码、并发 / 重试 / 间隔 / 工作时段 / 计划窗口 / 状态 / 变量 |
lines / line_numbers |
外呼线路与号码池:拨号串模板、共享 maxConcurrency、号码分配模式(一号多并发 / 号码池轮换 / 一号一并发);一条线路可挂多个主叫号码并带号码级并发与轮换权重 |
contacts |
活动中的单个被叫:号码、状态、重试计数、最近呼叫 / 业务结果快照 |
call_attempts |
一次 originate 尝试:提交状态、通话状态、挂机原因、时间、业务结果与情绪字段 |
callout_batches |
一次派发批次(manual / scheduler)的请求 / 占用 / 提交 / 失败计数 |
callout_policies |
策略中心:禁呼 / 节假日 / 时段 / 号码规则 / 频控 / 重试 / 并发 |
dnc_numbers |
DNC 免打扰名单 |
users / auth_sessions |
管理台用户、角色与会话 |
audit_logs |
敏感操作审计 |
agents / agent_groups |
坐席与坐席分组 |
follow_up_records |
人工跟进记录 |
softphone_calls |
坐席软电话呼出 / 回拨 / 转人工的提交与 FS 接听 / 挂机状态 |
import_jobs / import_job_rows / import_job_errors |
异步导入任务、暂存行、号码校验与错误明细 |
media_assets / transcript_segments |
录音 / 转写资产与分角色转写片段 |
conference_sessions |
转人工会议会话:会议室 ID、关联呼叫 / 联系人、客户腿 UUID、当前主坐席及软电话腿、状态 |
result_schemas |
业务结果模板:意向等级、意向分类、可结构化字段定义 |
runtime_events |
调度、补偿、会议对账、告警等运行事件 |
配置
运行时配置来自 apps/callout-server/config.json,可被 CALLOUT_ 前缀环境变量覆盖。常用分组:
1 | { |
| 字段 | 说明 |
|---|---|
server.host / port / allowedOrigins |
HTTP 监听与管理台跨域白名单 |
auth.enabled / sessionTtlSeconds |
角色鉴权开关与会话有效期 |
scheduler.* |
后台调度开关、轮询间隔、每轮每活动批量 |
redis.* |
多实例分布式锁(缺省单实例运行) |
emotionAnalysis.* |
通话后情绪分析开关、端点、超时 / 并发 / 重试 |
internalApi.callResultToken |
/api/call-results 内部回写令牌(请求头 X-Callout-Internal-Token) |
freeswitch.callbackHost / callbackPort |
callflow-esl outbound socket 地址,必须 FS 可达 |
freeswitch.originateDialStringTemplate |
默认拨号串模板,活动可用 dialStringTemplate 覆盖 |
db.url |
PostgreSQL;callout 从中派生 callout 与 callflow 两个 schema |
lifecycleReconcile/conferenceReconcile/importJobs/notifications/ringbackDetection/freeswitchEvents等带内置默认值的分组、以及完整字段表与环境变量覆盖,见 配置参考。callbackHost/callbackPort与 callflow-esl 同样的”回拨地址”陷阱在这里同样成立,参见
部署拓扑 的网络陷阱章节。
前端 callout-webpage
Quasar 前端,开发端口 19920,通过 VITE_CALLOUT_API_BASE_URL(默认http://127.0.0.1:9920)连接 callout-server,并按 GET /api/auth/me 返回的 permissions
做菜单 / 路由 / 按钮门控。主要界面:
- 看板:活动 / 联系人 / 尝试计数与接通、完成、失败率、平均时长,以及趋势图;
- 活动列表与详情:新建 / 编辑活动,启动 / 暂停调度;联系人、拨打尝试、派发批次三联面板,
支持导入名单、手动派发、重试、暂停、增删改联系人; - 策略 / DNC / 线路:策略中心、DNC 名单、外呼线路与号码池维护(含号码分配模式);
- 运营监控:实时并发、调度批次、卡住联系人、近 1 分钟提交 / 失败与运行事件告警(SSE);
- 实时作战大屏:聚合运行态势、通话流时间线、坐席状态墙、进行中转人工会议看板、线路 / 任务容量与实时事件,面向运营与演示;
- 转人工会议监控:查看进行中会议与成员腿,盲转 / 协商转目标坐席、主管监听 / 插话、移除成员,并显示 FreeSWITCH 实时成员数;
- 坐席工作台:坐席状态、领取线索、人工外呼 / 回拨 / 转人工、软电话记录;
- 呼叫明细:按意向等级 / 分类 / 结果模板筛选线索,命中
recordingUrl可直接播放,并展示通话后情绪分析(极性 + 分数 + 文本 / 音频原始结论)。