diff --git a/packages/cli/src/rpc/configuration.ts b/packages/cli/src/rpc/configuration.ts index 64bb856aff..8bba568916 100644 --- a/packages/cli/src/rpc/configuration.ts +++ b/packages/cli/src/rpc/configuration.ts @@ -22,7 +22,7 @@ export class Configuration { } static async buildFromRpcParams(params: ConfigurationRpc.V2.Set.Params): Promise { - return new Configuration(params.projectDirectories, params.appmapConfigFiles); + return new Configuration(params.projectDirectories || [], params.appmapConfigFiles || []); } } diff --git a/packages/cli/src/rpc/explain/explain.ts b/packages/cli/src/rpc/explain/explain.ts index fbd41d0b7b..5c9688c921 100644 --- a/packages/cli/src/rpc/explain/explain.ts +++ b/packages/cli/src/rpc/explain/explain.ts @@ -10,6 +10,7 @@ import INavie, { INavieProvider } from './navie/inavie'; import configuration, { AppMapDirectory } from '../configuration'; import collectProjectInfos from '../../cmds/navie/projectInfo'; import collectHelp from '../../cmds/navie/help'; +import { basename } from 'path'; const searchStatusByUserMessageId = new Map(); @@ -73,19 +74,37 @@ export class Explain extends EventEmitter { } async searchContext(data: ContextV2.ContextRequest): Promise { - let { vectorTerms: keywords } = data; + let { vectorTerms } = data; let { tokenCount } = data; + this.status.vectorTerms = vectorTerms; + + if (data.labels) this.status.labels = data.labels; + const labels = data.labels || []; + if (!tokenCount) { warn(chalk.bold(`Warning: Token limit not set, defaulting to ${DEFAULT_TOKEN_LIMIT}`)); tokenCount = DEFAULT_TOKEN_LIMIT; } - if (!keywords || keywords.length === 0) { + if (!vectorTerms || vectorTerms.length === 0) { warn(chalk.bold(`Warning: No keywords provided, context result may be unpredictable`)); - keywords = []; } - this.status.vectorTerms = keywords; + const keywords = [...vectorTerms]; + if ( + labels.find((label) => label.name === 'architecture') || + labels.find((label) => label.name === 'overview') + ) { + keywords.push('architecture'); + keywords.push('design'); + keywords.push('readme'); + keywords.push('about'); + keywords.push('overview'); + for (const dir of this.projectDirectories) { + keywords.push(basename(dir)); + } + } + // TODO: For 'troubleshoot', include log information this.status.step = ExplainRpc.Step.SEARCH_APPMAPS; diff --git a/packages/navie/src/agent.ts b/packages/navie/src/agent.ts index 6762c77a34..1aa4ec11ba 100644 --- a/packages/navie/src/agent.ts +++ b/packages/navie/src/agent.ts @@ -1,3 +1,4 @@ +import { ContextV2 } from './context'; import { ProjectInfo } from './project-info'; export enum AgentMode { @@ -12,7 +13,8 @@ export class AgentOptions { public aggregateQuestion: string, public chatHistory: string[], public projectInfo: ProjectInfo[], - public codeSelection?: string + public codeSelection?: string, + public contextLabels?: ContextV2.ContextLabel[] ) {} get hasAppMaps() { diff --git a/packages/navie/src/agents/explain-agent.ts b/packages/navie/src/agents/explain-agent.ts index eaf6c34215..481c617e6f 100644 --- a/packages/navie/src/agents/explain-agent.ts +++ b/packages/navie/src/agents/explain-agent.ts @@ -38,11 +38,32 @@ Do NOT emit a "Considerations" section in your response, describing the importan engineering concepts. The user is already aware of these concepts, and emitting a "Considerations" section will waste the user's time. The user wants direct answers to their questions. +**Making AppMap data** + +You may encourage the user to make AppMap data if the context that you receive seems incomplete, and +you believe that you could provide a better answer if you had access to sequence diagrams, +HTTP server and client requests, exceptions, log messages, and database queries. + +Observe the following guidelines when encouraging the user to make AppMap data: + +1) For Node.js, recommend the user to run \`npx appmap-node \` to generate AppMap data. + Do not recommend appmap-agent-js, as this Node.js library is deprecated. +2) For Ruby, do not instruct the user to set \`APPMAP=true\`, because + these languages already generate AppMap data automatically based on the Rails environment. +3) Do not instruct the user to install the NPM package or CLI binary package \`@appland/appmap\` for the purpose of + making AppMap data, beacuse this package is not needed for that purpose. + **Teach the user about the @help prefix** -If it seems like the user might be asking about how to use AppMap, rather than about the contents of their AppMaps, -you should teach the user about the "@help" prefix. You can inform the user that they can begin their question with -the "@help" prefix to get help with using AppMap. +If it seems like the user is probably asking about how to record AppMap data for their project, +you should teach the user about the "@help" prefix. You can inform the user that they can begin +their question with the "@help" prefix to get help with using AppMap. + +**Teach the user about the @generate prefix** + +If it seems like the user is probably asking about how to generate code for their project, +you should teach the user about the "@generate" prefix. You can inform the user that they can begin +their question with the "@generate" prefix to get an answer that is directed towards code generation. `; export default class ExplainAgent implements Agent { @@ -70,7 +91,11 @@ export default class ExplainAgent implements Agent { const tokenCount = tokensAvailable(); const vectorTerms = await this.vectorTermsService.suggestTerms(options.aggregateQuestion); - const context = await this.lookupContextService.lookupContext(vectorTerms, tokenCount); + const context = await this.lookupContextService.lookupContext( + vectorTerms, + tokenCount, + options.contextLabels + ); const help = await this.lookupContextService.lookupHelp(languages, vectorTerms, tokenCount); LookupContextService.applyContext(context, help, this.applyContextService, tokenCount); diff --git a/packages/navie/src/agents/generate-agent.ts b/packages/navie/src/agents/generate-agent.ts index d4671571c4..3dae49fe52 100644 --- a/packages/navie/src/agents/generate-agent.ts +++ b/packages/navie/src/agents/generate-agent.ts @@ -22,16 +22,17 @@ You do not need to explain the importance of programming concepts like planning **Response Format** -Your solution must be provided as a series of code files and/or patches that implement the desired functionality within the project +Your solution must be provided as a series of code files and snippets that implement the desired functionality within the project code. Do not propose wrapping the project with other code, running the project in a different environment, wrapping the project with shell commands, or other workarounds. Your solution must be suitable for use as a pull request to the project. -* Your response should be provided as series of code files and/or patches that implement the desired functionality. +* Your response should be provided as series of code files and/or snippets that implement the desired functionality. * You should emit code that is designed to solve the problem described by the user. -* To modify existing code, emit a patch that can be applied to the existing codebase. -* To create new code, emit a new file that can be added to the existing codebase. +* To modify existing code, emit a code snippet that augments or replaces code in an existing file. + Tell the user which file they need to modify. +* To create new code, emit a new file that can be added to the existing codebase. Tell the user where to add the new file. * At the beginning of every patch file or code file you emit, you must print the path to the code file within the workspace. - +* Limit the amount of text explanation you emit to the minimum necessary. The user is primarily interested in the code itself. `; export class GenerateAgent implements Agent { constructor( diff --git a/packages/navie/src/agents/help-agent.ts b/packages/navie/src/agents/help-agent.ts index 71fbd8f9e2..83ce46de72 100644 --- a/packages/navie/src/agents/help-agent.ts +++ b/packages/navie/src/agents/help-agent.ts @@ -58,6 +58,11 @@ When helping the user make AppMaps for JavaScript, Node.js and/or TypeScript, yo use "appmap-node", which is the new AppMap agent for JavaScript, Node.js and TypeScript. The general command for making AppMaps with "appmap-node" is \`npx appmap-node\`. +Do not recommend appmap-agent-js, as this Node.js library is deprecated. + +Do not instruct the user to install the NPM package or CLI binary package \`@appland/appmap\` for the purpose of +making AppMap data, beacuse this package is not needed for that purpose. + Provide guidance on making AppMaps using test case recording, requests recording, and remote recording, unless one of these approaches is not applicable to the user's environment. diff --git a/packages/navie/src/context.ts b/packages/navie/src/context.ts index 0125c533d9..90af1d4153 100644 --- a/packages/navie/src/context.ts +++ b/packages/navie/src/context.ts @@ -57,6 +57,29 @@ export namespace ContextV2 { score?: number; }; + export enum ContextLabelName { + HelpWithAppMap = 'help-with-appmap', + Architecture = 'architecture', + Feature = 'feature', + Overview = 'overview', + Troubleshoot = 'troubleshoot', + Explain = 'explain', + Generate = 'generate', + } + + export enum ContextLabelWeight { + // The label is very relevant to the request. + High = 'high', + // The label is somewhat relevant to the request. + Medium = 'medium', + } + + // A label that describes the nature of the user's request. + export type ContextLabel = { + name: ContextLabelName | string; + weight: ContextLabelWeight | string; + }; + // Request a set of context items from the context provider. export type ContextRequest = ContextV1.ContextRequest & { // Boost recent context items. For example, if the user is asking about an event that has recently occurred, such @@ -69,11 +92,8 @@ export namespace ContextV2 { locations?: string[]; // When specified, only return context items of these types. itemTypes?: ContextItemType[]; - // Weight the importance of the context items. The sum of the weights should be 1. - // Item types not specified will be omitted from the response, along with item types whose weight is 0 or less. - // If the user's question is directed most specifically to a certain type of context item, the weights should be - // set to emphasize that type of context item. If the user's question is more general, the weights can be omitted. - weights?: Record; + // Emphasize context items that are relevant to the classification of the user's request. + labels?: ContextLabel[]; }; export type ContextResponse = ContextItem[]; diff --git a/packages/navie/src/explain.ts b/packages/navie/src/explain.ts index 3ba3350c50..1fe846c8e3 100644 --- a/packages/navie/src/explain.ts +++ b/packages/navie/src/explain.ts @@ -52,7 +52,7 @@ export class CodeExplainerService { ): AsyncIterable { const { question: baseQuestion, codeSelection } = clientRequest; - const classificationRequest = this.classifierService.classifyQuestion(baseQuestion); + const contextLabelsFn = this.classifierService.classifyQuestion(baseQuestion); const projectInfoResponse = await this.projectInfoService.lookupProjectInfo(); const projectInfo: ProjectInfo[] = Array.isArray(projectInfoResponse) @@ -78,12 +78,20 @@ export class CodeExplainerService { .filter(Boolean) .join('\n\n'); + const contextLabels = await contextLabelsFn; + warn( + `Classification: ${contextLabels + .map((label) => [label.name, label.weight].join('=')) + .join(', ')}` + ); + const agentOptions = new AgentOptions( question, aggregateQuestion, chatHistory?.map((message) => message.content) || [], projectInfo, - codeSelection + codeSelection, + contextLabels ); await mode.perform(agentOptions, tokensAvailable); @@ -97,11 +105,6 @@ export class CodeExplainerService { if (codeSelection) this.codeSelectionService.applyCodeSelection(codeSelection); mode.applyQuestionPrompt(question); - { - const classification = await classificationRequest; - warn(`Classification: ${classification}`); - } - const response = this.completionService.complete(); for await (const token of response) { yield token; diff --git a/packages/navie/src/services/agent-selection-service.ts b/packages/navie/src/services/agent-selection-service.ts index 4c90fca864..4f3a8a1ae9 100644 --- a/packages/navie/src/services/agent-selection-service.ts +++ b/packages/navie/src/services/agent-selection-service.ts @@ -32,7 +32,7 @@ export default class AgentSelectionService { selectAgent( question: string, options: ExplainOptions, - projectInfo: ProjectInfo[] + _projectInfo: ProjectInfo[] ): AgentModeResult { let modifiedQuestion = question; diff --git a/packages/navie/src/services/classification-service.ts b/packages/navie/src/services/classification-service.ts index 0919506953..71beb7274b 100644 --- a/packages/navie/src/services/classification-service.ts +++ b/packages/navie/src/services/classification-service.ts @@ -1,6 +1,7 @@ import { ChatOpenAI } from '@langchain/openai'; import OpenAI from 'openai'; import InteractionHistory from '../interaction-history'; +import { ContextV2 } from '../context'; const SYSTEM_PROMPT = `**Question classifier** @@ -9,51 +10,80 @@ There are several types of questions that the developer may be asking. Your task is to assign a likelihood to each of the following question types: -- **Help with AppMap**: The developer is asking for help using the AppMap product. -- **Project architecture**: The developer is asking about the high level architecture of their project. -- **Explaining code**: The developer is asking for an explanation of how a specific feature of their project works. -- **Generating code**: The developer is asking for code to be generated for a specific task. +- **help-with-appmap**: The developer is asking for help using the AppMap product. +- **architecture**: The developer is asking about the architecture of the project. +- **feature**: The developer is asking for an explanation of how a specific feature of the project works. +- **overview**: The developer is asking a high-level question about the structure, purpose, + functionality or intent of the project. +- **troubleshoot**: The developer is asking for help troubleshooting an issue. +- **explain**: The developer is asking for an explanation of a specific piece of code or functionality. +- **generate**: The developer is asking for code to be generated for a specific task. **Classification scores** Each question type is assigned one of the following likelihoods: -- **High**: The question is very likely to be of this type. -- **Medium**: The question is somewhat likely to be of this type. -- **Low**: The question is unlikely to be of this type. +- **high**: The question is very likely to be of this type. +- **medium**: The question is somewhat likely to be of this type. +- **low**: The question is unlikely to be of this type. **Response** -Respond with a list of question types and their likelihoods. The question types should be one of the following: 'Help with AppMap', -'Project architecture', 'Explaining code', 'Generating code'. The likelihoods should be one of the following: 'High', 'Medium', 'Low'. +Respond with the likelihood of each question type. Question types with "low" likelihood may +be omitted. -**Example** +**Examples** Some examples of questions and their classifications are: \`\`\` -Question: How do I install? -Classification: Help with AppMap (High) -Classification: Project architecture (Low) -Classification: Explaining code (Low) -Classification: Generating code (Low) +question: How do I record AppMap data of my Spring app? +classification: + - help-with-appmap: high + - architecture: low + - feature: low + - overview: low + - troubleshoot: low + - explain: low + - generate: low \`\`\` \`\`\` -Question: How does the project work? -Classification: Help with AppMap (Low) -Classification: Project architecture (High) -Classification: Explaining code (Low) -Classification: Generating code (Low) +question: How does the project work? +classification: + - help-with-appmap: low + - architecture: high + - feature: low + - overview: high + - troubleshoot: low + - explain: low + - generate: low \`\`\` \`\`\` -Question: Generate a new user -Classification: Help with AppMap (Low) -Classification: Project architecture (Low) -Classification: Explaining code (Low) -Classification: Generating code (High) +question: Generate a form and controller to update the user profile +classification: + - help-with-appmap: low + - architecture: medium + - feature: high + - overview: low + - explain: low + - generate: high \`\`\` + +\`\`\` +question: Why am I getting a 500 error? +classification: + - help-with-appmap: low + - architecture: low + - feature: low + - overview: low + - troubleshoot: high + - explain: medium + - generate: low +\`\`\` + + `; export default class ClassificationService { @@ -63,7 +93,7 @@ export default class ClassificationService { public temperature: number ) {} - async classifyQuestion(question: string): Promise { + async classifyQuestion(question: string): Promise { const openAI: ChatOpenAI = new ChatOpenAI({ modelName: this.modelName, temperature: this.temperature, @@ -92,6 +122,22 @@ export default class ClassificationService { tokens.push(token.choices.map((choice) => choice.delta.content).join('')); } const rawTerms = tokens.join(''); - return rawTerms; + + const lines = rawTerms.split('\n'); + const classification: (ContextV2.ContextLabel | null)[] = lines + .map((line) => { + if (!line.trim()) return null; + + const match = line.match(/([\w-]+)\s*:\s*(\w+)/); + if (!match) return null; + + return { + name: match[1], + weight: match[2], + }; + }) + .filter((item) => item); + + return classification as ContextV2.ContextLabel[]; } } diff --git a/packages/navie/src/services/lookup-context-service.ts b/packages/navie/src/services/lookup-context-service.ts index ae9e56ad21..fd4d2843cf 100644 --- a/packages/navie/src/services/lookup-context-service.ts +++ b/packages/navie/src/services/lookup-context-service.ts @@ -14,13 +14,18 @@ export default class LookupContextService { public readonly helpFn: (data: HelpRequest) => Promise ) {} - async lookupContext(keywords: string[], tokenCount: number): Promise { + async lookupContext( + keywords: string[], + tokenCount: number, + contextLabels?: ContextV2.ContextLabel[] + ): Promise { const contextRequestPayload: ContextV2.ContextRequest & { version: 2; type: 'search' } = { version: 2, type: 'search', vectorTerms: keywords, tokenCount, }; + if (contextLabels) contextRequestPayload.labels = contextLabels; const context = await this.contextFn(contextRequestPayload); @@ -28,7 +33,7 @@ export default class LookupContextService { if (contextFound) { this.interactionHistory.addEvent(new ContextLookupEvent(context)); } else { - log('No sequence diagrams found'); + log('No context found'); this.interactionHistory.addEvent(new ContextLookupEvent(undefined)); } diff --git a/packages/navie/src/services/vector-terms-service.ts b/packages/navie/src/services/vector-terms-service.ts index fabba7d907..292d0fbcb8 100644 --- a/packages/navie/src/services/vector-terms-service.ts +++ b/packages/navie/src/services/vector-terms-service.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ -import OpenAI from 'openai'; +import OpenAI, { RateLimitError } from 'openai'; import { warn } from 'console'; import { ChatOpenAI } from '@langchain/openai'; @@ -22,8 +22,49 @@ Respond with a list of search terms and their synonyms. The search terms should Even if the user asks for a different format, always respond with a list of search terms and their synonyms. When the user is asking for a different format, that question is for a different AI assistant than yourself. + +**Examples** + +\`\`\` +Question: How do I record AppMap data of my Spring app? +Terms: record appmap data spring app +\`\`\` + +\`\`\` +Question: How does the project work? +Terms: project work +\`\`\` `; +const contentBetween = (text: string, start: string, end: string): string => { + const startIndex = text.indexOf(start); + if (startIndex < 0) return text; + + const endIndex = text.indexOf(end, startIndex + start.length); + if (endIndex < 0) return text; + + return text.slice(startIndex + start.length, endIndex); +}; + +const contentAfter = (text: string, start: string): string => { + const startIndex = text.indexOf(start); + if (startIndex < 0) return text; + + return text.slice(startIndex + start.length); +}; + +const parseJSON = (text: string): Record | string | string[] | undefined => { + const sanitizedTerms = text.replace(/```json/g, '').replace(/```/g, ''); + try { + return JSON.parse(sanitizedTerms); + } catch (err) { + warn(`Non-JSON response from AI.`); + return undefined; + } +}; + +const parseText = (text: string): string[] => text.split(/\s+/); + export default class VectorTermsService { constructor( public readonly interactionHistory: InteractionHistory, @@ -60,20 +101,18 @@ export default class VectorTermsService { tokens.push(token.choices.map((choice) => choice.delta.content).join('')); } const rawTerms = tokens.join(''); + warn(`rawTerms: ${rawTerms}`); - const parseJSON = (): Record | string | string[] | undefined => { - const sanitizedTerms = rawTerms.replace(/```json/g, '').replace(/```/g, ''); - try { - return JSON.parse(sanitizedTerms); - } catch (err) { - warn(`Non-JSON response from AI.`); - return undefined; - } - }; - - const parseText = (): string[] => rawTerms.split(/\s+/); + let searchTermsObject: Record | string | string[] | undefined; + { + let responseText = rawTerms; + responseText = contentAfter(responseText, 'Terms:'); + responseText = contentBetween(responseText, '```json', '```'); + responseText = contentBetween(responseText, '```yaml', '```'); + responseText = responseText.trim(); + searchTermsObject = parseJSON(responseText) || parseText(responseText); + } - const searchTermsObject = parseJSON() || parseText(); warn(`searchTermsObject: ${JSON.stringify(searchTermsObject)}`); const terms = new Set(); { diff --git a/packages/navie/test/agents/explain-agent.spec.ts b/packages/navie/test/agents/explain-agent.spec.ts index 9205b5835b..7c1941a9c4 100644 --- a/packages/navie/test/agents/explain-agent.spec.ts +++ b/packages/navie/test/agents/explain-agent.spec.ts @@ -81,7 +81,8 @@ describe('@explain agent', () => { expect(lookupContextService.lookupContext).toHaveBeenCalledWith( ['user', 'management'], - tokensAvailable + tokensAvailable, + undefined ); expect(lookupContextService.lookupHelp).toHaveBeenCalledWith( ['ruby'], diff --git a/packages/navie/test/explain.spec.ts b/packages/navie/test/explain.spec.ts index 42e7a2eff9..feb032e14b 100644 --- a/packages/navie/test/explain.spec.ts +++ b/packages/navie/test/explain.spec.ts @@ -1,5 +1,5 @@ import assert from 'assert'; -import { Agent, AgentMode, AgentOptions } from '../src/agent'; +import { Agent, AgentMode } from '../src/agent'; import { ChatHistory, ClientRequest, CodeExplainerService, ExplainOptions } from '../src/explain'; import InteractionHistory from '../src/interaction-history'; import Message from '../src/message'; @@ -110,7 +110,7 @@ describe('CodeExplainerService', () => { complete: () => TOKEN_STREAM, }; classificationService = { - classifyQuestion: jest.fn().mockResolvedValue(undefined), + classifyQuestion: jest.fn().mockResolvedValue([]), } as unknown as ClassificationService; codeSelection = undefined; codeSelectionService = new CodeSelectionService(interactionHistory); @@ -140,6 +140,7 @@ describe('CodeExplainerService', () => { aggregateQuestion: userMessage1, chatHistory: [], codeSelection: undefined, + contextLabels: [], projectInfo: [ { appmapConfig: APPMAP_CONFIG, @@ -189,6 +190,7 @@ describe('CodeExplainerService', () => { aggregateQuestion: [userMessage1, userMessage2].join('\n\n'), chatHistory: [userMessage1, assistantMessage1], codeSelection: undefined, + contextLabels: [], projectInfo: [ { appmapConfig: APPMAP_CONFIG, diff --git a/packages/navie/test/services/classification-service.spec.ts b/packages/navie/test/services/classification-service.spec.ts index 21d6e73011..b3986b0490 100644 --- a/packages/navie/test/services/classification-service.spec.ts +++ b/packages/navie/test/services/classification-service.spec.ts @@ -20,9 +20,23 @@ describe('ClassificationService', () => { describe('when LLM responds', () => { it('returns the response', async () => { - mockAIResponse(completionWithRetry, [`Classification: user management`]); + const classification = ` +architecture: high +- troubleshooting: low +`; + + mockAIResponse(completionWithRetry, [classification]); const response = await service.classifyQuestion('user management'); - expect(response).toEqual(`Classification: user management`); + expect(response).toEqual([ + { + name: 'architecture', + weight: 'high', + }, + { + name: 'troubleshooting', + weight: 'low', + }, + ]); expect(completionWithRetry).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/navie/test/services/vector-terms-service.spec.ts b/packages/navie/test/services/vector-terms-service.spec.ts index a7678b6f7b..f295080a4d 100644 --- a/packages/navie/test/services/vector-terms-service.spec.ts +++ b/packages/navie/test/services/vector-terms-service.spec.ts @@ -52,6 +52,7 @@ describe('VectorTermsService', () => { expect(completionWithRetry).toHaveBeenCalledTimes(1); }); }); + describe('are a valid JSON list', () => { it('should return the terms', async () => { mockAIResponse(completionWithRetry, ['["user", "management"]']); @@ -59,6 +60,7 @@ describe('VectorTermsService', () => { expect(terms).toEqual(['user', 'management']); }); }); + describe('are valid JSON wrapped in fences', () => { it('should return the terms', async () => { mockAIResponse(completionWithRetry, ['```json', '["user", "management"]', '```']); @@ -67,7 +69,24 @@ describe('VectorTermsService', () => { }); }); - describe('are invalid JSON', () => { + describe('is YAML', () => { + it('parses the terms', async () => { + mockAIResponse(completionWithRetry, ['response_key:', ' - user', ' - management']); + const terms = await service.suggestTerms('user management'); + expect(terms).toEqual(['response', 'key', 'user', 'management']); + }); + }); + + describe('is prefixed by "Terms:"', () => { + it('is accepted and processed', async () => { + mockAIResponse(completionWithRetry, ['Terms: ["user", "management"]']); + const terms = await service.suggestTerms('user management'); + expect(terms).toEqual(['user', 'management']); + expect(completionWithRetry).toHaveBeenCalledTimes(1); + }); + }); + + describe('is list-ish ', () => { it('is accepted and processed', async () => { mockAIResponse(completionWithRetry, ['-user -mgmt']); const terms = await service.suggestTerms('user management'); diff --git a/packages/rpc/src/explain.ts b/packages/rpc/src/explain.ts index ec0ccc6163..e19f291e2c 100644 --- a/packages/rpc/src/explain.ts +++ b/packages/rpc/src/explain.ts @@ -44,11 +44,17 @@ export namespace ExplainRpc { score?: number; }; + export type ContextLabel = { + name: string; + weight: string; + }; + export type ExplainStatusResponse = { step: Step; threadId?: string; err?: Error | RpcError; vectorTerms?: string[]; + labels?: ContextLabel[]; searchResponse?: SearchRpc.SearchResponse; contextResponse?: ContextItem[]; explanation?: string[];