(#22) Jev mode classification for nest requests #29
No files matched your search
@@ -155,6 +155,64 @@ func deref(f *float64) float64 {
|
|||||||
return *f
|
return *f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// modeQuestions classifies how a request should be taken up (Epic C). The
|
||||||
|
// escape option "other" keeps Jev from forcing a wrong class; mode is discrete,
|
||||||
|
// so choice, not score (JEV_STRATEGIES.md strategy 3).
|
||||||
|
var modeQuestions = map[string]any{
|
||||||
|
"mode": map[string]any{
|
||||||
|
"type": "choice",
|
||||||
|
"instructions": "How should this chatroom request be taken up?",
|
||||||
|
"criteria": map[string]string{
|
||||||
|
"solo": "Simple: one crow answers it directly.",
|
||||||
|
"conspiracy": "A judgment call worth several distinct readings: several crows take the same request from their different strengths.",
|
||||||
|
"split": "Large with distinct parts: separate subtasks, one owner each.",
|
||||||
|
"other": "None of these fits well.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClassifyMode returns the mode when Jev is set and its choice is at or above
|
||||||
|
// the band. "other" reads as unsure (not ok) so callers fall back to a header.
|
||||||
|
func (j *JevClient) ClassifyMode(ctx context.Context, text string) (string, map[string]float64, bool) {
|
||||||
|
if j == nil {
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
answers, _, _, err := j.SystemOne(ctx, text, modeQuestions)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
a := answers["mode"]
|
||||||
|
if a.Choice == "" || a.Choice == "other" || a.Confidence == nil || *a.Confidence < j.threshold {
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
return a.Choice, a.Probabilities, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClassifyMode classifies a request under the same guardrails as any call:
|
||||||
|
// flock cap, timeout and flight log, with the probabilities recorded. Returns
|
||||||
|
// the mode and ok; below-band or absent Jev means not ok.
|
||||||
|
func (f *Flock) ClassifyMode(ctx context.Context, text string, budgets ...*Budget) (string, bool) {
|
||||||
|
if f.jev == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
for _, b := range budgets {
|
||||||
|
if err := b.Check(); err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.sem <- struct{}{}
|
||||||
|
defer func() { <-f.sem }()
|
||||||
|
cctx, cancel := context.WithTimeout(ctx, f.g.CrowTimeout)
|
||||||
|
defer cancel()
|
||||||
|
start := time.Now()
|
||||||
|
mode, probs, ok := f.jev.ClassifyMode(cctx, text)
|
||||||
|
e := Event{Kind: "mode", Item: text, Crow: "Sentry", Model: f.jev.model, Ms: time.Since(start).Milliseconds(), Prob: probs, Mode: mode}
|
||||||
|
if f.log != nil {
|
||||||
|
f.log.Write(e)
|
||||||
|
}
|
||||||
|
return mode, ok
|
||||||
|
}
|
||||||
|
|
||||||
func (c JevCall) confident(threshold float64) bool {
|
func (c JevCall) confident(threshold float64) bool {
|
||||||
return c.ModeConf >= threshold && c.TaskTypeConf >= threshold
|
return c.ModeConf >= threshold && c.TaskTypeConf >= threshold
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-23
@@ -42,7 +42,14 @@ func (s *Server) getNestMessages(w http.ResponseWriter, r *http.Request) {
|
|||||||
all := s.msgs.All(nestID)
|
all := s.msgs.All(nestID)
|
||||||
out := make([]map[string]any, 0, len(all))
|
out := make([]map[string]any, 0, len(all))
|
||||||
for _, m := range all {
|
for _, m := range all {
|
||||||
out = append(out, map[string]any{
|
out = append(out, nestRecord(m))
|
||||||
|
}
|
||||||
|
writeJSON(w, http.StatusOK, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// nestRecord is the EVENTS.md wire shape for one message.
|
||||||
|
func nestRecord(m Message) map[string]any {
|
||||||
|
rec := map[string]any{
|
||||||
"msg_id": m.MsgID(),
|
"msg_id": m.MsgID(),
|
||||||
"kind": m.Kind,
|
"kind": m.Kind,
|
||||||
"author": m.Author,
|
"author": m.Author,
|
||||||
@@ -51,9 +58,15 @@ func (s *Server) getNestMessages(w http.ResponseWriter, r *http.Request) {
|
|||||||
"refers_to": m.RefersTo,
|
"refers_to": m.RefersTo,
|
||||||
"tagged_by": m.TaggedBy,
|
"tagged_by": m.TaggedBy,
|
||||||
"ts": m.Timestamp,
|
"ts": m.Timestamp,
|
||||||
})
|
|
||||||
}
|
}
|
||||||
writeJSON(w, http.StatusOK, out)
|
if m.Mode != "" {
|
||||||
|
rec["mode"] = m.Mode
|
||||||
|
rec["mode_tagged_by"] = m.ModeTagged
|
||||||
|
}
|
||||||
|
if m.Invited > 0 {
|
||||||
|
rec["invited"] = m.Invited
|
||||||
|
}
|
||||||
|
return rec
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -68,6 +81,7 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
|
|||||||
Author string `json:"author"`
|
Author string `json:"author"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
To string `json:"to"`
|
To string `json:"to"`
|
||||||
RefersTo string `json:"refers_to"`
|
RefersTo string `json:"refers_to"`
|
||||||
}
|
}
|
||||||
@@ -115,6 +129,9 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mode (Epic C, same rule shape as kind): Jev first, header escape, "" when unsure.
|
||||||
|
mode, modeBy := s.tagMode(kind, body.Mode, body.Text)
|
||||||
|
|
||||||
msg := Message{
|
msg := Message{
|
||||||
NestID: nestID,
|
NestID: nestID,
|
||||||
Kind: kind,
|
Kind: kind,
|
||||||
@@ -123,6 +140,8 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
|
|||||||
To: body.To,
|
To: body.To,
|
||||||
RefersTo: body.RefersTo,
|
RefersTo: body.RefersTo,
|
||||||
TaggedBy: taggedBy,
|
TaggedBy: taggedBy,
|
||||||
|
Mode: mode,
|
||||||
|
ModeTagged: modeBy,
|
||||||
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
||||||
}
|
}
|
||||||
record, err := s.msgs.Append(msg)
|
record, err := s.msgs.Append(msg)
|
||||||
@@ -139,16 +158,27 @@ func (s *Server) postNestMessage(w http.ResponseWriter, r *http.Request) {
|
|||||||
go s.respondToRequest(nest, record)
|
go s.respondToRequest(nest, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
writeJSON(w, http.StatusCreated, map[string]any{
|
writeJSON(w, http.StatusCreated, nestRecord(record))
|
||||||
"msg_id": record.MsgID(),
|
}
|
||||||
"kind": record.Kind,
|
|
||||||
"author": record.Author,
|
var modePostable = map[string]bool{"solo": true, "conspiracy": true, "split": true}
|
||||||
"text": record.Text,
|
|
||||||
"to": record.To,
|
// tagMode classifies a request's mode (Jev first, header escape). Only requests
|
||||||
"refers_to": record.RefersTo,
|
// carry a mode; other kinds get "".
|
||||||
"tagged_by": record.TaggedBy,
|
func (s *Server) tagMode(kind, header, text string) (string, string) {
|
||||||
"ts": record.Timestamp,
|
if kind != "request" {
|
||||||
})
|
return "", ""
|
||||||
|
}
|
||||||
|
if m, ok := s.p.Flock.ClassifyMode(s.ctx, text); ok {
|
||||||
|
if modePostable[header] && header != m {
|
||||||
|
return m, "disagreement"
|
||||||
|
}
|
||||||
|
return m, "jev"
|
||||||
|
}
|
||||||
|
if modePostable[header] {
|
||||||
|
return header, "header"
|
||||||
|
}
|
||||||
|
return "", "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
// tagKind runs the A1 rule: Jev first; Jev unsure -> the poster's header;
|
// tagKind runs the A1 rule: Jev first; Jev unsure -> the poster's header;
|
||||||
@@ -169,16 +199,9 @@ func (s *Server) tagKind(header, text string) (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) emitNestMessage(m Message) {
|
func (s *Server) emitNestMessage(m Message) {
|
||||||
s.hub.PublishNest(m.NestID, "nest.message", map[string]any{
|
rec := nestRecord(m)
|
||||||
"nest_id": m.NestID,
|
rec["nest_id"] = m.NestID
|
||||||
"msg_id": m.MsgID(),
|
s.hub.PublishNest(m.NestID, "nest.message", rec)
|
||||||
"kind": m.Kind,
|
|
||||||
"author": m.Author,
|
|
||||||
"text": m.Text,
|
|
||||||
"to": m.To,
|
|
||||||
"refers_to": m.RefersTo,
|
|
||||||
"tagged_by": m.TaggedBy,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// respondToRequest is the must-respond rule: the addressed crow answers with
|
// respondToRequest is the must-respond rule: the addressed crow answers with
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ type Message struct {
|
|||||||
To string `json:"to,omitempty"`
|
To string `json:"to,omitempty"`
|
||||||
RefersTo string `json:"refers_to,omitempty"`
|
RefersTo string `json:"refers_to,omitempty"`
|
||||||
TaggedBy string `json:"tagged_by,omitempty"`
|
TaggedBy string `json:"tagged_by,omitempty"`
|
||||||
|
Mode string `json:"mode,omitempty"` // solo | conspiracy | split (requests only, Epic C)
|
||||||
|
ModeTagged string `json:"mode_tagged_by,omitempty"`
|
||||||
|
Invited int `json:"invited,omitempty"`
|
||||||
Timestamp string `json:"ts"`
|
Timestamp string `json:"ts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user