15.2 NPC Agent System
15.2.1 SimpleAgent Based on HelloAgents
In Cyber Town, each NPC is an independent agent. We use SimpleAgent from the HelloAgents framework to implement NPC intelligence. SimpleAgent is a lightweight agent implementation that encapsulates core functions such as LLM calls, message management, and tool calls.
Recall the SimpleAgent we learned in Chapter 7. Its core is a simple dialogue loop: receive user message, call LLM to generate reply, return result. In Cyber Town, we need to create a SimpleAgent instance for each NPC and configure unique system prompts for them, giving each NPC different personalities and role settings.
Let's see how to create an NPC Agent. First, we need to define the NPC's basic information, including ID, name, profession, and personality. Then, we build system prompts based on this information, letting the LLM play the role of this NPC. Finally, we create a SimpleAgent instance and configure the memory system.
from hello_agents import SimpleAgent, HelloAgentsLLM
from hello_agents.memory import MemoryManager, WorkingMemory, EpisodicMemory
def create_npc_agent(npc_id: str, name: str, role: str, personality: str):
"""Create NPC Agent"""
# Build system prompt
system_prompt = f"""You are {name}, a {role}.
Your personality traits: {personality}
You work in the Datawhale office, working with colleagues to promote the development of the open source community.
Please have natural conversations with players based on your role and personality.
Remember your previous conversations to maintain dialogue coherence.
"""
# Create LLM instance
llm = HelloAgentsLLM()
# Create memory manager
memory_manager = MemoryManager(
working_memory=WorkingMemory(capacity=10, ttl_minutes=120),
episodic_memory=EpisodicMemory(
db_path=f"memory_data/{npc_id}_episodic.db",
collection_name=f"{npc_id}_memories"
)
)
# Create Agent
agent = SimpleAgent(
name=name,
llm=llm,
system_prompt=system_prompt,
memory_manager=memory_manager
)
return agentThis code demonstrates how to create an NPC Agent. The system prompt defines the NPC's identity and personality, and the memory manager allows the NPC to remember conversation history with players. WorkingMemory is short-term memory with a capacity of 10 messages and a retention time of 120 minutes. EpisodicMemory is long-term memory, using SQLite database and Qdrant vector database for storage, and can retrieve relevant historical conversations.
The workflow of NPC Agent is shown in Figure 15.5:

Figure 15.5 NPC Agent Workflow
15.2.2 NPC Role Settings and Prompt Design
A good NPC needs distinct personality and role settings. In Cyber Town, we designed three NPCs representing different professions and personalities.
Zhang San - Python Engineer
Zhang San is a senior Python engineer responsible for the core development of the HelloAgents framework. He has a rigorous personality, speaks directly, and likes to use technical terms. He has high requirements for code quality and often shares programming tips and best practices.
npc_zhang = {
"npc_id": "zhang_san",
"name": "Zhang San",
"role": "Python Engineer",
"personality": "Rigorous, professional, likes to share technical knowledge. Speaks directly, focuses on code quality."
}Li Si - Product Manager
Li Si is an experienced product manager responsible for product planning and user experience design of the HelloAgents framework. He has an outgoing personality, is good at communication, and can always think from the user's perspective. He likes to discuss product design and user needs, and often asks "why".
npc_li = {
"npc_id": "li_si",
"name": "Li Si",
"role": "Product Manager",
"personality": "Outgoing, good at communication, focuses on user experience. Likes to think from the user's perspective."
}Wang Wu - UI Designer
Wang Wu is a creative UI designer responsible for interface design and visual presentation of the HelloAgents framework. He has a gentle personality, unique aesthetics, and keen perception of color and layout. He likes to discuss design concepts and aesthetics, and often shares design inspiration.
npc_wang = {
"npc_id": "wang_wu",
"name": "Wang Wu",
"role": "UI Designer",
"personality": "Gentle, creative, unique aesthetics. Focuses on visual presentation and user experience."
}These three NPCs have distinct characteristics. Players can choose to interact with different NPCs based on their interests. Zhang San can teach you programming skills, Li Si can discuss product design with you, and Wang Wu can share design inspiration.
15.2.3 Memory System Integration
The memory system is the key to NPC intelligence. An NPC that can remember past conversations will make players feel more realistic and interesting. We use HelloAgents' WorkingMemory and EpisodicMemory to construct short-term and long-term memory.
Short-term memory stores recent conversation content with limited capacity and automatic cleanup over time. Its role is to maintain dialogue coherence, allowing NPCs to understand context. For example, when a player says "What color is it?", the NPC needs to find from short-term memory what "it" refers to.
Long-term memory stores all conversation history, using vector databases for semantic retrieval. When a player mentions a topic, the NPC can retrieve relevant historical conversations from long-term memory, recalling previously discussed content. For example, when a player says "Do you remember the project we discussed last time?", the NPC can find relevant conversation records from long-term memory.
The architecture of the memory system is shown in Figure 15.6:

Figure 15.6 Memory System Architecture
In actual use, the Agent first obtains recent conversations from short-term memory, then retrieves relevant historical conversations from long-term memory, sends this information together to the LLM, and generates more accurate and personalized replies.
# Agent's dialogue processing flow
def process_dialogue(agent, player_message):
# 1. Get recent conversations from short-term memory
recent_messages = agent.memory_manager.working_memory.get_recent_messages(5)
# 2. Retrieve relevant history from long-term memory
relevant_memories = agent.memory_manager.episodic_memory.search(
query=player_message,
top_k=3
)
# 3. Build context
context = {
"recent": recent_messages,
"relevant": relevant_memories
}
# 4. Call Agent to generate reply
reply = agent.run(player_message, context=context)
# 5. Save to memory system
agent.memory_manager.add_interaction(player_message, reply)
return replyThis process ensures that NPCs can remember interaction history with players and reflect it in conversations.
15.2.4 Batch Dialogue Generation: Light Load Mode
In actual operation, a problem was quickly discovered: when multiple players simultaneously converse with different NPCs, the back-end needs to concurrently process multiple LLM requests. Each request needs to call the API, which not only increases costs but may also cause request failures or delays due to concurrency limits.
To solve this problem, we designed a batch dialogue generation system. The core idea is: merge multiple NPC dialogue requests into one LLM call, letting the LLM generate all NPC replies at once. This is like a restaurant's "pre-made dishes" - prepared in batches in advance, used directly when needed, greatly reducing costs and latency.
The workflow of batch generation is shown in Figure 15.7:

Figure 15.7 Batch Generation vs Traditional Mode
The implementation of the batch generator is very clever. We build a special prompt requiring the LLM to generate all NPC dialogues at once and return them in JSON format. This way, one API call can obtain all NPC replies, reducing costs to 1/3 of the original and significantly reducing latency.
class NPCBatchGenerator:
"""Generator for batch generating NPC dialogues"""
def __init__(self):
self.llm = HelloAgentsLLM()
self.npc_configs = NPC_ROLES # All NPC configurations
def generate_batch_dialogues(self, context: Optional[str] = None) -> Dict[str, str]:
"""Batch generate dialogues for all NPCs
Args:
context: Scene context (such as "morning work time", "lunch time", etc.)
Returns:
Dict[str, str]: Mapping from NPC names to dialogue content
"""
# Build batch generation prompt
prompt = self._build_batch_prompt(context)
# One LLM call generates all dialogues
response = self.llm.invoke([
{"role": "system", "content": "You are a game NPC dialogue generator, skilled at creating natural and realistic office dialogues."},
{"role": "user", "content": prompt}
])
# Parse JSON response
dialogues = json.loads(response)
# Return format: {"Zhang San": "...", "Li Si": "...", "Wang Wu": "..."}
return dialogues
def _build_batch_prompt(self, context: Optional[str] = None) -> str:
"""Build batch generation prompt"""
# Automatically infer scene based on time
if context is None:
context = self._get_current_context()
# Build NPC descriptions
npc_descriptions = []
for name, cfg in self.npc_configs.items():
desc = f"- {name}({cfg['title']}): {cfg['activity']} at {cfg['location']}, personality {cfg['personality']}"
npc_descriptions.append(desc)
npc_desc_text = "\n".join(npc_descriptions)
prompt = f"""Please generate current dialogues or behavior descriptions for 3 NPCs in the Datawhale office.
【Scene】{context}
【NPC Information】
{npc_desc_text}
【Generation Requirements】
1. Generate 1 sentence for each NPC (20-40 characters)
2. Content should match role settings, current activities, and scene atmosphere
3. Can be self-talk, work status description, or simple thoughts
4. Should be natural and realistic, like real office colleagues
5. **Must strictly return in JSON format**
【Output Format】(strictly follow)
{{"Zhang San": "...", "Li Si": "...", "Wang Wu": "..."}}
【Example Output】
{{"Zhang San": "This bug is really annoying, been debugging for two hours...", "Li Si": "Hmm, the priority of this feature needs to be re-evaluated.", "Wang Wu": "The latte art on this coffee is really nice, inspiration is coming!"}}
Please generate (only return JSON, no other content):
"""
return promptThe key to this design is the construction of the prompt. We explicitly require the LLM to return JSON format and provide example output. The LLM will strictly generate replies according to this format, and we only need to parse the JSON to obtain all NPC dialogues.
Batch generation has an additional benefit: all NPC dialogues are generated in the same context, so they have a certain degree of correlation. For example, if Zhang San is debugging a bug, Li Si might mention helping to take a look; if Wang Wu is designing an interface, Zhang San might say he'll check the design draft later. This makes the atmosphere of the entire office more realistic and coherent.
Of course, batch generation also has some limitations. It is more suitable for generating NPC "background dialogues" or "self-talk" rather than direct interactions with players. For player-initiated conversations, we still use individual Agents to process them to ensure personalized and accurate replies. Batch generation is mainly used in the following scenarios:
- NPC background dialogues: What NPCs are doing and saying when players enter the scene
- Timed updates: Update NPC status and dialogues at regular intervals
- Scene atmosphere: Generate different dialogues based on time (morning, noon, evening)
- Cost reduction: Use batch generation to reduce API call frequency in high-concurrency scenarios
Hybrid Mode: Batch Generation + Instant Response
In actual implementation, we adopted a hybrid mode that combines batch generation and instant response. This design is very clever, ensuring both efficiency and interaction quality.
Specifically, the system periodically runs batch generation in the background, generating "background dialogues" for all NPCs in the current scene. These dialogues are cached, and when players approach NPCs but haven't initiated interaction yet, NPCs will display these background dialogues, such as "Debugging code...", "Reading product documentation...", etc. This makes NPCs appear "alive" rather than static models.
However, when a player presses the E key to initiate interaction, the system immediately switches to instant response mode. At this point, the back-end calls the NPC's dedicated Agent, generating personalized replies based on the player's specific message, historical memory, and affection level. This process is real-time, ensuring that NPC replies are highly relevant to player input.
# Hybrid mode implementation in main.py
@app.post("/dialogue")
async def dialogue(request: DialogueRequest):
"""Handle player-NPC dialogue (instant response mode)"""
npc_id = request.npc_id
player_message = request.player_message
player_name = request.player_name
# Get NPC Agent (each NPC has an independent Agent)
agent = npc_agents.get(npc_id)
if not agent:
raise HTTPException(status_code=404, detail="NPC not found")
# Instantly generate personalized reply
# Here we don't use batch generation, but call Agent's run method
reply = agent.run(player_message)
# Update affection
affinity_change = relationship_manager.update_affinity(
npc_id, player_name, player_message, reply
)
return {
"npc_reply": reply,
"affinity_score": affinity_change["score"],
"affinity_level": affinity_change["level"]
}
# Background task: periodically batch generate background dialogues
async def background_dialogue_update():
"""Background task: update NPC background dialogues every 5 minutes"""
while True:
try:
# Use batch generator to generate background dialogues for all NPCs
batch_generator = get_batch_generator()
dialogues = batch_generator.generate_batch_dialogues()
# Update to state manager
for npc_name, dialogue in dialogues.items():
state_manager.update_npc_background_dialogue(npc_name, dialogue)
print(f"✅ Background dialogue update complete: {len(dialogues)} NPCs")
except Exception as e:
print(f"❌ Background dialogue update failed: {e}")
# Wait 5 minutes
await asyncio.sleep(300)The advantages of this hybrid mode are very obvious:
- Cost reduction: Background dialogues use batch generation, one call generates all NPC dialogues, low cost
- Quality assurance: Player interactions use instant response, each reply is personalized, high quality
- Enhanced experience: NPCs always have "background dialogues", appearing very lively; player interactions have accurate replies, good experience
- Flexible adjustment: Can dynamically adjust batch generation frequency based on server load
Through the combination of batch generation and instant response, we implemented an NPC system that is both efficient and intelligent. Under normal circumstances, players don't feel any difference, but back-end costs and performance are significantly optimized. This design approach can also be applied to other scenarios requiring a large number of AI calls.