Files
agent/app/service/cozeService.ts
zch1234qq 4afe6529fb
Some checks failed
Docker Build and Deploy / build-and-push (push) Has been cancelled
Docker Build and Deploy / deploy-hk (push) Has been cancelled
qwe
2025-10-21 09:17:52 +08:00

261 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { Message } from '../types/types';
import { CozeAPI } from '@coze/api';
// 定义Coze事件数据接口
interface DeltaEventData {
content?: string;
delta?: { content?: string };
}
interface CompletedEventData {
conversation_id?: string;
usage?: { token_count?: number };
token_count?: number;
messages?: { content?: string }[];
}
// 错误事件数据接口 - 备用
// interface ErrorEventData {
// code?: string;
// message?: string;
// type?: string;
// }
class CozeService {
private apiKey: string | null = null;
private botId: string | null = null;
private userId: string = `user_${Date.now()}`; // 生成一个临时用户ID
private apiClient: CozeAPI | null = null;
private conversationId: string | null = null; // 存储对话ID用于后续调用
constructor() {
// 初始化服务实际使用时需要设置API密钥和Bot ID
}
/**
* 设置API密钥和Bot ID
* @param apiKey Coze API密钥
* @param botId Bot ID
*/
setCredentials(apiKey: string, botId: string): void {
this.apiKey = apiKey;
this.botId = botId;
// 初始化Coze API客户端 - 严格按照官方示例实现
this.apiClient = new CozeAPI({
token: apiKey,
baseURL: 'https://api.coze.cn'
});
console.log('Coze API client initialized with official SDK');
}
/**
* 发送消息给Coze agent
* 使用官方SDK的stream方法实现流式响应
* 严格按照api.ts示例实现使用for await...of循环处理流式响应
* @param message 用户消息内容
* @param onStream 流式响应回调函数
* @returns Promise<Message> 完整的响应消息
*/
async sendMessage(
message: string,
onStream?: (partialText: string, isDone: boolean) => void
): Promise<Message> {
// 检查初始化状态
if (!this.isInitialized()) {
console.warn('Coze服务尚未初始化或凭据不完整。使用模拟回复。');
return this.getMockResponse(message);
}
try {
console.log('正在使用官方SDK发送消息到Coze服务...');
console.log('Bot ID:', this.botId);
console.log('User ID:', this.userId);
// 严格按照api.ts示例调用chat.stream方法
const res = await this.apiClient!.chat.stream({
bot_id: this.botId!,
user_id: this.userId,
conversation_id: this.conversationId || undefined, // 如果有保存的对话ID则使用
additional_messages: [
{
"content": message,
"content_type": "text",
"role": "user",
"type": "question"
}
] as any // eslint-disable-line @typescript-eslint/no-explicit-any,
});
let fullContent = '';
const messageId = Date.now().toString();
console.log('开始处理Coze流式响应...');
// 严格按照api.ts的实现方式使用for await...of循环处理流式响应
for await (const event of res) {
try {
console.log('接收到Coze事件:', event.event || 'unknown');
// 1. 处理消息增量事件
if (event.event === 'conversation.message.delta') {
// 根据TypeScript类型定义content可能直接在data上
const deltaData = event.data as DeltaEventData;
if (deltaData && deltaData.content) {
const deltaContent = deltaData.content;
fullContent += deltaContent;
console.log('增量内容:', fullContent.substring(0, 100) + '...');
// 通知流式更新
if (onStream) {
onStream(fullContent, false);
}
} else if (deltaData && deltaData.delta && deltaData.delta.content) {
// 兼容另一种可能的数据结构
const deltaContent = deltaData.delta.content;
fullContent += deltaContent;
console.log('增量内容(delta路径):', fullContent.substring(0, 100) + '...');
if (onStream) {
onStream(fullContent, false);
}
}
}
// 2. 处理聊天完成事件
if (event.event === 'conversation.chat.completed') {
console.log(); // 输出换行
// 尝试从data中获取conversation_id和usage信息
const dataObj = event.data as CompletedEventData;
// 打印conversation_id并保存
if (dataObj && dataObj.conversation_id) {
console.log('Conversation ID:', dataObj.conversation_id);
this.conversationId = dataObj.conversation_id; // 保存对话ID供下次使用
}
// 打印token使用量
if (dataObj && dataObj.usage && dataObj.usage.token_count) {
console.log('token usage:', dataObj.usage.token_count);
} else if (dataObj && dataObj.token_count) {
console.log('token usage:', dataObj.token_count);
}
// 尝试从data中获取完整内容如果有的话
if (dataObj && dataObj.messages && dataObj.messages[0] && dataObj.messages[0].content) {
fullContent = dataObj.messages[0].content || '';
console.log('使用完成事件中的完整内容');
}
}
// 3. 处理错误事件
if (event.event === 'error') {
console.error('错误事件:', event.data);
throw new Error(`Coze服务错误: ${JSON.stringify(event.data)}`);
}
} catch (err) {
console.error('处理事件时出错:', err);
console.log('错误事件完整数据:', JSON.stringify(event, null, 2));
}
}
// 流式处理完成,检查是否有内容
console.log('Coze流式响应处理完成总内容长度:', fullContent.length);
if (fullContent.trim()) {
const botMessage: Message = {
id: messageId,
content: fullContent,
role: 'assistant',
content_type: 'text',
timestamp: new Date()
};
// 通知流式更新完成
if (onStream) {
onStream(fullContent, true);
}
return botMessage;
} else {
throw new Error('Coze服务返回空内容请检查API配置和网络连接');
}
} catch (error) {
console.error('Error sending message to Coze with official SDK:', error);
// 获取具体的错误信息
const errorMsg = error instanceof Error ? error.message : '未知错误';
// 即使出错也返回模拟回复
return this.getMockResponse(message, errorMsg);
}
}
/**
* 获取模拟回复当Coze API调用失败时使用
*/
private getMockResponse(message: string, error?: string): Message {
let mockContent = '';
// 根据用户消息内容生成更有针对性的模拟回复
if (message.includes('你好') || message.includes('hello') || message.includes('hi')) {
mockContent = '你好!我是你的爆款文案策划师。为了给你提供更有针对性的方案,请告诉我:\n1. 你的行业/产品/服务\n2. 你的目标用户群体\n有了这些信息我就能帮你打造更精准的文案了';
} else if (message.includes('文案') || message.includes('内容') || message.includes('写作')) {
mockContent = '关于文案创作,我有一些专业建议:\n1. 明确目标受众和核心卖点\n2. 使用吸引人的标题和开头\n3. 突出产品/服务的独特价值\n4. 加入社会证明(案例、数据)\n5. 包含明确的行动号召\n需要针对特定行业的文案模板吗';
} else if (message.includes('价格') || message.includes('费用') || message.includes('多少钱')) {
mockContent = '文案服务的价格因需求复杂度而异。我们提供:\n- 基础文案套餐¥500-1000\n- 营销策划套餐¥1000-3000\n- 品牌全案套餐¥3000+\n欢迎告诉我你的具体需求我可以为你提供更精准的报价。';
} else {
mockContent = '感谢你的提问!作为文案策划师,我可以帮你:\n- 撰写吸引人的营销文案\n- 设计爆款标题\n- 优化产品描述\n- 制定内容营销策略\n请告诉我你具体需要哪方面的帮助';
}
// 如果有错误信息,可以选择性地在开头添加提示
if (error) {
console.log('使用模拟回复,原因:', error);
// 不直接显示错误,只显示友好提示
mockContent = '【提示:当前使用的是智能助手的推荐回复】\n\n' + mockContent;
}
return {
id: Date.now().toString(),
content: mockContent,
role: 'assistant',
content_type: 'text',
timestamp: new Date()
};
}
/**
* 重置对话状态
*/
resetConversation(): void {
this.conversationId = null; // 清除保存的对话ID
console.log('Conversation reset');
}
/**
* 检查客户端是否已初始化
*/
isInitialized(): boolean {
return !!this.apiClient && !!this.apiKey && !!this.botId;
}
/**
* 设置用户ID
*/
setUserId(userId: string): void {
this.userId = userId;
}
/**
* 获取用户ID
*/
getUserId(): string {
return this.userId;
}
}
// 导出单例实例
// 创建并导出单例实例
const cozeServiceInstance = new CozeService();
export default cozeServiceInstance;