Skip to content

Commit 28d8c4d

Browse files
authored
refactor: Agent
2 parents f71e233 + 050b07f commit 28d8c4d

File tree

5 files changed

+114
-100
lines changed

5 files changed

+114
-100
lines changed

examples/mcp/agent.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,18 @@ async function main() {
99
const agent = new Agent({
1010
name: 'WeatherAgent',
1111
instructions: 'Consulte informações meteorológicas usando ferramentas remotas.',
12-
mcpServers: [mcpWeatherUrl]
12+
mcp: { servers: [mcpWeatherUrl] }
1313
});
1414

15-
// Inicializa o agente e aguarda o carregamento das ferramentas MCP
16-
await agent.init();
17-
18-
// Exibe as ferramentas disponíveis
19-
console.log('Ferramentas disponíveis:', agent.tools.map(t => t.name));
20-
2115
// Input natural do usuário
2216
const input = 'Quais alertas meteorológicos existem para NY?';
2317

2418
// Executa o agente, que decide qual ferramenta usar
2519
const result = await Runner.run(agent, input);
2620
console.log('Saída final do agente:', result.finalOutput);
2721

22+
console.log('Ferramentas disponíveis:', agent.tools.map(t => t.name));
23+
2824
process.exit(0);
2925
}
3026

src/agent.js

Lines changed: 68 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
const OpenAIProvider = require('./providers/OpenAIProvider');
22

3-
const { MCPManager } = require('./mcp');
3+
const MCP = require('./mcp');
44
const Tool = require('./tool');
55

6+
const utils = require('./utils');
7+
68
class Agent {
79
/**
810
* Create a new agent
@@ -11,107 +13,88 @@ class Agent {
1113
* @param {string} config.instructions - Instructions for the agent (system prompt)
1214
* @param {Array} [config.tools=[]] - Tools that the agent can use
1315
* @param {Array} [config.handoffs=[]] - Agents that this agent can hand off to
14-
* @param {string} [config.provider='openai'] - The LLM provider to use
15-
* @param {Object} [config.providerConfig={}] - Provider-specific configuration
16-
* @param {string} [config.model='gpt-4'] - The model to use
17-
* @param {Object} [config.modelSettings={}] - Settings for the model (temperature, etc.)
16+
* @param {Object} [config.provider] - Provider and model configuration
17+
* @param {string} [config.provider.name='openai'] - The LLM provider to use
18+
* @param {Object} [config.provider.config={}] - Provider-specific configuration
19+
* @param {Object} [config.provider.model] - Model configuration
20+
* @param {string} [config.provider.model.name='gpt-4.1-nano'] - The model to use
21+
* @param {Object} [config.provider.model.settings={}] - Settings for the model (temperature, etc.)
1822
* @param {Array} [config.guardrails=[]] - Guardrails para validação de input e output
19-
* @param {Object} [config.outputType=null] - Type definition for structured output
2023
* @param {Array} [config.mcpServers=[]] - URLs dos servidores MCP
2124
*/
22-
constructor({
23-
name,
24-
instructions,
25-
tools = [],
26-
handoffs = [],
27-
provider = 'openai',
28-
providerConfig = {},
29-
model = 'gpt-4.1-nano',
30-
modelSettings = {},
31-
guardrails = { input: [], output: [] },
32-
outputType = null,
33-
handoffDescription = null
34-
}) {
25+
constructor({ name, instructions, provider = {}, mcp = {}, tools = [], handoffs = [], guardrails = {} }) {
26+
27+
this._initCalled = false;
28+
29+
const mergedProvider = utils.deepMerge({
30+
name: 'openai',
31+
config: {
32+
apiKey: ''
33+
},
34+
model: {
35+
name: 'gpt-4.1-nano',
36+
settings: { temperature: 0.7, top_p: 1 }
37+
}
38+
}, provider);
39+
const mergedGuardrails = utils.deepMerge({ input: [], output: [] }, guardrails);
40+
const mergedMcp = utils.deepMerge({ servers: [] }, mcp);
41+
3542
this.name = name;
3643
this.instructions = instructions;
3744
this.tools = tools;
3845
this.handoffs = handoffs;
39-
this.provider = provider;
40-
this.model = model;
41-
this.modelSettings = {
42-
temperature: 0.7,
43-
top_p: 1,
44-
...modelSettings
46+
this.provider = {
47+
...mergedProvider,
48+
instance: null
49+
};
50+
this.guardrails = mergedGuardrails;
51+
this.mcp = {
52+
servers: mergedMcp.servers,
53+
manager: mergedMcp.servers.length > 0 ? new MCP(mergedMcp.servers) : null
4554
};
46-
this.guardrails = guardrails;
47-
this.outputType = outputType;
48-
this.handoffDescription = handoffDescription;
49-
50-
// Inicializa MCPManager se houver MCPs
51-
this.mcpServers = Array.isArray(arguments[0]?.mcpServers) ? arguments[0].mcpServers : [];
52-
this.mcpManager = this.mcpServers.length > 0 ? new MCPManager(this.mcpServers) : null;
53-
54-
55-
// Initialize the provider
56-
this.llmProvider = this.initializeProvider(provider, {
57-
...providerConfig,
58-
model: this.model,
59-
...this.modelSettings
60-
});
6155

62-
// Se houver MCPs, inicializa ferramentas remotas automaticamente (não bloqueia construtor)
63-
if (this.mcpManager) {
64-
(async () => {
65-
await this.initializeMCPTools();
66-
})();
67-
}
6856
}
6957

70-
/**
71-
* Inicializa ferramentas MCP remotas e integra ao array this.tools
72-
*/
73-
/**
74-
* Inicializa o agente e aguarda o carregamento das ferramentas MCP (se houver MCPs).
75-
* Uso recomendado: await agent.init();
76-
* Torna transparente para o usuário a espera pelo carregamento das ferramentas remotas.
77-
*/
78-
async init({ maxTentativas = 10, intervaloMs = 500 } = {}) {
79-
if (!this.mcpManager) return; // Não há MCPs, nada a esperar
80-
let tentativas = 0;
81-
while (this.tools.length === 0 && tentativas < maxTentativas) {
82-
await new Promise(r => setTimeout(r, intervaloMs));
83-
tentativas++;
84-
}
85-
if (this.tools.length === 0) {
86-
throw new Error('Ferramentas MCP não carregadas após aguardar.');
87-
}
58+
get initCalled() {
59+
return this._initCalled;
8860
}
8961

90-
async initializeMCPTools() {
91-
if (!this.mcpManager) return;
92-
await this.mcpManager.connectAll();
93-
const mcpToolsInfo = await this.mcpManager.listAllTools();
94-
// Cria Tool para cada ferramenta MCP
95-
const mcpTools = mcpToolsInfo.map(toolInfo =>
96-
Tool.fromMCP(toolInfo, this.mcpManager.getClientForTool(toolInfo))
97-
);
98-
// Integra ferramentas MCP às ferramentas locais
99-
this.tools = [...this.tools, ...mcpTools];
62+
set initCalled(value) {
63+
this._initCalled = value;
10064
}
10165

10266
/**
103-
* Initialize the appropriate LLM provider
104-
* @param {string} provider - Provider name
105-
* @param {Object} config - Provider configuration
106-
* @returns {LLMProvider} - Initialized provider instance
67+
* Inicializa o agente, provider e ferramentas MCP (se houver).
68+
* Uso obrigatório: await agent.init();
69+
* Torna transparente para o usuário a espera pelo carregamento das ferramentas remotas.
10770
*/
108-
initializeProvider(provider, config) {
109-
// console.log('Initializing provider:', provider, config);
110-
switch (provider.toLowerCase()) {
71+
async init() {
72+
73+
switch (this.provider.name.toLowerCase()) {
11174
case 'openai':
112-
return new OpenAIProvider(config);
75+
this.provider.instance = new OpenAIProvider(this.provider.config);
76+
break;
11377
default:
114-
throw new Error(`Unsupported provider: ${provider}`);
78+
throw new Error(`Unsupported provider: ${this.provider.name}`);
79+
}
80+
81+
this.initCalled = true;
82+
83+
try {
84+
if (!this.mcp.manager) return;
85+
86+
await this.mcp.manager.connectAll();
87+
const mcpToolsInfo = await this.mcp.manager.listAllTools();
88+
89+
// Cria Tool para cada ferramenta MCP
90+
const mcpTools = mcpToolsInfo.map(toolInfo =>
91+
Tool.fromMCP(toolInfo, this.mcp.manager.getClientForTool(toolInfo))
92+
);
93+
94+
// Integra ferramentas MCP às ferramentas locais
95+
this.tools = [...this.tools, ...mcpTools];
96+
} catch (error) {
97+
console.error('Erro ao carregar ferramentas MCP:', error);
11598
}
11699
}
117100

@@ -126,11 +109,9 @@ class Agent {
126109
instructions: this.instructions,
127110
tools: this.tools,
128111
handoffs: this.handoffs,
129-
model: this.model,
130-
modelSettings: this.modelSettings,
112+
provider: this.provider,
131113
guardrails: this.guardrails,
132-
outputType: this.outputType,
133-
handoffDescription: this.handoffDescription,
114+
mcp: this.mcp,
134115
...newProps
135116
});
136117
}

src/mcp.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3-
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
1+
const { Client } = require("@modelcontextprotocol/sdk/client/index.js");
2+
const { StreamableHTTPClientTransport } = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
3+
const { SSEClientTransport } = require("@modelcontextprotocol/sdk/client/sse.js");
44

55
// Classe para gerenciar conexões e listagem de ferramentas dos MCPs
6-
export class MCPManager {
6+
class MCP {
77
constructor(servers = []) {
88
this.servers = servers; // Array de URLs dos MCPs
99
this.clients = []; // Array de clientes MCP conectados
@@ -59,3 +59,5 @@ export class MCPManager {
5959
return this.toolsMap[toolInfo.name];
6060
}
6161
}
62+
63+
module.exports = MCP;

src/runner.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ class Runner {
2020
maxTurns = 10,
2121
runConfig = {}
2222
} = {}) {
23+
if (!startingAgent.initCalled) {
24+
await startingAgent.init();
25+
return Runner.run(startingAgent, input, { context, maxTurns, runConfig });
26+
}
27+
2328
// Utilitários privados
2429
const prepareTools = agent => [
2530
...agent.tools.map(tool => ({
@@ -88,7 +93,7 @@ class Runner {
8893
let outputGuardrailResults = [];
8994

9095
// Validação
91-
currentAgent.llmProvider.validateConfig();
96+
currentAgent.provider.instance.validateConfig();
9297

9398
// Guardrails de entrada
9499
inputGuardrailResults = await runGuardrails('input', currentAgent, messages, context);
@@ -105,7 +110,7 @@ class Runner {
105110

106111
// Chamada ao modelo
107112
const tools = prepareTools(currentAgent);
108-
const response = await currentAgent.llmProvider.complete(
113+
const response = await currentAgent.provider.instance.complete(
109114
messages,
110115
{
111116
tools: tools.length > 0 ? tools : undefined,

src/utils.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
/**
3+
* Faz um merge profundo entre dois objetos.
4+
* Valores do source sobrescrevem os do target apenas onde definidos.
5+
* Não modifica os objetos originais.
6+
* @param {Object} target
7+
* @param {Object} source
8+
* @returns {Object} Novo objeto mesclado
9+
*/
10+
function deepMerge(target, source) {
11+
const result = { ...target };
12+
for (const key in source) {
13+
if (
14+
source[key] &&
15+
typeof source[key] === 'object' &&
16+
!Array.isArray(source[key])
17+
) {
18+
result[key] = deepMerge(target[key] ? { ...target[key] } : {}, source[key]);
19+
} else {
20+
result[key] = source[key];
21+
}
22+
}
23+
return result;
24+
}
25+
26+
27+
28+
module.exports = {
29+
deepMerge
30+
};

0 commit comments

Comments
 (0)