Lección 25: Lo que llega a la skill de transposición
Secciones
Ajustes de lectura
Ninguna voz para este idioma en este navegador.
Más voces
Código y términos técnicos en inglés
Desactivado: la voz de la página lo lee todo, sin pausa entre dos voces.
Ninguna voz para este idioma en este navegador.
Más voces
Más voces: Microsoft Edge ofrece voces naturales en línea, y Windows (Configuración › Hora e idioma › Voz), macOS y Android instalan otras.
La lección 10 llamó a la closure que TransposeSkill le dice a un modelo que use, con los argumentos que prescribe su SKILL.md. Esta lección pregunta por el paso anterior: qué preguntas llegan a la skill. El enrutador de intenciones del chatbot puntúa cada skill por su mejor coincidencia entre su descripción y sus prompts de ejemplo, suma +0,06 por cada pista de enrutamiento cuyo patrón coincide, y solo enruta si la mejor puntuación alcanza MinConfidence (SemanticIntentRouter.cs líneas 129-257). El curso no tiene un modelo para calcular los embeddings, así que ejecuta las dos partes que no lo necesitan: las pistas, y lo que hace el chatbot con una transposición cuando no se puede calcular ningún embedding.
Todos los enlaces a GA apuntan al commit a826864, el commit fijado del curso, salvo los que nombran otro. En el main de GA en f4f5b4a (2026-10-01), TransposeSkill, su SKILL.md, las reglas de pistas, SkillMdPlugin y el registro de las skills no han cambiado, pero los dos enrutadores cambiaron para una pregunta cuyo embedding no pueden calcular: el enrutador de intenciones pregunta a cada intención si coincide sin embeddings, y el enrutador de agentes recurre a sus palabras clave en lugar de lanzar una excepción. Los dos cambios vienen de las pull requests de GA #686 y #688, que corregían hallazgos de este curso y de music-theory-ga. GaMain arranca ahora el host del chatbot de main igual que GaAi arranca el del commit fijado, con el mismo puerto de Ollama cerrado, sin clave de API y con el índice de la lección 3 construido con el código de main. La salida procede de:
dotnet run --project code/ga-ai/GaAi -c Release -- l25dotnet run --project code/ga-ai/GaMain -c Release -- l25Cómo llega una pregunta a la skill
Sección titulada «Cómo llega una pregunta a la skill»DefaultRoutingHintProvider tiene una regla para la transposición: una palabra que empieza por “transpos”, o “shift”, “bring” o “move” seguida, entre una y seis palabras después, de “up”, “down”, o de “to” y una mayúscula:
(new Regex(@"\btranspos\w+\b|\b(shift|bring|move)\b\s+\S+(?:\s+\S+){0,5}?\s+(?:up|down|to\s+(?-i:[A-G]))\b", RegexOptions.IgnoreCase | RegexOptions.Compiled), "skill.transpose"),TransposeSkill le pasa la pregunta a un modelo, y la clase de la que deriva responde que no a todo CanHandle:
/// <inheritdoc /> /// <remarks> /// Tool-driven skills only route via the <see cref="Intents.SemanticIntentRouter"/>; /// the legacy <c>CanHandle</c> regex shadow is intentionally disabled. /// </remarks> public bool CanHandle(string message) => false;En main, cuando no se puede calcular el embedding de la pregunta, el enrutador de intenciones se la da a la primera intención, en orden de registro, que coincide con ella sin embeddings. Para la intención de una skill, eso es el CanHandle de la skill (OrchestratorSkillIntent.cs línea 29):
// Offline degradation: when the embedding backend cannot score the query, the // first intent (registration order) whose high-precision keyword predicate // matches claims it at the threshold confidence, so deterministic skills still // answer without Ollama. Returns null when none matches (LLM path as before). private IntentMatch? KeywordFallback(string query, IReadOnlyList<IIntent> intents) { foreach (var intent in intents) { if (!intent.MatchesWithoutEmbeddings(query)) continue;
logger.LogInformation( "SemanticIntentRouter: embeddings unavailable; keyword fallback picked {IntentId} for query={Query}", intent.Id, SanitizeForLog(query));
return new IntentMatch(intent, MinConfidence, KeywordFallbackSource) { Ranking = [new RoutingCandidate(intent.Id, MinConfidence, 0f, MinConfidence, KeywordFallbackSource)], }; }
return null; }Las pistas que recibe una transposición
Sección titulada «Las pistas que recibe una transposición»El programa le pregunta a DefaultRoutingHintProvider por 26 formulaciones: los 13 prompts de ejemplo de la skill, la única formulación de su SKILL.md que no está entre ellos (línea 80), los cuatro prompts de transposición del corpus de GA y ocho del curso. También busca en cada una los disparadores del SKILL.md, como subcadenas en minúsculas, igual que SkillMdDrivenSkill.CanHandle (SkillMdDrivenSkill.cs líneas 54-65):
== Transpose phrasings: the intents whose routing hint fires, and the triggers of transpose's SKILL.md they contain (at the pin)source phrasing hints, +0.06 each SKILL.md triggersexample transpose this progression down a half step skill.transpose transpose, down aexample transpose this progression up a whole step skill.transpose transpose, up aexample transpose C-Am-F-G to G major skill.transpose transposeexample shift this progression up a half step skill.transpose up aexample bring D minor down to A minor skill.transpose noneexample Transpose Cmaj7 up a perfect fourth skill.interval, skill.transpose transpose, up aexample Move this F chord down a minor third skill.interval, skill.transpose down aexample What's Dm7 up a whole step? none up aexample Transpose G7 to Eb skill.transpose transposeexample Shift Am7 up a fifth skill.transpose up aexample raise the key by two semitones none noneexample lower the key by a half step none noneexample transposing the chorus down a tone skill.transpose down aSKILL.md Cmaj7 in the key of G none in the key ofcorpus Transpose C E G to D skill.transpose transposecorpus Transpose C major to E skill.transpose transposecorpus Transpose this progression to capo 3 skill.capo, skill.transpose transposecorpus Transpose A minor to C minor skill.transpose transposecourse Transpose Dm7 down a half step skill.transpose transpose, down acourse Move Am up two frets skill.transpose nonecourse Shift the song to the key of E none nonecourse Put Cmaj7 in the key of A none in the key ofcourse Raise Bb7 by a minor third skill.interval nonecourse Lower G by a tritone skill.interval nonecourse Play this song a whole step down skill.alternatetunings nonecourse Take F#m up a fourth none up a26 phrasings: the transpose hint fires on 16; another intent's hint on 6, 3 of them without the transpose hint; no hint on 7; a SKILL.md trigger in 18- La pista de transposición no se activa con 3 de los 13 prompts de ejemplo de la skill. “What’s Dm7 up a whole step?”, “raise the key by two semitones” y “lower the key by a half step” no tienen ninguno de sus verbos, y tampoco el “Cmaj7 in the key of G” del SKILL.md. El “Shift the song to the key of E” del curso tiene el verbo, pero termina en “of E”, no en “to E”.
- Dos prompts de ejemplo refuerzan a
skill.intervaltanto como askill.transpose. “Transpose Cmaj7 up a perfect fourth” y “Move this F chord down a minor third” nombran un intervalo, que lee la regla de los intervalos (líneas 74-76); con +0,06 en las dos intenciones, las pistas dejan la distancia entre ellas donde la ponen los embeddings. “Raise Bb7 by a minor third” y “Lower G by a tritone” solo refuerzan askill.interval, y “Play this song a whole step down” solo askill.alternatetunings, cuya regla toma “whole step down” por una afinación (líneas 159-161).
En main, la tabla es la misma.
Lo que la pista y los disparadores también se llevan
Sección titulada «Lo que la pista y los disparadores también se llevan»El programa pregunta lo mismo con los prompts de ejemplo de todas las demás intenciones:
== The other intents' example prompts that the transpose hint or transpose's SKILL.md triggers claim (at the pin)intent example prompt hint SKILL.md triggersskill.chordinfo what notes make up a G7 chord no up askill.modes What are modes of limited transposition yes noneskill.progressionmood Brighten up a minor key tune no up askill.diatonicchords All chords in the key of D no in the key ofskill.settheoryequivalence Are pitch class sets 0,1,3 and 0,2,3 equivalent under tr… yes noneskill.settheoryequivalence Are pitch classes 0,2,4 and 1,3,5 transpositionally equi… yes noneskill.voiceleading best way to move from G7 to C yes noneskill.grothendieckdelta harmonic cost to move from C to G yes noneskill.grothendieckparse what does Transpose(C ⊗ G) mean yes transposeskill.grothendieckparse parse Transpose ∘ Invert yes transposeskill.grothendieckparse parse pullback(Cmaj7, Transpose, Gmaj7) yes transposeskill.grothendieckparse parse functor Transpose: Chords -> Chords yes transposeskill.grothendieckparse parse equalizer Transpose Invert yes transpose407 example prompts of 34 other intents: the transpose hint fires on 10, a SKILL.md trigger is in 8- La pista de transposición se activa con 10 prompts de ejemplo de otras intenciones. “transposition” y “transpositionally” en dos prompts de teoría de conjuntos y en “What are modes of limited transposition”; “Transpose”, el nombre de un funtor, en cinco de los de la skill de análisis; y “move from G7 to C” y “move from C to G”, donde “to” y una mayúscula cierran la frase. Las lecciones 23 y 24 imprimieron algunos en sus columnas de pistas.
- Los disparadores son subcadenas. “up a” está en “what notes make up a G7 chord” y en “Brighten up a minor key tune”, e “in the key of” en “All chords in the key of D”.
En main, las mismas 13 filas salen de 412 prompts de ejemplo de 35 intenciones.
Las skills detrás del nombre
Sección titulada «Las skills detrás del nombre»SkillMdPlugin registra cada SKILL.md que tiene disparadores como una SkillMdDrivenSkill (SkillMdPlugin.cs líneas 83-92), así que dos skills llevan el nombre:
== The skills registered under the name Transpose, and the intent the router can pick for each (at the pin)skill name intent CanHandle acceptsSkillMdDrivenSkill transpose none 18 of the 26 phrasingsTransposeSkill Transpose skill.transpose 0 of the 26 phrasingsIOrchestratorSkill registrations 50, of them built from a SKILL.md 18, behind an intent 0- Solo
TransposeSkilltiene una intención, y suCanHandleno acepta ninguna de las 26 formulaciones. La skill del SKILL.md acepta 18, pero ninguna de las 18 skills construidas a partir de un SKILL.md está detrás de una intención: el enrutador solo lee intenciones, y el orquestador ya no llama aCanHandle, como mostró la lección 4.
En main, la tabla es la misma.
Sin embeddings
Sección titulada «Sin embeddings»Con el puerto de Ollama cerrado, el programa le pide al SemanticIntentRouter del host que enrute cada formulación y después la envía a /api/chatbot/chat. En el commit fijado:
== Without embeddings: the intent SemanticIntentRouter picks for each phrasing, and what POST /api/chatbot/chat answers (at the pin)phrasing router picks chat: agent (routing method) first line of the answertranspose this progression down a half step none HTTP 500transpose this progression up a whole step none HTTP 500transpose C-Am-F-G to G major none HTTP 500shift this progression up a half step none HTTP 500bring D minor down to A minor none HTTP 500Transpose Cmaj7 up a perfect fourth none HTTP 500Move this F chord down a minor third none HTTP 500What's Dm7 up a whole step? none HTTP 500Transpose G7 to Eb none HTTP 500Shift Am7 up a fifth none HTTP 500raise the key by two semitones none HTTP 500lower the key by a half step none HTTP 500transposing the chorus down a tone none HTTP 500Cmaj7 in the key of G none HTTP 500Transpose C E G to D none HTTP 500Transpose C major to E none HTTP 500Transpose this progression to capo 3 none HTTP 500Transpose A minor to C minor none HTTP 500Transpose Dm7 down a half step none HTTP 500Move Am up two frets none HTTP 500Shift the song to the key of E none HTTP 500Put Cmaj7 in the key of A none HTTP 500Raise Bb7 by a minor third none HTTP 500Lower G by a tritone none HTTP 500Play this song a whole step down none HTTP 500Take F#m up a fourth none HTTP 50026 phrasings; the router picks none 26; the chat endpoint answers with HTTP 500 26- Todas las formulaciones terminan en HTTP 500. El enrutador de intenciones no devuelve nada cuando falla el embedding de la pregunta (
SemanticIntentRouter.cslíneas 115-127), y el enrutador de agentes que va detrás no captura el fallo de su propio embedding (SemanticRouter.cslíneas 70-73), como rastreó la lección 4; tres de los prompts del corpus terminaron igual en la lección 8.
En main:
== Without embeddings: the intent SemanticIntentRouter picks for each phrasing, and what POST /api/chatbot/chat answers (on main)phrasing router picks chat: agent (routing method) first line of the answertranspose this progression down a half step none voicing (keyword) Found 10 voicings matching tags [half]:transpose this progression up a whole step none voicing (keyword) Found 10 voicings matching tags [whole]:transpose C-Am-F-G to G major none voicing (keyword) Found 10 voicings matching chord G + mode major +…shift this progression up a half step none voicing (keyword) Found 10 voicings matching tags [half]:bring D minor down to A minor none voicing (keyword) Found 10 voicings matching chord D + mode minor +…Transpose Cmaj7 up a perfect fourth none voicing (keyword) Found 10 voicings matching chord Cmaj7 + tags [fo…Move this F chord down a minor third skill.chordinfo skill.chordinfo (orchestrator-skill-semantic) F major chord contains F, A, and C.What's Dm7 up a whole step? skill.chordinfo skill.chordinfo (orchestrator-skill-semantic) D minor 7 chord contains D, F, A, and C.Transpose G7 to Eb none voicing (keyword) Found 10 voicings matching chord G7:Shift Am7 up a fifth none voicing (keyword) Found 10 voicings matching chord Am7 + tags [fift…raise the key by two semitones none fallback-direct (error-fallback-unavailable) Our reasoning service is currently unavailable, s…lower the key by a half step none fallback-direct (error-fallback-unavailable) Our reasoning service is currently unavailable, s…transposing the chorus down a tone none voicing (keyword) Found 10 voicings matching tags [the, tone]:Cmaj7 in the key of G none fallback-direct (error-fallback-unavailable) Our reasoning service is currently unavailable, s…Transpose C E G to D none voicing (keyword) Found 10 voicings matching chord C:Transpose C major to E none voicing (keyword) Found 10 voicings matching chord C + mode major:Transpose this progression to capo 3 none voicing (keyword) I couldn't find a chord name, mode, or style tag …Transpose A minor to C minor none voicing (keyword) Found 10 voicings matching chord A + mode minor +…Transpose Dm7 down a half step none voicing (keyword) Found 10 voicings matching chord Dm7 + tags [half…Move Am up two frets none fallback-direct (error-fallback-unavailable) Our reasoning service is currently unavailable, s…Shift the song to the key of E none fallback-direct (error-fallback-unavailable) Our reasoning service is currently unavailable, s…Put Cmaj7 in the key of A none fallback-direct (error-fallback-unavailable) Our reasoning service is currently unavailable, s…Raise Bb7 by a minor third none voicing (keyword) Found 10 voicings matching chord Bb7 + mode minor…Lower G by a tritone skill.chordsubstitution skill.chordsubstitution (orchestrator-skill-semantic) Harmonic substitutions for **G** (ranked by ICV d…Play this song a whole step down none fallback-direct (error-fallback-unavailable) Our reasoning service is currently unavailable, s…Take F#m up a fourth none voicing (keyword) Found 10 voicings matching chord F#m + tags [four…26 phrasings; the router picks none 23, skill.chordinfo 2, skill.chordsubstitution 1; the chat endpoint answers with voicing (keyword) 16, fallback-direct (error-fallback-unavailable) 7, skill.chordinfo (orchestrator-skill-semantic) 2, skill.chordsubstitution (orchestrator-skill-semantic) 1- El enrutador de intenciones enruta 3 de las 26, nunca a
skill.transpose. “Move this F chord down a minor third” y “What’s Dm7 up a whole step?” van aChordInfoSkill, que da las notas del acorde antes de la transposición; “Lower G by a tritone” va aChordSubstitutionSkill, que lista sustitutos de G. El método de enrutamiento de las tres diceorchestrator-skill-semantic, queOrchestratorSkillIntentescribe en cada respuesta (línea 54), aunque ningún embedding las eligió. - Las otras 23 van al enrutador de agentes, que ahora captura el fallo del embedding y recurre a sus palabras clave (
SemanticRouter.cslíneas 72-84). Cada agente con una lista de palabras clave puntúa la proporción de sus palabras clave que contiene la pregunta; el agente de voicings no tiene lista y puntúa 0,1:
var keywords = new Dictionary<string, string[]> { [AgentIds.Tab] = ["tab", "tablature", "fret", "string", "ascii", "parse", "e|", "a|", "d|"], [AgentIds.Theory] = ["chord", "scale", "key", "mode", "interval", "pitch", "harmonic", "function", "cadence", "theory"], [AgentIds.Technique] = ["finger", "position", "play", "technique", "stretch", "barre", "slide", "bend"], [AgentIds.Composer] = ["compose", "create", "generate", "reharmonize", "variation", "arrangement"], [AgentIds.Critic] = ["evaluate", "critique", "review", "improve", "suggest", "better"] };
var lowerQuery = query.ToLowerInvariant(); var scores = new List<(GuitarAlchemistAgentBase Agent, float Score)>();
foreach (var agent in _agents) { if (keywords.TryGetValue(agent.AgentId, out var agentKeywords)) { var matchCount = agentKeywords.Count(k => lowerQuery.Contains(k)); var score = (float)matchCount / agentKeywords.Length; scores.Add((agent, score)); } else { scores.Add((agent, 0.1f)); // Default low score } }- 16 formulaciones reciben una búsqueda de voicings. Una pregunta sin ninguna de las palabras clave va al agente de voicings, cuya búsqueda lee “half”, “whole” o “fourth” como etiquetas: “Shift Am7 up a fifth” recibe voicings de Am7. Las 7 que contienen “key”, “fret” o “play” van a un agente que necesita un modelo, y la respuesta es el “Our reasoning service is currently unavailable” del respaldo. “fret” y “play” llevan al agente de tablaturas o al de técnica por encima de 0,1; “key” solo lleva al agente de teoría a 0,1, la puntuación del agente de voicings, y el agente de teoría gana el empate porque está registrado antes que el agente de voicings (
ServiceCollectionExtensions.cslíneas 94-99) y la ordenación conserva ese orden entre puntuaciones iguales (SemanticRouter.cslínea 373). No se imprime a qué agente llegó cada una; esto se lee en el código.
La skill en sí
Sección titulada «La skill en sí»Llamada directamente, sin modelo:
== TransposeSkill called directly with "Transpose Cmaj7 up a perfect fourth" (at the pin)confidence 0.00 | I encountered an error processing your request. Please try again. evidence: Source: skills/transpose/SKILL.md evidence: Closure: domain.transposeChord (via ga_dsl_eval) evidence: warning: ga_dsl_eval was NOT invoked — answer is LLM-only, not deterministic- La respuesta es un error, y la evidencia la presenta como producida solo por el LLM.
SkillMdDrivenSkillcaptura el fallo de la llamada al modelo y responde con su mensaje de error (SkillMdDrivenSkill.cslíneas 204-226); el envoltorio no encuentra ninguna llamada aga_dsl_evaly añade “answer is LLM-only”, la línea engañosa que la lección 8 encontró en el registro deDiatonicChordsSkill, comunicada en la issue de GA #764. Aunque se la enrute, la skill no puede transponer sin conexión: sin modelo, la respuesta honesta a una transposición es que ahora no se puede responder.
En main, la respuesta y la evidencia son las mismas.
Hasta dónde llega el curso
Sección titulada «Hasta dónde llega el curso»- No se calculan los embeddings. Una pista vale +0,06 sobre una puntuación coseno; que cambie la skill que elige el enrutador depende de puntuaciones que el curso no puede calcular. La lección mide las pistas y los caminos sin embeddings, no el enrutamiento con ellos.
- Las formulaciones son una muestra: las de la skill y de su SKILL.md, los cuatro prompts del corpus de GA y ocho del propio curso.
- El endpoint de chat se consulta a través de
WebApplicationFactory, con la URL de Ollama en un puerto local cerrado y una clave de API vacía, y las tablas imprimen la primera línea de cada respuesta.
Comunicado upstream
Sección titulada «Comunicado upstream»- Se comunicaron después de escribir esta lección, en la issue de GA #809: los prompts de ejemplo que la pista de transposición no reconoce y los de otras intenciones con los que se activa, el respaldo por palabras clave que no puede llegar a
skill.transposey la victoria por defecto del agente de voicings, y los disparadores del SKILL.md hacia los que ninguna intención enruta.
Ejercicios
Sección titulada «Ejercicios»- ¿Por qué “What’s Dm7 up a whole step?” no recibe la pista de transposición? Reformúlalo para que la reciba.
- “best way to move from G7 to C” es un prompt de ejemplo de la conducción de voces. ¿Qué parte de la regla de transposición coincide con él?
- En
main, sin embeddings, ¿por qué el enrutador de intenciones nunca puede elegirskill.transpose? - En
main, sin embeddings, ¿por qué “Transpose C major to E” recibe una búsqueda de voicings y “Move Am up two frets” recibe “Our reasoning service is currently unavailable”?
Soluciones
- No tiene ninguna palabra que empiece por “transpos” ni ninguno de “shift”, “bring” y “move” (línea 257). “Shift Dm7 up a whole step” la recibiría, como “Shift Am7 up a fifth” en la tabla.
- “move”, después “from” y “G7”, después “to C”: la regla lee entre una y seis palabras de cualquier tipo tras el verbo, y después “to” y una mayúscula.
- Pregunta a cada intención si coincide sin embeddings, lo que para la intención de una skill es el
CanHandlede la skill (OrchestratorSkillIntent.cslínea 29), y el deTransposeSkilldevuelve false (SkillMdDrivenWrapperBase.cslínea 85). La skill del SKILL.md, cuyos disparadores aceptan 18 de las 26 formulaciones, no tiene intención. - El enrutador de intenciones no elige nada para ninguna de las dos, así que ambas llegan a las palabras clave del enrutador de agentes. “Transpose C major to E” no contiene ninguna de las palabras clave de los cinco agentes que tienen lista, así que cada uno puntúa 0 y el agente de voicings conserva su 0,1. “Move Am up two frets” contiene “fret”, una de las nueve palabras clave del agente de tablaturas, que puntúa entonces unos 0,11; ese agente necesita un modelo, y el respaldo que reemplaza su respuesta tampoco tiene. Razonado a partir de las listas de palabras clave; el programa solo imprime el agente que responde.
Puntos clave
Sección titulada «Puntos clave»- Prueba primero una regla de pistas con los propios prompts de ejemplo de la skill: aquí 3 de 13 no reciben nada.
- Un refuerzo que reciben dos intenciones no desempata entre ellas.
- Un disparador por subcadena como “up a” también coincide con “make up a G7 chord”.
- Un respaldo por palabras clave solo llega a las skills cuyo
CanHandlepuede decir que sí: una skill que siempre dice que no es inalcanzable sin embeddings. - Una puntuación por defecto para un agente sin palabras clave lo convierte en la respuesta a toda pregunta sin palabras clave: sin modelo, un “no disponible” claro es mejor que una búsqueda de voicings segura de sí misma.
Fuentes
Sección titulada «Fuentes»- GuitarAlchemist/ga en
a826864:Common/GA.Business.ML/Agents/Intents/DefaultRoutingHintProvider.cs,Common/GA.Business.ML/Agents/Intents/SemanticIntentRouter.cs,Common/GA.Business.ML/Agents/SemanticRouter.cs,Common/GA.Business.ML/Agents/Skills/TransposeSkill.cs,Common/GA.Business.ML/Agents/Skills/SkillMdDrivenWrapperBase.cs,Common/GA.Business.ML/Agents/Skills/SkillMdDrivenSkill.cs,Common/GA.Business.ML/Agents/Plugins/SkillMdPlugin.cs,skills/transpose/SKILL.md,Tests/Apps/GaChatbot.Api.Tests/Corpus/prompts.yaml. - GuitarAlchemist/ga en
f4f5b4a:SemanticIntentRouter.cscon su respaldo por palabras clave,Common/GA.Business.Core.Orchestration/Intents/OrchestratorSkillIntent.cs,SemanticRouter.cscon el fallo de embedding que captura, y el host del chatbot. - Los programas del curso:
code/ga-ai/GaAi/Lesson25.cs,code/ga-ai/GaMain/Program.cs,code/ga-ai/GaMain/MainChatHost.cs,code/ga-ai/Shared/TransposeRoutingProbe.cs.