beta release conversation context

This commit is contained in:
2025-08-03 23:31:56 +00:00
parent 0bfff52fd0
commit 0f178fcfa9
5 changed files with 101 additions and 12 deletions

View File

@ -2,6 +2,7 @@ DATABASE_URL="file:../dev.db" # SQLite database relative to the ./prisma path
PLEROMA_INSTANCE_URL="https://instance.tld" # Pleroma instance full URL including scheme PLEROMA_INSTANCE_URL="https://instance.tld" # Pleroma instance full URL including scheme
PLEROMA_INSTANCE_DOMAIN="instance.tld" # used if you want to only want to respond to people from a particular instance PLEROMA_INSTANCE_DOMAIN="instance.tld" # used if you want to only want to respond to people from a particular instance
PLEROMA_ACCOUNT_ID="" # obtained from /api/v1/accounts/{nickname} - used so we don't spam mentions when not directly addressed PLEROMA_ACCOUNT_ID="" # obtained from /api/v1/accounts/{nickname} - used so we don't spam mentions when not directly addressed
REPLY_WITH_CONTEXT="" # set to true or false whether you want the bot to fetch context or not
ONLY_WHITELIST="true" # change to "false" if you want to accept prompts from any and all domains - *** USE WITH CAUTION *** ONLY_WHITELIST="true" # change to "false" if you want to accept prompts from any and all domains - *** USE WITH CAUTION ***
WHITELISTED_DOMAINS="" # comma separated list of domains you want to allow the bot to accept prompts from (i.e. poa.st,nicecrew.digital,detroitriotcity.com,decayable.ink) WHITELISTED_DOMAINS="" # comma separated list of domains you want to allow the bot to accept prompts from (i.e. poa.st,nicecrew.digital,detroitriotcity.com,decayable.ink)
OLLAMA_URL="http://localhost:11434" # OLLAMA connection URL OLLAMA_URL="http://localhost:11434" # OLLAMA connection URL

View File

@ -1,5 +1,5 @@
import { envConfig, prisma } from "./main.js"; import { envConfig, prisma } from "./main.js";
import { PleromaEmoji, Notification } from "../types.js"; import { PleromaEmoji, Notification, ContextResponse } from "../types.js";
const getNotifications = async () => { const getNotifications = async () => {
const { bearerToken, pleromaInstanceUrl } = envConfig; const { bearerToken, pleromaInstanceUrl } = envConfig;
@ -22,6 +22,32 @@ const getNotifications = async () => {
} }
}; };
const getStatusContext = async (statusId: string) => {
const { bearerToken, pleromaInstanceUrl } = envConfig;
try {
const response = await fetch(
`${pleromaInstanceUrl}/api/v1/statuses/${statusId}/context`,
{
method: "GET",
headers: {
Authorization: `Bearer ${bearerToken}`,
},
}
);
if (!response.ok) {
throw new Error(
`Could not get conversation context: ${response.status} - ${response.statusText}`
);
}
const data: ContextResponse = await response.json();
return data;
} catch (error: unknown) {
if (error instanceof Error) {
throw new Error(error.message);
}
}
};
const getInstanceEmojis = async () => { const getInstanceEmojis = async () => {
const { bearerToken, pleromaInstanceUrl } = envConfig; const { bearerToken, pleromaInstanceUrl } = envConfig;
try { try {
@ -72,4 +98,9 @@ const deleteNotification = async (notification: Notification) => {
} }
}; };
export { deleteNotification, getInstanceEmojis, getNotifications }; export {
deleteNotification,
getInstanceEmojis,
getNotifications,
getStatusContext,
};

View File

@ -6,6 +6,7 @@ import {
// OllamaChatResponse, // OllamaChatResponse,
OllamaRequest, OllamaRequest,
OllamaResponse, OllamaResponse,
PostAncestorsForModel,
} from "../types.js"; } from "../types.js";
// import striptags from "striptags"; // import striptags from "striptags";
import { PrismaClient } from "../generated/prisma/client.js"; import { PrismaClient } from "../generated/prisma/client.js";
@ -13,13 +14,14 @@ import {
getInstanceEmojis, getInstanceEmojis,
deleteNotification, deleteNotification,
getNotifications, getNotifications,
getStatusContext,
} from "./api.js"; } from "./api.js";
import { storeUserData, storePromptData } from "./prisma.js"; import { storeUserData, storePromptData } from "./prisma.js";
import { import {
isFromWhitelistedDomain, isFromWhitelistedDomain,
alreadyRespondedTo, alreadyRespondedTo,
recordPendingResponse, recordPendingResponse,
trimInputData, // trimInputData,
selectRandomEmoji, selectRandomEmoji,
shouldContinue, shouldContinue,
} from "./util.js"; } from "./util.js";
@ -44,6 +46,7 @@ export const envConfig = {
? parseInt(process.env.RANDOM_POST_INTERVAL) ? parseInt(process.env.RANDOM_POST_INTERVAL)
: 3600000, : 3600000,
botAccountId: process.env.PLEROMA_ACCOUNT_ID, botAccountId: process.env.PLEROMA_ACCOUNT_ID,
replyWithContext: process.env.REPLY_WITH_CONTEXT === "true" ? true : false,
}; };
const ollamaConfig: OllamaConfigOptions = { const ollamaConfig: OllamaConfigOptions = {
@ -60,8 +63,13 @@ const ollamaConfig: OllamaConfigOptions = {
const generateOllamaRequest = async ( const generateOllamaRequest = async (
notification: Notification notification: Notification
): Promise<OllamaResponse | undefined> => { ): Promise<OllamaResponse | undefined> => {
const { whitelistOnly, ollamaModel, ollamaSystemPrompt, ollamaUrl } = const {
envConfig; whitelistOnly,
ollamaModel,
ollamaSystemPrompt,
ollamaUrl,
replyWithContext,
} = envConfig;
try { try {
if (shouldContinue(notification)) { if (shouldContinue(notification)) {
if (whitelistOnly && !isFromWhitelistedDomain(notification)) { if (whitelistOnly && !isFromWhitelistedDomain(notification)) {
@ -73,12 +81,30 @@ const generateOllamaRequest = async (
} }
await recordPendingResponse(notification); await recordPendingResponse(notification);
await storeUserData(notification); await storeUserData(notification);
let conversationHistory: PostAncestorsForModel[] = [];
if (replyWithContext) {
const contextPosts = await getStatusContext(notification.status.id);
if (!contextPosts?.ancestors || !contextPosts) {
throw new Error(`Unable to obtain post context ancestors.`);
}
conversationHistory = contextPosts.ancestors.map((ancestor) => {
const mentions = ancestor.mentions.map((mention) => mention.acct);
return {
account_fqn: ancestor.account.fqn,
mentions,
plaintext_content: ancestor.pleroma.content["text/plain"],
};
});
// console.log(conversationHistory);
}
const oneOffPrompt = `${notification.status.account.fqn} says: ${notification.status.pleroma.content["text/plain"]}\n[/INST]`;
const contextPrompt = `<<SYS>>[INST]\n${ollamaSystemPrompt}\nHere is the previous conversation context in JSON format:\n${JSON.stringify(
conversationHistory
)}\nAssume the {account_fqn} key is the user who posted the {plaintext_content} to the users in {mentions}\nReply as if you are a party to the conversation. If you see '@nice-ai' or 'nice-ai' in the {mentions}, you are an addressee of the conversation.\nAppend the '@' sign to each username at the beginning when addressing users.<</SYS>>`;
const ollamaRequestBody: OllamaRequest = { const ollamaRequestBody: OllamaRequest = {
model: ollamaModel, model: ollamaModel,
prompt: `${notification.status.account.fqn} says: ${trimInputData( prompt: oneOffPrompt,
notification.status.content system: replyWithContext ? contextPrompt : ollamaSystemPrompt,
)}`,
system: ollamaSystemPrompt,
stream: false, stream: false,
options: ollamaConfig, options: ollamaConfig,
}; };
@ -87,6 +113,7 @@ const generateOllamaRequest = async (
body: JSON.stringify(ollamaRequestBody), body: JSON.stringify(ollamaRequestBody),
}); });
const ollamaResponse: OllamaResponse = await response.json(); const ollamaResponse: OllamaResponse = await response.json();
await storePromptData(notification, ollamaResponse); await storePromptData(notification, ollamaResponse);
return ollamaResponse; return ollamaResponse;
} }

View File

@ -40,7 +40,7 @@ const shouldContinue = (notification: Notification) => {
const { botAccountId } = envConfig; const { botAccountId } = envConfig;
const statusContent = trimInputData(notification.status.content); const statusContent = trimInputData(notification.status.content);
if ( if (
notification.status.visibility !== "private" && // notification.status.visibility !== "private" &&
!notification.account.bot && !notification.account.bot &&
notification.type === "mention" notification.type === "mention"
) { ) {

34
types.d.ts vendored
View File

@ -7,8 +7,38 @@ export interface Notification {
} }
export interface ContextResponse { export interface ContextResponse {
ancestors: Notification[]; ancestors: ContextObject[];
descendents: Notification[]; descendents: ContextObject[];
}
export interface PostAncestorsForModel {
account_fqn: string;
mentions: string[];
plaintext_content: string;
}
interface ContextAccountObject {
acct: string;
avatar: string;
bot: boolean;
display_name: string;
followers_count: number;
following_count: number;
fqn: string;
id: string;
}
export interface ContextObject {
content: string;
id: string;
in_reply_to_account_id: string | null;
in_reply_to_id: string | null;
media_attachments: string[];
mentions: Mention[];
pleroma: PleromaObjectInResponse;
visibility: "public" | "private" | "unlisted";
uri: string;
account: ContextAccountObject;
} }
export interface NewStatusBody { export interface NewStatusBody {