Spaces:
Running
Running
| 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. | |