221 lines
8 KiB
Python
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())
|