@celljs/ai-core 3.4.3 → 3.5.1

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.
Files changed (93) hide show
  1. package/README.md +26 -1
  2. package/lib/common/chat/index.d.ts +1 -0
  3. package/lib/common/chat/index.d.ts.map +1 -1
  4. package/lib/common/chat/index.js +1 -0
  5. package/lib/common/chat/index.js.map +1 -1
  6. package/lib/common/chat/message/message-protocol.d.ts +29 -4
  7. package/lib/common/chat/message/message-protocol.d.ts.map +1 -1
  8. package/lib/common/chat/message/message-protocol.js.map +1 -1
  9. package/lib/common/chat/metadata/chat-generation-metadata.d.ts +2 -2
  10. package/lib/common/chat/metadata/chat-generation-metadata.d.ts.map +1 -1
  11. package/lib/common/chat/metadata/chat-generation-metadata.js +2 -3
  12. package/lib/common/chat/metadata/chat-generation-metadata.js.map +1 -1
  13. package/lib/common/chat/metadata/metadata-protocol.d.ts +15 -11
  14. package/lib/common/chat/metadata/metadata-protocol.d.ts.map +1 -1
  15. package/lib/common/chat/metadata/metadata-protocol.js +26 -41
  16. package/lib/common/chat/metadata/metadata-protocol.js.map +1 -1
  17. package/lib/common/chat/model/chat-protocol.d.ts +3 -0
  18. package/lib/common/chat/model/chat-protocol.d.ts.map +1 -1
  19. package/lib/common/chat/model/chat-protocol.js +18 -2
  20. package/lib/common/chat/model/chat-protocol.js.map +1 -1
  21. package/lib/common/chat/model/index.d.ts +1 -0
  22. package/lib/common/chat/model/index.d.ts.map +1 -1
  23. package/lib/common/chat/model/index.js +1 -0
  24. package/lib/common/chat/model/index.js.map +1 -1
  25. package/lib/common/chat/model/proxy-chat-model.d.ts +12 -0
  26. package/lib/common/chat/model/proxy-chat-model.d.ts.map +1 -0
  27. package/lib/common/chat/model/proxy-chat-model.js +58 -0
  28. package/lib/common/chat/model/proxy-chat-model.js.map +1 -0
  29. package/lib/common/chat/prompt/prompt-protocol.d.ts +2 -1
  30. package/lib/common/chat/prompt/prompt-protocol.d.ts.map +1 -1
  31. package/lib/common/chat/prompt/prompt-template.d.ts +2 -1
  32. package/lib/common/chat/prompt/prompt-template.d.ts.map +1 -1
  33. package/lib/common/chat/prompt/prompt-template.js +10 -4
  34. package/lib/common/chat/prompt/prompt-template.js.map +1 -1
  35. package/lib/common/chat/tool/index.d.ts +3 -0
  36. package/lib/common/chat/tool/index.d.ts.map +1 -0
  37. package/lib/common/chat/tool/index.js +6 -0
  38. package/lib/common/chat/tool/index.js.map +1 -0
  39. package/lib/common/chat/tool/tool-handler.d.ts +12 -0
  40. package/lib/common/chat/tool/tool-handler.d.ts.map +1 -0
  41. package/lib/common/chat/tool/tool-handler.js +54 -0
  42. package/lib/common/chat/tool/tool-handler.js.map +1 -0
  43. package/lib/common/chat/tool/tool-protocol.d.ts +8 -0
  44. package/lib/common/chat/tool/tool-protocol.d.ts.map +1 -0
  45. package/lib/common/chat/tool/tool-protocol.js +5 -0
  46. package/lib/common/chat/tool/tool-protocol.js.map +1 -0
  47. package/lib/common/error/error.d.ts +3 -0
  48. package/lib/common/error/error.d.ts.map +1 -1
  49. package/lib/common/error/error.js +7 -1
  50. package/lib/common/error/error.js.map +1 -1
  51. package/lib/common/model/function/function-callback-register.d.ts +9 -0
  52. package/lib/common/model/function/function-callback-register.d.ts.map +1 -0
  53. package/lib/common/model/function/function-callback-register.js +43 -0
  54. package/lib/common/model/function/function-callback-register.js.map +1 -0
  55. package/lib/common/model/function/function-protocol.d.ts +28 -2
  56. package/lib/common/model/function/function-protocol.d.ts.map +1 -1
  57. package/lib/common/model/function/function-protocol.js +42 -1
  58. package/lib/common/model/function/function-protocol.js.map +1 -1
  59. package/lib/common/model/function/index.d.ts +1 -0
  60. package/lib/common/model/function/index.d.ts.map +1 -1
  61. package/lib/common/model/function/index.js +1 -0
  62. package/lib/common/model/function/index.js.map +1 -1
  63. package/lib/common/utils/index.d.ts +1 -0
  64. package/lib/common/utils/index.d.ts.map +1 -1
  65. package/lib/common/utils/index.js +1 -0
  66. package/lib/common/utils/index.js.map +1 -1
  67. package/lib/common/utils/sse-util.d.ts +1 -1
  68. package/lib/common/utils/sse-util.d.ts.map +1 -1
  69. package/lib/common/utils/sse-util.js.map +1 -1
  70. package/lib/common/utils/usage-util.d.ts +17 -0
  71. package/lib/common/utils/usage-util.d.ts.map +1 -0
  72. package/lib/common/utils/usage-util.js +62 -0
  73. package/lib/common/utils/usage-util.js.map +1 -0
  74. package/package.json +5 -5
  75. package/src/common/chat/index.ts +1 -0
  76. package/src/common/chat/message/message-protocol.ts +31 -4
  77. package/src/common/chat/metadata/chat-generation-metadata.ts +4 -4
  78. package/src/common/chat/metadata/metadata-protocol.ts +33 -49
  79. package/src/common/chat/model/chat-protocol.ts +22 -4
  80. package/src/common/chat/model/index.ts +1 -0
  81. package/src/common/chat/model/proxy-chat-model.ts +54 -0
  82. package/src/common/chat/prompt/prompt-protocol.ts +2 -1
  83. package/src/common/chat/prompt/prompt-template.ts +12 -6
  84. package/src/common/chat/tool/index.ts +2 -0
  85. package/src/common/chat/tool/tool-handler.ts +51 -0
  86. package/src/common/chat/tool/tool-protocol.ts +9 -0
  87. package/src/common/error/error.ts +6 -0
  88. package/src/common/model/function/function-callback-register.ts +38 -0
  89. package/src/common/model/function/function-protocol.ts +59 -2
  90. package/src/common/model/function/index.ts +1 -0
  91. package/src/common/utils/index.ts +1 -0
  92. package/src/common/utils/sse-util.ts +1 -1
  93. package/src/common/utils/usage-util.ts +60 -0
@@ -15,12 +15,12 @@ export interface ChatGenerationMetadata extends ResultMetadata {
15
15
  * The reason this choice completed for the generation.
16
16
  */
17
17
  readonly finishReason?: string;
18
+
19
+ readonly metadata: Record<string, any>;
18
20
  }
19
21
 
20
22
  export class ChatGenerationMetadata {
21
23
 
22
- static EMPTY: ChatGenerationMetadata = ChatGenerationMetadata.from();
23
-
24
24
  /**
25
25
  * Factory method used to construct a new {@link ChatGenerationMetadata} from the
26
26
  * given finish reason and content filter metadata.
@@ -29,9 +29,9 @@ export class ChatGenerationMetadata {
29
29
  * @param contentFilterMetadata The content filter metadata.
30
30
  * @returns A new instance of {@link ChatGenerationMetadata}.
31
31
  */
32
- static from(finishReason?: string, contentFilterMetadata?: any): ChatGenerationMetadata {
32
+ static from(finishReason?: string, metadata: Record<string, any> = {}, contentFilterMetadata?: any): ChatGenerationMetadata {
33
33
  return {
34
- finishReason, contentFilterMetadata
34
+ finishReason, contentFilterMetadata, metadata
35
35
  };
36
36
  }
37
37
  }
@@ -10,25 +10,29 @@ export interface Usage {
10
10
  /**
11
11
  * The number of tokens used in the prompt of the AI request.
12
12
  */
13
- readonly promptTokens: number;
13
+ readonly promptTokens?: number;
14
14
 
15
15
  /**
16
16
  * The number of tokens returned in the generation (aka completion)
17
17
  * of the AI's response.
18
18
  */
19
- readonly generationTokens: number;
19
+ readonly generationTokens?: number;
20
20
 
21
21
  /**
22
22
  * The total number of tokens from both the prompt of an AI request
23
23
  * and generation of the AI's response.
24
24
  */
25
- readonly totalTokens: number;
25
+ readonly totalTokens?: number;
26
26
 
27
27
  }
28
28
 
29
29
  export namespace Usage {
30
- export function from(promptTokens: number, generationTokens: number): Usage {
31
- return { promptTokens, generationTokens, totalTokens: promptTokens + generationTokens };
30
+ export function createEmpty(): Usage {
31
+ return { promptTokens: 0, generationTokens: 0, totalTokens: 0 };
32
+ }
33
+
34
+ export function from(promptTokens = 0, generationTokens = 0, totalTokens = 0): Usage {
35
+ return { promptTokens, generationTokens, totalTokens: totalTokens ? totalTokens : promptTokens + generationTokens };
32
36
  }
33
37
  }
34
38
 
@@ -42,37 +46,37 @@ export interface RateLimit {
42
46
  * The maximum number of requests that are permitted before exhausting the
43
47
  * rate limit.
44
48
  */
45
- readonly requestsLimit: number;
49
+ readonly requestsLimit?: number;
46
50
 
47
51
  /**
48
52
  * The remaining number of requests that are permitted before exhausting the
49
53
  * requestsLimit rate limit.
50
54
  */
51
- readonly requestsRemaining: number;
55
+ readonly requestsRemaining?: number;
52
56
 
53
57
  /**
54
58
  * The duration time until the rate limit (based on requests) resets
55
59
  * to its requestsLimit initial state.
56
60
  */
57
- readonly requestsReset: number;
61
+ readonly requestsReset?: number;
58
62
 
59
63
  /**
60
64
  * The maximum number of tokens that are permitted before exhausting the rate
61
65
  * limit.
62
66
  */
63
- readonly tokensLimit: number;
67
+ readonly tokensLimit?: number;
64
68
 
65
69
  /**
66
70
  * The remaining number of tokens that are permitted before exhausting the
67
71
  * tokensLimit rate limit.
68
72
  */
69
- readonly tokensRemaining: number;
73
+ readonly tokensRemaining?: number;
70
74
 
71
75
  /**
72
76
  * The duration time until the rate limit (based on tokens) resets to
73
77
  * its tokensLimit initial state.
74
78
  */
75
- readonly tokensReset: number;
79
+ readonly tokensReset?: number;
76
80
 
77
81
  }
78
82
 
@@ -123,6 +127,12 @@ export namespace PromptMetadata {
123
127
  }
124
128
  }
125
129
 
130
+ export namespace RateLimit {
131
+ export function createEmpty(): RateLimit {
132
+ return { requestsLimit: 0, requestsRemaining: 0, requestsReset: 0, tokensLimit: 0, tokensRemaining: 0, tokensReset: 0 };
133
+ }
134
+ }
135
+
126
136
  export interface ChatResponseMetadata extends ResponseMetadata {
127
137
  id?: string;
128
138
  model?: string;
@@ -144,25 +154,7 @@ export interface ChatResponseMetadata extends ResponseMetadata {
144
154
 
145
155
  export class ChatResponseMetadataBuilder {
146
156
 
147
- private readonly chatResponseMetadata: ChatResponseMetadata = {
148
- id: '',
149
- model: '',
150
- rateLimit: {
151
- requestsLimit: 0,
152
- requestsRemaining: 0,
153
- requestsReset: 0,
154
- tokensLimit: 0,
155
- tokensRemaining: 0,
156
- tokensReset: 0
157
- },
158
- usage: {
159
- promptTokens: 0,
160
- generationTokens: 0,
161
- totalTokens: 0
162
- },
163
- promptMetadata: [] as PromptMetadata,
164
- extra: {}
165
- };
157
+ private readonly chatResponseMetadata: ChatResponseMetadata = ChatResponseMetadata.createEmpty();
166
158
 
167
159
  id(id: string): ChatResponseMetadataBuilder {
168
160
  this.chatResponseMetadata.id = id;
@@ -207,25 +199,17 @@ export class ChatResponseMetadataBuilder {
207
199
  }
208
200
 
209
201
  export namespace ChatResponseMetadata {
210
- export const EMPTY: ChatResponseMetadata = {
211
- id: '',
212
- model: '',
213
- rateLimit: {
214
- requestsLimit: 0,
215
- requestsRemaining: 0,
216
- requestsReset: 0,
217
- tokensLimit: 0,
218
- tokensRemaining: 0,
219
- tokensReset: 0
220
- },
221
- usage: {
222
- promptTokens: 0,
223
- generationTokens: 0,
224
- totalTokens: 0
225
- },
226
- promptMetadata: [] as PromptMetadata,
227
- extra: {}
228
- };
202
+
203
+ export function createEmpty(): ChatResponseMetadata {
204
+ return {
205
+ id: '',
206
+ model: '',
207
+ rateLimit: RateLimit.createEmpty(),
208
+ usage: Usage.createEmpty(),
209
+ promptMetadata: [] as PromptMetadata,
210
+ extra: {}
211
+ };
212
+ }
229
213
 
230
214
  export function builder(): ChatResponseMetadataBuilder {
231
215
  return new ChatResponseMetadataBuilder();
@@ -10,23 +10,41 @@ export const ChatModel = Symbol('ChatModel');
10
10
  * Represents a response returned by the AI.
11
11
  */
12
12
  export interface Generation extends ModelResult<AssistantMessage> {
13
-
13
+ metadata: ChatGenerationMetadata;
14
14
  }
15
15
 
16
16
  export interface ChatResponse extends ModelResponse<Generation> {
17
-
17
+ metadata: ChatResponseMetadata;
18
18
  }
19
19
 
20
20
  export namespace Generation {
21
- export function from(output: AssistantMessage, metadata = ChatGenerationMetadata.EMPTY): Generation {
21
+ export function from(output: AssistantMessage, metadata = ChatGenerationMetadata.from()): Generation {
22
22
  return { output, metadata };
23
23
  }
24
24
  }
25
25
 
26
26
  export namespace ChatResponse {
27
- export function from(results: Generation[], metadata = ChatResponseMetadata.EMPTY): ChatResponse {
27
+ export function from(results: Generation[], metadata = ChatResponseMetadata.createEmpty()): ChatResponse {
28
28
  return { result: results[0], results, metadata };
29
29
  }
30
+
31
+ export function isToolCall(chatResponse: ChatResponse, toolCallFinishReasons: Set<string>): boolean {
32
+ if (!chatResponse) {
33
+ return false;
34
+ }
35
+
36
+ const generations = chatResponse.results;
37
+ if (!generations || generations.length === 0) {
38
+ return false;
39
+ }
40
+
41
+ return generations.some(g => doIsToolCall(g, toolCallFinishReasons));
42
+ }
43
+
44
+ function doIsToolCall(generation: Generation, toolCallFinishReasons: Set<string>): boolean {
45
+ const finishReason = generation.metadata.finishReason ?? '';
46
+ return generation.output.toolCalls?.length > 0 && toolCallFinishReasons.has(finishReason.toLowerCase());
47
+ }
30
48
  }
31
49
 
32
50
  export interface StreamingChatModel extends StreamingModel<Prompt, ChatResponse> {
@@ -1 +1,2 @@
1
1
  export * from './chat-protocol';
2
+ export * from './proxy-chat-model';
@@ -0,0 +1,54 @@
1
+ import { Autowired, Component, IllegalArgumentError } from '@celljs/core';
2
+ import { ChatModel, ChatResponse } from './chat-protocol';
3
+ import { Observable } from 'rxjs';
4
+ import { Prompt } from '../prompt';
5
+
6
+ @Component(ProxyChatModel)
7
+ export class ProxyChatModel implements ChatModel {
8
+
9
+ @Autowired(ChatModel)
10
+ protected readonly chatModels: ChatModel[];
11
+
12
+ protected readonly chatModelMap: Map<string, ChatModel> = new Map();
13
+
14
+ protected init() {
15
+ for (const chatModel of this.chatModels) {
16
+ if ('provider' in chatModel) {
17
+ // @ts-ignore
18
+ this.chatModelMap.set(chatModel.provider as string, chatModel);
19
+ } else {
20
+ const provider = chatModel.constructor.name.split('ChatModel')[0].toLowerCase();
21
+ this.chatModelMap.set(provider, chatModel);
22
+ }
23
+ }
24
+ }
25
+
26
+ protected resolveChatModelAndPrompt(prompt: Prompt): [ChatModel, Prompt] {
27
+ const model = prompt.options.model;
28
+ if (!model) {
29
+ throw new IllegalArgumentError('Model not specified in prompt options.');
30
+ }
31
+ if (!model.includes(':')) {
32
+ throw new IllegalArgumentError('Invalid model name format. Expected "<provider>:<model-name>".');
33
+ }
34
+ const [provider, modelName] = model.split(':');
35
+ const chatModel = this.chatModelMap.get(provider);
36
+ if (!chatModel) {
37
+ throw new IllegalArgumentError(`Chat model provider "${provider}" not found.`);
38
+ }
39
+ const newPrompt = prompt.copy();
40
+ newPrompt.options.model = modelName;
41
+ return [chatModel, newPrompt];
42
+ }
43
+
44
+ call(prompt: Prompt): Promise<ChatResponse> {
45
+ const [chatModel, newPrompt] = this.resolveChatModelAndPrompt(prompt);
46
+ return chatModel.call(newPrompt);
47
+ }
48
+
49
+ stream(prompt: Prompt): Promise<Observable<ChatResponse>> {
50
+ const [chatModel, newPrompt] = this.resolveChatModelAndPrompt(prompt);
51
+ return chatModel.stream(newPrompt);
52
+ }
53
+
54
+ }
@@ -19,6 +19,7 @@ export interface ChatOptions extends ModelOptions {
19
19
 
20
20
  export interface Prompt extends ModelRequest<Message[]> {
21
21
  readonly contents: string;
22
+ readonly options: ChatOptions;
22
23
  copy(): Prompt;
23
24
  }
24
25
 
@@ -30,5 +31,5 @@ export interface PromptTemplateContext {
30
31
 
31
32
  export interface PromptTemplate {
32
33
  render(template: string, ctx?: PromptTemplateContext): Promise<string>;
33
- create(template: string, ctx?: PromptTemplateContext): Promise<Prompt>;
34
+ create(template: string | Message[], ctx?: PromptTemplateContext): Promise<Prompt>;
34
35
  }
@@ -1,7 +1,7 @@
1
1
  import { Autowired, Component, ExpressionHandler, IllegalArgumentError } from '@celljs/core';
2
2
  import { Prompt, PromptTemplate, PromptTemplateContext } from './prompt-protocol';
3
3
  import { PromptImpl } from './prompt';
4
- import { MessageType, SystemMessage } from '../message';
4
+ import { Message, MessageType, SystemMessage } from '../message';
5
5
 
6
6
  @Component(PromptTemplate)
7
7
  export class PromptTemplateImpl implements PromptTemplate {
@@ -15,11 +15,17 @@ export class PromptTemplateImpl implements PromptTemplate {
15
15
  }
16
16
  return template;
17
17
  }
18
- async create(template: string, ctx?: PromptTemplateContext): Promise<Prompt> {
19
- if (!ctx?.messageType || ctx?.messageType === MessageType.USER) {
20
- return new PromptImpl(await this.render(template, ctx), ctx?.chatOptions);
21
- } else if (ctx?.messageType === MessageType.SYSTEM) {
22
- return new PromptImpl(new SystemMessage(await this.render(template, ctx)), ctx?.chatOptions);
18
+ async create(template: string | Message[], ctx?: PromptTemplateContext): Promise<Prompt> {
19
+ if (Array.isArray(template)
20
+ && template.some(message => Message.isMessage(message))
21
+ ) {
22
+ return new PromptImpl(template, ctx?.chatOptions);
23
+ } else if (typeof template === 'string') {
24
+ if (!ctx?.messageType || ctx?.messageType === MessageType.USER) {
25
+ return new PromptImpl(await this.render(template, ctx), ctx?.chatOptions);
26
+ } else if (ctx?.messageType === MessageType.SYSTEM) {
27
+ return new PromptImpl(new SystemMessage(await this.render(template, ctx)), ctx?.chatOptions);
28
+ }
23
29
  }
24
30
  throw new IllegalArgumentError(`Invalid message type: ${ctx?.messageType}`);
25
31
  }
@@ -0,0 +1,2 @@
1
+ export * from './tool-handler';
2
+ export * from './tool-protocol';
@@ -0,0 +1,51 @@
1
+ import { Autowired, Component, IllegalStateError } from '@celljs/core';
2
+ import { FunctionCallbackRegister, FunctionCallingOptions, ToolContext } from '../../model';
3
+ import { ChatResponse } from '../model';
4
+ import { Prompt } from '../prompt';
5
+ import { ToolHandler } from './tool-protocol';
6
+ import { AssistantMessage, Message, ToolResponse, ToolResponseMessage } from '../message';
7
+
8
+ @Component(ToolHandler)
9
+ export class ToolHanderImpl implements ToolHandler {
10
+
11
+ @Autowired(FunctionCallbackRegister)
12
+ protected readonly functionCallbackRegister: FunctionCallbackRegister;
13
+
14
+ protected async executeFunctions(assistantMessage: AssistantMessage, toolContext: ToolContext): Promise<ToolResponseMessage> {
15
+ const toolResponses: ToolResponse[] = [];
16
+ for (const toolCall of assistantMessage.toolCalls) {
17
+ const functionResponse = await this.functionCallbackRegister.call(toolCall.name, toolCall.arguments, toolContext);
18
+ toolResponses.push({
19
+ id: toolCall.id,
20
+ name: toolCall.name,
21
+ responseData: functionResponse
22
+ });
23
+ }
24
+ return new ToolResponseMessage(toolResponses);
25
+ }
26
+
27
+ protected buildToolCallConversation(previousMessages: Message[], assistantMessage: AssistantMessage, toolResponseMessage: ToolResponseMessage): Message[] {
28
+ const messages = [...previousMessages];
29
+ messages.push(assistantMessage);
30
+ messages.push(toolResponseMessage);
31
+ return messages;
32
+ }
33
+
34
+ async handle(prompt: Prompt, chatResponse: ChatResponse): Promise<Message[]> {
35
+ const toolCallGeneration = chatResponse.results.find(result => result.output.toolCalls?.length > 0);
36
+ if (!toolCallGeneration) {
37
+ throw new IllegalStateError('No tool call generation found in the response!');
38
+ }
39
+ const assistantMessage = toolCallGeneration.output;
40
+ let toolContext: ToolContext = {};
41
+ const options = prompt.options;
42
+ if (FunctionCallingOptions.is(options) && options.toolContext) {
43
+ toolContext = { ...options.toolContext };
44
+ const toolCallHistory = prompt.copy().instructions;
45
+ toolContext[ToolContext.TOOL_CALL_HISTORY] = toolCallHistory;
46
+ }
47
+ const toolResponseMessage = await this.executeFunctions(assistantMessage, toolContext);
48
+ const toolConversationHistory = this.buildToolCallConversation(prompt.instructions, assistantMessage, toolResponseMessage);
49
+ return toolConversationHistory;
50
+ }
51
+ }
@@ -0,0 +1,9 @@
1
+ import { Message } from '../message';
2
+ import { ChatResponse } from '../model';
3
+ import { Prompt } from '../prompt';
4
+
5
+ export const ToolHandler = Symbol('ToolHandler');
6
+
7
+ export interface ToolHandler {
8
+ handle(prompt: Prompt, chatResponse: ChatResponse): Promise<Message[]>;
9
+ }
@@ -5,3 +5,9 @@ export class AIError extends CustomError {
5
5
  super(message);
6
6
  }
7
7
  }
8
+
9
+ export class NotFoundError extends AIError {
10
+ constructor(message: string) {
11
+ super(message);
12
+ }
13
+ }
@@ -0,0 +1,38 @@
1
+ import { Component } from '@celljs/core';
2
+ import { FunctionCallback, FunctionCallbackRegister, ToolContext } from './function-protocol';
3
+ import { NotFoundError } from '../../error';
4
+
5
+ @Component(FunctionCallbackRegister)
6
+ export class FunctionCallbackRegisterImpl implements FunctionCallbackRegister {
7
+
8
+ private functionCallbacks: Map<string, FunctionCallback> = new Map<string, FunctionCallback>();
9
+
10
+ register(functionCallback: FunctionCallback): void {
11
+ this.functionCallbacks.set(functionCallback.name, functionCallback);
12
+ }
13
+
14
+ unregister(functionName: string): void {
15
+ this.functionCallbacks.delete(functionName);
16
+ }
17
+
18
+ call(functionName: string, functionArguments: string, toolContext: ToolContext): Promise<string> {
19
+ const functionCallback = this.functionCallbacks.get(functionName);
20
+ if (functionCallback) {
21
+ return functionCallback.call(functionArguments, toolContext);
22
+ }
23
+ throw new NotFoundError(`Function ${functionName} not found`);
24
+ }
25
+
26
+ resolve(functionNames: string[]): FunctionCallback[] {
27
+ const functionCallbacks: FunctionCallback[] = [];
28
+ new Set(functionNames).forEach(functionName => {
29
+ const functionCallback = this.functionCallbacks.get(functionName);
30
+ if (functionCallback) {
31
+ functionCallbacks.push(functionCallback);
32
+ } else {
33
+ throw new NotFoundError(`No function callback found for name: ${functionName}`);
34
+ }
35
+ });
36
+ return functionCallbacks;
37
+ }
38
+ }
@@ -1,5 +1,13 @@
1
1
  import { ChatOptions } from '../../chat/prompt';
2
2
 
3
+ export const FunctionCallbackRegister = Symbol('FunctionCallbackRegister');
4
+
5
+ export interface ToolContext extends Record<string, any> {}
6
+
7
+ export namespace ToolContext {
8
+ export const TOOL_CALL_HISTORY = 'TOOL_CALL_HISTORY';
9
+ }
10
+
3
11
  /**
4
12
  * Represents a model function call handler. Implementations are registered with the
5
13
  * Models and called on prompts that trigger the function call.
@@ -28,9 +36,10 @@ export interface FunctionCallback {
28
36
  * @param functionArguments JSON string with the function arguments to be passed to the
29
37
  * function. The arguments are defined as JSON schema usually registered with the the
30
38
  * model.
39
+ * @param toolContext Optional tool context to be passed to the function call.
31
40
  * @return String containing the function call response.
32
41
  */
33
- call(functionArguments: string): Promise<string>;
42
+ call(functionArguments: string, toolContext?: ToolContext): Promise<string>;
34
43
 
35
44
  }
36
45
 
@@ -49,13 +58,61 @@ export interface FunctionCallingOptions {
49
58
  * List of function names from the ChatClient registry to be used in the next
50
59
  * chat completion requests.
51
60
  */
52
- functions: Set<String>;
61
+ functions: Set<string>;
62
+
63
+ proxyToolCalls?: boolean;
64
+
65
+ toolContext?: ToolContext;
53
66
  }
54
67
 
55
68
  export namespace FunctionCallingOptions {
56
69
  export function is(obj: any): obj is FunctionCallingOptions {
57
70
  return obj && obj.functionCallbacks !== undefined && obj.functions !== undefined;
58
71
  }
72
+
73
+ export function getEnabledFunctionsToCall(runtimeFunctionOptions: FunctionCallingOptions): Set<string> {
74
+ const enabledFunctionsToCall = new Set<string>();
75
+ if (runtimeFunctionOptions) {
76
+ // Add the explicitly enabled functions.
77
+ if (runtimeFunctionOptions.functions) {
78
+ runtimeFunctionOptions.functions.forEach(fn => {
79
+ enabledFunctionsToCall.add(fn);
80
+ });
81
+ }
82
+
83
+ if (runtimeFunctionOptions.functionCallbacks) {
84
+ runtimeFunctionOptions.functionCallbacks.forEach(functionCallback => {
85
+ // Automatically enable the function, usually from prompt callback.
86
+ enabledFunctionsToCall.add(functionCallback.name);
87
+ });
88
+ }
89
+ }
90
+ return enabledFunctionsToCall;
91
+ }
92
+
93
+ /**
94
+ * Check if the proxyToolCalls is enabled for the given prompt or the default tool
95
+ * call options. The prompt options take precedence over the default options. When the
96
+ * proxyToolCalls is enabled the ChatModel implementation will pass the proxyToolCalls to the
97
+ * function call.
98
+ * @param chatOptions the chat options to check.
99
+ * @param defaultOptions the default tool call options to check.
100
+ * @return true if the proxyToolCalls is enabled, false otherwise.
101
+ */
102
+ export function isProxyToolCalls(chatOptions: ChatOptions, defaultOptions: FunctionCallingOptions): boolean {
103
+ if (FunctionCallingOptions.is(chatOptions) && chatOptions.proxyToolCalls !== undefined) {
104
+ return chatOptions.proxyToolCalls;
105
+ }
106
+ return defaultOptions.proxyToolCalls ?? false;
107
+ }
108
+
109
+ }
110
+
111
+ export interface FunctionCallbackRegister {
112
+ resolve(functionNames: string[]): FunctionCallback[];
113
+ register(functionCallback: FunctionCallback): void;
114
+ unregister(functionName: string): void;
115
+ call(functionName: string, functionArguments: string, toolContext: ToolContext): Promise<string>;
59
116
  }
60
117
 
61
118
  export interface PortableFunctionCallingOptions extends FunctionCallingOptions, ChatOptions {
@@ -1 +1,2 @@
1
1
  export * from './function-protocol';
2
+ export * from './function-callback-register';
@@ -1,2 +1,3 @@
1
1
  export * from './prompt-util';
2
2
  export * from './sse-util';
3
+ export * from './usage-util';
@@ -5,7 +5,7 @@ import { Bytes } from '@celljs/core';
5
5
  /**
6
6
  * SSE utility class.
7
7
  */
8
- export class SSEUtil {
8
+ export abstract class SSEUtil {
9
9
 
10
10
  static toObservable<Data>(readableStream: ReadableStream, controller?: AbortController): Observable<StreamEvent<Data>> {
11
11
  return new Observable<StreamEvent<Data>>(subscriber => {
@@ -0,0 +1,60 @@
1
+ import { ChatResponse } from '../chat';
2
+ import { Usage } from '../chat/metadata';
3
+
4
+ /**
5
+ * An utility class to provide support methods handling {@link Usage}.
6
+ */
7
+ export abstract class UsageUtil {
8
+
9
+ /**
10
+ * Accumulate usage tokens from the previous chat response to the current usage
11
+ * tokens.
12
+ * @param currentUsage the current usage.
13
+ * @param previousChatResponse the previous chat response.
14
+ * @return accumulated usage.
15
+ */
16
+ public static getCumulativeUsage(currentUsage: Usage, previousChatResponse?: ChatResponse): Usage {
17
+ let usageFromPreviousChatResponse: Usage | undefined = undefined;
18
+ if (previousChatResponse?.metadata?.usage) {
19
+ usageFromPreviousChatResponse = previousChatResponse.metadata.usage;
20
+ } else {
21
+ return currentUsage;
22
+ }
23
+ // For a valid usage from previous chat response, accumulate it to the current
24
+ // usage.
25
+ if (!this.isEmpty(currentUsage)) {
26
+ let promptTokens = currentUsage.promptTokens ?? 0;
27
+ let generationTokens = currentUsage.generationTokens ?? 0;
28
+ let totalTokens = currentUsage.totalTokens ?? 0;
29
+ // Make sure to accumulate the usage from the previous chat response.
30
+ promptTokens += usageFromPreviousChatResponse.promptTokens ?? 0;
31
+ generationTokens += usageFromPreviousChatResponse.generationTokens ?? 0;
32
+ totalTokens += usageFromPreviousChatResponse.totalTokens ?? 0;
33
+ return Usage.from(promptTokens, generationTokens, totalTokens);
34
+ }
35
+ // When current usage is empty, return the usage from the previous chat response.
36
+ return usageFromPreviousChatResponse;
37
+ }
38
+
39
+ // /**
40
+ // * Check if the {@link Usage} is empty. Returns true when the {@link Usage} is null.
41
+ // * Returns true when the {@link Usage} has zero tokens.
42
+ // * @param usage the usage to check against.
43
+ // * @return the boolean value to represent if it is empty.
44
+ // */
45
+ // static isEmpty(usage: Usage | null): boolean {
46
+ // if (!usage) {
47
+ // return true;
48
+ // } else if (usage != null && usage.totalTokens === 0) {
49
+ // return true;
50
+ // }
51
+ // return false;
52
+ // }
53
+ static isEmpty(usage: Usage | null): boolean {
54
+ if (!usage) {
55
+ return true;
56
+ }
57
+
58
+ return false;
59
+ }
60
+ }