流式问答对话
流式问答对话是模仿打字机效果的对话方式,大模型可以在很短时间内就开始输出,体验更好。推荐在任何支持流式输出的场景中使用这种方式进行对话。
发起请求
- 请求方法: POST
- 请求 URL: /v1/ctai/query_text_chat
请求参数
{ "query_text": "你好,请问2路汽车在哪里乘坐", "ctdoc_id": "test_doc_id", "session_id": "test_id", "enable_citation": "1", "enable_followup": "1", "smart_temp": "0", "lang":"en", "context": "test_context", "stream": "1", }
请求参数
参数 | 类型 | 必需 | 参数说明 |
---|---|---|---|
query_text | string | 是 | 问题文本,例如:你好。 |
ctdoc_id | string | 否 | 文档 id,多个用英文逗号分隔,如果指定了文档 id 则只从指定的文档中思考,如果不指定则从全部文档中思考。 |
session_id | string | 是 | 会话 id,用于串连多轮聊天的会话 id,相同会话 id 的多次问答才能形成多轮问答。不能超过 64 字节,只允许数字英文和 -_. 字符。 |
enable_citation | string | 否 | 是否返回引文,默认返回。0:否 , 1:是。 |
enable_followup | string | 否 | 是否返回推荐问题,默认返回。0:否 , 1:是。 |
smart_temp | string | 否 | AI 创造性等级,取值是 0-10。0 为最保守等级,10 为最大限度自由发挥等级。 |
lang | string | 否 | 语言,如果没有指定会根据 query_text 自动检测语言。指定只可提高回复语音准确性 可选值有:zh_CN / en_US / ko_KR / ja_JP / th_TH。 |
context | string | 否 | 默认是空。外部传入的上下文, 字符串类型,会被当作一个完整的检索出来的片段,放入给大模型的prompt中的[参考资料]中 |
enable_elaborate | string | 否 | 是否启用详细阐述,默认是启用。0:关闭 , 1:启用。其中这个功能可以让AI适当发挥写出更丰满的文字,但适当发挥也可能导致它产出一些错误答案 |
stream | string | 否 | 是否以流式输出, 默认是非流式,0: 否 ,1: 是。流式会话问答,填1 |
category | string | 否 | 用什么方式聊天 doc_chat:基于知识库聊天(默认值) tpl_chat:创作中心 |
ctprofile_id | string | 否 | 聊天人设选择。 当category为doc_chat时,这个值不用传。在项目设置中设置啥人设,就是啥人设 当category是tpl_chat是,有如下两个选择: – creator:无历史记录的创作者中心专用人设。 – writer:有历史记录的创作中心专用人设(默认值)。 |
流式应答
流式应答使用 SSE(Server-Sent Events)协议;且暂不支持接收 Last-Event-Id Header,也就是暂不支持通过包 id 恢复中断的 SSE 连接;
流式应答分包规则为“中间包”和“结束包”两种,分包的最后一个包为结束包,除结束包外其他分包均为中间包,所有分包送出完毕之后会主动关闭连接。他们分别如下:
- 中间包的规则如下:
- 中间包仅包含当前包中内容的引文的文档列表和推荐问题列表;
- 中间包的 event 字段为空串;
- 结束包的规则如下:
- 结束包不包含任何实际 answer 内容,也就是说结束包是在所有 answer 全部输出完成之后(通过中间包输出),额外单独附加的一个特殊包;
- 结束包的 event 字段为 stop;
- 结束包中包含了本次 query 的整体 answer 的全部引文的文档列表和推荐问题列表;
应答 Header
- Content-Type : text/event-stream
应答示例
这里以应答“你好”为例,它可能被拆成两个数据包传输,并且在最后附加一个结束标志包(event为stop)。最终应答一共三个数据包。
id: test_id_1 data: {"ret":"0","data":{"msg_id":"test_id_1","msg_sn":"0","event":"","answer_text":"你"}} id: test_id_2 data: {"ret":"0","data":{"msg_id":"test_id_2","msg_sn":"1","event":"","answer_text":"好"}} id: test_id_3 data: {"ret":"0","data":{"msg_id":"test_id_3","msg_sn":"2","event":"stop","answer_text":""}}
具体每一个数据包的数据包格式化后是这个样子:
{ "ret":"0", "msg":"", "data":{ "msg_id": "test_id", "msg_sn": "1", "event": "stop", "answer_text": "你", "ref_doc_list": [], "follow_up_list": [], "illegal_status": "0", "extend_info": { "ialgo_extend_info": { "return_type": "intervention_return", "doc_score": [ { "doc_id": "", "score": "1.233" }, // 其他文档分值信息 ] } }, } }
参数 | 类型 | 参数说明 |
---|---|---|
ret | string | 结果代码,字符串类型,成功是 0,失败是非 0,具体错误详见附录以及具体接口说明。 |
msg | string | 错误信息,如果成功是空串。 |
data | object | 业务参数信息对象。 |
msg_id | string | 每个分包的唯一 id,每个分包 id 都是全局唯一的 |
msg_sn | string | 每个分包的序号,从 0 开始,每个包 +1 |
event | string | 当前流式分包的 event,event=为空串表示中间包,event=stop 表示最后一个包 |
answer_text | string | 与非流式接口的同名参数一致。每个引文的文档 id 块和 follow up 块会整体返回,不会被拆分成多个包返回。 |
ref_doc_list | array | 文档引用列表,与非流式应答的同名参数一致。 |
follow_up_list | array | 推荐问题列表,与非流式应答的同名参数一致。 |
extend_info | jsonobject | 扩展信息字段,与非流式应答的同名参数一致。 |
前端使用流式应答API
如果是JavaScript前端使用SSE,我们推荐微软的库:FetchEventSource。 这个库封装得很好,下面是一个使用的示例:
import { EventSourceMessage, fetchEventSource } from '@microsoft/fetch-event-source'; fetchEventSource(url,{ method: 'POST', headers, body: JSON.stringify(d), openWhenHidden:true, onmessage:(ev:EventSourceMessage) => { try { const res = JSON.parse(ev.data); console.log('【SSE】',res); const {ret,data,msg} = res; if (ret === '0') { if (data.ref_doc_list && data.ref_doc_list.length > 0) { doc_list = doc_list.concat(data.ref_doc_list) } data.ref_doc_list = doc_list; result += data.answer_text; onMessage && onMessage(result,data); } else { const error = getErrorInfo(ret,msg); if (result) { result += "\n"+error } else { result = error; } } } catch (error) { console.log('【chat-error】',error); } }, async onopen(response) { console.log('SEE-open',response.status); if (response.status === 200) { return; } }, onclose() { console.log('SEE-close'); return; }, onerror(err) { console.log('SEE-e',err); } }).then(()=>{ console.log('最终答案',result); return { text:result, ref_doc_list:doc_list, } }).catch(e=>{ console.log('query-catch',e); return { text:result, ref_doc_list:doc_list, } })