examples/gmi-completion-gateway.mjs
Rama master · réplica de lectura del repositorio original.
#!/usr/bin/env node
// Example: a GMI whose model calls run through a completion gateway, and the
// step, tool and usage chunks of one turn.
//
// The persona's model is the primary hop. The gateway defaults give the
// primary a base URL with no server behind it, so its attempt fails before
// any output with a network error. That error is retryable: the gateway
// resolves the next hop of the fallback chain, and the GMI rebuilds the prompt
// for that hop's model. When the model calls get_weather, the fallback hop
// serves two model steps with a tool round between them; a turn stays on the
// hop that served its last step.
//
// What this example prints:
// 1. USAGE_UPDATE for every provider chunk that carries usage
// 2. STEP_FINISHED for each model step that completes: its index, the hop
// that served it (0 is the primary), the model and the finish reason
// 3. TOOL_RESULT for each tool result the GMI records
// 4. the turn's usage total, from the GMIOutput the turn returns
// 5. the reasoning trace's WARNING entries for the failed hop and the fallback
//
// It exits with status 1 when the turn fails.
//
// Usage (the example imports the built package from ../dist):
// pnpm run build
// export OPENAI_API_KEY="sk-..."
// node examples/gmi-completion-gateway.mjs
//
// AGENTOS_FALLBACK_PROVIDER and AGENTOS_FALLBACK_MODEL set another fallback
// hop (set both); its key must be in the environment.
import {
createCompletionGateway,
GatewayProviderManager,
GMIInteractionType,
GMIOutputChunkType,
ReasoningEntryType,
} from '../dist/index.js';
import { GMI } from '../dist/cognition/substrate/index.js';
import { InMemoryWorkingMemory } from '../dist/cognition/substrate/memory/InMemoryWorkingMemory.js';
import { PromptEngine } from '../dist/core/llm/PromptEngine.js';
const fallbackProvider = process.env.AGENTOS_FALLBACK_PROVIDER || 'openai';
const fallbackModel = process.env.AGENTOS_FALLBACK_MODEL || 'gpt-4o-mini';
// The persona names the primary hop. Its completion options reach every
// model call of the turn.
const persona = {
id: 'weather_assistant',
name: 'Weather Assistant',
version: '1.0.0',
baseSystemPrompt: 'You answer weather questions. Call get_weather for current conditions.',
defaultProviderId: 'anthropic',
defaultModelId: 'claude-haiku-4-5-20251001',
defaultModelCompletionOptions: { temperature: 0.2, maxTokens: 512 },
metaPrompts: [],
};
const toolOrchestrator = {
async listAvailableTools() {
return [
{
name: 'get_weather',
description: 'Current weather for a city.',
inputSchema: {
type: 'object',
properties: { city: { type: 'string' } },
required: ['city'],
},
},
];
},
async processToolCall({ toolCallRequest }) {
return {
toolCallId: toolCallRequest.id,
toolName: toolCallRequest.name,
output: { city: toolCallRequest.arguments.city, conditions: 'light rain', temperatureC: 14 },
};
},
};
async function main() {
const promptEngine = new PromptEngine();
await promptEngine.initialize({
defaultTemplateName: 'openai_chat',
availableTemplates: {},
tokenCounting: { strategy: 'estimated' },
historyManagement: {
defaultMaxMessages: 20,
maxTokensForHistory: 4000,
summarizationTriggerRatio: 0.8,
preserveImportantMessages: true,
},
contextManagement: {
maxRAGContextTokens: 1500,
summarizationQualityTier: 'balanced',
preserveSourceAttributionInSummary: true,
},
contextualElementSelection: {
maxElementsPerType: {},
defaultMaxElementsPerType: 3,
priorityResolutionStrategy: 'highest_first',
conflictResolutionStrategy: 'skip_conflicting',
},
performance: { enableCaching: false, cacheTimeoutSeconds: 60 },
});
const gmi = new GMI('gateway-example');
await gmi.initialize(persona, {
workingMemory: new InMemoryWorkingMemory(),
promptEngine,
toolOrchestrator,
// The GMI calls the utility AI for sentiment tracking, metaprompts and
// RAG summaries; this persona turns none of them on.
utilityAI: {},
completionGateway: createCompletionGateway({
// Credentials for the primary hop only. Nothing listens on this address.
apiKey: 'unused',
baseUrl: 'http://127.0.0.1:9',
// Fallback hops read their keys from the environment.
fallbackProviders: [{ provider: fallbackProvider, model: fallbackModel }],
}),
llmProviderManager: new GatewayProviderManager().asProviderManager(),
});
const turn = gmi.processTurnStream({
interactionId: 'turn-1',
userId: 'user-1',
sessionId: 'session-1',
type: GMIInteractionType.TEXT,
content: 'What is the weather in Lisbon right now?',
});
// Read the chunks with next() so the turn's GMIOutput, the generator's
// return value, is kept.
let next = await turn.next();
while (!next.done) {
const chunk = next.value;
switch (chunk.type) {
case GMIOutputChunkType.TEXT_DELTA:
process.stdout.write(chunk.content);
break;
case GMIOutputChunkType.TOOL_CALL_REQUEST:
for (const call of chunk.content) {
console.log(`\n[tool call] ${call.name} ${JSON.stringify(call.arguments)}`);
}
break;
case GMIOutputChunkType.USAGE_UPDATE: {
const usage = chunk.content;
const failed = chunk.metadata?.attemptFailed ? ` (billed failed attempt, hop ${chunk.metadata.hop})` : '';
console.log(`\n[usage] prompt=${usage.promptTokens ?? 0} completion=${usage.completionTokens ?? 0}${failed}`);
break;
}
case GMIOutputChunkType.STEP_FINISHED: {
const step = chunk.content;
console.log(
`\n[step ${step.stepIndex}] hop=${step.hop} model=${step.providerId}/${step.modelId} ` +
`finish=${step.finishReason} text=${step.text.length} chars`,
);
break;
}
case GMIOutputChunkType.TOOL_RESULT: {
const result = chunk.content;
console.log(`[tool result] ${result.name} isError=${result.isError} ${JSON.stringify(result.result)}`);
break;
}
case GMIOutputChunkType.ERROR:
console.error(`\n[error] ${chunk.content}`);
break;
default:
break;
}
next = await turn.next();
}
const output = next.value;
console.log('\n--- turn usage (GMIOutput.usage) ---');
console.log(JSON.stringify(output.usage));
console.log('\n--- reasoning trace warnings ---');
for (const entry of gmi.getReasoningTrace().entries) {
if (entry.type === ReasoningEntryType.WARNING) console.log(`- ${entry.message}`);
}
await gmi.shutdown();
// A failed turn still returns its GMIOutput, with `error` set: for example
// when the fallback hop's key is missing or its call fails too.
if (output.error) {
console.error(`\nThe turn failed: ${output.error.code}: ${output.error.message}`);
process.exitCode = 1;
}
}
// Exit with process.exitCode once stdout and stderr have flushed, whatever
// handles are still open.
function exitAfterFlush() {
process.stdout.write('', () => process.stderr.write('', () => process.exit()));
}
main()
.catch((error) => {
console.error(error);
process.exitCode = 1;
})
.finally(exitAfterFlush);