Human-Agent Dialogue with Clarification¤
This guide shows how to use the dialogue()
function for 1:1 human-agent conversations where the agent can request
clarification when it lacks sufficient data. The application manages the
clarification loop — receive the question, collect the human's response, and
resume the dialogue.
Concepts covered
dialogue()— 1:1 conversation loopDialogueAgentMessage— agent's text responseAgentOutputClarification— structured request for more infoDialogueComplete— terminal event with optional typed outputAgentConfig— agent identity and behaviorMessage— conversation history entry
What you'll build¤
A customer support agent for NovaTech that helps with product inquiries, account questions, and technical issues. When the user asks about a specific order or account without providing enough details, the agent requests clarification instead of guessing:
User: "What's the status of my order?"
Agent: "I'd be happy to look that up! Could you provide your order number
or the email address associated with your account?"
User: "Order #NVT-2024-0842"
Agent: "Your order #NVT-2024-0842 shipped on July 8 and is expected to
arrive by July 12. Tracking: 1Z999AA10123456784"
Dialogue vs. Operation tree
dialogue() is a standalone
function — it does not use the
Operation tree. It calls
the runtime directly with assembled instructions and conversation history.
Use it for 1:1 conversational patterns.
1 Define the agent¤
The agent's identity block describes its persona and when to request clarification:
from crewmaster.agents.agent import AgentConfig
support_agent = AgentConfig(
name="support_agent",
identity_blocks=[
"""You are a helpful customer support agent for NovaTech,
an AI & Developer Tools company. Assist customers with product
inquiries, technical questions, and account-related matters.
Guidelines:
- Be friendly, professional, and concise.
- If a customer asks about an order, account, or billing detail
and hasn't provided enough information (order number, account ID,
email), you MUST ask for clarification rather than guessing.
- When clarification is needed, use the structured clarification
format to request exactly the information you need.
- If the question is general (product features, pricing), answer
directly without requesting clarification.
- Always strive to be helpful and resolve the question in as few
turns as possible.""",
],
)
2 Run a single dialogue turn¤
Call dialogue() with the agent,
the human's message, and a runtime. Iterate over the async generator to collect
events:
import asyncio
from crewmaster.conversation.dialogue import (
AgentOutputClarification,
DialogueAgentMessage,
DialogueComplete,
dialogue,
)
async def chat_turn(driver, agent, human_message, history=None):
"""Run one turn of dialogue and return parsed events."""
events = []
async for event in dialogue(
agent=agent,
human_message=human_message,
history=history or [],
runtime=driver,
):
events.append(event)
return events
3 Interpret the events¤
Each turn yields three possible event types in order:
| Event | Meaning |
|---|---|
DialogueAgentMessage |
The agent produced a normal text response |
AgentOutputClarification |
The agent needs more information |
DialogueComplete |
Terminal event (always yielded last) |
def interpret(events):
"""Extract the agent's intent from dialogue events."""
agent_text = None
clarification = None
typed_output = None
for event in events:
if isinstance(event, DialogueAgentMessage):
agent_text = event.content
elif isinstance(event, AgentOutputClarification):
clarification = event.question
elif isinstance(event, DialogueComplete):
typed_output = event.typed_output
if clarification:
return {"type": "clarification", "question": clarification}
return {"type": "response", "text": agent_text, "output": typed_output}
4 Build the clarification loop¤
The application manages the full conversation loop — receive a clarification
request, collect the human's response, and call dialogue()
again with the accumulated history:
from crewmaster.conversation.channel import Message
async def support_session(driver, initial_message: str):
"""Run a multi-turn dialogue with clarification support."""
print(f"User: {initial_message}")
history: list[Message] = []
current_message = initial_message
for turn in range(5): # Max 5 turns
events = await chat_turn(
driver, support_agent, current_message, history
)
for event in events:
if isinstance(event, DialogueAgentMessage):
print(f"\nAgent: {event.content}")
history.append(Message(role="user", content=current_message))
history.append(Message(role="assistant", content=event.content))
return # Done
elif isinstance(event, AgentOutputClarification):
print(f"\nAgent (needs info): {event.question}")
history.append(Message(role="user", content=current_message))
history.append(Message(
role="assistant",
content=f"[CLARIFICATION] {event.question}"
))
# Collect human response (in a real app, prompt the user)
current_message = input("\nYour response: ")
print(f"User: {current_message}")
break # Restart the loop with updated history
print("\n[Session ended — max turns reached]")
5 Execute with a real driver¤
from crewmaster.execution.drivers.pydantic_ai import PydanticAIDriver
async def main():
driver = PydanticAIDriver(model_name="openai:gpt-4o-mini")
# Scenario 1: Direct answer (no clarification needed)
print("=== Scenario 1: General question ===")
await support_session(driver, "What are NovaTech's pricing plans?")
# Scenario 2: Clarification needed
print("\n=== Scenario 2: Missing order info ===")
await support_session(driver, "What's the status of my order?")
asyncio.run(main())
Expected output:
=== Scenario 1: General question ===
User: What are NovaTech's pricing plans?
Agent: NovaTech offers three tiers: Starter ($29/mo), Professional
($99/mo), and Enterprise (custom). All plans include API access and
community support. Professional adds SSO and priority support.
Enterprise includes dedicated support and custom SLAs.
=== Scenario 2: Missing order info ===
User: What's the status of my order?
Agent (needs info): I'd be happy to check your order status! Could
you provide your order number or the email associated with your account?
Your response: Order #NVT-2024-0842
User: Order #NVT-2024-0842
Agent: Your order #NVT-2024-0842 shipped on July 8 via UPS. It's
expected to arrive by July 12. Tracking number: 1Z999AA10123456784.
6 How clarification works under the hood¤
When the agent detects insufficient information, it uses a structured format:
---CLARIFICATION---
question: What is your order number?
schema: OrderLookupInput
---END CLARIFICATION---
The dialogue() function parses
this and yields an AgentOutputClarification
event. The response_schema field (when set) tells the application what
structured data the agent expects:
if isinstance(event, AgentOutputClarification):
print(f"Question: {event.question}")
if event.response_schema is not None:
print(f"Expected format: {event.response_schema.__name__}")
# e.g., Expected format: OrderLookupInput
The schema is a lightweight type reference — the application decides how to collect and validate the human's response.
7 Complete script¤
dialogue_support.py
"""Human-agent dialogue with clarification support."""
import asyncio
from crewmaster.agents.agent import AgentConfig
from crewmaster.conversation.channel import Message
from crewmaster.conversation.dialogue import (
AgentOutputClarification,
DialogueAgentMessage,
DialogueComplete,
dialogue,
)
from crewmaster.execution.drivers.pydantic_ai import PydanticAIDriver
# ── Agent ────────────────────────────────────────────────────────
support_agent = AgentConfig(
name="support_agent",
identity_blocks=[
"""You are a customer support agent for NovaTech.
If a customer asks about an order, account, or billing and hasn't
provided enough info (order number, account ID, email), request
clarification. Use the structured format:
---CLARIFICATION---
question: <your question>
---END CLARIFICATION---
For general questions, answer directly. Be friendly and concise."""
],
)
# ── Dialogue helpers ─────────────────────────────────────────────
async def chat_turn(driver, agent, human_message, history=None):
events = []
async for event in dialogue(
agent=agent,
human_message=human_message,
history=history or [],
runtime=driver,
):
events.append(event)
return events
async def support_session(driver, initial_message: str):
print(f"User: {initial_message}")
history: list[Message] = []
current = initial_message
for _ in range(5):
events = await chat_turn(driver, support_agent, current, history)
for event in events:
if isinstance(event, DialogueAgentMessage):
print(f"\nAgent: {event.content}")
history.append(Message(role="user", content=current))
history.append(Message(role="assistant", content=event.content))
return
elif isinstance(event, AgentOutputClarification):
print(f"\nAgent (needs info): {event.question}")
history.append(Message(role="user", content=current))
history.append(Message(
role="assistant",
content=f"[CLARIFICATION] {event.question}"
))
current = input("\nYour response: ")
print(f"User: {current}")
break
print("\n[Session ended]")
# ── Execute ──────────────────────────────────────────────────────
async def main():
driver = PydanticAIDriver(model_name="openai:gpt-4o-mini")
print("=== General question ===")
await support_session(driver, "What are your pricing plans?")
print("\n=== Missing order info ===")
await support_session(driver, "What's the status of my order?")
asyncio.run(main())
Next steps¤
- Combine dialogue with a sequential channel for scenarios where a human converses with multiple specialized agents in sequence.
- Add typed output models to the dialogue with
DialogueComplete.typed_outputfor structured responses. - Read the Conversation API reference for
full documentation on
dialogue,AgentOutputClarification, andMessage.