diff --git a/.gitignore b/.gitignore
index d7adbe2..a0ad719 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,6 @@ pnpmfile.js
# env files
.env
!.env.example
+
+# Sensible example
+examples/insurance
diff --git a/.idea/runConfigurations/build_watch.xml b/.idea/runConfigurations/build_watch.xml
new file mode 100644
index 0000000..505a13e
--- /dev/null
+++ b/.idea/runConfigurations/build_watch.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/tests.xml b/.idea/runConfigurations/tests.xml
new file mode 100644
index 0000000..71bf2fb
--- /dev/null
+++ b/.idea/runConfigurations/tests.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dist/index.cjs b/dist/index.cjs
index a912bb6..436daac 100644
--- a/dist/index.cjs
+++ b/dist/index.cjs
@@ -13,7 +13,7 @@ class Assistant {
constructor(params) {
this.client = params.client;
this.instructions = params.instructions;
- this.functions = params.functions;
+ this.tools = params.tools;
this.deployment = params.deployment;
}
listChatCompletions(messages) {
@@ -24,7 +24,7 @@ class Assistant {
};
messages = [systemMessage, ...messages];
const options = {
- functions: this.functions,
+ tools: this.tools,
};
const completions = this.client.listChatCompletions(this.deployment, messages, options);
return stream.Readable.from(completions, {
@@ -36,8 +36,9 @@ class Assistant {
class Thread extends EventEmitter {
constructor(messages = []) {
super();
- this.messages = messages;
+ this.messages = [];
this._stream = null;
+ this.messages = messages;
}
get stream() {
if (!this._stream) {
@@ -46,8 +47,7 @@ class Thread extends EventEmitter {
return this._stream;
}
addMessage(message) {
- this.messages.push(message);
- this.emit('message', message);
+ this.doAddMessage(message);
}
run(assistant) {
this._stream = new stream.Readable({
@@ -57,7 +57,8 @@ class Thread extends EventEmitter {
}
doRun(assistant) {
this.emit('in_progress');
- const stream = assistant.listChatCompletions(this.messages);
+ const messages = this.getRequestMessages();
+ const stream = assistant.listChatCompletions(messages);
/**
* When the LLM responds with a function call, the first completion's first choice looks like this:
* {
@@ -90,12 +91,32 @@ class Thread extends EventEmitter {
if (!delta) {
throw new Error('No delta returned');
}
- if (delta.functionCall) {
- const name = delta.functionCall.name;
- this.handleStreamAsFunctionCall(name, stream, assistant);
+ if (delta.toolCalls.length > 0) {
+ this.handleStreamAsToolCalls(delta.toolCalls, stream, assistant);
}
else {
- this.handleStreamAsChatMessage(stream);
+ this.handleStreamAsChatResponseMessage(stream);
+ }
+ });
+ }
+ /**
+ * Convert the mix of ChatRequestMessages and ChatResponseMessages to ChatRequestMessages only
+ * so they can be sent again to the LLM.
+ */
+ getRequestMessages() {
+ return this.messages.map((m) => {
+ if (m.role === 'system' || m.role === 'user' || m.role === 'tool') {
+ // These are messages from the application (a.k.a request messages)
+ return m;
+ }
+ else {
+ // These are messages from the assistant (a.k.a response messages)
+ const responseMessage = m;
+ return {
+ role: 'assistant',
+ content: responseMessage.content,
+ toolCalls: responseMessage.toolCalls,
+ };
}
});
}
@@ -117,8 +138,8 @@ class Thread extends EventEmitter {
* }
* { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call
*/
- handleStreamAsFunctionCall(name, stream, assistant) {
- let args = '';
+ handleStreamAsToolCalls(toolCalls, stream, assistant) {
+ const argsList = Array(toolCalls.length).fill('');
stream.on('data', (completions) => {
const choice = completions.choices[0];
if (!choice) {
@@ -128,36 +149,35 @@ class Thread extends EventEmitter {
if (!delta) {
throw new Error('No delta returned');
}
- if (delta.functionCall) {
- const functionCall = delta.functionCall;
- if (functionCall.arguments) {
- args += functionCall.arguments;
- }
- }
- if (choice.finishReason === 'function_call') {
- const functionCall = {
- name,
- arguments: args,
- };
+ delta.toolCalls.forEach((toolCall, index) => {
+ argsList[index] += toolCall.function.arguments;
+ });
+ if (choice.finishReason === 'tool_calls') {
+ const finalToolCalls = toolCalls.map((toolCall, index) => ({
+ ...toolCall,
+ function: {
+ ...toolCall.function,
+ arguments: argsList[index],
+ },
+ }));
// Adds the assistant's response to the messages
const message = {
role: 'assistant',
content: null,
- functionCall,
+ toolCalls: finalToolCalls,
};
- this.addMessage(message);
- const requiredAction = new RequiredAction({
- name,
- arguments: args,
- });
- requiredAction.on('submitting', (toolOutput) => {
- // Adds the tool output to the messages
- const message = {
- role: 'function',
- name: functionCall.name,
- content: JSON.stringify(toolOutput),
- };
- this.addMessage(message);
+ this.doAddMessage(message);
+ const requiredAction = new RequiredAction(finalToolCalls);
+ requiredAction.on('submitting', (toolOutputs) => {
+ // Adds the tool outputs to the messages
+ for (const toolOutput of toolOutputs) {
+ const message = {
+ role: 'tool',
+ content: JSON.stringify(toolOutput.value),
+ toolCallId: toolOutput.callId,
+ };
+ this.doAddMessage(message);
+ }
this.doRun(assistant);
});
this.emit('requires_action', requiredAction);
@@ -182,7 +202,7 @@ class Thread extends EventEmitter {
* }
* { index: 0, finishReason: 'stop', delta: {} } <---- end of the message
*/
- handleStreamAsChatMessage(stream) {
+ handleStreamAsChatResponseMessage(stream) {
let content = '';
stream.on('data', (completions) => {
const choice = completions.choices[0];
@@ -206,28 +226,44 @@ class Thread extends EventEmitter {
const message = {
role: 'assistant',
content,
+ toolCalls: [],
};
- this.addMessage(message);
+ this.doAddMessage(message);
this.emit('completed');
this._stream?.push(null);
}
});
}
+ doAddMessage(message) {
+ this.messages.push(message);
+ this.emit('message', message);
+ if (isChatRequestMessage(message)) {
+ this.emit('message:request', message);
+ }
+ else {
+ this.emit('message:response', message);
+ }
+ }
}
class RequiredAction extends EventEmitter {
- constructor(functionCall) {
+ constructor(toolCalls) {
super();
- this.toolCall = {
- name: functionCall.name,
- arguments: JSON.parse(functionCall.arguments),
- };
+ this.toolCalls = toolCalls;
}
- submitToolOutput(toolOutput) {
- this.emit('submitting', toolOutput);
+ submitToolOutputs(toolOutputs) {
+ this.emit('submitting', toolOutputs);
}
}
+function isChatResponseMessage(m) {
+ return 'toolCalls' in m;
+}
+function isChatRequestMessage(m) {
+ return !isChatResponseMessage(m);
+}
exports.Assistant = Assistant;
exports.RequiredAction = RequiredAction;
exports.Thread = Thread;
+exports.isChatRequestMessage = isChatRequestMessage;
+exports.isChatResponseMessage = isChatResponseMessage;
//# sourceMappingURL=index.cjs.map
diff --git a/dist/index.cjs.map b/dist/index.cjs.map
index 5ad512b..f40f5c3 100644
--- a/dist/index.cjs.map
+++ b/dist/index.cjs.map
@@ -1 +1 @@
-{"version":3,"file":"index.cjs","sources":["../src/assistant/assistant.ts","../src/thread/thread.ts"],"sourcesContent":["import type { FunctionDefinition, OpenAIClient } from '@azure/openai';\nimport type { GetChatCompletionsOptions } from '@azure/openai/types/src/api/models';\nimport type { ChatMessage } from '@azure/openai/types/src/models/models';\nimport { Readable } from 'stream';\n\nexport interface AssistantCreateParams {\n client: OpenAIClient;\n instructions: string;\n functions: FunctionDefinition[];\n deployment: string;\n}\n\nexport class Assistant {\n public readonly client: OpenAIClient;\n\n private readonly instructions: string;\n private readonly functions: FunctionDefinition[];\n private readonly deployment: string;\n\n constructor(params: AssistantCreateParams) {\n this.client = params.client;\n this.instructions = params.instructions;\n this.functions = params.functions;\n this.deployment = params.deployment;\n }\n\n listChatCompletions(messages: ChatMessage[]): Readable {\n // Prepend the messages with our instructions as a \"system\" message\n const systemMessage: ChatMessage = {\n role: 'system',\n content: this.instructions,\n };\n messages = [systemMessage, ...messages];\n\n const options: GetChatCompletionsOptions = {\n functions: this.functions,\n };\n\n const completions = this.client.listChatCompletions(\n this.deployment,\n messages,\n options,\n );\n\n return Readable.from(completions, {\n objectMode: true,\n });\n }\n}\n","import type { ChatCompletions, ChatMessage, FunctionCall } from '@azure/openai';\nimport EventEmitter from 'events';\nimport { Readable } from 'stream';\nimport { Assistant } from '../assistant';\n\nexport class Thread extends EventEmitter {\n private _stream: Readable | null = null;\n\n constructor(private readonly messages: ChatMessage[] = []) {\n super();\n }\n\n get stream(): Readable | null {\n if (!this._stream) {\n return null;\n }\n\n return this._stream;\n }\n\n addMessage(message: ChatMessage): void {\n this.messages.push(message);\n this.emit('message', message);\n }\n\n run(assistant: Assistant): void {\n this._stream = new Readable({\n read: () => {},\n });\n this.doRun(assistant);\n }\n\n private doRun(assistant: Assistant): void {\n this.emit('in_progress');\n\n const stream = assistant.listChatCompletions(this.messages);\n\n /**\n * When the LLM responds with a function call, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * functionCall: { name: 'get_customer_profile', arguments: undefined }\n * },\n * contentFilterResults: {}\n * }\n *\n * When the LLM responds with a message, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * },\n * contentFilterResults: {}\n *\n * We're only interested in the first completion and then we let the dedicated handler handle the rest of the stream\n */\n stream.once('data', (completion: ChatCompletions) => {\n const choice = completion.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.functionCall) {\n const name = delta.functionCall.name;\n this.handleStreamAsFunctionCall(name, stream, assistant);\n } else {\n this.handleStreamAsChatMessage(stream);\n }\n });\n }\n\n /**\n * Handles the stream as a function call after we determined from the beginning of the stream that it is a function call.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '{\"' } }, <---- beginning of the arguments as a stringified JSON\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '\"}' } } <---- end of the arguments as a stringified JSON\n * }\n * { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call\n */\n private handleStreamAsFunctionCall(\n name: string,\n stream: Readable,\n assistant: Assistant,\n ): void {\n let args = '';\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.functionCall) {\n const functionCall = delta.functionCall;\n if (functionCall.arguments) {\n args += functionCall.arguments;\n }\n }\n\n if (choice.finishReason === 'function_call') {\n const functionCall: FunctionCall = {\n name,\n arguments: args,\n };\n\n // Adds the assistant's response to the messages\n const message: ChatMessage = {\n role: 'assistant',\n content: null,\n functionCall,\n };\n this.addMessage(message);\n\n const requiredAction = new RequiredAction({\n name,\n arguments: args,\n });\n\n requiredAction.on('submitting', (toolOutput: ToolOutput) => {\n // Adds the tool output to the messages\n const message: ChatMessage = {\n role: 'function',\n name: functionCall.name,\n content: JSON.stringify(toolOutput),\n };\n this.addMessage(message);\n\n this.doRun(assistant);\n });\n\n this.emit('requires_action', requiredAction);\n }\n });\n }\n\n /**\n * Handles the stream as a chat message after we determined from the beginning of the stream that it is a chat message.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \"Lorem\" }, <---- beginning of the response\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \" ipsum\" } <---- end of the response\n * }\n * { index: 0, finishReason: 'stop', delta: {} } <---- end of the message\n */\n private handleStreamAsChatMessage(stream: Readable): void {\n let content = '';\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.content) {\n content += delta.content;\n\n // Write also to the stream of the thread\n if (!this._stream) {\n throw new Error('No stream available');\n }\n this._stream?.push(delta.content);\n }\n\n if (choice.finishReason === 'stop') {\n // Adds the assistant's response to the messages\n const message: ChatMessage = {\n role: 'assistant',\n content,\n };\n this.addMessage(message);\n\n this.emit('completed');\n this._stream?.push(null);\n }\n });\n }\n}\n\nexport class RequiredAction extends EventEmitter {\n toolCall: ToolCall;\n\n constructor(functionCall: FunctionCall) {\n super();\n\n this.toolCall = {\n name: functionCall.name,\n arguments: JSON.parse(functionCall.arguments),\n };\n }\n\n submitToolOutput(toolOutput: ToolOutput): void {\n this.emit('submitting', toolOutput);\n }\n}\n\nexport interface ToolCall {\n name: string;\n arguments: Record;\n}\n\nexport interface ToolOutput {\n value: unknown;\n}\n"],"names":["Readable"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"index.cjs","sources":["../src/assistant/assistant.ts","../src/thread/thread.ts"],"sourcesContent":["import type {\n ChatRequestMessage,\n ChatRequestSystemMessage,\n GetChatCompletionsOptions,\n OpenAIClient,\n} from '@azure/openai';\nimport type { ChatCompletionsToolDefinition } from '@azure/openai/types/src/models/models';\nimport { Readable } from 'stream';\n\nexport interface AssistantCreateParams {\n client: OpenAIClient;\n instructions: string;\n tools: ChatCompletionsToolDefinition[];\n deployment: string;\n}\n\nexport class Assistant {\n public readonly client: OpenAIClient;\n\n private readonly instructions: string;\n private readonly tools: ChatCompletionsToolDefinition[];\n private readonly deployment: string;\n\n constructor(params: AssistantCreateParams) {\n this.client = params.client;\n this.instructions = params.instructions;\n this.tools = params.tools;\n this.deployment = params.deployment;\n }\n\n listChatCompletions(messages: ChatRequestMessage[]): Readable {\n // Prepend the messages with our instructions as a \"system\" message\n const systemMessage: ChatRequestSystemMessage = {\n role: 'system',\n content: this.instructions,\n };\n messages = [systemMessage, ...messages];\n\n const options: GetChatCompletionsOptions = {\n tools: this.tools,\n };\n\n const completions = this.client.listChatCompletions(\n this.deployment,\n messages,\n options,\n );\n\n return Readable.from(completions, {\n objectMode: true,\n });\n }\n}\n","import type {\n ChatCompletions,\n ChatCompletionsToolCall,\n ChatRequestMessage,\n ChatRequestToolMessage,\n ChatResponseMessage,\n} from '@azure/openai';\nimport EventEmitter from 'events';\nimport { Readable } from 'stream';\nimport { Assistant } from '../assistant';\n\nexport class Thread extends EventEmitter {\n private readonly messages: (ChatRequestMessage | ChatResponseMessage)[] =\n [];\n private _stream: Readable | null = null;\n\n constructor(messages: ChatRequestMessage[] = []) {\n super();\n this.messages = messages;\n }\n\n get stream(): Readable | null {\n if (!this._stream) {\n return null;\n }\n\n return this._stream;\n }\n\n addMessage(message: ChatRequestMessage): void {\n this.doAddMessage(message);\n }\n\n run(assistant: Assistant): void {\n this._stream = new Readable({\n read: () => {},\n });\n this.doRun(assistant);\n }\n\n private doRun(assistant: Assistant): void {\n this.emit('in_progress');\n\n const messages = this.getRequestMessages();\n\n const stream = assistant.listChatCompletions(messages);\n\n /**\n * When the LLM responds with a function call, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * functionCall: { name: 'get_customer_profile', arguments: undefined }\n * },\n * contentFilterResults: {}\n * }\n *\n * When the LLM responds with a message, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * },\n * contentFilterResults: {}\n *\n * We're only interested in the first completion and then we let the dedicated handler handle the rest of the stream\n */\n stream.once('data', (completion: ChatCompletions) => {\n const choice = completion.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.toolCalls.length > 0) {\n this.handleStreamAsToolCalls(\n delta.toolCalls,\n stream,\n assistant,\n );\n } else {\n this.handleStreamAsChatResponseMessage(stream);\n }\n });\n }\n\n /**\n * Convert the mix of ChatRequestMessages and ChatResponseMessages to ChatRequestMessages only\n * so they can be sent again to the LLM.\n */\n private getRequestMessages(): ChatRequestMessage[] {\n return this.messages.map((m) => {\n if (m.role === 'system' || m.role === 'user' || m.role === 'tool') {\n // These are messages from the application (a.k.a request messages)\n return m as ChatRequestMessage;\n } else {\n // These are messages from the assistant (a.k.a response messages)\n const responseMessage = m as ChatResponseMessage;\n return {\n role: 'assistant',\n content: responseMessage.content,\n toolCalls: responseMessage.toolCalls,\n };\n }\n });\n }\n\n /**\n * Handles the stream as a function call after we determined from the beginning of the stream that it is a function call.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '{\"' } }, <---- beginning of the arguments as a stringified JSON\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '\"}' } } <---- end of the arguments as a stringified JSON\n * }\n * { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call\n */\n private handleStreamAsToolCalls(\n toolCalls: ChatCompletionsToolCall[],\n stream: Readable,\n assistant: Assistant,\n ): void {\n const argsList = Array(toolCalls.length).fill('');\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n delta.toolCalls.forEach((toolCall, index) => {\n argsList[index] += toolCall.function.arguments;\n });\n\n if (choice.finishReason === 'tool_calls') {\n const finalToolCalls: ChatCompletionsToolCall[] = toolCalls.map(\n (toolCall, index) => ({\n ...toolCall,\n function: {\n ...toolCall.function,\n arguments: argsList[index],\n },\n }),\n );\n\n // Adds the assistant's response to the messages\n const message: ChatResponseMessage = {\n role: 'assistant',\n content: null,\n toolCalls: finalToolCalls,\n };\n this.doAddMessage(message);\n\n const requiredAction = new RequiredAction(finalToolCalls);\n\n requiredAction.on('submitting', (toolOutputs: ToolOutput[]) => {\n // Adds the tool outputs to the messages\n for (const toolOutput of toolOutputs) {\n const message: ChatRequestToolMessage = {\n role: 'tool',\n content: JSON.stringify(toolOutput.value),\n toolCallId: toolOutput.callId,\n };\n this.doAddMessage(message);\n }\n\n this.doRun(assistant);\n });\n\n this.emit('requires_action', requiredAction);\n }\n });\n }\n\n /**\n * Handles the stream as a chat message after we determined from the beginning of the stream that it is a chat message.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \"Lorem\" }, <---- beginning of the response\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \" ipsum\" } <---- end of the response\n * }\n * { index: 0, finishReason: 'stop', delta: {} } <---- end of the message\n */\n private handleStreamAsChatResponseMessage(stream: Readable): void {\n let content = '';\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.content) {\n content += delta.content;\n\n // Write also to the stream of the thread\n if (!this._stream) {\n throw new Error('No stream available');\n }\n this._stream?.push(delta.content);\n }\n\n if (choice.finishReason === 'stop') {\n // Adds the assistant's response to the messages\n const message: ChatResponseMessage = {\n role: 'assistant',\n content,\n toolCalls: [],\n };\n this.doAddMessage(message);\n\n this.emit('completed');\n this._stream?.push(null);\n }\n });\n }\n\n private doAddMessage(\n message: ChatRequestMessage | ChatResponseMessage,\n ): void {\n this.messages.push(message);\n this.emit('message', message);\n\n if (isChatRequestMessage(message)) {\n this.emit('message:request', message);\n } else {\n this.emit('message:response', message);\n }\n }\n}\n\nexport class RequiredAction extends EventEmitter {\n constructor(public readonly toolCalls: ChatCompletionsToolCall[]) {\n super();\n }\n\n submitToolOutputs(toolOutputs: ToolOutput[]): void {\n this.emit('submitting', toolOutputs);\n }\n}\n\nexport interface ToolCall {\n name: string;\n arguments: Record;\n}\n\nexport interface ToolOutput {\n callId: string;\n value: unknown;\n}\n\nexport function isChatResponseMessage(\n m: ChatRequestMessage | ChatResponseMessage,\n): m is ChatResponseMessage {\n return 'toolCalls' in m;\n}\n\nexport function isChatRequestMessage(\n m: ChatRequestMessage | ChatResponseMessage,\n): m is ChatRequestMessage {\n return !isChatResponseMessage(m);\n}\n"],"names":["Readable"],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/index.esm.js b/dist/index.esm.js
index c668fc4..2f8e194 100644
--- a/dist/index.esm.js
+++ b/dist/index.esm.js
@@ -11,7 +11,7 @@ class Assistant {
constructor(params) {
this.client = params.client;
this.instructions = params.instructions;
- this.functions = params.functions;
+ this.tools = params.tools;
this.deployment = params.deployment;
}
listChatCompletions(messages) {
@@ -22,7 +22,7 @@ class Assistant {
};
messages = [systemMessage, ...messages];
const options = {
- functions: this.functions,
+ tools: this.tools,
};
const completions = this.client.listChatCompletions(this.deployment, messages, options);
return Readable.from(completions, {
@@ -34,8 +34,9 @@ class Assistant {
class Thread extends EventEmitter {
constructor(messages = []) {
super();
- this.messages = messages;
+ this.messages = [];
this._stream = null;
+ this.messages = messages;
}
get stream() {
if (!this._stream) {
@@ -44,8 +45,7 @@ class Thread extends EventEmitter {
return this._stream;
}
addMessage(message) {
- this.messages.push(message);
- this.emit('message', message);
+ this.doAddMessage(message);
}
run(assistant) {
this._stream = new Readable({
@@ -55,7 +55,8 @@ class Thread extends EventEmitter {
}
doRun(assistant) {
this.emit('in_progress');
- const stream = assistant.listChatCompletions(this.messages);
+ const messages = this.getRequestMessages();
+ const stream = assistant.listChatCompletions(messages);
/**
* When the LLM responds with a function call, the first completion's first choice looks like this:
* {
@@ -88,12 +89,32 @@ class Thread extends EventEmitter {
if (!delta) {
throw new Error('No delta returned');
}
- if (delta.functionCall) {
- const name = delta.functionCall.name;
- this.handleStreamAsFunctionCall(name, stream, assistant);
+ if (delta.toolCalls.length > 0) {
+ this.handleStreamAsToolCalls(delta.toolCalls, stream, assistant);
}
else {
- this.handleStreamAsChatMessage(stream);
+ this.handleStreamAsChatResponseMessage(stream);
+ }
+ });
+ }
+ /**
+ * Convert the mix of ChatRequestMessages and ChatResponseMessages to ChatRequestMessages only
+ * so they can be sent again to the LLM.
+ */
+ getRequestMessages() {
+ return this.messages.map((m) => {
+ if (m.role === 'system' || m.role === 'user' || m.role === 'tool') {
+ // These are messages from the application (a.k.a request messages)
+ return m;
+ }
+ else {
+ // These are messages from the assistant (a.k.a response messages)
+ const responseMessage = m;
+ return {
+ role: 'assistant',
+ content: responseMessage.content,
+ toolCalls: responseMessage.toolCalls,
+ };
}
});
}
@@ -115,8 +136,8 @@ class Thread extends EventEmitter {
* }
* { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call
*/
- handleStreamAsFunctionCall(name, stream, assistant) {
- let args = '';
+ handleStreamAsToolCalls(toolCalls, stream, assistant) {
+ const argsList = Array(toolCalls.length).fill('');
stream.on('data', (completions) => {
const choice = completions.choices[0];
if (!choice) {
@@ -126,36 +147,35 @@ class Thread extends EventEmitter {
if (!delta) {
throw new Error('No delta returned');
}
- if (delta.functionCall) {
- const functionCall = delta.functionCall;
- if (functionCall.arguments) {
- args += functionCall.arguments;
- }
- }
- if (choice.finishReason === 'function_call') {
- const functionCall = {
- name,
- arguments: args,
- };
+ delta.toolCalls.forEach((toolCall, index) => {
+ argsList[index] += toolCall.function.arguments;
+ });
+ if (choice.finishReason === 'tool_calls') {
+ const finalToolCalls = toolCalls.map((toolCall, index) => ({
+ ...toolCall,
+ function: {
+ ...toolCall.function,
+ arguments: argsList[index],
+ },
+ }));
// Adds the assistant's response to the messages
const message = {
role: 'assistant',
content: null,
- functionCall,
+ toolCalls: finalToolCalls,
};
- this.addMessage(message);
- const requiredAction = new RequiredAction({
- name,
- arguments: args,
- });
- requiredAction.on('submitting', (toolOutput) => {
- // Adds the tool output to the messages
- const message = {
- role: 'function',
- name: functionCall.name,
- content: JSON.stringify(toolOutput),
- };
- this.addMessage(message);
+ this.doAddMessage(message);
+ const requiredAction = new RequiredAction(finalToolCalls);
+ requiredAction.on('submitting', (toolOutputs) => {
+ // Adds the tool outputs to the messages
+ for (const toolOutput of toolOutputs) {
+ const message = {
+ role: 'tool',
+ content: JSON.stringify(toolOutput.value),
+ toolCallId: toolOutput.callId,
+ };
+ this.doAddMessage(message);
+ }
this.doRun(assistant);
});
this.emit('requires_action', requiredAction);
@@ -180,7 +200,7 @@ class Thread extends EventEmitter {
* }
* { index: 0, finishReason: 'stop', delta: {} } <---- end of the message
*/
- handleStreamAsChatMessage(stream) {
+ handleStreamAsChatResponseMessage(stream) {
let content = '';
stream.on('data', (completions) => {
const choice = completions.choices[0];
@@ -204,26 +224,40 @@ class Thread extends EventEmitter {
const message = {
role: 'assistant',
content,
+ toolCalls: [],
};
- this.addMessage(message);
+ this.doAddMessage(message);
this.emit('completed');
this._stream?.push(null);
}
});
}
+ doAddMessage(message) {
+ this.messages.push(message);
+ this.emit('message', message);
+ if (isChatRequestMessage(message)) {
+ this.emit('message:request', message);
+ }
+ else {
+ this.emit('message:response', message);
+ }
+ }
}
class RequiredAction extends EventEmitter {
- constructor(functionCall) {
+ constructor(toolCalls) {
super();
- this.toolCall = {
- name: functionCall.name,
- arguments: JSON.parse(functionCall.arguments),
- };
+ this.toolCalls = toolCalls;
}
- submitToolOutput(toolOutput) {
- this.emit('submitting', toolOutput);
+ submitToolOutputs(toolOutputs) {
+ this.emit('submitting', toolOutputs);
}
}
+function isChatResponseMessage(m) {
+ return 'toolCalls' in m;
+}
+function isChatRequestMessage(m) {
+ return !isChatResponseMessage(m);
+}
-export { Assistant, RequiredAction, Thread };
+export { Assistant, RequiredAction, Thread, isChatRequestMessage, isChatResponseMessage };
//# sourceMappingURL=index.esm.js.map
diff --git a/dist/index.esm.js.map b/dist/index.esm.js.map
index 81615bc..66e5769 100644
--- a/dist/index.esm.js.map
+++ b/dist/index.esm.js.map
@@ -1 +1 @@
-{"version":3,"file":"index.esm.js","sources":["../src/assistant/assistant.ts","../src/thread/thread.ts"],"sourcesContent":["import type { FunctionDefinition, OpenAIClient } from '@azure/openai';\nimport type { GetChatCompletionsOptions } from '@azure/openai/types/src/api/models';\nimport type { ChatMessage } from '@azure/openai/types/src/models/models';\nimport { Readable } from 'stream';\n\nexport interface AssistantCreateParams {\n client: OpenAIClient;\n instructions: string;\n functions: FunctionDefinition[];\n deployment: string;\n}\n\nexport class Assistant {\n public readonly client: OpenAIClient;\n\n private readonly instructions: string;\n private readonly functions: FunctionDefinition[];\n private readonly deployment: string;\n\n constructor(params: AssistantCreateParams) {\n this.client = params.client;\n this.instructions = params.instructions;\n this.functions = params.functions;\n this.deployment = params.deployment;\n }\n\n listChatCompletions(messages: ChatMessage[]): Readable {\n // Prepend the messages with our instructions as a \"system\" message\n const systemMessage: ChatMessage = {\n role: 'system',\n content: this.instructions,\n };\n messages = [systemMessage, ...messages];\n\n const options: GetChatCompletionsOptions = {\n functions: this.functions,\n };\n\n const completions = this.client.listChatCompletions(\n this.deployment,\n messages,\n options,\n );\n\n return Readable.from(completions, {\n objectMode: true,\n });\n }\n}\n","import type { ChatCompletions, ChatMessage, FunctionCall } from '@azure/openai';\nimport EventEmitter from 'events';\nimport { Readable } from 'stream';\nimport { Assistant } from '../assistant';\n\nexport class Thread extends EventEmitter {\n private _stream: Readable | null = null;\n\n constructor(private readonly messages: ChatMessage[] = []) {\n super();\n }\n\n get stream(): Readable | null {\n if (!this._stream) {\n return null;\n }\n\n return this._stream;\n }\n\n addMessage(message: ChatMessage): void {\n this.messages.push(message);\n this.emit('message', message);\n }\n\n run(assistant: Assistant): void {\n this._stream = new Readable({\n read: () => {},\n });\n this.doRun(assistant);\n }\n\n private doRun(assistant: Assistant): void {\n this.emit('in_progress');\n\n const stream = assistant.listChatCompletions(this.messages);\n\n /**\n * When the LLM responds with a function call, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * functionCall: { name: 'get_customer_profile', arguments: undefined }\n * },\n * contentFilterResults: {}\n * }\n *\n * When the LLM responds with a message, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * },\n * contentFilterResults: {}\n *\n * We're only interested in the first completion and then we let the dedicated handler handle the rest of the stream\n */\n stream.once('data', (completion: ChatCompletions) => {\n const choice = completion.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.functionCall) {\n const name = delta.functionCall.name;\n this.handleStreamAsFunctionCall(name, stream, assistant);\n } else {\n this.handleStreamAsChatMessage(stream);\n }\n });\n }\n\n /**\n * Handles the stream as a function call after we determined from the beginning of the stream that it is a function call.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '{\"' } }, <---- beginning of the arguments as a stringified JSON\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '\"}' } } <---- end of the arguments as a stringified JSON\n * }\n * { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call\n */\n private handleStreamAsFunctionCall(\n name: string,\n stream: Readable,\n assistant: Assistant,\n ): void {\n let args = '';\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.functionCall) {\n const functionCall = delta.functionCall;\n if (functionCall.arguments) {\n args += functionCall.arguments;\n }\n }\n\n if (choice.finishReason === 'function_call') {\n const functionCall: FunctionCall = {\n name,\n arguments: args,\n };\n\n // Adds the assistant's response to the messages\n const message: ChatMessage = {\n role: 'assistant',\n content: null,\n functionCall,\n };\n this.addMessage(message);\n\n const requiredAction = new RequiredAction({\n name,\n arguments: args,\n });\n\n requiredAction.on('submitting', (toolOutput: ToolOutput) => {\n // Adds the tool output to the messages\n const message: ChatMessage = {\n role: 'function',\n name: functionCall.name,\n content: JSON.stringify(toolOutput),\n };\n this.addMessage(message);\n\n this.doRun(assistant);\n });\n\n this.emit('requires_action', requiredAction);\n }\n });\n }\n\n /**\n * Handles the stream as a chat message after we determined from the beginning of the stream that it is a chat message.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \"Lorem\" }, <---- beginning of the response\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \" ipsum\" } <---- end of the response\n * }\n * { index: 0, finishReason: 'stop', delta: {} } <---- end of the message\n */\n private handleStreamAsChatMessage(stream: Readable): void {\n let content = '';\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.content) {\n content += delta.content;\n\n // Write also to the stream of the thread\n if (!this._stream) {\n throw new Error('No stream available');\n }\n this._stream?.push(delta.content);\n }\n\n if (choice.finishReason === 'stop') {\n // Adds the assistant's response to the messages\n const message: ChatMessage = {\n role: 'assistant',\n content,\n };\n this.addMessage(message);\n\n this.emit('completed');\n this._stream?.push(null);\n }\n });\n }\n}\n\nexport class RequiredAction extends EventEmitter {\n toolCall: ToolCall;\n\n constructor(functionCall: FunctionCall) {\n super();\n\n this.toolCall = {\n name: functionCall.name,\n arguments: JSON.parse(functionCall.arguments),\n };\n }\n\n submitToolOutput(toolOutput: ToolOutput): void {\n this.emit('submitting', toolOutput);\n }\n}\n\nexport interface ToolCall {\n name: string;\n arguments: Record;\n}\n\nexport interface ToolOutput {\n value: unknown;\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"index.esm.js","sources":["../src/assistant/assistant.ts","../src/thread/thread.ts"],"sourcesContent":["import type {\n ChatRequestMessage,\n ChatRequestSystemMessage,\n GetChatCompletionsOptions,\n OpenAIClient,\n} from '@azure/openai';\nimport type { ChatCompletionsToolDefinition } from '@azure/openai/types/src/models/models';\nimport { Readable } from 'stream';\n\nexport interface AssistantCreateParams {\n client: OpenAIClient;\n instructions: string;\n tools: ChatCompletionsToolDefinition[];\n deployment: string;\n}\n\nexport class Assistant {\n public readonly client: OpenAIClient;\n\n private readonly instructions: string;\n private readonly tools: ChatCompletionsToolDefinition[];\n private readonly deployment: string;\n\n constructor(params: AssistantCreateParams) {\n this.client = params.client;\n this.instructions = params.instructions;\n this.tools = params.tools;\n this.deployment = params.deployment;\n }\n\n listChatCompletions(messages: ChatRequestMessage[]): Readable {\n // Prepend the messages with our instructions as a \"system\" message\n const systemMessage: ChatRequestSystemMessage = {\n role: 'system',\n content: this.instructions,\n };\n messages = [systemMessage, ...messages];\n\n const options: GetChatCompletionsOptions = {\n tools: this.tools,\n };\n\n const completions = this.client.listChatCompletions(\n this.deployment,\n messages,\n options,\n );\n\n return Readable.from(completions, {\n objectMode: true,\n });\n }\n}\n","import type {\n ChatCompletions,\n ChatCompletionsToolCall,\n ChatRequestMessage,\n ChatRequestToolMessage,\n ChatResponseMessage,\n} from '@azure/openai';\nimport EventEmitter from 'events';\nimport { Readable } from 'stream';\nimport { Assistant } from '../assistant';\n\nexport class Thread extends EventEmitter {\n private readonly messages: (ChatRequestMessage | ChatResponseMessage)[] =\n [];\n private _stream: Readable | null = null;\n\n constructor(messages: ChatRequestMessage[] = []) {\n super();\n this.messages = messages;\n }\n\n get stream(): Readable | null {\n if (!this._stream) {\n return null;\n }\n\n return this._stream;\n }\n\n addMessage(message: ChatRequestMessage): void {\n this.doAddMessage(message);\n }\n\n run(assistant: Assistant): void {\n this._stream = new Readable({\n read: () => {},\n });\n this.doRun(assistant);\n }\n\n private doRun(assistant: Assistant): void {\n this.emit('in_progress');\n\n const messages = this.getRequestMessages();\n\n const stream = assistant.listChatCompletions(messages);\n\n /**\n * When the LLM responds with a function call, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * functionCall: { name: 'get_customer_profile', arguments: undefined }\n * },\n * contentFilterResults: {}\n * }\n *\n * When the LLM responds with a message, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * },\n * contentFilterResults: {}\n *\n * We're only interested in the first completion and then we let the dedicated handler handle the rest of the stream\n */\n stream.once('data', (completion: ChatCompletions) => {\n const choice = completion.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.toolCalls.length > 0) {\n this.handleStreamAsToolCalls(\n delta.toolCalls,\n stream,\n assistant,\n );\n } else {\n this.handleStreamAsChatResponseMessage(stream);\n }\n });\n }\n\n /**\n * Convert the mix of ChatRequestMessages and ChatResponseMessages to ChatRequestMessages only\n * so they can be sent again to the LLM.\n */\n private getRequestMessages(): ChatRequestMessage[] {\n return this.messages.map((m) => {\n if (m.role === 'system' || m.role === 'user' || m.role === 'tool') {\n // These are messages from the application (a.k.a request messages)\n return m as ChatRequestMessage;\n } else {\n // These are messages from the assistant (a.k.a response messages)\n const responseMessage = m as ChatResponseMessage;\n return {\n role: 'assistant',\n content: responseMessage.content,\n toolCalls: responseMessage.toolCalls,\n };\n }\n });\n }\n\n /**\n * Handles the stream as a function call after we determined from the beginning of the stream that it is a function call.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '{\"' } }, <---- beginning of the arguments as a stringified JSON\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '\"}' } } <---- end of the arguments as a stringified JSON\n * }\n * { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call\n */\n private handleStreamAsToolCalls(\n toolCalls: ChatCompletionsToolCall[],\n stream: Readable,\n assistant: Assistant,\n ): void {\n const argsList = Array(toolCalls.length).fill('');\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n delta.toolCalls.forEach((toolCall, index) => {\n argsList[index] += toolCall.function.arguments;\n });\n\n if (choice.finishReason === 'tool_calls') {\n const finalToolCalls: ChatCompletionsToolCall[] = toolCalls.map(\n (toolCall, index) => ({\n ...toolCall,\n function: {\n ...toolCall.function,\n arguments: argsList[index],\n },\n }),\n );\n\n // Adds the assistant's response to the messages\n const message: ChatResponseMessage = {\n role: 'assistant',\n content: null,\n toolCalls: finalToolCalls,\n };\n this.doAddMessage(message);\n\n const requiredAction = new RequiredAction(finalToolCalls);\n\n requiredAction.on('submitting', (toolOutputs: ToolOutput[]) => {\n // Adds the tool outputs to the messages\n for (const toolOutput of toolOutputs) {\n const message: ChatRequestToolMessage = {\n role: 'tool',\n content: JSON.stringify(toolOutput.value),\n toolCallId: toolOutput.callId,\n };\n this.doAddMessage(message);\n }\n\n this.doRun(assistant);\n });\n\n this.emit('requires_action', requiredAction);\n }\n });\n }\n\n /**\n * Handles the stream as a chat message after we determined from the beginning of the stream that it is a chat message.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \"Lorem\" }, <---- beginning of the response\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \" ipsum\" } <---- end of the response\n * }\n * { index: 0, finishReason: 'stop', delta: {} } <---- end of the message\n */\n private handleStreamAsChatResponseMessage(stream: Readable): void {\n let content = '';\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.content) {\n content += delta.content;\n\n // Write also to the stream of the thread\n if (!this._stream) {\n throw new Error('No stream available');\n }\n this._stream?.push(delta.content);\n }\n\n if (choice.finishReason === 'stop') {\n // Adds the assistant's response to the messages\n const message: ChatResponseMessage = {\n role: 'assistant',\n content,\n toolCalls: [],\n };\n this.doAddMessage(message);\n\n this.emit('completed');\n this._stream?.push(null);\n }\n });\n }\n\n private doAddMessage(\n message: ChatRequestMessage | ChatResponseMessage,\n ): void {\n this.messages.push(message);\n this.emit('message', message);\n\n if (isChatRequestMessage(message)) {\n this.emit('message:request', message);\n } else {\n this.emit('message:response', message);\n }\n }\n}\n\nexport class RequiredAction extends EventEmitter {\n constructor(public readonly toolCalls: ChatCompletionsToolCall[]) {\n super();\n }\n\n submitToolOutputs(toolOutputs: ToolOutput[]): void {\n this.emit('submitting', toolOutputs);\n }\n}\n\nexport interface ToolCall {\n name: string;\n arguments: Record;\n}\n\nexport interface ToolOutput {\n callId: string;\n value: unknown;\n}\n\nexport function isChatResponseMessage(\n m: ChatRequestMessage | ChatResponseMessage,\n): m is ChatResponseMessage {\n return 'toolCalls' in m;\n}\n\nexport function isChatRequestMessage(\n m: ChatRequestMessage | ChatResponseMessage,\n): m is ChatRequestMessage {\n return !isChatResponseMessage(m);\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/index.js b/dist/index.js
index 1c909d6..3611543 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -13,7 +13,7 @@ class Assistant {
constructor(params) {
this.client = params.client;
this.instructions = params.instructions;
- this.functions = params.functions;
+ this.tools = params.tools;
this.deployment = params.deployment;
}
listChatCompletions(messages) {
@@ -24,7 +24,7 @@ class Assistant {
};
messages = [systemMessage, ...messages];
const options = {
- functions: this.functions,
+ tools: this.tools,
};
const completions = this.client.listChatCompletions(this.deployment, messages, options);
return stream.Readable.from(completions, {
@@ -36,8 +36,9 @@ class Assistant {
class Thread extends EventEmitter {
constructor(messages = []) {
super();
- this.messages = messages;
+ this.messages = [];
this._stream = null;
+ this.messages = messages;
}
get stream() {
if (!this._stream) {
@@ -46,8 +47,7 @@ class Thread extends EventEmitter {
return this._stream;
}
addMessage(message) {
- this.messages.push(message);
- this.emit('message', message);
+ this.doAddMessage(message);
}
run(assistant) {
this._stream = new stream.Readable({
@@ -57,7 +57,8 @@ class Thread extends EventEmitter {
}
doRun(assistant) {
this.emit('in_progress');
- const stream = assistant.listChatCompletions(this.messages);
+ const messages = this.getRequestMessages();
+ const stream = assistant.listChatCompletions(messages);
/**
* When the LLM responds with a function call, the first completion's first choice looks like this:
* {
@@ -90,12 +91,32 @@ class Thread extends EventEmitter {
if (!delta) {
throw new Error('No delta returned');
}
- if (delta.functionCall) {
- const name = delta.functionCall.name;
- this.handleStreamAsFunctionCall(name, stream, assistant);
+ if (delta.toolCalls.length > 0) {
+ this.handleStreamAsToolCalls(delta.toolCalls, stream, assistant);
}
else {
- this.handleStreamAsChatMessage(stream);
+ this.handleStreamAsChatResponseMessage(stream);
+ }
+ });
+ }
+ /**
+ * Convert the mix of ChatRequestMessages and ChatResponseMessages to ChatRequestMessages only
+ * so they can be sent again to the LLM.
+ */
+ getRequestMessages() {
+ return this.messages.map((m) => {
+ if (m.role === 'system' || m.role === 'user' || m.role === 'tool') {
+ // These are messages from the application (a.k.a request messages)
+ return m;
+ }
+ else {
+ // These are messages from the assistant (a.k.a response messages)
+ const responseMessage = m;
+ return {
+ role: 'assistant',
+ content: responseMessage.content,
+ toolCalls: responseMessage.toolCalls,
+ };
}
});
}
@@ -117,8 +138,8 @@ class Thread extends EventEmitter {
* }
* { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call
*/
- handleStreamAsFunctionCall(name, stream, assistant) {
- let args = '';
+ handleStreamAsToolCalls(toolCalls, stream, assistant) {
+ const argsList = Array(toolCalls.length).fill('');
stream.on('data', (completions) => {
const choice = completions.choices[0];
if (!choice) {
@@ -128,36 +149,35 @@ class Thread extends EventEmitter {
if (!delta) {
throw new Error('No delta returned');
}
- if (delta.functionCall) {
- const functionCall = delta.functionCall;
- if (functionCall.arguments) {
- args += functionCall.arguments;
- }
- }
- if (choice.finishReason === 'function_call') {
- const functionCall = {
- name,
- arguments: args,
- };
+ delta.toolCalls.forEach((toolCall, index) => {
+ argsList[index] += toolCall.function.arguments;
+ });
+ if (choice.finishReason === 'tool_calls') {
+ const finalToolCalls = toolCalls.map((toolCall, index) => ({
+ ...toolCall,
+ function: {
+ ...toolCall.function,
+ arguments: argsList[index],
+ },
+ }));
// Adds the assistant's response to the messages
const message = {
role: 'assistant',
content: null,
- functionCall,
+ toolCalls: finalToolCalls,
};
- this.addMessage(message);
- const requiredAction = new RequiredAction({
- name,
- arguments: args,
- });
- requiredAction.on('submitting', (toolOutput) => {
- // Adds the tool output to the messages
- const message = {
- role: 'function',
- name: functionCall.name,
- content: JSON.stringify(toolOutput),
- };
- this.addMessage(message);
+ this.doAddMessage(message);
+ const requiredAction = new RequiredAction(finalToolCalls);
+ requiredAction.on('submitting', (toolOutputs) => {
+ // Adds the tool outputs to the messages
+ for (const toolOutput of toolOutputs) {
+ const message = {
+ role: 'tool',
+ content: JSON.stringify(toolOutput.value),
+ toolCallId: toolOutput.callId,
+ };
+ this.doAddMessage(message);
+ }
this.doRun(assistant);
});
this.emit('requires_action', requiredAction);
@@ -182,7 +202,7 @@ class Thread extends EventEmitter {
* }
* { index: 0, finishReason: 'stop', delta: {} } <---- end of the message
*/
- handleStreamAsChatMessage(stream) {
+ handleStreamAsChatResponseMessage(stream) {
let content = '';
stream.on('data', (completions) => {
const choice = completions.choices[0];
@@ -206,28 +226,44 @@ class Thread extends EventEmitter {
const message = {
role: 'assistant',
content,
+ toolCalls: [],
};
- this.addMessage(message);
+ this.doAddMessage(message);
this.emit('completed');
this._stream?.push(null);
}
});
}
+ doAddMessage(message) {
+ this.messages.push(message);
+ this.emit('message', message);
+ if (isChatRequestMessage(message)) {
+ this.emit('message:request', message);
+ }
+ else {
+ this.emit('message:response', message);
+ }
+ }
}
class RequiredAction extends EventEmitter {
- constructor(functionCall) {
+ constructor(toolCalls) {
super();
- this.toolCall = {
- name: functionCall.name,
- arguments: JSON.parse(functionCall.arguments),
- };
+ this.toolCalls = toolCalls;
}
- submitToolOutput(toolOutput) {
- this.emit('submitting', toolOutput);
+ submitToolOutputs(toolOutputs) {
+ this.emit('submitting', toolOutputs);
}
}
+function isChatResponseMessage(m) {
+ return 'toolCalls' in m;
+}
+function isChatRequestMessage(m) {
+ return !isChatResponseMessage(m);
+}
exports.Assistant = Assistant;
exports.RequiredAction = RequiredAction;
exports.Thread = Thread;
+exports.isChatRequestMessage = isChatRequestMessage;
+exports.isChatResponseMessage = isChatResponseMessage;
//# sourceMappingURL=index.js.map
diff --git a/dist/index.js.map b/dist/index.js.map
index 4165291..22934d0 100644
--- a/dist/index.js.map
+++ b/dist/index.js.map
@@ -1 +1 @@
-{"version":3,"file":"index.js","sources":["../src/assistant/assistant.ts","../src/thread/thread.ts"],"sourcesContent":["import type { FunctionDefinition, OpenAIClient } from '@azure/openai';\nimport type { GetChatCompletionsOptions } from '@azure/openai/types/src/api/models';\nimport type { ChatMessage } from '@azure/openai/types/src/models/models';\nimport { Readable } from 'stream';\n\nexport interface AssistantCreateParams {\n client: OpenAIClient;\n instructions: string;\n functions: FunctionDefinition[];\n deployment: string;\n}\n\nexport class Assistant {\n public readonly client: OpenAIClient;\n\n private readonly instructions: string;\n private readonly functions: FunctionDefinition[];\n private readonly deployment: string;\n\n constructor(params: AssistantCreateParams) {\n this.client = params.client;\n this.instructions = params.instructions;\n this.functions = params.functions;\n this.deployment = params.deployment;\n }\n\n listChatCompletions(messages: ChatMessage[]): Readable {\n // Prepend the messages with our instructions as a \"system\" message\n const systemMessage: ChatMessage = {\n role: 'system',\n content: this.instructions,\n };\n messages = [systemMessage, ...messages];\n\n const options: GetChatCompletionsOptions = {\n functions: this.functions,\n };\n\n const completions = this.client.listChatCompletions(\n this.deployment,\n messages,\n options,\n );\n\n return Readable.from(completions, {\n objectMode: true,\n });\n }\n}\n","import type { ChatCompletions, ChatMessage, FunctionCall } from '@azure/openai';\nimport EventEmitter from 'events';\nimport { Readable } from 'stream';\nimport { Assistant } from '../assistant';\n\nexport class Thread extends EventEmitter {\n private _stream: Readable | null = null;\n\n constructor(private readonly messages: ChatMessage[] = []) {\n super();\n }\n\n get stream(): Readable | null {\n if (!this._stream) {\n return null;\n }\n\n return this._stream;\n }\n\n addMessage(message: ChatMessage): void {\n this.messages.push(message);\n this.emit('message', message);\n }\n\n run(assistant: Assistant): void {\n this._stream = new Readable({\n read: () => {},\n });\n this.doRun(assistant);\n }\n\n private doRun(assistant: Assistant): void {\n this.emit('in_progress');\n\n const stream = assistant.listChatCompletions(this.messages);\n\n /**\n * When the LLM responds with a function call, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * functionCall: { name: 'get_customer_profile', arguments: undefined }\n * },\n * contentFilterResults: {}\n * }\n *\n * When the LLM responds with a message, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * },\n * contentFilterResults: {}\n *\n * We're only interested in the first completion and then we let the dedicated handler handle the rest of the stream\n */\n stream.once('data', (completion: ChatCompletions) => {\n const choice = completion.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.functionCall) {\n const name = delta.functionCall.name;\n this.handleStreamAsFunctionCall(name, stream, assistant);\n } else {\n this.handleStreamAsChatMessage(stream);\n }\n });\n }\n\n /**\n * Handles the stream as a function call after we determined from the beginning of the stream that it is a function call.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '{\"' } }, <---- beginning of the arguments as a stringified JSON\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '\"}' } } <---- end of the arguments as a stringified JSON\n * }\n * { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call\n */\n private handleStreamAsFunctionCall(\n name: string,\n stream: Readable,\n assistant: Assistant,\n ): void {\n let args = '';\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.functionCall) {\n const functionCall = delta.functionCall;\n if (functionCall.arguments) {\n args += functionCall.arguments;\n }\n }\n\n if (choice.finishReason === 'function_call') {\n const functionCall: FunctionCall = {\n name,\n arguments: args,\n };\n\n // Adds the assistant's response to the messages\n const message: ChatMessage = {\n role: 'assistant',\n content: null,\n functionCall,\n };\n this.addMessage(message);\n\n const requiredAction = new RequiredAction({\n name,\n arguments: args,\n });\n\n requiredAction.on('submitting', (toolOutput: ToolOutput) => {\n // Adds the tool output to the messages\n const message: ChatMessage = {\n role: 'function',\n name: functionCall.name,\n content: JSON.stringify(toolOutput),\n };\n this.addMessage(message);\n\n this.doRun(assistant);\n });\n\n this.emit('requires_action', requiredAction);\n }\n });\n }\n\n /**\n * Handles the stream as a chat message after we determined from the beginning of the stream that it is a chat message.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \"Lorem\" }, <---- beginning of the response\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \" ipsum\" } <---- end of the response\n * }\n * { index: 0, finishReason: 'stop', delta: {} } <---- end of the message\n */\n private handleStreamAsChatMessage(stream: Readable): void {\n let content = '';\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.content) {\n content += delta.content;\n\n // Write also to the stream of the thread\n if (!this._stream) {\n throw new Error('No stream available');\n }\n this._stream?.push(delta.content);\n }\n\n if (choice.finishReason === 'stop') {\n // Adds the assistant's response to the messages\n const message: ChatMessage = {\n role: 'assistant',\n content,\n };\n this.addMessage(message);\n\n this.emit('completed');\n this._stream?.push(null);\n }\n });\n }\n}\n\nexport class RequiredAction extends EventEmitter {\n toolCall: ToolCall;\n\n constructor(functionCall: FunctionCall) {\n super();\n\n this.toolCall = {\n name: functionCall.name,\n arguments: JSON.parse(functionCall.arguments),\n };\n }\n\n submitToolOutput(toolOutput: ToolOutput): void {\n this.emit('submitting', toolOutput);\n }\n}\n\nexport interface ToolCall {\n name: string;\n arguments: Record;\n}\n\nexport interface ToolOutput {\n value: unknown;\n}\n"],"names":["Readable"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"index.js","sources":["../src/assistant/assistant.ts","../src/thread/thread.ts"],"sourcesContent":["import type {\n ChatRequestMessage,\n ChatRequestSystemMessage,\n GetChatCompletionsOptions,\n OpenAIClient,\n} from '@azure/openai';\nimport type { ChatCompletionsToolDefinition } from '@azure/openai/types/src/models/models';\nimport { Readable } from 'stream';\n\nexport interface AssistantCreateParams {\n client: OpenAIClient;\n instructions: string;\n tools: ChatCompletionsToolDefinition[];\n deployment: string;\n}\n\nexport class Assistant {\n public readonly client: OpenAIClient;\n\n private readonly instructions: string;\n private readonly tools: ChatCompletionsToolDefinition[];\n private readonly deployment: string;\n\n constructor(params: AssistantCreateParams) {\n this.client = params.client;\n this.instructions = params.instructions;\n this.tools = params.tools;\n this.deployment = params.deployment;\n }\n\n listChatCompletions(messages: ChatRequestMessage[]): Readable {\n // Prepend the messages with our instructions as a \"system\" message\n const systemMessage: ChatRequestSystemMessage = {\n role: 'system',\n content: this.instructions,\n };\n messages = [systemMessage, ...messages];\n\n const options: GetChatCompletionsOptions = {\n tools: this.tools,\n };\n\n const completions = this.client.listChatCompletions(\n this.deployment,\n messages,\n options,\n );\n\n return Readable.from(completions, {\n objectMode: true,\n });\n }\n}\n","import type {\n ChatCompletions,\n ChatCompletionsToolCall,\n ChatRequestMessage,\n ChatRequestToolMessage,\n ChatResponseMessage,\n} from '@azure/openai';\nimport EventEmitter from 'events';\nimport { Readable } from 'stream';\nimport { Assistant } from '../assistant';\n\nexport class Thread extends EventEmitter {\n private readonly messages: (ChatRequestMessage | ChatResponseMessage)[] =\n [];\n private _stream: Readable | null = null;\n\n constructor(messages: ChatRequestMessage[] = []) {\n super();\n this.messages = messages;\n }\n\n get stream(): Readable | null {\n if (!this._stream) {\n return null;\n }\n\n return this._stream;\n }\n\n addMessage(message: ChatRequestMessage): void {\n this.doAddMessage(message);\n }\n\n run(assistant: Assistant): void {\n this._stream = new Readable({\n read: () => {},\n });\n this.doRun(assistant);\n }\n\n private doRun(assistant: Assistant): void {\n this.emit('in_progress');\n\n const messages = this.getRequestMessages();\n\n const stream = assistant.listChatCompletions(messages);\n\n /**\n * When the LLM responds with a function call, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * functionCall: { name: 'get_customer_profile', arguments: undefined }\n * },\n * contentFilterResults: {}\n * }\n *\n * When the LLM responds with a message, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * },\n * contentFilterResults: {}\n *\n * We're only interested in the first completion and then we let the dedicated handler handle the rest of the stream\n */\n stream.once('data', (completion: ChatCompletions) => {\n const choice = completion.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.toolCalls.length > 0) {\n this.handleStreamAsToolCalls(\n delta.toolCalls,\n stream,\n assistant,\n );\n } else {\n this.handleStreamAsChatResponseMessage(stream);\n }\n });\n }\n\n /**\n * Convert the mix of ChatRequestMessages and ChatResponseMessages to ChatRequestMessages only\n * so they can be sent again to the LLM.\n */\n private getRequestMessages(): ChatRequestMessage[] {\n return this.messages.map((m) => {\n if (m.role === 'system' || m.role === 'user' || m.role === 'tool') {\n // These are messages from the application (a.k.a request messages)\n return m as ChatRequestMessage;\n } else {\n // These are messages from the assistant (a.k.a response messages)\n const responseMessage = m as ChatResponseMessage;\n return {\n role: 'assistant',\n content: responseMessage.content,\n toolCalls: responseMessage.toolCalls,\n };\n }\n });\n }\n\n /**\n * Handles the stream as a function call after we determined from the beginning of the stream that it is a function call.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '{\"' } }, <---- beginning of the arguments as a stringified JSON\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '\"}' } } <---- end of the arguments as a stringified JSON\n * }\n * { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call\n */\n private handleStreamAsToolCalls(\n toolCalls: ChatCompletionsToolCall[],\n stream: Readable,\n assistant: Assistant,\n ): void {\n const argsList = Array(toolCalls.length).fill('');\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n delta.toolCalls.forEach((toolCall, index) => {\n argsList[index] += toolCall.function.arguments;\n });\n\n if (choice.finishReason === 'tool_calls') {\n const finalToolCalls: ChatCompletionsToolCall[] = toolCalls.map(\n (toolCall, index) => ({\n ...toolCall,\n function: {\n ...toolCall.function,\n arguments: argsList[index],\n },\n }),\n );\n\n // Adds the assistant's response to the messages\n const message: ChatResponseMessage = {\n role: 'assistant',\n content: null,\n toolCalls: finalToolCalls,\n };\n this.doAddMessage(message);\n\n const requiredAction = new RequiredAction(finalToolCalls);\n\n requiredAction.on('submitting', (toolOutputs: ToolOutput[]) => {\n // Adds the tool outputs to the messages\n for (const toolOutput of toolOutputs) {\n const message: ChatRequestToolMessage = {\n role: 'tool',\n content: JSON.stringify(toolOutput.value),\n toolCallId: toolOutput.callId,\n };\n this.doAddMessage(message);\n }\n\n this.doRun(assistant);\n });\n\n this.emit('requires_action', requiredAction);\n }\n });\n }\n\n /**\n * Handles the stream as a chat message after we determined from the beginning of the stream that it is a chat message.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \"Lorem\" }, <---- beginning of the response\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \" ipsum\" } <---- end of the response\n * }\n * { index: 0, finishReason: 'stop', delta: {} } <---- end of the message\n */\n private handleStreamAsChatResponseMessage(stream: Readable): void {\n let content = '';\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.content) {\n content += delta.content;\n\n // Write also to the stream of the thread\n if (!this._stream) {\n throw new Error('No stream available');\n }\n this._stream?.push(delta.content);\n }\n\n if (choice.finishReason === 'stop') {\n // Adds the assistant's response to the messages\n const message: ChatResponseMessage = {\n role: 'assistant',\n content,\n toolCalls: [],\n };\n this.doAddMessage(message);\n\n this.emit('completed');\n this._stream?.push(null);\n }\n });\n }\n\n private doAddMessage(\n message: ChatRequestMessage | ChatResponseMessage,\n ): void {\n this.messages.push(message);\n this.emit('message', message);\n\n if (isChatRequestMessage(message)) {\n this.emit('message:request', message);\n } else {\n this.emit('message:response', message);\n }\n }\n}\n\nexport class RequiredAction extends EventEmitter {\n constructor(public readonly toolCalls: ChatCompletionsToolCall[]) {\n super();\n }\n\n submitToolOutputs(toolOutputs: ToolOutput[]): void {\n this.emit('submitting', toolOutputs);\n }\n}\n\nexport interface ToolCall {\n name: string;\n arguments: Record;\n}\n\nexport interface ToolOutput {\n callId: string;\n value: unknown;\n}\n\nexport function isChatResponseMessage(\n m: ChatRequestMessage | ChatResponseMessage,\n): m is ChatResponseMessage {\n return 'toolCalls' in m;\n}\n\nexport function isChatRequestMessage(\n m: ChatRequestMessage | ChatResponseMessage,\n): m is ChatRequestMessage {\n return !isChatResponseMessage(m);\n}\n"],"names":["Readable"],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/index.mjs b/dist/index.mjs
index d65561b..69cf4b1 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -11,7 +11,7 @@ class Assistant {
constructor(params) {
this.client = params.client;
this.instructions = params.instructions;
- this.functions = params.functions;
+ this.tools = params.tools;
this.deployment = params.deployment;
}
listChatCompletions(messages) {
@@ -22,7 +22,7 @@ class Assistant {
};
messages = [systemMessage, ...messages];
const options = {
- functions: this.functions,
+ tools: this.tools,
};
const completions = this.client.listChatCompletions(this.deployment, messages, options);
return Readable.from(completions, {
@@ -34,8 +34,9 @@ class Assistant {
class Thread extends EventEmitter {
constructor(messages = []) {
super();
- this.messages = messages;
+ this.messages = [];
this._stream = null;
+ this.messages = messages;
}
get stream() {
if (!this._stream) {
@@ -44,8 +45,7 @@ class Thread extends EventEmitter {
return this._stream;
}
addMessage(message) {
- this.messages.push(message);
- this.emit('message', message);
+ this.doAddMessage(message);
}
run(assistant) {
this._stream = new Readable({
@@ -55,7 +55,8 @@ class Thread extends EventEmitter {
}
doRun(assistant) {
this.emit('in_progress');
- const stream = assistant.listChatCompletions(this.messages);
+ const messages = this.getRequestMessages();
+ const stream = assistant.listChatCompletions(messages);
/**
* When the LLM responds with a function call, the first completion's first choice looks like this:
* {
@@ -88,12 +89,32 @@ class Thread extends EventEmitter {
if (!delta) {
throw new Error('No delta returned');
}
- if (delta.functionCall) {
- const name = delta.functionCall.name;
- this.handleStreamAsFunctionCall(name, stream, assistant);
+ if (delta.toolCalls.length > 0) {
+ this.handleStreamAsToolCalls(delta.toolCalls, stream, assistant);
}
else {
- this.handleStreamAsChatMessage(stream);
+ this.handleStreamAsChatResponseMessage(stream);
+ }
+ });
+ }
+ /**
+ * Convert the mix of ChatRequestMessages and ChatResponseMessages to ChatRequestMessages only
+ * so they can be sent again to the LLM.
+ */
+ getRequestMessages() {
+ return this.messages.map((m) => {
+ if (m.role === 'system' || m.role === 'user' || m.role === 'tool') {
+ // These are messages from the application (a.k.a request messages)
+ return m;
+ }
+ else {
+ // These are messages from the assistant (a.k.a response messages)
+ const responseMessage = m;
+ return {
+ role: 'assistant',
+ content: responseMessage.content,
+ toolCalls: responseMessage.toolCalls,
+ };
}
});
}
@@ -115,8 +136,8 @@ class Thread extends EventEmitter {
* }
* { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call
*/
- handleStreamAsFunctionCall(name, stream, assistant) {
- let args = '';
+ handleStreamAsToolCalls(toolCalls, stream, assistant) {
+ const argsList = Array(toolCalls.length).fill('');
stream.on('data', (completions) => {
const choice = completions.choices[0];
if (!choice) {
@@ -126,36 +147,35 @@ class Thread extends EventEmitter {
if (!delta) {
throw new Error('No delta returned');
}
- if (delta.functionCall) {
- const functionCall = delta.functionCall;
- if (functionCall.arguments) {
- args += functionCall.arguments;
- }
- }
- if (choice.finishReason === 'function_call') {
- const functionCall = {
- name,
- arguments: args,
- };
+ delta.toolCalls.forEach((toolCall, index) => {
+ argsList[index] += toolCall.function.arguments;
+ });
+ if (choice.finishReason === 'tool_calls') {
+ const finalToolCalls = toolCalls.map((toolCall, index) => ({
+ ...toolCall,
+ function: {
+ ...toolCall.function,
+ arguments: argsList[index],
+ },
+ }));
// Adds the assistant's response to the messages
const message = {
role: 'assistant',
content: null,
- functionCall,
+ toolCalls: finalToolCalls,
};
- this.addMessage(message);
- const requiredAction = new RequiredAction({
- name,
- arguments: args,
- });
- requiredAction.on('submitting', (toolOutput) => {
- // Adds the tool output to the messages
- const message = {
- role: 'function',
- name: functionCall.name,
- content: JSON.stringify(toolOutput),
- };
- this.addMessage(message);
+ this.doAddMessage(message);
+ const requiredAction = new RequiredAction(finalToolCalls);
+ requiredAction.on('submitting', (toolOutputs) => {
+ // Adds the tool outputs to the messages
+ for (const toolOutput of toolOutputs) {
+ const message = {
+ role: 'tool',
+ content: JSON.stringify(toolOutput.value),
+ toolCallId: toolOutput.callId,
+ };
+ this.doAddMessage(message);
+ }
this.doRun(assistant);
});
this.emit('requires_action', requiredAction);
@@ -180,7 +200,7 @@ class Thread extends EventEmitter {
* }
* { index: 0, finishReason: 'stop', delta: {} } <---- end of the message
*/
- handleStreamAsChatMessage(stream) {
+ handleStreamAsChatResponseMessage(stream) {
let content = '';
stream.on('data', (completions) => {
const choice = completions.choices[0];
@@ -204,26 +224,40 @@ class Thread extends EventEmitter {
const message = {
role: 'assistant',
content,
+ toolCalls: [],
};
- this.addMessage(message);
+ this.doAddMessage(message);
this.emit('completed');
this._stream?.push(null);
}
});
}
+ doAddMessage(message) {
+ this.messages.push(message);
+ this.emit('message', message);
+ if (isChatRequestMessage(message)) {
+ this.emit('message:request', message);
+ }
+ else {
+ this.emit('message:response', message);
+ }
+ }
}
class RequiredAction extends EventEmitter {
- constructor(functionCall) {
+ constructor(toolCalls) {
super();
- this.toolCall = {
- name: functionCall.name,
- arguments: JSON.parse(functionCall.arguments),
- };
+ this.toolCalls = toolCalls;
}
- submitToolOutput(toolOutput) {
- this.emit('submitting', toolOutput);
+ submitToolOutputs(toolOutputs) {
+ this.emit('submitting', toolOutputs);
}
}
+function isChatResponseMessage(m) {
+ return 'toolCalls' in m;
+}
+function isChatRequestMessage(m) {
+ return !isChatResponseMessage(m);
+}
-export { Assistant, RequiredAction, Thread };
+export { Assistant, RequiredAction, Thread, isChatRequestMessage, isChatResponseMessage };
//# sourceMappingURL=index.mjs.map
diff --git a/dist/index.mjs.map b/dist/index.mjs.map
index ae0d9ae..44af743 100644
--- a/dist/index.mjs.map
+++ b/dist/index.mjs.map
@@ -1 +1 @@
-{"version":3,"file":"index.mjs","sources":["../src/assistant/assistant.ts","../src/thread/thread.ts"],"sourcesContent":["import type { FunctionDefinition, OpenAIClient } from '@azure/openai';\nimport type { GetChatCompletionsOptions } from '@azure/openai/types/src/api/models';\nimport type { ChatMessage } from '@azure/openai/types/src/models/models';\nimport { Readable } from 'stream';\n\nexport interface AssistantCreateParams {\n client: OpenAIClient;\n instructions: string;\n functions: FunctionDefinition[];\n deployment: string;\n}\n\nexport class Assistant {\n public readonly client: OpenAIClient;\n\n private readonly instructions: string;\n private readonly functions: FunctionDefinition[];\n private readonly deployment: string;\n\n constructor(params: AssistantCreateParams) {\n this.client = params.client;\n this.instructions = params.instructions;\n this.functions = params.functions;\n this.deployment = params.deployment;\n }\n\n listChatCompletions(messages: ChatMessage[]): Readable {\n // Prepend the messages with our instructions as a \"system\" message\n const systemMessage: ChatMessage = {\n role: 'system',\n content: this.instructions,\n };\n messages = [systemMessage, ...messages];\n\n const options: GetChatCompletionsOptions = {\n functions: this.functions,\n };\n\n const completions = this.client.listChatCompletions(\n this.deployment,\n messages,\n options,\n );\n\n return Readable.from(completions, {\n objectMode: true,\n });\n }\n}\n","import type { ChatCompletions, ChatMessage, FunctionCall } from '@azure/openai';\nimport EventEmitter from 'events';\nimport { Readable } from 'stream';\nimport { Assistant } from '../assistant';\n\nexport class Thread extends EventEmitter {\n private _stream: Readable | null = null;\n\n constructor(private readonly messages: ChatMessage[] = []) {\n super();\n }\n\n get stream(): Readable | null {\n if (!this._stream) {\n return null;\n }\n\n return this._stream;\n }\n\n addMessage(message: ChatMessage): void {\n this.messages.push(message);\n this.emit('message', message);\n }\n\n run(assistant: Assistant): void {\n this._stream = new Readable({\n read: () => {},\n });\n this.doRun(assistant);\n }\n\n private doRun(assistant: Assistant): void {\n this.emit('in_progress');\n\n const stream = assistant.listChatCompletions(this.messages);\n\n /**\n * When the LLM responds with a function call, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * functionCall: { name: 'get_customer_profile', arguments: undefined }\n * },\n * contentFilterResults: {}\n * }\n *\n * When the LLM responds with a message, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * },\n * contentFilterResults: {}\n *\n * We're only interested in the first completion and then we let the dedicated handler handle the rest of the stream\n */\n stream.once('data', (completion: ChatCompletions) => {\n const choice = completion.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.functionCall) {\n const name = delta.functionCall.name;\n this.handleStreamAsFunctionCall(name, stream, assistant);\n } else {\n this.handleStreamAsChatMessage(stream);\n }\n });\n }\n\n /**\n * Handles the stream as a function call after we determined from the beginning of the stream that it is a function call.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '{\"' } }, <---- beginning of the arguments as a stringified JSON\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '\"}' } } <---- end of the arguments as a stringified JSON\n * }\n * { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call\n */\n private handleStreamAsFunctionCall(\n name: string,\n stream: Readable,\n assistant: Assistant,\n ): void {\n let args = '';\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.functionCall) {\n const functionCall = delta.functionCall;\n if (functionCall.arguments) {\n args += functionCall.arguments;\n }\n }\n\n if (choice.finishReason === 'function_call') {\n const functionCall: FunctionCall = {\n name,\n arguments: args,\n };\n\n // Adds the assistant's response to the messages\n const message: ChatMessage = {\n role: 'assistant',\n content: null,\n functionCall,\n };\n this.addMessage(message);\n\n const requiredAction = new RequiredAction({\n name,\n arguments: args,\n });\n\n requiredAction.on('submitting', (toolOutput: ToolOutput) => {\n // Adds the tool output to the messages\n const message: ChatMessage = {\n role: 'function',\n name: functionCall.name,\n content: JSON.stringify(toolOutput),\n };\n this.addMessage(message);\n\n this.doRun(assistant);\n });\n\n this.emit('requires_action', requiredAction);\n }\n });\n }\n\n /**\n * Handles the stream as a chat message after we determined from the beginning of the stream that it is a chat message.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \"Lorem\" }, <---- beginning of the response\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \" ipsum\" } <---- end of the response\n * }\n * { index: 0, finishReason: 'stop', delta: {} } <---- end of the message\n */\n private handleStreamAsChatMessage(stream: Readable): void {\n let content = '';\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.content) {\n content += delta.content;\n\n // Write also to the stream of the thread\n if (!this._stream) {\n throw new Error('No stream available');\n }\n this._stream?.push(delta.content);\n }\n\n if (choice.finishReason === 'stop') {\n // Adds the assistant's response to the messages\n const message: ChatMessage = {\n role: 'assistant',\n content,\n };\n this.addMessage(message);\n\n this.emit('completed');\n this._stream?.push(null);\n }\n });\n }\n}\n\nexport class RequiredAction extends EventEmitter {\n toolCall: ToolCall;\n\n constructor(functionCall: FunctionCall) {\n super();\n\n this.toolCall = {\n name: functionCall.name,\n arguments: JSON.parse(functionCall.arguments),\n };\n }\n\n submitToolOutput(toolOutput: ToolOutput): void {\n this.emit('submitting', toolOutput);\n }\n}\n\nexport interface ToolCall {\n name: string;\n arguments: Record;\n}\n\nexport interface ToolOutput {\n value: unknown;\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"index.mjs","sources":["../src/assistant/assistant.ts","../src/thread/thread.ts"],"sourcesContent":["import type {\n ChatRequestMessage,\n ChatRequestSystemMessage,\n GetChatCompletionsOptions,\n OpenAIClient,\n} from '@azure/openai';\nimport type { ChatCompletionsToolDefinition } from '@azure/openai/types/src/models/models';\nimport { Readable } from 'stream';\n\nexport interface AssistantCreateParams {\n client: OpenAIClient;\n instructions: string;\n tools: ChatCompletionsToolDefinition[];\n deployment: string;\n}\n\nexport class Assistant {\n public readonly client: OpenAIClient;\n\n private readonly instructions: string;\n private readonly tools: ChatCompletionsToolDefinition[];\n private readonly deployment: string;\n\n constructor(params: AssistantCreateParams) {\n this.client = params.client;\n this.instructions = params.instructions;\n this.tools = params.tools;\n this.deployment = params.deployment;\n }\n\n listChatCompletions(messages: ChatRequestMessage[]): Readable {\n // Prepend the messages with our instructions as a \"system\" message\n const systemMessage: ChatRequestSystemMessage = {\n role: 'system',\n content: this.instructions,\n };\n messages = [systemMessage, ...messages];\n\n const options: GetChatCompletionsOptions = {\n tools: this.tools,\n };\n\n const completions = this.client.listChatCompletions(\n this.deployment,\n messages,\n options,\n );\n\n return Readable.from(completions, {\n objectMode: true,\n });\n }\n}\n","import type {\n ChatCompletions,\n ChatCompletionsToolCall,\n ChatRequestMessage,\n ChatRequestToolMessage,\n ChatResponseMessage,\n} from '@azure/openai';\nimport EventEmitter from 'events';\nimport { Readable } from 'stream';\nimport { Assistant } from '../assistant';\n\nexport class Thread extends EventEmitter {\n private readonly messages: (ChatRequestMessage | ChatResponseMessage)[] =\n [];\n private _stream: Readable | null = null;\n\n constructor(messages: ChatRequestMessage[] = []) {\n super();\n this.messages = messages;\n }\n\n get stream(): Readable | null {\n if (!this._stream) {\n return null;\n }\n\n return this._stream;\n }\n\n addMessage(message: ChatRequestMessage): void {\n this.doAddMessage(message);\n }\n\n run(assistant: Assistant): void {\n this._stream = new Readable({\n read: () => {},\n });\n this.doRun(assistant);\n }\n\n private doRun(assistant: Assistant): void {\n this.emit('in_progress');\n\n const messages = this.getRequestMessages();\n\n const stream = assistant.listChatCompletions(messages);\n\n /**\n * When the LLM responds with a function call, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * functionCall: { name: 'get_customer_profile', arguments: undefined }\n * },\n * contentFilterResults: {}\n * }\n *\n * When the LLM responds with a message, the first completion's first choice looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: {\n * role: 'assistant',\n * },\n * contentFilterResults: {}\n *\n * We're only interested in the first completion and then we let the dedicated handler handle the rest of the stream\n */\n stream.once('data', (completion: ChatCompletions) => {\n const choice = completion.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.toolCalls.length > 0) {\n this.handleStreamAsToolCalls(\n delta.toolCalls,\n stream,\n assistant,\n );\n } else {\n this.handleStreamAsChatResponseMessage(stream);\n }\n });\n }\n\n /**\n * Convert the mix of ChatRequestMessages and ChatResponseMessages to ChatRequestMessages only\n * so they can be sent again to the LLM.\n */\n private getRequestMessages(): ChatRequestMessage[] {\n return this.messages.map((m) => {\n if (m.role === 'system' || m.role === 'user' || m.role === 'tool') {\n // These are messages from the application (a.k.a request messages)\n return m as ChatRequestMessage;\n } else {\n // These are messages from the assistant (a.k.a response messages)\n const responseMessage = m as ChatResponseMessage;\n return {\n role: 'assistant',\n content: responseMessage.content,\n toolCalls: responseMessage.toolCalls,\n };\n }\n });\n }\n\n /**\n * Handles the stream as a function call after we determined from the beginning of the stream that it is a function call.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '{\"' } }, <---- beginning of the arguments as a stringified JSON\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { functionCall: { name: undefined, arguments: '\"}' } } <---- end of the arguments as a stringified JSON\n * }\n * { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call\n */\n private handleStreamAsToolCalls(\n toolCalls: ChatCompletionsToolCall[],\n stream: Readable,\n assistant: Assistant,\n ): void {\n const argsList = Array(toolCalls.length).fill('');\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n delta.toolCalls.forEach((toolCall, index) => {\n argsList[index] += toolCall.function.arguments;\n });\n\n if (choice.finishReason === 'tool_calls') {\n const finalToolCalls: ChatCompletionsToolCall[] = toolCalls.map(\n (toolCall, index) => ({\n ...toolCall,\n function: {\n ...toolCall.function,\n arguments: argsList[index],\n },\n }),\n );\n\n // Adds the assistant's response to the messages\n const message: ChatResponseMessage = {\n role: 'assistant',\n content: null,\n toolCalls: finalToolCalls,\n };\n this.doAddMessage(message);\n\n const requiredAction = new RequiredAction(finalToolCalls);\n\n requiredAction.on('submitting', (toolOutputs: ToolOutput[]) => {\n // Adds the tool outputs to the messages\n for (const toolOutput of toolOutputs) {\n const message: ChatRequestToolMessage = {\n role: 'tool',\n content: JSON.stringify(toolOutput.value),\n toolCallId: toolOutput.callId,\n };\n this.doAddMessage(message);\n }\n\n this.doRun(assistant);\n });\n\n this.emit('requires_action', requiredAction);\n }\n });\n }\n\n /**\n * Handles the stream as a chat message after we determined from the beginning of the stream that it is a chat message.\n * The stream emits some completions.\n * The first choice of these completions successively looks like this:\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \"Lorem\" }, <---- beginning of the response\n * contentFilterResults: {}\n * }\n * ... <---- more completions\n * {\n * index: 0,\n * finishReason: null,\n * delta: { content: \" ipsum\" } <---- end of the response\n * }\n * { index: 0, finishReason: 'stop', delta: {} } <---- end of the message\n */\n private handleStreamAsChatResponseMessage(stream: Readable): void {\n let content = '';\n\n stream.on('data', (completions: ChatCompletions) => {\n const choice = completions.choices[0];\n if (!choice) {\n throw new Error('No completions returned');\n }\n const delta = choice.delta;\n if (!delta) {\n throw new Error('No delta returned');\n }\n\n if (delta.content) {\n content += delta.content;\n\n // Write also to the stream of the thread\n if (!this._stream) {\n throw new Error('No stream available');\n }\n this._stream?.push(delta.content);\n }\n\n if (choice.finishReason === 'stop') {\n // Adds the assistant's response to the messages\n const message: ChatResponseMessage = {\n role: 'assistant',\n content,\n toolCalls: [],\n };\n this.doAddMessage(message);\n\n this.emit('completed');\n this._stream?.push(null);\n }\n });\n }\n\n private doAddMessage(\n message: ChatRequestMessage | ChatResponseMessage,\n ): void {\n this.messages.push(message);\n this.emit('message', message);\n\n if (isChatRequestMessage(message)) {\n this.emit('message:request', message);\n } else {\n this.emit('message:response', message);\n }\n }\n}\n\nexport class RequiredAction extends EventEmitter {\n constructor(public readonly toolCalls: ChatCompletionsToolCall[]) {\n super();\n }\n\n submitToolOutputs(toolOutputs: ToolOutput[]): void {\n this.emit('submitting', toolOutputs);\n }\n}\n\nexport interface ToolCall {\n name: string;\n arguments: Record;\n}\n\nexport interface ToolOutput {\n callId: string;\n value: unknown;\n}\n\nexport function isChatResponseMessage(\n m: ChatRequestMessage | ChatResponseMessage,\n): m is ChatResponseMessage {\n return 'toolCalls' in m;\n}\n\nexport function isChatRequestMessage(\n m: ChatRequestMessage | ChatResponseMessage,\n): m is ChatRequestMessage {\n return !isChatResponseMessage(m);\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/index.umd.js b/dist/index.umd.js
deleted file mode 100644
index 607f0b6..0000000
--- a/dist/index.umd.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*!
- * llobotomy-azure v0.0.0
- * (c) Matthieu Balmes
- * Released under the MIT License.
- */
-
-(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["llobotomy-azure"] = {}));
-})(this, (function (exports) { 'use strict';
-
- /**
- * Check if value is parseable to number.
- * @example
- * ```js
- * isNumberParseable('AAAA');
- * //=> false
- *
- * isNumberParseable('100');
- * //=> true
- *
- * if (!isNumberParseable(value))
- * throw new Error('Value can\'t be parseable to `Number`.')
- * return Number(value);
- * ```
- * @param value - An `unknown` value to be checked.
- */
- var isNumberParseable = function (value) {
- return !Number.isNaN(Number(value));
- };
-
- exports.isNumberParseable = isNumberParseable;
-
-}));
-//# sourceMappingURL=index.umd.js.map
diff --git a/dist/index.umd.js.map b/dist/index.umd.js.map
deleted file mode 100644
index 3bad2fe..0000000
--- a/dist/index.umd.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"index.umd.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * A Branded Type for values parseable to number.\n */\nexport type NumberParseable = (number | string | boolean) & {\n readonly isNumberParseble: unique symbol;\n};\n\n/**\n * Check if value is parseable to number.\n * @example\n * ```js\n * isNumberParseable('AAAA');\n * //=> false\n *\n * isNumberParseable('100');\n * //=> true\n *\n * if (!isNumberParseable(value))\n * throw new Error('Value can\\'t be parseable to `Number`.')\n * return Number(value);\n * ```\n * @param value - An `unknown` value to be checked.\n */\nexport const isNumberParseable = (value: unknown): value is NumberParseable =>\n !Number.isNaN(Number(value));\n"],"names":[],"mappings":";;;;;;;;;;;;EAOA;;;;;;;;;;;;;;;EAeG;AACI,MAAM,iBAAiB,GAAG,UAAC,KAAc,EAAA;MAC9C,OAAA,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;EAA5B;;;;;;;;"}
\ No newline at end of file
diff --git a/dist/index.umd.min.js b/dist/index.umd.min.js
deleted file mode 100644
index 7af2c1d..0000000
--- a/dist/index.umd.min.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * llobotomy-azure v0.0.0
- * (c) Matthieu Balmes
- * Released under the MIT License.
- */
-!function(e,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i((e="undefined"!=typeof globalThis?globalThis:e||self)["llobotomy-azure"]={})}(this,(function(e){"use strict";e.isNumberParseable=function(e){return!Number.isNaN(Number(e))}}));
-//# sourceMappingURL=index.umd.min.js.map
diff --git a/dist/index.umd.min.js.map b/dist/index.umd.min.js.map
deleted file mode 100644
index e15182a..0000000
--- a/dist/index.umd.min.js.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"index.umd.min.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * A Branded Type for values parseable to number.\n */\nexport type NumberParseable = (number | string | boolean) & {\n readonly isNumberParseble: unique symbol;\n};\n\n/**\n * Check if value is parseable to number.\n * @example\n * ```js\n * isNumberParseable('AAAA');\n * //=> false\n *\n * isNumberParseable('100');\n * //=> true\n *\n * if (!isNumberParseable(value))\n * throw new Error('Value can\\'t be parseable to `Number`.')\n * return Number(value);\n * ```\n * @param value - An `unknown` value to be checked.\n */\nexport const isNumberParseable = (value: unknown): value is NumberParseable =>\n !Number.isNaN(Number(value));\n"],"names":["value","Number","isNaN"],"mappings":";;;;;qRAuBiC,SAACA,GAChC,OAACC,OAAOC,MAAMD,OAAOD,GAArB"}
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index d8b6314..780d80f 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -15,3 +15,48 @@ LLobotoMy for Azure
- [AssistantCreateParams](interfaces/AssistantCreateParams.md)
- [ToolCall](interfaces/ToolCall.md)
- [ToolOutput](interfaces/ToolOutput.md)
+
+### Functions
+
+- [isChatRequestMessage](README.md#ischatrequestmessage)
+- [isChatResponseMessage](README.md#ischatresponsemessage)
+
+## Functions
+
+### isChatRequestMessage
+
+▸ **isChatRequestMessage**(`m`): m is ChatRequestMessage
+
+#### Parameters
+
+| Name | Type |
+| :------ | :------ |
+| `m` | `ChatRequestMessage` \| `ChatResponseMessage` |
+
+#### Returns
+
+m is ChatRequestMessage
+
+#### Defined in
+
+[src/thread/thread.ts:290](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L290)
+
+___
+
+### isChatResponseMessage
+
+▸ **isChatResponseMessage**(`m`): m is ChatResponseMessage
+
+#### Parameters
+
+| Name | Type |
+| :------ | :------ |
+| `m` | `ChatRequestMessage` \| `ChatResponseMessage` |
+
+#### Returns
+
+m is ChatResponseMessage
+
+#### Defined in
+
+[src/thread/thread.ts:284](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L284)
diff --git a/docs/classes/Assistant.md b/docs/classes/Assistant.md
index 160f7ed..1b3d40f 100644
--- a/docs/classes/Assistant.md
+++ b/docs/classes/Assistant.md
@@ -12,8 +12,8 @@
- [client](Assistant.md#client)
- [deployment](Assistant.md#deployment)
-- [functions](Assistant.md#functions)
- [instructions](Assistant.md#instructions)
+- [tools](Assistant.md#tools)
### Methods
@@ -33,7 +33,7 @@
#### Defined in
-[src/assistant/assistant.ts:20](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/assistant/assistant.ts#L20)
+[src/assistant/assistant.ts:24](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/assistant/assistant.ts#L24)
## Properties
@@ -43,7 +43,7 @@
#### Defined in
-[src/assistant/assistant.ts:14](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/assistant/assistant.ts#L14)
+[src/assistant/assistant.ts:18](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/assistant/assistant.ts#L18)
___
@@ -53,27 +53,27 @@ ___
#### Defined in
-[src/assistant/assistant.ts:18](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/assistant/assistant.ts#L18)
+[src/assistant/assistant.ts:22](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/assistant/assistant.ts#L22)
___
-### functions
+### instructions
-• `Private` `Readonly` **functions**: `FunctionDefinition`[]
+• `Private` `Readonly` **instructions**: `string`
#### Defined in
-[src/assistant/assistant.ts:17](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/assistant/assistant.ts#L17)
+[src/assistant/assistant.ts:20](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/assistant/assistant.ts#L20)
___
-### instructions
+### tools
-• `Private` `Readonly` **instructions**: `string`
+• `Private` `Readonly` **tools**: `ChatCompletionsFunctionToolDefinition`[]
#### Defined in
-[src/assistant/assistant.ts:16](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/assistant/assistant.ts#L16)
+[src/assistant/assistant.ts:21](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/assistant/assistant.ts#L21)
## Methods
@@ -85,7 +85,7 @@ ___
| Name | Type |
| :------ | :------ |
-| `messages` | `ChatMessage`[] |
+| `messages` | `ChatRequestMessage`[] |
#### Returns
@@ -93,4 +93,4 @@ ___
#### Defined in
-[src/assistant/assistant.ts:27](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/assistant/assistant.ts#L27)
+[src/assistant/assistant.ts:31](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/assistant/assistant.ts#L31)
diff --git a/docs/classes/RequiredAction.md b/docs/classes/RequiredAction.md
index cbb14ee..52b4be8 100644
--- a/docs/classes/RequiredAction.md
+++ b/docs/classes/RequiredAction.md
@@ -16,7 +16,7 @@
### Properties
-- [toolCall](RequiredAction.md#toolcall)
+- [toolCalls](RequiredAction.md#toolcalls)
- [captureRejectionSymbol](RequiredAction.md#capturerejectionsymbol)
- [captureRejections](RequiredAction.md#capturerejections)
- [defaultMaxListeners](RequiredAction.md#defaultmaxlisteners)
@@ -40,7 +40,7 @@
- [removeAllListeners](RequiredAction.md#removealllisteners)
- [removeListener](RequiredAction.md#removelistener)
- [setMaxListeners](RequiredAction.md#setmaxlisteners)
-- [submitToolOutput](RequiredAction.md#submittooloutput)
+- [submitToolOutputs](RequiredAction.md#submittooloutputs)
- [addAbortListener](RequiredAction.md#addabortlistener)
- [getEventListeners](RequiredAction.md#geteventlisteners)
- [getMaxListeners](RequiredAction.md#getmaxlisteners-1)
@@ -53,13 +53,13 @@
### constructor
-• **new RequiredAction**(`functionCall`)
+• **new RequiredAction**(`toolCalls`)
#### Parameters
| Name | Type |
| :------ | :------ |
-| `functionCall` | `FunctionCall` |
+| `toolCalls` | `ChatCompletionsFunctionToolCall`[] |
#### Overrides
@@ -67,17 +67,17 @@ EventEmitter.constructor
#### Defined in
-[src/thread/thread.ts:218](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L218)
+[src/thread/thread.ts:265](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L265)
## Properties
-### toolCall
+### toolCalls
-• **toolCall**: [`ToolCall`](../interfaces/ToolCall.md)
+• `Readonly` **toolCalls**: `ChatCompletionsFunctionToolCall`[]
#### Defined in
-[src/thread/thread.ts:216](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L216)
+[src/thread/thread.ts:265](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L265)
___
@@ -918,15 +918,15 @@ node_modules/@types/node/events.d.ts:716
___
-### submitToolOutput
+### submitToolOutputs
-▸ **submitToolOutput**(`toolOutput`): `void`
+▸ **submitToolOutputs**(`toolOutputs`): `void`
#### Parameters
| Name | Type |
| :------ | :------ |
-| `toolOutput` | [`ToolOutput`](../interfaces/ToolOutput.md) |
+| `toolOutputs` | [`ToolOutput`](../interfaces/ToolOutput.md)[] |
#### Returns
@@ -934,7 +934,7 @@ ___
#### Defined in
-[src/thread/thread.ts:227](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L227)
+[src/thread/thread.ts:269](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L269)
___
diff --git a/docs/classes/Thread.md b/docs/classes/Thread.md
index c9caaa4..a222d43 100644
--- a/docs/classes/Thread.md
+++ b/docs/classes/Thread.md
@@ -32,12 +32,14 @@
- [[captureRejectionSymbol]](Thread.md#[capturerejectionsymbol])
- [addListener](Thread.md#addlistener)
- [addMessage](Thread.md#addmessage)
+- [doAddMessage](Thread.md#doaddmessage)
- [doRun](Thread.md#dorun)
- [emit](Thread.md#emit)
- [eventNames](Thread.md#eventnames)
- [getMaxListeners](Thread.md#getmaxlisteners)
-- [handleStreamAsChatMessage](Thread.md#handlestreamaschatmessage)
-- [handleStreamAsFunctionCall](Thread.md#handlestreamasfunctioncall)
+- [getRequestMessages](Thread.md#getrequestmessages)
+- [handleStreamAsChatResponseMessage](Thread.md#handlestreamaschatresponsemessage)
+- [handleStreamAsToolCalls](Thread.md#handlestreamastoolcalls)
- [listenerCount](Thread.md#listenercount)
- [listeners](Thread.md#listeners)
- [off](Thread.md#off)
@@ -68,7 +70,7 @@
| Name | Type | Default value |
| :------ | :------ | :------ |
-| `messages` | `ChatMessage`[] | `[]` |
+| `messages` | `ChatRequestMessage`[] | `[]` |
#### Overrides
@@ -76,7 +78,7 @@ EventEmitter.constructor
#### Defined in
-[src/thread/thread.ts:9](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L9)
+[src/thread/thread.ts:17](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L17)
## Properties
@@ -86,17 +88,17 @@ EventEmitter.constructor
#### Defined in
-[src/thread/thread.ts:7](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L7)
+[src/thread/thread.ts:15](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L15)
___
### messages
-• `Private` `Readonly` **messages**: `ChatMessage`[] = `[]`
+• `Private` `Readonly` **messages**: (`ChatRequestMessage` \| `ChatResponseMessage`)[] = `[]`
#### Defined in
-[src/thread/thread.ts:9](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L9)
+[src/thread/thread.ts:13](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L13)
___
@@ -229,7 +231,7 @@ node_modules/@types/node/events.d.ts:395
#### Defined in
-[src/thread/thread.ts:13](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L13)
+[src/thread/thread.ts:22](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L22)
## Methods
@@ -298,7 +300,7 @@ ___
| Name | Type |
| :------ | :------ |
-| `message` | `ChatMessage` |
+| `message` | `ChatRequestMessage` |
#### Returns
@@ -306,7 +308,27 @@ ___
#### Defined in
-[src/thread/thread.ts:21](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L21)
+[src/thread/thread.ts:30](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L30)
+
+___
+
+### doAddMessage
+
+▸ `Private` **doAddMessage**(`message`): `void`
+
+#### Parameters
+
+| Name | Type |
+| :------ | :------ |
+| `message` | `ChatRequestMessage` \| `ChatResponseMessage` |
+
+#### Returns
+
+`void`
+
+#### Defined in
+
+[src/thread/thread.ts:250](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L250)
___
@@ -326,7 +348,7 @@ ___
#### Defined in
-[src/thread/thread.ts:33](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L33)
+[src/thread/thread.ts:41](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L41)
___
@@ -461,9 +483,26 @@ node_modules/@types/node/events.d.ts:722
___
-### handleStreamAsChatMessage
+### getRequestMessages
+
+▸ `Private` **getRequestMessages**(): `ChatRequestMessage`[]
+
+Convert the mix of ChatRequestMessages and ChatResponseMessages to ChatRequestMessages only
+so they can be sent again to the LLM.
+
+#### Returns
+
+`ChatRequestMessage`[]
+
+#### Defined in
+
+[src/thread/thread.ts:98](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L98)
+
+___
+
+### handleStreamAsChatResponseMessage
-▸ `Private` **handleStreamAsChatMessage**(`stream`): `void`
+▸ `Private` **handleStreamAsChatResponseMessage**(`stream`): `void`
Handles the stream as a chat message after we determined from the beginning of the stream that it is a chat message.
The stream emits some completions.
@@ -494,13 +533,13 @@ The first choice of these completions successively looks like this:
#### Defined in
-[src/thread/thread.ts:177](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L177)
+[src/thread/thread.ts:212](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L212)
___
-### handleStreamAsFunctionCall
+### handleStreamAsToolCalls
-▸ `Private` **handleStreamAsFunctionCall**(`name`, `stream`, `assistant`): `void`
+▸ `Private` **handleStreamAsToolCalls**(`toolCalls`, `stream`, `assistant`): `void`
Handles the stream as a function call after we determined from the beginning of the stream that it is a function call.
The stream emits some completions.
@@ -523,7 +562,7 @@ The first choice of these completions successively looks like this:
| Name | Type |
| :------ | :------ |
-| `name` | `string` |
+| `toolCalls` | `ChatCompletionsFunctionToolCall`[] |
| `stream` | `Readable` |
| `assistant` | [`Assistant`](Assistant.md) |
@@ -533,7 +572,7 @@ The first choice of these completions successively looks like this:
#### Defined in
-[src/thread/thread.ts:99](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L99)
+[src/thread/thread.ts:133](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L133)
___
@@ -1048,7 +1087,7 @@ ___
#### Defined in
-[src/thread/thread.ts:26](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L26)
+[src/thread/thread.ts:34](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L34)
___
diff --git a/docs/interfaces/AssistantCreateParams.md b/docs/interfaces/AssistantCreateParams.md
index 5437af5..7893220 100644
--- a/docs/interfaces/AssistantCreateParams.md
+++ b/docs/interfaces/AssistantCreateParams.md
@@ -8,8 +8,8 @@
- [client](AssistantCreateParams.md#client)
- [deployment](AssistantCreateParams.md#deployment)
-- [functions](AssistantCreateParams.md#functions)
- [instructions](AssistantCreateParams.md#instructions)
+- [tools](AssistantCreateParams.md#tools)
## Properties
@@ -19,7 +19,7 @@
#### Defined in
-[src/assistant/assistant.ts:7](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/assistant/assistant.ts#L7)
+[src/assistant/assistant.ts:11](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/assistant/assistant.ts#L11)
___
@@ -29,24 +29,24 @@ ___
#### Defined in
-[src/assistant/assistant.ts:10](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/assistant/assistant.ts#L10)
+[src/assistant/assistant.ts:14](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/assistant/assistant.ts#L14)
___
-### functions
+### instructions
-• **functions**: `FunctionDefinition`[]
+• **instructions**: `string`
#### Defined in
-[src/assistant/assistant.ts:9](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/assistant/assistant.ts#L9)
+[src/assistant/assistant.ts:12](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/assistant/assistant.ts#L12)
___
-### instructions
+### tools
-• **instructions**: `string`
+• **tools**: `ChatCompletionsFunctionToolDefinition`[]
#### Defined in
-[src/assistant/assistant.ts:8](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/assistant/assistant.ts#L8)
+[src/assistant/assistant.ts:13](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/assistant/assistant.ts#L13)
diff --git a/docs/interfaces/ToolCall.md b/docs/interfaces/ToolCall.md
index 8e7ee5d..827be13 100644
--- a/docs/interfaces/ToolCall.md
+++ b/docs/interfaces/ToolCall.md
@@ -17,7 +17,7 @@
#### Defined in
-[src/thread/thread.ts:234](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L234)
+[src/thread/thread.ts:276](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L276)
___
@@ -27,4 +27,4 @@ ___
#### Defined in
-[src/thread/thread.ts:233](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L233)
+[src/thread/thread.ts:275](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L275)
diff --git a/docs/interfaces/ToolOutput.md b/docs/interfaces/ToolOutput.md
index 3009ea3..4d55d0a 100644
--- a/docs/interfaces/ToolOutput.md
+++ b/docs/interfaces/ToolOutput.md
@@ -6,14 +6,25 @@
### Properties
+- [callId](ToolOutput.md#callid)
- [value](ToolOutput.md#value)
## Properties
+### callId
+
+• **callId**: `string`
+
+#### Defined in
+
+[src/thread/thread.ts:280](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L280)
+
+___
+
### value
• **value**: `unknown`
#### Defined in
-[src/thread/thread.ts:238](https://github.com/paztek/llobotomy-azure/blob/6b547f5/src/thread/thread.ts#L238)
+[src/thread/thread.ts:281](https://github.com/paztek/llobotomy-azure/blob/3780e4f/src/thread/thread.ts#L281)
diff --git a/examples/.gitkeep b/examples/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/package-lock.json b/package-lock.json
index b6e9603..714510e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,15 +1,15 @@
{
"name": "llobotomy-azure",
- "version": "0.0.0",
+ "version": "0.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "llobotomy-azure",
- "version": "0.0.0",
+ "version": "0.0.2",
"license": "MIT",
"dependencies": {
- "@azure/openai": "^1.0.0-beta.7"
+ "@azure/openai": "^1.0.0-beta.8"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^25.0.7",
@@ -100,37 +100,22 @@
"node": ">=14.0.0"
}
},
- "node_modules/@azure/core-lro": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.4.tgz",
- "integrity": "sha512-3GJiMVH7/10bulzOKGrrLeG/uCBH/9VtxqaMcB9lIqAeamI/xYQSHJL/KcsLDuH+yTjYpro/u6D/MuRe4dN70Q==",
- "dependencies": {
- "@azure/abort-controller": "^1.0.0",
- "@azure/core-util": "^1.2.0",
- "@azure/logger": "^1.0.0",
- "tslib": "^2.2.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
"node_modules/@azure/core-rest-pipeline": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.12.2.tgz",
- "integrity": "sha512-wLLJQdL4v1yoqYtEtjKNjf8pJ/G/BqVomAWxcKOR1KbZJyCEnCv04yks7Y1NhJ3JzxbDs307W67uX0JzklFdCg==",
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.13.0.tgz",
+ "integrity": "sha512-a62aP/wppgmnfIkJLfcB4ssPBcH94WzrzPVJ3tlJt050zX4lfmtnvy95D3igDo3f31StO+9BgPrzvkj4aOxnoA==",
"dependencies": {
- "@azure/abort-controller": "^1.0.0",
+ "@azure/abort-controller": "^1.1.0",
"@azure/core-auth": "^1.4.0",
"@azure/core-tracing": "^1.0.1",
"@azure/core-util": "^1.3.0",
"@azure/logger": "^1.0.0",
- "form-data": "^4.0.0",
"http-proxy-agent": "^5.0.0",
"https-proxy-agent": "^5.0.0",
"tslib": "^2.2.0"
},
"engines": {
- "node": ">=16.0.0"
+ "node": ">=18.0.0"
}
},
"node_modules/@azure/core-sse": {
@@ -179,18 +164,16 @@
}
},
"node_modules/@azure/openai": {
- "version": "1.0.0-beta.7",
- "resolved": "https://registry.npmjs.org/@azure/openai/-/openai-1.0.0-beta.7.tgz",
- "integrity": "sha512-LhZXgEGOoDBYyLDnSe5XwkLM5OUC8RppVW1X6+IrmmGnonsaa0POQ2BYr45YAqlpggtATYRVUbmyLi5SxMzhLg==",
+ "version": "1.0.0-beta.8",
+ "resolved": "https://registry.npmjs.org/@azure/openai/-/openai-1.0.0-beta.8.tgz",
+ "integrity": "sha512-IUwRhV1qrMJR5J98HwIZuzACMFwgkQoAHY8q9z9aO30ClZ+nU2LgWCXYfrSaI/WA4UpHbET8G+TI/191LaPl6w==",
"dependencies": {
"@azure-rest/core-client": "^1.1.4",
"@azure/core-auth": "^1.4.0",
- "@azure/core-lro": "^2.5.3",
- "@azure/core-rest-pipeline": "^1.12.2",
+ "@azure/core-rest-pipeline": "^1.13.0",
"@azure/core-sse": "^1.0.0",
+ "@azure/core-util": "^1.4.0",
"@azure/logger": "^1.0.3",
- "form-data-encoder": "^3.0.0",
- "formdata-node": "^5.0.0",
"tslib": "^2.4.0"
},
"engines": {
@@ -2120,11 +2103,6 @@
"node": ">=8"
}
},
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
- },
"node_modules/babel-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
@@ -2517,17 +2495,6 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "dependencies": {
- "delayed-stream": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
@@ -2788,14 +2755,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "engines": {
- "node": ">=0.4.0"
- }
- },
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -3317,39 +3276,6 @@
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
"dev": true
},
- "node_modules/form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/form-data-encoder": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-3.0.1.tgz",
- "integrity": "sha512-f8HPYqVUtZcpe+eg0xxDXryMxfFMZdNQZVXs3KOY3nSeLUDQBaz3w3UUVXJSgR266pgW4ruwnvV5JR+cJJD6dw==",
- "engines": {
- "node": ">= 16.5"
- }
- },
- "node_modules/formdata-node": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-5.0.1.tgz",
- "integrity": "sha512-8xnIjMYGKPj+rY2BTbAmpqVpi8der/2FT4d9f7J32FlsCpO5EzZPq3C/N56zdv8KweHzVF6TGijsS1JT6r1H2g==",
- "dependencies": {
- "node-domexception": "1.0.0",
- "web-streams-polyfill": "4.0.0-beta.3"
- },
- "engines": {
- "node": ">= 14.17"
- }
- },
"node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
@@ -4754,25 +4680,6 @@
"node": ">=8.6"
}
},
- "node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
@@ -4832,24 +4739,6 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
- "node_modules/node-domexception": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
- "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "github",
- "url": "https://paypal.me/jimmywarting"
- }
- ],
- "engines": {
- "node": ">=10.5.0"
- }
- },
"node_modules/node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -6148,14 +6037,6 @@
"makeerror": "1.0.12"
}
},
- "node_modules/web-streams-polyfill": {
- "version": "4.0.0-beta.3",
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
- "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
- "engines": {
- "node": ">= 14"
- }
- },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index 99d2962..deda0d6 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
"lint": "eslint \"*/**/*.{ts,js,json}\"",
"lint:fix": "eslint \"*/**/*.{ts,js,json}\" --fix",
"build": "rollup --config ./rollup.config.mjs",
+ "build:watch": "rollup --config ./rollup.config.mjs --watch",
"prepublishOnly": "npm run doc && npm run lint && npm run test && npm run build",
"examples:insurance": "ts-node-dev examples/insurance/index.ts"
},
@@ -69,6 +70,6 @@
},
"homepage": "https://github.com/paztek/llobotomy-azure#readme",
"dependencies": {
- "@azure/openai": "^1.0.0-beta.7"
+ "@azure/openai": "^1.0.0-beta.8"
}
}
diff --git a/src/assistant/assistant.ts b/src/assistant/assistant.ts
index d7c6914..0a74b32 100644
--- a/src/assistant/assistant.ts
+++ b/src/assistant/assistant.ts
@@ -1,12 +1,16 @@
-import type { FunctionDefinition, OpenAIClient } from '@azure/openai';
-import type { GetChatCompletionsOptions } from '@azure/openai/types/src/api/models';
-import type { ChatMessage } from '@azure/openai/types/src/models/models';
+import type {
+ ChatRequestMessage,
+ ChatRequestSystemMessage,
+ GetChatCompletionsOptions,
+ OpenAIClient,
+} from '@azure/openai';
+import type { ChatCompletionsToolDefinition } from '@azure/openai/types/src/models/models';
import { Readable } from 'stream';
export interface AssistantCreateParams {
client: OpenAIClient;
instructions: string;
- functions: FunctionDefinition[];
+ tools: ChatCompletionsToolDefinition[];
deployment: string;
}
@@ -14,26 +18,26 @@ export class Assistant {
public readonly client: OpenAIClient;
private readonly instructions: string;
- private readonly functions: FunctionDefinition[];
+ private readonly tools: ChatCompletionsToolDefinition[];
private readonly deployment: string;
constructor(params: AssistantCreateParams) {
this.client = params.client;
this.instructions = params.instructions;
- this.functions = params.functions;
+ this.tools = params.tools;
this.deployment = params.deployment;
}
- listChatCompletions(messages: ChatMessage[]): Readable {
+ listChatCompletions(messages: ChatRequestMessage[]): Readable {
// Prepend the messages with our instructions as a "system" message
- const systemMessage: ChatMessage = {
+ const systemMessage: ChatRequestSystemMessage = {
role: 'system',
content: this.instructions,
};
messages = [systemMessage, ...messages];
const options: GetChatCompletionsOptions = {
- functions: this.functions,
+ tools: this.tools,
};
const completions = this.client.listChatCompletions(
diff --git a/src/thread/thread.spec.ts b/src/thread/thread.spec.ts
index 4b77dca..df0132f 100644
--- a/src/thread/thread.spec.ts
+++ b/src/thread/thread.spec.ts
@@ -1,4 +1,10 @@
-import type { ChatChoice, ChatCompletions, ChatMessage } from '@azure/openai';
+import type {
+ ChatChoice,
+ ChatCompletions,
+ ChatRequestMessage,
+ ChatRequestUserMessage,
+ ChatResponseMessage,
+} from '@azure/openai';
import { mock } from 'jest-mock-extended';
import { Readable } from 'stream';
import { Assistant } from '../assistant';
@@ -21,14 +27,15 @@ describe('Thread', () => {
});
describe('addMessage', () => {
- const message: ChatMessage = {
+ const message: ChatRequestUserMessage = {
role: 'user',
content: 'Hello',
};
- it('emits a "message" event', () => {
+ it('emits a "message" and a "message:request" events', () => {
thread.addMessage(message);
- expect(emitSpy).toHaveBeenCalledWith('message', message);
+ expect(emitSpy).nthCalledWith(1, 'message', message);
+ expect(emitSpy).nthCalledWith(2, 'message:request', message);
});
});
@@ -42,7 +49,7 @@ describe('Thread', () => {
expect(emitSpy).toHaveBeenCalledWith('in_progress');
});
- describe('when the completions from the assistant are a function call', () => {
+ describe('when the completions from the assistant are some tool calls', () => {
const functionName = 'get_customer_profile';
beforeEach(() => {
@@ -52,56 +59,76 @@ describe('Thread', () => {
finishReason: null,
delta: {
role: 'assistant',
- functionCall: {
- name: functionName,
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- arguments: undefined,
- },
+ toolCalls: [
+ {
+ id: 'tool-call-1234abc',
+ type: 'function',
+ function: {
+ name: functionName,
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ arguments: undefined,
+ },
+ },
+ ],
},
},
{
index: 0,
finishReason: null,
delta: {
- functionCall: {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- name: undefined,
- arguments: '{"',
- },
+ toolCalls: [
+ {
+ function: {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ name: undefined,
+ arguments: '{"',
+ },
+ },
+ ],
},
},
{
index: 0,
finishReason: null,
delta: {
- functionCall: {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- name: undefined,
- arguments: 'id":"',
- },
+ toolCalls: [
+ {
+ function: {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ name: undefined,
+ arguments: 'id":"',
+ },
+ },
+ ],
},
},
{
index: 0,
finishReason: null,
delta: {
- functionCall: {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- name: undefined,
- arguments: 'ABC"}',
- },
+ toolCalls: [
+ {
+ function: {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ name: undefined,
+ arguments: 'ABC"}',
+ },
+ },
+ ],
},
},
{
index: 0,
- finishReason: 'function_call',
+ finishReason: 'tool_calls',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
- delta: {},
+ delta: {
+ toolCalls: [],
+ },
},
];
const completions: ChatCompletions[] = choices.map(
@@ -109,6 +136,7 @@ describe('Thread', () => {
id: 'chat-cmpl-1234abc',
created: new Date(),
choices: [choice],
+ promptFilterResults: [],
}),
);
@@ -119,14 +147,14 @@ describe('Thread', () => {
);
});
- it('emits a "message" event from the assistant, with the function call parameters', async () => {
+ it('emits a "message" event from the assistant, with the tool calls parameters', async () => {
thread.run(assistant);
// The "message" event is sent asynchronously, we need to wait for it to be emitted
await new Promise((resolve, reject) => {
thread.on('error', reject);
thread.on('message', (message) => {
- if (isFunctionCallMessage(message)) {
+ if (isChatResponseAssistantToolCallsMessage(message)) {
resolve();
}
});
@@ -135,12 +163,18 @@ describe('Thread', () => {
expect(emitSpy).nthCalledWith(2, 'message', {
role: 'assistant',
content: null,
- functionCall: {
- name: functionName,
- arguments: JSON.stringify({
- id: 'ABC',
- }),
- },
+ toolCalls: [
+ {
+ id: 'tool-call-1234abc',
+ type: 'function',
+ function: {
+ name: functionName,
+ arguments: JSON.stringify({
+ id: 'ABC',
+ }),
+ },
+ },
+ ],
});
});
@@ -154,18 +188,22 @@ describe('Thread', () => {
});
expect(emitSpy).nthCalledWith(
- 3,
+ 4,
'requires_action',
expect.any(RequiredAction),
);
const requiredAction = emitSpy.mock
.calls[2][1] as RequiredAction;
- expect(requiredAction.toolCall).toBeDefined();
- expect(requiredAction.toolCall.name).toEqual(functionName);
- expect(requiredAction.toolCall.arguments).toEqual({
- id: 'ABC',
- });
+ expect(requiredAction.toolCalls).toBeDefined();
+ expect(requiredAction.toolCalls[0]?.function.name).toEqual(
+ functionName,
+ );
+ expect(requiredAction.toolCalls[0]?.function.arguments).toEqual(
+ JSON.stringify({
+ id: 'ABC',
+ }),
+ );
});
});
@@ -179,6 +217,7 @@ describe('Thread', () => {
// @ts-ignore
delta: {
role: 'assistant',
+ toolCalls: [],
},
},
{
@@ -188,6 +227,7 @@ describe('Thread', () => {
// @ts-ignore
delta: {
content: 'Lorem ipsum',
+ toolCalls: [],
},
},
{
@@ -197,6 +237,7 @@ describe('Thread', () => {
// @ts-ignore
delta: {
content: ' dolor sit',
+ toolCalls: [],
},
},
{
@@ -206,6 +247,7 @@ describe('Thread', () => {
// @ts-ignore
delta: {
content: ' amet',
+ toolCalls: [],
},
},
{
@@ -221,6 +263,7 @@ describe('Thread', () => {
id: 'chat-cmpl-1234abc',
created: new Date(),
choices: [choice],
+ promptFilterResults: [],
}),
);
@@ -238,7 +281,7 @@ describe('Thread', () => {
await new Promise((resolve, reject) => {
thread.on('error', reject);
thread.on('message', (message) => {
- if (isTextMessage(message)) {
+ if (isChatResponseAssistantContentMessage(message)) {
resolve();
}
});
@@ -247,6 +290,7 @@ describe('Thread', () => {
expect(emitSpy).nthCalledWith(2, 'message', {
role: 'assistant',
content: 'Lorem ipsum dolor sit amet',
+ toolCalls: [],
});
});
@@ -259,7 +303,7 @@ describe('Thread', () => {
thread.on('completed', resolve);
});
- expect(emitSpy).nthCalledWith(3, 'completed');
+ expect(emitSpy).nthCalledWith(4, 'completed');
});
it('also writes to the stream of the thread', async () => {
@@ -289,12 +333,16 @@ async function* createAsyncIterable(items: T[]): AsyncIterable {
}
}
-function isFunctionCallMessage(message: ChatMessage): boolean {
- return (
- message.role === 'assistant' && message.functionCall?.name !== undefined
- );
+function isChatResponseAssistantContentMessage(
+ m: ChatRequestMessage | ChatResponseMessage,
+): m is ChatResponseMessage {
+ return m.role === 'assistant' && m.content !== null;
}
-function isTextMessage(message: ChatMessage): boolean {
- return message.role === 'assistant' && message.functionCall === undefined;
+function isChatResponseAssistantToolCallsMessage(
+ m: ChatRequestMessage | ChatResponseMessage,
+): m is ChatResponseMessage {
+ return (
+ m.role === 'assistant' && 'toolCalls' in m && m.toolCalls?.length > 0
+ );
}
diff --git a/src/thread/thread.ts b/src/thread/thread.ts
index ecbf3ac..aabce8d 100644
--- a/src/thread/thread.ts
+++ b/src/thread/thread.ts
@@ -1,13 +1,22 @@
-import type { ChatCompletions, ChatMessage, FunctionCall } from '@azure/openai';
+import type {
+ ChatCompletions,
+ ChatCompletionsToolCall,
+ ChatRequestMessage,
+ ChatRequestToolMessage,
+ ChatResponseMessage,
+} from '@azure/openai';
import EventEmitter from 'events';
import { Readable } from 'stream';
import { Assistant } from '../assistant';
export class Thread extends EventEmitter {
+ private readonly messages: (ChatRequestMessage | ChatResponseMessage)[] =
+ [];
private _stream: Readable | null = null;
- constructor(private readonly messages: ChatMessage[] = []) {
+ constructor(messages: ChatRequestMessage[] = []) {
super();
+ this.messages = messages;
}
get stream(): Readable | null {
@@ -18,9 +27,8 @@ export class Thread extends EventEmitter {
return this._stream;
}
- addMessage(message: ChatMessage): void {
- this.messages.push(message);
- this.emit('message', message);
+ addMessage(message: ChatRequestMessage): void {
+ this.doAddMessage(message);
}
run(assistant: Assistant): void {
@@ -33,7 +41,9 @@ export class Thread extends EventEmitter {
private doRun(assistant: Assistant): void {
this.emit('in_progress');
- const stream = assistant.listChatCompletions(this.messages);
+ const messages = this.getRequestMessages();
+
+ const stream = assistant.listChatCompletions(messages);
/**
* When the LLM responds with a function call, the first completion's first choice looks like this:
@@ -69,11 +79,35 @@ export class Thread extends EventEmitter {
throw new Error('No delta returned');
}
- if (delta.functionCall) {
- const name = delta.functionCall.name;
- this.handleStreamAsFunctionCall(name, stream, assistant);
+ if (delta.toolCalls.length > 0) {
+ this.handleStreamAsToolCalls(
+ delta.toolCalls,
+ stream,
+ assistant,
+ );
} else {
- this.handleStreamAsChatMessage(stream);
+ this.handleStreamAsChatResponseMessage(stream);
+ }
+ });
+ }
+
+ /**
+ * Convert the mix of ChatRequestMessages and ChatResponseMessages to ChatRequestMessages only
+ * so they can be sent again to the LLM.
+ */
+ private getRequestMessages(): ChatRequestMessage[] {
+ return this.messages.map((m) => {
+ if (m.role === 'system' || m.role === 'user' || m.role === 'tool') {
+ // These are messages from the application (a.k.a request messages)
+ return m as ChatRequestMessage;
+ } else {
+ // These are messages from the assistant (a.k.a response messages)
+ const responseMessage = m as ChatResponseMessage;
+ return {
+ role: 'assistant',
+ content: responseMessage.content,
+ toolCalls: responseMessage.toolCalls,
+ };
}
});
}
@@ -96,12 +130,12 @@ export class Thread extends EventEmitter {
* }
* { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call
*/
- private handleStreamAsFunctionCall(
- name: string,
+ private handleStreamAsToolCalls(
+ toolCalls: ChatCompletionsToolCall[],
stream: Readable,
assistant: Assistant,
): void {
- let args = '';
+ const argsList = Array(toolCalls.length).fill('');
stream.on('data', (completions: ChatCompletions) => {
const choice = completions.choices[0];
@@ -113,40 +147,41 @@ export class Thread extends EventEmitter {
throw new Error('No delta returned');
}
- if (delta.functionCall) {
- const functionCall = delta.functionCall;
- if (functionCall.arguments) {
- args += functionCall.arguments;
- }
- }
-
- if (choice.finishReason === 'function_call') {
- const functionCall: FunctionCall = {
- name,
- arguments: args,
- };
+ delta.toolCalls.forEach((toolCall, index) => {
+ argsList[index] += toolCall.function.arguments;
+ });
+
+ if (choice.finishReason === 'tool_calls') {
+ const finalToolCalls: ChatCompletionsToolCall[] = toolCalls.map(
+ (toolCall, index) => ({
+ ...toolCall,
+ function: {
+ ...toolCall.function,
+ arguments: argsList[index],
+ },
+ }),
+ );
// Adds the assistant's response to the messages
- const message: ChatMessage = {
+ const message: ChatResponseMessage = {
role: 'assistant',
content: null,
- functionCall,
+ toolCalls: finalToolCalls,
};
- this.addMessage(message);
+ this.doAddMessage(message);
- const requiredAction = new RequiredAction({
- name,
- arguments: args,
- });
+ const requiredAction = new RequiredAction(finalToolCalls);
- requiredAction.on('submitting', (toolOutput: ToolOutput) => {
- // Adds the tool output to the messages
- const message: ChatMessage = {
- role: 'function',
- name: functionCall.name,
- content: JSON.stringify(toolOutput),
- };
- this.addMessage(message);
+ requiredAction.on('submitting', (toolOutputs: ToolOutput[]) => {
+ // Adds the tool outputs to the messages
+ for (const toolOutput of toolOutputs) {
+ const message: ChatRequestToolMessage = {
+ role: 'tool',
+ content: JSON.stringify(toolOutput.value),
+ toolCallId: toolOutput.callId,
+ };
+ this.doAddMessage(message);
+ }
this.doRun(assistant);
});
@@ -174,7 +209,7 @@ export class Thread extends EventEmitter {
* }
* { index: 0, finishReason: 'stop', delta: {} } <---- end of the message
*/
- private handleStreamAsChatMessage(stream: Readable): void {
+ private handleStreamAsChatResponseMessage(stream: Readable): void {
let content = '';
stream.on('data', (completions: ChatCompletions) => {
@@ -199,33 +234,40 @@ export class Thread extends EventEmitter {
if (choice.finishReason === 'stop') {
// Adds the assistant's response to the messages
- const message: ChatMessage = {
+ const message: ChatResponseMessage = {
role: 'assistant',
content,
+ toolCalls: [],
};
- this.addMessage(message);
+ this.doAddMessage(message);
this.emit('completed');
this._stream?.push(null);
}
});
}
+
+ private doAddMessage(
+ message: ChatRequestMessage | ChatResponseMessage,
+ ): void {
+ this.messages.push(message);
+ this.emit('message', message);
+
+ if (isChatRequestMessage(message)) {
+ this.emit('message:request', message);
+ } else {
+ this.emit('message:response', message);
+ }
+ }
}
export class RequiredAction extends EventEmitter {
- toolCall: ToolCall;
-
- constructor(functionCall: FunctionCall) {
+ constructor(public readonly toolCalls: ChatCompletionsToolCall[]) {
super();
-
- this.toolCall = {
- name: functionCall.name,
- arguments: JSON.parse(functionCall.arguments),
- };
}
- submitToolOutput(toolOutput: ToolOutput): void {
- this.emit('submitting', toolOutput);
+ submitToolOutputs(toolOutputs: ToolOutput[]): void {
+ this.emit('submitting', toolOutputs);
}
}
@@ -235,5 +277,18 @@ export interface ToolCall {
}
export interface ToolOutput {
+ callId: string;
value: unknown;
}
+
+export function isChatResponseMessage(
+ m: ChatRequestMessage | ChatResponseMessage,
+): m is ChatResponseMessage {
+ return 'toolCalls' in m;
+}
+
+export function isChatRequestMessage(
+ m: ChatRequestMessage | ChatResponseMessage,
+): m is ChatRequestMessage {
+ return !isChatResponseMessage(m);
+}
diff --git a/types/assistant/assistant.d.ts b/types/assistant/assistant.d.ts
index b4b8736..d1cccf3 100644
--- a/types/assistant/assistant.d.ts
+++ b/types/assistant/assistant.d.ts
@@ -1,19 +1,19 @@
///
-import type { FunctionDefinition, OpenAIClient } from '@azure/openai';
-import type { ChatMessage } from '@azure/openai/types/src/models/models';
+import type { ChatRequestMessage, OpenAIClient } from '@azure/openai';
+import type { ChatCompletionsToolDefinition } from '@azure/openai/types/src/models/models';
import { Readable } from 'stream';
export interface AssistantCreateParams {
client: OpenAIClient;
instructions: string;
- functions: FunctionDefinition[];
+ tools: ChatCompletionsToolDefinition[];
deployment: string;
}
export declare class Assistant {
readonly client: OpenAIClient;
private readonly instructions;
- private readonly functions;
+ private readonly tools;
private readonly deployment;
constructor(params: AssistantCreateParams);
- listChatCompletions(messages: ChatMessage[]): Readable;
+ listChatCompletions(messages: ChatRequestMessage[]): Readable;
}
//# sourceMappingURL=assistant.d.ts.map
\ No newline at end of file
diff --git a/types/assistant/assistant.d.ts.map b/types/assistant/assistant.d.ts.map
index 5dd0cf1..c785980 100644
--- a/types/assistant/assistant.d.ts.map
+++ b/types/assistant/assistant.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"assistant.d.ts","sourceRoot":"","sources":["../../src/assistant/assistant.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,MAAM,WAAW,qBAAqB;IAClC,MAAM,EAAE,YAAY,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,kBAAkB,EAAE,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,SAAS;IAClB,SAAgB,MAAM,EAAE,YAAY,CAAC;IAErC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuB;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,MAAM,EAAE,qBAAqB;IAOzC,mBAAmB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,QAAQ;CAsBzD"}
\ No newline at end of file
+{"version":3,"file":"assistant.d.ts","sourceRoot":"","sources":["../../src/assistant/assistant.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EACR,kBAAkB,EAGlB,YAAY,EACf,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,uCAAuC,CAAC;AAC3F,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,MAAM,WAAW,qBAAqB;IAClC,MAAM,EAAE,YAAY,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,6BAA6B,EAAE,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,SAAS;IAClB,SAAgB,MAAM,EAAE,YAAY,CAAC;IAErC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IACxD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,MAAM,EAAE,qBAAqB;IAOzC,mBAAmB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,QAAQ;CAsBhE"}
\ No newline at end of file
diff --git a/types/assistant/function-tool.d.ts b/types/assistant/function-tool.d.ts
deleted file mode 100644
index e0dbdda..0000000
--- a/types/assistant/function-tool.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { FunctionDefinition } from '@azure/openai';
-export interface FunctionTool = Record, U = unknown> {
- definition: FunctionDefinition;
- execute(args: T): Promise;
-}
-//# sourceMappingURL=function-tool.d.ts.map
\ No newline at end of file
diff --git a/types/assistant/function-tool.d.ts.map b/types/assistant/function-tool.d.ts.map
deleted file mode 100644
index 8d116fa..0000000
--- a/types/assistant/function-tool.d.ts.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"file":"function-tool.d.ts","sourceRoot":"","sources":["../../src/assistant/function-tool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,MAAM,WAAW,YAAY,CACzB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,CAAC,GAAG,OAAO;IAEX,UAAU,EAAE,kBAAkB,CAAC;IAC/B,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAChC"}
\ No newline at end of file
diff --git a/types/thread/thread.d.ts b/types/thread/thread.d.ts
index 7b0ba18..ead645e 100644
--- a/types/thread/thread.d.ts
+++ b/types/thread/thread.d.ts
@@ -1,17 +1,22 @@
///
///
-import type { ChatMessage, FunctionCall } from '@azure/openai';
+import type { ChatCompletionsToolCall, ChatRequestMessage, ChatResponseMessage } from '@azure/openai';
import EventEmitter from 'events';
import { Readable } from 'stream';
import { Assistant } from '../assistant';
export declare class Thread extends EventEmitter {
private readonly messages;
private _stream;
- constructor(messages?: ChatMessage[]);
+ constructor(messages?: ChatRequestMessage[]);
get stream(): Readable | null;
- addMessage(message: ChatMessage): void;
+ addMessage(message: ChatRequestMessage): void;
run(assistant: Assistant): void;
private doRun;
+ /**
+ * Convert the mix of ChatRequestMessages and ChatResponseMessages to ChatRequestMessages only
+ * so they can be sent again to the LLM.
+ */
+ private getRequestMessages;
/**
* Handles the stream as a function call after we determined from the beginning of the stream that it is a function call.
* The stream emits some completions.
@@ -30,7 +35,7 @@ export declare class Thread extends EventEmitter {
* }
* { index: 0, finishReason: 'function_call', delta: {} } <---- end of the function call
*/
- private handleStreamAsFunctionCall;
+ private handleStreamAsToolCalls;
/**
* Handles the stream as a chat message after we determined from the beginning of the stream that it is a chat message.
* The stream emits some completions.
@@ -49,18 +54,22 @@ export declare class Thread extends EventEmitter {
* }
* { index: 0, finishReason: 'stop', delta: {} } <---- end of the message
*/
- private handleStreamAsChatMessage;
+ private handleStreamAsChatResponseMessage;
+ private doAddMessage;
}
export declare class RequiredAction extends EventEmitter {
- toolCall: ToolCall;
- constructor(functionCall: FunctionCall);
- submitToolOutput(toolOutput: ToolOutput): void;
+ readonly toolCalls: ChatCompletionsToolCall[];
+ constructor(toolCalls: ChatCompletionsToolCall[]);
+ submitToolOutputs(toolOutputs: ToolOutput[]): void;
}
export interface ToolCall {
name: string;
arguments: Record;
}
export interface ToolOutput {
+ callId: string;
value: unknown;
}
+export declare function isChatResponseMessage(m: ChatRequestMessage | ChatResponseMessage): m is ChatResponseMessage;
+export declare function isChatRequestMessage(m: ChatRequestMessage | ChatResponseMessage): m is ChatRequestMessage;
//# sourceMappingURL=thread.d.ts.map
\ No newline at end of file
diff --git a/types/thread/thread.d.ts.map b/types/thread/thread.d.ts.map
index cc06bb7..a317db1 100644
--- a/types/thread/thread.d.ts.map
+++ b/types/thread/thread.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../../src/thread/thread.ts"],"names":[],"mappings":";;AAAA,OAAO,KAAK,EAAmB,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,YAAY,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,qBAAa,MAAO,SAAQ,YAAY;IAGxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAFrC,OAAO,CAAC,OAAO,CAAyB;gBAEX,QAAQ,GAAE,WAAW,EAAO;IAIzD,IAAI,MAAM,IAAI,QAAQ,GAAG,IAAI,CAM5B;IAED,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAKtC,GAAG,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAO/B,OAAO,CAAC,KAAK;IAgDb;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,0BAA0B;IA4DlC;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,yBAAyB;CAoCpC;AAED,qBAAa,cAAe,SAAQ,YAAY;IAC5C,QAAQ,EAAE,QAAQ,CAAC;gBAEP,YAAY,EAAE,YAAY;IAStC,gBAAgB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;CAGjD;AAED,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,OAAO,CAAC;CAClB"}
\ No newline at end of file
+{"version":3,"file":"thread.d.ts","sourceRoot":"","sources":["../../src/thread/thread.ts"],"names":[],"mappings":";;AAAA,OAAO,KAAK,EAER,uBAAuB,EACvB,kBAAkB,EAElB,mBAAmB,EACtB,MAAM,eAAe,CAAC;AACvB,OAAO,YAAY,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,qBAAa,MAAO,SAAQ,YAAY;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAClB;IACP,OAAO,CAAC,OAAO,CAAyB;gBAE5B,QAAQ,GAAE,kBAAkB,EAAO;IAK/C,IAAI,MAAM,IAAI,QAAQ,GAAG,IAAI,CAM5B;IAED,UAAU,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAI7C,GAAG,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAO/B,OAAO,CAAC,KAAK;IAqDb;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,uBAAuB;IA6D/B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,iCAAiC;IAsCzC,OAAO,CAAC,YAAY;CAYvB;AAED,qBAAa,cAAe,SAAQ,YAAY;aAChB,SAAS,EAAE,uBAAuB,EAAE;gBAApC,SAAS,EAAE,uBAAuB,EAAE;IAIhE,iBAAiB,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,IAAI;CAGrD;AAED,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,UAAU;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,qBAAqB,CACjC,CAAC,EAAE,kBAAkB,GAAG,mBAAmB,GAC5C,CAAC,IAAI,mBAAmB,CAE1B;AAED,wBAAgB,oBAAoB,CAChC,CAAC,EAAE,kBAAkB,GAAG,mBAAmB,GAC5C,CAAC,IAAI,kBAAkB,CAEzB"}
\ No newline at end of file