A lightweight Python framework to structure LLM conversations using a validated state model and a tool-based interaction design.
- Installation
- Quick Start
- ποΈ Core Concepts
- π¨ Advanced Features
- π§ͺ Built-in Validators
- π Examples
- π§ Configuration
- π§ͺ Testing
- π API Reference
- π License
- π Roadmap
pip install stateagentHere's a simple example to get you up and running:
π€ Hi! I need to collect some contact information from you.
π€ My name is John Smith
π€ Great! I've recorded your name as John Smith.
Now, could you please provide your email address?
π€ john@company.com
π€ Perfect! I've saved your email.
Finally, what message would you like to send?
π€ I'm interested in learning more about your services.
π€ Excellent! I have all the information I need:
β Name: John Smith
β Email: john@company.com
β Message: I'm interested in learning more about your services.
Thank you for your submission!
Define your data structure with validation:
from dataclasses import dataclass, field
from llm_structured_agent import StateModel, Field, create_range_validator
@dataclass
class UserProfile(StateModel):
name: str = field(default=None)
age: int = field(default=None)
email: str = field(default=None)
_field_info = {
'name': Field(
required=True,
description="User's full name",
validator=create_length_validator(min_length=2)
),
'age': Field(
required=True,
description="User's age",
validator=create_range_validator(min_val=13, max_val=120)
),
'email': Field(
required=True,
description="Email address",
validator=create_email_validator()
)
}The LLM automatically gets these tools to interact with state:
set_field(field_name, value)- Update a field with validationvalidate_state()- Check if all required fields are completeget_state()- View current state snapshotclear_state()- Reset all fields to defaults
The main orchestrator that manages the conversation:
agent = StructuredAgent(
state_cls=UserProfile,
llm=OpenAIAdapter(model="gpt-4o-mini"),
hooks={
"on_field_set": lambda state, field: print(f"Updated {field}"),
"on_submit": lambda state: save_to_database(state.snapshot())
},
max_turns=20
)import re
from llm_structured_agent import ValidationError
def validate_phone_number(value: str):
if not re.match(r'^\+?[\d\s\-]+$', value):
raise ValidationError("Invalid phone format")
return value.strip()
@dataclass
class ContactInfo(StateModel):
phone: str = field(default=None)
_field_info = {
'phone': Field(
required=True,
description="Phone number with country code",
validator=validate_phone_number
)
}@dataclass
class OrderForm(StateModel):
item_type: str = field(default=None)
shipping_address: str = field(default=None)
gift_message: str = field(default=None)
def validate(self) -> List[str]:
missing = super().validate()
# Gift message required for gift items
if (self.item_type == "gift" and
not self.gift_message):
missing.append("gift_message")
return missingdef on_field_updated(state, field_name):
print(f"π {field_name} updated to: {getattr(state, field_name)}")
def on_form_complete(state):
# Save to database
save_to_database(state.snapshot())
# Send notification
send_notification(state.email, "Form completed!")
# Log completion
logger.info(f"Form completed for {state.name}")
agent = StructuredAgent(
state_cls=MyForm,
llm=OpenAIAdapter(),
hooks={
"on_field_set": on_field_updated,
"on_submit": on_form_complete
}
)# OpenAI (default)
agent = StructuredAgent(
state_cls=MyState,
llm=OpenAIAdapter(model="gpt-4o-mini")
)
# Custom LLM provider
class CustomLLMAdapter(LLMAdapter):
def chat(self, messages, tools=None):
# Your implementation
pass
def extract_function_calls(self, response):
# Your implementation
pass
agent = StructuredAgent(
state_cls=MyState,
llm=CustomLLMAdapter()
)# For API endpoints or batch processing
agent = StructuredAgent(state_cls=ContactForm, llm=OpenAIAdapter())
# Process single message
result = agent.process_single_turn("My name is John and email is john@example.com")
if result["complete"]:
print("Form completed:", result["state"])
else:
print("Next response:", result["message"])
print("Still missing:", result["missing_fields"])The library includes common validators:
from llm_structured_agent import (
create_email_validator,
create_range_validator,
create_length_validator,
create_regex_validator,
create_choice_validator
)
# Email validation
email_validator = create_email_validator()
# Numeric range
age_validator = create_range_validator(min_val=18, max_val=100)
# String length
name_validator = create_length_validator(min_length=2, max_length=50)
# Regex pattern
phone_validator = create_regex_validator(
pattern=r'^\+?[\d\s\-]+$',
error_message="Invalid phone number format"
)
# Choice validation
department_validator = create_choice_validator([
"Engineering", "Sales", "Marketing", "HR"
])The library includes complete examples:
# Run the KYC example
python -m llm_structured_agent.examples.kyc_example
# Or if installed via pip
kyc-agent# Run the employee onboarding example
python -m llm_structured_agent.examples.onboarding_example
# Or if installed via pip
onboarding-agent# Required for OpenAI
export OPENAI_API_KEY="your-openai-api-key"
# Optional: Custom model
export OPENAI_MODEL="gpt-4o-mini"
custom_prompt = """You are a helpful assistant collecting user information.
Be conversational and friendly while systematically gathering:
- Name (required)
- Email (required)
- Phone (optional)
Use the provided tools to store information and check completeness."""
agent = StructuredAgent(
state_cls=ContactForm,
llm=OpenAIAdapter(),
system_prompt=custom_prompt
)# Install with dev dependencies
pip install stateagent[dev]
# Run tests
pytest
# Run with coverage
pytest --cov=llm_structured_agent
# Run specific test
pytest tests/test_state.py -vBase class for defining structured state:
class StateModel:
def set_field(self, name: str, value: Any) -> None
def validate(self) -> List[str]
def snapshot(self) -> Dict[str, Any]
def clear(self) -> None
def get_field_info(self, name: str) -> Optional[Field]Field definition with validation:
class Field:
def __init__(
self,
required: bool = False,
description: str = "",
validator: Optional[Callable] = None,
default: Any = None
)Main orchestration class:
class StructuredAgent:
def __init__(
self,
state_cls: type,
llm: LLMAdapter,
hooks: Optional[Dict[str, Callable]] = None,
max_turns: int = 20,
system_prompt: Optional[str] = None
)
def run_chat(self) -> None
def process_single_turn(self, user_input: str) -> Dict[str, Any]
def reset(self) -> NoneThis project is licensed under the MIT License - see the LICENSE file for details.
- More LLM provider integrations
- FastAPI integration
- Streamlit demo interface
- Database persistence layer
- MCP Server
Built with β€οΈ for the AI community