Ctrl K
ComunidadGuías…

specs/071-decision-evaluate.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Spec: Decision Evaluation Action

## Status

Implemented.

## Scope

This spec defines `decision.evaluate`, its provider connection, typed questions,
response validation, and automatic or explicit output capture.

## Goal

Workflow authors can classify, score, and evaluate yes/no questions against
shared context, then route execution using the structured answers.

## Behavior

`with.provider`, `with.model`, `with.state`, and `with.questions` are required.
Connection settings belong to the action; the DAG-level `llm` block is not used.
The model is a single string passed unchanged to the endpoint. State accepts a
string, object, or array. Questions are a nonempty map of IDs to questions.

Each question has `type`, `instructions` (string, object, or array), and
type-specific `criteria`:

- `choice`: 2–255 named options, each with a string description or null.
- `score`: 2–10 string descriptions ordered from level zero upward.
- `noul`: an optional object with both `true` and `false` string descriptions.
  The keys are the literal strings, so YAML must quote them.

Question IDs correlate answers; instructions contain the question's meaning.
Standard runtime references resolve in provider, model, base URL, and string
leaves of state, instructions, and criteria. Object and array structure is
preserved. The API key variable name is a literal environment variable name.

### Connection

| Provider | Default base URL | Request path | Default API key variable |
| --- | --- | --- | --- |
| `openrouter` | `https://openrouter.ai/api/alpha` | `/decisions` | `OPENROUTER_API_KEY` |
| `typesafe` | `https://api.typesafe.ai/v1` | `/systemone` | `TYPESAFE_API_KEY` |

`with.base_url` overrides the API root, including its version prefix. A trailing
slash is accepted. Query and fragment delimiters, including empty trailing
`?` and `#`, are rejected. The URL must use HTTPS, except HTTP is allowed for
localhost and loopback IP addresses. `with.api_key_name` optionally names another
environment variable; it is not the key's value.
The key is resolved from the workflow environment, including declared secrets.
An absent or empty key fails before sending a request.

Requests use JSON HTTP POST with bearer authentication. The body contains only
`model`, `state`, and `questions`. All questions share one request. Requests
use the existing LLM HTTP retry policy and respect step timeout and cancellation.
Without a step timeout a request has no deadline of its own.
There is no streaming, model fallback, chat history, or tool execution.

### Answers and outputs

A successful response is one JSON object containing `model`, `answers`, and
`usage`. `usage` is an object whose `input_tokens` and `output_tokens` counts are
validated as non-negative integers when present; a response that omits them is
still valid. Each requested question must have an answer of the matching type:

- `choice`: a selected criterion, probabilities for every option, and confidence.
- `score`: a numeric score within the configured scale, probabilities and legend
  entries for every level, and confidence.
- `noul`: a probability between zero and one, not a boolean.

Probabilities and confidence must be numbers between zero and one. Low
confidence is a successful result, not an execution error. Provider metadata
is retained. An integer a float64 cannot hold exactly keeps its precision and
representation through capture, persistence, and direct output references. Every
other number renders through float64. Whitespace and object-key order may change. Diagnostics do not appear in the JSON stdout response.

Without explicit output configuration, Dagu captures the response automatically.
The existing `${classify.output.answers.department.choice}` JSON lookup works
for a dependent step. Top-level `answers`, `model`, and `usage` are also named
outputs accessible through `${steps.classify.outputs.answers}` and equivalent
top-level references. Strict step-output references do not support nested paths.

Explicit `output`, `output_schema`, or `stdout.outputs` uses existing capture
semantics instead of the automatic default. `output: RESULT` captures the raw
response, and object-form output can select or rename fields. Output size limits,
secret masking, and persisted run output behavior apply. The raw provider
response is also bounded by `max_output_size` (1 MiB by default), before JSON
decoding or masking. An oversized response fails without publishing output.
Failed attempts publish no successful named outputs. Each retry captures only its own response.

## Errors

Missing required fields, unsupported providers or question types, invalid
criteria, and invalid literal URLs fail validation. Values containing runtime
references are checked after resolution. Unknown action configuration fields
are rejected. Invalid JSON responses, missing answers, mismatched answer types,
invalid answer fields, exhausted HTTP retries, cancellation, and timeout fail
the step. Response validation errors must not include the response content.

## Example

```yaml
type: graph
secrets:
  - name: OPENROUTER_API_KEY
    provider: env
    key: OPENROUTER_API_KEY
steps:
  - id: classify
    action: decision.evaluate
    with:
      provider: openrouter
      model: typesafe/jev-1.13
      state: I was charged twice.
      questions:
        department:
          type: choice
          instructions: Which department should handle this request?
          criteria:
            billing: Charges and refunds
            other: Anything else
        refund:
          type: noul
          instructions: Is a refund requested?
          criteria:
            "true": A refund is requested
            "false": No refund is requested
  - id: consume
    depends: [classify]
    action: log.write
    with:
      message: ${classify.output.answers.department.choice}
```