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

doc: add chat context documentation #719

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
176 changes: 175 additions & 1 deletion src/docs/theia_ai.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ High Level Architecture of Theia AI
- [Variables](#variables)
- [Agent-specific Variables](#agent-specific-variables)
- [Global Variables](#global-variables)
- [Chat Context Variables](#chat-context-variables)
- [Tool Functions](#tool-functions)
- [Custom Response Part Rendering](#custom-response-part-rendering)
- [Managing the State of a Chat Response](#managing-the-state-of-a-chat-response)
Expand Down Expand Up @@ -217,7 +218,180 @@ export class TodayVariableContribution implements AIVariableContribution, AIVari
bind(AIVariableContribution).to(TodayVariableContribution).inSingletonScope();
```

It can now be used in any prompt template, as well as in user requests.
### Chat Context Variables

Theia AI supports attaching rich contextual information to chat requests via **context variables**. Unlike standard variables discussed above, which simply inject a value into the prompt, context variables provide both a `value` and a `contextValue`. The `value` is inserted at the position of the variable usage, while the `contextValue` is added to the `ChatRequestModel.context`—supplying additional data that the chat agent and underlying LLM can leverage for more informed responses.

Context variables enable users to scope their requests by including elements such as files, symbols, or other domain-specific data elements.
It is up to the agent to decide how this additional data is processed. Common processing approaches include:

1. **Summarization:** The agent may summarize the provided context (e.g., listing file names) before passing it to the LLM.
2. **Context Window Management:** The agent may decide how much context to include the entire context if it fits, apply ranking/summarization if too large, or use multi-turn prompt flows to incrementally identify relevant parts and refine the provided context.
3. **On-Demand Retrieval:** Instead of sending all context upfront, the agent may also expose tool functions so that the LLM can fetch specific elements when needed.

#### Usage

Users can attach context elements to chat requests in several ways:

- **Drag and Drop:** Drag elements into the chat input, e.g. dropping files from the file explorer.
- **Typing:** Enter the context variable name (e.g. `#file`), which triggers a quick pick selection dialog.
- **Auto-completion:** Type and select a specific element (e.g. `#file:myFile.txt`).

<img src="../../context-variables.png" alt="Attach Files to the Context" style="max-width: 525px">

Once attached, the context elements are displayed in the chat input to the user.

##### Implementation Example: File Context Variable

The following code registers a file context variable provider along with its label provider:

```ts
export default new ContainerModule(bind => {
...
// the plain variable registration
bind(AIVariableContribution).to(FileVariableContribution).inSingletonScope();
// the registration of the additional support contributions, such as the drag-and-drop handler, auto-completion, etc.
bind(AIVariableContribution).to(FileChatVariableContribution).inSingletonScope();
// the label provider controlling how the context element is displayed in the chat input
bind(ContextFileVariableLabelProvider).toSelf().inSingletonScope();
bind(LabelProviderContribution).toService(ContextFileVariableLabelProvider);
...
});
```

#### File Variable Provider

This provider resolves a file variable by reading the contents of the specified file. Note the difference: it returns a `value` (e.g., a workspace-relative file path) which will be inserted in to the user request when `#file:abc.txt` is used, and a `contextValue` (the file’s actual content) for further processing of the agent.

```ts
export const FILE_VARIABLE: AIVariable = {
id: 'file-provider',
description: 'Resolves the path and the contents of a file',
name: 'file',
label: 'File',
// specifies the icon to be used in the user interface for selecting this variable type
iconClasses: codiconArray('file'),
// sets this variable as a context variable
isContextVariable: true,
// specifying the arguments this variable takes (the URI of the file)
args: [{ name: 'uri', description: 'The URI of the requested file.' }]
};

@injectable()
export class FileVariableContribution implements AIVariableContribution, AIVariableResolver {
@inject(FileService)
protected readonly fileService: FileService;

@inject(WorkspaceService)
protected readonly wsService: WorkspaceService;

registerVariables(service: AIVariableService): void {
service.registerResolver(FILE_VARIABLE, this);
}

async canResolve(request: AIVariableResolutionRequest, _: AIVariableContext): Promise<number> {
return request.variable.name === FILE_VARIABLE.name ? 1 : 0;
}

async resolve(request: AIVariableResolutionRequest, _: AIVariableContext): Promise<ResolvedAIContextVariable | undefined> {
//...
try {
const content = await this.fileService.readFile(path);
return {
variable: request.variable,
value: await this.wsService.getWorkspaceRelativePath(path),
contextValue: content.value.toString(),
};
} catch (error) {
return undefined;
}
}
//...
}
```

#### File Chat Variable Contributions

To enhance usability, additional contributions are implemented to handle argument picking, auto-completion, and drag-and-drop support for file context variables.

```ts
export class FileChatVariableContribution implements FrontendVariableContribution {
@inject(FileService)
protected readonly fileService: FileService;

@inject(WorkspaceService)
protected readonly wsService: WorkspaceService;

@inject(QuickInputService)
protected readonly quickInputService: QuickInputService;

@inject(QuickFileSelectService)
protected readonly quickFileSelectService: QuickFileSelectService;

registerVariables(service: FrontendVariableService): void {
service.registerArgumentPicker(FILE_VARIABLE, this.triggerArgumentPicker.bind(this));
service.registerArgumentCompletionProvider(FILE_VARIABLE, this.provideArgumentCompletionItems.bind(this));
service.registerDropHandler(this.handleDrop.bind(this));
}

protected async triggerArgumentPicker(): Promise<string | undefined> {
// triggered when the user auto-completes the variable name in the chat input, e.g. #file
// below we use the quick input service to show a file picker
const quickPick = this.quickInputService.createQuickPick();
quickPick.items = await this.quickFileSelectService.getPicks();

const updateItems = async (value: string) => {
quickPick.items = await this.quickFileSelectService.getPicks(value, CancellationToken.None);
};

const onChangeListener = quickPick.onDidChangeValue(updateItems);
quickPick.show();

return new Promise(resolve => {
quickPick.onDispose(onChangeListener.dispose);
quickPick.onDidAccept(async () => {
const selectedItem = quickPick.selectedItems[0];
if (selectedItem && FileQuickPickItem.is(selectedItem)) {
quickPick.dispose();
resolve(await this.wsService.getWorkspaceRelativePath(selectedItem.uri));
}
});
});
}

protected async provideArgumentCompletionItems(
model: monaco.editor.ITextModel,
position: monaco.Position
): Promise<monaco.languages.CompletionItem[] | undefined> {
// triggered when the user auto-completes after `#` or `#<variable-name>:`
// your implementation needs to return a list of completion items
//...
}

protected async handleDrop(event: DragEvent, _: AIVariableContext): Promise<AIVariableDropResult | undefined> {
// triggered when the user drags an element into the chat input
// your implementation needs to read the `event.dataTransfer` and return a variable to add to the request
// and optionally a text to add to the chat input
const data = event.dataTransfer?.getData('selected-tree-nodes');
if (!data) {
return undefined;
}

try {
//...
return { variables, text };
} catch {
return undefined;
}
}
}
```

#### Using the Context Variables from Agents

Your custom agent can now use the context variables in various ways. The context variables are available in the `context` property of the `ChatRequestModel` and apply your custom logic to decide in which way your agent passes the context data to the LLM.

Your agents can use the variables `#contextSummary` or `#contextDetails`, which resolve to either a list or the full context, in your system message to transfer the attached context to the LLM. Alternatively, you can use the tool functions `~{context_ListChatContext}` and `~{context_ResolveChatContext}` to allow the LLM obtaining the context on demand.

### Tool Functions

Expand Down
7 changes: 6 additions & 1 deletion src/docs/user_ai.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,12 @@ The Theia IDE provides a global chat interface where users can interact with all

<img src="../../general-chat.png" alt="General AI Chat in the Theia IDE" style="max-width: 525px">

Some agents produce special results, such as buttons (shown in the screenshot above) or code that can be directly inserted. You can augment your requests in the chat with context by using variables. For example, to refer to the currently selected text, use `#selectedText` in your request. Pressing '#' in the chat will show a list of available variables.
Some agents produce special results, such as buttons (shown in the screenshot above) or code that can be directly inserted. You can augment your requests in the chat with context by using variables. For example, to refer to the currently selected text, use `#selectedText` in your request. Pressing '#' in ^the chat will show a list of available variables.

You can also pass context files into the chat to further specify the scope of your request. To do this, drag and drop a file into the chat view, or use the auto-completion feature by typing `#file` or directly typing `#<file-name>`.
Note that the use of the variable `#file:src/my-code.ts` in the chat input text will resolve to the workspace-relative path, while attaching the file into the context, makes the contents available to the chat agent. This allows adding the file content and then referring to it in the chat input text conveniently.

<img src="../../context-variables.png" alt="Attach Files to the Context" style="max-width: 525px">

## AI Configuration

Expand Down
Binary file added static/context-variables.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.