1
+ // ℹ️ This file was originally sourced from the LangChain repository.
2
+ // It has been modified to allow a different ref strategy
3
+ // and to improve logging and error handling.
4
+
5
+ import {
6
+ FunctionDefinition ,
7
+ ToolDefinition ,
8
+ } from '@langchain/core/language_models/base' ;
1
9
import {
2
10
AIMessage ,
3
11
BaseMessage ,
@@ -14,8 +22,13 @@ import {
14
22
RunnableLike ,
15
23
RunnablePassthrough ,
16
24
RunnableSequence ,
25
+ RunnableToolLike ,
17
26
} from '@langchain/core/runnables' ;
18
- import type { StructuredToolInterface } from '@langchain/core/tools' ;
27
+ import type {
28
+ StructuredToolInterface ,
29
+ StructuredToolParams ,
30
+ } from '@langchain/core/tools' ;
31
+ import { isLangChainTool } from '@langchain/core/utils/function_calling' ;
19
32
import { OpenAIClient } from '@langchain/openai' ;
20
33
import { AnyType } from '@lightdash/common' ;
21
34
import {
@@ -76,25 +89,88 @@ export class AgentRunnableSequence<
76
89
}
77
90
}
78
91
79
- function formatToOpenAIAssistantTool ( tool : StructuredToolInterface ) {
80
- const jsonSchema = zodToJsonSchema ( tool . schema , {
81
- $refStrategy : 'none' ,
82
- } ) ;
83
-
84
- // console.debug('~~~~~~~~~~~~~~~~~~~~');
85
- // console.debug(jsonSchema);
86
- // console.debug('~~~~~~~~~~~~~~~~~~~~');
92
+ /**
93
+ * Formats a `StructuredTool` or `RunnableToolLike` instance into a format
94
+ * that is compatible with OpenAI function calling. It uses the `zodToJsonSchema`
95
+ * function to convert the schema of the `StructuredTool` or `RunnableToolLike`
96
+ * into a JSON schema, which is then used as the parameters for the OpenAI function.
97
+ *
98
+ * @param {StructuredToolInterface | RunnableToolLike } tool The tool to convert to an OpenAI function.
99
+ * @returns {FunctionDefinition } The inputted tool in OpenAI function format.
100
+ */
101
+ export function convertToOpenAIFunction (
102
+ tool : StructuredToolInterface | RunnableToolLike | StructuredToolParams ,
103
+ fields ?:
104
+ | {
105
+ /**
106
+ * If `true`, model output is guaranteed to exactly match the JSON Schema
107
+ * provided in the function definition.
108
+ */
109
+ strict ?: boolean ;
110
+ }
111
+ | number ,
112
+ ) : FunctionDefinition {
113
+ // @TODO 0.3.0 Remove the `number` typing
114
+ const fieldsCopy = typeof fields === 'number' ? undefined : fields ;
87
115
88
116
return {
89
- type : 'function' ,
90
- function : {
91
- name : tool . name ,
92
- description : tool . description ,
93
- parameters : jsonSchema ,
94
- } ,
117
+ name : tool . name ,
118
+ description : tool . description ,
119
+ parameters : zodToJsonSchema ( tool . schema , {
120
+ // ℹ️ we have to use `none` strategy, otherwise it breaks.
121
+ $refStrategy : 'none' ,
122
+ } ) ,
123
+ // Do not include the `strict` field if it is `undefined`.
124
+ ...( fieldsCopy ?. strict !== undefined
125
+ ? { strict : fieldsCopy . strict }
126
+ : { } ) ,
95
127
} ;
96
128
}
97
129
130
+ /**
131
+ * Formats a `StructuredTool` or `RunnableToolLike` instance into a
132
+ * format that is compatible with OpenAI tool calling. It uses the
133
+ * `zodToJsonSchema` function to convert the schema of the `StructuredTool`
134
+ * or `RunnableToolLike` into a JSON schema, which is then used as the
135
+ * parameters for the OpenAI tool.
136
+ *
137
+ * @param {StructuredToolInterface | Record<string, any> | RunnableToolLike } tool The tool to convert to an OpenAI tool.
138
+ * @returns {ToolDefinition } The inputted tool in OpenAI tool format.
139
+ */
140
+ export function convertToOpenAITool (
141
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
+ tool : StructuredToolInterface | Record < string , any > | RunnableToolLike ,
143
+ fields ?:
144
+ | {
145
+ /**
146
+ * If `true`, model output is guaranteed to exactly match the JSON Schema
147
+ * provided in the function definition.
148
+ */
149
+ strict ?: boolean ;
150
+ }
151
+ | number ,
152
+ ) : ToolDefinition {
153
+ // @TODO 0.3.0 Remove the `number` typing
154
+ const fieldsCopy = typeof fields === 'number' ? undefined : fields ;
155
+
156
+ let toolDef : ToolDefinition | undefined ;
157
+ if ( isLangChainTool ( tool ) ) {
158
+ toolDef = {
159
+ type : 'function' ,
160
+ function : convertToOpenAIFunction ( tool ) ,
161
+ } ;
162
+ } else {
163
+ toolDef = tool as ToolDefinition ;
164
+ }
165
+
166
+ if ( fieldsCopy ?. strict !== undefined ) {
167
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
168
+ ( toolDef . function as any ) . strict = fieldsCopy . strict ;
169
+ }
170
+
171
+ return toolDef ;
172
+ }
173
+
98
174
/**
99
175
* Convert agent action and observation into a function message.
100
176
* @param agentAction - The tool invocation request from the agent
@@ -195,8 +271,8 @@ export class OpenAIToolsAgentOutputParser extends AgentMultiActionOutputParser {
195
271
throw new OutputParserException (
196
272
`Failed to parse tool arguments from chat model response. Text: "${ JSON . stringify (
197
273
toolCalls ,
198
- ) } ".
199
-
274
+ ) } ".
275
+
200
276
${ error }
201
277
202
278
Analyze what fields are missing based on the schema.
@@ -231,7 +307,8 @@ export async function createOpenAIToolsAgent({
231
307
tools,
232
308
prompt,
233
309
streamRunnable,
234
- } : CreateOpenAIToolsAgentParams ) {
310
+ strict,
311
+ } : CreateOpenAIToolsAgentParams & { strict ?: boolean } ) {
235
312
if ( ! prompt . inputVariables . includes ( 'agent_scratchpad' ) ) {
236
313
throw new Error (
237
314
[
@@ -240,9 +317,11 @@ export async function createOpenAIToolsAgent({
240
317
] . join ( '\n' ) ,
241
318
) ;
242
319
}
320
+
243
321
const modelWithTools = llm . bind ( {
244
- tools : tools . map ( formatToOpenAIAssistantTool ) ,
322
+ tools : tools . map ( ( tool ) => convertToOpenAITool ( tool , { strict } ) ) ,
245
323
} ) ;
324
+
246
325
const agent = AgentRunnableSequence . fromRunnables (
247
326
[
248
327
RunnablePassthrough . assign ( {
0 commit comments