# NPC Chat API Integration Guide ## New FastAPI Endpoints for NPC Chat Add these routes to your existing `service.py`: ````python # === NPC CHAT ENDPOINTS === class NPCInitializeRequest(BaseModel): """Request to initialize a new NPC.""" npc_id: str name: str biography: str realm: str = "dialogue" alignment: str = "neutral" class NPCChatRequest(BaseModel): """Request to chat with an NPC.""" npc_id: str player_id: str = "anonymous" message: str class NPCChatResponse(BaseModel): """Response from NPC chat.""" conversation_id: str npc_id: str player_id: str player_message: str npc_response: str emotion: str intent: str coherence_score: float timestamp: str turn_number: int @app.post("/npc/initialize", response_model=Dict[str, Any]) async def initialize_npc(request: NPCInitializeRequest) -> Dict[str, Any]: """Initialize a new NPC character.""" global npc_chat_service if npc_chat_service is None: npc_chat_service = NPCChatService( retrieval_api=apiinstance, embedding_provider=EmbeddingProviderFactory.get_default_provider(), summarization_ladder=SummarizationLadder(), semantic_anchors=SemanticAnchorGraph(), llm_provider=llm_provider, config={"enable_self_consumption": True}, ) profile = npc_chat_service.initialize_npc( npc_id=request.npc_id, name=request.name, biography=request.biography, realm=request.realm, alignment=request.alignment, ) return { "status": "initialized", "npc_id": profile.npc_id, "name": profile.name, "biography": profile.biography[:100], "realm": profile.realm, "alignment": profile.alignment, "timestamp": datetime.now().isoformat(), } @app.post("/npc/chat", response_model=NPCChatResponse) async def chat_with_npc(request: NPCChatRequest) -> NPCChatResponse: """Send message to NPC, get response with self-consumption.""" global npc_chat_service if npc_chat_service is None: raise HTTPException( status_code=503, detail="NPC Chat Service not initialized. Call /npc/initialize first.", ) try: result = npc_chat_service.chat_with_npc( npc_id=request.npc_id, player_id=request.player_id, player_message=request.message, ) return NPCChatResponse( conversation_id=result["conversation_id"], npc_id=result["npc_id"], player_id=result["player_id"], player_message=result["player_message"], npc_response=result["npc_response"], emotion=result["emotion"], intent=result["intent"], coherence_score=result["coherence_score"], timestamp=result["timestamp"], turn_number=result["turn_number"], ) except Exception as e: logger.error(f"Error in NPC chat: {e}") raise HTTPException(status_code=500, detail=str(e)) @app.get("/npc/{npc_id}/profile") async def get_npc_profile(npc_id: str) -> Dict[str, Any]: """Get NPC profile with conversation statistics.""" global npc_chat_service if npc_chat_service is None: raise HTTPException(status_code=503, detail="NPC Chat Service not initialized") profile = npc_chat_service.get_npc_profile(npc_id) if not profile: raise HTTPException(status_code=404, detail=f"NPC {npc_id} not found") return profile @app.get("/conversation/{conversation_id}") async def get_conversation(conversation_id: str) -> Dict[str, Any]: """Retrieve full conversation history.""" global npc_chat_service if npc_chat_service is None: raise HTTPException(status_code=503, detail="NPC Chat Service not initialized") history = npc_chat_service.get_conversation_history(conversation_id) if not history: raise HTTPException(status_code=404, detail="Conversation not found") return history @app.get("/npc/metrics/self-consumption") async def get_self_consumption_metrics() -> Dict[str, Any]: """Get learning loop performance metrics.""" global npc_chat_service if npc_chat_service is None: return { "status": "uninitialized", "message": "NPC Chat Service not yet started", } return npc_chat_service.get_self_consumption_metrics() # Add to global state in lifespan npc_chat_service: Optional[NPCChatService] = None llm_provider: Optional[Any] = None # Initialize your LLM provider here @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan with NPC Chat initialization.""" initapi() autoloadpacks() global llm_provider try: # Initialize your LLM provider here # Options: HuggingFace local, OpenAI API, etc. from sentence_transformers import SentenceTransformer llm_provider = SentenceTransformer("all-MiniLM-L6-v2") except Exception as e: logger.warning(f"Could not initialize LLM provider: {e}") yield # Cleanup logger.info("NPC Chat Service shutting down") ```` --- ## New CLI Commands for NPC Chat Add these commands to your `cli.py`: ````python # === NPC CHAT COMMANDS === @cli.group() @click.pass_context def npc(ctx): """NPC chat commands - initialize and converse with characters.""" pass @npc.command() @click.option("--npc-id", required=True, help="Unique NPC identifier") @click.option("--name", required=True, help="NPC character name") @click.option("--biography", required=True, help="NPC character biography") @click.option("--realm", default="dialogue", help="NPC realm/domain") @click.option("--alignment", default="neutral", help="NPC alignment (neutral, harmonic, chaotic)") @click.pass_context def init(ctx, npc_id, name, biography, realm, alignment): """Initialize a new NPC character.""" client = ctx.obj["client"] baseurl = ctx.obj["api_url"] try: response = requests.post( f"{baseurl}/npc/initialize", json={ "npc_id": npc_id, "name": name, "biography": biography, "realm": realm, "alignment": alignment, }, timeout=30, ) response.raise_for_status() result = response.json() click.secho(f"✓ NPC Initialized", fg="green") click.echo(f" ID: {result['npc_id']}") click.echo(f" Name: {result['name']}") click.echo(f" Realm: {result['realm']}") click.echo(f" Status: Ready for chat") except Exception as e: click.secho(f"✗ Error: {str(e)}", fg="red") @npc.command() @click.option("--npc-id", required=True, help="NPC to chat with") @click.option("--message", required=True, help="Message to send") @click.option("--player-id", default="player1", help="Your player ID") @click.option("--json-output", is_flag=True, help="Output as JSON") @click.pass_context def chat(ctx, npc_id, message, player_id, json_output): """Chat with an NPC and get response with self-consumption.""" client = ctx.obj["client"] baseurl = ctx.obj["api_url"] try: response = requests.post( f"{baseurl}/npc/chat", json={ "npc_id": npc_id, "player_id": player_id, "message": message, }, timeout=30, ) response.raise_for_status() result = response.json() if json_output: click.echo(json.dumps(result, indent=2)) else: click.echo("\n" + "="*60) click.secho(f"{result['npc_id']} says:", fg="cyan", bold=True) click.echo(f"\n{result['npc_response']}\n") click.echo("="*60) # Show metrics click.echo(f"Turn: {result['turn_number']} | Coherence: {result['coherence_score']:.2f}") click.echo(f"Emotion: {result['emotion']} | Intent: {result['intent']}") click.echo(f"Conversation ID: {result['conversation_id']}") except Exception as e: click.secho(f"✗ Error: {str(e)}", fg="red") @npc.command() @click.option("--npc-id", required=True, help="NPC to query") @click.option("--json-output", is_flag=True, help="Output as JSON") @click.pass_context def profile(ctx, npc_id, json_output): """Show NPC profile and statistics.""" client = ctx.obj["client"] baseurl = ctx.obj["api_url"] try: response = requests.get(f"{baseurl}/npc/{npc_id}/profile", timeout=30) response.raise_for_status() profile_data = response.json() if json_output: click.echo(json.dumps(profile_data, indent=2)) else: click.secho(f"NPC Profile: {profile_data['name']}", bold=True) click.echo(f"ID: {profile_data['npc_id']}") click.echo(f"Realm: {profile_data['realm']}") click.echo(f"Alignment: {profile_data['alignment']}") click.echo(f"Total Conversations: {profile_data['total_conversations']}") click.echo(f"Average Coherence: {profile_data['average_coherence']:.2f}") click.echo(f"Learned Traits: {profile_data['personality_anchor_count']}") except Exception as e: click.secho(f"✗ Error: {str(e)}", fg="red") @npc.command() @click.option("--conversation-id", required=True, help="Conversation ID to retrieve") @click.option("--json-output", is_flag=True, help="Output as JSON") @click.pass_context def history(ctx, conversation_id, json_output): """Show conversation history.""" client = ctx.obj["client"] baseurl = ctx.obj["api_url"] try: response = requests.get(f"{baseurl}/conversation/{conversation_id}", timeout=30) response.raise_for_status() history_data = response.json() if json_output: click.echo(json.dumps(history_data, indent=2)) else: click.secho(f"Conversation {history_data['conversation_id']}", bold=True) click.echo(f"NPC: {history_data['npc_id']} | Player: {history_data['player_id']}") click.echo(f"Messages: {history_data['message_count']} | Depth: {history_data['conversation_depth']}") click.echo(f"Coherence: {history_data['coherence_score']:.2f}\n") click.echo("Recent Messages:") for msg in history_data["messages"]: speaker = "You" if msg["speaker"] == "player" else history_data["npc_id"] click.echo(f" {speaker}: {msg['text']}") except Exception as e: click.secho(f"✗ Error: {str(e)}", fg="red") @npc.command() @click.option("--json-output", is_flag=True, help="Output as JSON") @click.pass_context def metrics(ctx, json_output): """Show self-consumption learning metrics.""" client = ctx.obj["client"] baseurl = ctx.obj["api_url"] try: response = requests.get(f"{baseurl}/npc/metrics/self-consumption", timeout=30) response.raise_for_status() metrics_data = response.json() if json_output: click.echo(json.dumps(metrics_data, indent=2)) else: click.secho("Self-Consumption Metrics", bold=True) click.echo(f"Conversations: {metrics_data['conversations_processed']}") click.echo(f"Anchors Created: {metrics_data['anchors_created']}") click.echo(f"Micro-Summaries: {metrics_data['micro_summaries_distilled']}") click.echo(f"Macro Distillations: {metrics_data['macro_distillations_created']}") click.echo(f"Total Conversations Stored: {metrics_data['total_conversations']}") click.echo(f"Total NPCs: {metrics_data['total_npcs']}") click.echo(f"Timestamp: {metrics_data['timestamp']}") except Exception as e: click.secho(f"✗ Error: {str(e)}", fg="red") @npc.command() @click.option("--npc-id", required=True, help="NPC to chat with") @click.option("--player-id", default="player1", help="Your player ID") @click.pass_context def interactive(ctx, npc_id, player_id): """Start interactive conversation with an NPC.""" baseurl = ctx.obj["api_url"] click.secho(f"Starting conversation with {npc_id}...", fg="green") click.echo("Type 'quit' to exit\n") while True: try: user_input = click.prompt(f"You").strip() if user_input.lower() == "quit": click.echo("Goodbye!") break if not user_input: continue response = requests.post( f"{baseurl}/npc/chat", json={ "npc_id": npc_id, "player_id": player_id, "message": user_input, }, timeout=30, ) response.raise_for_status() result = response.json() click.secho(f"{npc_id}: {result['npc_response']}\n", fg="cyan") except KeyboardInterrupt: click.echo("\nGoodbye!") break except Exception as e: click.secho(f"Error: {str(e)}", fg="red") ```` --- ## Example Usage Workflow ````bash # Initialize an NPC $ python -m warbler_cda.cli npc init \ --npc-id "gandalf-01" \ --name "Gandalf" \ --biography "A wise wizard with deep knowledge of ancient lore and magic. Known for cryptic riddles and patient guidance." # Chat with the NPC $ python -m warbler_cda.cli npc chat \ --npc-id "gandalf-01" \ --player-id "player-frodo" \ --message "What lies ahead on our journey?" # Start interactive conversation $ python -m warbler_cda.cli npc interactive \ --npc-id "gandalf-01" \ --player-id "player-frodo" # View NPC profile $ python -m warbler_cda.cli npc profile --npc-id "gandalf-01" # Check self-consumption metrics $ python -m warbler_cda.cli npc metrics # Retrieve conversation history $ python -m warbler_cda.cli npc history \ --conversation-id "conv-gandalf-01-player-frodo-1733754000" ```` --- ## API HTTP Examples Using curl or httpie: ````bash # Initialize NPC curl -X POST http://localhost:8000/npc/initialize \ -H "Content-Type: application/json" \ -d '{ "npc_id": "gandalf-01", "name": "Gandalf", "biography": "A wise wizard...", "realm": "dialogue", "alignment": "neutral" }' # Chat curl -X POST http://localhost:8000/npc/chat \ -H "Content-Type: application/json" \ -d '{ "npc_id": "gandalf-01", "player_id": "player-frodo", "message": "What lies ahead?" }' # Get profile curl http://localhost:8000/npc/gandalf-01/profile # Get metrics curl http://localhost:8000/npc/metrics/self-consumption ```` ````