braiin
Version:
Behavioral Reasoning AI for Intelligent Navigation
168 lines (167 loc) • 7.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createOrchestrator = void 0;
const action_factory_1 = require("../factory/action.factory");
const string_factory_1 = require("../factory/string.factory");
const llm_1 = require("../model/llm");
const llm_service_1 = require("../service/llm.service");
const prompt_service_1 = require("../service/prompt.service");
const stream_filter_1 = require("../service/stream.filter");
const timer_1 = require("../service/timer");
const agent_1 = require("./agent");
const DEFAULT_MAX_STEPS = 50;
const DEFAULT_TIMEOUT_MS = 60000;
const DEFAULT_SERVER_URL = 'https://api.openai.com/v1';
const DEFAULT_MODEL = 'gpt-4o';
const DEFAULT_TEMPERATURE = 0;
const buildLLMConfig = (config) => {
const backend = config.backend ?? 'openai';
if (backend === 'claude-code') {
if (!config.sessionId) {
throw new Error('OrchestratorConfig.sessionId is required when backend is "claude-code"');
}
return {
kind: 'claude-code',
sessionId: config.sessionId,
cliPath: config.cliPath,
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
signal: config.signal
};
}
if (!config.apiKey) {
throw new Error('OrchestratorConfig.apiKey is required when backend is "openai"');
}
return {
kind: 'openai',
apiKey: config.apiKey,
serverUrl: config.serverUrl ?? DEFAULT_SERVER_URL,
model: config.model ?? DEFAULT_MODEL,
temperature: config.temperature ?? DEFAULT_TEMPERATURE,
maxTokens: config.maxTokens,
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
signal: config.signal,
enablePromptCaching: config.enablePromptCaching,
enforceJsonOutput: config.enforceJsonOutput
};
};
const runLLM = async (ctx, prompt, history) => {
const { logCallback, onToken } = ctx;
if (!logCallback && !onToken) {
return ctx.llm.ask(ctx.globalContext, prompt, history);
}
const answerFilter = onToken ? (0, stream_filter_1.createAnswerStreamFilter)(onToken) : undefined;
const callback = (delta) => {
logCallback?.(delta);
answerFilter?.(delta);
};
return ctx.llm.ask(ctx.globalContext, prompt, history, callback);
};
const chain = async (prompt, history, toolTraces, step, ctx) => {
if (step >= ctx.maxSteps) {
ctx.logCallback?.('Max steps reached');
return { status: 'error', answer: 'Max steps reached', toolTraces };
}
if (ctx.stepsInterval)
await (0, timer_1.freeze)(ctx.stepsInterval);
const answer = await runLLM(ctx, prompt, history);
if (answer?.error) {
return { status: 'error', answer: `An error occured when calling the LLM: ${answer.error.message}`, toolTraces };
}
const raw = answer?.choices[0]?.message.content?.trim() ?? '';
if (!raw) {
ctx.logCallback?.('LLM returned an empty response');
return { status: 'error', answer: 'LLM returned an empty response', toolTraces };
}
ctx.logCallback?.(raw);
const parsed = (0, string_factory_1.extractJson)(raw);
if (!parsed) {
return { status: 'error', answer: 'Data in wrong format: ' + raw, toolTraces };
}
const validation = (0, action_factory_1.parseLLMAction)(parsed);
if (!validation.ok) {
return { status: 'error', answer: `Invalid action: ${validation.error}. Raw: ${raw}`, toolTraces };
}
return dispatch(validation.action, raw, prompt, history, toolTraces, step + 1, ctx);
};
const dispatch = async (action, raw, prompt, history, toolTraces, nextStep, ctx) => {
const chainNext = (nextPrompt, nextHistory) => chain(nextPrompt, nextHistory, toolTraces, nextStep, ctx);
switch (action.action) {
case 'finish':
return { status: 'success', answer: action.answer, toolTraces };
case 'abort':
return { status: 'error', answer: action.reason, toolTraces };
case 'describe': {
const agent = ctx.agents.find(a => a.name === action.agent);
if (!agent) {
ctx.logCallback?.('No matching agent found for this task');
return { status: 'error', answer: 'No matching agent found for this task', toolTraces };
}
return chainNext(JSON.stringify(agent.tools), [
...history,
{ role: llm_1.LLMMessageRole.USER, content: prompt },
{ role: llm_1.LLMMessageRole.ASSISTANT, content: raw }
]);
}
case 'call': {
const agent = ctx.agents.find(a => a.name === action.agent);
if (!agent) {
ctx.logCallback?.('No matching agent found for this task');
return { status: 'error', answer: 'No matching agent found for this task', toolTraces };
}
const tool = (0, agent_1.findTool)(agent, action.tool);
if (!tool) {
ctx.logCallback?.('No matching tool found for this task');
return { status: 'error', answer: 'No matching tool found for this task', toolTraces };
}
let toolResponse;
try {
toolResponse = await tool.call(action.input);
}
catch (error) {
const message = error instanceof Error ? error.message : 'Unknown tool error';
ctx.logCallback?.(`Tool "${action.tool}" failed: ${message}`);
return { status: 'error', answer: `Tool "${action.tool}" failed: ${message}`, toolTraces };
}
toolTraces.push({ tool: action.tool, input: action.input ?? '', result: toolResponse });
return chainNext(toolResponse, [
...history,
{ role: llm_1.LLMMessageRole.USER, content: prompt },
{ role: llm_1.LLMMessageRole.ASSISTANT, content: raw },
{ role: llm_1.LLMMessageRole.USER, content: `tool response: ${toolResponse}` }
]);
}
}
};
const createOrchestrator = (agents, config) => {
const maxSteps = config.maxSteps ?? DEFAULT_MAX_STEPS;
const globalContext = (0, prompt_service_1.buildSystemPrompt)(agents, config.optionalPrompt);
const llm = config.llmService ?? (0, llm_service_1.createLLMService)(buildLLMConfig(config));
return {
executeTask: async (prompt, history, toolTraces, logCallback, onToken) => {
const contextHistory = [...(history || [])];
if (toolTraces?.length) {
contextHistory.unshift({
role: llm_1.LLMMessageRole.SYSTEM,
content: 'Known context from previous interactions: ' + JSON.stringify(toolTraces)
});
}
const ctx = {
agents,
llm,
globalContext,
maxSteps,
stepsInterval: config.stepsInterval,
logCallback,
onToken
};
// Prior toolTraces are injected as context above, but the accumulator
// starts empty: TaskResult.toolTraces holds only what THIS call produces.
// Callers accumulate across turns themselves if they need a running history.
return chain(prompt, contextHistory, [], 0, ctx);
},
askLLM: async (systemPrompt, prompt, history, logCallback) => {
return llm.ask(systemPrompt, prompt, history || [], logCallback);
}
};
};
exports.createOrchestrator = createOrchestrator;