graph-rag / src /graph_rag_service /api /simulation.py
GitHub Action
Automated sync to Hugging Face
bb63c6c
Raw History Blame Contribute Delete
4.96 kB
from fastapi import APIRouter, Depends, HTTPException
from typing import Dict, Any
from pydantic import BaseModel
from ..core.neo4j_store import Neo4jStore
from ..core.llm_factory import UnifiedLLMProvider
from .auth import get_current_user, User
router = APIRouter(prefix="/api/v1/simulation", tags=["Simulation Interaction"])
def get_global_store():
# Lazy import to avoid circular dependency with server.py
from .server import graph_store
if graph_store is None:
raise HTTPException(status_code=500, detail="Neo4j connection not initialized.")
return graph_store
def get_global_llm():
return UnifiedLLMProvider()
def require_admin(user: User = Depends(get_current_user)) -> User:
"""Simulation jobs mutate shared state and consume LLM budget — admin only."""
if "admin" not in getattr(user, "scopes", []):
raise HTTPException(status_code=403, detail="Admin scope required.")
return user
class InterviewRequest(BaseModel):
agent_id: str
user_query: str
@router.post("/interview", response_model=Dict[str, Any])
async def live_interview_agent(
request: InterviewRequest,
store: Neo4jStore = Depends(get_global_store),
llm: UnifiedLLMProvider = Depends(get_global_llm),
current_user: User = Depends(get_current_user),
):
"""
MiroFish Point 5: 'Live Interviews' with Simulated Personas.
Allows users to chat with a graph entity in character,
injecting their exact Neo4j memory into the system prompt.
"""
# 1. Fetch Agent Persona
query_agent = """
MATCH (a:Entity {id: $agent_id})
RETURN a.name as name, a.persona as persona
"""
results = await store.execute_query(query_agent, {"agent_id": request.agent_id})
if not results:
raise HTTPException(status_code=404, detail="Agent persona not found.")
agent = results[0]
# 2. Fetch Agent's 'Memory' (Recent relationships and properties)
query_memory = """
MATCH (a:Entity {id: $agent_id})-[r]->(t:Entity)
WITH t.name as target, type(r) as relation, coalesce(r.content, r.properties) as details, r.valid_from as timestamp
ORDER BY timestamp DESC
LIMIT 5
RETURN relation, target, details
"""
memories = await store.execute_query(query_memory, {"agent_id": request.agent_id})
memory_string = "Your recent memories/actions in the simulation sandbox:\n"
for m in memories:
memory_string += f"- You {m['relation']} ({m['target']}): {m['details']}\n"
# 3. Construct System Persona Prompt
system_prompt = (
f"You are engaging in a live roleplay interview.\n"
f"Your Name: {agent['name']}\n"
f"Your Psychological Profile & Background:\n{agent['persona']}\n\n"
f"{memory_string if memories else 'You have no recent memories recorded.'}\n\n"
f"Answer the user's question completely in character based ONLY on your profile and memories."
)
# 4. Send to Local LLM
try:
response_text = await llm.complete(
prompt=request.user_query,
system_prompt=system_prompt,
temperature=0.8 # slightly higher for creative roleplay
)
return {
"agent_id": request.agent_id,
"agent_name": agent['name'],
"response": response_text
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"LLM Interaction Failed: {str(e)}")
from ..retrieval.report_agent import ReportAgent
@router.get('/report', response_model=Dict[str, Any])
async def get_sandbox_report(
store: Neo4jStore = Depends(get_global_store),
llm: UnifiedLLMProvider = Depends(get_global_llm),
current_user: User = Depends(get_current_user),
):
'''
MiroFish Point 3: Dedicated ReAct Reporting Agent & Tooling.
Triggers the analytical engine to build a report off the recent sandbox events.
'''
try:
agent = ReportAgent(store, llm)
report = await agent.generate_report(
topic="Recent simulation sandbox activity, personas, and events",
report_type="detailed",
tenant_id=getattr(current_user, "tenant_id", None),
)
return report.model_dump()
except Exception as e:
raise HTTPException(status_code=500, detail=f'Report generation failed: {str(e)}')
from ..workers.celery_worker import celery_app
@router.post('/generate_personas')
async def start_persona_generation(admin_user: User = Depends(require_admin)):
'''Trigger background Celery task to generate personas.'''
task = celery_app.send_task('generate_personas')
return {'status': 'accepted', 'task_id': task.id}
@router.post('/tick')
async def start_simulation_tick(admin_user: User = Depends(require_admin)):
'''Trigger background Celery task to run a simulation loop tick.'''
task = celery_app.send_task('run_simulation_tick')
return {'status': 'accepted', 'task_id': task.id}