@theia/ai-chat 1.54.0 → 1.55.0-next.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/ai-chat-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-frontend-module.js +5 -0
- package/lib/browser/ai-chat-frontend-module.js.map +1 -1
- package/lib/common/chat-agents.d.ts +19 -5
- package/lib/common/chat-agents.d.ts.map +1 -1
- package/lib/common/chat-agents.js +106 -83
- package/lib/common/chat-agents.js.map +1 -1
- package/lib/common/chat-model.d.ts +16 -1
- package/lib/common/chat-model.d.ts.map +1 -1
- package/lib/common/chat-model.js +20 -3
- package/lib/common/chat-model.js.map +1 -1
- package/lib/common/orchestrator-chat-agent.d.ts.map +1 -1
- package/lib/common/orchestrator-chat-agent.js +28 -3
- package/lib/common/orchestrator-chat-agent.js.map +1 -1
- package/lib/common/parse-contents.d.ts +11 -0
- package/lib/common/parse-contents.d.ts.map +1 -0
- package/lib/common/parse-contents.js +67 -0
- package/lib/common/parse-contents.js.map +1 -0
- package/lib/common/parse-contents.spec.d.ts +9 -0
- package/lib/common/parse-contents.spec.d.ts.map +1 -0
- package/lib/common/parse-contents.spec.js +133 -0
- package/lib/common/parse-contents.spec.js.map +1 -0
- package/lib/common/response-content-matcher.d.ts +63 -0
- package/lib/common/response-content-matcher.d.ts.map +1 -0
- package/lib/common/response-content-matcher.js +86 -0
- package/lib/common/response-content-matcher.js.map +1 -0
- package/package.json +7 -7
- package/src/browser/ai-chat-frontend-module.ts +6 -0
- package/src/common/chat-agents.ts +108 -90
- package/src/common/chat-model.ts +30 -6
- package/src/common/orchestrator-chat-agent.ts +29 -4
- package/src/common/parse-contents.spec.ts +142 -0
- package/src/common/parse-contents.ts +92 -0
- package/src/common/response-content-matcher.ts +102 -0
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
LanguageModel,
|
|
26
26
|
LanguageModelRequirement,
|
|
27
27
|
LanguageModelResponse,
|
|
28
|
+
LanguageModelStreamResponse,
|
|
28
29
|
PromptService,
|
|
29
30
|
ResolvedPromptTemplate,
|
|
30
31
|
ToolRequest,
|
|
@@ -37,19 +38,20 @@ import {
|
|
|
37
38
|
LanguageModelStreamResponsePart,
|
|
38
39
|
MessageActor,
|
|
39
40
|
} from '@theia/ai-core/lib/common';
|
|
40
|
-
import { CancellationToken, CancellationTokenSource, ILogger, isArray } from '@theia/core';
|
|
41
|
-
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
41
|
+
import { CancellationToken, CancellationTokenSource, ContributionProvider, ILogger, isArray } from '@theia/core';
|
|
42
|
+
import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
|
|
42
43
|
import { ChatAgentService } from './chat-agent-service';
|
|
43
44
|
import {
|
|
44
45
|
ChatModel,
|
|
45
46
|
ChatRequestModel,
|
|
46
47
|
ChatRequestModelImpl,
|
|
47
48
|
ChatResponseContent,
|
|
48
|
-
CodeChatResponseContentImpl,
|
|
49
49
|
ErrorChatResponseContentImpl,
|
|
50
50
|
MarkdownChatResponseContentImpl,
|
|
51
51
|
ToolCallChatResponseContentImpl
|
|
52
52
|
} from './chat-model';
|
|
53
|
+
import { findFirstMatch, parseContents } from './parse-contents';
|
|
54
|
+
import { DefaultResponseContentFactory, ResponseContentMatcher, ResponseContentMatcherProvider } from './response-content-matcher';
|
|
53
55
|
|
|
54
56
|
/**
|
|
55
57
|
* A conversation consists of a sequence of ChatMessages.
|
|
@@ -121,13 +123,27 @@ export abstract class AbstractChatAgent {
|
|
|
121
123
|
@inject(ILogger) protected logger: ILogger;
|
|
122
124
|
@inject(CommunicationRecordingService) protected recordingService: CommunicationRecordingService;
|
|
123
125
|
@inject(PromptService) protected promptService: PromptService;
|
|
126
|
+
|
|
127
|
+
@inject(ContributionProvider) @named(ResponseContentMatcherProvider)
|
|
128
|
+
protected contentMatcherProviders: ContributionProvider<ResponseContentMatcherProvider>;
|
|
129
|
+
protected contentMatchers: ResponseContentMatcher[] = [];
|
|
130
|
+
|
|
131
|
+
@inject(DefaultResponseContentFactory)
|
|
132
|
+
protected defaultContentFactory: DefaultResponseContentFactory;
|
|
133
|
+
|
|
124
134
|
constructor(
|
|
125
135
|
public id: string,
|
|
126
136
|
public languageModelRequirements: LanguageModelRequirement[],
|
|
127
137
|
protected defaultLanguageModelPurpose: string,
|
|
128
138
|
public iconClass: string = 'codicon codicon-copilot',
|
|
129
139
|
public locations: ChatAgentLocation[] = ChatAgentLocation.ALL,
|
|
130
|
-
public tags: String[] = ['Chat']
|
|
140
|
+
public tags: String[] = ['Chat'],
|
|
141
|
+
public defaultLogging: boolean = true) {
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@postConstruct()
|
|
145
|
+
init(): void {
|
|
146
|
+
this.contentMatchers = this.contentMatcherProviders.getContributions().flatMap(provider => provider.matchers);
|
|
131
147
|
}
|
|
132
148
|
|
|
133
149
|
async invoke(request: ChatRequestModelImpl): Promise<void> {
|
|
@@ -137,14 +153,16 @@ export abstract class AbstractChatAgent {
|
|
|
137
153
|
throw new Error('Couldn\'t find a matching language model. Please check your setup!');
|
|
138
154
|
}
|
|
139
155
|
const messages = await this.getMessages(request.session);
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
156
|
+
if (this.defaultLogging) {
|
|
157
|
+
this.recordingService.recordRequest({
|
|
158
|
+
agentId: this.id,
|
|
159
|
+
sessionId: request.session.id,
|
|
160
|
+
timestamp: Date.now(),
|
|
161
|
+
requestId: request.id,
|
|
162
|
+
request: request.request.text,
|
|
163
|
+
messages
|
|
164
|
+
});
|
|
165
|
+
}
|
|
148
166
|
|
|
149
167
|
const systemMessageDescription = await this.getSystemMessageDescription();
|
|
150
168
|
const tools: Map<string, ToolRequest> = new Map();
|
|
@@ -177,18 +195,28 @@ export abstract class AbstractChatAgent {
|
|
|
177
195
|
);
|
|
178
196
|
await this.addContentsToResponse(languageModelResponse, request);
|
|
179
197
|
request.response.complete();
|
|
180
|
-
this.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
198
|
+
if (this.defaultLogging) {
|
|
199
|
+
this.recordingService.recordResponse({
|
|
200
|
+
agentId: this.id,
|
|
201
|
+
sessionId: request.session.id,
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
requestId: request.response.requestId,
|
|
204
|
+
response: request.response.response.asString()
|
|
205
|
+
});
|
|
206
|
+
}
|
|
187
207
|
} catch (e) {
|
|
188
208
|
this.handleError(request, e);
|
|
189
209
|
}
|
|
190
210
|
}
|
|
191
211
|
|
|
212
|
+
protected parseContents(text: string): ChatResponseContent[] {
|
|
213
|
+
return parseContents(
|
|
214
|
+
text,
|
|
215
|
+
this.contentMatchers,
|
|
216
|
+
this.defaultContentFactory?.create.bind(this.defaultContentFactory)
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
192
220
|
protected handleError(request: ChatRequestModelImpl, error: Error): void {
|
|
193
221
|
request.response.response.addContent(new ErrorChatResponseContentImpl(error));
|
|
194
222
|
request.response.error(error);
|
|
@@ -251,13 +279,22 @@ export abstract class AbstractChatAgent {
|
|
|
251
279
|
tools: ToolRequest[] | undefined,
|
|
252
280
|
token: CancellationToken
|
|
253
281
|
): Promise<LanguageModelResponse> {
|
|
282
|
+
const settings = this.getLlmSettings();
|
|
254
283
|
const languageModelResponse = languageModel.request({
|
|
255
284
|
messages,
|
|
256
285
|
tools,
|
|
286
|
+
settings,
|
|
257
287
|
}, token);
|
|
258
288
|
return languageModelResponse;
|
|
259
289
|
}
|
|
260
290
|
|
|
291
|
+
/**
|
|
292
|
+
* @returns the settings, such as `temperature`, to be used in all language model requests. Returns `undefined` by default.
|
|
293
|
+
*/
|
|
294
|
+
protected getLlmSettings(): { [key: string]: unknown; } | undefined {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
|
|
261
298
|
protected abstract addContentsToResponse(languageModelResponse: LanguageModelResponse, request: ChatRequestModelImpl): Promise<void>;
|
|
262
299
|
}
|
|
263
300
|
|
|
@@ -281,79 +318,33 @@ export abstract class AbstractStreamParsingChatAgent extends AbstractChatAgent {
|
|
|
281
318
|
|
|
282
319
|
protected override async addContentsToResponse(languageModelResponse: LanguageModelResponse, request: ChatRequestModelImpl): Promise<void> {
|
|
283
320
|
if (isLanguageModelTextResponse(languageModelResponse)) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
);
|
|
321
|
+
const contents = this.parseContents(languageModelResponse.text);
|
|
322
|
+
request.response.response.addContents(contents);
|
|
287
323
|
request.response.complete();
|
|
288
|
-
this.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
324
|
+
if (this.defaultLogging) {
|
|
325
|
+
this.recordingService.recordResponse({
|
|
326
|
+
agentId: this.id,
|
|
327
|
+
sessionId: request.session.id,
|
|
328
|
+
timestamp: Date.now(),
|
|
329
|
+
requestId: request.response.requestId,
|
|
330
|
+
response: request.response.response.asString()
|
|
331
|
+
|
|
332
|
+
});
|
|
333
|
+
}
|
|
295
334
|
return;
|
|
296
335
|
}
|
|
297
336
|
if (isLanguageModelStreamResponse(languageModelResponse)) {
|
|
298
|
-
|
|
299
|
-
const newContents = this.parse(token, request.response.response.content);
|
|
300
|
-
if (isArray(newContents)) {
|
|
301
|
-
newContents.forEach(newContent => request.response.response.addContent(newContent));
|
|
302
|
-
} else {
|
|
303
|
-
request.response.response.addContent(newContents);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const lastContent = request.response.response.content.pop();
|
|
307
|
-
if (lastContent === undefined) {
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
const text = lastContent.asString?.();
|
|
311
|
-
if (text === undefined) {
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
let curSearchIndex = 0;
|
|
315
|
-
const result: ChatResponseContent[] = [];
|
|
316
|
-
while (curSearchIndex < text.length) {
|
|
317
|
-
// find start of code block: ```[language]\n<code>[\n]```
|
|
318
|
-
const codeStartIndex = text.indexOf('```', curSearchIndex);
|
|
319
|
-
if (codeStartIndex === -1) {
|
|
320
|
-
break;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// find language specifier if present
|
|
324
|
-
const newLineIndex = text.indexOf('\n', codeStartIndex + 3);
|
|
325
|
-
const language = codeStartIndex + 3 < newLineIndex ? text.substring(codeStartIndex + 3, newLineIndex) : undefined;
|
|
326
|
-
|
|
327
|
-
// find end of code block
|
|
328
|
-
const codeEndIndex = text.indexOf('```', codeStartIndex + 3);
|
|
329
|
-
if (codeEndIndex === -1) {
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// add text before code block as markdown content
|
|
334
|
-
result.push(new MarkdownChatResponseContentImpl(text.substring(curSearchIndex, codeStartIndex)));
|
|
335
|
-
// add code block as code content
|
|
336
|
-
const codeText = text.substring(newLineIndex + 1, codeEndIndex).trimEnd();
|
|
337
|
-
result.push(new CodeChatResponseContentImpl(codeText, language));
|
|
338
|
-
curSearchIndex = codeEndIndex + 3;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (result.length > 0) {
|
|
342
|
-
result.forEach(r => {
|
|
343
|
-
request.response.response.addContent(r);
|
|
344
|
-
});
|
|
345
|
-
} else {
|
|
346
|
-
request.response.response.addContent(lastContent);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
337
|
+
await this.addStreamResponse(languageModelResponse, request);
|
|
349
338
|
request.response.complete();
|
|
350
|
-
this.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
339
|
+
if (this.defaultLogging) {
|
|
340
|
+
this.recordingService.recordResponse({
|
|
341
|
+
agentId: this.id,
|
|
342
|
+
sessionId: request.session.id,
|
|
343
|
+
timestamp: Date.now(),
|
|
344
|
+
requestId: request.response.requestId,
|
|
345
|
+
response: request.response.response.asString()
|
|
346
|
+
});
|
|
347
|
+
}
|
|
357
348
|
return;
|
|
358
349
|
}
|
|
359
350
|
this.logger.error(
|
|
@@ -366,11 +357,38 @@ export abstract class AbstractStreamParsingChatAgent extends AbstractChatAgent {
|
|
|
366
357
|
);
|
|
367
358
|
}
|
|
368
359
|
|
|
369
|
-
|
|
360
|
+
protected async addStreamResponse(languageModelResponse: LanguageModelStreamResponse, request: ChatRequestModelImpl): Promise<void> {
|
|
361
|
+
for await (const token of languageModelResponse.stream) {
|
|
362
|
+
const newContents = this.parse(token, request.response.response.content);
|
|
363
|
+
if (isArray(newContents)) {
|
|
364
|
+
request.response.response.addContents(newContents);
|
|
365
|
+
} else {
|
|
366
|
+
request.response.response.addContent(newContents);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const lastContent = request.response.response.content.pop();
|
|
370
|
+
if (lastContent === undefined) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const text = lastContent.asString?.();
|
|
374
|
+
if (text === undefined) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const result: ChatResponseContent[] = findFirstMatch(this.contentMatchers, text) ? this.parseContents(text) : [];
|
|
379
|
+
if (result.length > 0) {
|
|
380
|
+
request.response.response.addContents(result);
|
|
381
|
+
} else {
|
|
382
|
+
request.response.response.addContent(lastContent);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
protected parse(token: LanguageModelStreamResponsePart, previousContent: ChatResponseContent[]): ChatResponseContent | ChatResponseContent[] {
|
|
370
388
|
const content = token.content;
|
|
371
389
|
// eslint-disable-next-line no-null/no-null
|
|
372
390
|
if (content !== undefined && content !== null) {
|
|
373
|
-
return
|
|
391
|
+
return this.defaultContentFactory.create(content);
|
|
374
392
|
}
|
|
375
393
|
const toolCalls = token.tool_calls;
|
|
376
394
|
if (toolCalls !== undefined) {
|
|
@@ -378,7 +396,7 @@ export abstract class AbstractStreamParsingChatAgent extends AbstractChatAgent {
|
|
|
378
396
|
new ToolCallChatResponseContentImpl(toolCall.id, toolCall.function?.name, toolCall.function?.arguments, toolCall.finished, toolCall.result));
|
|
379
397
|
return toolCallContents;
|
|
380
398
|
}
|
|
381
|
-
return
|
|
399
|
+
return this.defaultContentFactory.create('');
|
|
382
400
|
}
|
|
383
401
|
|
|
384
402
|
}
|
package/src/common/chat-model.ts
CHANGED
|
@@ -73,6 +73,7 @@ export interface ChatRequestModel {
|
|
|
73
73
|
readonly response: ChatResponseModel;
|
|
74
74
|
readonly message: ParsedChatRequest;
|
|
75
75
|
readonly agentId?: string;
|
|
76
|
+
readonly data?: { [key: string]: unknown };
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
export interface ChatProgressMessage {
|
|
@@ -342,14 +343,29 @@ export class ChatRequestModelImpl implements ChatRequestModel {
|
|
|
342
343
|
protected _request: ChatRequest;
|
|
343
344
|
protected _response: ChatResponseModelImpl;
|
|
344
345
|
protected _agentId?: string;
|
|
346
|
+
protected _data: { [key: string]: unknown };
|
|
345
347
|
|
|
346
|
-
constructor(session: ChatModel, public readonly message: ParsedChatRequest, agentId?: string
|
|
348
|
+
constructor(session: ChatModel, public readonly message: ParsedChatRequest, agentId?: string,
|
|
349
|
+
data: { [key: string]: unknown } = {}) {
|
|
347
350
|
// TODO accept serialized data as a parameter to restore a previously saved ChatRequestModel
|
|
348
351
|
this._request = message.request;
|
|
349
352
|
this._id = generateUuid();
|
|
350
353
|
this._session = session;
|
|
351
354
|
this._response = new ChatResponseModelImpl(this._id, agentId);
|
|
352
355
|
this._agentId = agentId;
|
|
356
|
+
this._data = data;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
get data(): { [key: string]: unknown } | undefined {
|
|
360
|
+
return this._data;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
addData(key: string, value: unknown): void {
|
|
364
|
+
this._data[key] = value;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
getDataByKey(key: string): unknown {
|
|
368
|
+
return this._data[key];
|
|
353
369
|
}
|
|
354
370
|
|
|
355
371
|
get id(): string {
|
|
@@ -601,10 +617,20 @@ class ChatResponseImpl implements ChatResponse {
|
|
|
601
617
|
return this._content;
|
|
602
618
|
}
|
|
603
619
|
|
|
620
|
+
addContents(contents: ChatResponseContent[]): void {
|
|
621
|
+
contents.forEach(c => this.doAddContent(c));
|
|
622
|
+
this._onDidChangeEmitter.fire();
|
|
623
|
+
}
|
|
624
|
+
|
|
604
625
|
addContent(nextContent: ChatResponseContent): void {
|
|
605
626
|
// TODO: Support more complex merges affecting different content than the last, e.g. via some kind of ProcessorRegistry
|
|
606
627
|
// TODO: Support more of the built-in VS Code behavior, see
|
|
607
628
|
// https://github.com/microsoft/vscode/blob/a2cab7255c0df424027be05d58e1b7b941f4ea60/src/vs/workbench/contrib/chat/common/chatModel.ts#L188-L244
|
|
629
|
+
this.doAddContent(nextContent);
|
|
630
|
+
this._onDidChangeEmitter.fire();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
protected doAddContent(nextContent: ChatResponseContent): void {
|
|
608
634
|
if (ToolCallChatResponseContent.is(nextContent) && nextContent.id !== undefined) {
|
|
609
635
|
const fittingTool = this._content.find(c => ToolCallChatResponseContent.is(c) && c.id === nextContent.id);
|
|
610
636
|
if (fittingTool !== undefined) {
|
|
@@ -613,10 +639,9 @@ class ChatResponseImpl implements ChatResponse {
|
|
|
613
639
|
this._content.push(nextContent);
|
|
614
640
|
}
|
|
615
641
|
} else {
|
|
616
|
-
const lastElement =
|
|
617
|
-
this._content.length
|
|
618
|
-
|
|
619
|
-
: undefined;
|
|
642
|
+
const lastElement = this._content.length > 0
|
|
643
|
+
? this._content[this._content.length - 1]
|
|
644
|
+
: undefined;
|
|
620
645
|
if (lastElement?.kind === nextContent.kind && ChatResponseContent.hasMerge(lastElement)) {
|
|
621
646
|
const mergeSuccess = lastElement.merge(nextContent);
|
|
622
647
|
if (!mergeSuccess) {
|
|
@@ -627,7 +652,6 @@ class ChatResponseImpl implements ChatResponse {
|
|
|
627
652
|
}
|
|
628
653
|
}
|
|
629
654
|
this._updateResponseRepresentation();
|
|
630
|
-
this._onDidChangeEmitter.fire();
|
|
631
655
|
}
|
|
632
656
|
|
|
633
657
|
protected _updateResponseRepresentation(): void {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
-
import { AgentSpecificVariables,
|
|
17
|
+
import { AgentSpecificVariables, getJsonOfText, getTextOfResponse, LanguageModelResponse } from '@theia/ai-core';
|
|
18
18
|
import {
|
|
19
19
|
PromptTemplate
|
|
20
20
|
} from '@theia/ai-core/lib/common';
|
|
@@ -22,6 +22,7 @@ import { inject, injectable } from '@theia/core/shared/inversify';
|
|
|
22
22
|
import { ChatAgentService } from './chat-agent-service';
|
|
23
23
|
import { AbstractStreamParsingChatAgent, ChatAgent, SystemMessageDescription } from './chat-agents';
|
|
24
24
|
import { ChatRequestModelImpl, InformationalChatResponseContentImpl } from './chat-model';
|
|
25
|
+
import { generateUuid } from '@theia/core';
|
|
25
26
|
|
|
26
27
|
export const orchestratorTemplate: PromptTemplate = {
|
|
27
28
|
id: 'orchestrator-system',
|
|
@@ -59,6 +60,7 @@ You must only use the \`id\` attribute of the agent, never the name.
|
|
|
59
60
|
`};
|
|
60
61
|
|
|
61
62
|
export const OrchestratorChatAgentId = 'Orchestrator';
|
|
63
|
+
const OrchestatorRequestIdKey = 'orchestatorRequestIdKey';
|
|
62
64
|
|
|
63
65
|
@injectable()
|
|
64
66
|
export class OrchestratorChatAgent extends AbstractStreamParsingChatAgent implements ChatAgent {
|
|
@@ -74,7 +76,7 @@ export class OrchestratorChatAgent extends AbstractStreamParsingChatAgent implem
|
|
|
74
76
|
super(OrchestratorChatAgentId, [{
|
|
75
77
|
purpose: 'agent-selection',
|
|
76
78
|
identifier: 'openai/gpt-4o',
|
|
77
|
-
}], 'agent-selection', 'codicon codicon-symbol-boolean');
|
|
79
|
+
}], 'agent-selection', 'codicon codicon-symbol-boolean', undefined, undefined, false);
|
|
78
80
|
this.name = OrchestratorChatAgentId;
|
|
79
81
|
this.description = 'This agent analyzes the user request against the description of all available chat agents and selects the best fitting agent to answer the request \
|
|
80
82
|
(by using AI).The user\'s request will be directly delegated to the selected agent without further confirmation.';
|
|
@@ -88,8 +90,19 @@ export class OrchestratorChatAgent extends AbstractStreamParsingChatAgent implem
|
|
|
88
90
|
@inject(ChatAgentService)
|
|
89
91
|
protected chatAgentService: ChatAgentService;
|
|
90
92
|
|
|
91
|
-
override invoke(request: ChatRequestModelImpl): Promise<void> {
|
|
93
|
+
override async invoke(request: ChatRequestModelImpl): Promise<void> {
|
|
92
94
|
request.response.addProgressMessage({ content: 'Determining the most appropriate agent', status: 'inProgress' });
|
|
95
|
+
// We generate a dedicated ID for recording the orchestrator request/response, as we will forward the original request to another agent
|
|
96
|
+
const orchestartorRequestId = generateUuid();
|
|
97
|
+
request.addData(OrchestatorRequestIdKey, orchestartorRequestId);
|
|
98
|
+
const userPrompt = request.request.text;
|
|
99
|
+
this.recordingService.recordRequest({
|
|
100
|
+
agentId: this.id,
|
|
101
|
+
sessionId: request.session.id,
|
|
102
|
+
timestamp: Date.now(),
|
|
103
|
+
requestId: orchestartorRequestId,
|
|
104
|
+
request: userPrompt,
|
|
105
|
+
});
|
|
93
106
|
return super.invoke(request);
|
|
94
107
|
}
|
|
95
108
|
|
|
@@ -100,8 +113,20 @@ export class OrchestratorChatAgent extends AbstractStreamParsingChatAgent implem
|
|
|
100
113
|
|
|
101
114
|
protected override async addContentsToResponse(response: LanguageModelResponse, request: ChatRequestModelImpl): Promise<void> {
|
|
102
115
|
let agentIds: string[] = [];
|
|
116
|
+
const responseText = await getTextOfResponse(response);
|
|
117
|
+
// We use the previously generated, dedicated ID to log the orchestrator response before we forward the original request
|
|
118
|
+
const orchestratorRequestId = request.getDataByKey(OrchestatorRequestIdKey);
|
|
119
|
+
if (typeof orchestratorRequestId === 'string') {
|
|
120
|
+
this.recordingService.recordResponse({
|
|
121
|
+
agentId: this.id,
|
|
122
|
+
sessionId: request.session.id,
|
|
123
|
+
timestamp: Date.now(),
|
|
124
|
+
requestId: orchestratorRequestId,
|
|
125
|
+
response: responseText,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
103
128
|
try {
|
|
104
|
-
const jsonResponse = await
|
|
129
|
+
const jsonResponse = await getJsonOfText(responseText);
|
|
105
130
|
if (Array.isArray(jsonResponse)) {
|
|
106
131
|
agentIds = jsonResponse.filter((id: string) => id !== this.id);
|
|
107
132
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2024 EclipseSource GmbH.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { expect } from 'chai';
|
|
18
|
+
import { ChatResponseContent, CodeChatResponseContentImpl, MarkdownChatResponseContentImpl } from './chat-model';
|
|
19
|
+
import { parseContents } from './parse-contents';
|
|
20
|
+
import { CodeContentMatcher, ResponseContentMatcher } from './response-content-matcher';
|
|
21
|
+
|
|
22
|
+
export class CommandChatResponseContentImpl implements ChatResponseContent {
|
|
23
|
+
constructor(public readonly command: string) { }
|
|
24
|
+
kind = 'command';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const CommandContentMatcher: ResponseContentMatcher = {
|
|
28
|
+
start: /^<command>$/m,
|
|
29
|
+
end: /^<\/command>$/m,
|
|
30
|
+
contentFactory: (content: string) => {
|
|
31
|
+
const code = content.replace(/^<command>\n|<\/command>$/g, '');
|
|
32
|
+
return new CommandChatResponseContentImpl(code.trim());
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe('parseContents', () => {
|
|
37
|
+
it('should parse code content', () => {
|
|
38
|
+
const text = '```typescript\nconsole.log("Hello World");\n```';
|
|
39
|
+
const result = parseContents(text);
|
|
40
|
+
expect(result).to.deep.equal([new CodeChatResponseContentImpl('console.log("Hello World");', 'typescript')]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should parse markdown content', () => {
|
|
44
|
+
const text = 'Hello **World**';
|
|
45
|
+
const result = parseContents(text);
|
|
46
|
+
expect(result).to.deep.equal([new MarkdownChatResponseContentImpl('Hello **World**')]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should parse multiple content blocks', () => {
|
|
50
|
+
const text = '```typescript\nconsole.log("Hello World");\n```\nHello **World**';
|
|
51
|
+
const result = parseContents(text);
|
|
52
|
+
expect(result).to.deep.equal([
|
|
53
|
+
new CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
|
|
54
|
+
new MarkdownChatResponseContentImpl('\nHello **World**')
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should parse multiple content blocks with different languages', () => {
|
|
59
|
+
const text = '```typescript\nconsole.log("Hello World");\n```\n```python\nprint("Hello World")\n```';
|
|
60
|
+
const result = parseContents(text);
|
|
61
|
+
expect(result).to.deep.equal([
|
|
62
|
+
new CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
|
|
63
|
+
new CodeChatResponseContentImpl('print("Hello World")', 'python')
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should parse multiple content blocks with different languages and markdown', () => {
|
|
68
|
+
const text = '```typescript\nconsole.log("Hello World");\n```\nHello **World**\n```python\nprint("Hello World")\n```';
|
|
69
|
+
const result = parseContents(text);
|
|
70
|
+
expect(result).to.deep.equal([
|
|
71
|
+
new CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
|
|
72
|
+
new MarkdownChatResponseContentImpl('\nHello **World**\n'),
|
|
73
|
+
new CodeChatResponseContentImpl('print("Hello World")', 'python')
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should parse content blocks with empty content', () => {
|
|
78
|
+
const text = '```typescript\n```\nHello **World**\n```python\nprint("Hello World")\n```';
|
|
79
|
+
const result = parseContents(text);
|
|
80
|
+
expect(result).to.deep.equal([
|
|
81
|
+
new CodeChatResponseContentImpl('', 'typescript'),
|
|
82
|
+
new MarkdownChatResponseContentImpl('\nHello **World**\n'),
|
|
83
|
+
new CodeChatResponseContentImpl('print("Hello World")', 'python')
|
|
84
|
+
]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should parse content with markdown, code, and markdown', () => {
|
|
88
|
+
const text = 'Hello **World**\n```typescript\nconsole.log("Hello World");\n```\nGoodbye **World**';
|
|
89
|
+
const result = parseContents(text);
|
|
90
|
+
expect(result).to.deep.equal([
|
|
91
|
+
new MarkdownChatResponseContentImpl('Hello **World**\n'),
|
|
92
|
+
new CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
|
|
93
|
+
new MarkdownChatResponseContentImpl('\nGoodbye **World**')
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should handle text with no special content', () => {
|
|
98
|
+
const text = 'Just some plain text.';
|
|
99
|
+
const result = parseContents(text);
|
|
100
|
+
expect(result).to.deep.equal([new MarkdownChatResponseContentImpl('Just some plain text.')]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should handle text with only start code block', () => {
|
|
104
|
+
const text = '```typescript\nconsole.log("Hello World");';
|
|
105
|
+
const result = parseContents(text);
|
|
106
|
+
expect(result).to.deep.equal([new MarkdownChatResponseContentImpl('```typescript\nconsole.log("Hello World");')]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should handle text with only end code block', () => {
|
|
110
|
+
const text = 'console.log("Hello World");\n```';
|
|
111
|
+
const result = parseContents(text);
|
|
112
|
+
expect(result).to.deep.equal([new MarkdownChatResponseContentImpl('console.log("Hello World");\n```')]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should handle text with unmatched code block', () => {
|
|
116
|
+
const text = '```typescript\nconsole.log("Hello World");\n```\n```python\nprint("Hello World")';
|
|
117
|
+
const result = parseContents(text);
|
|
118
|
+
expect(result).to.deep.equal([
|
|
119
|
+
new CodeChatResponseContentImpl('console.log("Hello World");', 'typescript'),
|
|
120
|
+
new MarkdownChatResponseContentImpl('\n```python\nprint("Hello World")')
|
|
121
|
+
]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should parse code block without newline after language', () => {
|
|
125
|
+
const text = '```typescript console.log("Hello World");```';
|
|
126
|
+
const result = parseContents(text);
|
|
127
|
+
expect(result).to.deep.equal([
|
|
128
|
+
new MarkdownChatResponseContentImpl('```typescript console.log("Hello World");```')
|
|
129
|
+
]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should parse with matches of multiple different matchers and default', () => {
|
|
133
|
+
const text = '<command>\nMY_SPECIAL_COMMAND\n</command>\nHello **World**\n```python\nprint("Hello World")\n```\n<command>\nMY_SPECIAL_COMMAND2\n</command>';
|
|
134
|
+
const result = parseContents(text, [CodeContentMatcher, CommandContentMatcher]);
|
|
135
|
+
expect(result).to.deep.equal([
|
|
136
|
+
new CommandChatResponseContentImpl('MY_SPECIAL_COMMAND'),
|
|
137
|
+
new MarkdownChatResponseContentImpl('\nHello **World**\n'),
|
|
138
|
+
new CodeChatResponseContentImpl('print("Hello World")', 'python'),
|
|
139
|
+
new CommandChatResponseContentImpl('MY_SPECIAL_COMMAND2'),
|
|
140
|
+
]);
|
|
141
|
+
});
|
|
142
|
+
});
|