npc-clients-python/tests/test_npc_integration.py

221 lines
8 KiB
Python

"""Integration tests demonstrating NPC chat history and serialization in action."""
import json
import tempfile
import os
import asyncio
from pathlib import Path
from gumyum_npc_client import GumYumClient, NPC
from gumyum_npc_sync import GumYumClient as SyncGumYumClient, NPC as SyncNPC
async def test_async_npc_conversation_flow():
"""Test a complete conversation flow with history tracking."""
# This would use real API in production
# For testing, we'd mock the client responses
# Example flow:
async with GumYumClient(api_key="test-key", api_secret="test-secret") as client:
# Spawn an NPC
npc = await client.npcs.spawn(
universe_id="blade-runner", seed=42, npc_id=123456789
)
# First interaction
response1 = await npc.chat_with_history("Tell me about this city")
print(f"{npc.name}: {response1.choices[0].message.content}")
# Continue conversation - history is automatically included
response2 = await npc.chat_with_history("What's the most dangerous part?")
print(f"{npc.name}: {response2.choices[0].message.content}")
# Save the conversation
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
f.write(npc.to_json())
saved_file = f.name
# Later... load and continue
with open(saved_file, "r") as f:
loaded_npc = NPC.from_json(f.read(), client)
# Continue from where we left off
response3 = await loaded_npc.chat_with_history("How can I stay safe there?")
print(f"{loaded_npc.name}: {response3.choices[0].message.content}")
# Clean up
os.unlink(saved_file)
def test_sync_npc_game_save_example():
"""Example of saving/loading NPCs in a game save system."""
class GameSaveSystem:
def __init__(self, save_dir):
self.save_dir = Path(save_dir)
self.save_dir.mkdir(exist_ok=True)
def save_npc(self, npc: SyncNPC, slot: str):
"""Save an NPC to a save slot."""
save_path = self.save_dir / f"npc_{slot}_{npc.npc_id}.json"
with open(save_path, "w") as f:
f.write(npc.to_json())
return save_path
def load_npc(self, slot: str, npc_id: int, client) -> SyncNPC:
"""Load an NPC from a save slot."""
save_path = self.save_dir / f"npc_{slot}_{npc_id}.json"
if not save_path.exists():
return None
with open(save_path, "r") as f:
return SyncNPC.from_json(f.read(), client)
def save_all_npcs(self, npcs: list, slot: str):
"""Save all active NPCs."""
manifest = {"slot": slot, "npcs": []}
for npc in npcs:
path = self.save_npc(npc, slot)
manifest["npcs"].append(
{"npc_id": npc.npc_id, "name": npc.name, "path": str(path.name)}
)
# Save manifest
manifest_path = self.save_dir / f"manifest_{slot}.json"
with open(manifest_path, "w") as f:
json.dump(manifest, f, indent=2)
return manifest
def load_all_npcs(self, slot: str, client) -> list:
"""Load all NPCs from a save slot."""
manifest_path = self.save_dir / f"manifest_{slot}.json"
if not manifest_path.exists():
return []
with open(manifest_path, "r") as f:
manifest = json.load(f)
npcs = []
for npc_info in manifest["npcs"]:
npc = self.load_npc(slot, npc_info["npc_id"], client)
if npc:
npcs.append(npc)
return npcs
# Example usage
with tempfile.TemporaryDirectory() as temp_dir:
save_system = GameSaveSystem(temp_dir)
client = SyncGumYumClient(api_key="test", api_secret="test")
# Create some NPCs with conversations
npcs = []
# Merchant with trade conversation
merchant = SyncNPC(
npc_id=1001,
name="Elara",
profession="merchant",
personality_type=3,
universe_id="fantasy",
seed=42,
spawned={"location": "market", "mood": "friendly", "stress_level": 2},
)
merchant.chat_history.extend(
[
{"role": "user", "content": "What are you selling?"},
{
"role": "assistant",
"content": "I have potions, herbs, and rare artifacts!",
},
{"role": "user", "content": "How much for a healing potion?"},
{
"role": "assistant",
"content": "50 gold pieces, but I'll give you a discount - 40 gold.",
},
]
)
npcs.append(merchant)
# Guard with security conversation
guard = SyncNPC(
npc_id=1002,
name="Marcus",
profession="guard",
personality_type=1,
universe_id="fantasy",
seed=42,
spawned={"location": "gate", "mood": "alert", "stress_level": 4},
)
guard.chat_history.extend(
[
{"role": "user", "content": "Can I enter the city?"},
{"role": "assistant", "content": "State your business, traveler."},
{"role": "user", "content": "I'm here to trade goods."},
{
"role": "assistant",
"content": "Very well. Keep your weapons sheathed and cause no trouble.",
},
]
)
npcs.append(guard)
# Save game state
manifest = save_system.save_all_npcs(npcs, "save_001")
print(f"Saved {len(npcs)} NPCs to slot: save_001")
# Later... load game state
loaded_npcs = save_system.load_all_npcs("save_001", client)
print(f"Loaded {len(loaded_npcs)} NPCs from save")
# Verify conversations were preserved
for npc in loaded_npcs:
print(f"\n{npc.name} ({npc.profession}) - {len(npc.chat_history)} messages")
if npc.chat_history:
print(f" Last exchange: {npc.chat_history[-1]['content'][:50]}...")
def test_streaming_with_history():
"""Example of streaming responses with history tracking."""
async def streaming_conversation():
async with GumYumClient(api_key="test", api_secret="test") as client:
npc = await client.npcs.spawn("cyberpunk", 42, 999888777)
# Stream first response
print(f"{npc.name}: ", end="", flush=True)
async for chunk in await npc.chat_with_history(
"Tell me your story", stream=True
):
if "choices" in chunk and chunk["choices"]:
delta = chunk["choices"][0].get("delta", {})
if content := delta.get("content"):
print(content, end="", flush=True)
print() # New line
# Check history - streaming responses should still be tracked
print(f"\nConversation history: {len(npc.chat_history)} messages")
# Continue with another streamed message
print(f"{npc.name}: ", end="", flush=True)
async for chunk in await npc.chat_with_history(
"What happened next?", stream=True
):
if "choices" in chunk and chunk["choices"]:
delta = chunk["choices"][0].get("delta", {})
if content := delta.get("content"):
print(content, end="", flush=True)
print()
# Note: This would be run with asyncio.run(streaming_conversation()) in practice
if __name__ == "__main__":
# Run sync example
print("=== Sync Game Save Example ===")
test_sync_npc_game_save_example()
print("\n=== Async Conversation Example ===")
# Async example would need proper async environment
# asyncio.run(test_async_npc_conversation_flow())