Public vals
24
xkonti
gptApiFramework
Script
Allows for automatic generation of Hono API compatible with GPTs. Endpoints' inputs and outputs need to be specified via types from which the Open API spec is generated automatically and available via /gpt/schema endpoint. ⚠️ Breaking changes introduced in v23 & 24: nothingToJson doesn't take the generic TResponse anymore. The type is inferred from the endpoint definition. The endpoint definition doesn't need the requestSchema , requestDesc and responseDesc defined anymore. The descriptions are inferred from the schema description. jsonToJson doesn't take the generic TRequest and TResponse anymore. Types are inferred from the endpoint definition. The endpoint definition doesn't need the requestDesc and responseDesc defined anymore. The descriptions are inferred from the schema description. Usage example: import { GptApi } from "https://esm.town/v/xkonti/gptApiFramework";
import { z } from "npm:zod";
/**
* COMMON TYPES
*/
const ResponseCommandSchema = z.object({
feedback: z.string().describe("Feedback regarding submitted action"),
command: z.string().describe("The command for the Mediator AI to follow strictly"),
data: z.string().optional().describe("Additional data related to the given command"),
}).describe("Contains feedback and further instructions to follow");
/**
* INITIALIZE API
*/
const api = new GptApi({
url: "https://xkonti-planoverseerai.web.val.run",
title: "Overseer AI API",
description: "The API for interacting with the Overseer AI",
version: "1.0.0",
policyGetter: async () => {
const { markdownToPrettyPage } = await import("https://esm.town/v/xkonti/markdownToHtmlPage?v=5");
return await markdownToPrettyPage("# Privacy Policy\n\n## Section 1...");
},
});
/**
* REQUIREMENTS GATHERING ENDPOINTS
*/
api.nothingToJson({
verb: "POST",
path: "/newproblem",
operationId: "new-problem",
desc: "Endpoint for informing Overseer AI about a new problem presented by the User",
responseSchema: ResponseCommandSchema,
responseDesc: "Instruction on how to proceed with the new problem",
}, async (ctx) => {
return {
feedback: "User input downloaded. Problem analysis is required.",
command: await getPrompt("analyze-problem"),
data: "",
};
});
export default api.serve();
2
xkonti
cache
Script
Implementation of Redis-like cache - a key-value store with expiring keys. Data is stored in the Val Town SQLite database and shared between all your vals. Setup First you should decide on a name of a SQL table that will be used for storing cache data. It could something like cacheData or kv . Set that value to a new Environment Variable CACHE_TABLE_NAME . Optionally you might add a new CACHE_DEFAULT_TTL Environment Variable. It's value should be set to a number of seconds that will be used when saving new values to the cache without providing the expiration time. By default it's 24h. The setup() function should be ran before using the cache for the first time. You can do that by creating a small temporary Val: import { setup } from "https://esm.town/v/xkonti/cache";
await setup(); Optionally create a scheduled val that will delete expired keys on some interval - 15 minutes can be a good start. import { deleteExpired } from "https://esm.town/v/xkonti/cache";
export default async function cacheCleaner(interval: Interval) {
await deleteExpired();
} Usage After setting your cache up you can use it simply by importing functions from https://esm.town/v/xkonti/cache . set(key, value, ttl): Promise Set a value in the cache. key - the key to set the value for value - the value to set - it can be any value that can be serialized to JSON ttl - the time to live in seconds. In other words, after how many seconds the key will expire. If not set, the default TTL is used. returns the number of keys set: 1 if the key was inserted/updated, 0 if the ttl was 0 or invalid // Set a value in the cache with a default TTL
await set("luckyNumber", 13);
// Set a value that will expire in 5 minutes
await set("product:344798", { name: "Audio Interface", price: 209.99}, 5 * 60); setUntil(key, value, expiresAt): Promise Set a value in the cache until a specific date and time. key - the key to set the value for value - the value to set - it can be any value that can be serialized to JSON expiresAt - the expiration time as a UTC date string returns the number of keys set: 1 if the key was inserted/updated, 0 if the `expiresAt`` was in the past // Set a value in the cache until 2024-01-01 16:23:05 UTC
await setUntil(
"product:155392",
{ name: "Audio Interface", price: 209.99 },
new Date('2024-01-01T16:23:05Z').toISOString()
); setExpiration(key, ttl): Promise Update the expiration date of a cache entry based on TTL. If the key does not exist or is expired, nothing happens. key - the key of the cache entry to update ttl - the time to live in seconds from now. In other words, after how many seconds the key will expire. If not set, the default TTL is used. returns the number of keys updated: 1 if updated, 0 if not found or ttl was 0 // Set the expiration date in the cache with a default TTL
await setExpiration("luckyNumber");
// Set the expiration date in the cache for 5 minutes from now.
await setExpiration("luckyNumber", 5 * 60); setExpirationUntil(key, expiresAt): Promise Update the expiration date of a cache entry to a specific UTC date and time. If the key does not exist or is expired, nothing happens. key - the key of the cache entry to update expiresAt - the expiration time as a UTC date string returns the number of keys updated: 1 if updated, 0 if not found or expiresAt was in the past // Set the expiration date in the cache until 2024-01-01 16:23:05 UTC
await setExpirationUntil(
"product:155392",
new Date('2024-01-01T16:23:05Z').toISOString()
); exists(key): Promise Checks if the provided key exists (has value) in the cache. If the key is expired, it's considered non-existent. key - the key to check for existence // Check if the key is present in the cache
const hasLuckyNumber: Boolean = await exists("luckyNumber"); get (key): Promise<T | null> Get a value from the cache by key. You can provide a type of the return value or it will default to unknown . If there is no value for the key or the value has expired, null is returned. key - the key to get the value for // Get a value from the cache
const luckyNumber: number = await get<number>("luckyNumber");
const luckyNumber: number = await get("luckyNumber") as number; // same as above listKeys(prefix): Promise<string[]> Gets a list of all non-expired keys in the cache that match the prefix. If no prefix is provided, all keys are returned. prefix - the optional prefix to match keys against // Get all keys from the cache
const keys: string[] = await listKeys();
// Get all keys from the cache that start with "product:"
const keys: string[] = await listKeys("product:"); getMany (prefix, limit): Promise<Array<{ key: string, value: T }>> Get many key-value pairs from the cache that match the given prefix. prefix - the optional prefix to match keys against. If not provided, all keys are considered. limit - the optional maximum number of key-value pairs to return. If 0 , no limit is applied. Defaults to 0 . returns An array of key-value pairs. Each pair is an object with key and value properties. // Get all non-expired keys and their values
const everything = await getMany();
// Get all keys and values with a matching prefix
const allProducts = await getMany("product:");
// Get 5 keys and values with a matching prefix
const discountedProducts = await getMany("discounts:", 5); deleteKey(key): Promise Delete a key from the cache. key - the key to delete returns the number of keys deleted: 1 if the key was deleted, 0 if the key did not exist. // Delete a key from the cache
await deleteKey("luckyNumber"); deleteKeys(prefix): Promise Delete all keys from the cache that match the prefix. If no prefix is provided, all keys in the cache are deleted. prefix - the optional prefix to match keys against returns the number of keys deleted // Delete all keys from the cache
await deleteKeys();
// Delete all keys from the cache that start with "product:"
await deleteKeys("product:"); deleteExpired(): Promise Delete all expired keys from the cache. Perfect for running on a schedule to keep the cache small and fast. returns the number of keys deleted // Delete all expired keys from the cache
await deleteExpired();
2
xkonti
gptMemoryManager
Script
A simple Rest API that allows for you GPT to save and recall snippets of data (memories). You can read my blog post explaining it in detail here: xkonti.tech Demonstration First conversation: What GPT sent do the API: {
"name": "Life Essence and Biological Processes",
"description": "Explore the role of life essence in enabling biological processes, with a focus on how dimensional interference, particularly from the Incorporeal dimension, facilitates the existence of these processes. This exploration should delve into the complex interplay between the Physical and Incorporeal dimensions, examining how life essence acts as a crucial element in the emergence and sustenance of life in the Physical dimension.",
"summary": "Expanding on the role of life essence in biological processes, emphasizing the interaction between the Physical and Incorporeal dimensions.",
"reason": "To deepen the understanding of how life essence, influenced by dimensional interference, is essential for biological processes in the novel's universe."
} Separate conversation somewhere in the future: Setup There are several steps to set up the API: deploy and configure the API create the API key for your GPT add an action for the API in you GPT add prompt section to your GPT so that it can use it properly Deploying the API on Val Town Deploy your own memory API. You can fork the following Val to do it: https://www.val.town/v/xkonti/memoryApiExample In the code configure the appropriate values: apiName the name of your API - used in the Privacy Policy (eg. Memory API ) contactEmail - the email to provide for contact in the Privacy Policy (eg. some@email.com ) lastPolicyUpdate - the date the Privacy Policy was last updated (eg. 2023-11-28 ) blobKeyPrefix - the prefix for the blob storage keys used by your API - more info below (eg. gpt:memories: ) apiKeyPrefix - the prefix for you API Keys secrets - more info below (eg. GPTMEMORYAPI_KEY_ ) Create API keys The Memory API is designed to serve multiple GPTs at the same time. Each GPT should have it's own unique name and API key . The name is used for identifying the specific GPT and appended to both: blobKeyPrefix - to maintain separate memory storage from other GPTs apiKeyPrefix - to maintain separate API key for each GPT Please pick a unique alphanumeric name for your GPT. For example personaltrainer . Generate some alphanumeric API key for your GPT. For example Wrangle-Chapped-Monkhood4-Domain-Suspend Add a new secret to your Val.town secrets storage. The Key should be the picked name prefixed by apiKeyPrefix . Using the default it would be GPTMEMORYAPI_KEY_personaltrainer . The value of the secret should be the API key itself. The memories of the GPT will be stored in the blob storage under the key blobKeyPrefix + name , for example: gpt:memories:personaltrainer . Adding GPT action Add a new action in your GPT. Get the OpenAPI spefication by calling the /openapi endpoint of your API Change all <APIURL> instances within the specification to the url of your deployed API. For example https://xkonti-memoryapiexample.web.val.run Set the authentication method to basic and provide a base64 encoded version of the <name>:<apiKey> . For example: personaltrainer:Wrangle-Chapped-Monkhood4-Domain-Suspend -> cGVyc29uYWx0cmFpbmVyOldyYW5nbGUtQ2hhcHBlZC1Nb25raG9vZDQtRG9tYWluLVN1c3BlbmQ= Add the link to the privacy policy, which is the /privacy endpoint of your API. For example: https://xkonti-memoryapiexample.web.val.run/privacy Adding the prompt section To make your GPT understand the usage of your new action you should include usage instruction in your prompt. Here's an example of such instructions section: # Long-term memory
At some point the user might ask you to do something with "memory". Things like "remember", "save to memory", "forget", "update memory", etc. Please use corresponding actions to achieve those tasks. User might also ask you to perform some task with the context of your "memory" - in that case fetch all memories before proceeding with the task. The memories should be formed in a clear and purely informative language, void of unnecessary adjectives or decorative language forms. An exception to that rule might be a case when the language itself is the integral part of information (snippet of writing to remember for later, examples of some specific language forms, quotes, etc.).
Structure of a memory:
- name - a simple name of the memory to give it context at a glance
- description - a detailed description of the thing that should be remembered. There is no length limit.
- summary - a short summary of the memory. This should be formed in a way that will allow for ease of understanding which memories to retrieve in full detail just by reading the list of summaries. If there are some simple facts that have to be remembered and are the main point of the memory they should be included in the summary. The summary should also be written in a compressed way with all unnecessary words skipped if possible (more like a set of keywords or a Google Search input).
- reason - the reason for the remembering - this should give extra information about the situation in which the memory was requested to be saved.
The memory accessed through those actions is a long-term memory persistent between various conversations with the user. You can assume that there already are many memories available for retrieval.
In some situations you might want to save some information to your memory for future recall. Do it in situations where you expect that some important details of the conversation might be lost and should be preserved.
Analogously you can retrieve memories at any point if the task at hand suggests the need or there isn't much information about the subject in your knowledge base.
3