Documentation
¶
Index ¶
- func QuestionCriteria[T CriteriaScore | CriteriaOthers](q Question) (T, bool)
- type Answer
- type CriteriaOthers
- type CriteriaScore
- type Field
- type Instructions
- func (i Instructions) Decode(key string, v any) error
- func (i Instructions) Err() error
- func (i Instructions) Get(key string) (json.RawMessage, bool)
- func (i Instructions) MarshalJSON() ([]byte, error)
- func (i Instructions) Set(key string, value any) Instructions
- func (i *Instructions) UnmarshalJSON(data []byte) error
- func (i Instructions) WithCompare(paths ...string) Instructions
- func (i Instructions) WithExtractedValue(value any) Instructions
- func (i Instructions) WithField(f Field) Instructions
- func (i Instructions) WithFocus(focus string) Instructions
- func (i Instructions) WithNote(note string) Instructions
- type Question
- type QuestionType
- type Request
- type Response
- type Usage
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 ¶
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 ¶
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?" } } } }