Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Consider labels when providing the context #1730

Merged
merged 3 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cli/src/rpc/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class Configuration {
}

static async buildFromRpcParams(params: ConfigurationRpc.V2.Set.Params): Promise<Configuration> {
return new Configuration(params.projectDirectories, params.appmapConfigFiles);
return new Configuration(params.projectDirectories || [], params.appmapConfigFiles || []);
}
}

Expand Down
27 changes: 23 additions & 4 deletions packages/cli/src/rpc/explain/explain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ExplainRpc.ExplainStatusResponse>();

Expand Down Expand Up @@ -73,19 +74,37 @@ export class Explain extends EventEmitter {
}

async searchContext(data: ContextV2.ContextRequest): Promise<ContextV2.ContextResponse> {
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;

Expand Down
4 changes: 3 additions & 1 deletion packages/navie/src/agent.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ContextV2 } from './context';
import { ProjectInfo } from './project-info';

export enum AgentMode {
Expand All @@ -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() {
Expand Down
33 changes: 29 additions & 4 deletions packages/navie/src/agents/explain-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <command>\` 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 {
Expand Down Expand Up @@ -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);
Expand Down
11 changes: 6 additions & 5 deletions packages/navie/src/agents/generate-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
5 changes: 5 additions & 0 deletions packages/navie/src/agents/help-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
30 changes: 25 additions & 5 deletions packages/navie/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<ContextItemType, number>;
// Emphasize context items that are relevant to the classification of the user's request.
labels?: ContextLabel[];
};

export type ContextResponse = ContextItem[];
Expand Down
17 changes: 10 additions & 7 deletions packages/navie/src/explain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class CodeExplainerService {
): AsyncIterable<string> {
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)
Expand All @@ -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);

Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion packages/navie/src/services/agent-selection-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default class AgentSelectionService {
selectAgent(
question: string,
options: ExplainOptions,
projectInfo: ProjectInfo[]
_projectInfo: ProjectInfo[]
): AgentModeResult {
let modifiedQuestion = question;

Expand Down
Loading
Loading