脚本撰写指南 #
基于 Starlark 的语法和中控提供的运行时环境,我们可以撰写出各种各样的脚本,用于实现各种定制的功能。本文将介绍如何撰写脚本,以及脚本的配置方式。
设计思想 #
脚本的作用主要有两个:
- 为后续脚本提供运行时环境,例如定义一些全局变量,或者初始化一些资源。
- 对请求或结果进行特定的干涉,例如对请求进行预处理,或者对结果进行后处理。
具体脚本的运行时机和运行环境,需要考虑以下方面:
- 入口点 (entrypoint):脚本的执行时机,即中控中对应的执行阶段,一个入口点可以有多个脚本顺序独立执行。
- 全局环境 (global):脚本的默认执行环境,即所有脚本中都可以使用的变量和函数,不会因入口点变化而改变。
- 执行环境 (environment):脚本的执行环境,即脚本中可以使用的变量和函数,会随着入口点的不同而不同。
- 输出参数 (output):脚本执行后将作为输出结果继续使用的参数,修改会影响外部执行结果,会随着入口点的不同而不同。
- 异常处理 (exception):脚本执行过程中可能发生异常的处理,处理方式会随着入口点的不同而不同。
因为中控脚本的设计是以干涉为目的,因此输出参数相对于传统执行环境而言,是比较特殊的,具体特点是:
- 参数都是直接在环境中作为变量可用,可以直接进行修改操作,而不需要实现一个特定签名的函数来处理输入和输出。
- 执行空白脚本或不执行脚本的结果是一致的,即脚本的执行仅对外部参数进行修改,而不是从零构造参数本身。
- 复杂数据类型为 object,即 Go 类型指针在 Starlark 中的包壳,可以直接在 Starlark 中进行类似 dict 的修改操作,而不需要进行转换。
关于允许时环境的数据传递和覆盖:
- 相对于普通 Starlark 环境,中控开启了
--globalreassign参数,即允许变量复用/覆盖,支持在顶层使用if语句和for循环。 - 不同 entrypoint 的执行结果会向后续层级传递,即 server -> session -> flow 等顺序,下级覆盖上级。
- 同一 entrypoint 下的多个脚本之间自身数据独立,即无法相互访问执行结果,后执行的结果会覆盖先执行的,但对输出参数的修改结果是有后效的。
- 不同 entrypoint 的脚本可以对传递的参数进行覆盖,结果会向后续层级传递;而对输出参数的直接覆盖,则无法影响外部执行结果,应选择原地修改。
- 对于同一 entrypoint 执行失败的情况,已经成功执行的脚本、部分的执行结果会被保持,如果流程没有退出,则会向后续层级传递。
配置方式 #
脚本的内容和执行条件,需要以列表的形式写入到中控的配置文件的 script 字段中。具体的脚本配置参数如下:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name | string | 空字符串 | 表示脚本的名称。它可以是任何字符串,并不影响功能。 |
enabled | boolean | true | 表示脚本是否处于启用状态。如果未指定,默认会将脚本视为启用状态。 |
weight | integer | 0 | 用于对同一入口点下的多个脚本进行排序。权重值较小的脚本会在权重值较大的脚本之前执行。 |
repl | boolean | false | 表示启用 REPL 调试模式。如果设置为 true,中控将在脚本执行后,进入类似 Python 交互的 REPL 模式,直到 Ctrl+D 退出。 |
timeout | string | 10s | 指定脚本允许运行的最长持续时间。如果未提供,将默认为"10s"(10秒)。格式是持续时间的字符串表示形式,例如:“5s"表示5秒,“2m"表示2分钟等。 |
source | string | text | 表示脚本的来源。可以是 file(本地文件), url(在线文件) 或 text(文本)。 |
entrypoint | string | 必填 | 表示脚本的入口点,即中控中对应的执行阶段。 |
async | boolean | false | 指定脚本是否应异步运行,异步执行的脚本的返回值将不会被记录。 |
code | string | 空字符串 | 脚本的源代码。如果 source 是 text,则这个字段是必填的。 |
url | string | 空字符串 | 脚本的在线地址,需要 GET 方法直接可访问。如果 source 是 url,则这个字段是必填的。 |
path | string | 空字符串 | 脚本的本地路径,需要直接可访问,有读取权限。如果 source 是 file,则这个字段是必填的。 |
**注意**: - REPL 调试模式仅用于单并发情况下的测试,切勿用于上线,否则可能会导致中控无法正常工作。 - 所有启用的脚本需要内容非空,启动时会进行检查,如果发现空脚本或者获取失败,中控将报错并退出。
示例配置:
script:
- name: ""
weight: 1
timeout: 1s
entrypoint: server_start
repl: true
code: |
print("Hello New Server! @", time.now())
a = 1
b = 2
c = a + b
print("EP:", entrypoint)
r = http.get('https://dhpoc.softsugar.com/demo0/ping')
print(r)
rj = r.json()
print("中控版本", rj['hostname'], hash.md5(rj['message']))
- name: test2
enabled: true
weight: 2
timeout: 1s
entrypoint: server_start
async: true
code: |
print("Hello New Server! 2")
x = [enabled_tts, enabled_asr, enabled_nlp]
print(type(cs_config))
print(dir(cs_config))
sleep(0.5)
print("Wake up ~~~ CS Config:", cs_config.DebugMode)
- name: test3
weight: 10
timeout: 1s
entrypoint: session_start
code: |
print("Hello New Session!")
全局环境 Globals #
全局环境变量 #
每个脚本无论入口点,都可以使用以下全局变量:
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
hostname | string | st78 | 中控运行机器或容器的 hostname |
workdir | string | /home/dev | 中控运行时的工作目录 |
os_platform | string | darwin | 中控运行时的操作系统平台 |
debug_mode | boolean | true | 中控运行时的是否处于调试模式 |
enabled_asr | list | [5, 6, 7] | 中控运行时的启用的 ASR 服务商 ID 列表 |
enabled_nlp | list | [2, 6] | 中控运行时的启用的 NLP 服务商 ID 列表 |
enabled_tts | list | [3] | 中控运行时的启用的 TTS 服务商 ID 列表 |
entrypoint | string | server_start | 当前脚本的入口点,具体取值会根据运行时的入口点改变 |
cron_cache | object | {"key": "value", ...} | Cron Job 的存储信息,用于定时任务的存储 |
**注意**: 全局变量仅供参考,对它们的修改不会影响外部执行结果。
全局扩展模块 #
每个脚本无论入口点,都可以使用以下函数模块:
| 名称 | 说明 |
|---|---|
go_idiomatic | 独立扩展方法合计,直接使用,调用时不需要写模块名称 |
base64 | Base64 编解码 |
hashlib | MD5、SHA等哈希函数计算 |
http | HTTP 请求发送 |
json | JSON 编解码 |
math | 常用数学计算函数 |
random | 常用随机变量生成 |
re | 正则表达式 |
struct | Starlark struct 构建 |
time | 常用时间变量操作函数 |
具体模块和函数的使用方法,请参考附录的相关文档。
入口点 Entrypoint #
定义好的脚本将在以下入口点(即 entrypoint)被中控调用执行,具体的执行时机和执行环境,请参考如下具体说明。
各个入口点之间的执行顺序和数据继承关系如下:
flowchart TD
ServerS(server_start)
SessionS(session_start)
ASRA(asr_after)
NLPB(nlp_before)
NLPA(nlp_after)
TTSB(tts_before)
TTSA(tts_after)
ServerS --> SessionS
SessionS --> NLPB
NLPB --> NLPA
SessionS --> ASRA
SessionS --> TTSB
TTSB --> TTSA
server_start 中控启动
#
运行时机 #
中控进程启动过程中,当正确加载配置文件、显示了版本和配置信息后,将执行 server_start 入口点下的所有脚本。
即当脚本执行时,中控的授权校验、Web 服务启动尚未进行。
异常处理 #
如果脚本执行过程中发生异常,中控将报告错误信息并进程退出。
环境变量 #
无特定环境变量。
输出参数 #
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
server_config | object | {"endpoint": "/v1", "asr": {...}, ...} | 中控的配置文件的 JSON 形式 |
即可以通过对 server_config 的调整,来动态对中控的配置文件进行修改,修改仅执行一次,运行时全局有效。
它所设定的全局变量和函数,可以在后续的 session_start 等入口点的执行环境中被使用。
使用场景举例 #
可以参考但不限于以下场景:
- 进行一些启动前的准备工作,例如:进行一些第三方接口的鉴权,或者进行记录日志,上报监控等。
- 调用外部接口,获取中控的特定配置项并进行修改,例如:设置 WebHook 回调地址,控制授权时间等。
- 根据特定条件,决定是否阻止中控启动,例如:检查是否联网,相关服务是否可用等。
server_cron 定时任务
#
运行时机 #
中控启动后,会根据配置文件中的 Cron Job 配置,定时执行 server_cron 入口点下的所有脚本。
异常处理 #
如果脚本执行过程中发生异常,中控将报告错误信息,但不会影响其他 Cron Job 的执行。
环境变量 #
在 server_start 脚本输出的变量基础上,无特定环境变量。
输出参数 #
无输出参数。
使用场景举例 #
可以参考但不限于以下场景:
- 定时获取 CDN 上的配置文件,更新中控的配置。
- 定时刷新第三方 Token,保证中控的鉴权信息有效。
session_start 会话建立
#
运行时机 #
客户端与中控建立 WebSocket 链接后,已经发送了格式正确的 Starter 包,并且中控已经成功解析了 Starter 包,确认了有效期和授权信息有效后,将执行 session_start 入口点下的所有脚本。
即当脚本执行时,鉴权通过的回复,尚未发送给客户端,客户端尚未收到任何业务数据。中控对应的 Driver 的 Client 也尚未建立。
异常处理 #
如果脚本执行过程中发生异常,中控将以 1011 错误码关闭当前 WebSocket 会话,其他会话不受影响。
环境变量 #
在 server_start 脚本输出的变量基础上,会根据具体会话设置以下环境变量:
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
client_ip | string | ::1 | 客户端真实 IP |
remote_addr | string | [::1]:56418 | 客户端连接到服务器的 IP 地址 |
tenant_id | string | 123 | 客户端指定的租户 ID,未指定时值为 None |
device_id | string | devbox | 客户端指定的设备 ID,未指定时值为空字符串 |
session_id | string | 92e962d0-db14-4bd9-a65d-70f08d30a03e | 客户端指定的会话 ID,未指定时值为中控随机分配的 UUID |
session_num | int | 1 | 自中控启动以来的会话序号 |
输出参数 #
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
starter_request | object | {"type": "NLP2", ...} | 解析后的 Starter 包请求 |
starter_response | object | {"status":"ok", ...} | 拟定的 Starter 包回复 |
session_config | object | {"endpoint": "/v1", ...} | 中控的配置文件的 JSON 形式 |
即可以通过对 session_config 的调整,来动态对中控的配置文件进行修改,修改仅对当前会话有效。
它所设定的全局变量和函数,可以在后续的 flow_start 等入口点的执行环境中被使用。
使用场景举例 #
可以参考但不限于以下场景:
- 关闭中控自带鉴权,将 auth 信息发给第三方接口进行鉴权和身份确认。
- 特定 Workflow Type 的修改,例如将所有 TTS4 的请求转发到 TTS5。
- 对于 Starter 其他字段进行修正,例如替换 xiaoyi 为其他合理的 DeviceID。
- 根据特定条件,修改对应的 VendorConfig,例如:根据用户请求的 VoiceID,使用私有的 TTS 配置信息。
- 进行额外的第三方监控上报,例如:将会话信息上报到第三方监控平台。
nlp_before NLP 发送请求前
#
运行时机 #
客户端向中控发送了 NLP 请求,但中控尚未把请求发送给 NLP 服务商前,将执行 nlp_before 入口点下的所有脚本。
即当脚本执行时,中控尚未发送 NLP 请求,也未在缓存中进行查询,也没有按具体服务商构建报文,可以在此对请求进行修改。
异常处理 #
如果脚本执行过程中发生异常,中控将当前请求的状态标记为错误,不再发送给 NLP 服务商,并将错误信息返回给客户端。但 WebSocket 会话不会关闭,后续请求不受影响。
环境变量 #
在 server_start 脚本和 session_start 脚本输出的变量基础上,会根据具体请求设置以下环境变量:
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
device_id | string | devbox | 客户端指定的设备 ID,未指定时值为空字符串 |
session_id | string | 92e962d0-db14-4bd9-a65d-70f08d30a03e | 客户端指定的会话 ID,未指定时值为中控随机分配的 UUID |
trace_id | string | 55d98d63-c86c-423a-b015-0920614ad897 | 当前请求的追踪 ID,为中控随机分配的 UUID |
flow_name | string | ASR+NLP | 当前会话所处的工作流,即 Starter 中 type 的指定 |
vendor_id | string | NLP6 | 当前会话使用的服务商 ID |
query_index | int | 1 | 标识当前会话中第几条 NLP 请求,从1开始计数 |
输出参数 #
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
nlp_request | object | {"query": "你好", ...} | 待处理的 NLP 请求 |
nlp_meta | object | {"omit_error": false, ...} | 当前会话中 NLP 的 Starter 包的中 NLP 相关配置信息 |
可以通过对 nlp_request 的调整,来动态对中控的 NLP 请求进行修改,修改仅对当前请求有效;可以通过对 nlp_meta 的调整,来动态对中控的会话配置进行修改,修改仅对当前会话有效。
使用场景举例 #
可以参考但不限于以下场景:
- 对请求进行预处理,例如:对请求中的敏感词进行过滤,或者对请求中的特定字段进行修改。
- 对请求进行额外的第三方监控上报,例如:将请求信息上报到第三方监控平台。
- 根据特定条件,修改会话对应的 NLP Meta 配置,例如:修改请求的 PromptHeader。
nlp_after NLP 收到回复后
#
运行时机 #
客户端向中控发送了 NLP 请求,中控已经把请求发送给 NLP 服务商,并收到了 NLP 服务商返回的结果,在将结果返回给客户端之前,将执行 nlp_after 入口点下的所有脚本。
即当脚本执行时,中控已经收到 NLP 服务商的结果并进行了归一化操作,但尚未发送给客户端,也未在缓存中存储查询,可以在此对结果进行修改。
异常处理 #
如果脚本执行过程中发生异常,中控将当前请求的状态标记为错误,并将错误信息返回给客户端。但 WebSocket 会话不会关闭,后续请求不受影响。
环境变量 #
在 server_start 脚本和 session_start 脚本输出的变量基础上,并结合当前请求的前序 nlp_before 的输出变量,会根据具体请求设置以下环境变量:
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
device_id | string | devbox | 客户端指定的设备 ID,未指定时值为空字符串 |
session_id | string | 92e962d0-db14-4bd9-a65d-70f08d30a03e | 客户端指定的会话 ID,未指定时值为中控随机分配的 UUID |
trace_id | string | 55d98d63-c86c-423a-b015-0920614ad897 | 当前请求的追踪 ID,为中控随机分配的 UUID |
flow_name | string | ASR+NLP | 当前会话所处的工作流,即 Starter 中 type 的指定 |
vendor_id | string | NLP6 | 当前会话使用的服务商 ID |
query_index | int | 1 | 标识当前会话中第几条 NLP 请求,从1开始计数 |
nlp_error | string | failed to call ... | 当前请求收到的错误信息,空字符串表示未出错 |
is_cached | bool | True | 当前回复内容是否来自缓存,True 表示为缓存结果 |
输出参数 #
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
nlp_request | object | {"query": "你好", ...} | 解析后的 NLP 请求 |
nlp_meta | object | {"omit_error": false, ...} | 当前会话中 NLP 的 Starter 包的中 NLP 相关配置信息 |
nlp_response | object | {"text": "你好", ...} | 待处理的 NLP 回复 |
可以通过对 nlp_response 的调整,来动态对中控的 NLP 回复进行修改,修改仅对当前请求有效;可以通过对 nlp_meta 的调整,来动态对中控的会话配置进行修改,修改仅对当前会话有效;此处修改 nlp_request 意义不大,因为已经发送给 NLP 服务商,无法再修改。
使用场景举例 #
可以参考但不限于以下场景:
- 对回复进行预处理,例如:对回复中的敏感词进行过滤,或者对回复中的特定字段进行修改。
- 对回复内容和错误进行额外的第三方监控上报,例如:将回复信息上报到第三方监控平台。
asr_after ASR 收到结果后
#
运行时机 #
客户端向中控发送了 ASR 数据请求,中控已经把请求发送给 ASR 服务商,并收到了 ASR 服务商返回的结果后,在将结果返回给客户端之前,将执行 asr_after 入口点下的所有脚本。
即当脚本执行时,中控已经收到 ASR 服务商的结果并进行了归一化操作,但尚未发送给客户端,也未进行字幕等进一步处理,可以在此对结果进行修改。
异常处理 #
如果脚本执行过程中发生异常,中控将当前结果的状态标记为错误,并将错误信息返回给客户端。但 WebSocket 会话不会关闭,后续结果不受影响。
环境变量 #
在 server_start 脚本和 session_start 脚本输出的变量基础上,会根据当前请求的特征设置如下环境变量:
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
device_id | string | devbox | 客户端指定的设备 ID,未指定时为空字符串 |
session_id | string | 92e962d0-db14-4bd9-a65d-70f08d30a03e | 客户端指定的会话 ID,未指定时为中控随机分配的 UUID |
trace_id | string | 55d98d63-c86c-423a-b015-0920614ad897 | 当前请求的追踪 ID,为中控随机分配的 UUID |
flow_name | string | ASR+NLP | 当前会话所处的工作流,即 Starter 中 type 的指定 |
vendor_id | string | ASR5 | 当前会话使用的服务商 ID |
result_index | int | 1 | 标识当前结果中第几条 ASR 请求,从1开始计数 |
输出参数 #
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
asr_meta | object | {"omit_error": false, ...} | 当前会话中 ASR 的 Starter 包的中 ASR 相关配置信息 |
asr_response | object | {"type": "text", ...} | 待处理的 ASR 结果 |
可以通过对 asr_response 的调整,来动态对中控的 ASR 结果进行修改,修改仅对当前结果有效;可以通过对 asr_meta 的调整,来动态对中控的会话配置进行修改,修改仅对当前会话有效。
使用场景举例 #
可以参考但不限于以下场景:
- 对结果进行预处理,例如:对结果中的敏感词进行过滤,或者对结果中的特定字段进行修改。
- 对结果内容和错误进行额外的第三方监控上报,例如:将结果信息上报到第三方监控平台。
tts_before TTS 发送请求前
#
运行时机 #
客户端向中控发送了 TTS 请求,但中控尚未把请求发送给 TTS 服务商前,将执行 tts_before 入口点下的所有脚本。
即当脚本执行时,中控尚未发送 TTS 请求,也未在缓存中进行查询,也没有按具体服务商构建报文,可以在此对请求进行修改。
异常处理 #
如果脚本执行过程中发生异常,中控将当前请求的状态标记为错误,不再发送给 TTS 服务商,并将错误信息返回给客户端。但 WebSocket 会话不会关闭,后续请求不受影响。
环境变量 #
在 server_start 脚本和 session_start 脚本输出的变量基础上,会根据具体请求设置以下环境变量:
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
device_id | string | devbox | 客户端指定的设备 ID,未指定时值为空字符串 |
session_id | string | 92e962d0-db14-4bd9-a65d-70f08d30a03e | 客户端指定的会话 ID,未指定时值为中控随机分配的 UUID |
trace_id | string | 55d98d63-c86c-423a-b015-0920614ad897 | 当前请求的追踪 ID,为中控随机分配的 UUID |
flow_name | string | NLP+TTS | 当前会话所处的工作流,即 Starter 中 type 的指定 |
vendor_id | string | TTS5 | 当前会话使用的服务商 ID |
request_id | string | an5m178kx6wgsskg7zyl6sxlb | 当前请求使用的标识符 ID,未指定时值为中控随机分配 |
query_index | int | 1 | 标识当前会话中第几条 TTS 请求,从1开始计数 |
输出参数 #
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
tts_request | object | {"query": "你好", ...} | 待处理的 TTS 请求 |
tts_meta | object | {"language": "zh-CN", ...} | 当前会话中 TTS 的 Starter 包的中 TTS 相关配置信息 |
可以通过对 tts_request 的调整,来动态对中控的 TTS 请求进行修改,修改仅对当前请求有效;可以通过对 tts_meta 的调整,来动态对中控的会话配置进行修改,修改仅对当前会话有效。
使用场景举例 #
可以参考但不限于以下场景:
- 对请求进行预处理,例如:对请求中的特定字段进行修改。
- 对请求进行额外的第三方监控上报,例如:将请求信息上报到第三方监控平台。
- 根据特定条件,修改会话对应的 TTS Meta 配置,例如:修改请求的 voice 或 volume。
tts_after TTS 收到回复后
#
运行时机 #
客户端向中控发送了 TTS 请求,中控已经把请求发送给 TTS 服务商,并收到了 TTS 服务商返回的结果,在将结果返回给客户端之前,将执行 tts_after 入口点下的所有脚本。
即当脚本执行时,中控已经收到 TTS 服务商的结果并进行了归一化操作,但尚未发送给客户端,也未在缓存中存储查询,可以在此对结果进行修改。
异常处理 #
如果脚本执行过程中发生异常,中控将当前请求的状态标记为错误,并将错误信息返回给客户端。但 WebSocket 会话不会关闭,后续请求不受影响。
环境变量 #
在 server_start 脚本和 session_start 脚本输出的变量基础上,并结合当前请求的前序 tts_before 的输出变量,会根据具体请求设置以下环境变量:
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
device_id | string | devbox | 客户端指定的设备 ID,未指定时值为空字符串 |
session_id | string | 92e962d0-db14-4bd9-a65d-70f08d30a03e | 客户端指定的会话 ID,未指定时值为中控随机分配的 UUID |
trace_id | string | 55d98d63-c86c-423a-b015-0920614ad897 | 当前请求的追踪 ID,为中控随机分配的 UUID |
flow_name | string | NLP+TTS | 当前会话所处的工作流,即 Starter 中 type 的指定 |
vendor_id | string | TTS5 | 当前会话使用的服务商 ID |
request_id | string | an5m178kx6wgsskg7zyl6sxlb | 当前请求使用的标识符 ID,未指定时值为中控随机分配 |
query_index | int | 1 | 标识当前会话中第几条 TTS 请求,从1开始计数 |
输出参数 #
| 名称 | 类型 | 举例 | 说明 |
|---|---|---|---|
tts_request | object | {"query": "你好", ...} | 待处理的 TTS 请求 |
tts_meta | object | {"language": "zh-CN", ...} | 当前会话中 TTS 的 Starter 包的中 TTS 相关配置信息 |
tts_response | object | {"type": "subtitle", ...} | 待处理的 TTS 结果 |
可以通过对 tts_response 的调整,来动态对中控的 TTS 回复进行修改,修改仅对当前请求有效;此处修改 tts_request 意义不大,因为已经发送给 TTS 服务商,无法再修改。
使用场景举例 #
可以参考但不限于以下场景:
- 对回复进行预处理,例如:对回复中的字幕、多音字字段进行繁简转换,或者对回复中的特定字段进行修改。
- 对回复内容和错误进行额外的第三方监控上报,例如:将回复信息上报到第三方监控平台。