Learn how to invoke agents programmatically through a simple HTTP API, perfect for system integrations and headless AI workflows.
What You'll Accomplish
Section titled “What You'll Accomplish”By the end of this guide, you will:
- Deploy an agent without a chat app
- Get the converse function URL
- Authenticate requests with AWS IAM and JWT
- Make agent invocation API calls
- Handle streaming responses
- Build a complete CLI tool
Prerequisites
Section titled “Prerequisites”- A running Pika installation
- AWS CDK or Serverless Framework for deployment
- Node.js for building tools
- Understanding of AWS IAM authentication
- Basic knowledge of JWT tokens
Understanding Direct Agent Invocation
Section titled “Understanding Direct Agent Invocation”Direct Agent Invocation lets you call agents through HTTP API without creating chat applications. You deploy only the agent and its tools, then make API calls to get responses.
Use Cases
Section titled “Use Cases”- System Integrations: Call agents from backend services
- Headless Workflows: Automate AI tasks without UI
- API Services: Build REST APIs powered by AI agents
- Batch Processing: Process multiple requests programmatically
- Custom Interfaces: Build your own chat interfaces
Authentication Requirements
Section titled “Authentication Requirements”Direct invocation requires two layers of authentication:
- AWS IAM Authentication: Required for Lambda Function URL
- JWT Authentication: Required by application logic
Step 1: Deploy Agent-Only Stack
Section titled “Step 1: Deploy Agent-Only Stack”Deploy just the agent and tools without a chat app.
Using CDK
Section titled “Using CDK”import { AgentDataRequest } from 'pika-shared/types/chatbot/chatbot-types';import { gzipAndBase64EncodeString } from 'pika-shared/util/gzip-util';
// Create agent without chat appconst agentData: AgentDataRequest = { userId: `cloudformation/${this.stackName}`, agent: { agentId: `weather-agent-${stage}`, basePrompt: 'You are a helpful weather assistant that provides accurate weather information.', modelId: 'anthropic.claude-3-5-sonnet-20241022-v2:0' }, tools: [{ toolId: `weather-tool-${stage}`, name: 'weather-tool', displayName: 'Weather Tool', description: 'Get current weather information', executionType: 'lambda', lambdaArn: 'WILL_BE_REPLACED_BY_CUSTOM_RESOURCE', functionSchema: weatherFunctionSchema }]};
// Deploy the agent (no chat app custom resource)new cdk.CustomResource(this, 'WeatherAgentResource', { serviceToken: agentCustomResourceArn, properties: { AgentData: gzipAndBase64EncodeString(JSON.stringify(agentData)), ToolIdToLambdaArnMap: { [`weather-tool-${stage}`]: weatherLambda.functionArn } }});Using Serverless Framework
Section titled “Using Serverless Framework”service: weather-direct
plugins: - pika-serverless
custom: pika: # Define agent without chat app agents: weather-agent: basePrompt: 'You are a helpful weather assistant...' tools: - weather-tool
tools: weather-tool: name: weather-tool displayName: Weather Tool description: Get current weather information handler: src/weather.handler functionSchema: - name: get_weather description: Get current weather parameters: type: object properties: location: type: string description: City name required: [location]
# No chatApps section = agent-only deploymentStep 2: Get Function URL and Secrets
Section titled “Step 2: Get Function URL and Secrets”Retrieve required values from AWS SSM Parameter Store.
Install AWS SDK
Section titled “Install AWS SDK”npm install @aws-sdk/client-ssmGet Function URL
Section titled “Get Function URL”import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm';
const ssm = new SSMClient({ region: 'us-east-1' });
// Get the converse function URLconst urlParam = await ssm.send( new GetParameterCommand({ Name: '/stack/pika/test/function/converse_url' }));
const converseFunctionUrl = urlParam.Parameter.Value;Get JWT Secret
Section titled “Get JWT Secret”// Get JWT secret (with decryption for SecureString)const jwtSecret = await ssm.send( new GetParameterCommand({ Name: '/stack/pika/test/jwt-secret', WithDecryption: true // Important for SecureString parameters })).then(result => result.Parameter.Value);Step 3: Create JWT Token
Section titled “Step 3: Create JWT Token”Install jsonwebtoken library and create tokens.
Install JWT Library
Section titled “Install JWT Library”npm install jsonwebtokenCreate Token
Section titled “Create Token”import jwt from 'jsonwebtoken';
function createJWT(user: {userId: string; customUserData?: any}, secret: string): string { return jwt.sign(user, secret, { expiresIn: '1h' });}
// Create token for default userconst user = { userId: '123', // Default hardcoded user ID (exists in Pika by default) customUserData: undefined};
const jwtToken = createJWT(user, jwtSecret);Step 4: Sign Requests with AWS IAM
Section titled “Step 4: Sign Requests with AWS IAM”Lambda Function URLs require AWS IAM authentication.
Install Signing Libraries
Section titled “Install Signing Libraries”npm install @aws-crypto/sha256-js @aws-sdk/credential-provider-node @smithy/signature-v4Sign and Make Request
Section titled “Sign and Make Request”import { Sha256 } from '@aws-crypto/sha256-js';import { defaultProvider } from '@aws-sdk/credential-provider-node';import { SignatureV4 } from '@smithy/signature-v4';
async function invokeAgent( functionUrl: string, request: any, jwtToken: string, region: string) { // Parse Function URL const url = new URL(functionUrl);
// Prepare request for AWS IAM signing const requestToSign = { method: 'POST', hostname: url.hostname, path: url.pathname + url.search, protocol: url.protocol, headers: { Host: url.hostname, 'Content-Type': 'application/json', 'x-chat-auth': `Bearer ${jwtToken}` // Include Bearer prefix }, body: JSON.stringify(request) };
// Create AWS IAM signer const signer = new SignatureV4({ credentials: defaultProvider(), region: region, service: 'lambda', // Lambda Function URLs use 'lambda' service sha256: Sha256 });
// Sign the request const signedRequest = await signer.sign(requestToSign);
// Make the signed request const response = await fetch(functionUrl, { method: signedRequest.method, headers: signedRequest.headers, body: signedRequest.body });
return response;}Step 5: Make Agent Invocation Request
Section titled “Step 5: Make Agent Invocation Request”Create the request and invoke the agent.
Request Format
Section titled “Request Format”const request = { invocationMode: 'direct-agent-invoke', // Required message: 'What is the weather in San Francisco?', agentId: 'weather-agent-test', // Must match deployed agent userId: '123', // Must exist in chat-user table
// Optional: Override features features: { verifyResponse: { enabled: true }, instructionAugmentation: { enabled: true }, tags: { tagsEnabled: [] } },
// Optional: Resume existing session sessionId: 'existing-session-id',
// Optional: File attachments files: [{ fileName: 'data.csv', fileContent: 'base64-encoded-content', mimeType: 'text/csv' }]};Handle Streaming Response
Section titled “Handle Streaming Response”// Make requestconst response = await invokeAgent(converseFunctionUrl, request, jwtToken, 'us-east-1');
if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP ${response.status}: ${errorText}`);}
// Handle streaming responseconst reader = response.body.getReader();const decoder = new TextDecoder();let content = '';
while (true) { const { done, value } = await reader.read(); if (done) break;
const chunk = decoder.decode(value); const lines = chunk.split('\n');
for (const line of lines) { if (line.trim()) { try { const data = JSON.parse(line); if (data.content) { content += data.content; process.stdout.write(data.content); // Stream to console } else if (data.error) { console.error('\nError:', data.error); } } catch { // Handle non-JSON content process.stdout.write(line); } } }}
console.log('\n\nComplete response:', content);Complete CLI Tool Example
Section titled “Complete CLI Tool Example”Here's a complete working CLI tool:
#!/usr/bin/env nodeimport { Sha256 } from '@aws-crypto/sha256-js';import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm';import { defaultProvider } from '@aws-sdk/credential-provider-node';import { SignatureV4 } from '@smithy/signature-v4';import { Command } from 'commander';import jwt from 'jsonwebtoken';
async function getParameterFromSSM(parameterName: string, region: string) { const client = new SSMClient({ region }); const response = await client.send( new GetParameterCommand({ Name: parameterName, WithDecryption: true }) );
if (!response.Parameter?.Value) { throw new Error(`Parameter ${parameterName} not found`); }
return response.Parameter.Value;}
function createJWT(user: any, jwtSecret: string) { return jwt.sign(user, jwtSecret, { expiresIn: '1h' });}
async function invokeAgent( functionUrl: string, request: any, jwtToken: string, region: string) { const url = new URL(functionUrl);
const requestToSign = { method: 'POST', hostname: url.hostname, path: url.pathname + url.search, protocol: url.protocol, headers: { Host: url.hostname, 'Content-Type': 'application/json', 'x-chat-auth': `Bearer ${jwtToken}` }, body: JSON.stringify(request) };
const signer = new SignatureV4({ credentials: defaultProvider(), region: region, service: 'lambda', sha256: Sha256 });
const signedRequest = await signer.sign(requestToSign);
const response = await fetch(functionUrl, { method: signedRequest.method, headers: signedRequest.headers, body: signedRequest.body });
if (!response.ok) { const errorText = await response.text(); throw new Error(`HTTP ${response.status}: ${errorText}`); }
// Handle streaming response const reader = response.body.getReader(); const decoder = new TextDecoder();
console.log('Response:'); console.log('────────────');
while (true) { const { done, value } = await reader.read(); if (done) break;
const chunk = decoder.decode(value, { stream: true }); const lines = chunk.split('\n');
for (const line of lines) { if (line.trim()) { try { const data = JSON.parse(line); if (data.content) { process.stdout.write(data.content); } else if (data.error) { console.error('\nError:', data.error); } } catch { process.stdout.write(line); } } } }
console.log('\n────────────'); console.log('Done');}
async function main() { const program = new Command();
program .name('pika-direct-cli') .description('CLI tool for direct agent invocation') .version('1.0.0') .argument('<message>', 'message to send to the agent') .option('-s, --stage <stage>', 'deployment stage', 'test') .option('-u, --user-id <userId>', 'user ID', '123') .option('-r, --region <region>', 'AWS region', 'us-east-1') .option('--pika-service <name>', 'pika service name', 'pika') .option('--agent-service <name>', 'agent service name', 'my-agent') .action(async (message, options) => { const { stage, userId, region, pikaService, agentService } = options;
// Get parameters from SSM const converseFunctionUrl = await getParameterFromSSM( `/stack/${pikaService}/${stage}/function/converse_url`, region ); const agentId = await getParameterFromSSM( `/stack/${agentService}/${stage}/agent_id`, region ); const jwtSecret = await getParameterFromSSM( `/stack/${pikaService}/${stage}/jwt-secret`, region );
// Create JWT const user = { userId, customUserData: undefined }; const jwtToken = createJWT(user, jwtSecret);
// Create request const request = { invocationMode: 'direct-agent-invoke', message, agentId, userId, features: { verifyResponse: { enabled: true }, instructionAugmentation: { enabled: true }, tags: { tagsEnabled: [] } } };
// Invoke agent await invokeAgent(converseFunctionUrl, request, jwtToken, region); });
program.parse();}
main().catch(console.error);Use the CLI
Section titled “Use the CLI”# Install dependenciesnpm install
# Run the CLIpnpm run cli "What's the weather in Tokyo?"
# Or with pnpmpnpm run cli "What's the weather in Tokyo?"
# Specify different user IDpnpm run cli "What's the weather?" --user-id "my-user-123"Testing Checklist
Section titled “Testing Checklist”Verify direct agent invocation works:
Working Sample
Section titled “Working Sample”The Pika repository includes a complete working sample:
Location: services/samples/weather-direct/
cd services/samples/weather-direct
# Install dependenciesnpm install
# Deploy the stackpnpm run cdk:deploy
# Test the CLIpnpm run cli -- "What's the weather in Tokyo?"
# With pnpmpnpm run cli "What's the weather in Tokyo?"Best Practices
Section titled “Best Practices”Authentication
Section titled “Authentication”- AWS Credentials: Ensure environment has valid AWS credentials
- JWT Secrets: Always use
WithDecryption: truewhen retrieving from SSM - Bearer Prefix: Include
Bearerprefix inx-chat-authheader - User ID: Must exist in chat-user table
- Token Caching: Cache JWT tokens (valid for 1 hour)
Error Handling
Section titled “Error Handling”- Check HTTP status codes
- Handle streaming connection drops gracefully
- Implement retry logic for transient failures
- Log errors for debugging
Performance
Section titled “Performance”- Reuse HTTP connections when possible
- Buffer streaming responses for better UX
- Set appropriate timeouts
- Monitor response times
Troubleshooting
Section titled “Troubleshooting”Authentication Failures
Section titled “Authentication Failures”- Verify AWS credentials configured
- Check JWT secret retrieved with decryption
- Ensure Bearer prefix in auth header
- Confirm user ID exists in database
- Review IAM permissions
Agent Not Found
Section titled “Agent Not Found”- Verify agent deployed correctly
- Check agent ID matches exactly
- Ensure agent registered in DynamoDB
- Review deployment logs
Streaming Issues
Section titled “Streaming Issues”- Check network connectivity
- Verify response body is readable stream
- Handle partial JSON lines correctly
- Implement proper error recovery
Next Steps
Section titled “Next Steps”- Define Agents Using Configuration - Learn agent configuration
- Implement Tool Use with Inline Tools - Add tools to agents
- Build Custom Web Components - Create UI for agents
Related Documentation
Section titled “Related Documentation”- Agent Configuration Reference - Complete agent options
- AWS Lambda Function URLs - AWS documentation
- JWT Authentication - Learn about JWT tokens