Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/basic/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def get_weather(city: Annotated[str, "The city to get the weather for"]) -> Weat
print("[debug] get_weather called")
return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.")


agent = Agent(
name="Hello world",
instructions="You are a helpful agent.",
Expand Down
8 changes: 8 additions & 0 deletions src/agents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,14 @@ def __post_init__(self):
if not isinstance(self.name, str):
raise TypeError(f"Agent name must be a string, got {type(self.name).__name__}")

# Enhanced agent name validation with helpful guidance
from .util._transforms import validate_agent_name

try:
validate_agent_name(self.name)
except ValueError as e:
raise ValueError(f"Invalid agent name: {e}") from e

if self.handoff_description is not None and not isinstance(self.handoff_description, str):
raise TypeError(
f"Agent handoff_description must be a string or None, "
Expand Down
26 changes: 25 additions & 1 deletion src/agents/realtime/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,29 @@ class RealtimeModelTracingConfig(TypedDict):
"""Additional metadata to include with the trace."""


class RealtimeAudioStorageConfig(TypedDict):
"""Configuration for audio storage in realtime sessions."""

enabled: NotRequired[bool]
"""Whether audio storage is enabled. Defaults to False."""

storage_path: NotRequired[str]
"""The path where audio files should be stored. If not provided, uses a
default temp directory."""

max_duration_seconds: NotRequired[int]
"""Maximum duration in seconds for stored audio clips. Defaults to 300 (5 minutes)."""

audio_format: NotRequired[RealtimeAudioFormat]
"""The format to store audio in. Defaults to 'pcm16'."""

compression_enabled: NotRequired[bool]
"""Whether to compress stored audio files. Defaults to True."""

retention_days: NotRequired[int]
"""Number of days to retain stored audio files. Defaults to 7."""


class RealtimeRunConfig(TypedDict):
"""Configuration for running a realtime agent session."""

Expand All @@ -184,7 +207,8 @@ class RealtimeRunConfig(TypedDict):
tracing_disabled: NotRequired[bool]
"""Whether tracing is disabled for this run."""

# TODO (rm) Add history audio storage config
audio_storage_config: NotRequired[RealtimeAudioStorageConfig]
"""Configuration for audio storage in realtime sessions."""


class RealtimeUserInputText(TypedDict):
Expand Down
2 changes: 1 addition & 1 deletion src/agents/realtime/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ def _get_new_history(
)
return new_history

# TODO (rm) Add support for audio storage config
# Audio storage config is now available in RealtimeRunConfig.audio_storage_config

# If the item already exists, update it
existing_index = next(
Expand Down
49 changes: 49 additions & 0 deletions src/agents/util/_transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,52 @@ def transform_string_function_style(name: str) -> str:
)

return transformed_name.lower()


def validate_agent_name(name: str) -> None:
"""Validate agent name and provide helpful guidance.

Agent names are used in handoffs, tracing, and debugging. This function ensures
that agent names follow good conventions and will work well throughout the system.

Args:
name: The agent name to validate

Raises:
ValueError: If the name has issues that should be fixed
"""
if not name:
raise ValueError("Agent name cannot be empty")

if not name.strip():
raise ValueError("Agent name cannot be only whitespace")

# Check for common problematic patterns
if name != name.strip():
raise ValueError(
f"Agent name {name!r} has leading/trailing whitespace. "
f"Consider using {name.strip()!r} instead."
)

# Warn about characters that might cause issues in handoffs
problematic_chars = re.findall(r"[^a-zA-Z0-9\s_-]", name)
if problematic_chars:
unique_chars = sorted(set(problematic_chars))
raise ValueError(
f"Agent name {name!r} contains characters {unique_chars} that may cause issues "
f"in handoffs or function calls. Consider using only letters, numbers, spaces, "
f"hyphens, and underscores."
Comment on lines +49 to +56

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow non-ASCII agent names

The new validation rejects any character outside [a-zA-Z0-9\s_-], so names containing accented characters, CJK characters, emoji, etc. now raise ValueError. Prior versions only enforced type checking, so existing agents with Unicode names will fail to initialize despite the commit claiming backward compatibility. Unless there is a documented requirement that names must be ASCII, this is a breaking change for multilingual users. Consider broadening the allowed character set or making this restriction optional.

Useful? React with 👍 / 👎.

)

# Check for very long names that might be unwieldy
if len(name) > 100:
raise ValueError(
f"Agent name {name!r} is {len(name)} characters long. "
f"Consider using a shorter, more concise name (under 100 characters)."
)

# Check for names that start with numbers (can cause issues in some contexts)
if name[0].isdigit():
raise ValueError(
f"Agent name {name!r} starts with a number. Consider starting with a letter instead."
)
197 changes: 197 additions & 0 deletions tests/test_agent_name_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
"""Tests for enhanced agent name validation."""

import pytest

from agents import Agent
from agents.util._transforms import validate_agent_name


class TestAgentNameValidation:
"""Test suite for enhanced agent name validation."""

def test_valid_names_pass(self):
"""Test that valid agent names are accepted."""
valid_names = [
"Assistant",
"Customer Service Agent",
"data_analyst",
"Research-Bot",
"Agent_1",
"Multi Word Agent",
"Simple123",
"a", # Single character
"Agent-Helper-Bot",
"user_support_agent",
]

for name in valid_names:
# Should not raise any exception
validate_agent_name(name)
# Should be able to create agent successfully
Agent(name=name)

def test_empty_name_fails(self):
"""Test that empty names are rejected."""
with pytest.raises(ValueError, match="Agent name cannot be empty"):
validate_agent_name("")

with pytest.raises(ValueError, match="Invalid agent name: Agent name cannot be empty"):
Agent(name="")

def test_whitespace_only_name_fails(self):
"""Test that whitespace-only names are rejected."""
whitespace_names = [" ", "\t", "\n", " \t \n "]

for name in whitespace_names:
with pytest.raises(ValueError, match="Agent name cannot be only whitespace"):
validate_agent_name(name)

with pytest.raises(
ValueError, match="Invalid agent name: Agent name cannot be only whitespace"
):
Agent(name=name)

def test_leading_trailing_whitespace_fails(self):
"""Test that names with leading/trailing whitespace are rejected."""
whitespace_names = [
" Agent",
"Agent ",
" Agent ",
"\tAgent",
"Agent\n",
]

for name in whitespace_names:
with pytest.raises(ValueError, match="has leading/trailing whitespace"):
validate_agent_name(name)

with pytest.raises(
ValueError, match="Invalid agent name.*has leading/trailing whitespace"
):
Agent(name=name)

def test_problematic_characters_fail(self):
"""Test that names with problematic characters are rejected."""
problematic_names = [
"Agent@Home", # @ symbol
"Agent#1", # # symbol
"Agent$", # $ symbol
"Agent%Bot", # % symbol
"Agent&Co", # & symbol
"Agent*Star", # * symbol
"Agent+Plus", # + symbol
"Agent=Equal", # = symbol
"Agent|Pipe", # | symbol
"Agent\\Back", # \ symbol
"Agent/Slash", # / symbol
"Agent<Less", # < symbol
"Agent>More", # > symbol
"Agent?Quest", # ? symbol
"Agent!Bang", # ! symbol
"Agent(Paren", # ( symbol
"Agent)Close", # ) symbol
"Agent[Brack", # [ symbol
"Agent]End", # ] symbol
"Agent{Brace", # { symbol
"Agent}Close", # } symbol
"Agent:Colon", # : symbol
"Agent;Semi", # ; symbol
"Agent'Quote", # ' symbol
'Agent"Doub', # " symbol
"Agent,Comma", # , symbol
"Agent.Dot", # . symbol
]

for name in problematic_names:
with pytest.raises(ValueError, match="contains characters .* that may cause issues"):
validate_agent_name(name)

with pytest.raises(
ValueError, match="Invalid agent name.*contains characters .* that may cause issues"
):
Agent(name=name)

def test_names_starting_with_numbers_fail(self):
"""Test that names starting with numbers are rejected."""
number_names = [
"1Agent",
"2nd_Agent",
"99problems",
"0zero",
]

for name in number_names:
with pytest.raises(ValueError, match="starts with a number"):
validate_agent_name(name)

with pytest.raises(ValueError, match="Invalid agent name.*starts with a number"):
Agent(name=name)

def test_very_long_names_fail(self):
"""Test that very long names are rejected."""
long_name = "A" * 101 # 101 characters

with pytest.raises(
ValueError, match="is 101 characters long.*shorter.*under 100 characters"
):
validate_agent_name(long_name)

with pytest.raises(ValueError, match="Invalid agent name.*is 101 characters long"):
Agent(name=long_name)

def test_exactly_100_characters_passes(self):
"""Test that names with exactly 100 characters are accepted."""
name_100_chars = "A" * 100 # Exactly 100 characters

# Should not raise
validate_agent_name(name_100_chars)
Agent(name=name_100_chars)

def test_type_validation_still_works(self):
"""Test that type validation still works as before."""
with pytest.raises(TypeError, match="Agent name must be a string, got int"):
Agent(name=123) # type: ignore

with pytest.raises(TypeError, match="Agent name must be a string, got NoneType"):
Agent(name=None) # type: ignore

def test_error_messages_are_helpful(self):
"""Test that error messages provide helpful guidance."""
# Test whitespace suggestion
with pytest.raises(ValueError) as exc_info:
validate_agent_name(" Agent ")
assert "Consider using 'Agent' instead" in str(exc_info.value)

# Test character list in error
with pytest.raises(ValueError) as exc_info:
validate_agent_name("Agent@#$")
error_msg = str(exc_info.value)
assert "['#', '$', '@']" in error_msg
assert "Consider using only letters, numbers, spaces, hyphens, and underscores" in error_msg

# Test length guidance
with pytest.raises(ValueError) as exc_info:
validate_agent_name("A" * 150)
assert "150 characters long" in str(exc_info.value)
assert "under 100 characters" in str(exc_info.value)

def test_existing_agent_creation_still_works(self):
"""Test that existing valid agent creation patterns still work."""
# These should all work as before
Agent(name="Assistant")
Agent(name="Customer Service")
Agent(name="data_processor")
Agent(name="Multi-Agent-System")

# Test with other parameters
Agent(name="Test Agent", instructions="Test instructions")
Agent(name="Another Agent", handoff_description="Test description")

def test_validation_function_direct_usage(self):
"""Test that the validation function can be used directly."""
# Should not raise
validate_agent_name("Valid Agent")

# Should raise with helpful message
with pytest.raises(ValueError, match="Agent name cannot be empty"):
validate_agent_name("")