BID · Console
Baseline · Intelligence · Decision
src/intelligence/tools.ts 5,260 bytes · typescript
/**
 * Intelligence — Anthropic tool surface for the methodology library.
 *
 * Every Intelligence agent exposes the same two tools to its LLM:
 *
 *   find_methodologies({type, domain, agent, triggers})
 *     → search the library for active methodologies that match. Used
 *       at the start of any analytical operation (Std 5 — declared
 *       methods only).
 *
 *   get_methodology({methodology_id})
 *     → fetch the full content of one entry. Used after the LLM
 *       picks a candidate so it can apply the formula / rule / framework.
 *
 * The library is loaded lazily on first call and cached for the
 * process lifetime. Failures are returned as structured
 * MethodologyToolResult objects so the agent's tool-use loop never
 * sees a raw exception (Std 12).
 */

import {
  findMethodologies,
  getMethodology,
  type Methodology,
  type MethodologyAgent,
  type MethodologyType,
} from './library.js';

export interface MethodologyToolDescriptor {
  readonly name: string;
  readonly description: string;
  readonly input_schema: {
    readonly type: 'object';
    readonly properties: Record<string, { type: string; description: string }>;
    readonly required: readonly string[];
  };
}

export const METHODOLOGY_TOOLS: readonly MethodologyToolDescriptor[] = [
  {
    name: 'find_methodologies',
    description:
      "Search the SME methodology library for active entries that match the current analytical " +
      "operation. Filter by `type` (metric_definition | comparison_method | insight_framework | " +
      "normalization_rule), `domain` (banking | wealth_management | insurance | all), `agent` " +
      "(analytical_table | performance_metrics | comparisons_synthesis | insight_synthesis), and " +
      "free-text `triggers`. Returns lightweight {methodology_id, name, type, domain, triggers, " +
      "sourceFile} entries; call get_methodology for the full definition.",
    input_schema: {
      type: 'object',
      properties: {
        type: {
          type: 'string',
          description: 'Filter by methodology type.',
        },
        domain: {
          type: 'string',
          description: 'Filter by domain (banking, wealth_management, insurance, all, …). Entries with domain="all" always match.',
        },
        agent: {
          type: 'string',
          description: 'Filter by intended agent.',
        },
        triggers: {
          type: 'string',
          description: 'Optional comma-separated free-text trigger terms (e.g. "growth, trend"). Each term must appear in the entry\'s triggers list (case-insensitive substring).',
        },
      },
      required: [],
    },
  },
  {
    name: 'get_methodology',
    description:
      'Fetch the full content of one methodology by its methodology_id. Returns the formula / ' +
      'comparison method / insight framework, the inputs/outputs schema, the comparability rules, ' +
      'and the rationale.',
    input_schema: {
      type: 'object',
      properties: {
        methodology_id: { type: 'string', description: 'Unique snake_case identifier.' },
      },
      required: ['methodology_id'],
    },
  },
];

export interface MethodologyToolResult {
  readonly ok: boolean;
  readonly result?: unknown;
  readonly error?: { readonly category: string; readonly message: string };
}

export async function executeMethodologyTool(
  name: string,
  rawInput: unknown,
): Promise<MethodologyToolResult> {
  const input = (rawInput && typeof rawInput === 'object') ? (rawInput as Record<string, unknown>) : {};
  try {
    switch (name) {
      case 'find_methodologies': {
        const triggers = typeof input.triggers === 'string'
          ? input.triggers.split(',').map(s => s.trim()).filter(s => s.length > 0)
          : undefined;
        const results = await findMethodologies({
          type: typeof input.type === 'string' ? (input.type as MethodologyType) : undefined,
          domain: typeof input.domain === 'string' ? input.domain : undefined,
          agent: typeof input.agent === 'string' ? (input.agent as MethodologyAgent) : undefined,
          triggers,
        });
        return { ok: true, result: results.map(summarize) };
      }
      case 'get_methodology': {
        const id = typeof input.methodology_id === 'string' ? input.methodology_id : '';
        if (!id) {
          return { ok: false, error: { category: 'invalid-request', message: 'methodology_id is required.' } };
        }
        const m = await getMethodology(id);
        if (!m) {
          return { ok: false, error: { category: 'not-found', message: `no methodology "${id}" in library.` } };
        }
        return { ok: true, result: m };
      }
      default:
        return { ok: false, error: { category: 'unknown-tool', message: `unknown methodology tool "${name}"` } };
    }
  } catch (err) {
    return {
      ok: false,
      error: { category: 'internal', message: err instanceof Error ? err.message : String(err) },
    };
  }
}

function summarize(m: Methodology) {
  return {
    methodology_id: m.methodology_id,
    name: m.name,
    type: m.type,
    domain: m.domain,
    agent: m.applies_to.agent,
    triggers: m.applies_to.triggers,
    status: m.status,
    sourceFile: m.sourceFile,
  };
}