applyStructuredOutputs extracts the answer + each declared output from the parsed JSON and routes each value to its destination. "answer" goes through ApplyOutput (it's a proper slot on the agent node); declared outputs go through ApplyDeclaration (emit → new var under this node, assign → existing r
(scope *engine.Scope, parsed map[string]any)
| 329 | // through ApplyDeclaration (emit → new var under this node, assign → existing ref). |
| 330 | func (n *Agent) applyStructuredOutputs(scope *engine.Scope, parsed map[string]any) error { |
| 331 | answer, _ := parsed["answer"].(string) |
| 332 | if err := engine.ApplyOutput(scope, n.ID(), agentAnswerOutID, n.answerBinding, expr.StringVal(answer)); err != nil { |
| 333 | return fmt.Errorf("applying answer: %w", err) |
| 334 | } |
| 335 | for _, od := range n.outputDecl { |
| 336 | raw, ok := parsed[od.Name] |
| 337 | if !ok { |
| 338 | return fmt.Errorf("missing declared output %q in agent response", od.Name) |
| 339 | } |
| 340 | val, err := expr.Coerce(od.DataType, raw) |
| 341 | if err != nil { |
| 342 | return fmt.Errorf("output %q: %w", od.Name, err) |
| 343 | } |
| 344 | if err := engine.ApplyDeclaration(scope, n.ID(), od, val); err != nil { |
| 345 | return fmt.Errorf("applying output %s: %w", od.Name, err) |
| 346 | } |
| 347 | } |
| 348 | return nil |
| 349 | } |
| 350 | |
| 351 | // buildBranchingPrompt appends a "choose one of" instruction block to steer |
| 352 | // the model toward populating the "choice" field. Emits the token→description |
| 353 | // legend so the model knows which opaque choice_N corresponds to which branch. |