agentnet 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +317 -364
- package/_OLD_README.md +554 -0
- package/assets/network01.png +0 -0
- package/examples/customer-support/README.md +66 -0
- package/examples/customer-support/agents.yaml +457 -0
- package/examples/customer-support/index.js +408 -0
- package/examples/event-planner/README.md +69 -0
- package/examples/event-planner/agents.yaml +318 -0
- package/examples/event-planner/index.js +547 -0
- package/{src/examples → examples/simple}/simple.js +2 -2
- package/{src/examples → examples/smartness}/agents-smartness.yaml +8 -17
- package/{src/examples/def3.js → examples/smartness/index.js} +9 -9
- package/jest.config.js +1 -0
- package/package.json +6 -3
- package/src/agent/agent-loader.js +75 -12
- package/src/agent/agent.js +13 -3
- package/src/agent/runtime.js +9 -6
- package/src/llm/base.js +131 -0
- package/src/llm/gemini.js +137 -117
- package/src/llm/gpt.js +131 -104
- package/src/store/store.js +82 -48
- package/src/tests/agent.test.js +350 -0
- package/src/tools/migrate-version.js +250 -0
- package/src/transport/README.md +123 -0
- package/src/transport/base.js +237 -0
- package/src/transport/index.js +89 -0
- package/src/transport/kafka.js +474 -0
- package/src/transport/nats.js +521 -0
- package/src/transport/rabbitmq.js +722 -0
- package/src/transport/redis.js +532 -0
- package/src/utils/version.js +212 -0
- package/src/agent/runtimes/nats.js +0 -379
- package/src/examples/agents.yaml +0 -394
- package/src/examples/def.js +0 -74
- package/src/examples/def2.js +0 -65
package/src/llm/gemini.js
CHANGED
|
@@ -1,155 +1,175 @@
|
|
|
1
1
|
import { GoogleGenAI } from '@google/genai'
|
|
2
2
|
import { logger } from '../utils/logger.js'
|
|
3
3
|
import { LLMError } from '../errors/index.js'
|
|
4
|
+
import { BaseLLM } from './base.js'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Gemini LLM implementation
|
|
8
|
+
*/
|
|
9
|
+
class GeminiLLM extends BaseLLM {
|
|
10
|
+
constructor() {
|
|
11
|
+
super('gemini');
|
|
12
|
+
}
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Initializes and returns a Gemini client
|
|
16
|
+
* @returns {Promise<GoogleGenAI>} The initialized Gemini client
|
|
17
|
+
* @throws {LLMError} If initialization fails
|
|
18
|
+
*/
|
|
19
|
+
async getClient() {
|
|
20
|
+
this.checkApiKey('GEMINI_API_KEY');
|
|
21
|
+
|
|
8
22
|
try {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
'GEMINI_API_KEY environment variable is not set',
|
|
12
|
-
'gemini'
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
logger.debug('Initializing Gemini client');
|
|
17
|
-
return new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
|
23
|
+
logger.debug('Initializing Gemini client');
|
|
24
|
+
return new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
|
18
25
|
} catch (error) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
logger.error('Failed to initialize Gemini client', { error });
|
|
27
|
+
throw new LLMError(
|
|
28
|
+
`Failed to initialize Gemini client: ${error.message}`,
|
|
29
|
+
this.type,
|
|
30
|
+
{ originalError: error }
|
|
31
|
+
);
|
|
25
32
|
}
|
|
26
|
-
}
|
|
33
|
+
}
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Calls the Gemini model with the provided configuration and context
|
|
37
|
+
* @param {Object} llmClientConfig - Configuration for the Gemini model
|
|
38
|
+
* @param {Object} context - Context containing client, tools map and conversation
|
|
39
|
+
* @returns {Promise<Object>} The model response
|
|
40
|
+
* @throws {LLMError} If the API call fails
|
|
41
|
+
*/
|
|
42
|
+
async callModel(llmClientConfig, context) {
|
|
43
|
+
const { client, toolsAndHandoffsMap, conversation } = context;
|
|
44
|
+
const input = { ...llmClientConfig, contents: conversation };
|
|
36
45
|
|
|
46
|
+
// Configure tools if provided
|
|
37
47
|
if (input.config !== undefined && input.tools !== undefined) {
|
|
38
|
-
|
|
48
|
+
input.config.tools = toolsAndHandoffsMap.tools;
|
|
39
49
|
} else if (toolsAndHandoffsMap.tools.length > 0) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
input.config.tools = [{ functionDeclarations: toolsAndHandoffsMap.tools }];
|
|
50
|
+
input.config = input.config || {};
|
|
51
|
+
input.config.tools = [{ functionDeclarations: toolsAndHandoffsMap.tools }];
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
logger.debug('Calling Gemini model', {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
model: input.model,
|
|
56
|
+
conversationLength: conversation.length,
|
|
57
|
+
toolsCount: toolsAndHandoffsMap.tools.length
|
|
50
58
|
});
|
|
51
59
|
|
|
52
60
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
const res = await client.models.generateContent(input);
|
|
62
|
+
logger.debug('Gemini response received', {
|
|
63
|
+
responseType: res.response?.candidates ? 'candidates' : 'unknown',
|
|
64
|
+
hasContent: !!res.response?.candidates?.[0]?.content
|
|
65
|
+
});
|
|
66
|
+
return res;
|
|
59
67
|
} catch (error) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
logger.error('Gemini API error', {
|
|
69
|
+
error,
|
|
70
|
+
modelName: input.model
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
throw new LLMError(
|
|
74
|
+
`Gemini API error: ${error.message}`,
|
|
75
|
+
this.type,
|
|
76
|
+
{
|
|
77
|
+
statusCode: error.status || error.statusCode,
|
|
78
|
+
modelName: input.model
|
|
79
|
+
}
|
|
80
|
+
);
|
|
73
81
|
}
|
|
74
|
-
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Handle a specific tool call from Gemini response
|
|
86
|
+
* @param {Object} toolCall - The tool call to process
|
|
87
|
+
* @param {Object} state - Current application state
|
|
88
|
+
* @param {Array} conversation - The conversation history
|
|
89
|
+
* @param {Object} toolsAndHandoffsMap - Map of available tools
|
|
90
|
+
*/
|
|
91
|
+
async handleToolCall(toolCall, state, conversation, toolsAndHandoffsMap) {
|
|
92
|
+
const args = toolCall.args;
|
|
93
|
+
const name = toolCall.name;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const result = await super.executeToolCall(toolCall, name, args, state, conversation, toolsAndHandoffsMap);
|
|
97
|
+
|
|
98
|
+
// Add function call and response to conversation in Gemini-specific format
|
|
99
|
+
const function_response_part = {
|
|
100
|
+
name: name,
|
|
101
|
+
response: typeof result === 'string' ? { answer: result } : result
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
conversation.push({ role: 'model', parts: [{ functionCall: toolCall }] });
|
|
105
|
+
conversation.push({ role: 'user', parts: [{ functionResponse: function_response_part }] });
|
|
106
|
+
|
|
107
|
+
} catch (error) {
|
|
108
|
+
// Return error as function response in Gemini-specific format
|
|
109
|
+
const errorResponse = {
|
|
110
|
+
name: name,
|
|
111
|
+
response: { error: error.message }
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
conversation.push({ role: 'model', parts: [{ functionCall: toolCall }] });
|
|
115
|
+
conversation.push({ role: 'user', parts: [{ functionResponse: errorResponse }] });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
75
118
|
|
|
76
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Processes the model response, handling text responses and function calls
|
|
121
|
+
* @param {Object} state - Current application state
|
|
122
|
+
* @param {Array} conversation - The conversation history
|
|
123
|
+
* @param {Object} toolsAndHandoffsMap - Map of available tools
|
|
124
|
+
* @param {Object} response - The model response to process
|
|
125
|
+
* @returns {Promise<string|null>} Text response or null if processing tool calls
|
|
126
|
+
*/
|
|
127
|
+
async onResponse(state, conversation, toolsAndHandoffsMap, response) {
|
|
128
|
+
// Handle simple text response
|
|
77
129
|
if (response.text !== undefined) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
130
|
+
logger.debug('Gemini response contains text, returning directly');
|
|
131
|
+
conversation.push({ role: 'model', parts: [{ text: response.text }] });
|
|
132
|
+
return response.text;
|
|
81
133
|
}
|
|
82
134
|
|
|
135
|
+
// Handle function calls
|
|
83
136
|
logger.debug('Gemini response contains function calls', {
|
|
84
|
-
|
|
137
|
+
functionCallCount: response.functionCalls?.length || 0
|
|
85
138
|
});
|
|
86
139
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
toolName: name,
|
|
93
|
-
argsPreview: JSON.stringify(args).substring(0, 100)
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
if (!toolsAndHandoffsMap[name] || !toolsAndHandoffsMap[name].function) {
|
|
98
|
-
throw new Error(`Tool "${name}" not found or has no function implementation`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
let result = await toolsAndHandoffsMap[name].function(conversation, state, args);
|
|
102
|
-
if (toolsAndHandoffsMap[name].type === 'handoff') {
|
|
103
|
-
const resultParsed = JSON.parse(result)
|
|
104
|
-
// Update state with the result
|
|
105
|
-
if (resultParsed.session) {
|
|
106
|
-
for (const key of Object.keys(resultParsed.session)) {
|
|
107
|
-
state[key] = resultParsed.session[key]
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const function_response_part = {
|
|
113
|
-
name: name,
|
|
114
|
-
response: typeof result === 'string' ? { answer: result } : result
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// Append function call and result of the function execution to contents
|
|
118
|
-
conversation.push({ role: 'model', parts: [{ functionCall: toolCall }] });
|
|
119
|
-
conversation.push({ role: 'user', parts: [{ functionResponse: function_response_part }] });
|
|
120
|
-
|
|
121
|
-
logger.debug('Tool execution successful', { toolName: name });
|
|
122
|
-
} catch (error) {
|
|
123
|
-
logger.error(`Error executing tool "${name}"`, { error });
|
|
124
|
-
// Return error as function response
|
|
125
|
-
const errorResponse = {
|
|
126
|
-
name: name,
|
|
127
|
-
response: { error: error.message }
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
conversation.push({ role: 'model', parts: [{ functionCall: toolCall }] });
|
|
131
|
-
conversation.push({ role: 'user', parts: [{ functionResponse: errorResponse }] });
|
|
132
|
-
}
|
|
140
|
+
// Process all tool calls sequentially
|
|
141
|
+
if (response.functionCalls?.length) {
|
|
142
|
+
for (const toolCall of response.functionCalls) {
|
|
143
|
+
await this.handleToolCall(toolCall, state, conversation, toolsAndHandoffsMap);
|
|
144
|
+
}
|
|
133
145
|
}
|
|
134
146
|
|
|
135
147
|
return null;
|
|
136
|
-
}
|
|
148
|
+
}
|
|
137
149
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Adds a user prompt to the conversation
|
|
152
|
+
* @param {Array} conversation - The conversation history
|
|
153
|
+
* @param {string} formattedPrompt - The formatted user prompt
|
|
154
|
+
* @returns {Promise<void>}
|
|
155
|
+
*/
|
|
156
|
+
async prompt(conversation, formattedPrompt) {
|
|
157
|
+
await super.prompt(conversation, formattedPrompt);
|
|
142
158
|
|
|
143
159
|
conversation.push({
|
|
144
|
-
|
|
145
|
-
|
|
160
|
+
role: 'user',
|
|
161
|
+
parts: [{ text: formattedPrompt }]
|
|
146
162
|
});
|
|
163
|
+
}
|
|
147
164
|
}
|
|
148
165
|
|
|
166
|
+
// Create a singleton instance
|
|
167
|
+
const geminiLLM = new GeminiLLM();
|
|
168
|
+
|
|
149
169
|
export default {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
170
|
+
type: geminiLLM.type,
|
|
171
|
+
getClient: geminiLLM.getClient.bind(geminiLLM),
|
|
172
|
+
prompt: geminiLLM.prompt.bind(geminiLLM),
|
|
173
|
+
callModel: geminiLLM.callModel.bind(geminiLLM),
|
|
174
|
+
onResponse: geminiLLM.onResponse.bind(geminiLLM)
|
|
155
175
|
}
|
package/src/llm/gpt.js
CHANGED
|
@@ -1,89 +1,88 @@
|
|
|
1
1
|
import OpenAI from 'openai'
|
|
2
2
|
import { logger } from '../utils/logger.js'
|
|
3
3
|
import { LLMError } from '../errors/index.js'
|
|
4
|
+
import { BaseLLM } from './base.js'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
/**
|
|
7
|
+
* OpenAI LLM implementation
|
|
8
|
+
*/
|
|
9
|
+
class OpenAILLM extends BaseLLM {
|
|
10
|
+
constructor() {
|
|
11
|
+
super('openai');
|
|
12
|
+
}
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Initializes and returns an OpenAI client
|
|
16
|
+
* @returns {Promise<OpenAI>} The initialized OpenAI client
|
|
17
|
+
* @throws {LLMError} If initialization fails
|
|
18
|
+
*/
|
|
19
|
+
async getClient() {
|
|
20
|
+
this.checkApiKey('OPENAI_API_KEY');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
logger.debug('Initializing OpenAI client');
|
|
24
|
+
return new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
25
|
+
} catch (error) {
|
|
26
|
+
logger.error('Failed to initialize OpenAI client', { error });
|
|
10
27
|
throw new LLMError(
|
|
11
|
-
|
|
12
|
-
|
|
28
|
+
`Failed to initialize OpenAI client: ${error.message}`,
|
|
29
|
+
this.type,
|
|
30
|
+
{ originalError: error }
|
|
13
31
|
);
|
|
14
32
|
}
|
|
15
|
-
|
|
16
|
-
logger.debug('Initializing OpenAI client');
|
|
17
|
-
return new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
18
|
-
} catch (error) {
|
|
19
|
-
logger.error('Failed to initialize OpenAI client', { error });
|
|
20
|
-
throw new LLMError(
|
|
21
|
-
`Failed to initialize OpenAI client: ${error.message}`,
|
|
22
|
-
'openai',
|
|
23
|
-
{ originalError: error }
|
|
24
|
-
);
|
|
25
33
|
}
|
|
26
|
-
}
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
logger.debug('OpenAI response received');
|
|
46
|
-
return response
|
|
47
|
-
} catch (error) {
|
|
48
|
-
logger.error('OpenAI API error', {
|
|
49
|
-
error,
|
|
50
|
-
modelName: input.model
|
|
35
|
+
/**
|
|
36
|
+
* Calls the OpenAI model with the provided configuration and context
|
|
37
|
+
* @param {Object} llmClientConfig - Configuration for the OpenAI model
|
|
38
|
+
* @param {Object} context - Context containing client, tools map and conversation
|
|
39
|
+
* @returns {Promise<Object>} The model response
|
|
40
|
+
* @throws {LLMError} If the API call fails
|
|
41
|
+
*/
|
|
42
|
+
async callModel(llmClientConfig, context) {
|
|
43
|
+
const { client, toolsAndHandoffsMap, conversation } = context;
|
|
44
|
+
const input = { ...llmClientConfig };
|
|
45
|
+
input.tools = toolsAndHandoffsMap.tools;
|
|
46
|
+
input.input = conversation;
|
|
47
|
+
|
|
48
|
+
logger.debug('Calling OpenAI model', {
|
|
49
|
+
model: input.model,
|
|
50
|
+
conversationLength: conversation.length,
|
|
51
|
+
toolsCount: toolsAndHandoffsMap.tools.length
|
|
51
52
|
});
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
'
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
try {
|
|
55
|
+
const response = await client.responses.create(input);
|
|
56
|
+
logger.debug('OpenAI response received');
|
|
57
|
+
return response;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
logger.error('OpenAI API error', {
|
|
60
|
+
error,
|
|
58
61
|
modelName: input.model
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const reasoning = response.output.filter(x => x.type == 'reasoning')
|
|
72
|
-
const functionCalls = response.output.filter(x => x.type == 'function_call')
|
|
73
|
-
|
|
74
|
-
logger.debug('OpenAI response processing', {
|
|
75
|
-
reasoningCount: reasoning.length,
|
|
76
|
-
functionCallCount: functionCalls.length
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
for (const res of reasoning) {
|
|
80
|
-
conversation.push(res)
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
throw new LLMError(
|
|
65
|
+
`OpenAI API error: ${error.message}`,
|
|
66
|
+
this.type,
|
|
67
|
+
{
|
|
68
|
+
statusCode: error.status || error.statusCode,
|
|
69
|
+
modelName: input.model
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
81
73
|
}
|
|
82
74
|
|
|
83
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Handle a specific tool call from OpenAI response
|
|
77
|
+
* @param {Object} toolCall - The tool call to process
|
|
78
|
+
* @param {Object} state - Current application state
|
|
79
|
+
* @param {Array} conversation - The conversation history
|
|
80
|
+
* @param {Object} toolsAndHandoffsMap - Map of available tools
|
|
81
|
+
*/
|
|
82
|
+
async handleToolCall(toolCall, state, conversation, toolsAndHandoffsMap) {
|
|
84
83
|
try {
|
|
85
|
-
const args = JSON.parse(toolCall.arguments)
|
|
86
|
-
const name = toolCall.name
|
|
84
|
+
const args = JSON.parse(toolCall.arguments);
|
|
85
|
+
const name = toolCall.name;
|
|
87
86
|
|
|
88
87
|
logger.debug('Executing tool from OpenAI', {
|
|
89
88
|
toolName: name,
|
|
@@ -91,24 +90,10 @@ const onResponse = async function (state, conversation, toolsAndHandoffsMap, res
|
|
|
91
90
|
callId: toolCall.call_id
|
|
92
91
|
});
|
|
93
92
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
let result = await toolsAndHandoffsMap[name].function(conversation, state, args)
|
|
99
|
-
conversation.push(toolCall)
|
|
100
|
-
if (toolsAndHandoffsMap[name].type === 'handoff') {
|
|
101
|
-
console.log("GPT HANDOFF onResponse", name, result)
|
|
102
|
-
const resultParsed = JSON.parse(result)
|
|
103
|
-
// Update state with the result
|
|
104
|
-
if (resultParsed.session) {
|
|
105
|
-
for (const key of Object.keys(resultParsed.session)) {
|
|
106
|
-
state[key] = resultParsed.session[key]
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
93
|
+
const result = await super.executeToolCall(toolCall, name, args, state, conversation, toolsAndHandoffsMap);
|
|
94
|
+
conversation.push(toolCall);
|
|
110
95
|
|
|
111
|
-
const resultString = typeof result
|
|
96
|
+
const resultString = typeof result === 'string' ? result : JSON.stringify(result);
|
|
112
97
|
|
|
113
98
|
logger.debug('Tool execution successful', {
|
|
114
99
|
toolName: name,
|
|
@@ -119,7 +104,7 @@ const onResponse = async function (state, conversation, toolsAndHandoffsMap, res
|
|
|
119
104
|
type: "function_call_output",
|
|
120
105
|
call_id: toolCall.call_id,
|
|
121
106
|
output: resultString
|
|
122
|
-
})
|
|
107
|
+
});
|
|
123
108
|
} catch (error) {
|
|
124
109
|
logger.error(`Error executing tool "${toolCall.name}"`, { error });
|
|
125
110
|
|
|
@@ -132,24 +117,66 @@ const onResponse = async function (state, conversation, toolsAndHandoffsMap, res
|
|
|
132
117
|
});
|
|
133
118
|
}
|
|
134
119
|
}
|
|
135
|
-
return null
|
|
136
|
-
}
|
|
137
120
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Processes the model response, handling text responses and function calls
|
|
123
|
+
* @param {Object} state - Current application state
|
|
124
|
+
* @param {Array} conversation - The conversation history
|
|
125
|
+
* @param {Object} toolsAndHandoffsMap - Map of available tools
|
|
126
|
+
* @param {Object} response - The model response to process
|
|
127
|
+
* @returns {Promise<string|null>} Text response or null if processing tool calls
|
|
128
|
+
*/
|
|
129
|
+
async onResponse(state, conversation, toolsAndHandoffsMap, response) {
|
|
130
|
+
if (response.output_text !== undefined && response.output_text.length > 0) {
|
|
131
|
+
logger.debug('OpenAI response contains text, returning directly');
|
|
132
|
+
conversation.push({ role: 'model', parts: [{ text: response.output_text }] });
|
|
133
|
+
return response.output_text;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const reasoning = response.output.filter(x => x.type === 'reasoning');
|
|
137
|
+
const functionCalls = response.output.filter(x => x.type === 'function_call');
|
|
138
|
+
|
|
139
|
+
logger.debug('OpenAI response processing', {
|
|
140
|
+
reasoningCount: reasoning.length,
|
|
141
|
+
functionCallCount: functionCalls.length
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Add reasoning to conversation
|
|
145
|
+
for (const res of reasoning) {
|
|
146
|
+
conversation.push(res);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Process all tool calls sequentially
|
|
150
|
+
for (const toolCall of functionCalls) {
|
|
151
|
+
await this.handleToolCall(toolCall, state, conversation, toolsAndHandoffsMap);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Adds a user prompt to the conversation
|
|
159
|
+
* @param {Array} conversation - The conversation history
|
|
160
|
+
* @param {string} formattedPrompt - The formatted user prompt
|
|
161
|
+
* @returns {Promise<void>}
|
|
162
|
+
*/
|
|
163
|
+
async prompt(conversation, formattedPrompt) {
|
|
164
|
+
await super.prompt(conversation, formattedPrompt);
|
|
165
|
+
|
|
166
|
+
conversation.push({
|
|
167
|
+
role: 'user',
|
|
168
|
+
content: formattedPrompt
|
|
169
|
+
});
|
|
170
|
+
}
|
|
147
171
|
}
|
|
148
172
|
|
|
173
|
+
// Create a singleton instance
|
|
174
|
+
const openaiLLM = new OpenAILLM();
|
|
175
|
+
|
|
149
176
|
export default {
|
|
150
|
-
type,
|
|
151
|
-
getClient,
|
|
152
|
-
prompt,
|
|
153
|
-
callModel,
|
|
154
|
-
onResponse
|
|
177
|
+
type: openaiLLM.type,
|
|
178
|
+
getClient: openaiLLM.getClient.bind(openaiLLM),
|
|
179
|
+
prompt: openaiLLM.prompt.bind(openaiLLM),
|
|
180
|
+
callModel: openaiLLM.callModel.bind(openaiLLM),
|
|
181
|
+
onResponse: openaiLLM.onResponse.bind(openaiLLM)
|
|
155
182
|
}
|