backend / engine_bitai.py
muhammadpriv001's picture
Deploying YapStation Backend
6a5e36e
Raw
History Blame Contribute Delete
4.79 kB
import os
import asyncio
import re
from typing import TypedDict, List, Annotated, Sequence
from langchain_groq import ChatGroq
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
from langgraph.graph import StateGraph, END
from db import get_service_client
import engine_user
import engine_messaging
import engine_content
# --- State Definition ---
class AgentState(TypedDict):
messages: List[BaseMessage]
username: str
ai_name: str
personality: str
context: str
response: str
# --- RAG Logic ---
def get_user_context(username: str) -> str:
"""Collects recent posts and info about the user to provide context."""
try:
# Get user bio and basic info
user = engine_user.get_user_by_username(username)
bio = user.get("bio", "No bio provided.")
# Get recent posts by the user
posts = engine_content.get_user_posts(username, viewer=username)
recent_posts = "\n".join([f"- {p['content']}" for p in posts[:5]])
context = f"User Bio: {bio}\n\nRecent Posts by {username}:\n{recent_posts}"
return context
except Exception as e:
print(f"Error gathering context: {e}")
return "No specific context found."
# --- Nodes ---
def retrieve_node(state: AgentState):
"""Retrieve user-specific context."""
username = state["username"]
context = get_user_context(username)
return {"context": context}
def generate_node(state: AgentState):
"""Generate response using context, personality, and history."""
llm = ChatGroq(
model="llama-3.1-8b-instant",
groq_api_key=os.environ.get("GROQ_API_KEY"),
temperature=0.7,
max_tokens=256
)
username = state["username"]
ai_name = state["ai_name"]
personality = state["personality"]
context = state["context"]
system_prompt = f"""You are {ai_name}, a personal AI assistant for {username} on YapStation.
Your Personality/Instructions: {personality if personality else "Be a friendly and helpful cyberpunk assistant."}
YapStation Context:
- A cyberpunk social media platform.
- Features: Stations (podcasts), Posts, Real-time Chat, Stories.
User Context (Recent activity/bio):
{context}
Guidelines:
- If this is the first interaction and they are giving you a name, acknowledge it warmly.
- Always refer to yourself as {ai_name}.
- Keep responses relatively concise but engaging.
- Use cyberpunk slang occasionally if it fits.
"""
messages = [SystemMessage(content=system_prompt)] + state["messages"]
response = llm.invoke(messages)
return {"response": response.content}
# --- Graph Construction ---
def create_bitai_graph():
workflow = StateGraph(AgentState)
workflow.add_node("retrieve", retrieve_node)
workflow.add_node("generate", generate_node)
workflow.set_entry_point("retrieve")
workflow.add_edge("retrieve", "generate")
workflow.add_edge("generate", END)
return workflow.compile()
# --- Execution ---
async def run_bitai_chat(username: str, user_message: str, history: List[dict], settings: dict):
"""Runs the LangGraph for a BitAI chat interaction."""
graph = create_bitai_graph()
# Convert history to LangChain messages
messages = []
for m in history:
if m.get("role") == "user":
messages.append(HumanMessage(content=m.get("content", "")))
else:
messages.append(AIMessage(content=m.get("content", "")))
messages.append(HumanMessage(content=user_message))
inputs = {
"messages": messages,
"username": username,
"ai_name": settings.get("ai_name", "BitAI"),
"personality": settings.get("personality", ""),
"context": ""
}
result = await asyncio.to_thread(graph.invoke, inputs)
return result["response"]
def extract_name_from_sentence(sentence: str) -> str:
"""Helper to extract a name if the user enters a sentence like 'Your name is JARVIS'."""
# Simple heuristic: look for "name is [Name]" or "call you [Name]"
match = re.search(r"name\s+is\s+([A-Za-z0-9\s]+)", sentence, re.IGNORECASE)
if match:
return match.group(1).strip().split('\n')[0].split('.')[0]
match = re.search(r"call\s+you\s+([A-Za-z0-9\s]+)", sentence, re.IGNORECASE)
if match:
return match.group(1).strip().split('\n')[0].split('.')[0]
# If no pattern, just take the first 2 words if it's short, or the whole thing if it's one word
words = sentence.strip().split()
if len(words) <= 2:
return " ".join(words)
return words[-1] # Fallback to last word? Or just return the whole thing?
# User said: "I want to call you Jarvis" -> extract Jarvis.