From 2c601c1693e76f0eaf27f0eb2bdd18badc25e9a9 Mon Sep 17 00:00:00 2001 From: pftom <1043269994@qq.com> Date: Fri, 21 Feb 2025 10:44:51 +0800 Subject: [PATCH 1/6] style(canvas): update edge label styling with transparent background --- .../src/components/canvas/edges/custom-edge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ai-workspace-common/src/components/canvas/edges/custom-edge.tsx b/packages/ai-workspace-common/src/components/canvas/edges/custom-edge.tsx index 5c5e99693..2af66223e 100644 --- a/packages/ai-workspace-common/src/components/canvas/edges/custom-edge.tsx +++ b/packages/ai-workspace-common/src/components/canvas/edges/custom-edge.tsx @@ -108,7 +108,7 @@ export const CustomEdge = memo( /> ) : ( label && ( -
+
{label}
) From b1ce9b294fb4495f766e522c8a51e608c40e75b9 Mon Sep 17 00:00:00 2001 From: pftom <1043269994@qq.com> Date: Fri, 21 Feb 2025 17:45:06 +0800 Subject: [PATCH 2/6] feat(skills): enhance source data handling and chunking for large search results - Implement chunked source data emission in web search skill - Add support for partial source data transmission in invoke action hook - Update SSE post utility to improve error handling and logging - Add translations for generate answer step in i18n - Create mock sources for testing and development --- .../src/hooks/canvas/use-invoke-action.ts | 33 +- .../ai-workspace-common/src/utils/sse-post.ts | 5 +- packages/i18n/src/en-US/skill-log.ts | 4 + packages/i18n/src/zh-Hans/skill-log.ts | 4 + packages/skill-template/src/base.ts | 70 +++ .../module/multiLingualSearch/index.ts | 2 +- .../src/scheduler/utils/message.ts | 16 +- .../skill-template/src/skills/mock-sources.ts | 529 ++++++++++++++++++ .../skill-template/src/skills/web-search.ts | 24 +- 9 files changed, 670 insertions(+), 17 deletions(-) create mode 100644 packages/skill-template/src/skills/mock-sources.ts diff --git a/packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts b/packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts index 37b899c39..f4c643fe4 100644 --- a/packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts +++ b/packages/ai-workspace-common/src/hooks/canvas/use-invoke-action.ts @@ -141,10 +141,35 @@ export const useInvokeAction = () => { } const updatedStep: ActionStep = findOrCreateStep(result.steps ?? [], step); - updatedStep.structuredData = { - ...updatedStep.structuredData, - ...structuredData, - }; + + // Handle chunked sources data + if (structuredData.sources && Array.isArray(structuredData.sources)) { + const existingData = updatedStep.structuredData || {}; + const existingSources = (existingData.sources || []) as any[]; + + // If this is a chunk of sources, merge it with existing sources + if (structuredData.isPartial !== undefined) { + updatedStep.structuredData = { + ...existingData, + sources: [...existingSources, ...structuredData.sources], + isPartial: structuredData.isPartial, + chunkIndex: structuredData.chunkIndex, + totalChunks: structuredData.totalChunks, + }; + } else { + // Handle non-chunked data as before + updatedStep.structuredData = { + ...existingData, + ...structuredData, + }; + } + } else { + // Handle non-sources structured data + updatedStep.structuredData = { + ...updatedStep.structuredData, + ...structuredData, + }; + } const updatedResult = { ...result, diff --git a/packages/ai-workspace-common/src/utils/sse-post.ts b/packages/ai-workspace-common/src/utils/sse-post.ts index 6e2ed2143..117c43e01 100644 --- a/packages/ai-workspace-common/src/utils/sse-post.ts +++ b/packages/ai-workspace-common/src/utils/sse-post.ts @@ -68,7 +68,7 @@ export const ssePost = async ({ try { const response = await makeSSERequest(payload, controller); - const baseResp = await extractBaseResp(response); + const baseResp = await extractBaseResp(response, { success: true }); if (!baseResp.success) { onSkillError?.({ error: baseResp, event: 'error' }); return; @@ -107,7 +107,7 @@ export const ssePost = async ({ message: message.substring(6), error: err, }); - return; + // return; } if (skillEvent?.event === 'start') { @@ -141,6 +141,7 @@ export const ssePost = async ({ bufferStr = lines[lines.length - 1]; } catch (err) { + console.log('actual err', err); onSkillError(err); onCompleted?.(true); hasError = true; diff --git a/packages/i18n/src/en-US/skill-log.ts b/packages/i18n/src/en-US/skill-log.ts index f30ae3687..3e43c6441 100644 --- a/packages/i18n/src/en-US/skill-log.ts +++ b/packages/i18n/src/en-US/skill-log.ts @@ -27,6 +27,10 @@ const translations = { title: 'Select Related Results', description: 'Total of {{totalResults}} results, completed in {{duration}}ms', }, + generateAnswer: { + title: 'Generate Answer', + description: 'Start to generate answer...', + }, }; export default translations; diff --git a/packages/i18n/src/zh-Hans/skill-log.ts b/packages/i18n/src/zh-Hans/skill-log.ts index 5f492779b..183c840fe 100644 --- a/packages/i18n/src/zh-Hans/skill-log.ts +++ b/packages/i18n/src/zh-Hans/skill-log.ts @@ -27,6 +27,10 @@ const translations = { title: '选择关联结果', description: '总共 {{totalResults}} 个结果, 耗时 {{duration}} 毫秒', }, + generateAnswer: { + title: '生成答案', + description: '开始生成答案...', + }, }; export default translations; diff --git a/packages/skill-template/src/base.ts b/packages/skill-template/src/base.ts index 9c460f576..d00684ff9 100644 --- a/packages/skill-template/src/base.ts +++ b/packages/skill-template/src/base.ts @@ -87,6 +87,76 @@ export abstract class BaseSkill extends StructuredTool { emitter.emit(eventData.event, eventData); } + /** + * Emit large data in chunks with delay to prevent overwhelming the event system + * @param data The data to emit + * @param config The skill runnable config + * @param options Options for chunking and delay + */ + async emitLargeDataEvent( + data: { + event?: string; + data: T[]; + buildEventData: ( + chunk: T[], + meta: { isPartial: boolean; chunkIndex: number; totalChunks: number }, + ) => Partial; + }, + config: SkillRunnableConfig, + options: { + maxChunkSize?: number; + delayBetweenChunks?: number; + } = {}, + ): Promise { + const { maxChunkSize = 500, delayBetweenChunks = 10 } = options; + + // If no data or emitter, return early + if (!data.data?.length || !config?.configurable?.emitter) { + return; + } + + // Split data into chunks based on size + const chunks: T[][] = []; + let currentChunk: T[] = []; + let currentSize = 0; + + for (const item of data.data) { + const itemSize = JSON.stringify(item).length; + + if (currentSize + itemSize > maxChunkSize && currentChunk.length > 0) { + chunks.push(currentChunk); + currentChunk = []; + currentSize = 0; + } + + currentChunk.push(item); + currentSize += itemSize; + } + + // Push the last chunk if not empty + if (currentChunk.length > 0) { + chunks.push(currentChunk); + } + + // Emit chunks with delay + const emitPromises = chunks.map( + (chunk, i) => + new Promise((resolve) => { + setTimeout(() => { + const eventData = data.buildEventData(chunk, { + isPartial: i < chunks.length - 1, + chunkIndex: i, + totalChunks: chunks.length, + }); + this.emitEvent(eventData, config); + resolve(); + }, i * delayBetweenChunks); + }), + ); + + await Promise.all(emitPromises); + } + async _call( input: typeof this.graphState, _runManager?: CallbackManagerForToolRun, diff --git a/packages/skill-template/src/scheduler/module/multiLingualSearch/index.ts b/packages/skill-template/src/scheduler/module/multiLingualSearch/index.ts index 1037350ec..7e22a6e97 100644 --- a/packages/skill-template/src/scheduler/module/multiLingualSearch/index.ts +++ b/packages/skill-template/src/scheduler/module/multiLingualSearch/index.ts @@ -335,7 +335,7 @@ export const callMultiLingualWebSearch = async ( // Keep original results if deduplication fails } - ctxThis.emitEvent({ structuredData: { multiLingualSearchResult: finalResults } }, config); + // ctxThis.emitEvent({ structuredData: { multiLingualSearchResult: finalResults } }, config); // Return results with analysis return { diff --git a/packages/skill-template/src/scheduler/utils/message.ts b/packages/skill-template/src/scheduler/utils/message.ts index 1964866d0..bbdd9bbb5 100644 --- a/packages/skill-template/src/scheduler/utils/message.ts +++ b/packages/skill-template/src/scheduler/utils/message.ts @@ -46,12 +46,16 @@ export const buildFinalRequestMessages = ({ ...chatHistory, ...messages, ...contextMessages, - new HumanMessage({ - content: [ - { type: 'text', text: userPrompt }, - ...(images?.map((image) => ({ type: 'image_url', image_url: { url: image } })) || []), - ], - }), + new HumanMessage( + images?.length + ? { + content: [ + { type: 'text', text: userPrompt }, + ...(images.map((image) => ({ type: 'image_url', image_url: { url: image } })) || []), + ], + } + : userPrompt, + ), ]; return requestMessages; diff --git a/packages/skill-template/src/skills/mock-sources.ts b/packages/skill-template/src/skills/mock-sources.ts new file mode 100644 index 000000000..047196b23 --- /dev/null +++ b/packages/skill-template/src/skills/mock-sources.ts @@ -0,0 +1,529 @@ +export const mockSources = [ + { + url: 'https://www.threads.net/@yushengliin/post/DFOii6fTca-', + title: 'refly:高效智能的AI內容創作工具 - Threads', + pageContent: + 'refly 是一款集搜索、筆記、思維導圖與AI寫作為一體的超級工具,幫助用戶將想法高效轉化為文字,從靈感捕捉到內容創作,提供全方位的支持,讓創作過程更加流暢 ...', + metadata: { + originalLocale: 'en', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.threads.net/@yushengliin/post/DFOii6fTca-', + score: 0.8933094143867493, + source: 'https://www.threads.net/@yushengliin/post/DFOii6fTca-', + title: 'refly:高效智能的AI內容創作工具 - Threads', + sourceType: 'webSearch', + }, + }, + { + url: 'https://www.cnblogs.com/Agora/p/18671366', + title: 'Kyutai开源端侧模型Helium -1 preview;FoloToy内测「超级智能体」', + pageContent: + 'Refly 是一个基于自由画布的AI 原生创作引擎,旨在通过多线程对话、知识库集成、上下文记忆和智能搜索技术,帮助用户将创意转化为高质量内容。 该平台覆盖了 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.cnblogs.com/Agora/p/18671366', + score: 0.8723474144935608, + source: 'https://www.cnblogs.com/Agora/p/18671366', + title: 'Kyutai开源端侧模型Helium -1 preview;FoloToy内测「超级智能体」', + sourceType: 'webSearch', + }, + }, + { + url: 'https://www.aisharenet.com/refly-zhengshikaifangben/', + title: 'Refly 正式开放注册,文字创作者的最佳工作平台 - 首席AI分享圈', + pageContent: + 'Refly 是一个基于「 自由画布 」理念构建的AI 原生内容创作平台,通过多线程对话、知识库整合、上下文记忆、智能搜索与可见即可得的AI 文档编辑器,为用户 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.aisharenet.com/refly-zhengshikaifangben/', + score: 0.8714748620986938, + source: 'https://www.aisharenet.com/refly-zhengshikaifangben/', + title: 'Refly 正式开放注册,文字创作者的最佳工作平台 - 首席AI分享圈', + sourceType: 'webSearch', + }, + }, + { + url: 'https://www.163.com/dy/article/JMOURKU605567BLV.html', + title: 'Refly 是一个开源的AI..._手机网易网', + pageContent: + 'Refly是一个开源的AI原生创作引擎,提供了一个直观的自由格式画布界面,集成了多线程对话、AI知识库集成、上下文记忆、智能搜索、所见即所得的AI编辑器 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.163.com/dy/article/JMOURKU605567BLV.html', + score: 0.8688268065452576, + source: 'https://www.163.com/dy/article/JMOURKU605567BLV.html', + title: 'Refly 是一个开源的AI..._手机网易网', + sourceType: 'webSearch', + }, + }, + { + url: 'https://top.aibase.com/tool/refly', + title: 'Refly使用入口地址Ai网站最新工具和软件app下载 - AIbase', + pageContent: + 'Refly是一个AI Native创作引擎,通过多线程对话、知识库整合、上下文记忆和智能搜索等技术,帮助用户将创意转化为优质内容。 它覆盖了学术研究、技术文档等20+专业场景模板, ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://top.aibase.com/tool/refly', + score: 0.8679338097572327, + source: 'https://top.aibase.com/tool/refly', + title: 'Refly使用入口地址Ai网站最新工具和软件app下载 - AIbase', + sourceType: 'webSearch', + }, + }, + { + url: 'https://c.m.163.com/news/a/JMOURKU605567BLV.html', + title: 'Refly 是一个开源的AI... - 网易新闻', + pageContent: + 'Refly 是一个开源的AI 原生创作引擎,提供了一个直观的自由格式画布界面,集成了多线程对话、AI 知识库集成、上下文记忆、智能搜索、所见即所得的AI ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://c.m.163.com/news/a/JMOURKU605567BLV.html', + score: 0.8652240633964539, + source: 'https://c.m.163.com/news/a/JMOURKU605567BLV.html', + title: 'Refly 是一个开源的AI... - 网易新闻', + sourceType: 'webSearch', + }, + }, + { + url: 'https://www.aibase.com/zh/tool/35128', + title: 'Refly-基于自由画布的创作平台 - AIbase', + pageContent: + 'Refly是一个AI Native创作引擎,通过多线程对话、知识库整合、上下文记忆和智能搜索等技术,帮助用户将创意转化为优质内容。它覆盖了学术研究、技术文档等20+专业场景 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.aibase.com/zh/tool/35128', + score: 0.8643104434013367, + source: 'https://www.aibase.com/zh/tool/35128', + title: 'Refly-基于自由画布的创作平台 - AIbase', + sourceType: 'webSearch', + }, + }, + { + url: 'https://apps.shopify.com/refly?locale=zh-CN', + title: 'Refly: AI-Powered Multi-Channel Customer Service for Shopify', + pageContent: + 'Refly是一个AI驱动的工具,可以在多个渠道(包括Shopify、聊天小部件等)自动化客户服务。 适用于任何规模的企业,Refly简化了客户互动管理,自动化常规任务,并提供个性化 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://apps.shopify.com/refly?locale=zh-CN', + score: 0.8558511734008789, + source: 'https://apps.shopify.com/refly?locale=zh-CN', + title: 'Refly: AI-Powered Multi-Channel Customer Service for Shopify', + sourceType: 'webSearch', + }, + }, + { + url: 'https://github.com/refly-ai/refly/blob/main/README_CN.md', + title: 'refly/README_CN.md at main · refly-ai/refly - GitHub', + pageContent: + 'Refly 是一个开源的AI 原生创作引擎。Refly 直观的自由画布界面集成了多线程对话、RAG 检索流程、上下文记忆、智能搜索和AI 文档编辑等功能,让您轻松地将创意转化为 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://github.com/refly-ai/refly/blob/main/README_CN.md', + score: 0.854884684085846, + source: 'https://github.com/refly-ai/refly/blob/main/README_CN.md', + title: 'refly/README_CN.md at main · refly-ai/refly - GitHub', + sourceType: 'webSearch', + }, + }, + { + url: 'https://top.aibase.com/topic/%E6%99%BA%E8%83%BD%E6%90%9C%E7%B4%A2', + title: '最新Ai智能搜索网站工具和软件推荐_AiBase产品库', + pageContent: + 'Refly. 基于自由画布的创作平台,激发创作灵感。 Refly是一个AI Native创作引擎,通过多线程对话、知识库整合、上下文记忆和智能搜索等技术,帮助用户将创意转化为优质 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://top.aibase.com/topic/%E6%99%BA%E8%83%BD%E6%90%9C%E7%B4%A2', + score: 0.8418256044387817, + source: 'https://top.aibase.com/topic/%E6%99%BA%E8%83%BD%E6%90%9C%E7%B4%A2', + title: '最新Ai智能搜索网站工具和软件推荐_AiBase产品库', + sourceType: 'webSearch', + }, + }, + { + url: 'https://www.cnblogs.com/wuhuacong/archive/2012/03/30/2426091.html', + title: '利用Refly和CodeDom实现代码的动态生成和动态编译- 伍华聪- 博客园', + pageContent: + 'Refly则是国外一个开发者对CodeDom进行封装,目的是使得Codedom的实现更加方便易懂,和CodeDom的使用对比,代码更加简洁优雅,不过要了解整体的东西 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.cnblogs.com/wuhuacong/archive/2012/03/30/2426091.html', + score: 0.8198933005332947, + source: 'https://www.cnblogs.com/wuhuacong/archive/2012/03/30/2426091.html', + title: '利用Refly和CodeDom实现代码的动态生成和动态编译- 伍华聪- 博客园', + sourceType: 'webSearch', + }, + }, + { + url: 'https://dread.run/', + title: '帝阅- 专注信息摘要的智能侍读助理', + pageContent: + '1.Refly是一个AI原生创作平台,旨在简化创作流程。 2.平台基于LangGraph技术,提供强大的AI功能。 3.Refly开源,代码仓库在GitHub上可供访问 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://dread.run/', + score: 0.8198933005332947, + source: 'https://dread.run/', + title: '帝阅- 专注信息摘要的智能侍读助理', + sourceType: 'webSearch', + }, + }, + { + url: 'https://developer.aliyun.com/article/326893', + title: '利用Refly和CodeDom实现代码的动态生成和动态编译', + pageContent: + 'Refly则是国外一个开发者对CodeDom进行封装,目的是使得Codedom的实现更加方便易懂,和CodeDom的使用对比,代码更加简洁优雅,不过要了解整体的东西,也需要对CodeDOM ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://developer.aliyun.com/article/326893', + score: 0.815232515335083, + source: 'https://developer.aliyun.com/article/326893', + title: '利用Refly和CodeDom实现代码的动态生成和动态编译', + sourceType: 'webSearch', + }, + }, + { + url: 'https://www.aihub.cn/tools/writing/refly/', + title: 'Refly.ai:AI驱动的专业内容创作引擎 - AIHub', + pageContent: + 'Refly.ai 是一款专为专业内容创作设计的AI驱动平台,通过多线程对话、知识整合、上下文记忆和智能搜索功能,帮助用户将创意快速转化为高质量内容。', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.aihub.cn/tools/writing/refly/', + score: 0.8068526387214661, + source: 'https://www.aihub.cn/tools/writing/refly/', + title: 'Refly.ai:AI驱动的专业内容创作引擎 - AIHub', + sourceType: 'webSearch', + }, + }, + { + url: 'https://www.threads.net/@yongshuai1013/post/DFPqDQURqkF', + title: '開源分享AI智慧創作工具:refly,它是一個集成了搜索、筆記軟體', + pageContent: + '開源分享AI智慧創作工具:refly,它是一個集成了搜索、筆記軟體、心智圖以及AI寫作的超級工具,用它可以更高效的把想法變成文字.', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.threads.net/@yongshuai1013/post/DFPqDQURqkF', + score: 0.7839884757995605, + source: 'https://www.threads.net/@yongshuai1013/post/DFPqDQURqkF', + title: '開源分享AI智慧創作工具:refly,它是一個集成了搜索、筆記軟體', + sourceType: 'webSearch', + }, + }, + { + url: 'https://t.cj.sina.com.cn/articles/view/2194035935/m82c654df03301e9hi?from=tech', + title: 'AI创作引擎项目Refly.AI刚正式开源了 - 新浪财经', + pageContent: + 'AI创作引擎项目Refly.AI刚正式开源了github.com/refly-ai/refly/Refly 是一个开源的AI 原生创作引擎。Refly 直观的自由画布界面集成了多线程对话、RAG ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://t.cj.sina.com.cn/articles/view/2194035935/m82c654df03301e9hi?from=tech', + score: 0.7520125508308411, + source: 'https://t.cj.sina.com.cn/articles/view/2194035935/m82c654df03301e9hi?from=tech', + title: 'AI创作引擎项目Refly.AI刚正式开源了 - 新浪财经', + sourceType: 'webSearch', + }, + }, + { + url: 'https://www.ftium4.com/ux-weekly-215.html', + title: '体验碎周报第215 期(2024.12.23) - 龙爪槐守望者', + pageContent: + 'Refly——自由画布笔记 ... 把笔记放在画布上,通过多线程对话、知识库整合、上下文记忆和智能搜索,帮助用户轻松将创意转化为高质量内容。该平台支持超过20 种 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.ftium4.com/ux-weekly-215.html', + score: 0.7386690378189087, + source: 'https://www.ftium4.com/ux-weekly-215.html', + title: '体验碎周报第215 期(2024.12.23) - 龙爪槐守望者', + sourceType: 'webSearch', + }, + }, + { + url: 'https://x.com/aigclink/status/1882778663164461111', + title: 'AIGCLINK on X: "很棒的一个AI智能创作工具:refly,它是一个集成了 ...', + pageContent: + '很棒的一个AI智能创作工具:refly,它是一个集成了搜索、笔记软件、思维导图以及AI写作的超级工具,用它可以更高效的把想法变成文字自由画布界面设计, ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://x.com/aigclink/status/1882778663164461111', + score: 0.7371581792831421, + source: 'https://x.com/aigclink/status/1882778663164461111', + title: 'AIGCLINK on X: "很棒的一个AI智能创作工具:refly,它是一个集成了 ...', + sourceType: 'webSearch', + }, + }, + { + url: 'https://x.com/tuturetom/status/1868585642529284566', + title: 'Tom Huang on X: " 大家好! 很高兴向大家介绍我们的AI 写作产品 ...', + pageContent: + '大家好! 很高兴向大家介绍我们的AI 写作产品—— Refly,这是一款革新性的AI Native 内容创作引擎!⚡️ Refly 是基于「自由画布 ‍ ‍ 」理念打造的AI Native ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://x.com/tuturetom/status/1868585642529284566', + score: 0.7256486415863037, + source: 'https://x.com/tuturetom/status/1868585642529284566', + title: 'Tom Huang on X: " 大家好! 很高兴向大家介绍我们的AI 写作产品 ...', + sourceType: 'webSearch', + }, + }, + { + url: 'https://zhidao.baidu.com/question/451012173.html', + title: 'refly是什么意思? - 百度知道', + pageContent: 'refly是"答复"的意思。 ...全文.', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://zhidao.baidu.com/question/451012173.html', + score: 0.7185943722724915, + source: 'https://zhidao.baidu.com/question/451012173.html', + title: 'refly是什么意思? - 百度知道', + sourceType: 'webSearch', + }, + }, + { + url: 'https://ainote.tw/refly-ai-creation-platform-explained/', + title: 'Refly AI 創作平臺全面解析 - 奕昇AI學習平台', + pageContent: + 'Refly 的核心概念為自由畫布,讓創作者可以在一個開放的環境中自由探索與 ... 如果您需要一款功能強大且靈活的AI 創作工具,Refly 無疑是最佳選擇!', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://ainote.tw/refly-ai-creation-platform-explained/', + score: 0.7000752091407776, + source: 'https://ainote.tw/refly-ai-creation-platform-explained/', + title: 'Refly AI 創作平臺全面解析 - 奕昇AI學習平台', + sourceType: 'webSearch', + }, + }, + { + url: 'https://www.waytoagi.com/zh/question/87173', + title: '与知识库对话- flowith 2.0与refly的区别具体在哪里? - WaytoAGI', + pageContent: + 'Flowith 2.0 与Refly 的区别主要体现在以下方面:. 功能定位:Refly 是一款国产应用,是全站式的文本创作工具,集成了“知识库+自由画布+AI 搜索+内容编辑”等功能,覆盖主题 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.waytoagi.com/zh/question/87173', + score: 0.6388352513313293, + source: 'https://www.waytoagi.com/zh/question/87173', + title: '与知识库对话- flowith 2.0与refly的区别具体在哪里? - WaytoAGI', + sourceType: 'webSearch', + }, + }, + { + url: 'https://www.bgrdh.com/sites/49214.html', + title: 'Refly-基于“自由画布”理念的AI 原生内容创作引擎 - 办公人导航', + pageContent: + 'Refly 的适用范围非常广泛,涵盖了学术研究、技术文档编写、投资分析、法律文献 ... 用户可以根据自己的需求选择专业场景模板,并通过上下文记忆和智能搜索功能优化创作流程。', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.bgrdh.com/sites/49214.html', + score: 0.5404388308525085, + source: 'https://www.bgrdh.com/sites/49214.html', + title: 'Refly-基于“自由画布”理念的AI 原生内容创作引擎 - 办公人导航', + sourceType: 'webSearch', + }, + }, + { + url: 'https://www.youtube.com/watch?v=5efKJWEvR94', + title: '介绍AI自由画板写作工具Refly.ai 以及使用其逐步创建一个详细的游戏 ...', + pageContent: + '本片介绍一个AI自由画板写作工具——Refly.ai,并使用其逐步创建一个详细的游戏项目规则。视频只有3分多钟,很容易看完。稍后我会整理详细的各部分提示发 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.youtube.com/watch?v=5efKJWEvR94', + score: 0.4929509162902832, + source: 'https://www.youtube.com/watch?v=5efKJWEvR94', + title: '介绍AI自由画板写作工具Refly.ai 以及使用其逐步创建一个详细的游戏 ...', + sourceType: 'webSearch', + }, + }, + { + url: 'https://ai-bot.cn/refly/', + title: 'Refly - AI原生内容创作平台,结合自由画布与多种AI 功能 - AI工具集', + pageContent: + 'Refly的应用场景. 学术研究:帮助研究人员整理思路、构建研究框架,快速生成文献 ... 相关文章. Manga Image Translator – 开源漫画图片文字翻译工具,多语言翻译无 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://ai-bot.cn/refly/', + score: 0.4144248068332672, + source: 'https://ai-bot.cn/refly/', + title: 'Refly - AI原生内容创作平台,结合自由画布与多种AI 功能 - AI工具集', + sourceType: 'webSearch', + }, + }, + { + url: 'https://x.com/nishuang/status/1882819143860113520', + title: '倪爽- 上个月我推荐大家试用“自由画布”式AI 创作工具Refly - X', + pageContent: + '上个月我推荐大家试用“自由画布”式AI 创作工具Refly,当时我说了两个感受,没想到背后都有深意我说他们最慷慨,没想到今天他们把整个项目开源了 , ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://x.com/nishuang/status/1882819143860113520', + score: 0.39981165528297424, + source: 'https://x.com/nishuang/status/1882819143860113520', + title: '倪爽- 上个月我推荐大家试用“自由画布”式AI 创作工具Refly - X', + sourceType: 'webSearch', + }, + }, + { + url: 'https://aiguide.cc/sites/14379.html', + title: 'Refly | AI智库导航-aiguide.cc', + pageContent: + '主要应用场景. 内容创作:适用于各种需要快速生成高质量内容的创作者,如 ... 注册与登录:访问Refly的官方网站或相关应用平台,注册并登录账号。 创建画布:在 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://aiguide.cc/sites/14379.html', + score: 0.38676342368125916, + source: 'https://aiguide.cc/sites/14379.html', + title: 'Refly | AI智库导航-aiguide.cc', + sourceType: 'webSearch', + }, + }, + { + url: 'https://www.yjpoo.com/site/5081.html', + title: 'Refly Ai:一个开源的AI原生创作引擎 - 映技派', + pageContent: + 'Refly AI特别适合需要深度思考和结构化组织的创作场景,如学术研究、技术文档编写等,Refly Ai现已集成最火的DeepSeek R1。 Refly Ai.webp. Refly Ai核心功能:. Refly AI 的 ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://www.yjpoo.com/site/5081.html', + score: 0.3656831681728363, + source: 'https://www.yjpoo.com/site/5081.html', + title: 'Refly Ai:一个开源的AI原生创作引擎 - 映技派', + sourceType: 'webSearch', + }, + }, + { + url: 'http://npses.ezsale.tw/upload/0402240001.ppt', + title: '[PPT] 資訊安全基本認知教育訓練', + pageContent: + '不論學校內部採用多強大的防火牆系統、防毒軟體、或其他資安技術軟硬體設施 ... http://www.refly.net/passwordchecker; http://www.microsoft.com/taiwan/athome ...', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'http://npses.ezsale.tw/upload/0402240001.ppt', + score: 0.25091278553009033, + source: 'http://npses.ezsale.tw/upload/0402240001.ppt', + title: '[PPT] 資訊安全基本認知教育訓練', + sourceType: 'webSearch', + }, + }, + { + url: 'https://docs.refly.ai/zh/guide/configuration', + title: '配置说明| Refly 文档', + pageContent: + 'MinIO ​ Refly 需要两个MinIO 实例: 内部:用于存储画布、资源和文档数据,通常设置为私有可见性。 外部:用于存储上传的文件,通常设置为公开可见性。', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://docs.refly.ai/zh/guide/configuration', + score: 0.24508501589298248, + source: 'https://docs.refly.ai/zh/guide/configuration', + title: '配置说明| Refly 文档', + sourceType: 'webSearch', + }, + }, + { + url: 'https://chromewebstore.google.com/detail/refly-ai-native-%E5%88%9B%E4%BD%9C%E5%BC%95%E6%93%8E/lecbjbapfkinmikhadakbclblnemmjpd?hl=zh-CN', + title: 'Refly - AI Native 创作引擎- Chrome 应用商店', + pageContent: + '基于自由画布的创作平台,通过多线程对话、知识库整合、上下文记忆、智能搜索和AI 文档编辑器,轻松将想法转化为优质内容。', + metadata: { + originalLocale: 'zh-hans', + originalQuery: 'refly 的定义是什么?', + translatedQuery: undefined, + isTranslated: false, + url: 'https://chromewebstore.google.com/detail/refly-ai-native-%E5%88%9B%E4%BD%9C%E5%BC%95%E6%93%8E/lecbjbapfkinmikhadakbclblnemmjpd?hl=zh-CN', + score: 0.23091977834701538, + source: + 'https://chromewebstore.google.com/detail/refly-ai-native-%E5%88%9B%E4%BD%9C%E5%BC%95%E6%93%8E/lecbjbapfkinmikhadakbclblnemmjpd?hl=zh-CN', + title: 'Refly - AI Native 创作引擎- Chrome 应用商店', + sourceType: 'webSearch', + }, + }, +]; diff --git a/packages/skill-template/src/skills/web-search.ts b/packages/skill-template/src/skills/web-search.ts index 14d73d95f..3c6a3643e 100644 --- a/packages/skill-template/src/skills/web-search.ts +++ b/packages/skill-template/src/skills/web-search.ts @@ -13,7 +13,6 @@ import { safeStringifyJSON } from '@refly-packages/utils'; // utils import { buildFinalRequestMessages } from '../scheduler/utils/message'; import { prepareContext } from '../scheduler/utils/context'; - // prompts import * as webSearch from '../scheduler/module/webSearch/index'; import { truncateSource } from '../scheduler/utils/truncator'; @@ -112,10 +111,27 @@ export class WebSearch extends BaseSkill { this.engine.logger.log('Prepared context successfully!'); - if (sources.length > 0) { - this.emitEvent({ structuredData: { sources: truncateSource(sources) } }, config); + if (sources?.length > 0) { + // Split sources into smaller chunks based on size and emit them separately + const truncatedSources = truncateSource(sources); + await this.emitLargeDataEvent( + { + data: truncatedSources, + buildEventData: (chunk, { isPartial, chunkIndex, totalChunks }) => ({ + structuredData: { + // Build your event data here + sources: chunk, + isPartial, + chunkIndex, + totalChunks, + }, + }), + }, + config, + ); } + // Now proceed with building request messages after all chunks are sent const requestMessages = buildFinalRequestMessages({ module, locale, @@ -128,7 +144,7 @@ export class WebSearch extends BaseSkill { rewrittenQuery: optimizedQuery, }); - this.engine.logger.log(`Request messages: ${safeStringifyJSON(requestMessages)}`); + // this.engine.logger.log(`Request messages: ${safeStringifyJSON(requestMessages)}`); // Generate answer using the model const model = this.engine.chatModel({ temperature: 0.1 }); From e62d842945c7cf4d9b6b01ee8ae05428df6da694 Mon Sep 17 00:00:00 2001 From: pftom <1043269994@qq.com> Date: Fri, 21 Feb 2025 19:44:42 +0800 Subject: [PATCH 3/6] chore: remove debug logging in SSE post and web search skill --- packages/ai-workspace-common/src/utils/sse-post.ts | 1 - packages/skill-template/src/skills/web-search.ts | 2 -- 2 files changed, 3 deletions(-) diff --git a/packages/ai-workspace-common/src/utils/sse-post.ts b/packages/ai-workspace-common/src/utils/sse-post.ts index 117c43e01..b03d140ca 100644 --- a/packages/ai-workspace-common/src/utils/sse-post.ts +++ b/packages/ai-workspace-common/src/utils/sse-post.ts @@ -141,7 +141,6 @@ export const ssePost = async ({ bufferStr = lines[lines.length - 1]; } catch (err) { - console.log('actual err', err); onSkillError(err); onCompleted?.(true); hasError = true; diff --git a/packages/skill-template/src/skills/web-search.ts b/packages/skill-template/src/skills/web-search.ts index 3c6a3643e..aae4694e4 100644 --- a/packages/skill-template/src/skills/web-search.ts +++ b/packages/skill-template/src/skills/web-search.ts @@ -144,8 +144,6 @@ export class WebSearch extends BaseSkill { rewrittenQuery: optimizedQuery, }); - // this.engine.logger.log(`Request messages: ${safeStringifyJSON(requestMessages)}`); - // Generate answer using the model const model = this.engine.chatModel({ temperature: 0.1 }); const responseMessage = await model.invoke(requestMessages, { From 3417871e17fcc7fa6cabcf7838fc6b7c0fff36a3 Mon Sep 17 00:00:00 2001 From: pftom <1043269994@qq.com> Date: Fri, 21 Feb 2025 20:10:12 +0800 Subject: [PATCH 4/6] feat(skills): implement large data event emission for source truncation - Replace direct event emission with chunked `emitLargeDataEvent` method - Add support for partial source data transmission across multiple skills - Enhance source data handling with chunk indexing and completeness tracking --- .../module/multiLingualSearch/index.ts | 2 -- .../skill-template/src/skills/common-qna.ts | 17 +++++++++++++++- .../skill-template/src/skills/edit-doc.ts | 18 ++++++++++++++++- .../skill-template/src/skills/generate-doc.ts | 20 ++++++++++++++++++- .../src/skills/library-search.ts | 18 ++++++++++++++++- .../src/skills/recommend-questions.ts | 16 ++++++++++++++- .../skill-template/src/skills/rewrite-doc.ts | 16 ++++++++++++++- 7 files changed, 99 insertions(+), 8 deletions(-) diff --git a/packages/skill-template/src/scheduler/module/multiLingualSearch/index.ts b/packages/skill-template/src/scheduler/module/multiLingualSearch/index.ts index 7e22a6e97..349339d66 100644 --- a/packages/skill-template/src/scheduler/module/multiLingualSearch/index.ts +++ b/packages/skill-template/src/scheduler/module/multiLingualSearch/index.ts @@ -335,8 +335,6 @@ export const callMultiLingualWebSearch = async ( // Keep original results if deduplication fails } - // ctxThis.emitEvent({ structuredData: { multiLingualSearchResult: finalResults } }, config); - // Return results with analysis return { sources: finalResults, diff --git a/packages/skill-template/src/skills/common-qna.ts b/packages/skill-template/src/skills/common-qna.ts index 21f1184cd..da204e71a 100644 --- a/packages/skill-template/src/skills/common-qna.ts +++ b/packages/skill-template/src/skills/common-qna.ts @@ -139,7 +139,22 @@ export class CommonQnA extends BaseSkill { config.metadata.step = { name: 'answerQuestion' }; if (sources.length > 0) { - this.emitEvent({ structuredData: { sources: truncateSource(sources) } }, config); + // Truncate sources before emitting + const truncatedSources = truncateSource(sources); + await this.emitLargeDataEvent( + { + data: truncatedSources, + buildEventData: (chunk, { isPartial, chunkIndex, totalChunks }) => ({ + structuredData: { + sources: chunk, + isPartial, + chunkIndex, + totalChunks, + }, + }), + }, + config, + ); } const model = this.engine.chatModel({ temperature: 0.1 }); diff --git a/packages/skill-template/src/skills/edit-doc.ts b/packages/skill-template/src/skills/edit-doc.ts index 7f51d0bb5..d3afecdf9 100644 --- a/packages/skill-template/src/skills/edit-doc.ts +++ b/packages/skill-template/src/skills/edit-doc.ts @@ -166,7 +166,23 @@ export class EditDoc extends BaseSkill { this.engine.logger.log(`context: ${safeStringifyJSON(context)}`); if (sources.length > 0) { - this.emitEvent({ structuredData: { sources: truncateSource(sources) } }, config); + // Split sources into smaller chunks based on size and emit them separately + const truncatedSources = truncateSource(sources); + await this.emitLargeDataEvent( + { + data: truncatedSources, + buildEventData: (chunk, { isPartial, chunkIndex, totalChunks }) => ({ + structuredData: { + // Build your event data here + sources: chunk, + isPartial, + chunkIndex, + totalChunks, + }, + }), + }, + config, + ); } } diff --git a/packages/skill-template/src/skills/generate-doc.ts b/packages/skill-template/src/skills/generate-doc.ts index 0c4fbd83c..1d3cf4818 100644 --- a/packages/skill-template/src/skills/generate-doc.ts +++ b/packages/skill-template/src/skills/generate-doc.ts @@ -222,7 +222,7 @@ ${recentHistory.map((msg) => `${(msg as HumanMessage)?.getType?.()}: ${msg.conte buildContextUserPrompt: generateDocument.buildGenerateDocumentContextUserPrompt, }; - const { optimizedQuery, requestMessages, context, usedChatHistory } = + const { optimizedQuery, requestMessages, context, sources, usedChatHistory } = await this.commonPreprocess(state, config, module); // Generate title first @@ -263,6 +263,24 @@ ${recentHistory.map((msg) => `${(msg as HumanMessage)?.getType?.()}: ${msg.conte config, ); + if (sources.length > 0) { + const truncatedSources = truncateSource(sources); + await this.emitLargeDataEvent( + { + data: truncatedSources, + buildEventData: (chunk, { isPartial, chunkIndex, totalChunks }) => ({ + structuredData: { + sources: chunk, + isPartial, + chunkIndex, + totalChunks, + }, + }), + }, + config, + ); + } + const responseMessage = await model.invoke(requestMessages, { ...config, metadata: { diff --git a/packages/skill-template/src/skills/library-search.ts b/packages/skill-template/src/skills/library-search.ts index 7cee77f76..7519907bf 100644 --- a/packages/skill-template/src/skills/library-search.ts +++ b/packages/skill-template/src/skills/library-search.ts @@ -101,7 +101,23 @@ export class LibrarySearch extends BaseSkill { }; if (sources.length > 0) { - this.emitEvent({ structuredData: { sources: truncateSource(sources) } }, config); + // Split sources into smaller chunks based on size and emit them separately + const truncatedSources = truncateSource(sources); + await this.emitLargeDataEvent( + { + data: truncatedSources, + buildEventData: (chunk, { isPartial, chunkIndex, totalChunks }) => ({ + structuredData: { + // Build your event data here + sources: chunk, + isPartial, + chunkIndex, + totalChunks, + }, + }), + }, + config, + ); } const requestMessages = buildFinalRequestMessages({ diff --git a/packages/skill-template/src/skills/recommend-questions.ts b/packages/skill-template/src/skills/recommend-questions.ts index c7db80a52..da21252f1 100644 --- a/packages/skill-template/src/skills/recommend-questions.ts +++ b/packages/skill-template/src/skills/recommend-questions.ts @@ -177,7 +177,21 @@ ${ Please generate relevant recommended questions in ${locale} language.`; if (sources.length > 0) { - this.emitEvent({ structuredData: { sources: truncateSource(sources) } }, config); + const truncatedSources = truncateSource(sources); + await this.emitLargeDataEvent( + { + data: truncatedSources, + buildEventData: (chunk, { isPartial, chunkIndex, totalChunks }) => ({ + structuredData: { + sources: chunk, + isPartial, + chunkIndex, + totalChunks, + }, + }), + }, + config, + ); } const result = await extractStructuredData( diff --git a/packages/skill-template/src/skills/rewrite-doc.ts b/packages/skill-template/src/skills/rewrite-doc.ts index 251489517..491bdabda 100644 --- a/packages/skill-template/src/skills/rewrite-doc.ts +++ b/packages/skill-template/src/skills/rewrite-doc.ts @@ -154,7 +154,21 @@ export class RewriteDoc extends BaseSkill { this.engine.logger.log(`context: ${safeStringifyJSON(context)}`); if (sources.length > 0) { - this.emitEvent({ structuredData: { sources: truncateSource(sources) } }, config); + const truncatedSources = truncateSource(sources); + await this.emitLargeDataEvent( + { + data: truncatedSources, + buildEventData: (chunk, { isPartial, chunkIndex, totalChunks }) => ({ + structuredData: { + sources: chunk, + isPartial, + chunkIndex, + totalChunks, + }, + }), + }, + config, + ); } } From 39ce90268a0b7d395151c7cbbf6a69e7508dee0e Mon Sep 17 00:00:00 2001 From: pftom <1043269994@qq.com> Date: Fri, 21 Feb 2025 20:38:21 +0800 Subject: [PATCH 5/6] fix(result-aggregator): improve structured data merging for partial events - Handle partial structured data events with chunk tracking - Merge sources across multiple partial events - Preserve partial event metadata during aggregation --- apps/api/src/utils/result.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/api/src/utils/result.ts b/apps/api/src/utils/result.ts index 3dc467c06..cca770708 100644 --- a/apps/api/src/utils/result.ts +++ b/apps/api/src/utils/result.ts @@ -71,7 +71,24 @@ export class ResultAggregator { break; case 'structured_data': if (event.structuredData) { - step.structuredData = { ...step.structuredData, ...event.structuredData }; + const structuredData = event.structuredData; + console.log('structuredData', structuredData?.isPartial); + if (structuredData?.isPartial !== undefined) { + const existingData = step.structuredData || {}; + const existingSources = (existingData.sources || []) as any[]; + step.structuredData = { + ...existingData, + sources: [ + ...existingSources, + ...(Array.isArray(structuredData.sources) ? structuredData.sources : []), + ], + isPartial: structuredData.isPartial, + chunkIndex: structuredData.chunkIndex, + totalChunks: structuredData.totalChunks, + }; + } else { + step.structuredData = { ...step.structuredData, ...event.structuredData }; + } } break; case 'log': From 9f0dfbb24bac1d76ff6fb422e2942c6419474e7c Mon Sep 17 00:00:00 2001 From: pftom <1043269994@qq.com> Date: Fri, 21 Feb 2025 20:40:01 +0800 Subject: [PATCH 6/6] chore(result-aggregator): remove debug logging for structured data --- apps/api/src/utils/result.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/api/src/utils/result.ts b/apps/api/src/utils/result.ts index cca770708..c341617cb 100644 --- a/apps/api/src/utils/result.ts +++ b/apps/api/src/utils/result.ts @@ -72,7 +72,6 @@ export class ResultAggregator { case 'structured_data': if (event.structuredData) { const structuredData = event.structuredData; - console.log('structuredData', structuredData?.isPartial); if (structuredData?.isPartial !== undefined) { const existingData = step.structuredData || {}; const existingSources = (existingData.sources || []) as any[];