data

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 22, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func QuestionCriteria

func QuestionCriteria[T CriteriaScore | CriteriaOthers](q Question) (T, bool)

QuestionCriteria type-asserts a Question's criteria into the wanted concrete type. Use QuestionCriteria[CriteriaScore](q) for a "score" question or QuestionCriteria[CriteriaOthers](q) for a "noul"/"choice" one; ok is false if q.Criteria isn't that type (e.g. wrong T for q.Type).

Types

type Answer

type Answer struct {
	Type QuestionType `json:"type"`

	// Choice is the winning key, set for "choice" questions.
	Choice string `json:"choice,omitempty"`
	// Score is the position on the scale, set for "score" questions.
	Score *float64 `json:"score,omitempty"`
	// Noul is the yes/no reading, set for "noul" questions.
	Noul *float64 `json:"noul,omitempty"`

	// Confidence is how sure the model is of this answer.
	Confidence *float64 `json:"confidence,omitempty"`
	// Legend maps a "score" answer's scale positions back to their labels.
	Legend map[string]string `json:"legend,omitempty"`
	// Probabilities is the spread the answer was picked from.
	Probabilities map[string]float64 `json:"probabilities,omitempty"`
}

Answer is one judged question. Which fields are set depends on Type: "choice" fills Choice, "score" fills Score and Legend, "noul" fills Noul. Score, Noul and Confidence are pointers because 0 is a meaningful value for each of them and has to be told apart from "not returned".

type CriteriaOthers

type CriteriaOthers map[string]string

CriteriaOthers is the criteria shape for "noul" and "choice" questions: a set of possible answers mapped to the description that qualifies them.

type CriteriaScore

type CriteriaScore []string

CriteriaScore is the criteria shape for "score" questions: an ordered list of labels from lowest to highest, e.g. ["Calm", "Frustrated", "Very angry"].

type Field

type Field struct {
	Name        string `json:"name"`
	Type        string `json:"type"`
	Unit        string `json:"unit,omitempty"`
	Description string `json:"description,omitempty"`
}

Field describes the value a question is asked about. It is stored under the "field" key rather than being a member of Instructions, because not every question is about a field.

type Instructions

type Instructions struct {
	Question string
	Extra    map[string]json.RawMessage
	// contains filtered or unexported fields
}

Instructions is what a Question asks. On the wire it is either a bare string or an object with a required "question" key plus any number of context keys ("field", "extracted_value", "compare", "focus", "note", ...).

Question is the one key that is always present, so it is typed. Everything else is kept as raw JSON in Extra: the context keys are an open set, and keys this library doesn't know about must survive a decode/encode round trip untouched.

func Ask

func Ask(question string) Instructions

Ask starts Instructions from the question text. Used alone it encodes as a bare JSON string; each With* call adds a context key and switches the encoding to an object.

func (Instructions) Decode

func (i Instructions) Decode(key string, v any) error

Decode unmarshals the value stored under key into v, e.g. var f Field; err := ins.Decode("field", &f).

Example

ExampleInstructions_Decode shows reading structured instructions back off the wire, including a context key this library has no helper for.

package main

import (
	"bytes"
	"encoding/json"
	"fmt"

	"github.com/leonardjke/go-jev/data"
)

func main() {
	raw := []byte(`{
		"type": "noul",
		"instructions": {
			"question": "Does ` + "`extracted_value`" + ` match the ` + "`field`" + `?",
			"field": {"name": "amount_due", "type": "number", "unit": "USD"},
			"extracted_value": 4471,
			"tolerance": {"percent": 0.5}
		}
	}`)

	var q data.Question
	if err := json.Unmarshal(raw, &q); err != nil {
		panic(err)
	}

	var f data.Field
	if err := q.Instructions.Decode("field", &f); err != nil {
		panic(err)
	}
	fmt.Printf("question: %s\n", q.Instructions.Question)
	fmt.Printf("field: %s (%s, %s)\n", f.Name, f.Type, f.Unit)

	// Keys with no typed helper are still readable and still round-trip.
	tolerance, _ := q.Instructions.Get("tolerance")
	fmt.Printf("tolerance: %s\n", tolerance)

	out, err := json.Marshal(q.Instructions)
	if err != nil {
		panic(err)
	}

	// The encoder emits compact JSON on one line; it is indented here only so
	// the expected output below stays readable and within the line limit.
	var pretty bytes.Buffer
	if err := json.Indent(&pretty, out, "", "  "); err != nil {
		panic(err)
	}
	fmt.Printf("re-encoded:\n%s\n", &pretty)
}
Output:
question: Does `extracted_value` match the `field`?
field: amount_due (number, USD)
tolerance: {"percent": 0.5}
re-encoded:
{
  "extracted_value": 4471,
  "field": {
    "name": "amount_due",
    "type": "number",
    "unit": "USD"
  },
  "question": "Does `extracted_value` match the `field`?",
  "tolerance": {
    "percent": 0.5
  }
}

func (Instructions) Err

func (i Instructions) Err() error

Err reports the first failure from a Set or With* call on these Instructions. The fluent chain stays expression-shaped, so the error is carried here rather than returned; callers assembling a request report it alongside the key of the question it belongs to.

func (Instructions) Get

func (i Instructions) Get(key string) (json.RawMessage, bool)

Get returns the raw JSON stored under key.

func (Instructions) MarshalJSON

func (i Instructions) MarshalJSON() ([]byte, error)

MarshalJSON encodes a bare string when there is no context, an object otherwise, so simple questions stay simple on the wire.

func (Instructions) Set

func (i Instructions) Set(key string, value any) Instructions

Set stores value under an arbitrary key. It is the escape hatch for context keys this library has no typed helper for yet.

func (*Instructions) UnmarshalJSON

func (i *Instructions) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts either form, keeping unrecognized context keys in Extra.

func (Instructions) WithCompare

func (i Instructions) WithCompare(paths ...string) Instructions

WithCompare sets "compare": the state paths the question weighs against each other, e.g. "ticket.sender.display_name".

func (Instructions) WithExtractedValue

func (i Instructions) WithExtractedValue(value any) Instructions

WithExtractedValue sets "extracted_value": the candidate value to check the field against.

func (Instructions) WithField

func (i Instructions) WithField(f Field) Instructions

WithField sets "field": the value being judged.

func (Instructions) WithFocus

func (i Instructions) WithFocus(focus string) Instructions

WithFocus sets "focus": what the judge should look at.

func (Instructions) WithNote

func (i Instructions) WithNote(note string) Instructions

WithNote sets "note": a caveat for how to judge.

type Question

type Question struct {
	Type         QuestionType `json:"type"`
	Instructions Instructions `json:"instructions"`
	Criteria     any          `json:"criteria,omitempty"`
}

func (*Question) UnmarshalJSON

func (q *Question) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes Criteria into the concrete type that matches Type (CriteriaScore for "score", CriteriaOthers for "noul"/"choice"), so callers get a typed value back from QuestionCriteria instead of asserting on `any`.

type QuestionType

type QuestionType string
const (
	QuestionNoul   QuestionType = "noul"
	QuestionChoice QuestionType = "choice"
	QuestionScore  QuestionType = "score"
)

type Request

type Request struct {
	Model     string              `json:"model"`
	State     string              `json:"state"`
	Questions map[string]Question `json:"questions"`
}
Example

ExampleRequest covers the four instruction shapes at the wire level: plain text, a field plus the value extracted for it, a field on its own, and a question with extra guidance for the judge. Callers assembling a request normally use the constructors in the jev package instead; this is the layer underneath them.

package main

import (
	"encoding/json"
	"fmt"

	"github.com/leonardjke/go-jev/data"
)

func main() {
	req := data.Request{
		Model: "jev-latest",
		State: "...",
		Questions: map[string]data.Question{
			// Plain text — encodes as a bare JSON string.
			"department": {
				Type:         data.QuestionChoice,
				Instructions: data.Ask("Which team should handle this"),
				Criteria: data.CriteriaOthers{
					"billing":   "Payment or subscription issues",
					"technical": "Bugs or integration problems",
				},
			},

			// Field + the value we extracted for it.
			"invoice_number_matches": {
				Type: data.QuestionNoul,
				Instructions: data.Ask("Does `extracted_value` match the `field` as it appears in `source_text`?").
					WithField(data.Field{
						Name:        "invoice_number",
						Type:        "string",
						Description: "The identifier printed on the invoice.",
					}).
					WithExtractedValue("4471"),
			},

			// Field with a unit, judged on a scale.
			"amount_due_size": {
				Type: data.QuestionScore,
				Instructions: data.Ask("How large is the `field` value in `source_text`?").
					WithField(data.Field{
						Name:        "amount_due",
						Type:        "number",
						Unit:        "USD",
						Description: "The total the invoice asks to be paid.",
					}),
				Criteria: data.CriteriaScore{"Small", "Typical", "Unusually large"},
			},

			// State paths to weigh against each other, plus what to focus on.
			"sender_mismatch": {
				Type: data.QuestionNoul,
				Instructions: data.Ask("Does the claimed sender identity conflict with the sending domain?").
					WithCompare("ticket.sender.display_name", "ticket.sender.email").
					WithFocus("Compare the named organization with the email domain."),
			},

			// A caveat for how to judge.
			"pr_focus": {
				Type: data.QuestionScore,
				Instructions: data.Ask("How focused is this pull request description on a single change?").
					WithNote("Judge the number of independent changes, not the size of any one change."),
				Criteria: data.CriteriaScore{"One change", "A few related changes", "Unrelated changes"},
			},
		},
	}

	out, err := json.MarshalIndent(req, "", "  ")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(out))
}
Output:
{
  "model": "jev-latest",
  "state": "...",
  "questions": {
    "amount_due_size": {
      "type": "score",
      "instructions": {
        "field": {
          "name": "amount_due",
          "type": "number",
          "unit": "USD",
          "description": "The total the invoice asks to be paid."
        },
        "question": "How large is the `field` value in `source_text`?"
      },
      "criteria": [
        "Small",
        "Typical",
        "Unusually large"
      ]
    },
    "department": {
      "type": "choice",
      "instructions": "Which team should handle this",
      "criteria": {
        "billing": "Payment or subscription issues",
        "technical": "Bugs or integration problems"
      }
    },
    "invoice_number_matches": {
      "type": "noul",
      "instructions": {
        "extracted_value": "4471",
        "field": {
          "name": "invoice_number",
          "type": "string",
          "description": "The identifier printed on the invoice."
        },
        "question": "Does `extracted_value` match the `field` as it appears in `source_text`?"
      }
    },
    "pr_focus": {
      "type": "score",
      "instructions": {
        "note": "Judge the number of independent changes, not the size of any one change.",
        "question": "How focused is this pull request description on a single change?"
      },
      "criteria": [
        "One change",
        "A few related changes",
        "Unrelated changes"
      ]
    },
    "sender_mismatch": {
      "type": "noul",
      "instructions": {
        "compare": [
          "ticket.sender.display_name",
          "ticket.sender.email"
        ],
        "focus": "Compare the named organization with the email domain.",
        "question": "Does the claimed sender identity conflict with the sending domain?"
      }
    }
  }
}

type Response

type Response struct {
	Model   string            `json:"model"`
	Answers map[string]Answer `json:"answers"`
	Usage   Usage             `json:"usage"`
}

Response is what the API answers a Request with. Answers is keyed by the same keys as Request.Questions.

type Usage

type Usage struct {
	InputTokens  int `json:"input_tokens"`
	OutputTokens int `json:"output_tokens"`
}

Usage reports what the request cost.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL