Skip to content

Commit 6cacafd

Browse files
authored
Merge pull request #3 from riligar/2025-04-20_runner
refactor: runner.js with private utils and improved code organization
2 parents cd34987 + 2e0613a commit 6cacafd

File tree

1 file changed

+91
-138
lines changed

1 file changed

+91
-138
lines changed

src/runner.js

Lines changed: 91 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,6 @@ const { RunResult } = require('./result');
55
*/
66
class Runner {
77

8-
/**
9-
* Run input guardrails
10-
* @private
11-
*/
12-
13-
14-
/**
15-
* Run output guardrails
16-
* @private
17-
*/
18-
19-
208
/**
219
* Run an agent with the given input
2210
* @param {Agent} startingAgent - The agent to run
@@ -32,66 +20,91 @@ class Runner {
3220
maxTurns = 10,
3321
runConfig = {}
3422
} = {}) {
35-
// Initialize state
23+
// Utilitários privados
24+
const prepareTools = agent => [
25+
...agent.tools.map(tool => ({
26+
type: 'function',
27+
function: {
28+
name: tool.name,
29+
description: tool.description,
30+
parameters: tool.parameters || { type: 'object', properties: {} }
31+
}
32+
})),
33+
...agent.handoffs.map(handoff => ({
34+
type: 'function',
35+
function: {
36+
name: `transfer_to_${handoff.name.replace(/[^a-zA-Z0-9_-]/g, '_')}`,
37+
description: handoff.handoffDescription || `Transfer to the ${handoff.name} agent`,
38+
parameters: { type: 'object', properties: {} }
39+
}
40+
}))
41+
];
42+
43+
const runGuardrails = async (type, agent, data, context) => {
44+
const results = [];
45+
for (const guardrail of agent?.guardrails?.[type] || []) {
46+
const result = await guardrail.run(agent, data, context);
47+
if (result) results.push(guardrail.name);
48+
}
49+
return results;
50+
};
51+
52+
const processToolCalls = async (agent, assistantMessage, messages, context) => {
53+
for (const toolCall of assistantMessage.tool_calls) {
54+
// O id pode estar em toolCall.id ou toolCall.function.id dependendo do formato
55+
const id = toolCall.id || (toolCall.function && toolCall.function.id);
56+
const { name, arguments: args } = toolCall.function;
57+
if (name.startsWith('transfer_to_')) {
58+
const handoffAgentName = name.replace('transfer_to_', '');
59+
const handoffAgent = agent.handoffs.find(h => h.name.replace(/[^a-zA-Z0-9_-]/g, '_') === handoffAgentName);
60+
if (handoffAgent) {
61+
messages.push({ role: 'tool', tool_call_id: id, content: `Handoff to ${handoffAgent.name} successful` });
62+
return { handoffAgent, messages };
63+
}
64+
} else {
65+
const tool = agent.tools.find(t => t.name === name);
66+
if (tool) {
67+
try {
68+
const result = await tool.execute(JSON.parse(args));
69+
messages.push({ role: 'tool', tool_call_id: id, content: typeof result === 'string' ? result : JSON.stringify(result) });
70+
} catch (error) {
71+
messages.push({ role: 'tool', tool_call_id: id, content: `Error: ${error.message}` });
72+
}
73+
} else {
74+
// Caso não encontre a ferramenta, ainda assim envie uma mensagem de erro com tool_call_id
75+
messages.push({ role: 'tool', tool_call_id: id, content: `Error: tool '${name}' not found` });
76+
}
77+
}
78+
}
79+
return { handoffAgent: null, messages };
80+
};
81+
82+
// Estado inicial
3683
let currentTurn = 0;
3784
let currentAgent = startingAgent;
38-
let messages = [];
85+
let messages = typeof input === 'string' ? [{ role: 'user', content: input }] : [...input];
3986
let finalOutput = null;
40-
const inputGuardrailResults = [];
41-
const outputGuardrailResults = [];
87+
let inputGuardrailResults = [];
88+
let outputGuardrailResults = [];
4289

43-
// Validate provider configuration
90+
// Validação
4491
currentAgent.llmProvider.validateConfig();
4592

46-
// Add the initial user message
47-
if (typeof input === 'string') {
48-
messages.push({ role: 'user', content: input });
49-
} else {
50-
messages = [...input];
51-
}
93+
// Guardrails de entrada
94+
inputGuardrailResults = await runGuardrails('input', currentAgent, messages, context);
5295

53-
// Run input guardrails
54-
for (const guardrail of currentAgent?.guardrails?.input || []) {
55-
const result = await guardrail.run(currentAgent, messages, context);
56-
if (result) {
57-
inputGuardrailResults.push(guardrail.name);
58-
}
59-
}
60-
61-
// Main agent loop
96+
// Loop principal
6297
while (currentTurn < maxTurns) {
6398
currentTurn++;
6499
console.log(`Running agent ${currentAgent.name} (turn ${currentTurn})`);
65100

66-
// Prepare tools for the model
67-
const tools = [
68-
...currentAgent.tools.map(tool => ({
69-
type: 'function',
70-
function: {
71-
name: tool.name,
72-
description: tool.description,
73-
parameters: tool.parameters || { type: 'object', properties: {} }
74-
}
75-
})),
76-
...currentAgent.handoffs.map(handoff => ({
77-
type: 'function',
78-
function: {
79-
name: `transfer_to_${handoff.name.replace(/[^a-zA-Z0-9_-]/g, '_')}`,
80-
description: handoff.handoffDescription || `Transfer to the ${handoff.name} agent`,
81-
parameters: { type: 'object', properties: {} }
82-
}
83-
}))
84-
];
85-
86-
// Add system message if not already present
101+
// System message
87102
if (!messages.some(msg => msg.role === 'system')) {
88-
messages.unshift({
89-
role: 'system',
90-
content: currentAgent.instructions
91-
});
103+
messages.unshift({ role: 'system', content: currentAgent.instructions });
92104
}
93105

94-
// Call the model using the provider
106+
// Chamada ao modelo
107+
const tools = prepareTools(currentAgent);
95108
const response = await currentAgent.llmProvider.complete(
96109
messages,
97110
{
@@ -100,98 +113,40 @@ class Runner {
100113
...currentAgent.modelSettings
101114
}
102115
);
103-
104116
const assistantMessage = response;
105117
messages.push(assistantMessage);
106118

107-
// Check for tool calls
119+
// Tool calls
108120
if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
109-
110-
// console.log("Tool calls detected:", assistantMessage.tool_calls);
111-
112-
for (const toolCall of assistantMessage.tool_calls) {
113-
const { name, arguments: args } = toolCall.function;
114-
115-
// Check if it's a handoff
116-
if (name.startsWith('transfer_to_')) {
117-
const handoffAgentName = name.replace('transfer_to_', '');
118-
const handoffAgent = currentAgent.handoffs.find(h =>
119-
h.name.replace(/[^a-zA-Z0-9_-]/g, '_') === handoffAgentName);
120-
121-
if (handoffAgent) {
122-
// Add a tool response message for the handoff
123-
messages.push({
124-
role: 'tool',
125-
tool_call_id: toolCall.id,
126-
content: `Handoff to ${handoffAgent.name} successful`
127-
});
128-
129-
// Perform handoff
130-
currentAgent = handoffAgent;
131-
132-
// Reset messages for the new agent, keeping only the user's input
133-
const userMessages = messages.filter(msg => msg.role === 'user');
134-
messages = [
135-
{ role: 'system', content: handoffAgent.instructions },
136-
...userMessages
137-
];
138-
139-
// Get immediate response from new agent
140-
const handoffResponse = await handoffAgent.llmProvider.complete(
141-
messages,
142-
handoffAgent.modelSettings
143-
);
144-
145-
// Set final output and add response to messages
146-
finalOutput = handoffResponse.content;
147-
messages.push(handoffResponse);
148-
149-
// Exit the main loop since we have a final response
150-
currentTurn = maxTurns;
151-
break;
152-
}
153-
} else {
154-
// Regular tool call
155-
const tool = currentAgent.tools.find(t => t.name === name);
156-
if (tool) {
157-
try {
158-
const result = await tool.execute(JSON.parse(args));
159-
messages.push({
160-
role: 'tool',
161-
tool_call_id: toolCall.id,
162-
content: typeof result === 'string' ? result : JSON.stringify(result)
163-
});
164-
} catch (error) {
165-
messages.push({
166-
role: 'tool',
167-
tool_call_id: toolCall.id,
168-
content: `Error: ${error.message}`
169-
});
170-
}
171-
}
172-
}
121+
const { handoffAgent, messages: updatedMessages } = await processToolCalls(currentAgent, assistantMessage, messages, context);
122+
messages = updatedMessages;
123+
if (handoffAgent) {
124+
currentAgent = handoffAgent;
125+
const userMessages = messages.filter(msg => msg.role === 'user');
126+
messages = [
127+
{ role: 'system', content: handoffAgent.instructions },
128+
...userMessages
129+
];
130+
const handoffResponse = await handoffAgent.llmProvider.complete(
131+
messages,
132+
handoffAgent.modelSettings
133+
);
134+
finalOutput = handoffResponse.content;
135+
messages.push(handoffResponse);
136+
break;
173137
}
174138
} else {
175-
// No tool calls, this is a final output
176139
finalOutput = assistantMessage.content;
177-
178-
// If we have a final output, we're done
179140
break;
180141
}
181142
}
182143

183-
// Check if we exceeded max turns
184144
if (currentTurn >= maxTurns && !finalOutput) {
185145
throw new Error(`Max turns (${maxTurns}) exceeded`);
186146
}
187147

188-
// Run output guardrails
189-
for (const guardrail of currentAgent?.guardrails?.output || []) {
190-
const result = await guardrail.run(currentAgent, finalOutput, context);
191-
if (result) {
192-
outputGuardrailResults.push(guardrail.name);
193-
}
194-
}
148+
// Guardrails de saída
149+
outputGuardrailResults = await runGuardrails('output', currentAgent, finalOutput, context);
195150

196151
const result = new RunResult({
197152
input,
@@ -203,13 +158,11 @@ class Runner {
203158
output: outputGuardrailResults
204159
}
205160
});
206-
// setImmediate(() => process.exit(0));
207161
return result;
208-
209162
}
210163

211164
/**
212-
* Run an agent synchronously (for environments that don't support async/await)
165+
* Run an agent sincronamente (para ambientes sem suporte a async/await)
213166
*/
214167
static runSync(startingAgent, input, options = {}) {
215168
return Runner.run(startingAgent, input, options);

0 commit comments

Comments
 (0)