Spaces:
Running
on
Zero
Running
on
Zero
NPC Chat API Integration Guide
New FastAPI Endpoints for NPC Chat
Add these routes to your existing service.py:
# === 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:
# === 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
# 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:
# 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