beta release conversation context
This commit is contained in:
		| @ -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 | ||||||
|  | |||||||
							
								
								
									
										35
									
								
								src/api.ts
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								src/api.ts
									
									
									
									
									
								
							| @ -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, | ||||||
|  | }; | ||||||
|  | |||||||
							
								
								
									
										41
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								src/main.ts
									
									
									
									
									
								
							| @ -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; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -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
									
									
								
							
							
						
						
									
										34
									
								
								types.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -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 { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user