Juspay Agent Framework (JAF) Examples Guide¶
This comprehensive guide covers the example projects in the JAF framework, demonstrating real-world usage patterns and implementation strategies for building production-ready AI agent systems.
Table of Contents¶
- Overview
- Server Demo: Multi-Agent HTTP Server
- RAG Demo: Vertex AI Integration
- Common Patterns & Tutorials
- Integration Patterns
- Advanced Use Cases
- Troubleshooting
Overview¶
The JAF framework includes three comprehensive example projects that demonstrate different aspects of the framework:
- Server Demo (
examples/server-demo/
) - A multi-agent HTTP server with memory persistence - RAG Demo (
examples/rag-demo/
) - Vertex AI RAG integration with streaming responses - Visualization Demo (
examples/visualization-demo/
) - Graphviz-based agent and tool visualization
All examples showcase the framework's core principles: - Immutability: All state is deeply readonly - Pure Functions: Core logic expressed as pure, predictable functions - Type Safety: Leverages TypeScript's advanced features - Effects at the Edge: Side effects isolated in Provider modules - Composition over Configuration: Complex behavior through simple function composition
Server Demo: Multi-Agent HTTP Server¶
Overview¶
The Server Demo showcases how to build a production-ready HTTP API for AI agents using JAF's runServer
function. It demonstrates multi-agent architectures, memory persistence, and RESTful API design.
Location: /Users/anurag.sharan/repos/jaf/examples/server-demo/
Key Features¶
- Multiple Agent Types: Three specialized agents (MathTutor, ChatBot, Assistant)
- Memory Persistence: Three provider options (In-Memory, Redis, PostgreSQL)
- RESTful API: Standard HTTP endpoints with type-safe validation
- Tool Integration: Calculator and greeting tools with error handling
- Real-time Tracing: Console-based observability
- CORS Support: Cross-origin requests enabled
- Graceful Shutdown: Proper cleanup on exit
Architecture Deep Dive¶
Agent Definitions¶
// Math-focused agent with calculator tool
const mathAgent: Agent<MyContext, string> = {
name: 'MathTutor',
instructions: () => 'You are a helpful math tutor with access to conversation history. Use the calculator tool to perform calculations and explain math concepts clearly.',
tools: [calculatorTool],
};
// Social interaction agent with greeting tool
const chatAgent: Agent<MyContext, string> = {
name: 'ChatBot',
instructions: () => 'You are a friendly chatbot. Use the greeting tool when meeting new people, and engage in helpful conversation.',
tools: [greetingTool],
};
// Multi-purpose agent with both tools
const assistantAgent: Agent<MyContext, string> = {
name: 'Assistant',
instructions: () => 'You are a general-purpose assistant with access to conversation history. You can help with math calculations and provide greetings.',
tools: [calculatorTool, greetingTool],
};
Tool Implementation with Error Handling¶
The server demo showcases advanced tool implementation with the JAF error handling system:
const calculatorTool: Tool<{ expression: string }, MyContext> = {
schema: {
name: "calculate",
description: "Perform mathematical calculations",
parameters: z.object({
expression: z.string().describe("Math expression to evaluate (e.g., '2 + 2', '10 * 5')")
}),
},
execute: withErrorHandling('calculate', async (args, context) => {
// Input sanitization
const sanitized = args.expression.replace(/[^0-9+\-*/().\s]/g, '');
if (sanitized !== args.expression) {
return ToolResponse.validationError(
"Invalid characters in expression. Only numbers, +, -, *, /, (, ), and spaces are allowed.",
{
originalExpression: args.expression,
sanitizedExpression: sanitized,
invalidCharacters: args.expression.replace(/[0-9+\-*/().\s]/g, '')
}
);
}
try {
const result = eval(sanitized);
return ToolResponse.success(`${args.expression} = ${result}`, {
originalExpression: args.expression,
result,
calculationType: 'arithmetic'
});
} catch (evalError) {
return ToolResponse.error(
ToolErrorCodes.EXECUTION_FAILED,
`Failed to evaluate expression: ${evalError instanceof Error ? evalError.message : 'Unknown error'}`,
{
expression: args.expression,
evalError: evalError instanceof Error ? evalError.message : evalError
}
);
}
}),
};
Memory Provider Configuration¶
The server supports three memory providers, configured via environment variables:
// Environment-based memory provider setup
const memoryType = process.env.JAF_MEMORY_TYPE || 'memory';
const memoryProvider = await createMemoryProviderFromEnv(externalClients);
// Memory configuration in runServer
const server = await runServer(
[mathAgent, chatAgent, assistantAgent],
{
modelProvider,
maxTurns: 5,
onEvent: traceCollector.collect.bind(traceCollector),
memory: {
provider: memoryProvider,
autoStore: true, // Automatically store conversation history
maxMessages: 100 // Keep last 100 messages per conversation
}
},
{
port: parseInt(process.env.PORT || '3000'),
defaultMemoryProvider: memoryProvider
}
);
API Endpoints¶
The server provides comprehensive RESTful endpoints:
Endpoint | Method | Description | Use Case |
---|---|---|---|
/health |
GET | Health check | Monitoring, load balancer checks |
/agents |
GET | List available agents | Discovery, frontend integration |
/chat |
POST | General chat endpoint | Agent-agnostic conversations |
/agents/{name}/chat |
POST | Agent-specific endpoint | Direct agent communication |
/memory/health |
GET | Memory provider health | Memory system monitoring |
/conversations/:id |
GET | Get conversation by ID | Conversation retrieval |
/conversations/:id |
DELETE | Delete conversation | Conversation cleanup |
Setup and Configuration¶
Environment Variables¶
# Core LiteLLM configuration
LITELLM_URL=http://localhost:4000
LITELLM_API_KEY=sk-your-api-key
LITELLM_MODEL=gpt-3.5-turbo
# Server configuration
PORT=3000
# Memory provider selection
JAF_MEMORY_TYPE=memory # or 'redis' or 'postgres'
# Redis configuration (if using Redis)
JAF_REDIS_HOST=localhost
JAF_REDIS_PORT=6379
JAF_REDIS_PASSWORD=your-password
JAF_REDIS_DB=0
# PostgreSQL configuration (if using PostgreSQL)
JAF_POSTGRES_HOST=localhost
JAF_POSTGRES_PORT=5432
JAF_POSTGRES_DB=jaf_memory
JAF_POSTGRES_USER=postgres
JAF_POSTGRES_PASSWORD=your-password
Running the Server¶
Usage Examples¶
Basic Chat Request¶
curl -X POST http://localhost:3000/chat \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "What is 15 * 7?"}],
"agentName": "MathTutor",
"context": {"userId": "demo", "permissions": ["user"]}
}'
Conversation with Memory Persistence¶
# Start conversation
curl -X POST http://localhost:3000/chat \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "What is 15 * 7?"}],
"conversationId": "my-conversation-1",
"agentName": "MathTutor",
"context": {"userId": "demo", "permissions": ["user"]}
}'
# Continue conversation (references previous calculation)
curl -X POST http://localhost:3000/chat \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "What was my previous calculation?"}],
"conversationId": "my-conversation-1",
"agentName": "MathTutor",
"context": {"userId": "demo", "permissions": ["user"]}
}'
Response Format¶
All endpoints return structured JSON responses:
{
"success": boolean,
"data": {
"runId": string,
"traceId": string,
"conversationId": string,
"messages": Array<{role: string, content: string}>,
"outcome": {
"status": "completed" | "error" | "max_turns",
"output": string
},
"turnCount": number,
"executionTimeMs": number
},
"error": string?
}
Memory Provider Comparison¶
Provider | Best For | Persistence | Setup Complexity | Scalability |
---|---|---|---|---|
In-Memory | Development, testing | None (lost on restart) | None | Single instance |
Redis | Production, caching | Persistent | Moderate | High |
PostgreSQL | Production, complex queries | Full persistence | High | Very High |
RAG Demo: Vertex AI Integration¶
Overview¶
The RAG Demo demonstrates real-world integration with Google's Vertex AI RAG (Retrieval Augmented Generation) system. It showcases streaming responses, source attribution, and performance metrics.
Location: /Users/anurag.sharan/repos/jaf/examples/rag-demo/
Key Features¶
- Real Vertex AI Integration: Uses Google's google/genai SDK
- Streaming Responses: Real-time streaming from Vertex AI
- Source Attribution: Automatic grounding and citation
- Performance Metrics: Detailed timing and performance tracking
- Permission Control: Role-based access to RAG functionality
- Error Handling: Comprehensive error management
- Memory Integration: Conversation persistence with JAF memory providers
Architecture Deep Dive¶
RAG Tool Implementation¶
The RAG tool showcases advanced integration with external services:
export const vertexAIRAGTool: Tool<any, RAGContext> = {
schema: {
name: "vertex_ai_rag_query",
description: "Query the Vertex AI RAG system to get information from the knowledge base",
parameters: z.object({
query: z.string().describe("The question to ask the RAG system"),
similarity_top_k: z.number().describe("Number of similar documents to retrieve").default(20)
}),
},
execute: async (args, context) => {
// Permission validation
if (!context.permissions.includes('rag_access')) {
return JSON.stringify({
error: "permission_denied",
message: "RAG access requires 'rag_access' permission",
query: args.query
});
}
const result = await vertexAIRAG(args.query, args.similarity_top_k);
// Structured response with metrics
return `
**RAG Query Results**
**Query:** ${result.query}
**Model:** ${result.model}
**Response:**
${result.response}
**Sources:**
${result.sources.map((source, index) => `${index + 1}. ${source}`).join('\n')}
**Performance Metrics:**
- Total execution time: ${result.metrics?.total_execution_time.toFixed(3)}s
- Time to first chunk: ${result.metrics?.time_to_first_chunk?.toFixed(3)}s
- Chunks received: ${result.metrics?.chunk_count}
- Response length: ${result.metrics?.response_length} characters
- Average chars/second: ${result.metrics?.avg_chars_per_second.toFixed(1)}
`.trim();
}
};
Vertex AI RAG Implementation¶
The core RAG function demonstrates proper SDK usage:
async function vertexAIRAG(query: string, similarity_top_k: number): Promise<RAGResponse> {
const total_start_time = Date.now();
// Initialize Google Generative AI client for Vertex AI
const project = process.env.GOOGLE_CLOUD_PROJECT || "genius-dev-393512";
const location = process.env.GOOGLE_CLOUD_LOCATION || "global";
const client = new GoogleGenAI({
vertexai: true,
project,
location
});
const model = process.env.RAG_MODEL || "gemini-2.0-flash-exp";
// RAG request configuration
const request = {
model,
contents: [
{
role: "user" as const,
parts: [{ text: query }]
}
],
tools: [
{
retrieval: {
vertexRagStore: {
ragResources: [
{
ragCorpus: process.env.RAG_CORPUS_ID || "projects/genius-dev-393512/locations/us-central1/ragCorpora/2305843009213693952"
}
],
similarityTopK: similarity_top_k
}
}
}
]
};
// Execute RAG query with performance tracking
const result = await client.models.generateContent(request);
// Extract grounding metadata for source attribution
const source_urls: string[] = [];
if (result.candidates?.[0]?.groundingMetadata?.groundingChunks) {
for (const chunk of result.candidates[0].groundingMetadata.groundingChunks) {
if (chunk.retrievedContext?.uri && !source_urls.includes(chunk.retrievedContext.uri)) {
source_urls.push(chunk.retrievedContext.uri);
}
}
}
return {
response: extractResponseText(result),
query,
model,
sources: source_urls,
metrics: calculateMetrics(total_start_time, result)
};
}
RAG Agent Configuration¶
The RAG agent is specifically tuned for knowledge retrieval tasks:
export const ragAgent: Agent<RAGContext, string> = {
name: 'RAGAgent',
instructions: (state) => `You are a helpful AI assistant powered by Vertex AI RAG.
You have access to a comprehensive knowledge base through the RAG system. When users ask questions, you should:
1. Use the vertex_ai_rag_query tool to search the knowledge base
2. Provide comprehensive answers based on the retrieved information
3. Always cite your sources when providing information
4. If the knowledge base doesn't contain relevant information, clearly state that
Current user: ${state.context.userId}
User permissions: ${state.context.permissions.join(', ')}`,
tools: [vertexAIRAGTool],
modelConfig: {
temperature: parseFloat(process.env.RAG_TEMPERATURE || '0.1'), // Lower temperature for factual responses
maxTokens: parseInt(process.env.RAG_MAX_TOKENS || '2000')
}
};
Setup and Configuration¶
Prerequisites¶
- Google Cloud Project: Active GCP project with Vertex AI enabled
- Authentication: Properly configured
gcloud
authentication - RAG Corpus: Existing Vertex AI RAG corpus with indexed documents
- LiteLLM Proxy: Running LiteLLM instance for model access
Environment Configuration¶
# Google Cloud configuration
GOOGLE_CLOUD_PROJECT=your-project-id
# LiteLLM configuration
LITELLM_URL=http://localhost:4000
LITELLM_API_KEY=sk-your-api-key
LITELLM_MODEL=gemini-2.5-flash-lite
# RAG-specific configuration
RAG_CORPUS_ID=projects/your-project/locations/us-central1/ragCorpora/your-corpus-id
RAG_MODEL=gemini-2.0-flash-exp
RAG_TEMPERATURE=0.1
RAG_MAX_TOKENS=2000
RAG_SIMILARITY_TOP_K=20
RAG_MAX_TURNS=5
Authentication Setup¶
# Authenticate with Google Cloud with required scopes
gcloud auth application-default login --scopes=https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/generative-language.retriever
# Set your project
export GOOGLE_CLOUD_PROJECT="your-project-id"
Running the Demo¶
cd examples/rag-demo
npm install
cp .env.example .env
# Edit .env with your configuration
npm run dev
Demo Queries¶
The demo runs predefined queries to showcase RAG capabilities:
const demoQueries = [
"What is return URL?",
"How do I integrate hypercheckout on android? Remember what we discussed earlier."
];
These queries demonstrate: - Knowledge retrieval from the RAG corpus - Conversation memory with context from previous queries - Source attribution with automatic grounding - Performance metrics tracking
Performance Metrics¶
The RAG demo tracks comprehensive performance metrics:
interface RAGMetrics {
client_init_time: number; // Time to initialize the client
time_to_first_chunk: number | null; // Latency to first response chunk
total_generation_time: number; // Total generation time
total_execution_time: number; // End-to-end execution time
chunk_count: number; // Number of response chunks
response_length: number; // Character count of response
avg_chunks_per_second: number; // Throughput metric
avg_chars_per_second: number; // Character throughput
}
Sample Output¶
🔍 JAF Vertex AI RAG Demo
========================
📋 Demo Query 1: "What is return URL?"
==================================================
[RAG] Initializing Vertex AI client...
[RAG] Client initialized in 0.123s
[RAG] Querying RAG corpus with query: "What is return URL?"
[RAG] Retrieving top 20 similar documents
[RAG] Query completed in 2.456s, 15 chunks, 1247 chars
✅ RAG Query Completed Successfully!
📝 Response:
**RAG Query Results**
**Query:** What is return URL?
**Model:** gemini-2.0-flash-exp
**Response:**
A return URL is a web address where users are redirected after completing an action, such as payment processing or authentication. In the context of payment systems, the return URL is where customers are sent back to your application after completing their transaction on the payment provider's platform.
**Sources:**
1. https://docs.example.com/payment-integration
2. https://wiki.example.com/return-urls
3. https://api-docs.example.com/webhooks
**Performance Metrics:**
- Total execution time: 2.456s
- Time to first chunk: 0.234s
- Chunks received: 15
- Response length: 1247 characters
- Average chars/second: 507.3
Common Patterns & Tutorials¶
Pattern 1: Creating Custom Tools¶
Basic Tool Structure¶
import { z } from 'zod';
import { Tool, ToolResponse, ToolErrorCodes, withErrorHandling } from 'functional-agent-framework';
const myTool: Tool<{ input: string }, MyContext> = {
schema: {
name: "my_tool",
description: "Description of what this tool does",
parameters: z.object({
input: z.string().describe("Input parameter description")
}),
},
execute: withErrorHandling('my_tool', async (args, context) => {
// Input validation
if (!args.input || args.input.trim().length === 0) {
return ToolResponse.validationError("Input cannot be empty", { providedInput: args.input });
}
try {
// Tool logic here
const result = processInput(args.input);
return ToolResponse.success(`Processed: ${result}`, {
originalInput: args.input,
processedResult: result,
processingType: 'standard'
});
} catch (error) {
return ToolResponse.error(
ToolErrorCodes.EXECUTION_FAILED,
`Processing failed: ${error.message}`,
{ input: args.input, errorDetails: error }
);
}
}),
};
Tool with Validation Policies¶
import { withValidation, createPathValidator, composeValidations } from 'functional-agent-framework';
// Create validators
const pathValidator = createPathValidator(['/safe', '/public']);
const permissionValidator = (args: any, ctx: MyContext) => {
if (!ctx.permissions.includes('tool_access')) {
return { isValid: false, errorMessage: 'Insufficient permissions' };
}
return { isValid: true };
};
// Compose validators
const combinedValidator = composeValidations(pathValidator, permissionValidator);
// Apply to tool
const secureFileTool = withValidation(baseFileTool, combinedValidator);
Pattern 2: Multi-Agent Workflows¶
Agent Handoff Implementation¶
import { handoffTool, Agent } from 'functional-agent-framework';
const triageAgent: Agent<Context, { agentName: string }> = {
name: 'TriageAgent',
instructions: () => 'Route requests to specialized agents based on user needs',
tools: [handoffTool],
handoffs: ['MathTutor', 'FileManager', 'RAGAgent'], // Allowed handoff targets
outputCodec: z.object({
agentName: z.enum(['MathTutor', 'FileManager', 'RAGAgent'])
}),
};
Workflow Orchestration¶
const workflowConfig: RunConfig<MyContext> = {
agentRegistry: new Map([
['TriageAgent', triageAgent],
['MathTutor', mathAgent],
['FileManager', fileAgent],
['RAGAgent', ragAgent]
]),
modelProvider,
maxTurns: 10,
onEvent: (event) => {
if (event.type === 'handoff') {
console.log(`🔄 Handoff: ${event.data.from} → ${event.data.to}`);
}
}
};
Pattern 3: Memory and Persistence¶
Custom Memory Provider¶
import { MemoryProvider, ConversationMemory, Result } from 'functional-agent-framework';
class CustomMemoryProvider implements MemoryProvider {
async storeMessages(
conversationId: string,
messages: readonly Message[],
metadata?: any
): Promise<Result<void>> {
try {
// Custom storage logic
await this.storage.store(conversationId, messages, metadata);
return { success: true, data: undefined };
} catch (error) {
return {
success: false,
error: createMemoryStorageError('store', 'custom', error)
};
}
}
async getConversation(conversationId: string): Promise<Result<ConversationMemory | null>> {
try {
const conversation = await this.storage.get(conversationId);
return { success: true, data: conversation };
} catch (error) {
return {
success: false,
error: createMemoryNotFoundError(conversationId, 'custom')
};
}
}
// Implement other required methods...
}
Memory Configuration Patterns¶
// In-memory for development
const devMemoryConfig = {
provider: await createInMemoryProvider(),
autoStore: true,
maxMessages: 50
};
// Redis for production
const prodMemoryConfig = {
provider: await createRedisProvider({
url: process.env.REDIS_URL,
keyPrefix: 'myapp:conversations:',
ttl: 86400 // 24 hours
}),
autoStore: true,
maxMessages: 500,
compressionThreshold: 100
};
Pattern 4: Error Handling and Observability¶
Custom Trace Collector¶
import { TraceEvent, ConsoleTraceCollector } from 'functional-agent-framework';
class CustomTraceCollector {
constructor(private logLevel: 'debug' | 'info' | 'warn' | 'error' = 'info') {}
collect(event: TraceEvent) {
switch (event.type) {
case 'run_start':
console.log(`🚀 [${event.data.runId}] Starting conversation`);
break;
case 'llm_call_start':
console.log(`🤖 [${event.data.agentName}] Calling ${event.data.model}`);
break;
case 'tool_call_start':
console.log(`🔧 [${event.data.toolName}] Executing with args:`, event.data.args);
break;
case 'tool_call_end':
const status = event.data.status || 'completed';
console.log(`✅ [${event.data.toolName}] ${status}`);
break;
case 'handoff':
console.log(`🔄 Handoff: ${event.data.from} → ${event.data.to}`);
break;
case 'run_end':
const outcome = event.data.outcome;
if (outcome.status === 'completed') {
console.log(`🎉 Conversation completed`);
} else {
console.error(`❌ Conversation failed:`, outcome.error);
}
break;
}
}
}
Error Handling Strategies¶
import { JAFErrorHandler, RunResult } from 'functional-agent-framework';
async function handleRunResult<T>(result: RunResult<T>): Promise<T> {
if (result.outcome.status === 'completed') {
return result.outcome.output;
}
const error = result.outcome.error;
const formattedError = JAFErrorHandler.format(error);
const isRetryable = JAFErrorHandler.isRetryable(error);
const severity = JAFErrorHandler.getSeverity(error);
console.error(`[${severity}] ${formattedError} (retryable: ${isRetryable})`);
// Custom error handling based on error type
switch (error._tag) {
case 'MaxTurnsExceeded':
throw new Error(`Conversation exceeded ${error.turns} turns`);
case 'ToolCallError':
if (isRetryable) {
console.log(`Retrying tool call: ${error.tool}`);
// Implement retry logic
}
throw new Error(`Tool ${error.tool} failed: ${error.detail}`);
case 'AgentNotFound':
throw new Error(`Agent ${error.agentName} not found`);
default:
throw new Error(`Unexpected error: ${formattedError}`);
}
}
Pattern 5: Guardrails and Security¶
Content Filtering¶
import { Guardrail, createContentFilter, createRateLimiter } from 'functional-agent-framework';
const contentFilter: Guardrail<string> = createContentFilter({
blockedWords: ['spam', 'abuse'],
maxLength: 1000,
allowEmptyInput: false
});
const rateLimiter: Guardrail<string> = createRateLimiter(
10, // 10 requests
60000, // per 60 seconds
(input) => 'global' // rate limit key
);
const config: RunConfig<MyContext> = {
// ... other config
initialInputGuardrails: [contentFilter, rateLimiter],
finalOutputGuardrails: [contentFilter],
};
Custom Guardrails¶
const customGuardrail: Guardrail<string> = async (input: string) => {
// Check for sensitive information
const sensitivePatterns = [
/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/, // Credit card
/\b\d{3}-\d{2}-\d{4}\b/, // SSN
];
for (const pattern of sensitivePatterns) {
if (pattern.test(input)) {
return {
isValid: false,
errorMessage: 'Input contains sensitive information'
};
}
}
return { isValid: true };
};
Integration Patterns¶
MCP (Model Context Protocol) Integration¶
Setting Up MCP Tools¶
import { makeMCPClient, mcpToolToJAFTool } from 'functional-agent-framework';
// Connect to MCP server
const mcpClient = await makeMCPClient('python', ['-m', 'mcp_server']);
// Get available tools
const mcpTools = await mcpClient.listTools();
// Convert to JAF tools with validation
const jafTools = mcpTools.map(tool =>
mcpToolToJAFTool(mcpClient, tool, myValidationPolicy)
);
// Use in agent
const mcpAgent: Agent<MyContext, string> = {
name: 'MCPAgent',
instructions: () => 'You have access to external tools via MCP',
tools: jafTools,
};
MCP Tool Validation¶
const mcpValidationPolicy = (args: any, context: MyContext) => {
// Check permissions for MCP tool access
if (!context.permissions.includes('mcp_access')) {
return {
isValid: false,
errorMessage: 'MCP access requires special permissions'
};
}
// Validate specific tool arguments
if (args.command && args.command.includes('rm')) {
return {
isValid: false,
errorMessage: 'Destructive commands not allowed'
};
}
return { isValid: true };
};
External API Integration¶
HTTP Client Tool¶
const httpClientTool: Tool<{ url: string; method: string; body?: any }, MyContext> = {
schema: {
name: "http_request",
description: "Make HTTP requests to external APIs",
parameters: z.object({
url: z.string().url(),
method: z.enum(['GET', 'POST', 'PUT', 'DELETE']),
body: z.any().optional()
}),
},
execute: withErrorHandling('http_request', async (args, context) => {
// Validate URL against allowlist
const allowedDomains = ['api.example.com', 'service.mycompany.com'];
const urlObj = new URL(args.url);
if (!allowedDomains.includes(urlObj.hostname)) {
return ToolResponse.validationError(
`Domain ${urlObj.hostname} not in allowlist`,
{ requestedDomain: urlObj.hostname, allowedDomains }
);
}
try {
const response = await fetch(args.url, {
method: args.method,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${context.apiToken}` // from context
},
body: args.body ? JSON.stringify(args.body) : undefined
});
const result = await response.json();
return ToolResponse.success(`HTTP ${args.method} completed`, {
status: response.status,
url: args.url,
response: result
});
} catch (error) {
return ToolResponse.error(
ToolErrorCodes.NETWORK_ERROR,
`HTTP request failed: ${error.message}`,
{ url: args.url, method: args.method }
);
}
}),
};
Database Integration¶
Database Tool Pattern¶
const databaseTool: Tool<{ query: string; params?: any[] }, MyContext> = {
schema: {
name: "database_query",
description: "Execute safe database queries",
parameters: z.object({
query: z.string(),
params: z.array(z.any()).optional()
}),
},
execute: withErrorHandling('database_query', async (args, context) => {
// Validate query safety
const prohibitedKeywords = ['DROP', 'DELETE', 'UPDATE', 'INSERT'];
const upperQuery = args.query.toUpperCase();
for (const keyword of prohibitedKeywords) {
if (upperQuery.includes(keyword)) {
return ToolResponse.validationError(
`Query contains prohibited keyword: ${keyword}`,
{ query: args.query, prohibitedKeyword: keyword }
);
}
}
try {
const result = await this.db.query(args.query, args.params);
return ToolResponse.success(`Query executed successfully`, {
rowCount: result.rows.length,
columns: result.fields?.map(f => f.name) || [],
data: result.rows
});
} catch (error) {
return ToolResponse.error(
ToolErrorCodes.DATABASE_ERROR,
`Database query failed: ${error.message}`,
{ query: args.query, params: args.params }
);
}
}),
};
Advanced Use Cases¶
Use Case 1: Multi-Modal Agent System¶
File Processing Agent¶
const fileProcessingAgent: Agent<FileContext, ProcessingResult> = {
name: 'FileProcessor',
instructions: (state) => `Process files based on type and user requirements.
Available operations:
- Image processing (resize, format conversion)
- Document analysis (PDF, Word, Excel)
- Data transformation (CSV, JSON)
- Archive extraction (ZIP, TAR)`,
tools: [
imageProcessingTool,
documentAnalysisTool,
dataTransformationTool,
archiveExtractionTool
],
outputCodec: z.object({
processedFiles: z.array(z.object({
originalPath: z.string(),
processedPath: z.string(),
operation: z.string(),
metadata: z.any()
}))
})
};
Image Processing Tool¶
const imageProcessingTool: Tool<ImageProcessingArgs, FileContext> = {
schema: {
name: "process_image",
description: "Process images with various operations",
parameters: z.object({
imagePath: z.string(),
operation: z.enum(['resize', 'convert', 'enhance', 'compress']),
parameters: z.object({
width: z.number().optional(),
height: z.number().optional(),
format: z.enum(['jpg', 'png', 'webp']).optional(),
quality: z.number().min(1).max(100).optional()
})
})
},
execute: withErrorHandling('process_image', async (args, context) => {
// Validate file access
if (!context.permissions.includes('file_access')) {
return ToolResponse.permissionError('File access required');
}
// Validate file exists and is an image
const fileInfo = await validateImageFile(args.imagePath);
if (!fileInfo.isValid) {
return ToolResponse.validationError(fileInfo.error);
}
try {
const result = await processImage(args);
return ToolResponse.success('Image processed successfully', {
originalPath: args.imagePath,
processedPath: result.outputPath,
operation: args.operation,
originalSize: fileInfo.size,
newSize: result.size,
processingTime: result.processingTime
});
} catch (error) {
return ToolResponse.error(
ToolErrorCodes.PROCESSING_FAILED,
`Image processing failed: ${error.message}`,
{ imagePath: args.imagePath, operation: args.operation }
);
}
})
};
Use Case 2: Real-time Streaming Responses¶
Streaming Chat Implementation¶
import { EventSource } from 'eventsource';
const streamingChatTool: Tool<StreamingArgs, StreamingContext> = {
schema: {
name: "streaming_chat",
description: "Initiate streaming conversation",
parameters: z.object({
message: z.string(),
sessionId: z.string()
})
},
execute: async (args, context) => {
const streamId = generateStreamId();
// Start streaming in background
startStreamingResponse(streamId, args.message, context);
return ToolResponse.success('Streaming started', {
streamId,
streamUrl: `/stream/${streamId}`,
message: 'Connect to stream URL for real-time responses'
});
}
};
async function startStreamingResponse(streamId: string, message: string, context: StreamingContext) {
const stream = createResponseStream(streamId);
try {
stream.write({ type: 'start', data: { streamId } });
// Simulate streaming LLM response
const response = await streamLLMResponse(message);
for await (const chunk of response) {
stream.write({ type: 'chunk', data: { content: chunk } });
await delay(50); // Simulate realistic streaming delay
}
stream.write({ type: 'end', data: { completed: true } });
} catch (error) {
stream.write({ type: 'error', data: { error: error.message } });
} finally {
stream.close();
}
}
Use Case 3: Workflow Automation¶
Workflow Orchestration Agent¶
const workflowAgent: Agent<WorkflowContext, WorkflowResult> = {
name: 'WorkflowOrchestrator',
instructions: (state) => `Orchestrate complex workflows by coordinating multiple agents and tools.
Available workflow patterns:
- Sequential processing (step-by-step execution)
- Parallel processing (concurrent execution)
- Conditional branching (if-then-else logic)
- Loop processing (iterative operations)
- Error recovery (retry and fallback strategies)`,
tools: [
sequentialProcessorTool,
parallelProcessorTool,
conditionalBranchTool,
loopProcessorTool,
errorRecoveryTool
],
outputCodec: z.object({
workflowId: z.string(),
status: z.enum(['completed', 'failed', 'partial']),
steps: z.array(z.object({
stepId: z.string(),
status: z.enum(['completed', 'failed', 'skipped']),
output: z.any(),
executionTime: z.number()
})),
totalExecutionTime: z.number(),
errorCount: z.number()
})
};
Sequential Workflow Tool¶
const sequentialProcessorTool: Tool<SequentialWorkflowArgs, WorkflowContext> = {
schema: {
name: "execute_sequential_workflow",
description: "Execute a sequence of steps in order",
parameters: z.object({
workflowId: z.string(),
steps: z.array(z.object({
stepId: z.string(),
agentName: z.string(),
input: z.any(),
retryCount: z.number().default(3),
timeout: z.number().default(30000)
}))
})
},
execute: withErrorHandling('execute_sequential_workflow', async (args, context) => {
const results: WorkflowStepResult[] = [];
const startTime = Date.now();
for (const step of args.steps) {
const stepStartTime = Date.now();
try {
const stepResult = await executeWorkflowStep(step, context);
results.push({
stepId: step.stepId,
status: 'completed',
output: stepResult,
executionTime: Date.now() - stepStartTime
});
// Pass output to next step as context
context.previousStepOutput = stepResult;
} catch (error) {
results.push({
stepId: step.stepId,
status: 'failed',
output: error.message,
executionTime: Date.now() - stepStartTime
});
// Decide whether to continue or abort workflow
if (step.critical) {
break; // Abort workflow on critical step failure
}
}
}
const totalExecutionTime = Date.now() - startTime;
const errorCount = results.filter(r => r.status === 'failed').length;
const overallStatus = errorCount === 0 ? 'completed' :
errorCount < results.length ? 'partial' : 'failed';
return ToolResponse.success('Sequential workflow completed', {
workflowId: args.workflowId,
status: overallStatus,
steps: results,
totalExecutionTime,
errorCount
});
})
};
Troubleshooting¶
Common Issues and Solutions¶
1. LiteLLM Connection Issues¶
Problem: Connection refused
or 401 Authentication Error
Solutions:
# Check if LiteLLM proxy is running
curl http://localhost:4000/health
# Verify API key format (must start with 'sk-')
echo $LITELLM_API_KEY | grep -E '^sk-'
# Check available models
curl http://localhost:4000/v1/models
# Restart LiteLLM with correct configuration
litellm --model gpt-3.5-turbo --port 4000
2. Memory Provider Issues¶
Problem: Memory provider connection failed
Redis Solutions:
# Check Redis connection
redis-cli ping
# Check Redis logs
redis-cli monitor
# Restart Redis
brew services restart redis # macOS
sudo systemctl restart redis # Linux
PostgreSQL Solutions:
# Check PostgreSQL connection
psql -h localhost -U postgres -d jaf_memory -c "SELECT 1;"
# Create database if missing
createdb jaf_memory
# Check PostgreSQL logs
tail -f /usr/local/var/log/postgresql.log # macOS
journalctl -u postgresql -f # Linux
3. RAG Demo Issues¶
Problem: Google Cloud authentication not found
Solutions:
# Check current authentication
gcloud auth list
# Re-authenticate with correct scopes
gcloud auth application-default login --scopes=https://www.googleapis.com/auth/cloud-platform
# Verify project access
gcloud projects describe $GOOGLE_CLOUD_PROJECT
# Check Vertex AI API enablement
gcloud services list --enabled | grep aiplatform
Problem: RAG corpus not found
Solutions:
# List available RAG corpora
gcloud ai indexes list --region=us-central1
# Verify corpus ID format
echo $RAG_CORPUS_ID | grep -E '^projects/.*/locations/.*/ragCorpora/.*$'
# Check corpus permissions
gcloud projects get-iam-policy $GOOGLE_CLOUD_PROJECT
4. Agent Tool Execution Issues¶
Problem: Tool validation failures
Debug Steps:
// Enable debug logging for tools
const debugTool = {
...originalTool,
execute: async (args, context) => {
console.log('Tool input:', { args, context });
try {
const result = await originalTool.execute(args, context);
console.log('Tool output:', result);
return result;
} catch (error) {
console.error('Tool error:', error);
throw error;
}
}
};
5. Server Demo Port Issues¶
Problem: Port already in use
Solutions:
# Find process using port
lsof -i :3000
# Kill process if needed
kill -9 <PID>
# Use different port
PORT=3001 npm run dev
# Check available ports
netstat -tulpn | grep LISTEN
6. Type Safety Issues¶
Problem: TypeScript compilation errors
Solutions:
# Check TypeScript version compatibility
npm list typescript
# Update dependencies
npm update
# Clear build cache
rm -rf dist/ node_modules/.cache/
# Rebuild
npm run build
Performance Optimization¶
1. Memory Usage Optimization¶
// Configure memory limits
const memoryConfig = {
provider: memoryProvider,
autoStore: true,
maxMessages: 100, // Limit message history
compressionThreshold: 50 // Compress older messages
};
// Implement message compression
const compressMessages = (messages: Message[]): Message[] => {
if (messages.length <= 50) return messages;
// Keep first 10 and last 40 messages, compress middle
const start = messages.slice(0, 10);
const end = messages.slice(-40);
const middle = [{
role: 'assistant' as const,
content: `[Compressed ${messages.length - 50} messages]`
}];
return [...start, ...middle, ...end];
};
2. Response Time Optimization¶
// Configure reasonable timeouts
const optimizedConfig = {
maxTurns: 5, // Limit conversation length
modelOverride: 'gpt-3.5-turbo-16k', // Use faster model
timeout: 30000, // 30 second timeout
// Parallel tool execution where possible
parallelToolExecution: true,
// Cache model responses
responseCache: {
enabled: true,
ttl: 300000, // 5 minutes
maxSize: 1000
}
};
3. Error Recovery Strategies¶
// Implement exponential backoff for retries
const retryWithBackoff = async (
operation: () => Promise<any>,
maxRetries: number = 3,
baseDelay: number = 1000
) => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (attempt === maxRetries) throw error;
const delay = baseDelay * Math.pow(2, attempt - 1);
console.log(`Retry attempt ${attempt} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
};
Debugging Techniques¶
1. Enable Comprehensive Tracing¶
import { DetailedTraceCollector } from './custom-trace-collector';
const detailedTracer = new DetailedTraceCollector({
logLevel: 'debug',
includeArgs: true,
includeResults: true,
includeTimings: true,
outputFile: './debug-trace.log'
});
const config = {
// ... other config
onEvent: detailedTracer.collect.bind(detailedTracer)
};
2. Tool-Level Debugging¶
const createDebuggingWrapper = <A, Ctx>(tool: Tool<A, Ctx>) => ({
...tool,
execute: async (args: A, context: Ctx) => {
const startTime = Date.now();
console.log(`🔧 [${tool.schema.name}] Starting with:`, {
args: JSON.stringify(args, null, 2),
context: {
userId: context.userId,
permissions: context.permissions
}
});
try {
const result = await tool.execute(args, context);
const duration = Date.now() - startTime;
console.log(`✅ [${tool.schema.name}] Completed in ${duration}ms`);
console.log(`📤 [${tool.schema.name}] Result:`, result);
return result;
} catch (error) {
const duration = Date.now() - startTime;
console.error(`❌ [${tool.schema.name}] Failed after ${duration}ms:`, error);
throw error;
}
}
});
3. Memory Debugging¶
// Monitor memory provider health
setInterval(async () => {
const healthResult = await memoryProvider.healthCheck();
if (!healthResult.success) {
console.error('Memory provider unhealthy:', healthResult.error);
} else {
console.log('Memory provider status:', healthResult.data);
}
}, 30000); // Check every 30 seconds
// Log memory statistics
const stats = await memoryProvider.getStats();
if (stats.success) {
console.log('Memory statistics:', stats.data);
}
This guide provides comprehensive coverage of the JAF framework examples, from basic usage to advanced patterns and troubleshooting. Each section includes practical code examples and real-world implementation strategies to help developers build robust AI agent systems using the Juspay Agent Framework.