feat(server): summary tools (#13133)

fix AI-281

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added a new AI-powered conversation summary tool that generates
concise summaries of key topics, decisions, and details from
conversations, with options to focus on specific areas and adjust
summary length.
* Introduced a new prompt for conversation summarization, supporting
customizable focus and summary length.

* **Bug Fixes**
* Improved tool handling and error messages for conversation
summarization when required input is missing.

* **Tests**
* Expanded test coverage to include scenarios for the new conversation
summary feature.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2025-07-10 14:47:34 +08:00
committed by GitHub
parent 0f9b9789da
commit ad5a122391
7 changed files with 181 additions and 48 deletions
@@ -21,6 +21,7 @@ import {
buildDocKeywordSearchGetter,
buildDocSearchGetter,
createCodeArtifactTool,
createConversationSummaryTool,
createDocComposeTool,
createDocEditTool,
createDocKeywordSearchTool,
@@ -139,6 +140,10 @@ export abstract class CopilotProvider<C = any> {
if (options?.tools?.length) {
this.logger.debug(`getTools: ${JSON.stringify(options.tools)}`);
const ac = this.moduleRef.get(AccessController, { strict: false });
const docReader = this.moduleRef.get(DocReader, { strict: false });
const prompt = this.moduleRef.get(PromptService, {
strict: false,
});
for (const tool of options.tools) {
const toolDef = this.getProviderSpecificTools(tool, model);
@@ -150,9 +155,20 @@ export abstract class CopilotProvider<C = any> {
continue;
}
switch (tool) {
case 'codeArtifact': {
tools.code_artifact = createCodeArtifactTool(prompt, this.factory);
break;
}
case 'conversationSummary': {
tools.conversation_summary = createConversationSummaryTool(
options.session,
prompt,
this.factory
);
break;
}
case 'docEdit': {
const doc = this.moduleRef.get(DocReader, { strict: false });
const getDocContent = buildContentGetter(ac, doc);
const getDocContent = buildContentGetter(ac, docReader);
tools.doc_edit = createDocEditTool(
this.factory,
getDocContent.bind(null, options)
@@ -163,7 +179,6 @@ export abstract class CopilotProvider<C = any> {
const context = this.moduleRef.get(CopilotContextService, {
strict: false,
});
const docContext = options.session
? await context.getBySessionId(options.session)
: null;
@@ -175,9 +190,6 @@ export abstract class CopilotProvider<C = any> {
}
case 'docKeywordSearch': {
if (this.AFFiNEConfig.indexer.enabled) {
const ac = this.moduleRef.get(AccessController, {
strict: false,
});
const indexerService = this.moduleRef.get(IndexerService, {
strict: false,
});
@@ -192,9 +204,7 @@ export abstract class CopilotProvider<C = any> {
break;
}
case 'docRead': {
const ac = this.moduleRef.get(AccessController, { strict: false });
const models = this.moduleRef.get(Models, { strict: false });
const docReader = this.moduleRef.get(DocReader, { strict: false });
const getDoc = buildDocContentGetter(ac, docReader, models);
tools.doc_read = createDocReadTool(getDoc.bind(null, options));
break;
@@ -205,23 +215,7 @@ export abstract class CopilotProvider<C = any> {
break;
}
case 'docCompose': {
const promptService = this.moduleRef.get(PromptService, {
strict: false,
});
tools.doc_compose = createDocComposeTool(
promptService,
this.factory
);
break;
}
case 'codeArtifact': {
const promptService = this.moduleRef.get(PromptService, {
strict: false,
});
tools.code_artifact = createCodeArtifactTool(
promptService,
this.factory
);
tools.doc_compose = createDocComposeTool(prompt, this.factory);
break;
}
}
@@ -60,6 +60,8 @@ export const VertexSchema: JSONSchema = {
export const PromptConfigStrictSchema = z.object({
tools: z
.enum([
'codeArtifact',
'conversationSummary',
// work with morph
'docEdit',
// work with indexer
@@ -71,7 +73,6 @@ export const PromptConfigStrictSchema = z.object({
'webSearch',
// artifact tools
'docCompose',
'codeArtifact',
])
.array()
.nullable()
@@ -6,20 +6,10 @@ import {
ImagePart,
TextPart,
TextStreamPart,
ToolSet,
} from 'ai';
import { ZodType } from 'zod';
import {
createCodeArtifactTool,
createDocComposeTool,
createDocEditTool,
createDocKeywordSearchTool,
createDocReadTool,
createDocSemanticSearchTool,
createExaCrawlTool,
createExaSearchTool,
} from '../tools';
import { CustomAITools } from '../tools';
import { PromptMessage, StreamObject } from './types';
type ChatMessage = CoreUserMessage | CoreAssistantMessage;
@@ -385,17 +375,6 @@ export class CitationParser {
}
}
export interface CustomAITools extends ToolSet {
doc_edit: ReturnType<typeof createDocEditTool>;
doc_semantic_search: ReturnType<typeof createDocSemanticSearchTool>;
doc_keyword_search: ReturnType<typeof createDocKeywordSearchTool>;
doc_read: ReturnType<typeof createDocReadTool>;
doc_compose: ReturnType<typeof createDocComposeTool>;
web_search_exa: ReturnType<typeof createExaSearchTool>;
web_crawl_exa: ReturnType<typeof createExaCrawlTool>;
code_artifact: ReturnType<typeof createCodeArtifactTool>;
}
type ChunkType = TextStreamPart<CustomAITools>['type'];
export function toError(error: unknown): Error {
@@ -451,6 +430,10 @@ export class TextStreamParser {
);
result = this.addPrefix(result);
switch (chunk.toolName) {
case 'conversation_summary': {
result += `\nSummarizing context\n`;
break;
}
case 'web_search_exa': {
result += `\nSearching the web "${chunk.args.query}"\n`;
break;