11// altimate_change start - LLM-based dynamic skill selection
2- import { generateObject } from "ai"
2+ import { generateText } from "ai"
33import type { LanguageModelV2 } from "@openrouter/ai-sdk-provider"
4- import z from "zod"
54import { Provider } from "../provider/provider"
65import { Log } from "../util/log"
76import type { Skill } from "../skill"
87import type { Fingerprint } from "./fingerprint"
8+ import { Tracer } from "./observability/tracing"
99
1010const log = Log . create ( { service : "skill-selector" } )
1111
@@ -27,9 +27,8 @@ export interface SkillSelectorDeps {
2727 generate : ( params : {
2828 model : LanguageModelV2
2929 temperature : number
30- schema : z . ZodType
3130 messages : Array < { role : "system" | "user" ; content : string } >
32- } ) => Promise < { object : { selected : string [ ] } } >
31+ } ) => Promise < { text : string } >
3332}
3433
3534async function defaultResolveModel ( ) : Promise < LanguageModelV2 | undefined > {
@@ -44,7 +43,7 @@ async function defaultResolveModel(): Promise<LanguageModelV2 | undefined> {
4443
4544const defaultDeps : SkillSelectorDeps = {
4645 resolveModel : defaultResolveModel ,
47- generate : generateObject as any ,
46+ generate : generateText as any ,
4847}
4948
5049/**
@@ -58,12 +57,21 @@ export async function selectSkillsWithLLM(
5857 fingerprint : Fingerprint . Result | undefined ,
5958 deps ?: SkillSelectorDeps ,
6059) : Promise < Skill . Info [ ] > {
60+ const startTime = Date . now ( )
61+
6162 // Return cached result if cwd hasn't changed (0ms)
6263 const cwd = fingerprint ?. cwd
6364 if ( cachedResult && cwd === cachedCwd ) {
6465 log . info ( "returning cached skill selection" , {
6566 count : cachedResult . length ,
6667 } )
68+ Tracer . active ?. logSpan ( {
69+ name : "skill-selection" ,
70+ startTime,
71+ endTime : Date . now ( ) ,
72+ input : { fingerprint : fingerprint ?. tags , source : "cache" } ,
73+ output : { count : cachedResult . length , skills : cachedResult . map ( ( s ) => s . name ) } ,
74+ } )
6775 return cachedResult
6876 }
6977
@@ -96,15 +104,15 @@ export async function selectSkillsWithLLM(
96104 const params = {
97105 model,
98106 temperature : 0 ,
99- schema : z . object ( { selected : z . array ( z . string ( ) ) } ) ,
100107 messages : [
101108 {
102109 role : "system" as const ,
103110 content : [
104111 "You are a skill selector for a coding assistant." ,
105112 "Given a project environment and available skills, select which skills are relevant for this project." ,
106- "Return ONLY skill names the user likely needs. Select 0-15 skills." ,
107- "Prefer fewer, more relevant skills over many loosely related ones." ,
113+ "Return ONLY a JSON object: {\"selected\": [\"skill-name-1\", \"skill-name-2\"]}" ,
114+ "Select 0-15 skills. Prefer fewer, more relevant skills over many loosely related ones." ,
115+ "Return ONLY the JSON object, no other text." ,
108116 ] . join ( "\n" ) ,
109117 } ,
110118 {
@@ -125,7 +133,15 @@ export async function selectSkillsWithLLM(
125133 ) ,
126134 ] )
127135
128- const selected = result . object . selected . slice ( 0 , MAX_SKILLS )
136+ // Parse JSON from response text
137+ const text = result . text . trim ( )
138+ const jsonMatch = text . match ( / \{ [ \s \S ] * \} / )
139+ if ( ! jsonMatch ) {
140+ log . info ( "LLM response not valid JSON, returning all skills" )
141+ return cache ( skills )
142+ }
143+ const parsed = JSON . parse ( jsonMatch [ 0 ] ) as { selected ?: string [ ] }
144+ const selected = ( parsed . selected ?? [ ] ) . slice ( 0 , MAX_SKILLS )
129145
130146 // Zero-selection guard
131147 if ( selected . length === 0 ) {
@@ -147,11 +163,26 @@ export async function selectSkillsWithLLM(
147163 count : matched . length ,
148164 names : matched . map ( ( s ) => s . name ) ,
149165 } )
166+ Tracer . active ?. logSpan ( {
167+ name : "skill-selection" ,
168+ startTime,
169+ endTime : Date . now ( ) ,
170+ input : { fingerprint : fingerprint ?. tags , totalSkills : skills . length , source : "llm" } ,
171+ output : { count : matched . length , skills : matched . map ( ( s ) => s . name ) } ,
172+ } )
150173 return cache ( matched )
151174 } catch ( e ) {
152175 log . info ( "skill selection failed, returning all skills" , {
153176 error : e instanceof Error ? e . message : String ( e ) ,
154177 } )
178+ Tracer . active ?. logSpan ( {
179+ name : "skill-selection" ,
180+ startTime,
181+ endTime : Date . now ( ) ,
182+ status : "error" ,
183+ input : { fingerprint : fingerprint ?. tags , source : "fallback" } ,
184+ output : { count : skills . length , error : e instanceof Error ? e . message : String ( e ) } ,
185+ } )
155186 return cache ( skills )
156187 }
157188}
0 commit comments