Ctrl K
ComunidadGuías…

examples/gmi-completion-gateway.mjs

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/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);