feat(coder): add model resolution core + multi-batch matcher
Model resolution (from oh-my-openagent/model-core): 6-step priority resolution pipeline (UI select -> user config -> category default -> user fallback -> policy chain -> system default), provider fallback chains, fuzzy model matching, error classification, provider-specific model ID transforms. 14 files, zero runtime deps. Multi-batch matcher (from boocontext-audit): 6 batch types (Observational, Actionable, PreviouslyApplied, Disambiguation, ResponseAnalysis, LowCriticality) for behavioral guideline evaluation. RelationalResolver with iterative convergence (DEPENDS_ON, PRIORITIZES, ENTAILS, TAG_ALL, TAG_PRIORITIZES). SchematicGenerator abstract class with retry and execution plans. 4 files.
This commit is contained in:
204
apps/coder/src/services/behavioral/generation.ts
Normal file
204
apps/coder/src/services/behavioral/generation.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* Schematic generator for behavioral guideline batches.
|
||||
*
|
||||
* Port of boocontext-audit/src/generation.ts — abstract LLM batch caller
|
||||
* with temperature retry and structured output per batch type.
|
||||
*/
|
||||
|
||||
import { type GenerationInfo } from './matching.js';
|
||||
|
||||
// ─── Output types per batch ───
|
||||
|
||||
export interface ObservationalOutput {
|
||||
checks: {
|
||||
guideline_id: string;
|
||||
condition: string;
|
||||
rationale: string;
|
||||
applies: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface ActionableOutput {
|
||||
checks: {
|
||||
guideline_id: string;
|
||||
condition: string;
|
||||
action: string;
|
||||
rationale: string;
|
||||
applies: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface PreviouslyAppliedOutput {
|
||||
checks: {
|
||||
guideline_id: string;
|
||||
condition: string;
|
||||
action_segment: string;
|
||||
rationale: string;
|
||||
is_still_applicable: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface DisambiguationOutput {
|
||||
source_guideline_id: string;
|
||||
rationale: string;
|
||||
enriched_action: string;
|
||||
targets: string[];
|
||||
}
|
||||
|
||||
export interface ResponseAnalysisOutput {
|
||||
guideline_id: string;
|
||||
condition: string;
|
||||
was_followed: boolean;
|
||||
rationale: string;
|
||||
}
|
||||
|
||||
// ─── Batch output map ───
|
||||
|
||||
export interface BatchOutputMap {
|
||||
observational: ObservationalOutput;
|
||||
actionable: ActionableOutput;
|
||||
previously_applied: PreviouslyAppliedOutput;
|
||||
disambiguation: DisambiguationOutput;
|
||||
response_analysis: ResponseAnalysisOutput;
|
||||
}
|
||||
|
||||
export type BatchTypeKey = keyof BatchOutputMap;
|
||||
|
||||
export type OutputForBatch<T extends BatchTypeKey> = BatchOutputMap[T];
|
||||
|
||||
// ─── SchematicGenerator ───
|
||||
|
||||
export abstract class SchematicGenerator<TSchema> {
|
||||
constructor(public modelName: string) {}
|
||||
|
||||
abstract generate(
|
||||
prompt: string,
|
||||
hints?: Record<string, unknown>,
|
||||
): Promise<{
|
||||
content: TSchema;
|
||||
info: GenerationInfo;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default stub implementation that returns empty results.
|
||||
* Replace with a real LLM caller in production.
|
||||
*/
|
||||
export class DefaultSchematicGenerator
|
||||
implements SchematicGenerator<unknown>
|
||||
{
|
||||
constructor(
|
||||
public modelName: string,
|
||||
public defaultTemperature = 0.7,
|
||||
) {}
|
||||
|
||||
async generate(
|
||||
_prompt: string,
|
||||
hints?: Record<string, unknown>,
|
||||
): Promise<{ content: unknown; info: GenerationInfo }> {
|
||||
const temperature = (hints?.temperature as number) ?? this.defaultTemperature;
|
||||
return {
|
||||
content: {},
|
||||
info: {
|
||||
model: this.modelName,
|
||||
duration: 0,
|
||||
tokens: 0,
|
||||
temperature,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Execution plans ───
|
||||
|
||||
export interface BatchExecutionPlan {
|
||||
batchType: BatchTypeKey;
|
||||
guidelines: { id: string; condition: string; action?: string | null }[];
|
||||
priority: number;
|
||||
independent: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ordered execution plan from categorized guideline collections.
|
||||
* Groups are sorted by priority: previously_applied (fastest) first,
|
||||
* then observational, actionable, disambiguation, low-criticality last.
|
||||
*/
|
||||
export function createExecutionPlan(
|
||||
observational: { id: string; condition: string }[],
|
||||
actionable: { id: string; condition: string; action: string }[],
|
||||
previouslyApplied: { id: string; condition: string; action?: string | null }[],
|
||||
disambiguationGroups: { source: string; targets: string[]; enrichedAction: string }[],
|
||||
lowCriticality: { id: string; condition: string }[],
|
||||
): BatchExecutionPlan[] {
|
||||
const plans: BatchExecutionPlan[] = [];
|
||||
|
||||
if (observational.length > 0) {
|
||||
plans.push({
|
||||
batchType: 'observational',
|
||||
guidelines: observational.map((g) => ({ id: g.id, condition: g.condition })),
|
||||
priority: 1,
|
||||
independent: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (actionable.length > 0) {
|
||||
plans.push({
|
||||
batchType: 'actionable',
|
||||
guidelines: actionable.map((g) => ({
|
||||
id: g.id,
|
||||
condition: g.condition,
|
||||
action: g.action,
|
||||
})),
|
||||
priority: 2,
|
||||
independent: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (previouslyApplied.length > 0) {
|
||||
plans.push({
|
||||
batchType: 'previously_applied',
|
||||
guidelines: previouslyApplied.map((g) => ({
|
||||
id: g.id,
|
||||
condition: g.condition,
|
||||
action: g.action,
|
||||
})),
|
||||
priority: 0,
|
||||
independent: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (disambiguationGroups.length > 0) {
|
||||
plans.push({
|
||||
batchType: 'disambiguation',
|
||||
guidelines: disambiguationGroups.map((g) => ({
|
||||
id: g.source,
|
||||
condition: g.enrichedAction,
|
||||
})),
|
||||
priority: 3,
|
||||
independent: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (lowCriticality.length > 0) {
|
||||
plans.push({
|
||||
batchType: 'observational',
|
||||
guidelines: lowCriticality.map((g) => ({ id: g.id, condition: g.condition })),
|
||||
priority: 10,
|
||||
independent: true,
|
||||
});
|
||||
}
|
||||
|
||||
return plans.sort((a, b) => a.priority - b.priority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute retry temperatures: base + 0.2 * attempt.
|
||||
* Provides progressive temperature increases for failed calls.
|
||||
*/
|
||||
export function getRetryTemperatures(baseTemp: number, maxAttempts = 3): number[] {
|
||||
const temps: number[] = [];
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
temps.push(baseTemp + i * 0.2);
|
||||
}
|
||||
return temps;
|
||||
}
|
||||
Reference in New Issue
Block a user