
Product
Introducing Webhook Events for Alert Changes
Add real-time Socket webhook events to your workflows to automatically receive software supply chain alert changes in real time.
jaf-py
Advanced tools
A purely functional agent framework with immutable state and composable tools - Python implementation
A purely functional agent framework with immutable state and composable tools, professionally converted from TypeScript to Python. JAF enables building production-ready AI agent systems with built-in security, observability, and error handling.
Production Ready: Complete feature parity with TypeScript version, comprehensive test suite, and production deployment support.
Get Started β | API Reference β | Examples β
/docs# Install from GitHub (development version)
pip install git+https://github.com/xynehq/jaf-py.git
# Or install with all optional dependencies
pip install "jaf-py[all] @ git+https://github.com/xynehq/jaf-py.git"
# Install specific feature sets
pip install "jaf-py[server] @ git+https://github.com/xynehq/jaf-py.git" # FastAPI server support
pip install "jaf-py[memory] @ git+https://github.com/xynehq/jaf-py.git" # Redis/PostgreSQL memory providers
pip install "jaf-py[visualization] @ git+https://github.com/xynehq/jaf-py.git" # Graphviz visualization tools
pip install "jaf-py[tracing] @ git+https://github.com/xynehq/jaf-py.git" # OpenTelemetry and Langfuse tracing
pip install "jaf-py[dev] @ git+https://github.com/xynehq/jaf-py.git" # Development tools
JAF includes a powerful CLI for project management:
# Initialize a new JAF project
jaf init my-agent-project
# Run the development server
jaf server --host 0.0.0.0 --port 8000
# Show version and help
jaf version
jaf --help
git clone https://github.com/xynehq/jaf-py
cd jaf-py
pip install -e ".[dev]"
# Run tests
pytest
# Type checking and linting
mypy jaf/
ruff check jaf/
black jaf/
# Documentation
pip install -r requirements-docs.txt
./docs.sh serve # Start documentation server
./docs.sh deploy # Deploy to GitHub Pages
The complete, searchable documentation is available at xynehq.github.io/jaf-py with:
For offline access, documentation is also available in the docs/ directory:
jaf-py/
βββ docs/ # Complete documentation
βββ jaf/ # Core framework package
β βββ core/ # Core types and engine
β βββ memory/ # Conversation persistence
β βββ providers/ # External integrations (LiteLLM, MCP)
β βββ policies/ # Validation and security
β βββ server/ # FastAPI server
β βββ cli.py # Command-line interface
βββ examples/ # Example applications
βββ tests/ # Test suite
JAF includes powerful visualization capabilities to help you understand and document your agent systems.
First, install the system Graphviz dependency:
# macOS
brew install graphviz
# Ubuntu/Debian
sudo apt-get install graphviz
# Windows (via Chocolatey)
choco install graphviz
Then install JAF with visualization support:
pip install "jaf-py[visualization]"
import asyncio
from jaf import Agent, Tool, generate_agent_graph, GraphOptions
# Create your agents
agent = Agent(
name='MyAgent',
instructions=lambda state: "I am a helpful assistant.",
tools=[my_tool],
handoffs=['OtherAgent']
)
# Generate visualization
async def main():
result = await generate_agent_graph(
[agent],
GraphOptions(
title="My Agent System",
output_path="./my-agents.png",
color_scheme="modern",
show_tool_details=True
)
)
if result.success:
print(f" Visualization saved to: {result.output_path}")
else:
print(f"β Error: {result.error}")
asyncio.run(main())
default, modern, or minimal themesdot, circo, neato, etc.)The visualization system generates clear, professional diagrams showing:
from jaf.visualization import run_visualization_examples
# Run comprehensive examples
await run_visualization_examples()
# This generates multiple example files:
# - ./examples/agent-graph.png (agent system overview)
# - ./examples/tool-graph.png (tool ecosystem)
# - ./examples/runner-architecture.png (complete system)
# - ./examples/agent-modern.png (modern color scheme)
from dataclasses import dataclass
from pydantic import BaseModel, Field
from jaf import Agent, Tool, RunState, run
# Define your context type
@dataclass
class MyContext:
user_id: str
permissions: list[str]
# Define tool schema
class CalculateArgs(BaseModel):
expression: str = Field(description="Math expression to evaluate")
# Create a tool
class CalculatorTool:
@property
def schema(self):
return type('ToolSchema', (), {
'name': 'calculate',
'description': 'Perform mathematical calculations',
'parameters': CalculateArgs
})()
async def execute(self, args: CalculateArgs, context: MyContext) -> str:
result = eval(args.expression) # Don't do this in production!
return f"{args.expression} = {result}"
# Define an agent
def create_math_agent():
def instructions(state):
return 'You are a helpful math tutor'
return Agent(
name='MathTutor',
instructions=instructions,
tools=[CalculatorTool()]
)
import asyncio
from jaf import run, make_litellm_provider, generate_run_id, generate_trace_id
from jaf.core.types import RunState, RunConfig, Message
async def main():
model_provider = make_litellm_provider('http://localhost:4000')
math_agent = create_math_agent()
config = RunConfig(
agent_registry={'MathTutor': math_agent},
model_provider=model_provider,
max_turns=10,
on_event=lambda event: print(event), # Real-time tracing
)
initial_state = RunState(
run_id=generate_run_id(),
trace_id=generate_trace_id(),
messages=[Message(role='user', content='What is 2 + 2?')],
current_agent_name='MathTutor',
context=MyContext(user_id='user123', permissions=['user']),
turn_count=0,
)
result = await run(initial_state, config)
print(result.outcome.output if result.outcome.status == 'completed' else result.outcome.error)
asyncio.run(main())
from jaf.policies.validation import create_path_validator, create_permission_validator, compose_validations
# Create individual validators
path_validator = create_path_validator(['/shared', '/public'])
permission_validator = create_permission_validator('admin', lambda ctx: ctx)
# Compose them
combined_validator = compose_validations(path_validator, permission_validator)
# Apply to tools
secure_file_tool = with_validation(base_file_tool, combined_validator)
from jaf.policies.validation import create_content_filter, create_length_guardrail
config = RunConfig(
# ... other config
initial_input_guardrails=[
create_content_filter(['spam', 'inappropriate']), # Requires blocked patterns
create_length_guardrail(max_length=1000, min_length=1)
],
final_output_guardrails=[
create_content_filter(['harmful', 'unsafe'])
],
)
JAF 2.2+ introduces powerful agent-as-tool capabilities, allowing you to use agents as tools within other agents for hierarchical orchestration:
from jaf.core.agent_tool import create_agent_tool
from jaf.core.types import create_json_output_extractor
# Create specialized agents
spanish_agent = Agent(
name="spanish_translator",
instructions=lambda state: "Translate text to Spanish",
output_codec=TranslationOutput
)
french_agent = Agent(
name="french_translator",
instructions=lambda state: "Translate text to French",
output_codec=TranslationOutput
)
# Convert agents to tools with conditional enabling
spanish_tool = spanish_agent.as_tool(
tool_name="translate_to_spanish",
tool_description="Translate text to Spanish",
max_turns=3,
custom_output_extractor=create_json_output_extractor(),
is_enabled=True # Always enabled
)
french_tool = french_agent.as_tool(
tool_name="translate_to_french",
tool_description="Translate text to French",
max_turns=3,
custom_output_extractor=create_json_output_extractor(),
is_enabled=lambda context, agent: context.language_preference == "french_spanish"
)
# Create orchestrator agent using agent tools
orchestrator = Agent(
name="translation_orchestrator",
instructions=lambda state: "Use translation tools to respond in multiple languages",
tools=[spanish_tool, french_tool]
)
from jaf.policies.handoff import create_handoff_guardrail, HandoffPolicy
from pydantic import BaseModel
from enum import Enum
class AgentName(str, Enum):
MATH_TUTOR = 'MathTutor'
FILE_MANAGER = 'FileManager'
class HandoffOutput(BaseModel):
agent_name: AgentName
def create_triage_agent():
def instructions(state):
return 'Route requests to specialized agents'
return Agent(
name='TriageAgent',
instructions=instructions,
tools=[], # Regular tools would go here
handoffs=['MathTutor', 'FileManager'], # Allowed handoff targets
output_schema=HandoffOutput,
)
from jaf.core.tracing import ConsoleTraceCollector, FileTraceCollector, create_composite_trace_collector
# Console logging
console_tracer = ConsoleTraceCollector()
# File logging
file_tracer = FileTraceCollector('./traces.log')
# Composite tracing
tracer = create_composite_trace_collector(console_tracer, file_tracer)
config = RunConfig(
# ... other config
on_event=tracer.collect,
)
JAF 2.2+ includes built-in OpenTelemetry support for distributed tracing:
import os
from jaf.core.tracing import create_composite_trace_collector, ConsoleTraceCollector
# Configure OpenTelemetry endpoint
os.environ["TRACE_COLLECTOR_URL"] = "http://localhost:4318/v1/traces"
# Tracing will be automatically configured when creating a composite collector
trace_collector = create_composite_trace_collector(ConsoleTraceCollector())
config = RunConfig(
# ... other config
on_event=trace_collector.collect,
)
For LLM-specific observability and analytics:
import os
from jaf.core.tracing import create_composite_trace_collector, ConsoleTraceCollector
# Configure Langfuse credentials
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-your-public-key"
os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-your-secret-key"
os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com" # or your self-hosted instance
# Langfuse tracing will be automatically configured
trace_collector = create_composite_trace_collector(ConsoleTraceCollector())
config = RunConfig(
# ... other config
on_event=trace_collector.collect,
)
from jaf.core.errors import JAFErrorHandler
if result.outcome.status == 'error':
formatted_error = JAFErrorHandler.format_error(result.outcome.error)
is_retryable = JAFErrorHandler.is_retryable(result.outcome.error)
severity = JAFErrorHandler.get_severity(result.outcome.error)
print(f"[{severity}] {formatted_error} (retryable: {is_retryable})")
JAF provides a robust A2A communication layer that allows agents to interact with each other. This is useful for building multi-agent systems where different agents have specialized skills.
from jaf.a2a import create_a2a_agent, create_a2a_client, create_a2a_server
# Create agents
echo_agent = create_a2a_agent("EchoBot", "Echoes messages", "You are an echo bot.", [])
ping_agent = create_a2a_agent("PingBot", "Responds to pings", "You are a ping bot.", [])
# Create a server
server_config = {
"agents": {"EchoBot": echo_agent, "PingBot": ping_agent},
"agentCard": {"name": "Test Server"},
"port": 8080,
}
server = create_a2a_server(server_config)
# Create a client
client = create_a2a_client("http://localhost:8080")
from jaf.providers.model import make_litellm_provider
# Connect to LiteLLM proxy for 100+ model support
model_provider = make_litellm_provider(
'http://localhost:4000', # LiteLLM proxy URL
'your-api-key' # Optional API key
)
JAF includes full Model Context Protocol support for seamless tool integration:
from jaf.providers.mcp import create_mcp_stdio_client, create_mcp_tools_from_client
# Connect to MCP server via stdio
mcp_client = create_mcp_stdio_client(['python', '-m', 'my_mcp_server'])
# Initialize and get all available tools
await mcp_client.initialize()
mcp_tools = await create_mcp_tools_from_client(mcp_client)
# Use MCP tools in your agent
def create_mcp_agent():
def instructions(state):
return "You have access to powerful MCP tools for various tasks."
return Agent(
name='MCPAgent',
instructions=instructions,
tools=mcp_tools # Automatically converted JAF tools
)
# SSE client is also supported
from jaf.providers.mcp import create_mcp_sse_client
sse_client = create_mcp_sse_client('http://localhost:8080/sse')
JAF includes a built-in development server for testing agents locally via HTTP endpoints:
from jaf.server import run_server
from jaf.providers.model import make_litellm_provider
def create_my_agent():
def instructions(state):
return 'You are a helpful assistant'
return Agent(
name='MyAgent',
instructions=instructions,
tools=[calculator_tool, greeting_tool]
)
model_provider = make_litellm_provider('http://localhost:4000')
# Start server on port 3000
await run_server(
[create_my_agent()],
{'model_provider': model_provider},
{'port': 3000}
)
Server provides RESTful endpoints:
GET /health - Health checkGET /agents - List available agentsPOST /chat - General chat endpointPOST /agents/{name}/chat - Agent-specific endpointJAF supports functional composition patterns for building complex behaviors from simple, reusable functions:
from jaf import create_function_tool, ToolSource
# Enhanced tool with caching and retry
def with_cache(tool_func):
cache = {}
async def cached_execute(args, context):
cache_key = str(args)
if cache_key in cache:
return cache[cache_key]
result = await tool_func(args, context)
if result.status == "success":
cache[cache_key] = result
return result
return cached_execute
# Create enhanced tools by composition
enhanced_tool = create_function_tool({
'name': 'enhanced_search',
'description': 'Search with caching',
'execute': with_cache(base_search_function),
'parameters': SearchArgs,
'source': ToolSource.NATIVE
})
Key Benefits:
Explore the example applications to see the framework in action:
cd examples
python server_example.py
Features demonstrated:
Available endpoints:
GET /health - Server health checkGET /agents - List all available agentsPOST /chat - Chat with any agentGET /docs - Interactive API documentationcd examples
python agent_as_tool_example.py
# Or start as server
python agent_as_tool_example.py --server
Features demonstrated:
# OpenTelemetry tracing example
cd examples
python otel_tracing_demo.py
# Langfuse tracing example
python langfuse_tracing_demo.py
Features demonstrated:
cd examples/mcp_demo
python main.py
Features demonstrated:
pytest # Run tests
ruff check . # Lint code
mypy . # Type checking
black . # Format code
MIT
JAF (Juspay Agentic Framework) v2.2 - Building the future of functional AI agent systems
FAQs
A purely functional agent framework with immutable state and composable tools - Python implementation
We found that jaf-py demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Add real-time Socket webhook events to your workflows to automatically receive software supply chain alert changes in real time.

Security News
ENISA has become a CVE Program Root, giving the EU a central authority for coordinating vulnerability reporting, disclosure, and cross-border response.

Product
Socket now scans OpenVSX extensions, giving teams early detection of risky behaviors, hidden capabilities, and supply chain threats in developer tools.