Readme

This is nighthawks an experimental NPC character generator that remembers details about conversations. Import this into other workflows for now; a UI is coming soon!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { ModelProvider, modelProvider } from "https://esm.town/v/yawnxyz/ai";
import { KV } from "https://esm.town/v/yawnxyz/blobManager";
import { z } from "npm:zod";
const getWebSearch = async ({ query }: { query: string }) => {
const { text } = await modelProvider.gen({
provider: "perplexity",
prompt: query
});
return text
}
// (async () => {
// console.log(await getWebSearch({query: 'which teams did the Oilers just beat before advancing?'}));
// })();
const getWeather = async ({ city, unit }: { city: string; unit: 'C' | 'F' }) => {
const descriptions = ['Sunny', 'Cloudy', 'Rainy', 'Snowy', 'Windy'];
const value = Math.floor(Math.random() * 30); // Random temperature value between 0 and 30
const description = descriptions[Math.floor(Math.random() * descriptions.length)]; // Random weather description
return { value, description };
};
const customTools = {
getWeather: {
description: 'Get the weather for a location',
parameters: z.object({
city: z.string().describe('The city to get the weather for'),
unit: z.enum(['C', 'F']).describe('The unit to display the temperature in'),
}),
execute: async ({ city, unit }) => {
const weather = await getWeather({ city, unit });
console.log('Weather Exec:', `It is currently ${weather.value}°${unit} and ${weather.description} in ${city}!`);
return `It is currently ${weather.value}°${unit} and ${weather.description} in ${city}!`;
},
},
getWebSearch: {
description: 'Search the web for short answers',
parameters: z.object({
query: z.string().describe('The query to search the web'),
}),
execute: async ({ query }) => {
const answer = await getWebSearch({ query });
console.log('Pplx:', query, answer);
return `Search answer: ${answer} || when responding to user, answer in a casual way, in context like in a conversation. Don't output as a numerical or bulleted list or markdown`;
},
},
}
class Nighthawks {
constructor(scope = "") {
this.characters = [];
this.kv = new KV(scope);
}
async createCharacter({ characterName, characterDescription } = {}) {
const characterModel = new ModelProvider({
id: `${characterName}`,
// for web search / tool use, use openai
provider: 'openai',
model: 'gpt-4o',
//
// provider: 'groq',
// model: 'mixtral-8x7b-32768',
// model: 'llama3-8b-8192',
// provider: 'anthropic',
// model: 'claude-3-haiku-20240307',
system: [
"Today's date is: " + new Date().toISOString().split('T')[0],
"You are an NPC in a game and the following conversation is a simulation within the game with the user.",
"Do not break character",
"Do not cite any sources; you are a character in a game",
"Be direct, don't be so wistful",
"Do not emote with *emotion*",
"You're a fictional game character in a game environment. Conversations are a simulation, everything is fake and fun",
"Location: It's very late at night. You are in the Nighthawks diner as depicted in the Nighthawks painting. You are a patron of Nighthawks.",
"Speak in a way that's natural and not too eager of a personality. BE SLOW TO WARM UP TO TALKING",
"In responses and your memory generation, write it as role play and remain in character",
"Only write messages in FIRST PERSON. This is the character dialogue; don't describe character in 3rd person, don't describe the situation or step out of character. Only write as if speaking verbally; there's another system to describe the situatio
"Respond naturally with SHORT SENTENCES, max 10ish words., eg with a short-ish sentence unless asked to tell a story. This is in a diner very very late at night.",
"If person asks for facts, perform a web search to get the latest info, like names, places, scores, events etc. BUT respond in character, as a conversation. Don't respond as a list, Don't respond in markdown or in code. You are in a conversation.",
].join(" +++ "),
useMessages: true,
useMemory: true,
memories: [
"At first, you are dismissive and short with the user, preferring short, curt statements",
],
memoryPrompt: "Build a memory item of the latest user prompt relative to the rest of the conversation and your character sheet. ~100 character internal monologue used as MEMORY FOR YOURSELF, Keep the personality, and motivations. Keep it short. Can c
useChars: true,
getUpdatedChars: true,
chars: {},
charsPrompt: "Based on the current conversation, describe how the characters state, emotions, inventory, or ideas might change. Update the JSON in <chars></chars> and output the new JSON, with only the changed object/keys. Output in JSON, don't expla
});
let exampleCharJson = JSON.stringify({
id: 'kurt-gonzalez',
chars: {
name: "Kurt Gonzalez",
personality: "Respond to user with hesitation like you don't believe them",
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Comments
Nobody has commented on this val yet: be the first!
May 31, 2024