lib.rsannotatedlib.rssource108 lines · 4.5 KB · raw

What Jev is asked, and the rules that turn its answers into what happens.

Three judgments, each a module: a shell command before it runs ([command]), a turn before it ends ([stop]), and a prompt before its turn starts ([model]). Each is the same three things:

  • Facts: what can be known. Most are Jev's to give, and a threshold is applied where an answer is read, so a fact is a plain yes or no (or one of a few named values) and a rule can test it.
  • Questions: how Jev is asked for those facts (jev_facts::Source). Every word that matters is defined in the option or level it belongs to, as concrete acts, never as an adjective.
  • Rules: tests on facts and what to do when they hold, compiled into a rete network. Order is priority. The network is what decides, and it is what the website draws, so the picture cannot show other rules than the ones that run.

Nothing here does I/O or is async. The daemon asks a network what to do next, does it (asks Jev, measures memory), records what it learned, and asks again.

21#![forbid(unsafe_code)]
23use jev_protocol::{ModelId, ProtocolError};
24
25pub mod command;
26pub mod model;
27pub mod stop;

The Jev model every question goes to. Pinned, and pinned here, because the thresholds in this crate were set against this model's answers.

31pub const MODEL: &str = "jev-1.13.0";

[MODEL] as the protocol's type.

34pub fn jev_model() -> Result<ModelId, ProtocolError> {
35    ModelId::pinned(MODEL)
36}

How much of a command, a prompt or a final message Jev is shown.

39pub(crate) const SHOWN_CHARS: usize = 6000;

A domain with no effect, or no note, says so with this: it has no value.

42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub enum Never {}

The first count characters of text.

46pub fn head(text: &str, count: usize) -> &str {
47    text.char_indices().nth(count).map_or(text, |(at, _)| &text[..at])
48}

The last count characters of text.

51pub fn tail(text: &str, count: usize) -> &str {
52    let skip = text.chars().count().saturating_sub(count);
53    text.char_indices().nth(skip).map_or("", |(at, _)| &text[at..])
54}
56pub(crate) fn percent(probability: f64) -> f64 {
57    (probability * 100.0).round()
58}

A Score's answer as the response body carries it, for answers made up in a test: score spread over the two levels either side of it.

62#[cfg(feature = "made-up")]
63pub(crate) fn made_up_score(score: f64, levels: usize) -> String {
64    let score = score.clamp(0.0, (levels - 1) as f64);
65    let below = (score.floor() as usize).min(levels - 1);
66    let share = score - below as f64;
67    let probability = |level: usize| match level {
68        l if l == below => 1.0 - share,
69        l if l == below + 1 => share,
70        _ => 0.0,
71    };
72    let each = |value: &dyn Fn(usize) -> String| (0..levels).map(|level| format!(r#""{level}":{}"#, value(level))).collect::<Vec<_>>().join(",");
73    format!(
74        r#"{{"type":"score","score":{score},"confidence":1.0,"legend":{{{}}},"probabilities":{{{}}}}}"#,
75        each(&|level| format!(r#""level {level}""#)),
76        each(&|level| probability(level).to_string()),
77    )
78}

A Choice's answer as the response body carries it, for answers made up in a test: the likeliest option is the choice.

82#[cfg(feature = "made-up")]
83pub(crate) fn made_up_choice(probabilities: &[(&str, f64)]) -> String {
84    let (choice, confidence) = probabilities.iter().copied().max_by(|a, b| a.1.total_cmp(&b.1)).unwrap_or(("", 0.0));
85    let each = probabilities.iter().map(|(label, p)| format!(r#""{label}":{p}"#)).collect::<Vec<_>>().join(",");
86    format!(r#"{{"type":"choice","choice":"{choice}","confidence":{confidence},"probabilities":{{{each}}}}}"#)
87}

A whole response body with these answers, for answers made up in a test.

90#[cfg(feature = "made-up")]
91pub(crate) fn made_up_body(answers: &[(&str, String)]) -> String {
92    let each = answers.iter().map(|(id, answer)| format!(r#""{id}":{answer}"#)).collect::<Vec<_>>().join(",");
93    format!(r#"{{"model":"{MODEL}","answers":{{{each}}},"usage":{{"input_tokens":0,"output_tokens":0}}}}"#)
94}
96#[cfg(test)]
97mod tests {
98    use super::*;

Commands and messages are clipped by characters, never bytes: a cut inside a multi-byte character would panic on real text.

102    #[test]
103    fn clipping_keeps_whole_characters() {
104        assert_eq!(head("héllo", 2), "hé");
105        assert_eq!(tail("héllo — ✓", 3), "— ✓");
106        assert_eq!(tail("ab", 5), "ab");
107    }
108}