UNPKG

doomiaichat

Version:

Doomisoft OpenAI

358 lines (353 loc) 17.4 kB
/** * 扣子智能体 */ import { CozeAPI, COZE_CN_BASE_URL, ChatEventType, RoleType, StreamChatReq, EnterMessage, // ChatWorkflowReq, // CreateChatReq, // ChatStatus, VariableUpdateReq, VariableRetrieveReq, CreateChatReq, RunWorkflowReq, RunWorkflowData, CreateChatData, ChatStatus, WorkflowEventType, ChatWorkflowReq } from '@coze/api'; import GptBase from "./gptbase" import { CorzAuthorization } from './corzauthorization'; import { parse } from 'querystring' import { ApiResult } from './declare'; // 定义深度思考的动作标签 // 这是小鹭里面专用的几个思考动作标签 const DeepThinkingAction: any = { thinking: { start: '<xiaolu-think>', end: '</xiaolu-think>', tag: 'xiaolu-think' }, reasoning: { start: '<xiaolu-reason>', end: '</xiaolu-reason>', tag: 'xiaolu-reason' }, card: { start: '<xiaolu-card>', end: '</xiaolu-card>', tag: 'xiaolu-card' }, } // 定义深度思考的状态 enum DeepThinkingStatus { None, Thinking, // ContentOutput, ThinkingOver } // 智能体思考时输出的动作 type CardType = { type: number, tag: string } // type TThinkingMessage = { action: string, textposition: number } export default class CorzBot extends GptBase { private botid: string | null = null; // 智能体id private workflowid: string | null = null; // 工作流id private talkflowid: string | null = null; // 对话流id private apiKey: string; /** * * @param apikey 调用AI中台 的key * @param botid 智能体信息 */ constructor(private authorizationProvider: CorzAuthorization, private setting: any = {}) { super(); ////初始化扣子客户端 if (this.setting.workflowid) this.workflowid = this.setting.workflowid; else if (this.setting.talkflowid) this.talkflowid = this.setting.talkflowid; else if (this.setting.botid || this.setting.botID) this.botid = this.setting.botid || this.setting.botID; else throw new Error('no botid or talkflowid or workflowid setting for coze'); this.apiKey = this.setting['apiKey']; } private async createClient(): Promise<CozeAPI> { // 存在AppKey则使用AppKey进行认证 if (this.apiKey) return new CozeAPI({ baseURL: COZE_CN_BASE_URL, token: this.apiKey }); const accessToken = await this.authorizationProvider.getAccessToken(); if (!accessToken) throw new Error('get access token failed'); return new CozeAPI({ baseURL: COZE_CN_BASE_URL, token: accessToken }); } /** * 发起一次会话 */ override async createCoversation(client?: CozeAPI): Promise<string | null> { try { const czApi = client ?? await this.createClient(); const params = this.botid ? { bot_id: this.botid } : {}; const result = await czApi.conversations.create(params) return result.id; } catch (error) { console.error('createCoversation error in coze api'); return null; } } /** * 设置Coze的变量 * @param params * @returns */ override async setVariables(params: any): Promise<ApiResult> { const client = await this.createClient(); const cozeParams: VariableUpdateReq = Object.assign({}, this.botid ? { bot_id: this.botid } : {}, params); client.variables.update(cozeParams) return { successed: true }; } /** * 获取设置的变量 * @returns */ override async getVariables(params: any): Promise<any> { const client = await this.createClient(); const cozeParams: VariableRetrieveReq = Object.assign({}, { bot_id: this.botid }, params); const data = await client.variables.retrieve(cozeParams); if (data) return { successed: true, ...data } return { successed: false, error: 'get variables failed' }; } /** * 组装请求参数 * @param message * @param callChatOption * @param attach * @param _axiosOption */ async getRequestStream<T>(client: CozeAPI, message: EnterMessage[], callChatOption: any = {}): Promise<T> { //简单的对话请求 const conversation_id = callChatOption.session_id ?? await this.createCoversation(client); if (this.botid) { const req: StreamChatReq = { bot_id: this.botid, additional_messages: message, user_id: callChatOption.userid || callChatOption.cozeUserID, conversation_id, parameters: Object.assign({ request_src:1 }, callChatOption.parameters || {}), } req.custom_variables = Object.assign({}, this.setting.customVariables || {}, callChatOption.customVariables || {}); return req as T; } if (this.workflowid){ const worflowreq: RunWorkflowReq = { ext: callChatOption.ext, workflow_id: this.workflowid,//callChatOption.workflowid, is_async: false, // parameters: Object.assign({ input: message[0]?.content }, this.setting.customVariables || {}, callChatOption.customVariables || {}), parameters: Object.assign({ request_src :1},callChatOption.parameters || {}, { input: message[0]?.content}) } return worflowreq as T; } const worflowreq: ChatWorkflowReq = { additional_messages: message, ext: callChatOption.ext, workflow_id: this.talkflowid!,//callChatOption.workflowid, conversation_id, parameters: Object.assign({request_src:1},callChatOption.parameters || {}) //Object.assign({}, this.setting.customVariables || {}, callChatOption.customVariables || {}), //parameters: { input: message[0]?.content } } return worflowreq as T; } /** * 同步调用的问答请求需要获取会话详情 * @param chatid * @param conversation_id */ private async getChatDetail(client: CozeAPI, conversation_id: string, chatid: string) { const maxWaitTime = 120000; // 最大等待120秒 const startTime = Date.now(); while ((Date.now() - startTime) < maxWaitTime) { const chatinfo = await client.chat.retrieve(conversation_id, chatid); // 状态已经是完成了,可以去获取对话的内容了 if (chatinfo.status === ChatStatus.COMPLETED) return { usage: chatinfo.usage, messages: await client.chat.messages.list(conversation_id, chatid) }; await new Promise(resolve => setTimeout(resolve, 1500)); // 等待1500ms } return null; } /** * 非流式传输聊天请求 * @param chatText * @param callChatOption * @param _axiosOption */ public async chatRequest(message: any[] | string, callChatOption: any = {}, _axiosOption: any = {}): Promise<any> { if (!message) this.emit('chaterror', { successed: false, error: 'no message in chat' }); ///如果是字符,则组装成API需要的消息 if (typeof message === 'string') message = [ { role: RoleType.User, content_type: 'text', content: message } ]; const client = await this.createClient(); ////如果参数中用的是Workflow,则调用对话流来输出结果 ////否则用智能体的对话来输出结果 const params = await this.getRequestStream(client, message, callChatOption); const response = this.botid ? await client.chat.create(params as CreateChatReq) :await client.workflows.runs.create(params as RunWorkflowReq); if (this.workflowid && (response as RunWorkflowData).msg === 'Success') { const resp = (response as RunWorkflowData).data; return { successed: true, message: [{ role: 'assistant', type: 'answer', content: resp }] }; // try { // return { successed: true, message: [{ role: 'assistant', type: 'answer', content: JSON.parse(resp).data }] }; // } catch (error) { // return { successed: true, message: [{ role: 'assistant', type: 'answer', content: resp }] }; // } } if (!this.workflowid && (response as CreateChatData).conversation_id && (response as CreateChatData).id) { const ccd = response as CreateChatData; const chatData = await this.getChatDetail(client, ccd.conversation_id, ccd.id); if (chatData) { const message = chatData.messages.filter(x => x.type === 'answer').map(item => ({ role: item.role, type: item.type, content: item.content, })); return { successed: true, message, usage: chatData.usage, session_id: ccd.conversation_id }; } } return { successed: false, error: { message: '聊天未完成' } }; } /** * 提取XML标签中间的内容, * @param xmlStr * @param tagName * @returns */ extractXmlContent(xmlStr: string, tagName: string): string { // 构建匹配标签的正则(支持任意空白字符和大小写) const regex = new RegExp(`<\\s*${tagName}\\s*>([\\s\\S]*?)<\\s*\\/\\s*${tagName}\\s*>`, 'i'); const match = xmlStr.match(regex); if (match && match[1]) return match[1].trim(); return ''; // 未找到标签或内容 } /** * 解析深度思考的JSON内容 * @param content * @param status * @returns */ private parseDeepThinkingJson(content: string) { // const thinkingStartIndex = status === DeepThinkingStatus.Thinking ? content.indexOf("{\"process_msg") : // (status===DeepThinkingStatus.ReasonOutput ? content.indexOf("{\"reasoning_content") : content.indexOf("{\"card_resource")) ; const xmlTagLocation = [content.indexOf(DeepThinkingAction.thinking.start), content.indexOf(DeepThinkingAction.reasoning.start), content.indexOf(DeepThinkingAction.card.start)]; let minLocation = 10000, minIndex = -1; // 找到最小的并且大于0 的位置的下标 xmlTagLocation.forEach((x, index) => { if (x >= 0 && x < minLocation) { minLocation = x; minIndex = index; } }); // const tagLocation = xmlTagLocation.filter(x => x >= 0); const thinkingStartIndex = minIndex >= 0 ? minLocation : -1; //tagLocation.length > 0 ? Math.min(...tagLocation) : -1; if (thinkingStartIndex < 0) return { content, status: DeepThinkingStatus.ThinkingOver }; const currentAction = [DeepThinkingAction.thinking, DeepThinkingAction.reasoning, DeepThinkingAction.card][minIndex]; const currentActionIsOver = content.indexOf(currentAction.end, thinkingStartIndex); const thinkingEndIndex = currentActionIsOver >= 0 ? currentActionIsOver: content.indexOf('</', thinkingStartIndex); const thinkingContent = this.extractXmlContent(content.substring(thinkingStartIndex, thinkingEndIndex >= 0 ? thinkingEndIndex : undefined) + currentAction.end, currentAction.tag); //"\"}"; if (currentActionIsOver >= 0) content = content.substring(currentActionIsOver + currentAction.end.length); return { thinking: { action: currentAction, text: thinkingContent,//Object.values(thinkingObject)[0] completed: currentActionIsOver >= 0 }, content}; } /** * 流式传输聊天请求 * @param chatText * @param callChatOption * @param attach * @param _axiosOption * @returns */ override async chatRequestInStream(message: any[] | string, callChatOption: any = {}, attach?: any, _axiosOption?: any): Promise<any> { if (!message) this.emit('chaterror', { successed: false, error: 'no message in chat' }); ///如果是字符,则组装成API需要的消息 if (typeof message === 'string') message = [ { role: RoleType.User, content_type: 'text', content: message } ]; const client = await this.createClient(); ////如果参数中用的是Workflow,则调用对话流来输出结果 ////否则用智能体的对话来输出结果 let requestid = Math.ceil(Math.random() * (new Date().getTime() * Math.random()) / 1000), index = 0; const params = await this.getRequestStream(client, message, callChatOption); const stream = this.botid ? client.chat.stream(params as StreamChatReq) : (this.workflowid ? client.workflows.runs.stream(params as RunWorkflowReq) : client.workflows.chat.stream(params as ChatWorkflowReq)) ; let deltaindex = 0, fullanswer: string[] = [], followup: string[] = [], cardData: CardType[] = []; let deepThinking = '', thinkingStatus = DeepThinkingStatus.None, cardResource = ''; // 是否在深度思考中 for await (const part of stream) { if (part.event === ChatEventType.ERROR) return this.emit('chaterror', { successed: false, error: 'call failed' }); if (part.event === ChatEventType.CONVERSATION_MESSAGE_DELTA || part.event === WorkflowEventType.MESSAGE ) { let { conversation_id, content_type, type = 'answer', role = 'assistant', content, reasoning_content: reasoning } = part.data as any; if (!content&& reasoning) { this.emit('chatthinking', { text: reasoning, completed: false, action: 'deep-thinking' }); continue; } // 如果存在深度思考,则一开始就会带有相关的关键信息 if (deltaindex === 0 && content.startsWith("<xiaolu-")) { thinkingStatus = DeepThinkingStatus.Thinking; deltaindex++; } // 如果在深度思考中,则不输出消息 if (thinkingStatus === DeepThinkingStatus.Thinking) { deepThinking += content; const result = this.parseDeepThinkingJson(deepThinking) const thinking = result.thinking; if (thinking) { deepThinking = result.content; if (thinking.action === DeepThinkingAction.card) { cardResource += result.thinking.text; // 卡片流结束,解析卡片资源数据 if (result.thinking.completed) { const allCards = cardResource.replace(/[\x00-\x1F\x7F]/g, '').split('|') for (const item of allCards) { const cardinfo: any = parse(item); if (cardinfo.type && cardinfo.tag) cardData.push({ ...cardinfo }) } // 将卡片资源返回给客户端 this.emit('chatcard', cardData); } } else { this.emit('chatthinking', { text: result.thinking.text, completed: result.thinking.completed, action: thinking.action?.tag }); } } if (result.status != DeepThinkingStatus.ThinkingOver) continue; thinkingStatus = DeepThinkingStatus.ThinkingOver; // 将排除了thinking之后的消息内容要带下去成为正式的输出内容 content = deepThinking; } fullanswer.push(content); let output = { successed: true, type, content_type, role, requestid, segment: content, text: fullanswer.join(''), index: index++, session_id: conversation_id }; if (attach) output = Object.assign({}, output, attach); this.emit('chattext', output) } ////在流式传输中,提取相关推荐问题 if (part.event === ChatEventType.CONVERSATION_MESSAGE_COMPLETED) { const { type, content } = part.data ?? {}; if (type === 'follow_up') followup.push(content); } ///整个对话结束 if (part.event === ChatEventType.CONVERSATION_CHAT_COMPLETED || part.event === WorkflowEventType.DONE ) { const { conversation_id, content } =( part.data )?? {} as any; let output = { successed: true, cards: cardData.length ? cardData : null, followup, type: 'answer', content_type: 'text', role: RoleType.Assistant, requestid, segment: null, text: content??fullanswer.join(''), index: index++, session_id: conversation_id }; if (attach) output = Object.assign({}, output, attach); this.emit('chatdone', output) } } return { successed: true, requestid } } }