VoicedForm is a LangGraph-based application designed to facilitate voice-driven form completion using AI agents. The project demonstrates a multi-node workflow system that guides users through structured form completion processes with AI assistance.
- Framework: LangGraph (>=0.2.6)
- AI/LLM: OpenAI GPT-4 via LangChain
- Language: Python 3.9+
- Development Tools: LangGraph Studio, LangSmith
- Testing: pytest with async support (anyio)
- Linting/Formatting: ruff, mypy
- Container: Docker with Wolfi Linux distro
VoicedForm/
├── src/agent/ # Main agent implementation
│ ├── __init__.py # Module exports
│ └── graph.py # Template graph definition
├── tests/
│ ├── unit_tests/ # Unit test suite
│ └── integration_tests/ # Integration test suite
├── .github/workflows/ # CI/CD pipelines
├── voicedform_graph.py # Custom VoicedForm workflow
├── test_langsmith.py # LangSmith integration test
├── langgraph.json # LangGraph server configuration
├── pyproject.toml # Project dependencies
└── Makefile # Development commands
The application follows a state-based graph architecture using LangGraph:
Input → Supervisor → Form Selector → Form Completion → Validator → Output
- State Graph Pattern: Nodes represent discrete processing steps with shared state
- LLM Integration: OpenAI models provide intelligent decision-making and natural language processing
- Configuration-Driven: Runtime behavior controlled via configurable parameters
- Async-First: Asynchronous execution for scalability
- Synchronous Execution: Nodes execute sequentially based on graph edges
- State Persistence: State flows through nodes, accumulating data
- Terminal Condition: Graph terminates at END node after validation
Purpose: Minimal template demonstrating LangGraph structure.
@dataclass
class State:
changeme: str = "example" # Placeholder input fieldclass Configuration(TypedDict):
my_configurable_param: str # Runtime configuration parametercall_model: Single processing node that returns configured output
- Entry Point:
__start__→call_model - Compilation: Named "New Graph"
Purpose: Production implementation for form completion workflows.
class GraphState(TypedDict, total=False):
input: Optional[str] # User input
form_type: Optional[str] # Type of form to complete
first_field: Optional[str] # First field description from LLM
form_complete: Optional[str] # Completed form content
valid: Optional[bool] # Validation status- Role: Entry point, route determination
- Function: Decides which form type to process
- Output: Sets
form_type(currently stubbed to "accident_report")
- Role: Form template identification
- Function: Uses LLM to describe form structure
- Input:
form_type - Output:
first_field(description of initial form field) - LLM Prompt: "You are helping complete a form of type: {form_type}. What's the first field?"
- Role: Interactive form filling
- Function: Simulates form completion with LLM assistance
- Input:
first_field - Output:
form_complete - LLM Prompt: "Let's pretend to fill out this form together."
- Role: Output verification
- Function: Validates form completion
- Logic: Checks for presence of
form_completein state - Output:
valid(boolean)
START → supervisor → form_selector → form_completion → validator → END
State is a mutable dictionary that flows through the graph:
- Input State: Initialized with optional user input
- Node Processing: Each node reads and updates state
- State Accumulation: New keys added without overwriting existing
- Terminal State: Final state contains all accumulated data
| Key | Type | Source Node | Purpose |
|---|---|---|---|
input |
str | External | User's initial input |
form_type |
str | supervisor | Form category selection |
first_field |
str | form_selector | LLM-generated field description |
form_complete |
str | form_completion | Completed form content |
valid |
bool | validator | Validation status |
llm = ChatOpenAI(model="gpt-4", temperature=0)- Model: GPT-4 (OpenAI)
- Temperature: 0 (deterministic output)
- API Key: Loaded from environment variable
OPENAI_API_KEY
- Invoke: Synchronous LLM call with string prompt
- Response: Returns structured message with
contentattribute - Context Injection: Form type and state data embedded in prompts
Required in .env file:
OPENAI_API_KEY=sk-... # OpenAI API key
LANGSMITH_API_KEY=lsv2... # LangSmith tracing key (optional)
LANGSMITH_PROJECT=new-agent # LangSmith project name{
"dependencies": ["."],
"graphs": {
"agent": "./src/agent/graph.py:graph"
},
"env": ".env",
"image_distro": "wolfi"
}- Dependencies: Local package installation
- Graphs: Maps "agent" to template graph
- Environment: Loads
.envfile - Image: Uses Wolfi Linux for containers
langgraph>=0.2.6 # Graph framework
python-dotenv>=1.0.1 # Environment management
langchain-openai # OpenAI LLM integration
langchain-core # Core LangChain utilitiespytest>=8.3.5 # Testing framework
anyio>=4.7.0 # Async utilities
langgraph-cli[inmem]>=0.2.8 # LangGraph CLI
mypy>=1.13.0 # Type checking
ruff>=0.8.2 # Linting and formatting# Install dependencies
pip install -e . "langgraph-cli[inmem]"
# Configure environment
cp .env.example .env
# Edit .env with API keys
# Start development server
langgraph dev| Command | Description |
|---|---|
make test |
Run unit tests |
make integration_tests |
Run integration tests |
make lint |
Run linters (ruff, mypy) |
make format |
Format code with ruff |
make test_watch |
Run tests in watch mode |
- Linting: ruff with pycodestyle, pyflakes, isort
- Type Checking: mypy with strict mode
- Documentation: Google-style docstrings
- Formatting: Automatic with ruff
File: test_configuration.py
- Purpose: Validate graph instantiation
- Test: Confirms graph is a Pregel instance
- Coverage: Graph structure validation
File: test_graph.py
- Purpose: End-to-end graph execution
- Test:
test_agent_simple_passthrough- Input:
{"changeme": "some_val"} - Assertion: Non-null response
- Input:
- Markers:
@pytest.mark.anyio,@pytest.mark.langsmith
test_langsmith.py: Validates OpenAI connectionvoicedform_graph.py: Executable workflow test with debug output
The application deploys as a LangGraph Server instance:
langgraph dev # Development mode with hot reloadServer Features:
- REST API endpoints for graph invocation
- WebSocket support for streaming
- Built-in authentication (if configured)
- LangSmith tracing integration
- Base Image: Wolfi Linux (minimal, security-focused)
- Build System: setuptools with wheel
- Package Structure: Dual namespace (
agent,langgraph.templates.agent)
Located in .github/workflows/:
unit-tests.yml: Runs unit test suite on push/PRintegration-tests.yml: Runs integration tests with LangSmith
- Tracing: Automatic trace capture for all LLM calls
- Project Isolation: Traces grouped by
LANGSMITH_PROJECT - Debug Output: Console logging for local development
Console logging with emoji prefixes for visual clarity:
- 🧭 Supervisor decisions
- 📄 Form selection
- ✍️ Form completion
- ✅ Validation results
- Define node function with signature:
(state: dict) -> dict - Wrap with
RunnableLambdaif needed - Add to graph:
graph.add_node("name", function) - Define edges:
graph.add_edge("source", "target")
Modify Configuration class in graph.py:
class Configuration(TypedDict):
system_prompt: str
model_name: str
temperature: floatUpdate langgraph.json to expose multiple graphs:
{
"graphs": {
"agent": "./src/agent/graph.py:graph",
"voicedform": "./voicedform_graph.py:dag"
}
}- Supervisor Stubbed: Always returns "accident_report"
- No Persistence: State not saved between sessions
- Single Form Type: Only accident reports supported
- Mock Completion: Form filling is simulated
- Basic Validation: Only checks for key presence
- Dynamic Form Selection: LLM-based form type detection from user input
- Multi-Step Forms: Support for complex, multi-page forms
- Database Integration: Store completed forms
- Voice Input: Integrate speech-to-text for true voice-driven UX
- Validation Rules: Schema-based validation with error recovery
- User Authentication: Session management and user identification
- Form Templates: JSON-based form definitions
- Export Formats: PDF, JSON, CSV output options
from agent import graph
# Synchronous
result = graph.invoke({"changeme": "input"})
# Asynchronous
result = await graph.ainvoke({"changeme": "input"})from voicedform_graph import dag
result = dag.invoke({}) # Empty initial state
# Returns: {
# "form_type": "accident_report",
# "first_field": "...",
# "form_complete": "...",
# "valid": True
# }result = graph.invoke(
{"changeme": "input"},
config={"configurable": {"my_configurable_param": "value"}}
)- Storage: Environment variables only (never commit)
- Rotation: Regular key rotation recommended
- Scope: Minimum required permissions
- Current: No input sanitization
- Risk: Potential prompt injection
- Recommendation: Validate and sanitize user inputs
- PII Concern: Forms may contain sensitive data
- Recommendation: Implement encryption at rest
- Compliance: Consider GDPR/HIPAA requirements
- LLM Calls: 2 per execution (form_selector, form_completion)
- Expected Latency: 2-5 seconds per form completion
- Bottleneck: OpenAI API response time
- Concurrency: Supports async execution
- Rate Limits: Bound by OpenAI tier limits
- Optimization: Consider caching for repeated queries
- Initial prototype with basic form completion workflow
- Template graph implementation
- OpenAI GPT-4 integration
- LangGraph Studio support
- CI/CD pipeline setup
- Follow PEP 8 via ruff formatting
- Type hints required (enforced by mypy --strict)
- Docstrings required for public functions (Google style)
- Test coverage for new features
- Create feature branch
- Implement changes with tests
- Run
make lintandmake test - Submit PR with description
- Ensure CI passes
MIT License - See LICENSE file for details
- Original Template: https://github.com/langchain-ai/new-langgraph-project
- Author: William Fu-Hinthorn (hinthornw@users.noreply.github.com)
Last Updated: 2025-11-13 Document Version: 1.0