Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions zig/src/server/slide_prompts.zig
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ pub const judge_prompt = "Here is something you were told: \"{s}\" Using only th
pub const again_prompt = "Earlier the user told you: \"{s}\" Now the user tells you: \"{s}\" Is the new message only saying the same thing again, changing and taking back nothing? Reply yes or no.";
/// Whether a new message from the user takes back what they told before.
pub const replace_prompt = "Earlier the user told you: \"{s}\" Now the user tells you: \"{s}\" Does the new message change or take back what they told you earlier? Reply yes or no.";
/// Whether an earlier message still holds after a new one ("I live in Leeds now." after "I live in Bristol.").
pub const still_prompt = "Earlier the user told you: \"{s}\" Now the user tells you: \"{s}\" Is what they told you earlier still true? Reply yes or no.";
/// The 1.0.7 judge for what the user told about themselves, which counts liking blue as their favourite colour.
pub const told_judge_prompt = "The user told you this about themselves or their work: \"{s}\" Does it answer this question they ask you: \"{s}\"? Reply yes or no.";
pub const facts_prompt = "Read the text below and list the facts in it worth remembering later: names, numbers, versions, dates, times, tools, decisions and news. Write one fact per line. Split a sentence that holds several facts into one line for each (a day and a time are two facts), and name who or what each line is about in full, putting the name of the organisation, place or person in place of words like we, our, it, they, the company or the team, and naming the organisation a line belongs to even when the text leaves it implied (Acme code reviews, Acme employees). Write each fact about a person once more with the person first. Keep what each named thing or person is (a product's kind, a person's role). For example, from this text:\nAcme handbook. Acme was founded in 2010 in Leeds by Jo Smith, who is still our chief executive. We build Widgetly, a planning app for schools. Our support desk opens at 9 am every weekday. Every code review needs one approval. We track bugs in Linear.\nwrite:\nAcme was founded in 2010.\nAcme was founded in Leeds.\nAcme was founded by Jo Smith.\nJo Smith founded Acme.\nThe Acme chief executive is Jo Smith.\nJo Smith is the Acme chief executive.\nAcme builds Widgetly.\nWidgetly is a planning app for schools.\nThe Acme support desk opens at 9 am.\nThe Acme support desk opens every weekday.\nAn Acme code review needs one approval.\nAcme tracks bugs in Linear.\nOne fact per line, nothing else.\n\n{s}";
Expand Down
30 changes: 22 additions & 8 deletions zig/src/server/slide_replace.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ const prompts = @import("slide_prompts.zig");
const slide_ask = @import("slide_ask.zig");
const slide_graph = @import("slide_graph.zig");
const side = @import("slide_side.zig");
const take = @import("slide_take.zig");
const Server = @import("server.zig").Server;
const Allocator = std.mem.Allocator;
const Cx = errors.Cx;

const candidates = 32; // learned chat facts a new one is checked against, the latest first

/// The learned chat facts `fact` replaces: it answers one of their questions, or changes or takes back what they say.
/// The learned chat facts `fact` replaces: it changes or takes back what they say, or they no longer hold.
pub fn replaced(srv: *Server, cx: *Cx, fact: []const u8, id: u32, gone: anytype) ![]const slide_graph.Fact {
const a = cx.a;
const olds = try srv.slide.learnedChat(a, id, candidates);
Expand All @@ -33,13 +32,11 @@ pub fn replaced(srv: *Server, cx: *Cx, fact: []const u8, id: u32, gone: anytype)
const old = j.olds[i];
// "Now I like red." after "My favourite colour is red." says it again: the earlier one stays
const same = try slide_ask.askBase(j.srv, &jcx, null, try std.fmt.allocPrint(j.a, prompts.again_prompt, .{ old.text, j.fact }), 4, j.gone);
if (std.ascii.startsWithIgnoreCase(std.mem.trim(u8, same.content, " \t\r\n\"*"), "yes")) return;
for (old.questions) |q| if (try take.judged(j.srv, &jcx, j.fact, q, false, j.gone)) {
j.yes[i] = true;
return;
};
if (saysYes(same.content)) return;
const said = try slide_ask.askBase(j.srv, &jcx, null, try std.fmt.allocPrint(j.a, prompts.replace_prompt, .{ old.text, j.fact }), 4, j.gone);
j.yes[i] = std.ascii.startsWithIgnoreCase(std.mem.trim(u8, said.content, " \t\r\n\"*"), "yes");
// a model may say "My favourite colour is red." changes nothing in "I like blue.", yet not that it holds
const still = if (saysYes(said.content)) "" else (try slide_ask.askBase(j.srv, &jcx, null, try std.fmt.allocPrint(j.a, prompts.still_prompt, .{ old.text, j.fact }), 4, j.gone)).content;
j.yes[i] = givesWay(same.content, said.content, still);
}
};
try side.each(olds.len, &Job{ .srv = srv, .a = a, .fact = fact, .olds = olds, .gone = gone, .yes = yes });
Expand All @@ -50,3 +47,20 @@ pub fn replaced(srv: *Server, cx: *Cx, fact: []const u8, id: u32, gone: anytype)
};
return out.items;
}

/// Whether an earlier fact gives way, from the model's replies: not said again, and changed or no longer true.
fn givesWay(again: []const u8, changes: []const u8, still: []const u8) bool {
return !saysYes(again) and (saysYes(changes) or std.ascii.startsWithIgnoreCase(std.mem.trim(u8, still, " \t\r\n\"*"), "no"));
}

fn saysYes(reply: []const u8) bool {
return std.ascii.startsWithIgnoreCase(std.mem.trim(u8, reply, " \t\r\n\"*"), "yes");
}

test "an earlier fact gives way when changed or no longer true, not for answering the same kind of question" {
try std.testing.expect(givesWay("No.", "**Yes**", ""));
try std.testing.expect(givesWay("No", "No", "No.")); // "My favourite colour is red." after "I like blue."
try std.testing.expect(!givesWay("No", "No", "Yes")); // "My GitHub name is alex77." after "My name is Alex."
try std.testing.expect(!givesWay("Yes", "Yes", "No"));
try std.testing.expect(!givesWay("No", "No", "")); // no reply is no answer
}
31 changes: 31 additions & 0 deletions zig/src/server/slide_subject.zig
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,25 @@ fn opens(text: []const u8, word: []const u8) bool {

/// A fact's subject words: the phrase's its questions say; a document's other proper names join a phrase with none.
pub fn factSubject(a: std.mem.Allocator, phrase: []const u8, fact: []const u8, qs: []const []const u8, document: bool) ![]const []const u8 {
const words = try phraseSubject(a, phrase, fact, qs, document);
// a told fact's subject most of its open questions never say is its answer ("I contribute to TensorFold."): none
if (!document and !try namedByHalf(a, words, fact, qs)) return &.{};
return words;
}

/// Whether at least half of the questions that do not ask yes or no name the subject (yes-or-no ones say the fact).
fn namedByHalf(a: std.mem.Allocator, words: []const []const u8, fact: []const u8, qs: []const []const u8) !bool {
const proper = try named(a, words, fact, qs);
var open: usize = 0;
var said: usize = 0;
for (qs) |q| if (!whom.yesNo(q)) {
open += 1;
said += @intFromBool(names(proper, words, q));
};
return 2 * said >= open;
}

fn phraseSubject(a: std.mem.Allocator, phrase: []const u8, fact: []const u8, qs: []const []const u8, document: bool) ![]const []const u8 {
const own = try subjectWords(a, beforeVerb(phrase));
// a told fact named whole ("I drive a Volvo") keys on what its questions say (drive), never its answer
if (!document and whom.userOpens(phrase)) return asked(a, own, qs);
Expand Down Expand Up @@ -243,6 +262,18 @@ test "a fact's subject keeps its own proper names; a phrase without any takes th
try std.testing.expect(allergic.len == 1 and std.mem.eql(u8, allergic[0], "allergic"));
}

test "a told fact's subject that most of its open questions never name is none: it keys on the reply" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const a = arena.allocator();
const fact = "I contribute to TensorFold.";
const qs = [_][]const u8{ "What do I contribute to?", "Which project do I contribute to?", "Do I contribute to TensorFold?", "Is TensorFold something I work on?", "What is the name of the project I support?" };
try std.testing.expectEqual(@as(usize, 0), (try factSubject(a, "TensorFold", fact, &qs, false)).len);
const told = [_][]const u8{ "What is my dog called?", "Is my dog called Ginger?", "What did I name my dog?", "What is the name of my pet?" };
const dog = try factSubject(a, "My dog", "My dog is called Ginger.", &told, false);
try std.testing.expect(dog.len == 1 and std.mem.eql(u8, dog[0], "dog"));
}

const unkeyed = [_][]const u8{ "i", "me", "my", "mine", "myself", "we", "us", "our", "ours", "you", "your", "yours", "it", "its", "this", "that", "these", "those", "what", "which", "who", "whom", "whose", "where", "when", "why", "how", "do", "does", "did", "is", "are", "am", "was", "like", "likes" };

const small = [_][]const u8{ "the", "a", "an", "of", "on", "in", "at", "to", "for", "by", "with", "from", "and", "or" };
Expand Down
Loading