API Reference¶
Complete API documentation for LayerCode Gym.
Core Classes¶
LayercodeClient¶
The main client for running voice agent conversations.
Constructor¶
LayercodeClient(
simulator: UserSimulatorProtocol,
settings: Settings | None = None,
turn_callback: TurnCallback | None = None,
conversation_callback: ConversationCallback | None = None,
data_processor: ResponseDataProcessor | None = None
)
Parameters:
simulator: UserSimulator instance that generates user responsessettings: Optional Settings object (defaults to environment variables)turn_callback: Optional callback called after each turnconversation_callback: Optional callback called at conversation enddata_processor: Optional processor forresponse.dataevents (tool calls, UI updates)
Methods¶
run()¶
Run the conversation and return the conversation ID.
Returns: str - Unique conversation ID
Example:
UserSimulator¶
Factory class for creating user simulators.
Factory Methods¶
from_text()¶
@classmethod
def from_text(
cls,
messages: list[str],
send_as_text: bool = True,
tts_engine: TTSEngineProtocol | None = None,
settings: Settings | None = None
) -> UserSimulatorProtocol
Create a simulator with fixed text messages.
Parameters:
messages: List of text messages to sendsend_as_text: If True, send as text; if False, convert to audio via TTStts_engine: Optional custom TTS engine (auto-created if None and send_as_text=False)settings: Optional Settings object
Returns: UserSimulatorProtocol instance
Example:
from_files()¶
Create a simulator that streams pre-recorded audio files.
Parameters:
files: List of Path objects pointing to audio files (WAV or MP3)
Returns: UserSimulatorProtocol instance
Example:
from pathlib import Path
simulator = UserSimulator.from_files(
files=[Path("audio1.wav"), Path("audio2.wav")]
)
from_agent()¶
@classmethod
def from_agent(
cls,
persona: Persona | None = None,
agent: Agent | None = None,
deps: Any = None,
model: str = "openai:gpt-5-mini",
max_turns: int = 5,
send_as_text: bool = False,
tts_engine: TTSEngineProtocol | None = None,
settings: Settings | None = None
) -> UserSimulatorProtocol
Create an AI-driven simulator with dynamic responses.
Parameters:
persona: Persona object defining user background and intentagent: Optional custom PydanticAI Agent (overrides persona)deps: Optional dependencies for custom agentmodel: Model name (e.g., "openai:gpt-5-mini", "anthropic:claude-3-5-sonnet")max_turns: Maximum number of turns before endingsend_as_text: If True, send as text; if False, use TTStts_engine: Optional custom TTS enginesettings: Optional Settings object
Returns: UserSimulatorProtocol instance
Example:
simulator = UserSimulator.from_agent(
persona=Persona(
background_context="You are a busy executive",
intent="You want quick answers"
),
model="openai:gpt-5-mini",
max_turns=5
)
Persona¶
Defines user persona for AI-driven simulators.
Constructor¶
Parameters:
background_context: Description of user's background, personality, and situationintent: User's goal or intent for the conversation
Example:
persona = Persona(
background_context="""
You are Sarah, a 35-year-old small business owner.
You're tech-savvy but busy.
""",
intent="You want to understand pricing and features."
)
Settings¶
Configuration settings for LayerCode Gym.
Constructor¶
Settings(
server_url: str,
agent_id: str,
tts_model: str = "gpt-4o-mini-tts",
tts_voice: str = "coral",
tts_instructions: str = "",
chunk_ms: int = 100,
chunk_interval: float = 0.0,
output_root: str = "./conversations"
)
Parameters:
server_url: Your backend server URLagent_id: Your Layercode agent IDtts_model: OpenAI TTS model nametts_voice: TTS voice (alloy, echo, fable, onyx, nova, shimmer, coral)tts_instructions: Optional voice instructionschunk_ms: Audio chunk size in millisecondschunk_interval: Delay between chunks in secondsoutput_root: Directory for saving conversation results
Example:
settings = Settings(
server_url="http://localhost:8001",
agent_id="your_agent_id",
tts_voice="alloy"
)
Environment Variables:
Settings can be loaded from environment variables:
SERVER_URL="http://localhost:8001"
LAYERCODE_AGENT_ID="your_agent_id"
OPENAI_TTS_MODEL="gpt-4o-mini-tts"
OPENAI_TTS_VOICE="coral"
OPENAI_TTS_INSTRUCTIONS="Speak clearly"
LAYERCODE_CHUNK_MS="100"
LAYERCODE_CHUNK_INTERVAL="0.0"
LAYERCODE_OUTPUT_ROOT="./conversations"
LOGFIRE_TOKEN="..." # Optional: enable LogFire observability
Evaluation¶
CriteriaJudge¶
LLM-as-judge for evaluating conversations against pass/fail criteria.
Constructor¶
CriteriaJudge(
criteria: Sequence[str],
*,
additional_context: str | None = None,
model: str = "openai:gpt-5-mini"
)
Parameters:
criteria: List of evaluation criteria as true/false questionsadditional_context: Optional context about the conversation's purposemodel: LLM model for evaluation (usegpt-5for production accuracy)
Methods¶
evaluate()¶
Evaluate a conversation against the defined criteria.
save_results()¶
Save evaluation results to judge_evaluation.json in the conversation folder.
Example:
judge = CriteriaJudge(
criteria=[
"Did the agent answer the user's question?",
"Was the agent polite?"
],
# Note: gpt-5-mini is fast/cheap; use gpt-5 for production
model="openai:gpt-5-mini"
)
async def on_end(log):
result = await judge.evaluate(log)
judge.save_results(result, log.conversation_id, settings.output_root)
client = LayercodeClient(
simulator=simulator,
conversation_callback=on_end
)
JudgeOutput¶
Structured output from CriteriaJudge.
Attributes:
reasoning: str- Explanation of the evaluationcriteria_results: list[dict]- Per-criterion results:[{"criterion_id": 1, "passed": true}, ...]overall_pass: bool- True only if ALL criteria passed
Callbacks¶
TurnCallback¶
from typing import Callable, Awaitable
TurnCallback = Callable[
[int, str, str, str],
Awaitable[None]
]
Callback function called after each conversation turn.
Parameters:
turn_number: int- Current turn number (0-indexed)user_message: str- User's message textagent_message: str- Agent's response textconversation_id: str- Unique conversation ID
Example:
async def my_callback(
turn_number: int,
user_message: str,
agent_message: str,
conversation_id: str
) -> None:
print(f"Turn {turn_number}: User said '{user_message}'")
ConversationCallback¶
from typing import Callable, Awaitable
from layercode_gym.models import ConversationLog
ConversationCallback = Callable[
[ConversationLog],
Awaitable[None]
]
Callback function called at the end of a conversation.
Parameters:
conversation_log: ConversationLog- Complete conversation data
Example:
async def my_callback(log: ConversationLog) -> None:
print(f"Conversation {log.conversation_id} complete")
print(f"Total turns: {log.stats['total_turns']}")
Protocols¶
UserSimulatorProtocol¶
Protocol for implementing custom user simulators.
Methods¶
get_response()¶
Generate the next user response.
Parameters:
request: UserRequest- Contains conversation context
Returns: UserResponse or None to end conversation
Example:
from layercode_gym.simulator import (
UserSimulatorProtocol,
UserRequest,
UserResponse
)
class MySimulator(UserSimulatorProtocol):
async def get_response(
self,
request: UserRequest
) -> UserResponse | None:
if request.turn_number > 5:
return None # End conversation
return UserResponse(
text="My response",
audio_path=None,
data=()
)
TTSEngineProtocol¶
Protocol for implementing custom TTS engines.
Methods¶
synthesize()¶
Convert text to speech audio file.
Parameters:
text: str- Text to synthesize**kwargs- Additional parameters
Returns: Path - Path to generated audio file
Example:
from pathlib import Path
from layercode_gym.simulator import TTSEngineProtocol
class MyTTSEngine(TTSEngineProtocol):
async def synthesize(self, text: str, **kwargs) -> Path:
# Call your TTS service
audio_data = await my_tts_api.generate(text)
# Save to file
output_path = Path("output.wav")
output_path.write_bytes(audio_data)
return output_path
ResponseDataProcessor¶
Protocol for processing response.data events into text for AI context.
When the voice agent emits response.data events (tool calls, UI updates), this processor converts the raw data into human-readable text that the AI user simulator can "see" and react to.
Methods¶
__call__()¶
Convert a response.data payload to text.
Parameters:
data: dict- The raw data dictionary from response.data event
Returns: str - Human-readable description (empty string to skip)
Example:
from typing import Any
from layercode_gym import ResponseDataProcessor
class ProductProcessor(ResponseDataProcessor):
def __call__(self, data: dict[str, Any]) -> str:
if data.get("tool") == "search_products":
products = data.get("products", [])
return f"[DISPLAYED: {len(products)} products]"
return ""
client = LayercodeClient(
simulator=simulator,
data_processor=ProductProcessor()
)
Built-in processors:
from layercode_gym import default_data_processor, XMLDataProcessor
# Default: formats data as XML (LLM-friendly)
client = LayercodeClient(data_processor=default_data_processor)
# Configurable XML processor
processor = XMLDataProcessor(root_tag="tool_result")
client = LayercodeClient(data_processor=processor)
Data Models¶
UserRequest¶
Request data passed to user simulator.
Attributes:
conversation_id: str- Unique conversation IDturn_id: str | None- Current turn identifiertext: str | None- Full accumulated text from assistant (all content since last user response)data: Sequence[dict]- All accumulatedresponse.datapayloads from current turndata_text: str | None- Processed data as human-readable text (if data_processor set)wait_context: WaitContext | None- Context about waits during this turn (None if no waits yet)
UserResponse¶
Response from user simulator.
Attributes:
text: str | None- User message textaudio_path: Path | None- Optional path to audio filedata: Sequence[dict]- Data payloads to sendwait_seconds: float | None- If set, skip this turn and wait (2-300 seconds)
Properties:
has_payload: bool- True if response has content to sendis_wait: bool- True if this is a wait response
WaitContext¶
Context about waiting during the current assistant turn. Used by AI agent simulators to track wait state and make informed decisions.
Attributes:
wait_count: int- Number of times we've waited during this turntotal_wait_seconds: float- Total seconds waited during this turnlast_text_len: int- Text length at last wait (to detect new content)
Methods:
record_wait(wait_seconds, current_text_len)- Record a wait eventhas_new_content(current_text_len) -> bool- Check if new content arrived since last waitreset()- Reset context for new turn
WaitForAssistant¶
Signal from AI agent to wait for the assistant to finish before responding. Use when the assistant says "please wait", "processing...", or gives a time estimate.
Attributes:
wait_seconds: float- How long to wait (2-300 seconds). Add ~20% buffer to assistant's estimate.reason: str | None- Optional reason for waiting (for debugging/logging)
Example:
RespondToAssistant¶
Signal from AI agent to send a message to the assistant.
Attributes:
message: str- The message to send
Example:
ConversationLog¶
Complete conversation data.
Attributes:
conversation_id: str- Unique IDagent_id: str- Agent IDstarted_at: str- ISO timestampended_at: str- ISO timestampturns: list[Turn]- All conversation turnsstats: dict- Aggregate statistics
Stats Dictionary:
{
"total_turns": int,
"duration_seconds": float,
"avg_latency_ms": float,
"avg_ttfab_ms": float,
"total_user_words": int,
"total_agent_words": int
}
Turn¶
Single conversation turn.
Attributes:
turn_number: int- Turn indexuser_message: Message- User's messageagent_message: Message- Agent's response
Message¶
Single message in conversation.
Attributes:
text: str- Message texttimestamp: str- ISO timestampaudio_path: str | None- Optional audio file pathttfab_ms: int | None- Time to first audio byte (agent messages only)
Type Hints¶
LayerCode Gym is fully typed with mypy --strict compatibility.
from layercode_gym import (
LayercodeClient,
UserSimulator,
Persona,
Settings
)
from layercode_gym.simulator import (
UserSimulatorProtocol,
TTSEngineProtocol,
UserRequest,
UserResponse,
WaitContext,
WaitForAssistant,
RespondToAssistant
)
from layercode_gym.callbacks import (
TurnCallback,
ConversationCallback,
create_judge_callback
)
from layercode_gym.models import (
ConversationLog,
Turn,
Message
)
Run type checks:
Next Steps¶
- Examples - See practical usage examples
- Advanced Usage - LogFire integration and custom implementations
- Concepts - Understand the architecture