--- title: "A2A Capabilities | Kastrax Docs" description: "Detailed guide on defining and using capabilities in the Agent-to-Agent (A2A) protocol." --- # A2A Capabilities ✅ [EN] Source: https://kastrax.ai/en/docs/a2a/a2a-capabilities Capabilities are the core functional units of the A2A protocol, representing the functions that agents can expose to other agents. This guide explains how to define, implement, and use capabilities in Kastrax. ## What Are Capabilities? ✅ In the A2A protocol, capabilities are well-defined functions that agents can expose to other agents. Each capability has: - A unique identifier - A human-readable name and description - A set of parameters with types and descriptions - A return type - Optional examples ## Defining Capabilities ✅ You can define capabilities using the Kastrax DSL: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import kotlinx.coroutines.runBlocking fun main() = runBlocking { val a2aInstance = a2a { a2aAgent { id = "weather-agent" name = "Weather Agent" description = "Provides weather information" // Define a capability capability { id = "get_weather" name = "Get Weather" description = "Gets the current weather for a location" // Define parameters parameter { name = "location" type = "string" description = "The location to get weather for" required = true } parameter { name = "units" type = "string" description = "The units to use (metric or imperial)" required = false } // Define return type returnType = "json" // Add examples example { input = mapOf( "location" to "New York", "units" to "metric" ) output = mapOf( "temperature" to "22", "conditions" to "Sunny", "humidity" to "65%" ) description = "Get weather for New York in metric units" } } // Configure authentication authentication { type = AuthType.API_KEY } } } } ``` ## Implementing Capability Handlers ✅ You need to implement handlers for your capabilities: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import ai.kastrax.a2a.model.InvokeRequest import ai.kastrax.a2a.model.InvokeResponse import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put fun main() = runBlocking { val a2aInstance = a2a { a2aAgent { id = "weather-agent" name = "Weather Agent" description = "Provides weather information" // Define a capability capability { id = "get_weather" name = "Get Weather" description = "Gets the current weather for a location" // Define parameters parameter { name = "location" type = "string" description = "The location to get weather for" required = true } parameter { name = "units" type = "string" description = "The units to use (metric or imperial)" required = false } // Define return type returnType = "json" } // Implement capability handler handler("get_weather") { request: InvokeRequest -> // Extract parameters val location = request.parameters["location"]?.toString() ?: "" val units = request.parameters["units"]?.toString() ?: "metric" // Fetch weather data (simplified example) val weatherData = fetchWeatherData(location, units) // Create response InvokeResponse( id = request.id, result = buildJsonObject { put("temperature", JsonPrimitive(weatherData.temperature)) put("conditions", JsonPrimitive(weatherData.conditions)) put("humidity", JsonPrimitive(weatherData.humidity)) } ) } // Configure authentication authentication { type = AuthType.API_KEY } } } } // Simplified weather data class data class WeatherData( val temperature: String, val conditions: String, val humidity: String ) // Simplified weather data fetching function fun fetchWeatherData(location: String, units: String): WeatherData { // In a real implementation, this would call a weather API return WeatherData( temperature = "22", conditions = "Sunny", humidity = "65%" ) } ``` ## Invoking Capabilities ✅ You can invoke capabilities from other agents: ```kotlin import ai.kastrax.a2a.client.A2AClient import ai.kastrax.a2a.client.A2AClientConfig import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.jsonObject fun main() = runBlocking { // Create an A2A client val client = A2AClient( A2AClientConfig( serverUrl = "http://localhost:8080", apiKey = "your-api-key" ) ) // Invoke the get_weather capability val response = client.invoke( "get_weather", mapOf( "location" to "New York", "units" to "metric" ) ) // Process the response val result = response.result.jsonObject val temperature = result["temperature"]?.jsonPrimitive?.content val conditions = result["conditions"]?.jsonPrimitive?.content val humidity = result["humidity"]?.jsonPrimitive?.content println("Weather in New York:") println("Temperature: $temperature°C") println("Conditions: $conditions") println("Humidity: $humidity") // Close the client client.close() } ``` ## Capability Discovery ✅ Agents can discover capabilities of other agents: ```kotlin import ai.kastrax.a2a.client.A2AClient import ai.kastrax.a2a.client.A2AClientConfig import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an A2A client val client = A2AClient( A2AClientConfig( serverUrl = "http://localhost:8080", apiKey = "your-api-key" ) ) // Get the agent card val agentCard = client.getAgentCard() // List all capabilities println("Agent: ${agentCard.name}") println("Capabilities:") for (capability in agentCard.capabilities) { println("- ${capability.name}: ${capability.description}") println(" Parameters:") for (parameter in capability.parameters) { val requiredText = if (parameter.required) "required" else "optional" println(" - ${parameter.name} (${parameter.type}, $requiredText): ${parameter.description}") } println(" Return type: ${capability.returnType}") } // Close the client client.close() } ``` ## Capability Validation ✅ The A2A protocol validates capability parameters: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import ai.kastrax.a2a.model.JsonObject import ai.kastrax.a2a.validation.ParameterValidator import kotlinx.coroutines.runBlocking fun main() = runBlocking { val a2aInstance = a2a { a2aAgent { id = "data-validator-agent" name = "Data Validator Agent" description = "Validates data against schemas" // Define a capability with schema validation capability { id = "validate_user" name = "Validate User" description = "Validates user data against a schema" // Define a parameter with a JSON schema parameter { name = "user_data" type = "object" description = "User data to validate" required = true schema = JsonObject( mapOf( "type" to JsonPrimitive("object"), "required" to JsonArray(listOf("name", "email")), "properties" to JsonObject( mapOf( "name" to JsonObject( mapOf( "type" to JsonPrimitive("string"), "minLength" to JsonPrimitive(1) ) ), "email" to JsonObject( mapOf( "type" to JsonPrimitive("string"), "format" to JsonPrimitive("email") ) ), "age" to JsonObject( mapOf( "type" to JsonPrimitive("number"), "minimum" to JsonPrimitive(18) ) ) ) ) ) ) } // Define return type returnType = "json" } // Implement capability handler with validation handler("validate_user") { request -> // The parameters are automatically validated against the schema // If validation fails, an error response is returned // Process the validated data val userData = request.parameters["user_data"]?.jsonObject // Return validation result InvokeResponse( id = request.id, result = buildJsonObject { put("valid", JsonPrimitive(true)) put("message", JsonPrimitive("User data is valid")) } ) } // Configure authentication authentication { type = AuthType.API_KEY } } } } ``` ## Streaming Capabilities ✅ You can implement streaming capabilities for long-running operations: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import ai.kastrax.a2a.model.StreamingInvokeRequest import ai.kastrax.a2a.model.StreamingInvokeResponse import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put fun main() = runBlocking { val a2aInstance = a2a { a2aAgent { id = "data-processor-agent" name = "Data Processor Agent" description = "Processes large datasets with streaming updates" // Define a streaming capability streamingCapability { id = "process_dataset" name = "Process Dataset" description = "Processes a large dataset with streaming progress updates" // Define parameters parameter { name = "dataset_url" type = "string" description = "URL of the dataset to process" required = true } // Define return type returnType = "json" } // Implement streaming capability handler streamingHandler("process_dataset") { request: StreamingInvokeRequest -> // Return a flow of streaming responses flow { val datasetUrl = request.parameters["dataset_url"]?.toString() ?: "" // Simulate processing with progress updates for (i in 1..10) { val progress = i * 10 // Emit progress update emit( StreamingInvokeResponse( id = request.id, result = buildJsonObject { put("progress", JsonPrimitive(progress)) put("status", JsonPrimitive("Processing")) put("message", JsonPrimitive("Processed $progress% of the dataset")) } ) ) // Simulate processing time delay(1000) } // Emit final result emit( StreamingInvokeResponse( id = request.id, result = buildJsonObject { put("progress", JsonPrimitive(100)) put("status", JsonPrimitive("Completed")) put("message", JsonPrimitive("Dataset processing completed")) put("result_url", JsonPrimitive("https://example.com/results/123")) } ) ) } } // Configure authentication authentication { type = AuthType.API_KEY } } } } ``` ## Capability Composition ✅ You can compose capabilities from multiple agents: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.client.A2AClient import ai.kastrax.a2a.client.A2AClientConfig import ai.kastrax.a2a.model.AuthType import ai.kastrax.a2a.model.InvokeRequest import ai.kastrax.a2a.model.InvokeResponse import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.put fun main() = runBlocking { val a2aInstance = a2a { a2aAgent { id = "data-pipeline-agent" name = "Data Pipeline Agent" description = "Orchestrates data processing pipelines" // Define a capability that composes other capabilities capability { id = "process_and_visualize" name = "Process and Visualize" description = "Processes a dataset and creates visualizations" // Define parameters parameter { name = "dataset_url" type = "string" description = "URL of the dataset to process" required = true } parameter { name = "visualization_type" type = "string" description = "Type of visualization to create" required = true } // Define return type returnType = "json" } // Implement capability handler that composes other capabilities handler("process_and_visualize") { request: InvokeRequest -> // Extract parameters val datasetUrl = request.parameters["dataset_url"]?.toString() ?: "" val visualizationType = request.parameters["visualization_type"]?.toString() ?: "" // Create clients for other agents val processorClient = A2AClient( A2AClientConfig( serverUrl = "http://localhost:8081", apiKey = "processor-api-key" ) ) val visualizerClient = A2AClient( A2AClientConfig( serverUrl = "http://localhost:8082", apiKey = "visualizer-api-key" ) ) try { // Step 1: Process the dataset val processingResponse = processorClient.invoke( "process_dataset", mapOf( "dataset_url" to datasetUrl ) ) val processedDataUrl = processingResponse.result.jsonObject["result_url"]?.jsonPrimitive?.content ?: throw Exception("Processing failed") // Step 2: Create visualizations val visualizationResponse = visualizerClient.invoke( "create_visualization", mapOf( "data_url" to processedDataUrl, "visualization_type" to visualizationType ) ) val visualizationUrl = visualizationResponse.result.jsonObject["visualization_url"]?.jsonPrimitive?.content ?: throw Exception("Visualization failed") // Return combined result InvokeResponse( id = request.id, result = buildJsonObject { put("processed_data_url", JsonPrimitive(processedDataUrl)) put("visualization_url", JsonPrimitive(visualizationUrl)) put("status", JsonPrimitive("Completed")) } ) } finally { // Close clients processorClient.close() visualizerClient.close() } } // Configure authentication authentication { type = AuthType.API_KEY } } } } ``` ## Next Steps ✅ Now that you understand A2A capabilities, you can: 1. Learn about [A2A security](./a2a-security.mdx) 2. Explore [multi-agent systems](./multi-agent-systems.mdx) 3. Implement [A2A workflows](./a2a-workflows.mdx) --- title: "A2A Security | Kastrax Docs" description: "Detailed guide on security mechanisms in the Agent-to-Agent (A2A) protocol." --- # A2A Security ✅ [EN] Source: https://kastrax.ai/en/docs/a2a/a2a-security Security is a critical aspect of the A2A protocol, ensuring that agent communications are protected and that only authorized agents can access capabilities. This guide explains the security mechanisms in the Kastrax A2A implementation. ## Security Overview ✅ The Kastrax A2A protocol implements several security mechanisms: 1. **Authentication**: Verifying the identity of agents 2. **Authorization**: Controlling access to capabilities 3. **Encryption**: Protecting data in transit 4. **Rate Limiting**: Preventing abuse 5. **Audit Logging**: Tracking agent interactions 6. **Input Validation**: Preventing injection attacks ## Authentication Methods ✅ The A2A protocol supports multiple authentication methods: ### API Key Authentication ✅ API key authentication is the simplest method: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import kotlinx.coroutines.runBlocking fun main() = runBlocking { val a2aInstance = a2a { a2aAgent { id = "secure-agent" name = "Secure Agent" description = "An agent with API key authentication" // Configure API key authentication authentication { type = AuthType.API_KEY details = mapOf( "header" to "X-API-Key" ) } // Define capabilities... } // Configure server with security server { port = 8080 enableCors = true // Configure API keys apiKeys = mapOf( "client1" to "api-key-1", "client2" to "api-key-2" ) } } } ``` ### JWT Authentication ✅ JWT (JSON Web Token) authentication provides more advanced security: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import ai.kastrax.a2a.security.JwtConfig import kotlinx.coroutines.runBlocking fun main() = runBlocking { val a2aInstance = a2a { a2aAgent { id = "secure-agent" name = "Secure Agent" description = "An agent with JWT authentication" // Configure JWT authentication authentication { type = AuthType.JWT details = mapOf( "issuer" to "kastrax-a2a", "audience" to "secure-agent" ) } // Define capabilities... } // Configure server with JWT security server { port = 8080 enableCors = true // Configure JWT jwt { secret = "your-jwt-secret" // In production, use a secure secret issuer = "kastrax-a2a" audience = "secure-agent" expirationMs = 3600000 // 1 hour } } } } ``` ### OAuth2 Authentication ✅ OAuth2 authentication enables integration with external identity providers: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import ai.kastrax.a2a.security.OAuth2Config import kotlinx.coroutines.runBlocking fun main() = runBlocking { val a2aInstance = a2a { a2aAgent { id = "secure-agent" name = "Secure Agent" description = "An agent with OAuth2 authentication" // Configure OAuth2 authentication authentication { type = AuthType.OAUTH2 details = mapOf( "issuer" to "https://auth.example.com", "audience" to "secure-agent" ) } // Define capabilities... } // Configure server with OAuth2 security server { port = 8080 enableCors = true // Configure OAuth2 oauth2 { issuer = "https://auth.example.com" audience = "secure-agent" jwksUrl = "https://auth.example.com/.well-known/jwks.json" } } } } ``` ## Authorization ✅ Authorization controls which agents can access which capabilities: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import ai.kastrax.a2a.security.Role import ai.kastrax.a2a.security.Permission import kotlinx.coroutines.runBlocking fun main() = runBlocking { val a2aInstance = a2a { // Define roles role("reader") { permission(Permission.READ) } role("writer") { permission(Permission.READ) permission(Permission.WRITE) } role("admin") { permission(Permission.READ) permission(Permission.WRITE) permission(Permission.ADMIN) } a2aAgent { id = "secure-agent" name = "Secure Agent" description = "An agent with role-based authorization" // Configure authentication authentication { type = AuthType.API_KEY } // Define a capability with role-based access capability { id = "get_sensitive_data" name = "Get Sensitive Data" description = "Gets sensitive data that requires authorization" // Require admin role requiredRole = "admin" // Define parameters... returnType = "json" } // Define a capability with lower access requirements capability { id = "get_public_data" name = "Get Public Data" description = "Gets public data that requires minimal authorization" // Require reader role requiredRole = "reader" // Define parameters... returnType = "json" } } // Configure server with role assignments server { port = 8080 enableCors = true // Configure API keys with roles apiKeys = mapOf( "client1" to Pair("api-key-1", "reader"), "client2" to Pair("api-key-2", "writer"), "admin" to Pair("admin-api-key", "admin") ) } } } ``` ## Encryption ✅ The A2A protocol uses HTTPS to encrypt data in transit: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import ai.kastrax.a2a.security.TlsConfig import kotlinx.coroutines.runBlocking import java.io.File fun main() = runBlocking { val a2aInstance = a2a { a2aAgent { id = "secure-agent" name = "Secure Agent" description = "An agent with HTTPS encryption" // Configure authentication authentication { type = AuthType.API_KEY } // Define capabilities... } // Configure server with HTTPS server { port = 8443 enableCors = true // Configure TLS tls { keyStore = File("keystore.jks") keyStorePassword = "password" keyAlias = "a2a-server" privateKeyPassword = "password" } } } } ``` ## Rate Limiting ✅ Rate limiting prevents abuse of the A2A API: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import ai.kastrax.a2a.security.RateLimitConfig import kotlinx.coroutines.runBlocking fun main() = runBlocking { val a2aInstance = a2a { a2aAgent { id = "secure-agent" name = "Secure Agent" description = "An agent with rate limiting" // Configure authentication authentication { type = AuthType.API_KEY } // Define capabilities... } // Configure server with rate limiting server { port = 8080 enableCors = true // Configure rate limiting rateLimit { requestsPerMinute = 60 // 1 request per second burstSize = 10 // Allow bursts of up to 10 requests // Configure per-client limits clientLimits = mapOf( "client1" to 30, // 30 requests per minute "client2" to 120 // 120 requests per minute ) } } } } ``` ## Audit Logging ✅ Audit logging tracks all agent interactions: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import ai.kastrax.a2a.monitoring.AuditLogConfig import kotlinx.coroutines.runBlocking fun main() = runBlocking { val a2aInstance = a2a { a2aAgent { id = "secure-agent" name = "Secure Agent" description = "An agent with audit logging" // Configure authentication authentication { type = AuthType.API_KEY } // Define capabilities... } // Configure monitoring with audit logging monitoring { // Configure audit logging auditLog { enabled = true logRequests = true logResponses = true sensitiveFields = listOf("password", "apiKey", "token") // Configure storage storage = "file" // Options: "file", "database", "cloud" filePath = "logs/audit.log" // Configure retention retentionDays = 90 } } } } ``` ## Input Validation ✅ Input validation prevents injection attacks: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import ai.kastrax.a2a.model.JsonObject import ai.kastrax.a2a.validation.ParameterValidator import kotlinx.coroutines.runBlocking fun main() = runBlocking { val a2aInstance = a2a { a2aAgent { id = "secure-agent" name = "Secure Agent" description = "An agent with input validation" // Configure authentication authentication { type = AuthType.API_KEY } // Define a capability with schema validation capability { id = "process_data" name = "Process Data" description = "Processes data with strict validation" // Define a parameter with a JSON schema parameter { name = "data" type = "object" description = "Data to process" required = true schema = JsonObject( mapOf( "type" to JsonPrimitive("object"), "required" to JsonArray(listOf("id", "value")), "properties" to JsonObject( mapOf( "id" to JsonObject( mapOf( "type" to JsonPrimitive("string"), "pattern" to JsonPrimitive("^[a-zA-Z0-9_-]+$") // Alphanumeric only ) ), "value" to JsonObject( mapOf( "type" to JsonPrimitive("string"), "maxLength" to JsonPrimitive(1000) ) ) ) ) ) ) } // Define return type returnType = "json" } // Implement capability handler handler("process_data") { request -> // Parameters are automatically validated against the schema // If validation fails, an error response is returned // Process the validated data val data = request.parameters["data"]?.jsonObject // Return result InvokeResponse( id = request.id, result = buildJsonObject { put("status", JsonPrimitive("success")) put("message", JsonPrimitive("Data processed successfully")) } ) } } } } ``` ## Security Best Practices ✅ When implementing A2A security, follow these best practices: 1. **Use HTTPS**: Always use HTTPS in production to encrypt data in transit 2. **Implement Authentication**: Choose an appropriate authentication method for your use case 3. **Apply Authorization**: Use role-based access control to restrict capability access 4. **Validate Input**: Use schema validation to prevent injection attacks 5. **Implement Rate Limiting**: Protect your API from abuse 6. **Enable Audit Logging**: Track all agent interactions for security monitoring 7. **Rotate Secrets**: Regularly rotate API keys, JWT secrets, and other credentials 8. **Minimize Attack Surface**: Only expose necessary capabilities 9. **Apply Principle of Least Privilege**: Grant minimal permissions required for each agent 10. **Monitor for Suspicious Activity**: Set up alerts for unusual patterns ## Next Steps ✅ Now that you understand A2A security, you can: 1. Learn about [multi-agent systems](./multi-agent-systems.mdx) 2. Explore [A2A workflows](./a2a-workflows.mdx) 3. Implement [A2A monitoring](./a2a-monitoring.mdx) --- title: "A2A Protocol Overview | Kastrax Docs" description: "Overview of the Agent-to-Agent (A2A) protocol in Kastrax, detailing how agents can communicate and collaborate." --- # A2A Protocol Overview ✅ [EN] Source: https://kastrax.ai/en/docs/a2a/overview The Kastrax Agent-to-Agent (A2A) protocol provides a standardized way for AI agents to communicate, discover each other's capabilities, and collaborate on complex tasks. Built on top of the actor model, the A2A protocol enables the creation of sophisticated multi-agent systems that can work together to solve complex problems. ## What is the A2A Protocol? ✅ The Agent-to-Agent (A2A) protocol is a communication standard that enables AI agents to: - Discover other agents and their capabilities dynamically - Exchange structured messages with well-defined semantics - Invoke capabilities provided by other agents in a type-safe manner - Collaborate on complex tasks through delegation and coordination - Maintain security and access control with authentication and authorization - Scale across distributed environments with location transparency - Handle failures gracefully with supervision and recovery mechanisms ## A2A Protocol Architecture ✅ The Kastrax A2A protocol consists of several key components: 1. **Agent Card**: A structured description of an agent's identity, capabilities, and metadata 2. **Capabilities Registry**: A catalog of functions that agents can expose to other agents 3. **Message Protocol**: Standardized format for structured communication between agents 4. **Discovery Service**: A registry for finding agents and their capabilities dynamically 5. **Security Layer**: Authentication, authorization, and encryption mechanisms 6. **Actor Integration**: Seamless integration with the actor model for distributed communication 7. **Supervision Hierarchy**: Parent-child relationships for monitoring and recovery 8. **Capability Negotiation**: Dynamic negotiation of capabilities between agents ## Basic A2A Implementation ✅ Here's a simple example of creating an A2A agent in Kastrax: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a base agent val baseAgent = agent { name("DataAnalysisAgent") description("An agent that can analyze data") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // Create an A2A instance val a2aInstance = a2a { // Register the base agent agent(baseAgent) // Create an A2A agent a2aAgent { id = "data-analysis-agent" name = "Data Analysis Agent" description = "Provides data analysis and visualization capabilities" baseAgent = baseAgent // Define a capability capability { id = "analyze_data" name = "Analyze Data" description = "Analyzes a dataset and returns statistical results" // Define parameters parameter { name = "dataset_url" type = "string" description = "URL of the dataset to analyze" required = true } parameter { name = "analysis_type" type = "string" description = "Type of analysis to perform" required = true } returnType = "json" } // Configure authentication authentication { type = AuthType.API_KEY } } // Configure the server server { port = 8080 enableCors = true } // Add discovery service discovery("http://localhost:8080") } // Start the A2A server a2aInstance.start() println("A2A server started on port 8080") println("Press Enter to exit...") readLine() // Stop the A2A server a2aInstance.stop() } ``` ## Agent Card ✅ The Agent Card is a structured description of an agent's capabilities: ```kotlin // Define an agent card val agentCard = AgentCard( id = "data-analysis-agent", name = "Data Analysis Agent", description = "Provides data analysis and visualization capabilities", version = "1.0.0", endpoint = "http://localhost:8080/a2a/v1", capabilities = listOf( Capability( id = "analyze_data", name = "Analyze Data", description = "Analyzes a dataset and returns statistical results", parameters = listOf( Parameter( name = "dataset_url", type = "string", description = "URL of the dataset to analyze", required = true ), Parameter( name = "analysis_type", type = "string", description = "Type of analysis to perform", required = true ) ), returnType = "json" ) ), authentication = Authentication( type = AuthType.API_KEY ) ) ``` ## A2A Messages ✅ A2A agents communicate using structured messages: ```kotlin // Create a capability request message val capabilityRequest = CapabilityRequest( id = UUID.randomUUID().toString() ) // Create an invoke request message val invokeRequest = InvokeRequest( id = UUID.randomUUID().toString(), capabilityId = "analyze_data", parameters = mapOf( "dataset_url" to JsonPrimitive("https://example.com/dataset.csv"), "analysis_type" to JsonPrimitive("statistical") ) ) ``` ## A2A Client ✅ You can use the A2A client to interact with A2A agents: ```kotlin import ai.kastrax.a2a.client.A2AClient import ai.kastrax.a2a.client.A2AClientConfig import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an A2A client val client = A2AClient( A2AClientConfig( serverUrl = "http://localhost:8080", apiKey = "your-api-key" ) ) // Get the agent card val agentCard = client.getAgentCard() println("Agent: ${agentCard.name}") println("Capabilities: ${agentCard.capabilities.map { it.name }}") // Invoke a capability val response = client.invoke( "analyze_data", mapOf( "dataset_url" to "https://example.com/dataset.csv", "analysis_type" to "statistical" ) ) println("Response: $response") // Close the client client.close() } ``` ## A2A Server ✅ The A2A server exposes agent capabilities via HTTP: ```kotlin import ai.kastrax.a2a.server.A2AServer import ai.kastrax.a2a.server.A2AServerConfig import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an A2A server val server = A2AServer( A2AServerConfig( port = 8080, enableCors = true ) ) // Register an agent server.registerAgent(a2aAgent) // Start the server server.start() println("A2A server started on port 8080") println("Press Enter to exit...") readLine() // Stop the server server.stop() } ``` ## A2A Discovery Service ✅ The discovery service helps agents find each other: ```kotlin import ai.kastrax.a2a.discovery.A2ADiscoveryService import ai.kastrax.a2a.discovery.A2ADiscoveryConfig import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a discovery service val discoveryService = A2ADiscoveryService( A2ADiscoveryConfig( enableAutoDiscovery = true, discoveryInterval = 60000 // 1 minute ) ) // Add a server discoveryService.addServer("http://localhost:8080") // Find agents by capability val agents = discoveryService.findAgentsByCapability("analyze_data") for (agent in agents) { println("Found agent: ${agent.name}") } } ``` ## Multi-Agent Collaboration ✅ A2A enables complex multi-agent collaboration: ```kotlin import ai.kastrax.a2a.a2a import ai.kastrax.a2a.agent.a2aAgent import ai.kastrax.a2a.model.AuthType import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create A2A instance val a2aInstance = a2a { // Register data analysis agent a2aAgent { id = "data-analysis-agent" name = "Data Analysis Agent" description = "Provides data analysis capabilities" // Configuration... } // Register visualization agent a2aAgent { id = "visualization-agent" name = "Visualization Agent" description = "Provides data visualization capabilities" // Configuration... } // Register coordinator agent a2aAgent { id = "coordinator-agent" name = "Coordinator Agent" description = "Coordinates analysis and visualization tasks" // Configuration... } // Configure server server { port = 8080 enableCors = true } } // Start the A2A server a2aInstance.start() // Create a task val task = a2aInstance.createTask( Message( role = "user", parts = listOf( TextPart( text = "Analyze and visualize this dataset: https://example.com/dataset.csv" ) ) ) ) // Monitor task progress task.onUpdate { status -> println("Task status: ${status.state}") } // Wait for task completion val result = task.await() println("Task result: $result") // Stop the A2A server a2aInstance.stop() } ``` ## Next Steps ✅ Now that you understand the A2A protocol, you can: 1. Learn about [creating A2A agents](./creating-a2a-agents.mdx) 2. Explore [A2A capabilities](./a2a-capabilities.mdx) 3. Implement [multi-agent systems](./multi-agent-systems.mdx) 4. Set up [A2A security](./a2a-security.mdx) --- title: Actor-Agent Integration | Kastrax Docs description: Detailed guide on integrating Kastrax AI agents with the Actor model for building distributed, concurrent agent systems. --- # Actor-Agent Integration ✅ [EN] Source: https://kastrax.ai/en/docs/actor/actor-agent-integration-kotlin Kastrax provides a powerful integration between AI agents and the Actor model, allowing you to build distributed, concurrent agent systems. This guide explains how to integrate Kastrax agents with the Actor model using Kotlin. ## Overview ✅ The Actor-Agent integration in Kastrax combines: 1. **Kastrax Agents**: Intelligent AI agents with LLM capabilities 2. **kactor**: A Kotlin implementation of the Actor model This integration enables: - Distributed agent networks - Asynchronous communication between agents - Fault tolerance through supervision - Horizontal scaling across multiple machines ## KastraxActor ✅ The `KastraxActor` class wraps a Kastrax agent as an actor: ```kotlin import ai.kastrax.actor.KastraxActor import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import actor.proto.ActorSystem import actor.proto.Props import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an Actor system val system = ActorSystem("kastrax-system") // Create a Kastrax agent val myAgent = agent { name("MyAgent") description("A simple agent") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // Create a KastraxActor with the agent val props = Props.create { KastraxActor(myAgent) } val agentPid = system.root.spawn(props) // Send a message to the agent system.root.send(agentPid, AgentRequest("Hello, agent!")) system.shutdown() } ``` ## Message Types ✅ The Actor-Agent integration supports several message types: ### AgentRequest Used to send a text prompt to an agent: ```kotlin val request = AgentRequest( prompt = "What is the capital of France?", options = AgentGenerateOptions(temperature = 0.5) ) system.root.send(agentPid, request) ``` ### AgentStreamRequest Used for streaming responses: ```kotlin val streamRequest = AgentStreamRequest( prompt = "Tell me a story", options = AgentStreamOptions(temperature = 0.7), sender = senderPid ) system.root.send(agentPid, streamRequest) ``` ### MultimodalRequest Used for sending multimodal content (text, images, etc.): ```kotlin val multimodalRequest = MultimodalRequest( message = MultimodalMessage( content = "Describe this image", type = MultimodalType.TEXT ), options = AgentGenerateOptions() ) system.root.send(agentPid, multimodalRequest) ``` ### ToolCallRequest Used to directly call a tool: ```kotlin val toolRequest = ToolCallRequest( toolName = "calculator", input = JsonObject(mapOf("expression" to JsonPrimitive("2+2"))) ) system.root.send(agentPid, toolRequest) ``` ### CollaborationRequest Used for agent collaboration: ```kotlin val collaborationRequest = CollaborationRequest( task = "Research quantum computing", sender = "ResearchAgent", metadata = mapOf("priority" to "high") ) system.root.send(agentPid, collaborationRequest) ``` ## DSL for Actor-Agent Integration ✅ Kastrax provides a DSL for creating actor-based agents: ```kotlin import ai.kastrax.actor.actorAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import actor.proto.ActorSystem import kotlinx.coroutines.runBlocking import java.time.Duration fun main() = runBlocking { // Create an Actor system val system = ActorSystem("kastrax-system") // Create an actor-agent using the DSL val agentPid = system.actorAgent { // Configure the agent agent { name("AssistantAgent") description("A helpful assistant") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { tool("calculator") { description("Performs basic arithmetic operations") parameters { parameter("expression", "Mathematical expression to evaluate", String::class) } execute { params -> val expression = params["expression"] as String // Simple expression evaluator "Result: ${evaluateExpression(expression)}" } } } } // Configure the actor actor { // Set supervision strategy oneForOneStrategy { maxRetries = 3 withinTimeRange = Duration.ofMinutes(1) } // Set mailbox type unboundedMailbox() } } // Use the actor-agent system.root.send(agentPid, AgentRequest("What is 2 + 2?")) system.shutdown() } // Simple expression evaluator fun evaluateExpression(expression: String): Double { // Implementation... return 4.0 } ``` ## Agent Networks ✅ You can create networks of agents that collaborate to solve complex tasks: ```kotlin import ai.kastrax.actor.agentNetwork import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import actor.proto.ActorSystem import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an Actor system val system = ActorSystem("kastrax-system") // Create an agent network val network = system.agentNetwork { // Configure the coordinator agent coordinator { agent { name("Coordinator") description("A coordinator that manages multiple expert agents") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } actor { oneForOneStrategy { maxRetries = 5 } } } // Add expert agents agent("researcher") { agent { name("Researcher") description("A research expert") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } } agent("writer") { agent { name("Writer") description("A writing expert") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } } agent("critic") { agent { name("Critic") description("A critical reviewer") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } } } // Use the network val response = network.process("Research the impact of AI on healthcare") println(response) system.shutdown() } ``` ## Communication Patterns ✅ The Actor-Agent integration supports various communication patterns: ### Fire and Forget ```kotlin // Send a message without waiting for a response system.root.send(agentPid, AgentRequest("Hello, agent!")) ``` ### Request-Response ```kotlin // Send a message and wait for a response val response = system.root.requestAwait( agentPid, AgentRequest("What is your name?") ) println("Response: ${response.text}") ``` ### Streaming ```kotlin // Send a message and receive a stream of responses system.root.send( agentPid, AgentStreamRequest( prompt = "Generate a story", options = AgentStreamOptions(), sender = self ) ) // Handle stream chunks in the receiving actor override suspend fun Context.receive(msg: Any) { when (msg) { is AgentStreamChunk -> print(msg.chunk) is AgentStreamComplete -> println("\nStream complete") } } ``` ## Error Handling and Supervision ✅ The Actor model provides robust error handling through supervision: ```kotlin actor { oneForOneStrategy { maxRetries = 3 withinTimeRange = Duration.ofMinutes(1) decider = { pid, exception -> when (exception) { is IllegalArgumentException -> SupervisorDirective.Resume is RuntimeException -> SupervisorDirective.Restart else -> SupervisorDirective.Stop } } } } ``` ## Distributed Agent Systems ✅ You can create distributed agent systems using remote actors: ```kotlin import actor.proto.ActorSystem import actor.proto.remote.Remote import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an Actor system val system = ActorSystem("kastrax-system") // Configure remote capabilities val remote = Remote(system) remote.start("localhost", 8080) // Create an actor-agent val agentPid = system.actorAgent { agent { name("RemoteAgent") // Agent configuration... } } // Register the agent so it can be accessed remotely remote.register("remote-agent", agentPid) // Keep the system running println("Remote system running at localhost:8080") readLine() system.shutdown() } ``` And connecting to remote agents: ```kotlin import actor.proto.ActorSystem import actor.proto.PID import actor.proto.remote.Remote import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an Actor system val system = ActorSystem("kastrax-client") // Configure remote capabilities val remote = Remote(system) // Create a reference to the remote agent val remoteAgentPid = PID("localhost:8080", "remote-agent") // Send a message to the remote agent system.root.send( remoteAgentPid, AgentRequest("Hello from the client!") ) system.shutdown() } ``` ## Best Practices ✅ 1. **Message Immutability**: Ensure messages are immutable to avoid concurrency issues 2. **Actor Isolation**: Keep actors isolated and avoid shared state 3. **Supervision Strategies**: Use appropriate supervision strategies for error handling 4. **Message Patterns**: Choose the right message pattern for your use case 5. **Resource Management**: Properly manage actor lifecycles and resources 6. **Distributed Systems**: Consider network latency and failures in distributed systems 7. **Testing**: Test actors in isolation and in integration ## Advanced Features ✅ ### Multimodal Support The Actor-Agent integration supports multimodal interactions: ```kotlin // Send a text message val textRequest = MultimodalRequest( message = MultimodalMessage( content = "What is the capital of France?", type = MultimodalType.TEXT ), options = AgentGenerateOptions() ) system.root.send(agentPid, textRequest) // Send an image (if supported by the agent) val imageRequest = MultimodalRequest( message = MultimodalMessage( content = imageBytes, type = MultimodalType.IMAGE ), options = AgentGenerateOptions() ) system.root.send(agentPid, imageRequest) ``` ### Tool Integration Actors can directly call tools: ```kotlin val toolRequest = ToolCallRequest( toolName = "calculator", input = JsonObject(mapOf("expression" to JsonPrimitive("2+2"))) ) val response = system.root.requestAwait(agentPid, toolRequest) println("Result: ${response.result}") ``` ### Collaboration Agents can collaborate on complex tasks: ```kotlin val collaborationRequest = CollaborationRequest( task = "Research quantum computing and write a summary", sender = "UserAgent", metadata = mapOf("priority" to "high") ) val response = system.root.requestAwait( agentPid, collaborationRequest ) println("Collaboration result: ${response.result}") ``` ## Conclusion ✅ The Actor-Agent integration in Kastrax provides a powerful framework for building distributed, concurrent agent systems. By combining the intelligence of AI agents with the robustness of the Actor model, you can create complex agent networks that can solve a wide range of problems. ## Next Steps ✅ - Learn about [Remote Actors](./remote-actors-kotlin.mdx) - Explore [Agent Network Topologies](./agent-networks-kotlin.mdx) - Understand [Supervision Strategies](./supervision-kotlin.mdx) --- title: Actor Model Overview | Kastrax Docs description: Overview of the Actor model in Kastrax, detailing how it provides a foundation for building distributed, concurrent, and resilient AI agent systems. --- # Actor Model Overview ✅ [EN] Source: https://kastrax.ai/en/docs/actor/overview The Kastrax framework integrates the Actor model from the kactor library to provide a powerful foundation for building distributed, concurrent, and resilient AI agent systems. This integration combines the strengths of the actor model with advanced AI capabilities, creating a robust platform for developing complex agent networks. ## What is the Actor Model? ✅ The Actor model is a mathematical model of concurrent computation that treats "actors" as the universal primitives of concurrent computation. In response to a message it receives, an actor can: 1. Make local decisions 2. Create more actors 3. Send more messages 4. Determine how to respond to the next message received Actors are isolated from each other and communicate only through messages, which provides a clean separation of concerns and makes it easier to reason about concurrent systems. ## Actor Model in Kastrax ✅ In Kastrax, the Actor model is used to: 1. **Distribute Agents**: Run agents across multiple machines or processes 2. **Manage Concurrency**: Handle multiple agent interactions simultaneously without race conditions 3. **Ensure Resilience**: Recover from failures gracefully with supervision hierarchies 4. **Scale Systems**: Easily scale agent systems up or down based on demand 5. **Implement A2A Protocol**: Enable standardized communication between agents 6. **Support Multi-Agent Systems**: Build complex networks of specialized agents that collaborate 7. **Provide Location Transparency**: Access remote agents as if they were local ## Basic Actor Concepts ✅ ### Actors ✅ An actor is the fundamental unit of computation. It has: - A mailbox for receiving messages - Behavior that defines how to process messages - State that can be modified when processing messages - The ability to create other actors - The ability to send messages to other actors ### Messages ✅ Messages are the only way actors communicate. They are: - Immutable - Asynchronous - Processed one at a time by each actor ### Actor Systems ✅ Actor systems manage the lifecycle of actors and provide: - Actor creation and supervision - Message routing - Resource management - Configuration ## Creating a Basic Actor ✅ Here's how to create a simple actor in Kastrax: ```kotlin import actor.proto.Actor import actor.proto.Context import actor.proto.Props import actor.proto.spawn import kotlinx.coroutines.runBlocking // Define a message data class Greeting(val who: String) // Define an actor class GreetingActor : Actor { override suspend fun receive(context: Context) { val message = context.message when (message) { is Greeting -> { println("Hello, ${message.who}!") } else -> { println("Unknown message: $message") } } } } fun main() = runBlocking { // Create an actor system val system = actor.proto.ActorSystem.create() // Create an actor val greeter = system.spawn(Props.create(GreetingActor::class.java), "greeter") // Send a message to the actor greeter.tell(Greeting("World")) // Wait a bit for the message to be processed kotlinx.coroutines.delay(100) // Shutdown the system system.shutdown() } ``` ## Integrating Actors with Agents ✅ Kastrax allows you to wrap agents in actors to create distributed agent systems: ```kotlin import actor.proto.Actor import actor.proto.Context import actor.proto.Props import actor.proto.spawn import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking // Define messages data class GenerateRequest(val prompt: String) data class GenerateResponse(val text: String) // Define an actor that wraps an agent class AgentActor(private val agent: Agent) : Actor { override suspend fun receive(context: Context) { val message = context.message when (message) { is GenerateRequest -> { val response = agent.generate(message.prompt) context.sender.tell(GenerateResponse(response.text)) } else -> { println("Unknown message: $message") } } } companion object { fun props(agent: Agent): Props { return Props.create { AgentActor(agent) } } } } fun main() = runBlocking { // Create an agent val myAgent = agent { name("MyAgent") description("A simple agent") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // Create an actor system val system = actor.proto.ActorSystem.create() // Create an actor that wraps the agent val agentActor = system.spawn(AgentActor.props(myAgent), "agent-actor") // Create a future for the response val future = agentActor.ask(GenerateRequest("Tell me a joke"), 5000) // Wait for the response val response = future.await() as GenerateResponse println("Agent response: ${response.text}") // Shutdown the system system.shutdown() } ``` ## Remote Actors ✅ One of the most powerful features of the Actor model is the ability to distribute actors across multiple machines. Kastrax supports this through remote actors: ```kotlin import actor.proto.Actor import actor.proto.Context import actor.proto.Props import actor.proto.remote.RemoteConfig import actor.proto.remote.RemoteActorSystem import kotlinx.coroutines.runBlocking // Define a message data class Ping(val message: String) data class Pong(val message: String) // Define an actor class PongActor : Actor { override suspend fun receive(context: Context) { val message = context.message when (message) { is Ping -> { println("Received ping: ${message.message}") context.sender.tell(Pong("Pong response to: ${message.message}")) } else -> { println("Unknown message: $message") } } } } // Server code fun startServer() = runBlocking { // Create a remote configuration val config = RemoteConfig.create { hostname = "localhost" port = 8090 } // Create a remote actor system val system = RemoteActorSystem.create("server-system", config) // Create an actor val pongActor = system.spawn(Props.create(PongActor::class.java), "pong-actor") // Register the actor so it can be accessed remotely system.registerActor("pong", pongActor) println("Server started, press enter to exit...") readLine() // Shutdown the system system.shutdown() } // Client code fun startClient() = runBlocking { // Create a remote configuration val config = RemoteConfig.create { hostname = "localhost" port = 0 // Use any available port } // Create a remote actor system val system = RemoteActorSystem.create("client-system", config) // Get a reference to the remote actor val remotePongActor = system.getActorRef("pong@localhost:8090") // Send a message to the remote actor val future = remotePongActor.ask(Ping("Hello from client"), 5000) // Wait for the response val response = future.await() as Pong println("Received response: ${response.message}") // Shutdown the system system.shutdown() } ``` ## Actor Supervision ✅ Actors can supervise other actors, which allows for fault tolerance and recovery: ```kotlin import actor.proto.Actor import actor.proto.Context import actor.proto.Props import actor.proto.SupervisorStrategy import actor.proto.Directive import actor.proto.spawn import kotlinx.coroutines.runBlocking // Define messages data class Work(val input: Int) data class Result(val output: Int) // Define an actor that might fail class WorkerActor : Actor { override suspend fun receive(context: Context) { val message = context.message when (message) { is Work -> { if (message.input == 0) { throw ArithmeticException("Division by zero") } val result = 100 / message.input context.sender.tell(Result(result)) } else -> { println("Unknown message: $message") } } } } // Define a supervisor actor class SupervisorActor : Actor { private var workerRef: actor.proto.PID? = null override suspend fun receive(context: Context) { val message = context.message when (message) { is actor.proto.Started -> { // Create a worker when the supervisor starts workerRef = context.spawn(Props.create(WorkerActor::class.java), "worker") } is Work -> { // Forward the work to the worker workerRef?.tell(message) } is Result -> { println("Worker produced result: ${message.output}") } else -> { println("Unknown message: $message") } } } override fun supervisorStrategy(): SupervisorStrategy { return SupervisorStrategy.restartStrategy { _, _ -> // Restart the worker on any exception Directive.Restart } } } fun main() = runBlocking { // Create an actor system val system = actor.proto.ActorSystem.create() // Create a supervisor actor val supervisor = system.spawn(Props.create(SupervisorActor::class.java), "supervisor") // Send work to the supervisor supervisor.tell(Work(4)) // Should work fine kotlinx.coroutines.delay(100) supervisor.tell(Work(0)) // Will cause an exception kotlinx.coroutines.delay(100) supervisor.tell(Work(5)) // Should work again after restart kotlinx.coroutines.delay(100) // Shutdown the system system.shutdown() } ``` ## Actor Patterns ✅ ### Request-Response Pattern ✅ ```kotlin // Send a request and wait for a response val future = actorRef.ask(Request("some data"), 5000) val response = future.await() as Response println("Received response: ${response.data}") ``` ### Publish-Subscribe Pattern ✅ ```kotlin // Create a topic val topic = system.createTopic("events") // Subscribe to the topic topic.subscribe { event -> println("Received event: $event") } // Publish to the topic topic.publish(Event("something happened")) ``` ### Router Pattern ✅ ```kotlin // Create a round-robin router with 5 worker instances val router = system.createRouter( RouterConfig.roundRobin(5), Props.create(WorkerActor::class.java) ) // Send messages to the router for (i in 1..10) { router.tell(Work(i)) } ``` ## Next Steps ✅ Now that you understand the Actor model in Kastrax, you can: 1. Learn about [remote actor configuration](./remote-configuration.mdx) 2. Explore [actor supervision strategies](./supervision.mdx) 3. Implement [actor-based agent systems](./agent-actors.mdx) --- title: "Kastrax AI Agent Tools System | Agent Documentation | Kastrax" description: Comprehensive guide to implementing and using tools with Kastrax AI Agents. Learn how to create custom tools, integrate with external systems, and enhance your agents' capabilities. --- # Kastrax AI Agent Tools System ✅ [EN] Source: https://kastrax.ai/en/docs/agents/adding-tools Tools are a fundamental component of the Kastrax AI Agent framework that extend agents' capabilities beyond conversation. They allow agents to perform specific actions, access external systems, process data, and interact with the world. In Kastrax, tools are implemented as strongly-typed Kotlin functions with well-defined input/output schemas, execution logic, and error handling. ## Tool Architecture in Kastrax ✅ Kastrax implements a robust tool system with several key components: 1. **Tool Definition**: A strongly-typed interface that defines the tool's inputs, outputs, and metadata 2. **Tool Implementation**: The actual execution logic that performs the tool's function 3. **Tool Registry**: A central registry that makes tools available to agents 4. **Tool Invocation**: The mechanism by which agents call tools during execution This architecture ensures type safety, proper error handling, and seamless integration with the agent system. ## Creating Basic Tools ✅ Let's create a simple weather information tool that demonstrates the basic structure of Kastrax tools: ```kotlin filename="src/main/kotlin/ai/kastrax/tools/WeatherTool.kt" import ai.kastrax.core.tool.Tool import ai.kastrax.core.tool.ToolDefinition import ai.kastrax.core.tool.ToolResult import kotlinx.serialization.Serializable // Define the input parameters for the tool @Serializable data class WeatherToolInput( val city: String, val units: String = "metric" // Optional parameter with default value ) // Define the output structure @Serializable data class WeatherToolOutput( val temperature: Double, val conditions: String, val humidity: Int, val windSpeed: Double ) // Implement the tool class WeatherTool : Tool { // Define tool metadata override val definition = ToolDefinition( name = "get_weather", description = "Fetches current weather information for a specified city", inputType = WeatherToolInput::class, outputType = WeatherToolOutput::class ) // Implement the execution logic override suspend fun execute(input: WeatherToolInput): ToolResult { return try { // In a real implementation, this would call a weather API val weatherData = fetchWeatherData(input.city, input.units) ToolResult.Success( WeatherToolOutput( temperature = weatherData.temperature, conditions = weatherData.conditions, humidity = weatherData.humidity, windSpeed = weatherData.windSpeed ) ) } catch (e: Exception) { ToolResult.Error("Failed to fetch weather data: ${e.message}") } } // Helper method to fetch weather data (would be implemented with actual API calls) private suspend fun fetchWeatherData(city: String, units: String): WeatherData { // This would be an actual API call in a real implementation // For demonstration, we're returning mock data return WeatherData(22.5, "Sunny", 65, 10.2) } // Internal data class for the API response private data class WeatherData( val temperature: Double, val conditions: String, val humidity: Int, val windSpeed: Double ) } ``` ## Adding Tools to an Agent ✅ Once you've created your tools, you need to register them with your agent. In Kastrax, this is done by adding the tools to the agent's configuration: ```kotlin filename="src/main/kotlin/ai/kastrax/agents/WeatherAgent.kt" import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.AgentConfig import ai.kastrax.core.llm.DeepSeekProvider import ai.kastrax.tools.WeatherTool class WeatherAgent { // Create an instance of the weather tool private val weatherTool = WeatherTool() // Create the agent with the tool val agent = Agent( config = AgentConfig( name = "WeatherAssistant", description = "An assistant that can provide weather information for cities around the world", instructions = """ You are a helpful weather assistant that provides accurate weather information. When asked about the weather in a specific location, use the get_weather tool to fetch current data. Present the information in a clear, concise format including temperature, conditions, humidity, and wind speed. Always specify whether temperatures are in Celsius (metric) or Fahrenheit (imperial) based on the units used. """.trimIndent(), llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key"), tools = listOf(weatherTool) ) ) } ``` ## Registering Tools in the Application ✅ In a larger application, you might want to register your tools centrally so they can be used by multiple agents: ```kotlin filename="src/main/kotlin/ai/kastrax/Application.kt" import ai.kastrax.core.KastraxSystem import ai.kastrax.tools.WeatherTool import ai.kastrax.tools.CalculatorTool import ai.kastrax.tools.SearchTool import ai.kastrax.agents.WeatherAgent class Application { // Initialize the Kastrax system private val system = KastraxSystem() // Register tools centrally init { // Register individual tools system.registerTool(WeatherTool()) system.registerTool(CalculatorTool()) system.registerTool(SearchTool()) // Register agents system.registerAgent(WeatherAgent().agent) } fun start() { // Start the application system.start() } } ``` This approach allows you to maintain a central registry of tools that can be shared across different agents in your application. ## Handling Cancellation ✅ Kastrax supports cancellation of tool execution through Kotlin's built-in cancellation mechanism. This allows you to gracefully handle situations where a tool execution needs to be stopped: ```kotlin import ai.kastrax.core.tool.Tool import ai.kastrax.core.tool.ToolResult import kotlinx.coroutines.CancellationException import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import kotlinx.coroutines.Dispatchers class LongRunningTool : Tool { // Tool definition omitted for brevity override suspend fun execute(input: LongRunningToolInput): ToolResult { return try { // Set a timeout for the operation withTimeout(30000) { // 30 seconds timeout withContext(Dispatchers.IO) { // Check for cancellation periodically kotlinx.coroutines.ensureActive() // Perform long-running operation val result = performLongRunningTask(input) // Check again before returning kotlinx.coroutines.ensureActive() ToolResult.Success(result) } } } catch (e: CancellationException) { // Handle cancellation gracefully ToolResult.Error("Operation was cancelled") } catch (e: Exception) { ToolResult.Error("Operation failed: ${e.message}") } } } ``` When using the agent, you can provide a cancellation handler: ```kotlin val job = kotlinx.coroutines.launch { agent.generate( input = "Perform a long-running analysis", sessionId = "user-123", conversationId = "analysis-session" ) } // Cancel the operation after some condition if (userRequestedCancellation) { job.cancel() } ``` ## Context and Configuration Injection ✅ Kastrax allows you to inject context and configuration into tools, making them more flexible and adaptable to different scenarios: ```kotlin import ai.kastrax.core.tool.Tool import ai.kastrax.core.tool.ToolContext import ai.kastrax.core.tool.ToolResult class ConfigurableWeatherTool : Tool { // Tool definition omitted for brevity override suspend fun execute( input: WeatherToolInput, context: ToolContext = ToolContext() ): ToolResult { // Get user preferences from context val temperatureUnit = context.getPreference("temperature_unit") ?: "celsius" val language = context.getPreference("language") ?: "en" // Get API configuration from context val apiKey = context.getConfig("weather_api_key") ?: return ToolResult.Error("Weather API key not configured") return try { // Use the context information in the API call val weatherData = fetchWeatherData( city = input.city, units = if (temperatureUnit == "celsius") "metric" else "imperial", language = language, apiKey = apiKey ) ToolResult.Success(weatherData.toOutput()) } catch (e: Exception) { ToolResult.Error("Failed to fetch weather data: ${e.message}") } } } ``` When using the tool, you can provide the context: ```kotlin // Create a tool context with user preferences and configuration val toolContext = ToolContext().apply { // User preferences setPreference("temperature_unit", "fahrenheit") setPreference("language", "en-US") // System configuration setConfig("weather_api_key", "your-api-key-here") } // Use the context when generating a response val response = agent.generate( input = "What's the weather in Tokyo?", sessionId = "user-456", conversationId = "weather-session", toolContext = toolContext ) ``` ## Testing and Debugging Tools ✅ Kastrax provides robust support for testing and debugging tools. Here's how to write tests for your tools using Kotlin's testing frameworks: ```kotlin filename="src/test/kotlin/ai/kastrax/tools/WeatherToolTest.kt" import ai.kastrax.core.tool.ToolResult import ai.kastrax.tools.WeatherTool import ai.kastrax.tools.WeatherToolInput import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue class WeatherToolTest { private val weatherTool = WeatherTool() @Test fun `test successful weather retrieval`() = runBlocking { // Arrange val input = WeatherToolInput(city = "Tokyo", units = "metric") // Act val result = weatherTool.execute(input) // Assert assertTrue(result is ToolResult.Success) if (result is ToolResult.Success) { val output = result.value assertEquals("Sunny", output.conditions) // Add more assertions as needed } } @Test fun `test error handling for invalid city`() = runBlocking { // Arrange val input = WeatherToolInput(city = "", units = "metric") // Act val result = weatherTool.execute(input) // Assert assertTrue(result is ToolResult.Error) if (result is ToolResult.Error) { assertTrue(result.message.contains("city")) } } } ``` ## Using Tools in Practice ✅ Let's see a complete example of how to use a tool with an agent in a real application: ```kotlin filename="src/main/kotlin/ai/kastrax/examples/WeatherAssistantExample.kt" import ai.kastrax.agents.WeatherAgent import ai.kastrax.core.tool.ToolContext import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create the weather agent val weatherAgent = WeatherAgent().agent // Create a tool context with necessary configuration val toolContext = ToolContext().apply { setConfig("weather_api_key", System.getenv("WEATHER_API_KEY") ?: "demo-key") setPreference("temperature_unit", "celsius") } // Generate a response using the agent and its tools val response = weatherAgent.generate( input = "What's the current weather in Paris, France?", sessionId = "user-789", conversationId = "weather-inquiry", toolContext = toolContext ) // Print the response println("Agent response: ${response.text}") // The agent will automatically use the WeatherTool to fetch and include // current weather information for Paris in its response } ``` The agent will use the `WeatherTool` to fetch the current weather in Paris and incorporate that information into its response. ## Integration with External Systems ✅ Kastrax tools can integrate with various external systems and APIs. Here's an example of a tool that integrates with a database: ```kotlin filename="src/main/kotlin/ai/kastrax/tools/DatabaseTool.kt" import ai.kastrax.core.tool.Tool import ai.kastrax.core.tool.ToolDefinition import ai.kastrax.core.tool.ToolResult import kotlinx.serialization.Serializable import java.sql.Connection import java.sql.DriverManager @Serializable data class QueryInput( val query: String, val limit: Int = 10 ) @Serializable data class QueryResult( val columns: List, val rows: List> ) class DatabaseTool : Tool { override val definition = ToolDefinition( name = "database_query", description = "Execute a SQL query against the database to retrieve information", inputType = QueryInput::class, outputType = QueryResult::class ) private var connection: Connection? = null private fun getConnection(context: ToolContext): Connection { if (connection == null || connection?.isClosed == true) { val jdbcUrl = context.getConfig("jdbc_url") ?: throw IllegalStateException("Database URL not configured") val username = context.getConfig("db_username") ?: "" val password = context.getConfig("db_password") ?: "" connection = DriverManager.getConnection(jdbcUrl, username, password) } return connection!! } override suspend fun execute(input: QueryInput, context: ToolContext): ToolResult { return try { // Sanitize and validate the query for security val sanitizedQuery = sanitizeQuery(input.query, input.limit) // Execute the query val conn = getConnection(context) val statement = conn.createStatement() val resultSet = statement.executeQuery(sanitizedQuery) // Process the results val metadata = resultSet.metaData val columnCount = metadata.columnCount // Extract column names val columns = (1..columnCount).map { metadata.getColumnName(it) } // Extract rows val rows = mutableListOf>() while (resultSet.next() && rows.size < input.limit) { val row = (1..columnCount).map { resultSet.getString(it) ?: "null" } rows.add(row) } ToolResult.Success(QueryResult(columns, rows)) } catch (e: Exception) { ToolResult.Error("Database query failed: ${e.message}") } } private fun sanitizeQuery(query: String, limit: Int): String { // Implement query sanitization and validation // This is a simplified example - in production, use prepared statements // and more sophisticated validation if (!query.lowercase().startsWith("select")) { throw IllegalArgumentException("Only SELECT queries are allowed") } return if (query.lowercase().contains(" limit ")) { query } else { "$query LIMIT $limit" } } } ``` ## Tool Design Best Practices ✅ When creating tools for your Kastrax agents, follow these guidelines to ensure reliable and intuitive tool usage: ### Tool Descriptions Your tool's description should focus on its purpose and value: - Keep descriptions simple and focused on **what** the tool does - Emphasize the tool's primary use case - Avoid implementation details in the main description - Focus on helping the agent understand **when** to use the tool ```kotlin ToolDefinition( name = "document_search", description = "Search the knowledge base to find information needed to answer user questions", inputType = SearchInput::class, outputType = SearchResult::class ) ``` ### Input and Output Types Design clear, well-structured input and output types: - Make parameters self-documenting with clear names - Include default values for optional parameters - Use appropriate types for each parameter - Document the purpose of each field ```kotlin @Serializable data class SearchInput( val query: String, // The search query to find relevant information val limit: Int = 10, // Number of results to return val filters: Map = mapOf() // Optional filters to apply ) ``` ### Error Handling Implement robust error handling in your tools: - Return clear error messages that explain what went wrong - Handle expected failure cases gracefully - Provide guidance on how to fix the issue when possible - Log detailed error information for debugging ### Security Considerations Ensure your tools are secure: - Validate and sanitize all inputs - Use prepared statements for database queries - Implement proper authentication and authorization - Limit access to sensitive operations - Don't expose sensitive information in error messages ## Advanced Tool Features ✅ Kastrax supports several advanced tool features: ### Tool Composition You can compose multiple tools together to create more complex functionality: ```kotlin class CompositeSearchTool( private val webSearchTool: WebSearchTool, private val databaseTool: DatabaseTool, private val documentTool: DocumentTool ) : Tool { // Implementation that uses multiple tools together } ``` ### Tool Versioning Implement versioning for your tools to maintain backward compatibility: ```kotlin class WeatherToolV2 : Tool { override val definition = ToolDefinition( name = "get_weather_v2", description = "Enhanced weather information tool with additional data", inputType = WeatherToolInputV2::class, outputType = WeatherToolOutputV2::class, version = "2.0" ) // Implementation } ``` ### Tool Metrics and Monitoring Implement metrics collection for your tools: ```kotlin class MonitoredTool(private val delegate: Tool) : Tool { override val definition = delegate.definition override suspend fun execute(input: I, context: ToolContext): ToolResult { val startTime = System.currentTimeMillis() try { val result = delegate.execute(input, context) val duration = System.currentTimeMillis() - startTime recordMetrics(definition.name, duration, result is ToolResult.Success) return result } catch (e: Exception) { val duration = System.currentTimeMillis() - startTime recordMetrics(definition.name, duration, false) throw e } } private fun recordMetrics(toolName: String, durationMs: Long, success: Boolean) { // Record metrics to your monitoring system } } ``` ## Integration with Actor Model ✅ One of Kastrax's unique features is the integration of tools with the actor model, allowing for distributed tool execution: ```kotlin import ai.kastrax.actor.ActorRef import ai.kastrax.actor.ActorSystem import ai.kastrax.tools.WeatherTool class ToolActorSystem(private val system: ActorSystem) { // Create actor references for tools val weatherToolActor: ActorRef = system.actorOf(WeatherToolActor.props(WeatherTool())) val databaseToolActor: ActorRef = system.actorOf(DatabaseToolActor.props(DatabaseTool())) // Tool actor message types data class ToolRequest(val input: I, val replyTo: ActorRef) data class ToolResponse(val result: ToolResult) // Example of using a tool through the actor system suspend fun getWeather(city: String): ToolResult { val input = WeatherToolInput(city = city) val response = system.ask>( weatherToolActor, ToolRequest(input, system.selfRef()) ) return response.result } } ``` This integration enables building sophisticated multi-agent systems where tools can be distributed across different nodes and executed concurrently. --- title: "Adding Voice Capabilities to Kastrax AI Agents | Agents | Kastrax Docs" description: "Comprehensive guide to implementing voice interaction capabilities in Kastrax AI Agents, including speech synthesis, speech recognition, and real-time conversations." --- # Adding Voice Capabilities to Kastrax AI Agents ✅ [EN] Source: https://kastrax.ai/en/docs/agents/adding-voice Kastrax AI Agents can be enhanced with sophisticated voice capabilities, enabling natural spoken interactions with users. The voice system supports text-to-speech (TTS) for generating spoken responses, speech-to-text (STT) for understanding user input, and real-time speech-to-speech for continuous conversations. This guide explains how to implement these capabilities in your Kastrax agents. ## Voice Architecture in Kastrax ✅ Kastrax implements a sophisticated voice system with several key components: 1. **Voice Providers**: Adapters for various speech services (DeepSeek, ElevenLabs, Google, etc.) 2. **Voice Interface**: A unified API for text-to-speech and speech-to-text operations 3. **Realtime Voice**: Support for continuous, bidirectional speech conversations 4. **Voice Events**: An event system for monitoring and responding to voice interactions 5. **Voice Configuration**: Extensive options for customizing voice characteristics This architecture allows for flexible integration with different voice services while maintaining a consistent developer experience. ## Using a Single Provider ✅ The simplest way to add voice to an agent is to use a single provider for both speaking and listening: ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.AgentConfig import ai.kastrax.core.llm.DeepSeekProvider import ai.kastrax.voice.DeepSeekVoice import ai.kastrax.voice.config.SpeakOptions import ai.kastrax.voice.config.ListenOptions import ai.kastrax.voice.AudioFormat import java.io.File import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Initialize the voice provider with default settings val voice = DeepSeekVoice( apiKey = "your-deepseek-api-key", defaultVoice = "female-1" // Default voice ) // Create an agent with voice capabilities val agent = Agent( config = AgentConfig( name = "VoiceAssistant", description = "An assistant with voice interaction capabilities", instructions = "You are a helpful assistant with both speech recognition and speech synthesis capabilities.", llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key"), voice = voice ) ) // The agent can now use voice for interaction val audioStream = agent.voice.speak( text = "Hello, I'm your AI assistant!", options = SpeakOptions( format = AudioFormat.MP3, voice = "male-2", // Override the default voice speed = 1.0f, // Normal speaking speed pitch = 0.0f // Normal pitch ) ) // Save the audio to a file val outputFile = File("assistant_greeting.mp3") audioStream.use { input -> outputFile.outputStream().use { output -> input.copyTo(output) } } println("Saved greeting to ${outputFile.absolutePath}") // Transcribe audio from a file try { val inputFile = File("user_input.mp3") val transcription = agent.voice.listen( audioStream = inputFile.inputStream(), options = ListenOptions( format = AudioFormat.MP3, language = "en-US", // Specify language for better accuracy model = "standard" // Use standard recognition model ) ) println("Transcription: $transcription") } catch (e: Exception) { println("Error transcribing audio: ${e.message}") } } ``` ## Using Multiple Providers ✅ For more flexibility, you can use different providers for speaking and listening using the CompositeVoice class. This allows you to leverage the strengths of different voice services: ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.AgentConfig import ai.kastrax.core.llm.DeepSeekProvider import ai.kastrax.voice.CompositeVoice import ai.kastrax.voice.DeepSeekVoice import ai.kastrax.voice.ElevenLabsVoice import ai.kastrax.voice.GoogleVoice // Create an agent with multiple voice providers val agent = Agent( config = AgentConfig( name = "MultiProviderVoiceAssistant", description = "An assistant using multiple voice services", instructions = "You are a helpful assistant with advanced voice capabilities.", llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key"), // Create a composite voice using different providers for different functions voice = CompositeVoice( // Use DeepSeek for speech recognition (STT) input = DeepSeekVoice( apiKey = "your-deepseek-api-key", config = DeepSeekVoiceConfig( recognitionModel = "whisper-large-v3" ) ), // Use ElevenLabs for high-quality speech synthesis (TTS) output = ElevenLabsVoice( apiKey = "your-elevenlabs-api-key", defaultVoice = "Rachel", stability = 0.7f, similarityBoost = 0.5f ) ) ) ) // You can also create more complex combinations val advancedAgent = Agent( config = AgentConfig( name = "AdvancedVoiceAssistant", description = "An assistant with specialized voice capabilities", instructions = "You are a helpful assistant with advanced voice capabilities.", llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key"), // Create a composite voice with fallback providers voice = CompositeVoice( // Primary input provider with fallback input = CompositeVoice.InputWithFallback( primary = DeepSeekVoice(apiKey = "your-deepseek-api-key"), fallback = GoogleVoice(apiKey = "your-google-api-key") ), // Primary output provider with fallback output = CompositeVoice.OutputWithFallback( primary = ElevenLabsVoice(apiKey = "your-elevenlabs-api-key"), fallback = DeepSeekVoice(apiKey = "your-deepseek-api-key") ) ) ) ) ``` This approach allows you to: - Use specialized providers for different voice functions - Implement fallback mechanisms for improved reliability - Optimize for different quality, cost, or performance requirements - Mix and match providers based on specific use cases ## Working with Audio Streams ✅ The `speak()` and `listen()` methods in Kastrax work with Kotlin's `InputStream` and `OutputStream` for handling audio data. Here's how to work with audio files and streams: ### Saving Speech Output The `speak` method returns an input stream that you can save to a file or send to speakers: ```kotlin import ai.kastrax.voice.config.SpeakOptions import ai.kastrax.voice.AudioFormat import java.io.File import kotlinx.coroutines.runBlocking fun saveAgentSpeech() = runBlocking { // Generate speech with custom options val audioStream = agent.voice.speak( text = "Hello, world! This is a demonstration of Kastrax voice capabilities.", options = SpeakOptions( format = AudioFormat.MP3, voice = "female-1", speed = 1.1f, // Slightly faster than normal pitch = -0.2f // Slightly lower pitch ) ) // Save the audio to a file val outputFile = File("agent_speech.mp3") audioStream.use { input -> outputFile.outputStream().use { output -> input.copyTo(output) } } println("Speech saved to: ${outputFile.absolutePath}") } ``` ### Processing Audio in Memory You can also process audio data in memory for more advanced use cases: ```kotlin import ai.kastrax.voice.config.SpeakOptions import ai.kastrax.voice.AudioFormat import java.io.ByteArrayOutputStream import kotlinx.coroutines.runBlocking fun processAudioInMemory() = runBlocking { // Generate speech val audioStream = agent.voice.speak( text = "Processing audio in memory", options = SpeakOptions(format = AudioFormat.WAV) ) // Read the entire audio stream into memory val audioData = ByteArrayOutputStream().use { output -> audioStream.use { input -> input.copyTo(output) } output.toByteArray() } // Now you can process the audio data in memory println("Generated ${audioData.size} bytes of audio data") // Example: Apply audio processing (e.g., volume adjustment) val processedAudio = applyAudioProcessing(audioData) // Save the processed audio File("processed_audio.wav").writeBytes(processedAudio) } // Example audio processing function fun applyAudioProcessing(audioData: ByteArray): ByteArray { // This is a simplified example - in a real application, // you would implement actual audio processing logic here return audioData // Return unmodified for this example } ``` ### Transcribing Audio Input The `listen` method accepts an input stream of audio data from a microphone or file: ```kotlin import ai.kastrax.voice.config.ListenOptions import ai.kastrax.voice.AudioFormat import java.io.File import kotlinx.coroutines.runBlocking fun transcribeAudioFile() = runBlocking { // Read audio file val audioFile = File("user_input.mp3") try { println("Transcribing audio file...") val transcription = agent.voice.listen( audioStream = audioFile.inputStream(), options = ListenOptions( format = AudioFormat.MP3, language = "en-US", model = "large", // Use large model for better accuracy promptHint = "technical discussion" // Provide context hint ) ) println("Transcription: $transcription") // Generate a response based on the transcription val response = agent.generate( input = transcription, sessionId = "voice-user-123", conversationId = "voice-session-456" ) // Convert the response to speech val responseAudio = agent.voice.speak(response.text) File("agent_response.mp3").outputStream().use { output -> responseAudio.copyTo(output) } } catch (e: Exception) { println("Audio transcription error: ${e.message}") } } ``` ## Real-time Voice Conversations ✅ Kastrax supports real-time, bidirectional voice conversations through its realtime voice providers. This enables natural, continuous speech interactions between users and agents: ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.AgentConfig import ai.kastrax.core.llm.DeepSeekProvider import ai.kastrax.voice.realtime.RealtimeVoice import ai.kastrax.voice.realtime.RealtimeVoiceConfig import ai.kastrax.voice.realtime.VoiceEvent import ai.kastrax.tools.SearchTool import ai.kastrax.tools.CalculatorTool import ai.kastrax.audio.MicrophoneStream import kotlinx.coroutines.runBlocking import kotlinx.coroutines.launch fun main() = runBlocking { // Initialize the realtime voice provider val realtimeVoice = RealtimeVoice( config = RealtimeVoiceConfig( apiKey = "your-api-key", model = "deepseek-chat", voice = "female-1", responseSpeed = 0.9f, // Slightly faster response generation interruptible = true // Allow interrupting the agent while speaking ) ) // Create an agent with real-time voice capabilities val agent = Agent( config = AgentConfig( name = "RealtimeVoiceAssistant", description = "An assistant with real-time voice conversation capabilities", instructions = "You are a helpful assistant capable of real-time voice conversations. Respond naturally and concisely.", llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key"), tools = listOf( SearchTool(), CalculatorTool() ), voice = realtimeVoice ) ) // Set up event listeners setupVoiceEventListeners(agent) // Establish connection to the voice service agent.voice.connect() // Start the conversation with a greeting agent.voice.speak("Hello, I'm your AI assistant. How can I help you today?") // Get microphone input stream val microphoneStream = MicrophoneStream.create() // Send microphone audio to the agent launch { agent.voice.send(microphoneStream) } // Keep the application running until user ends the conversation println("Conversation started. Press Enter to end the conversation.") readLine() // Clean up when done microphoneStream.close() agent.voice.close() println("Conversation ended.") } // Set up event listeners for the voice system fun setupVoiceEventListeners(agent: Agent) { // Listen for speech audio data from the voice provider agent.voice.on(VoiceEvent.SPEAKING) { event -> val audio = event.audio // Process the audio data (e.g., play through speakers) playAudioThroughSpeakers(audio) } // Listen for transcribed text from both the voice provider and user agent.voice.on(VoiceEvent.WRITING) { event -> println("${event.role}: ${event.text}") // You can also save the conversation to a transcript if (event.isFinal) { saveToTranscript(event.role, event.text) } } // Listen for thinking events (when the agent is processing) agent.voice.on(VoiceEvent.THINKING) { event -> println("Agent is thinking...") // You could display a visual indicator here } // Listen for errors agent.voice.on(VoiceEvent.ERROR) { event -> println("Voice error: ${event.error}") } } // Example function to play audio through speakers fun playAudioThroughSpeakers(audio: InputStream) { // Implementation would depend on your audio playback library // This is a placeholder for actual audio playback code } // Example function to save conversation to transcript fun saveToTranscript(role: String, text: String) { File("conversation_transcript.txt").appendText("$role: $text\n") } ``` ### Advanced Real-time Features Kastrax's real-time voice system supports several advanced features: ```kotlin // Enable voice activity detection to automatically detect when the user stops speaking val voiceWithVAD = RealtimeVoice( config = RealtimeVoiceConfig( apiKey = "your-api-key", model = "deepseek-chat", voice = "female-1", voiceActivityDetection = VoiceActivityDetectionConfig( enabled = true, silenceThreshold = 0.3f, // Level of silence to detect end of speech silenceDuration = 1000 // Milliseconds of silence to trigger end of speech ) ) ) // Enable streaming responses for faster agent replies val streamingVoice = RealtimeVoice( config = RealtimeVoiceConfig( apiKey = "your-api-key", model = "deepseek-chat", voice = "female-1", streamingMode = StreamingMode.INCREMENTAL, // Start speaking before full response is generated chunkSize = 20 // Words per chunk for incremental speaking ) ) // Enable voice commands for controlling the conversation val voiceWithCommands = RealtimeVoice( config = RealtimeVoiceConfig( apiKey = "your-api-key", model = "deepseek-chat", voice = "female-1", voiceCommands = listOf( VoiceCommand("stop", "Stop the current response"), VoiceCommand("pause", "Pause the conversation"), VoiceCommand("resume", "Resume the conversation") ) ) ) ``` ## Supported Voice Providers ✅ Kastrax supports multiple voice providers for text-to-speech (TTS) and speech-to-text (STT) capabilities: | Provider | Package | Features | Reference | |----------|---------|----------|-----------| | DeepSeek | `ai.kastrax.voice.DeepSeekVoice` | TTS, STT | [Documentation](/reference/voice/deepseek) | | DeepSeek Realtime | `ai.kastrax.voice.realtime.DeepSeekRealtimeVoice` | Realtime speech-to-speech | [Documentation](/reference/voice/deepseek-realtime) | | ElevenLabs | `ai.kastrax.voice.ElevenLabsVoice` | High-quality TTS | [Documentation](/reference/voice/elevenlabs) | | Google | `ai.kastrax.voice.GoogleVoice` | TTS, STT | [Documentation](/reference/voice/google) | | Azure | `ai.kastrax.voice.AzureVoice` | TTS, STT | [Documentation](/reference/voice/azure) | | Whisper | `ai.kastrax.voice.WhisperVoice` | STT | [Documentation](/reference/voice/whisper) | For more details on voice capabilities, see the [Voice API Reference](/reference/voice/kastrax-voice). ## Integration with Actor Model ✅ One of Kastrax's unique features is the integration of the voice system with the actor model, enabling distributed voice processing: ```kotlin import ai.kastrax.actor.ActorSystem import ai.kastrax.actor.Props import ai.kastrax.voice.VoiceActor import ai.kastrax.voice.DeepSeekVoice import ai.kastrax.voice.messages.* import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an actor system val system = ActorSystem("voice-system") // Create a voice actor val voiceActor = system.actorOf( Props.create(VoiceActor::class.java, DeepSeekVoice(apiKey = "your-api-key")), "voice-actor" ) // Send a speech synthesis message val result = system.ask( voiceActor, SpeakMessage("Hello, I am a voice actor!") ) // Process the result when (result) { is VoiceResult.Success -> { val audioData = result.audio // Process the audio data... File("actor_speech.mp3").outputStream().use { output -> audioData.copyTo(output) } println("Speech generated successfully") } is VoiceResult.Error -> { println("Voice processing error: ${result.message}") } } // Send a speech recognition message val audioFile = File("input.mp3") val transcriptionResult = system.ask( voiceActor, ListenMessage(audioFile.inputStream()) ) // Process the transcription result when (transcriptionResult) { is VoiceResult.Success -> { val transcription = transcriptionResult.text println("Transcription: $transcription") } is VoiceResult.Error -> { println("Transcription error: ${transcriptionResult.message}") } } // Shutdown the actor system when done system.terminate() } ``` This integration enables building sophisticated multi-agent systems where voice processing can be distributed across different nodes and executed concurrently. ## Best Practices ✅ When implementing voice capabilities in your Kastrax agents, consider these best practices: 1. **Choose the Right Provider**: Select voice providers based on your specific requirements for quality, latency, and language support. 2. **Handle Errors Gracefully**: Implement robust error handling for network issues, service unavailability, or audio processing failures. 3. **Optimize Audio Settings**: Configure audio format, quality, and compression based on your bandwidth and storage constraints. 4. **Consider Privacy**: Be transparent about audio recording and processing, and implement appropriate data retention policies. 5. **Test with Real Users**: Voice interfaces require extensive testing with diverse accents, background noise conditions, and use cases. 6. **Provide Visual Feedback**: When using voice in applications with visual interfaces, provide feedback about listening and speaking states. 7. **Implement Fallbacks**: Always provide text-based alternatives for situations where voice interaction isn't possible or fails. 8. **Monitor Performance**: Track metrics like speech recognition accuracy, response times, and user satisfaction to continuously improve your voice interface. By following these guidelines, you can create Kastrax AI Agents with robust voice capabilities that provide natural and effective user interactions. --- title: "Kastrax AI Agent Memory System | Agents | Kastrax Docs" description: Comprehensive guide to implementing memory systems for Kastrax AI Agents, including conversation history, semantic memory, and persistent storage. --- # Kastrax AI Agent Memory System ✅ [EN] Source: https://kastrax.ai/en/docs/agents/agent-memory The Kastrax AI Agent framework provides a sophisticated memory system that enables agents to maintain context, recall past interactions, and build knowledge over time. This memory architecture is crucial for creating intelligent agents that can engage in meaningful, contextual conversations and improve their performance through experience. ## Memory Architecture in Kastrax ✅ The Kastrax memory system is built on a multi-layered architecture that provides different types of memory capabilities: 1. **Short-term Memory**: Stores recent conversation turns and immediate context 2. **Working Memory**: Maintains active information needed for the current task 3. **Long-term Memory**: Persists knowledge and experiences across sessions 4. **Semantic Memory**: Enables retrieval of information based on meaning rather than exact matches This architecture mimics human memory systems and allows agents to process information at different levels of abstraction and persistence. ## Implementing Agent Memory ✅ To implement memory in your Kastrax agent, you'll need to include the `kastrax-memory` module in your project and configure the appropriate memory components: ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.memory.AgentMemory import ai.kastrax.memory.storage.SQLiteStorage import ai.kastrax.memory.embedding.DeepSeekEmbedding // Create a memory system with SQLite storage and DeepSeek embeddings val memoryStorage = SQLiteStorage("agent-memory.db") val embeddingProvider = DeepSeekEmbedding(apiKey = "your-deepseek-api-key") val agentMemory = AgentMemory( storage = memoryStorage, embeddingProvider = embeddingProvider, maxHistoryLength = 20, // Number of conversation turns to keep in short-term memory workingMemoryEnabled = true // Enable working memory for active reasoning ) // Create an agent with memory val agent = Agent( name = "MemoryEnabledAssistant", instructions = "You are a helpful assistant with memory capabilities.", llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key"), memory = agentMemory ) ``` This setup creates an agent with a fully-featured memory system that uses SQLite for persistent storage and DeepSeek for generating embeddings. You can customize various aspects of the memory system based on your requirements. ## Using Memory in Agent Interactions ✅ When interacting with a memory-enabled agent, you need to provide conversation context through session and conversation identifiers: ```kotlin // Start a conversation with the agent val sessionId = "user-123" // Identifies the user or entity val conversationId = "support-456" // Identifies a specific conversation thread // First interaction - storing information val response1 = agent.generate( input = "My name is Alice and I prefer to be contacted via email at alice@example.com", sessionId = sessionId, conversationId = conversationId ) // Later interaction - retrieving information val response2 = agent.generate( input = "What's my contact information?", sessionId = sessionId, conversationId = conversationId ) // The agent will recall the email address from memory ``` The `sessionId` and `conversationId` parameters ensure that the agent can access the appropriate memory context for each interaction. This allows for continuous, contextual conversations across multiple interactions. ## Advanced Memory Features ✅ ### Working Memory Working memory allows agents to maintain and manipulate information during complex reasoning tasks: ```kotlin // Enable working memory operations val workingMemory = agentMemory.workingMemory // Store information in working memory workingMemory.store("current_task", "Researching quantum computing papers") workingMemory.store("important_finding", "Recent breakthrough in quantum error correction") // Retrieve information from working memory val currentTask = workingMemory.retrieve("current_task") ``` ### Semantic Search Kastrax's memory system supports semantic search to find relevant information based on meaning: ```kotlin // Search for semantically similar information in memory val searchResults = agentMemory.semanticSearch( query = "What do we know about error correction?", maxResults = 5, threshold = 0.7 // Similarity threshold (0-1) ) // Process search results searchResults.forEach { result -> println("Content: ${result.content}") println("Similarity: ${result.similarity}") } ``` ### Memory Persistence Kastrax supports multiple storage backends for memory persistence: ```kotlin // SQLite storage (local) val sqliteStorage = SQLiteStorage("agent-memory.db") // PostgreSQL storage (distributed) val postgresStorage = PostgresStorage( url = "jdbc:postgresql://localhost:5432/agent_memory", username = "postgres", password = "password" ) // In-memory storage (ephemeral, for testing) val inMemoryStorage = InMemoryStorage() ``` ## Integration with Actor Model ✅ Kastrax uniquely combines the agent memory system with the actor model, enabling distributed memory across agent networks: ```kotlin // Create an agent actor with memory val agentActor = system.actorOf(AgentActor.props( agent = agent, memory = agentMemory )) // Send a message to the agent actor agentActor.tell(AgentMessage( content = "What did we discuss yesterday?", sessionId = "user-123", conversationId = "meeting-789" )) ``` This integration allows for building sophisticated multi-agent systems where memory can be shared, distributed, or specialized across different agents in the network. ## Best Practices ✅ - **Memory Segmentation**: Use different conversation IDs for distinct topics to prevent context pollution - **Regular Pruning**: Implement policies to archive or delete old memories to prevent performance degradation - **Backup Strategy**: Regularly backup memory storage, especially for critical agent applications - **Privacy Considerations**: Implement appropriate data retention and privacy policies for stored memories - **Memory Monitoring**: Add logging and monitoring to track memory usage and performance By following these guidelines, you can create Kastrax AI Agents with robust memory capabilities that enable more intelligent, contextual, and personalized interactions. --- title: "Agent Architectures | Agent Documentation | Kastrax" description: "Overview of different agent architectures in Kastrax, including adaptive, goal-oriented, hierarchical, reflective, and creative agents." --- # Agent Architectures ✅ [EN] Source: https://kastrax.ai/en/docs/agents/architectures-kotlin Kastrax provides several specialized agent architectures, each designed for different use cases and behaviors. This guide explains the available architectures and how to implement them. ## Architecture Overview ✅ Kastrax supports the following agent architectures: 1. **Adaptive Agents**: Adjust behavior based on user preferences and feedback 2. **Goal-Oriented Agents**: Focus on achieving specific objectives through planning and task execution 3. **Hierarchical Agents**: Organize complex tasks into manageable subtasks with a hierarchy of responsibility 4. **Reflective Agents**: Self-monitor and improve performance through reflection 5. **Creative Agents**: Generate creative content with enhanced capabilities Each architecture extends the basic agent with specialized behaviors and capabilities. ## Adaptive Agents ✅ Adaptive agents adjust their behavior based on user preferences and feedback. They're ideal for personalized experiences that improve over time. ### Key Features ✅ - User preference tracking - Behavior adaptation - Personalized responses - Learning from feedback ### Implementation ✅ ```kotlin import ai.kastrax.core.agent.architecture.adaptiveAgent import ai.kastrax.core.agent.architecture.UserPreference import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createAdaptiveAgent() = adaptiveAgent { name("AdaptiveAssistant") description("An assistant that adapts to user preferences") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Define user preferences preferences { preference( UserPreference( name = "ResponseLength", description = "How detailed responses should be", options = listOf("Concise", "Detailed", "Comprehensive"), defaultValue = "Detailed" ) ) preference( UserPreference( name = "Tone", description = "The tone of responses", options = listOf("Professional", "Friendly", "Technical"), defaultValue = "Friendly" ) ) } // Define adaptation strategy adaptationStrategy { // Analyze user messages to infer preferences analyzeUserMessage { message -> if (message.contains("brief") || message.contains("concise")) { updatePreference("ResponseLength", "Concise") } else if (message.contains("detailed") || message.contains("explain")) { updatePreference("ResponseLength", "Detailed") } if (message.contains("technical") || message.contains("advanced")) { updatePreference("Tone", "Technical") } else if (message.contains("simple") || message.contains("easy")) { updatePreference("Tone", "Friendly") } } } } fun main() = runBlocking { val agent = createAdaptiveAgent() // Test the agent println(agent.generate("Can you give me a brief explanation of quantum computing?").text) println(agent.generate("Now I want a detailed technical explanation of quantum entanglement.").text) } ``` ## Goal-Oriented Agents ✅ Goal-oriented agents focus on achieving specific objectives through planning and task execution. They're ideal for complex problem-solving scenarios. ### Key Features ✅ - Goal definition and tracking - Task planning and prioritization - Progress monitoring - Outcome evaluation ### Implementation ✅ ```kotlin import ai.kastrax.core.agent.architecture.goalOrientedAgent import ai.kastrax.core.agent.architecture.GoalPriority import ai.kastrax.core.agent.architecture.TaskPriority import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import kotlinx.datetime.Clock import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.plus fun createGoalOrientedAgent() = goalOrientedAgent { name("ProjectManager") description("A goal-oriented agent that helps manage projects") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Define goals goals { goal( id = "complete-project", description = "Complete the website redesign project", priority = GoalPriority.HIGH, deadline = Clock.System.now().plus(30, DateTimeUnit.DAY) ) } // Define tasks for goals tasks("complete-project") { task( id = "gather-requirements", description = "Gather project requirements from stakeholders", priority = TaskPriority.HIGH, estimatedDuration = 3 // days ) task( id = "create-wireframes", description = "Create wireframes based on requirements", priority = TaskPriority.MEDIUM, estimatedDuration = 5, // days dependencies = listOf("gather-requirements") ) task( id = "develop-frontend", description = "Develop frontend based on wireframes", priority = TaskPriority.MEDIUM, estimatedDuration = 10, // days dependencies = listOf("create-wireframes") ) } } fun main() = runBlocking { val agent = createGoalOrientedAgent() // Test the agent println(agent.generate("What's the current status of the website redesign project?").text) println(agent.generate("What tasks should we focus on this week?").text) } ``` ## Hierarchical Agents ✅ Hierarchical agents break down complex tasks into manageable subtasks, creating a hierarchy of responsibility. They're ideal for complex workflows with multiple steps. ### Key Features ✅ - Task decomposition - Hierarchical planning - Delegated execution - Result coordination ### Implementation ✅ ```kotlin import ai.kastrax.core.agent.architecture.hierarchicalAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createHierarchicalAgent() = hierarchicalAgent { name("ResearchAssistant") description("A hierarchical agent that helps with research tasks") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Define coordinator agent coordinator { name("Coordinator") description("Coordinates the research process") systemPrompt(""" You are the coordinator of a research team. Your job is to: 1. Understand the research question 2. Break it down into subtasks 3. Assign tasks to specialists 4. Synthesize their results into a final answer Be thorough and organized in your approach. """.trimIndent()) } // Define specialist agents specialists { specialist("Researcher") { description("Finds relevant information") systemPrompt(""" You are a research specialist. Your job is to: 1. Search for relevant information on the specified topic 2. Evaluate the credibility of sources 3. Extract key facts and data 4. Organize information logically Be thorough and cite your sources. """.trimIndent()) } specialist("Analyst") { description("Analyzes information") systemPrompt(""" You are an analysis specialist. Your job is to: 1. Analyze the provided information 2. Identify patterns and trends 3. Make connections between different pieces of information 4. Evaluate the significance of findings Be critical and objective in your analysis. """.trimIndent()) } specialist("Writer") { description("Writes the final report") systemPrompt(""" You are a writing specialist. Your job is to: 1. Organize research findings into a coherent narrative 2. Write clear, concise, and engaging content 3. Ensure the writing is appropriate for the target audience 4. Proofread for errors and clarity Be clear, concise, and engaging in your writing. """.trimIndent()) } } } fun main() = runBlocking { val agent = createHierarchicalAgent() // Test the agent println(agent.generate("Research the impact of artificial intelligence on healthcare").text) } ``` ## Reflective Agents ✅ Reflective agents monitor their own performance and continuously improve through self-reflection. They're ideal for complex reasoning tasks that benefit from iterative improvement. ### Key Features ✅ - Self-monitoring - Performance evaluation - Strategy adjustment - Continuous improvement ### Implementation ✅ ```kotlin import ai.kastrax.core.agent.architecture.reflectiveAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createReflectiveAgent() = reflectiveAgent { name("ReflectiveAssistant") description("A reflective agent that improves through self-evaluation") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Define reflection criteria reflectionCriteria { criterion("Accuracy", "Was the information provided accurate and factual?") criterion("Completeness", "Did the response address all aspects of the query?") criterion("Clarity", "Was the response clear and easy to understand?") criterion("Relevance", "Was the response relevant to the user's query?") criterion("Helpfulness", "Did the response actually help the user?") } // Configure reflection frequency reflectionFrequency(3) // Reflect after every 3 interactions // Define reflection strategy reflectionStrategy { // Evaluate previous response evaluateResponse { response -> // Rate each criterion on a scale of 1-5 val ratings = mapOf( "Accuracy" to evaluateCriterion("Accuracy", response), "Completeness" to evaluateCriterion("Completeness", response), "Clarity" to evaluateCriterion("Clarity", response), "Relevance" to evaluateCriterion("Relevance", response), "Helpfulness" to evaluateCriterion("Helpfulness", response) ) // Identify areas for improvement val improvementAreas = ratings.filter { it.value < 4 }.keys // Generate improvement strategies val strategies = improvementAreas.map { criterion -> "Improve $criterion: ${generateImprovementStrategy(criterion)}" } // Update agent's approach based on reflection updateApproach(strategies) } } } fun main() = runBlocking { val agent = createReflectiveAgent() // Test the agent println(agent.generate("Explain how neural networks work").text) println(agent.generate("Compare supervised learning and unsupervised learning").text) println(agent.generate("What are the ethical implications of AI?").text) // After the third query, the agent will reflect and improve } ``` ## Creative Agents ✅ Creative agents specialize in generating stories, poetry, and creative ideas. They have enhanced capabilities for creative expression. ### Key Features ✅ - Creative content generation - Style adaptation - Theme exploration - Narrative construction ### Implementation ✅ ```kotlin import ai.kastrax.core.agent.architecture.creativeAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createCreativeAgent() = creativeAgent { name("CreativeWriter") description("A creative agent that generates stories and poetry") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.9) // Higher temperature for more creativity } // Define creative capabilities creativeCapabilities { capability("StoryGeneration", "Generate engaging short stories") capability("PoetryWriting", "Write poetry in various styles") capability("IdeaGeneration", "Generate creative ideas for various purposes") capability("CharacterCreation", "Create detailed and interesting characters") } // Define creative styles creativeStyles { style("Fantasy", "Magical and supernatural elements") style("SciFi", "Futuristic and technological themes") style("Mystery", "Suspenseful and mysterious tone") style("Comedy", "Humorous and lighthearted approach") style("Drama", "Emotional and character-driven narratives") } // Configure creativity parameters creativityParameters { parameter("Originality", 0.8f) // Preference for original ideas parameter("Coherence", 0.7f) // Balance between coherence and creativity parameter("Detail", 0.6f) // Level of descriptive detail parameter("EmotionalImpact", 0.9f) // Emphasis on emotional resonance } } fun main() = runBlocking { val agent = createCreativeAgent() // Test the agent println(agent.generate("Write a short story about a robot discovering emotions").text) println(agent.generate("Create a haiku about the changing seasons").text) } ``` ## Choosing the Right Architecture ✅ When choosing an agent architecture, consider these factors: | Architecture | Best For | Key Advantages | |--------------|----------|----------------| | Adaptive | Personalized experiences | Adapts to user preferences | | Goal-Oriented | Project management, planning | Focuses on achieving goals | | Hierarchical | Complex multi-step tasks | Breaks problems into manageable parts | | Reflective | Critical thinking, continuous improvement | Self-evaluates and improves | | Creative | Content generation, brainstorming | Generates creative and original content | You can also combine architectures by implementing their key features in a custom agent. ## Next Steps ✅ Now that you understand the different agent architectures, you can: 1. Explore the [memory system](../memory/overview.mdx) 2. Learn about [tool integration](../tools/overview.mdx) 3. Implement [agent workflows](../workflows/overview.mdx) # Agent Architectures [EN] Source: https://kastrax.ai/en/docs/agents/architectures Kastrax provides several specialized agent architectures, each designed for different use cases and behaviors. This guide explains the available architectures and how to implement them. ## Overview of Agent Architectures Kastrax supports the following agent architectures: 1. **Adaptive Agents**: Adjust behavior based on user preferences and feedback 2. **Goal-Oriented Agents**: Focus on achieving specific objectives with task planning 3. **Hierarchical Agents**: Organize complex tasks into manageable sub-tasks 4. **Reflective Agents**: Self-monitor and improve performance through reflection 5. **Creative Agents**: Generate creative content with enhanced capabilities Each architecture extends the basic agent with specialized behaviors and capabilities. ## Adaptive Agents Adaptive agents adjust their behavior based on user preferences and feedback. They're ideal for personalized experiences that improve over time. ### Key Features - User preference tracking - Behavior adaptation - Personalized responses - Learning from feedback ### Implementation ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.architecture.adaptiveAgent import ai.kastrax.core.agent.architecture.UserPreference import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createAdaptiveAgent() = adaptiveAgent { name("AdaptiveAssistant") description("An assistant that adapts to user preferences") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Define user preferences preferences { preference( UserPreference( name = "ResponseLength", description = "How detailed the responses should be", options = listOf("Concise", "Detailed", "Comprehensive"), defaultValue = "Detailed" ) ) preference( UserPreference( name = "Tone", description = "The tone of the responses", options = listOf("Professional", "Friendly", "Technical"), defaultValue = "Friendly" ) ) } // Define adaptation strategy adaptationStrategy { // Analyze user messages to infer preferences analyzeUserMessage { message -> if (message.contains("brief") || message.contains("short")) { updatePreference("ResponseLength", "Concise") } else if (message.contains("detail") || message.contains("explain")) { updatePreference("ResponseLength", "Detailed") } if (message.contains("technical") || message.contains("advanced")) { updatePreference("Tone", "Technical") } else if (message.contains("simple") || message.contains("easy")) { updatePreference("Tone", "Friendly") } } } } fun main() = runBlocking { val agent = createAdaptiveAgent() // Test the agent println(agent.generate("Can you give me a brief explanation of quantum computing?").text) println(agent.generate("Now I'd like a detailed technical explanation of quantum entanglement.").text) } ``` ## Goal-Oriented Agents Goal-oriented agents focus on achieving specific objectives through planning and task execution. They're ideal for complex problem-solving scenarios. ### Key Features - Goal definition and tracking - Task planning and prioritization - Progress monitoring - Outcome evaluation ### Implementation ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.architecture.goalOrientedAgent import ai.kastrax.core.agent.architecture.GoalPriority import ai.kastrax.core.agent.architecture.TaskPriority import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import kotlinx.datetime.Clock import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.plus fun createGoalOrientedAgent() = goalOrientedAgent { name("ProjectManager") description("A goal-oriented agent that helps manage projects") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Define goals goals { goal( id = "complete-project", description = "Complete the website redesign project", priority = GoalPriority.HIGH, deadline = Clock.System.now().plus(30, DateTimeUnit.DAY) ) } // Define tasks for the goal tasks("complete-project") { task( id = "gather-requirements", description = "Gather project requirements from stakeholders", priority = TaskPriority.HIGH, estimatedDuration = 3 // days ) task( id = "create-wireframes", description = "Create wireframes based on requirements", priority = TaskPriority.MEDIUM, estimatedDuration = 5, // days dependencies = listOf("gather-requirements") ) task( id = "develop-frontend", description = "Develop the frontend based on wireframes", priority = TaskPriority.MEDIUM, estimatedDuration = 10, // days dependencies = listOf("create-wireframes") ) } } fun main() = runBlocking { val agent = createGoalOrientedAgent() // Test the agent println(agent.generate("What's the current status of the website redesign project?").text) println(agent.generate("What tasks should we focus on this week?").text) } ``` ## Hierarchical Agents Hierarchical agents break down complex tasks into manageable sub-tasks, creating a hierarchy of responsibilities. They're ideal for complex workflows with multiple steps. ### Key Features - Task decomposition - Hierarchical planning - Delegated execution - Coordinated results ### Implementation ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.architecture.hierarchicalAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createHierarchicalAgent() = hierarchicalAgent { name("ResearchAssistant") description("A hierarchical agent that helps with research tasks") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Define the coordinator agent coordinator { name("Coordinator") description("Coordinates the research process") systemPrompt(""" You are the coordinator of a research team. Your job is to: 1. Understand the research question 2. Break it down into sub-tasks 3. Assign tasks to specialists 4. Synthesize their results into a final answer Be thorough and methodical in your approach. """.trimIndent()) } // Define specialist agents specialists { specialist("Researcher") { description("Finds relevant information") systemPrompt(""" You are a research specialist. Your job is to: 1. Search for relevant information on the assigned topic 2. Evaluate the credibility of sources 3. Extract key facts and data 4. Organize the information logically Be thorough and cite your sources. """.trimIndent()) } specialist("Analyst") { description("Analyzes the information") systemPrompt(""" You are an analysis specialist. Your job is to: 1. Analyze the information provided 2. Identify patterns and trends 3. Draw connections between different pieces of information 4. Evaluate the significance of the findings Be critical and objective in your analysis. """.trimIndent()) } specialist("Writer") { description("Writes the final report") systemPrompt(""" You are a writing specialist. Your job is to: 1. Organize the research findings into a coherent narrative 2. Write clear, concise, and engaging content 3. Ensure the writing is appropriate for the target audience 4. Proofread for errors and clarity Be clear, concise, and engaging in your writing. """.trimIndent()) } } } fun main() = runBlocking { val agent = createHierarchicalAgent() // Test the agent println(agent.generate("Research the impact of artificial intelligence on healthcare").text) } ``` ## Reflective Agents Reflective agents monitor their own performance and continuously improve through self-reflection. They're ideal for complex reasoning tasks that benefit from iterative refinement. ### Key Features - Self-monitoring - Performance evaluation - Strategy adjustment - Continuous improvement ### Implementation ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.architecture.reflectiveAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createReflectiveAgent() = reflectiveAgent { name("ReflectiveAssistant") description("A reflective agent that improves through self-evaluation") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Define reflection criteria reflectionCriteria { criterion("Accuracy", "Is the information provided accurate and factual?") criterion("Completeness", "Does the response address all aspects of the query?") criterion("Clarity", "Is the response clear and easy to understand?") criterion("Relevance", "Is the response relevant to the user's query?") criterion("Helpfulness", "Does the response actually help the user?") } // Configure reflection frequency reflectionFrequency(3) // Reflect after every 3 interactions // Define reflection strategy reflectionStrategy { // Evaluate the last response evaluateResponse { response -> // Rate each criterion from 1-5 val ratings = mapOf( "Accuracy" to evaluateCriterion("Accuracy", response), "Completeness" to evaluateCriterion("Completeness", response), "Clarity" to evaluateCriterion("Clarity", response), "Relevance" to evaluateCriterion("Relevance", response), "Helpfulness" to evaluateCriterion("Helpfulness", response) ) // Identify areas for improvement val improvementAreas = ratings.filter { it.value < 4 }.keys // Generate improvement strategies val strategies = improvementAreas.map { criterion -> "To improve $criterion: ${generateImprovementStrategy(criterion)}" } // Update the agent's approach based on reflection updateApproach(strategies) } } } fun main() = runBlocking { val agent = createReflectiveAgent() // Test the agent println(agent.generate("Explain how neural networks work").text) println(agent.generate("Compare supervised and unsupervised learning").text) println(agent.generate("What are the ethical implications of AI?").text) // After the third query, the agent will reflect and improve } ``` ## Creative Agents Creative agents are specialized for generating creative content like stories, poems, and ideas. They have enhanced capabilities for creative expression. ### Key Features - Creative content generation - Style adaptation - Thematic exploration - Narrative construction ### Implementation ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.architecture.creativeAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createCreativeAgent() = creativeAgent { name("CreativeWriter") description("A creative agent that generates stories and poems") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.9) // Higher temperature for more creativity } // Define creative capabilities creativeCapabilities { capability("StoryGeneration", "Generate engaging short stories") capability("PoetryWriting", "Write poems in various styles") capability("IdeaGeneration", "Generate creative ideas for various purposes") capability("CharacterCreation", "Create detailed and interesting characters") } // Define creative styles creativeStyles { style("Fantasy", "Magical and otherworldly elements") style("SciFi", "Futuristic and technological themes") style("Mystery", "Suspenseful and enigmatic tone") style("Comedy", "Humorous and light-hearted approach") style("Drama", "Emotional and character-driven narrative") } // Configure creativity parameters creativityParameters { parameter("Originality", 0.8f) // Preference for original ideas parameter("Coherence", 0.7f) // Balance between coherence and creativity parameter("Detail", 0.6f) // Level of descriptive detail parameter("EmotionalImpact", 0.9f) // Emphasis on emotional resonance } } fun main() = runBlocking { val agent = createCreativeAgent() // Test the agent println(agent.generate("Write a short story about a robot discovering emotions").text) println(agent.generate("Create a poem about the changing seasons in haiku style").text) } ``` ## Choosing the Right Architecture When selecting an agent architecture, consider the following factors: | Architecture | Best For | Key Strength | |--------------|----------|--------------| | Adaptive | Personalized experiences | Adjusts to user preferences | | Goal-Oriented | Project management, planning | Focuses on achieving objectives | | Hierarchical | Complex, multi-step tasks | Breaks down problems into manageable parts | | Reflective | Critical thinking, continuous improvement | Self-evaluates and improves | | Creative | Content generation, brainstorming | Generates creative and original content | You can also combine architectures by implementing their key features in a custom agent. ## Next Steps Now that you understand the different agent architectures, you can: 1. Explore [agent memory systems](../memory/overview.mdx) 2. Learn about [tool integration](../tools/overview.mdx) 3. Implement [agent workflows](../workflows/overview.mdx) --- title: "Integrating MCP with Kastrax AI Agents | Agents | Kastrax Docs" description: "Comprehensive guide to using Model Context Protocol (MCP) with Kastrax AI Agents to extend their capabilities with external tools and resources." --- # Integrating MCP with Kastrax AI Agents ✅ [EN] Source: https://kastrax.ai/en/docs/agents/mcp-guide [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is a standardized protocol that enables AI agents to discover and interact with external tools, services, and knowledge bases. Kastrax provides robust support for MCP, allowing your agents to leverage a wide ecosystem of third-party capabilities without writing custom integrations. ## MCP Architecture in Kastrax ✅ Kastrax implements MCP support through a layered architecture that provides flexibility and extensibility: 1. **MCP Client Layer**: Manages connections to MCP servers and handles protocol-specific communication 2. **Tool Adapter Layer**: Converts MCP tool definitions into Kastrax-compatible tool interfaces 3. **Agent Integration Layer**: Enables agents to discover and use MCP tools seamlessly This architecture supports multiple transport protocols: - **Stdio**: For local MCP servers running as child processes - **HTTP/WebSocket**: For remote MCP servers accessible over the network - **Custom Transports**: Extensible architecture for implementing additional protocols ## Adding MCP Support to Your Project ✅ To use MCP with Kastrax, you need to include the MCP module in your project dependencies: ```kotlin // In your build.gradle.kts file dependencies { implementation("ai.kastrax:kastrax-core:1.0.0") implementation("ai.kastrax:kastrax-mcp:1.0.0") // Add MCP support } ``` The `kastrax-mcp` module provides all the necessary components for integrating with MCP servers. ## Configuring MCP Clients ✅ Kastrax provides a flexible `MCPClient` class that manages connections to multiple MCP servers. The client automatically selects the appropriate transport protocol based on your configuration: - **Process-based servers**: Uses Stdio transport for local processes - **Network-based servers**: Uses HTTP/WebSocket for remote servers Here's how to configure an MCP client in Kastrax: ```kotlin import ai.kastrax.mcp.MCPClient import ai.kastrax.mcp.config.MCPServerConfig import ai.kastrax.mcp.config.ProcessConfig import ai.kastrax.mcp.config.HttpConfig // Create an MCP client with multiple server configurations val mcpClient = MCPClient( servers = mapOf( // Process-based MCP server (using Stdio transport) "sequential" to MCPServerConfig( process = ProcessConfig( command = "npx", args = listOf("-y", "@modelcontextprotocol/server-sequential-thinking"), environment = mapOf("NODE_ENV" to "production") ) ), // HTTP-based MCP server "weather" to MCPServerConfig( http = HttpConfig( url = "http://localhost:8080/mcp", headers = mapOf("Authorization" to "Bearer your-token") ) ) ) ) ## Integrating MCP Tools with Agents ✅ Kastrax provides two approaches for integrating MCP tools with your agents, each suited for different use cases: ### Static Tool Integration Use this approach when: - You have a single MCP connection - The tools are used by a single user/context - Tool configuration remains constant - You want to initialize an agent with a fixed set of tools ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.AgentConfig import ai.kastrax.core.llm.DeepSeekProvider import ai.kastrax.mcp.MCPClient // Create the MCP client val mcpClient = MCPClient(/* configuration */) // Get all available tools from the MCP servers val mcpTools = mcpClient.getTools() // Create an agent with the MCP tools val agent = Agent( config = AgentConfig( name = "CLI Assistant", description = "An assistant that helps with command-line tasks", instructions = "You help users with CLI tasks and provide accurate command suggestions.", llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key"), tools = mcpTools // Tools are fixed at agent creation ) ) ``` ### Dynamic Tool Integration Use this approach when: - You need per-request tool configuration - Tools need different credentials per user - Running in a multi-user environment - Tool configuration needs to change dynamically ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.AgentConfig import ai.kastrax.core.llm.DeepSeekProvider import ai.kastrax.mcp.MCPClient import ai.kastrax.mcp.config.MCPServerConfig import ai.kastrax.mcp.config.ProcessConfig // Create a configurable MCP client val mcpClient = MCPClient( servers = mapOf( "example" to MCPServerConfig( process = ProcessConfig( command = "npx", args = listOf("-y", "@example/fakemcp"), environment = mapOf("API_KEY" to "your-api-key") ) ) ) ) // Create the agent without MCP tools initially val agent = Agent( config = AgentConfig( name = "Dynamic Assistant", description = "An assistant that uses dynamically configured tools", instructions = "You help users with various tasks using the most appropriate tools.", llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key") // No tools specified here - they'll be provided at runtime ) ) // Later, when handling a user request: fun handleUserRequest(userId: String, input: String) { // Get user-specific tool configuration val userApiKey = getUserApiKey(userId) // Configure MCP client with user-specific credentials mcpClient.updateServerEnvironment("example", mapOf("API_KEY" to userApiKey)) // Get the current toolsets configured for this user val toolsets = mcpClient.getToolsets() // Generate a response using the dynamically configured tools val response = agent.generate( input = input, sessionId = userId, conversationId = "dynamic-tools-session", toolsets = toolsets ) // Return the response to the user return response.text } ``` ## Popular MCP Registries ✅ Kastrax can integrate with various MCP registries that provide curated collections of tools. Here are some popular registries and how to use them with Kastrax: ### mcp.run Registry [mcp.run](https://www.mcp.run/) provides pre-authenticated, secure MCP servers that can be easily integrated with Kastrax: ```kotlin import ai.kastrax.mcp.MCPClient import ai.kastrax.mcp.config.MCPServerConfig import ai.kastrax.mcp.config.HttpConfig // Create an MCP client with mcp.run configuration val mcpClient = MCPClient( servers = mapOf( "marketing" to MCPServerConfig( http = HttpConfig( url = System.getenv("MCP_RUN_SSE_URL") ?: "", // No additional headers needed as the URL contains authentication ) ) ) ) ``` > **Important**: Each SSE URL on [mcp.run](https://mcp.run) contains a unique signature that should be treated like a password. Store it securely in your environment variables or configuration system. ```properties # In your application.conf or environment variables MCP_RUN_SSE_URL=https://www.mcp.run/api/mcp/sse?nonce=... ``` ### Composio.dev Registry [Composio.dev](https://composio.dev) offers a registry of MCP servers for various services like Google Sheets, Gmail, and more: ```kotlin import ai.kastrax.mcp.MCPClient import ai.kastrax.mcp.config.MCPServerConfig import ai.kastrax.mcp.config.HttpConfig // Create an MCP client with Composio.dev configurations val mcpClient = MCPClient( servers = mapOf( "googleSheets" to MCPServerConfig( http = HttpConfig( url = "https://mcp.composio.dev/googlesheets/[private-url-path]" ) ), "gmail" to MCPServerConfig( http = HttpConfig( url = "https://mcp.composio.dev/gmail/[private-url-path]" ) ) ) ) ``` Composio tools include built-in authentication capabilities, allowing your agent to guide users through the authentication process during conversation. ### Smithery.ai Registry [Smithery.ai](https://smithery.ai) provides a registry of MCP servers that can be used with Kastrax: ```kotlin import ai.kastrax.mcp.MCPClient import ai.kastrax.mcp.config.MCPServerConfig import ai.kastrax.mcp.config.ProcessConfig // For Unix/Mac val mcpClient = MCPClient( servers = mapOf( "sequentialThinking" to MCPServerConfig( process = ProcessConfig( command = "npx", args = listOf( "-y", "@smithery/cli@latest", "run", "@smithery-ai/server-sequential-thinking", "--config", "{}" ) ) ) ) ) // For Windows val windowsMcpClient = MCPClient( servers = mapOf( "sequentialThinking" to MCPServerConfig( process = ProcessConfig( command = "cmd", args = listOf( "/c", "npx", "-y", "@smithery/cli@latest", "run", "@smithery-ai/server-sequential-thinking", "--config", "{}" ) ) ) ) ) ``` ## Filtering MCP Tools ✅ Kastrax allows you to filter the tools provided by MCP servers before passing them to your agents. This is useful when you want to limit the tools available to an agent or when you need to customize the tool set for specific use cases. ### Filtering Tools by Name Suppose your MCP server exposes three tools: `weather`, `stockPrice`, and `news`, but you want to exclude the `news` tool: ```kotlin import ai.kastrax.mcp.MCPClient import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.AgentConfig import ai.kastrax.core.llm.DeepSeekProvider import ai.kastrax.core.tool.Tool // Create the MCP client val mcpClient = MCPClient(/* configuration */) // Get all tools from the MCP server val allTools = mcpClient.getTools() // Filter out the 'news' tool (tool names are namespaced as 'serverName_toolName') val filteredTools = allTools.filterNot { (name, _) -> name == "myServer_news" } // Create an agent with only the filtered tools val agent = Agent( config = AgentConfig( name = "Selective Agent", description = "An assistant with access to selected tools only", instructions = "You can access only weather and stock price information.", llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key"), tools = filteredTools ) ) ``` ### Selecting Specific Tools Alternatively, you can select only the specific tools you want to use: ```kotlin // Get all tools from the MCP server val allTools = mcpClient.getTools() // Select only the weather and stock price tools val selectedTools = allTools.filter { (name, _) -> name == "myServer_weather" || name == "myServer_stockPrice" } // Create an agent with only the selected tools val agent = Agent( config = AgentConfig( name = "Selective Agent", description = "An assistant with access to selected tools only", instructions = "You can access only weather and stock price information.", llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key"), tools = selectedTools ) ) ``` ## Advanced MCP Features ✅ ### Tool Transformation Kastrax allows you to transform MCP tools before using them with your agents. This is useful for adding custom behavior, logging, or modifying tool parameters: ```kotlin import ai.kastrax.mcp.MCPClient import ai.kastrax.mcp.MCPTool import ai.kastrax.core.tool.Tool // Create a wrapper that adds logging to MCP tools fun addLoggingToTool(tool: Tool): Tool { return object : Tool by tool { override suspend fun execute(input: I, context: ToolContext): ToolResult { println("Executing tool: ${tool.definition.name} with input: $input") val startTime = System.currentTimeMillis() val result = tool.execute(input, context) val duration = System.currentTimeMillis() - startTime println("Tool execution completed in ${duration}ms with result: $result") return result } } } // Get MCP tools and transform them val mcpTools = mcpClient.getTools() val enhancedTools = mcpTools.mapValues { (_, tool) -> addLoggingToTool(tool) } ``` ### Custom MCP Server Implementation Kastrax supports implementing your own MCP servers that can be used by your agents or shared with others: ```kotlin import ai.kastrax.mcp.server.MCPServer import ai.kastrax.mcp.server.MCPToolDefinition class CustomMCPServer : MCPServer { override val serverInfo = MCPServerInfo( name = "custom-server", version = "1.0.0", description = "A custom MCP server with specialized tools" ) override val tools = listOf( MCPToolDefinition( name = "custom_tool", description = "A specialized tool for custom operations", parameters = /* parameter schema */, execute = { params -> // Tool implementation } ) ) } ``` ## Using the Kastrax Documentation Server ✅ Kastrax provides an MCP documentation server that can be used in your IDE to access comprehensive documentation about Kastrax features and capabilities. Check out our [MCP Documentation Server guide](/docs/getting-started/mcp-docs-server) to get started. ## Next Steps ✅ - Explore the [MCPClient API Reference](/reference/tools/mcp-client) for detailed information about the client API - Check out our [example projects](/examples) that demonstrate MCP integration - Learn about [creating custom MCP servers](/docs/advanced/custom-mcp-servers) for specialized use cases - Discover how to [combine MCP tools with custom tools](/docs/advanced/hybrid-tool-systems) for maximum flexibility --- title: Agent Monitoring and Diagnostics | Kastrax Docs description: Detailed guide on monitoring and diagnosing agent performance in Kastrax, including metrics collection, performance analysis, and debugging tools. --- # Agent Monitoring and Diagnostics ✅ [EN] Source: https://kastrax.ai/en/docs/agents/monitoring-kotlin Kastrax provides comprehensive monitoring and diagnostic capabilities for AI agents, allowing you to track performance, identify issues, and optimize behavior. This guide explains how to use these features effectively. ## Monitoring Overview ✅ Agent monitoring in Kastrax enables you to: - Track agent performance metrics - Monitor resource usage - Analyze agent behavior patterns - Identify and diagnose issues - Generate reports and visualizations - Set up alerts for anomalies ## Basic Monitoring Setup ✅ Here's how to set up basic monitoring for a Kastrax agent: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.MonitoringConfig import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a monitor with default configuration val monitor = AgentMonitor.create( MonitoringConfig( enabled = true, collectPerformanceMetrics = true, collectUsageMetrics = true, collectBehaviorMetrics = true, samplingRate = 1.0 // Monitor all interactions ) ) // Create an agent with monitoring val myAgent = agent { name("MonitoredAgent") description("An agent with monitoring capabilities") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Enable monitoring monitor = monitor } // Use the agent repeat(5) { i -> val response = myAgent.generate("Tell me something interesting about ${i + 1}") println("Response ${i + 1}: ${response.text}") } // Get monitoring data val metrics = monitor.getMetrics(myAgent.name) println("Collected metrics: $metrics") } ``` ## Performance Metrics ✅ Kastrax collects various performance metrics for agents: ### Response Time Metrics ```kotlin // Get response time metrics val responseTimeMetrics = monitor.getResponseTimeMetrics(myAgent.name) println("Average response time: ${responseTimeMetrics.average} ms") println("Median response time: ${responseTimeMetrics.median} ms") println("95th percentile response time: ${responseTimeMetrics.percentile95} ms") println("Maximum response time: ${responseTimeMetrics.max} ms") ``` ### Token Usage Metrics ```kotlin // Get token usage metrics val tokenUsageMetrics = monitor.getTokenUsageMetrics(myAgent.name) println("Total input tokens: ${tokenUsageMetrics.totalInputTokens}") println("Total output tokens: ${tokenUsageMetrics.totalOutputTokens}") println("Average tokens per request: ${tokenUsageMetrics.averageTokensPerRequest}") println("Token usage trend: ${tokenUsageMetrics.usageTrend}") ``` ### Error Rate Metrics ```kotlin // Get error rate metrics val errorMetrics = monitor.getErrorMetrics(myAgent.name) println("Error rate: ${errorMetrics.errorRate * 100}%") println("Common error types: ${errorMetrics.commonErrorTypes}") println("Error trend: ${errorMetrics.errorTrend}") ``` ## Resource Usage Monitoring ✅ You can monitor resource usage of your agents: ```kotlin // Get resource usage metrics val resourceMetrics = monitor.getResourceMetrics(myAgent.name) println("Memory usage: ${resourceMetrics.memoryUsage} MB") println("CPU usage: ${resourceMetrics.cpuUsage}%") println("Network usage: ${resourceMetrics.networkUsage} KB") ``` ## Behavior Analysis ✅ Kastrax can analyze agent behavior patterns: ```kotlin // Get behavior metrics val behaviorMetrics = monitor.getBehaviorMetrics(myAgent.name) println("Tool usage distribution: ${behaviorMetrics.toolUsageDistribution}") println("Response length distribution: ${behaviorMetrics.responseLengthDistribution}") println("Common topics: ${behaviorMetrics.commonTopics}") println("Sentiment distribution: ${behaviorMetrics.sentimentDistribution}") ``` ## Custom Metrics ✅ You can define and collect custom metrics for your agents: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.CustomMetric import ai.kastrax.core.agent.monitoring.MetricType import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a monitor with custom metrics val monitor = AgentMonitor.create() // Define custom metrics monitor.defineCustomMetric( CustomMetric( name = "user_satisfaction", description = "User satisfaction score", type = MetricType.GAUGE, unit = "score" ) ) monitor.defineCustomMetric( CustomMetric( name = "task_completion_rate", description = "Rate of successful task completions", type = MetricType.RATE, unit = "percent" ) ) // Create an agent with monitoring val myAgent = agent { name("CustomMetricsAgent") description("An agent with custom metrics") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Enable monitoring monitor = monitor } // Use the agent and record custom metrics val response = myAgent.generate("Help me solve this math problem: 2x + 5 = 15") println("Response: ${response.text}") // Record custom metrics monitor.recordCustomMetric( agentName = myAgent.name, metricName = "user_satisfaction", value = 4.5 // On a scale of 1-5 ) monitor.recordCustomMetric( agentName = myAgent.name, metricName = "task_completion_rate", value = 1.0 // 100% completion ) // Get custom metrics val customMetrics = monitor.getCustomMetrics(myAgent.name) println("Custom metrics: $customMetrics") } ``` ## Real-time Monitoring ✅ You can set up real-time monitoring with callbacks: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.MonitoringCallback import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a monitor with real-time callbacks val monitor = AgentMonitor.create() // Define monitoring callback monitor.addCallback(object : MonitoringCallback { override fun onRequestStart(agentName: String, requestId: String) { println("[$agentName] Request started: $requestId") } override fun onRequestComplete(agentName: String, requestId: String, durationMs: Long) { println("[$agentName] Request completed: $requestId (${durationMs}ms)") } override fun onError(agentName: String, requestId: String, error: Throwable) { println("[$agentName] Error in request $requestId: ${error.message}") } override fun onMetricUpdate(agentName: String, metricName: String, value: Double) { println("[$agentName] Metric update: $metricName = $value") } }) // Create an agent with monitoring val myAgent = agent { name("RealTimeMonitoredAgent") description("An agent with real-time monitoring") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Enable monitoring monitor = monitor } // Use the agent val response = myAgent.generate("Tell me a joke") println("Response: ${response.text}") } ``` ## Monitoring Dashboard ✅ Kastrax provides a monitoring dashboard for visualizing agent metrics: ```kotlin import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.dashboard.MonitoringDashboard import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Get the monitor val monitor = AgentMonitor.getInstance() // Create a monitoring dashboard val dashboard = MonitoringDashboard.create(monitor) // Start the dashboard on a specific port dashboard.start(port = 8080) println("Monitoring dashboard started at http://localhost:8080") // Keep the application running readLine() // Stop the dashboard when done dashboard.stop() } ``` ## Alerting ✅ You can set up alerts for specific conditions: ```kotlin import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.alert.AlertCondition import ai.kastrax.core.agent.monitoring.alert.AlertSeverity import ai.kastrax.core.agent.monitoring.alert.AlertChannel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Get the monitor val monitor = AgentMonitor.getInstance() // Configure email alert channel val emailChannel = AlertChannel.email( recipients = listOf("admin@example.com"), smtpConfig = mapOf( "host" to "smtp.example.com", "port" to "587", "username" to "alerts@example.com", "password" to "password" ) ) // Configure Slack alert channel val slackChannel = AlertChannel.slack( webhookUrl = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX", channel = "#agent-alerts" ) // Add alert conditions monitor.addAlertCondition( AlertCondition( name = "High Error Rate", metricName = "error_rate", threshold = 0.05, // 5% error rate comparison = AlertCondition.Comparison.GREATER_THAN, duration = 300, // 5 minutes severity = AlertSeverity.HIGH, channels = listOf(emailChannel, slackChannel) ) ) monitor.addAlertCondition( AlertCondition( name = "Slow Response Time", metricName = "response_time_p95", threshold = 5000.0, // 5 seconds comparison = AlertCondition.Comparison.GREATER_THAN, duration = 600, // 10 minutes severity = AlertSeverity.MEDIUM, channels = listOf(slackChannel) ) ) println("Alert conditions configured") } ``` ## Diagnostic Tools ✅ Kastrax provides diagnostic tools for troubleshooting agent issues: ### Request Tracing ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.diagnostics.RequestTracer import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a monitor with request tracing val monitor = AgentMonitor.create() val tracer = RequestTracer.create(monitor) // Create an agent with monitoring val myAgent = agent { name("DiagnosticAgent") description("An agent with diagnostic capabilities") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Enable monitoring monitor = monitor } // Start tracing a request val traceId = tracer.startTrace() // Use the agent with the trace ID val response = myAgent.generate( "Explain quantum computing", options = AgentGenerateOptions( metadata = mapOf("traceId" to traceId) ) ) // Get the trace val trace = tracer.getTrace(traceId) // Print trace details println("Trace ID: $traceId") println("Request duration: ${trace.duration} ms") println("Steps:") trace.steps.forEachIndexed { index, step -> println(" Step ${index + 1}: ${step.name} (${step.duration} ms)") println(" Input: ${step.input}") println(" Output: ${step.output}") } } ``` ### Performance Profiling ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.diagnostics.PerformanceProfiler import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a monitor val monitor = AgentMonitor.create() val profiler = PerformanceProfiler.create(monitor) // Create an agent with monitoring val myAgent = agent { name("ProfiledAgent") description("An agent with performance profiling") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Enable monitoring monitor = monitor } // Start profiling profiler.start(myAgent.name) // Use the agent repeat(10) { i -> val response = myAgent.generate("Tell me about topic ${i + 1}") println("Response ${i + 1}: ${response.text.take(50)}...") } // Stop profiling and get results val profile = profiler.stop(myAgent.name) // Print profile results println("Performance Profile:") println("Total duration: ${profile.totalDuration} ms") println("Average response time: ${profile.averageResponseTime} ms") println("Token processing rate: ${profile.tokenProcessingRate} tokens/second") println("Hotspots:") profile.hotspots.forEach { (component, time) -> println(" $component: ${time} ms (${(time / profile.totalDuration.toDouble() * 100).toInt()}%)") } } ``` ### Log Analysis ```kotlin import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.diagnostics.LogAnalyzer import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a monitor val monitor = AgentMonitor.getInstance() val logAnalyzer = LogAnalyzer.create(monitor) // Analyze logs for a specific agent val analysis = logAnalyzer.analyzeAgentLogs("MyAgent", timeRange = TimeRange.last(hours = 24)) // Print analysis results println("Log Analysis:") println("Total requests: ${analysis.totalRequests}") println("Error rate: ${analysis.errorRate * 100}%") println("Common error patterns:") analysis.errorPatterns.forEach { (pattern, count) -> println(" $pattern: $count occurrences") } println("Performance anomalies:") analysis.performanceAnomalies.forEach { anomaly -> println(" ${anomaly.timestamp}: ${anomaly.description} (${anomaly.severity})") } } ``` ## Exporting Monitoring Data ✅ You can export monitoring data for further analysis: ```kotlin import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.export.MetricsExporter import kotlinx.coroutines.runBlocking import java.io.File fun main() = runBlocking { // Get the monitor val monitor = AgentMonitor.getInstance() // Create exporters val csvExporter = MetricsExporter.csv() val jsonExporter = MetricsExporter.json() val prometheusExporter = MetricsExporter.prometheus() // Export metrics for a specific agent val agentName = "MyAgent" val timeRange = TimeRange.last(days = 7) // Export to CSV val csvData = csvExporter.exportMetrics(monitor, agentName, timeRange) File("agent_metrics.csv").writeText(csvData) // Export to JSON val jsonData = jsonExporter.exportMetrics(monitor, agentName, timeRange) File("agent_metrics.json").writeText(jsonData) // Start Prometheus exporter prometheusExporter.start(port = 9090) println("Metrics exported to CSV and JSON files") println("Prometheus metrics available at http://localhost:9090/metrics") // Keep the application running readLine() // Stop Prometheus exporter prometheusExporter.stop() } ``` ## Integration with External Monitoring Systems ✅ Kastrax can integrate with external monitoring systems: ```kotlin import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.integration.DatadogIntegration import ai.kastrax.core.agent.monitoring.integration.PrometheusIntegration import ai.kastrax.core.agent.monitoring.integration.GrafanaIntegration import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Get the monitor val monitor = AgentMonitor.getInstance() // Configure Datadog integration val datadogIntegration = DatadogIntegration.create( apiKey = "your-datadog-api-key", applicationKey = "your-datadog-application-key", tags = mapOf("environment" to "production") ) monitor.addIntegration(datadogIntegration) // Configure Prometheus integration val prometheusIntegration = PrometheusIntegration.create( port = 9090, endpoint = "/metrics" ) monitor.addIntegration(prometheusIntegration) // Configure Grafana integration val grafanaIntegration = GrafanaIntegration.create( url = "http://localhost:3000", apiKey = "your-grafana-api-key", dashboardName = "Kastrax Agents" ) monitor.addIntegration(grafanaIntegration) println("External monitoring integrations configured") } ``` ## Best Practices ✅ When using agent monitoring and diagnostics, follow these best practices: 1. **Selective Monitoring**: Monitor only what's necessary to avoid performance overhead 2. **Sampling**: Use sampling for high-traffic applications 3. **Retention Policies**: Set appropriate data retention policies 4. **Privacy**: Ensure monitoring respects privacy by not collecting sensitive information 5. **Alerting Thresholds**: Set appropriate alerting thresholds to avoid alert fatigue 6. **Regular Review**: Regularly review monitoring data to identify trends and issues 7. **Documentation**: Document monitoring setup and alert responses 8. **Testing**: Test monitoring and alerting in a staging environment ## Conclusion ✅ Agent monitoring and diagnostics provide powerful capabilities for understanding, optimizing, and troubleshooting AI agent systems in Kastrax. By collecting and analyzing metrics, you can ensure your agents perform reliably and efficiently. ## Next Steps ✅ - Learn about [Agent Versioning](./versioning-kotlin.mdx) - Explore [Performance Optimization](./optimization-kotlin.mdx) - Understand [Distributed Agents](../actor/actor-agent-integration-kotlin.mdx) --- title: "Creating and Using Agents | Agent Documentation | Kastrax" description: Overview of agents in Kastrax, detailing their capabilities and how they interact with tools, memory, and external systems. --- # Creating and Using Agents ✅ [EN] Source: https://kastrax.ai/en/docs/agents/overview Agents in Kastrax are intelligent systems that can autonomously decide on a sequence of actions to perform tasks. They have access to tools, memory, and external systems, enabling them to perform complex tasks and interact with various services. Agents can invoke custom functions, utilize third-party APIs through integrations, and access knowledge bases you have built. ## Agent Capabilities ✅ Kastrax agents provide several key capabilities: - **Multiple Architectures**: Choose from adaptive, goal-oriented, hierarchical, reflective, and creative agent architectures - **Memory Management**: Store and retrieve information across conversations using different memory types - **Tool Integration**: Use built-in tools or create custom tools for specific tasks with type-safe interfaces - **LLM Integration**: Work with various LLM providers including DeepSeek, OpenAI, Anthropic, and Google Gemini - **RAG Support**: Incorporate knowledge from documents and other data sources with advanced retrieval techniques - **Actor Model**: Build distributed agent systems using the actor model for scalability and resilience - **Versioning**: Create, manage, and roll back different versions of your agents - **State Management**: Track and update agent states throughout their lifecycle - **Session Management**: Organize conversations into sessions with metadata - **Performance Monitoring**: Collect metrics and analyze agent behavior for optimization ## Creating an Agent ✅ To create a basic agent in Kastrax, you use the `agent` DSL function: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("MyFirstAgent") description("A helpful assistant that can answer questions") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Add system prompt systemPrompt(""" You are a helpful, friendly assistant. Your goal is to provide accurate and useful information. Be concise but thorough in your responses. """.trimIndent()) } // Use the agent val response = myAgent.generate("What is artificial intelligence?") println(response.text) } ``` ## Adding Memory ✅ You can add memory to your agent to help it remember past interactions: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("AgentWithMemory") description("An agent that remembers past interactions") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Add memory memory = memory { workingMemory(true) conversationHistory(10) semanticMemory(true) } } // Use the agent val response1 = myAgent.generate("My name is Alice.") println(response1.text) val response2 = myAgent.generate("What's my name?") println(response2.text) } ``` ## Adding Tools ✅ You can add tools to your agent to enable it to perform specific actions: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import java.time.LocalDateTime import java.time.format.DateTimeFormatter fun main() = runBlocking { val myAgent = agent { name("AgentWithTools") description("An agent that can use tools") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Add tools tools { tool("getCurrentTime") { description("Get the current time") parameters {} execute { val currentTime = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("HH:mm:ss") "The current time is ${currentTime.format(formatter)}" } } tool("calculateSum") { description("Calculate the sum of two numbers") parameters { parameter("a", "First number", Double::class) parameter("b", "Second number", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double "The sum of $a and $b is ${a + b}" } } } } // Use the agent val response1 = myAgent.generate("What time is it now?") println(response1.text) val response2 = myAgent.generate("Calculate the sum of 5.2 and 3.8") println(response2.text) } ``` ## Using RAG with Agents ✅ You can incorporate Retrieval-Augmented Generation (RAG) into your agents: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.rag.document.DocumentLoader import ai.kastrax.rag.document.TextSplitter import ai.kastrax.rag.embedding.EmbeddingModel import ai.kastrax.rag.vectorstore.VectorStore import ai.kastrax.rag.retrieval.Retriever import ai.kastrax.rag.context.ContextBuilder import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Set up RAG components val loader = DocumentLoader.fromFile("knowledge_base.pdf") val documents = loader.load() val splitter = TextSplitter.recursive(chunkSize = 1000, chunkOverlap = 200) val chunks = splitter.split(documents) val embeddingModel = EmbeddingModel.create("all-MiniLM-L6-v2") val vectorStore = VectorStore.create("chroma") vectorStore.addDocuments(chunks, embeddingModel) val retriever = Retriever.fromVectorStore( vectorStore = vectorStore, embeddingModel = embeddingModel, topK = 5 ) val contextBuilder = ContextBuilder.create { template(""" Answer the question based on the following context: Context: {{context}} Question: {{query}} Answer: """.trimIndent()) } // Create a RAG agent val ragAgent = agent { name("RAGAgent") description("An agent with RAG capabilities") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure RAG rag { retriever(retriever) contextBuilder(contextBuilder) maxTokens(3000) } } // Use the RAG agent val response = ragAgent.generate("What are the key features of Kastrax?") println(response.text) } ``` ## Agent Architectures ✅ Kastrax provides several specialized agent architectures, each designed for different use cases: ### Adaptive Agents ✅ Adaptive agents adjust their behavior based on user preferences and feedback: ```kotlin import ai.kastrax.core.agent.architecture.adaptiveAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val adaptiveAgent = adaptiveAgent { name("AdaptiveAssistant") description("An assistant that adapts to user preferences") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Define user preferences preferences { // Preference configuration } // Define adaptation strategy adaptationStrategy { // Adaptation logic } } // Use the adaptive agent val response = adaptiveAgent.generate("Tell me about quantum computing") println(response.text) } ``` ### Goal-Oriented Agents ✅ Goal-oriented agents focus on achieving specific objectives through planning and task execution: ```kotlin import ai.kastrax.core.agent.architecture.goalOrientedAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val goalOrientedAgent = goalOrientedAgent { name("ProjectManager") description("A goal-oriented agent that helps manage projects") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Define goals and tasks goals { // Goal configuration } tasks("goal-id") { // Task configuration } } // Use the goal-oriented agent val response = goalOrientedAgent.generate("What's the status of the project?") println(response.text) } ``` For more detailed information about agent architectures, see the [Agent Architectures](./architectures.mdx) guide. ## Next Steps ✅ Now that you understand the basics of creating and using agents in Kastrax, you can: 1. Explore different [agent architectures](./architectures.mdx) 2. Learn about [memory systems](../memory/overview.mdx) 3. Create [custom tools](../tools/custom-tools.mdx) 4. Build [agent workflows](../workflows/overview.mdx) --- title: "Runtime Context in Kastrax AI Agents | Agents | Kastrax Docs" description: Learn how to use Kastrax's powerful dependency injection system to provide runtime configuration to AI agents and tools, enabling dynamic behavior adaptation. --- # Runtime Context in Kastrax AI Agents ✅ [EN] Source: https://kastrax.ai/en/docs/agents/runtime-variables Kastrax provides a sophisticated runtime context system based on dependency injection that enables you to dynamically configure your AI agents and tools with runtime variables. This powerful feature allows you to create flexible, adaptable agents that can modify their behavior based on runtime conditions without changing their underlying implementation. ## Runtime Context Architecture ✅ Kastrax implements a robust dependency injection system that provides several key capabilities: 1. **Type-safe Configuration**: Pass strongly-typed runtime variables to agents through a type-safe context 2. **Contextual Tool Execution**: Access context variables within tool execution environments 3. **Dynamic Behavior Adaptation**: Modify agent behavior at runtime without changing implementation code 4. **Shared Configuration**: Share configuration across multiple tools and components within an agent 5. **Environment-aware Processing**: Adapt agent responses based on deployment environment or user preferences ## Basic Usage ✅ Here's how to use runtime context with a Kastrax AI agent: ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.core.context.RuntimeContext import ai.kastrax.core.context.ContextKey // Define your runtime context keys using the type-safe ContextKey system object WeatherContextKeys { val TEMPERATURE_SCALE = ContextKey("temperature-scale") val LANGUAGE = ContextKey("language") val LOCATION = ContextKey("location") } // Create a runtime context and set values val runtimeContext = RuntimeContext().apply { set(WeatherContextKeys.TEMPERATURE_SCALE, "celsius") set(WeatherContextKeys.LANGUAGE, "en-US") set(WeatherContextKeys.LOCATION, "New York") } // Use the context when generating a response val response = agent.generate( input = "What's the weather like today?", sessionId = "user-123", conversationId = "weather-session", context = runtimeContext ) println(response.text) ``` This example demonstrates how to create a runtime context with temperature scale, language, and location preferences, then pass it to an agent when generating a response. ## Using with Web Services ✅ Kastrax makes it easy to integrate runtime context with web services. Here's how to dynamically set temperature units based on a user's location in a Ktor web service: ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.core.context.RuntimeContext import ai.kastrax.core.context.ContextKey import io.ktor.server.application.* import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.routing.* import io.ktor.server.request.* import io.ktor.server.response.* // Define context keys object ContextKeys { val TEMPERATURE_SCALE = ContextKey("temperature-scale") val LANGUAGE = ContextKey("language") } // Create a Ktor web service fun main() { embeddedServer(Netty, port = 8080) { // Configure the agent val weatherAgent = getWeatherAgent() // Add a request interceptor to set context based on headers intercept(ApplicationCallPipeline.Monitoring) { // Get country from header (similar to Cloudflare's CF-IPCountry) val country = call.request.header("X-Country-Code") // Store the runtime context in call attributes val runtimeContext = RuntimeContext().apply { // Set temperature scale based on country set(ContextKeys.TEMPERATURE_SCALE, if (country == "US") "fahrenheit" else "celsius") // Set language based on Accept-Language header val language = call.request.header("Accept-Language")?.split(",")?.firstOrNull() ?: "en-US" set(ContextKeys.LANGUAGE, language) } // Store context in call attributes for later use call.attributes.put(RuntimeContextKey, runtimeContext) } // Define routes routing { post("/ask") { // Get the user's question from the request val request = call.receive() // Get the runtime context from call attributes val runtimeContext = call.attributes[RuntimeContextKey] // Generate a response using the agent with the runtime context val response = weatherAgent.generate( input = request.question, sessionId = request.userId, conversationId = request.conversationId, context = runtimeContext ) // Return the response call.respond(WeatherResponse(text = response.text)) } } }.start(wait = true) } // Data classes for request/response data class WeatherRequest( val question: String, val userId: String, val conversationId: String ) data class WeatherResponse( val text: String ) // Key for storing runtime context in call attributes private val RuntimeContextKey = AttributeKey("RuntimeContext") ``` This example demonstrates how to create a web service that dynamically configures the agent's runtime context based on request headers, providing personalized responses to each user. ## Creating Context-Aware Tools ✅ Kastrax tools can access runtime context variables to adapt their behavior dynamically. Here's how to create a weather tool that uses context variables: ```kotlin import ai.kastrax.core.tool.Tool import ai.kastrax.core.tool.ToolDefinition import ai.kastrax.core.tool.ToolResult import ai.kastrax.core.context.RuntimeContext import ai.kastrax.core.context.ContextKey import kotlinx.serialization.Serializable // Define input and output types @Serializable data class WeatherToolInput( val location: String ) @Serializable data class WeatherToolOutput( val location: String, val temperature: String, val conditions: String, val unit: String ) // Define context keys object WeatherContextKeys { val TEMPERATURE_SCALE = ContextKey("temperature-scale") val LANGUAGE = ContextKey("language") } // Implement the context-aware tool class WeatherTool : Tool { // Define tool metadata override val definition = ToolDefinition( name = "get_weather", description = "Get the current weather for a specified location", inputType = WeatherToolInput::class, outputType = WeatherToolOutput::class ) // Implement the execution logic with context awareness override suspend fun execute( input: WeatherToolInput, context: RuntimeContext ): ToolResult { return try { // Get temperature scale from context (with default fallback) val temperatureScale = context.get(WeatherContextKeys.TEMPERATURE_SCALE) ?: "celsius" // Get language from context (with default fallback) val language = context.get(WeatherContextKeys.LANGUAGE) ?: "en-US" // Fetch weather data using the context variables val weather = fetchWeatherData( location = input.location, temperatureScale = temperatureScale, language = language ) // Return the result ToolResult.Success(weather) } catch (e: Exception) { ToolResult.Error("Failed to fetch weather data: ${e.message}") } } // Helper method to fetch weather data private suspend fun fetchWeatherData( location: String, temperatureScale: String, language: String ): WeatherToolOutput { // In a real implementation, this would call a weather API // For demonstration, we're returning mock data // Format temperature based on scale val temp = if (temperatureScale == "celsius") "22°C" else "72°F" // Get conditions in the appropriate language val conditions = when (language.substringBefore("-")) { "es" -> "Soleado" "fr" -> "Ensoleillé" else -> "Sunny" } return WeatherToolOutput( location = location, temperature = temp, conditions = conditions, unit = temperatureScale ) } } ``` ## Advanced Context Features ✅ Kastrax's runtime context system provides several advanced features for more complex scenarios: ### Context Inheritance ```kotlin // Create a base context with common settings val baseContext = RuntimeContext().apply { set(CommonKeys.LANGUAGE, "en-US") set(CommonKeys.TIMEZONE, "America/New_York") } // Create a derived context that inherits from the base context val userContext = RuntimeContext(parent = baseContext).apply { // Override or add user-specific settings set(UserKeys.TEMPERATURE_SCALE, "fahrenheit") set(UserKeys.LOCATION, "New York") } // The userContext now has access to both its own settings and those from baseContext ``` ### Context Scopes ```kotlin // Create a scoped context for a specific operation val result = runtimeContext.withScope { scopedContext -> // Temporarily modify the context for this scope only scopedContext.set(OperationKeys.DEBUG_MODE, true) scopedContext.set(OperationKeys.TIMEOUT, 30000) // Execute an operation with the scoped context performOperation(scopedContext) } // Changes made in the scope don't affect the original context ``` ### Context Observers ```kotlin // Register an observer to be notified when specific keys change runtimeContext.addObserver(UserKeys.LANGUAGE) { oldValue, newValue -> println("Language changed from $oldValue to $newValue") // Perform any necessary updates when the language changes updateTranslations(newValue) } ``` These advanced features enable sophisticated context management for complex AI agent applications. --- title: Agent Versioning and State Management | Kastrax Docs description: Detailed guide on managing agent versions and state in Kastrax, including version creation, activation, rollback, and state persistence. --- # Agent Versioning and State Management ✅ [EN] Source: https://kastrax.ai/en/docs/agents/versioning-kotlin Kastrax provides robust capabilities for managing agent versions and state, allowing you to track changes, roll back to previous versions, and maintain agent state across sessions. This guide explains how to use these features effectively. ## Agent Versioning ✅ Agent versioning allows you to create, manage, and switch between different versions of an agent's instructions and configuration. This is useful for: - Tracking changes to agent behavior over time - A/B testing different agent configurations - Rolling back to previous versions if issues arise - Maintaining a history of agent development ### Creating Agent Versions ✅ You can create versions of an agent using the `createVersion` method: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.version.AgentVersionManager import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with version management val myAgent = agent { name("VersionedAgent") description("An agent with version management") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Enable version management versionManager = AgentVersionManager.create() } // Create initial version val initialVersion = myAgent.createVersion( instructions = "You are a helpful assistant that provides concise answers.", name = "v1.0", description = "Initial version with basic functionality", activateImmediately = true ) println("Created initial version: ${initialVersion?.id}") // Create an improved version val improvedVersion = myAgent.createVersion( instructions = """ You are a helpful assistant that provides concise, accurate answers. When providing information, always cite your sources. If you're unsure about something, acknowledge the uncertainty. """.trimIndent(), name = "v1.1", description = "Improved version with source citation", metadata = mapOf("author" to "AI Team", "approved" to "true"), activateImmediately = false ) println("Created improved version: ${improvedVersion?.id}") } ``` ### Managing Versions ✅ You can list, retrieve, and manage agent versions: ```kotlin // Get all versions val versions = myAgent.getVersions() println("Available versions:") versions?.forEach { version -> println("- ${version.name}: ${version.description}") } // Get the active version val activeVersion = myAgent.getActiveVersion() println("Active version: ${activeVersion?.name}") // Activate a specific version val versionToActivate = versions?.find { it.name == "v1.1" } if (versionToActivate != null) { val activated = myAgent.activateVersion(versionToActivate.id) println("Activated version: ${activated?.name}") } ``` ### Rolling Back ✅ You can roll back to a previous version if needed: ```kotlin // Roll back to a previous version val versionToRollback = versions?.find { it.name == "v1.0" } if (versionToRollback != null) { val rolledBack = myAgent.rollbackToVersion(versionToRollback.id) println("Rolled back to version: ${rolledBack?.name}") } ``` ### Version Comparison ✅ You can compare different versions to understand changes: ```kotlin import ai.kastrax.core.agent.version.compareVersions // Compare two versions val v1 = versions?.find { it.name == "v1.0" } val v2 = versions?.find { it.name == "v1.1" } if (v1 != null && v2 != null) { val comparison = compareVersions(v1, v2) println("Changes from ${v1.name} to ${v2.name}:") println("- Instructions: ${comparison.instructionsDiff}") println("- Metadata changes: ${comparison.metadataChanges}") } ``` ## Agent State Management ✅ Agent state management allows you to maintain and control the state of an agent across interactions. This is useful for: - Maintaining context across sessions - Tracking agent progress on long-running tasks - Implementing stateful behaviors - Persisting important information ### Agent State Basics ✅ Agent state consists of: - **Status**: The current operational status of the agent - **Data**: Custom data associated with the agent - **Context**: Contextual information for the agent's operation - **Metadata**: Additional information about the state ### Managing Agent State ✅ You can get and update the agent's state: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.state.AgentStatus import ai.kastrax.core.agent.state.StateManager import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive fun main() = runBlocking { // Create an agent with state management val myAgent = agent { name("StatefulAgent") description("An agent with state management") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Enable state management stateManager = StateManager.create() } // Get the current state val currentState = myAgent.getState() println("Current state: ${currentState?.status}") // Update the agent's status val updatedState = myAgent.updateState( AgentStatus.BUSY, data = JsonObject(mapOf( "currentTask" to JsonPrimitive("Researching quantum computing"), "progress" to JsonPrimitive(25) )), context = JsonObject(mapOf( "userQuery" to JsonPrimitive("Explain quantum computing") )), metadata = mapOf( "priority" to "high", "estimatedCompletionTime" to "5 minutes" ) ) println("Updated state: ${updatedState?.status}") println("State data: ${updatedState?.data}") } ``` ### Persistent State ✅ You can configure the state manager to persist state across sessions: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.state.StateManager import ai.kastrax.core.agent.state.storage.FileStateStorage import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a persistent state manager val stateManager = StateManager.create( storage = FileStateStorage("agent_states") ) // Create an agent with persistent state management val myAgent = agent { name("PersistentAgent") description("An agent with persistent state") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Use the persistent state manager stateManager = stateManager } // Agent state will now be persisted across application restarts } ``` ### State Transitions ✅ You can define and enforce state transitions: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.state.AgentStatus import ai.kastrax.core.agent.state.StateManager import ai.kastrax.core.agent.state.StateTransition import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Define state transitions val transitions = listOf( StateTransition(from = AgentStatus.IDLE, to = AgentStatus.BUSY), StateTransition(from = AgentStatus.BUSY, to = AgentStatus.IDLE), StateTransition(from = AgentStatus.BUSY, to = AgentStatus.ERROR), StateTransition(from = AgentStatus.ERROR, to = AgentStatus.IDLE) ) // Create a state manager with defined transitions val stateManager = StateManager.create( allowedTransitions = transitions ) // Create an agent with the state manager val myAgent = agent { name("TransitionAgent") description("An agent with state transitions") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Use the state manager with transitions stateManager = stateManager } // Valid transition myAgent.updateState(AgentStatus.BUSY) // Invalid transition would throw an exception try { myAgent.updateState(AgentStatus.PAUSED) } catch (e: IllegalStateException) { println("Invalid state transition: ${e.message}") } } ``` ## Combining Versioning and State ✅ You can combine versioning and state management for comprehensive agent management: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.state.StateManager import ai.kastrax.core.agent.version.AgentVersionManager import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with both version and state management val myAgent = agent { name("ManagedAgent") description("An agent with version and state management") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Enable version management versionManager = AgentVersionManager.create() // Enable state management stateManager = StateManager.create() } // Create a version val version = myAgent.createVersion( instructions = "You are a helpful assistant.", name = "v1.0", activateImmediately = true ) // Update state myAgent.updateState(AgentStatus.IDLE) // Use the agent val response = myAgent.generate("Hello, how can you help me?") println(response.text) // Create a new version and activate it val newVersion = myAgent.createVersion( instructions = "You are a helpful assistant with expertise in programming.", name = "v1.1", activateImmediately = true ) // Use the updated agent val newResponse = myAgent.generate("Can you help me with Python?") println(newResponse.text) } ``` ## Monitoring and Diagnostics ✅ You can monitor agent versions and state for diagnostics: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.state.StateManager import ai.kastrax.core.agent.version.AgentVersionManager import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a monitor val monitor = AgentMonitor.create() // Create an agent with monitoring val myAgent = agent { name("MonitoredAgent") description("An agent with monitoring") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Enable version management versionManager = AgentVersionManager.create() // Enable state management stateManager = StateManager.create() // Enable monitoring monitor = monitor } // Create versions and update state myAgent.createVersion( instructions = "You are a helpful assistant.", name = "v1.0", activateImmediately = true ) myAgent.updateState(AgentStatus.IDLE) // Get monitoring data val versionHistory = monitor.getVersionHistory(myAgent.name) println("Version history: $versionHistory") val stateHistory = monitor.getStateHistory(myAgent.name) println("State history: $stateHistory") // Get performance metrics val metrics = monitor.getPerformanceMetrics(myAgent.name) println("Performance metrics: $metrics") } ``` ## Best Practices ✅ When using agent versioning and state management, follow these best practices: 1. **Version Naming**: Use a consistent naming scheme for versions (e.g., semantic versioning) 2. **Version Documentation**: Include detailed descriptions and metadata for each version 3. **State Validation**: Validate state data to ensure consistency 4. **Error Handling**: Implement proper error handling for state transitions and version activation 5. **Monitoring**: Monitor version and state changes for debugging and analytics 6. **Persistence**: Use persistent storage for production environments 7. **Testing**: Test different versions and state transitions thoroughly ## Conclusion ✅ Agent versioning and state management provide powerful capabilities for building sophisticated, maintainable AI agent systems in Kastrax. By tracking versions and managing state, you can create agents that evolve over time while maintaining reliability and consistency. ## Next Steps ✅ - Learn about [Agent Architectures](./architectures-kotlin.mdx) - Explore [Memory Integration](../memory/overview-kotlin.mdx) - Understand [Agent Monitoring](./monitoring-kotlin.mdx) --- title: "Agent Versioning | Kastrax Docs" description: "Learn how to manage different versions of your AI agents in Kastrax, including creating, testing, deploying, and rolling back agent versions." --- # Agent Versioning ✅ [EN] Source: https://kastrax.ai/en/docs/agents/versioning Kastrax provides a comprehensive agent versioning system that allows you to create, manage, and roll back different versions of your agents. This guide explains the agent versioning architecture and how to use it effectively. ## Why Version Agents? ✅ Versioning your agents provides several benefits: - **Controlled Evolution**: Make incremental improvements to your agents - **Safe Testing**: Test new agent versions without affecting production - **Easy Rollback**: Quickly revert to previous versions if issues arise - **A/B Testing**: Compare different agent versions to optimize performance - **Audit Trail**: Track changes to agent configurations over time - **Compliance**: Meet regulatory requirements for AI system changes ## Agent Versioning Architecture ✅ The Kastrax agent versioning system consists of several key components: 1. **Version Registry**: Central repository for agent versions 2. **Version Control**: Tools for creating and managing versions 3. **Deployment Management**: Tools for deploying versions to different environments 4. **Rollback Mechanisms**: Tools for reverting to previous versions 5. **Version Comparison**: Tools for comparing different versions ## Creating Agent Versions ✅ You can create new versions of your agents using the versioning API: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.versioning.versioning import ai.kastrax.integrations.deepseek.deepSeek import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with versioning val agent = agent { name = "CustomerSupportAgent" instructions = "You are a helpful customer support assistant." model = deepSeek { apiKey = "your-deepseek-api-key" } // Enable versioning versioning { enabled = true storageLocation = "s3://your-bucket/agent-versions" initialVersion = "1.0.0" description = "Initial version of customer support agent" } } // Create a new version val newVersion = agent.versioning.createVersion( version = "1.1.0", description = "Improved response quality and added product knowledge", changes = mapOf( "instructions" to "You are a helpful customer support assistant with detailed knowledge of our products.", "temperature" to 0.7 ) ) println("Created new version: ${newVersion.version}") println("Version ID: ${newVersion.id}") println("Created at: ${newVersion.createdAt}") } ``` ## Managing Agent Versions ✅ You can list, get, and delete agent versions: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.versioning.versioning import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with versioning val agent = agent { name = "CustomerSupportAgent" // ... other configuration ... versioning { enabled = true // ... versioning configuration ... } } // List all versions val versions = agent.versioning.listVersions() println("Available versions:") versions.forEach { version -> println("- ${version.version} (${version.description})") } // Get a specific version val version = agent.versioning.getVersion("1.1.0") println("Version details:") println("Version: ${version.version}") println("Description: ${version.description}") println("Created at: ${version.createdAt}") println("Created by: ${version.createdBy}") println("Changes: ${version.changes}") // Delete a version agent.versioning.deleteVersion("1.0.0") println("Deleted version 1.0.0") } ``` ## Deploying Agent Versions ✅ You can deploy different versions of your agents to different environments: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.versioning.versioning import ai.kastrax.core.versioning.Environment import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with versioning val agent = agent { name = "CustomerSupportAgent" // ... other configuration ... versioning { enabled = true // ... versioning configuration ... } } // Define environments val environments = listOf( Environment( name = "development", description = "Development environment for testing new features" ), Environment( name = "staging", description = "Staging environment for pre-production testing" ), Environment( name = "production", description = "Production environment for end users" ) ) // Register environments environments.forEach { env -> agent.versioning.registerEnvironment(env) } // Deploy versions to different environments agent.versioning.deploy("1.2.0", "development") agent.versioning.deploy("1.1.0", "staging") agent.versioning.deploy("1.0.0", "production") // Get deployed versions val deployedVersions = agent.versioning.getDeployedVersions() println("Deployed versions:") deployedVersions.forEach { (env, version) -> println("- $env: $version") } } ``` ## Rolling Back Agent Versions ✅ You can roll back to previous versions if issues arise: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.versioning.versioning import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with versioning val agent = agent { name = "CustomerSupportAgent" // ... other configuration ... versioning { enabled = true // ... versioning configuration ... } } // Deploy a new version to production agent.versioning.deploy("1.1.0", "production") println("Deployed version 1.1.0 to production") // Simulate an issue with the new version println("Issue detected with version 1.1.0") // Roll back to the previous version val rollback = agent.versioning.rollback("production") println("Rolled back to version ${rollback.version} in production") println("Rollback reason: ${rollback.reason}") println("Rollback performed by: ${rollback.performedBy}") println("Rollback timestamp: ${rollback.timestamp}") } ``` ## A/B Testing Agent Versions ✅ You can compare different agent versions using A/B testing: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.versioning.versioning import ai.kastrax.core.versioning.abTest import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with versioning val agent = agent { name = "CustomerSupportAgent" // ... other configuration ... versioning { enabled = true // ... versioning configuration ... } } // Create an A/B test val test = agent.versioning.abTest { name = "response-quality-test" description = "Testing improved response quality in version 1.1.0" versionA = "1.0.0" versionB = "1.1.0" trafficSplit = 0.5 // 50% of traffic to each version metrics = listOf( "response_time", "user_satisfaction", "task_completion" ) duration = 7 * 24 * 60 * 60 * 1000 // 7 days in milliseconds } println("Created A/B test: ${test.name}") println("Test ID: ${test.id}") println("Start time: ${test.startTime}") println("End time: ${test.endTime}") // Get A/B test results (after the test has run) val results = agent.versioning.getAbTestResults("response-quality-test") println("A/B test results:") println("Version A (${results.versionA}) metrics:") results.metricsA.forEach { (metric, value) -> println("- $metric: $value") } println("Version B (${results.versionB}) metrics:") results.metricsB.forEach { (metric, value) -> println("- $metric: $value") } println("Winner: ${results.winner}") println("Confidence: ${results.confidence}") } ``` ## Version Comparison ✅ You can compare different versions of your agents: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.versioning.versioning import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with versioning val agent = agent { name = "CustomerSupportAgent" // ... other configuration ... versioning { enabled = true // ... versioning configuration ... } } // Compare two versions val comparison = agent.versioning.compareVersions("1.0.0", "1.1.0") println("Version comparison:") println("Changes:") comparison.changes.forEach { (key, change) -> println("- $key: ${change.from} -> ${change.to}") } println("Performance difference:") comparison.performanceDiff.forEach { (metric, diff) -> println("- $metric: ${diff.percentage}% (${diff.direction})") } } ``` ## Version History and Audit Trail ✅ You can view the version history and audit trail of your agents: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.versioning.versioning import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with versioning val agent = agent { name = "CustomerSupportAgent" // ... other configuration ... versioning { enabled = true // ... versioning configuration ... } } // Get version history val history = agent.versioning.getHistory() println("Version history:") history.forEach { event -> println("- ${event.timestamp}: ${event.action} (${event.version})") println(" Performed by: ${event.user}") println(" Details: ${event.details}") } // Get audit trail for a specific version val auditTrail = agent.versioning.getAuditTrail("1.1.0") println("Audit trail for version 1.1.0:") auditTrail.forEach { entry -> println("- ${entry.timestamp}: ${entry.action}") println(" Performed by: ${entry.user}") println(" Details: ${entry.details}") } } ``` ## Conclusion ✅ Kastrax's agent versioning system provides a comprehensive solution for managing different versions of your AI agents. By leveraging these features, you can safely evolve your agents, test new versions, and quickly roll back if issues arise, all while maintaining a complete audit trail of changes. --- title: "Discord Community and Bot | Documentation | Kastrax" description: Information about the Kastrax Discord community and MCP bot. --- # Discord Community [EN] Source: https://kastrax.ai/en/docs/community/discord The Discord server has over 1000 members and serves as the main discussion forum for Kastrax. The Kastrax team monitors Discord during North American and European business hours, with community members active across other time zones.[Join the Discord server](https://discord.gg/BTYqqHKUrf). ## Discord MCP Bot In addition to community members, we have an (experimental!) Discord bot that can also help answer questions. It uses [Model Context Protocol (MCP)](/docs/agents/mcp-guide). You can ask it a question with `/ask` (either in public channels or DMs) and clear history (in DMs only) with `/cleardm`. --- title: "Licensing" description: "Kastrax License" --- # License [EN] Source: https://kastrax.ai/en/docs/community/licensing ## Elastic License 2.0 (ELv2) Kastrax is licensed under the Elastic License 2.0 (ELv2), a modern license designed to balance open-source principles with sustainable business practices. ### What is Elastic License 2.0? The Elastic License 2.0 is a source-available license that grants users broad rights to use, modify, and distribute the software while including specific limitations to protect the project's sustainability. It allows: - Free use for most purposes - Viewing, modifying, and redistributing the source code - Creating and distributing derivative works - Commercial use within your organization The primary limitation is that you cannot provide Kastrax as a hosted or managed service that offers users access to the substantial functionality of the software. ### Why We Chose Elastic License 2.0 We selected the Elastic License 2.0 for several important reasons: 1. **Sustainability**: It enables us to maintain a healthy balance between openness and the ability to sustain long-term development. 2. **Innovation Protection**: It ensures we can continue investing in innovation without concerns about our work being repackaged as competing services. 3. **Community Focus**: It maintains the spirit of open source by allowing users to view, modify, and learn from our code while protecting our ability to support the community. 4. **Business Clarity**: It provides clear guidelines for how Kastrax can be used in commercial contexts. ### Building Your Business with Kastrax Despite the licensing restrictions, there are numerous ways to build successful businesses using Kastrax: #### Allowed Business Models - **Building Applications**: Create and sell applications built with Kastrax - **Offering Consulting Services**: Provide expertise, implementation, and customization services - **Developing Custom Solutions**: Build bespoke AI solutions for clients using Kastrax - **Creating Add-ons and Extensions**: Develop and sell complementary tools that extend Kastrax's functionality - **Training and Education**: Offer courses and educational materials about using Kastrax effectively #### Examples of Compliant Usage - A company builds an AI-powered customer service application using Kastrax and sells it to clients - A consulting firm offers implementation and customization services for Kastrax - A developer creates specialized agents and tools with Kastrax and licenses them to other businesses - A startup builds a vertical-specific solution (e.g., healthcare AI assistant) powered by Kastrax #### What to Avoid The main restriction is that you cannot offer Kastrax itself as a hosted service where users access its core functionality. This means: - Don't create a SaaS platform that is essentially Kastrax with minimal modifications - Don't offer a managed Kastrax service where customers are primarily paying to use Kastrax's features ### Questions About Licensing? If you have specific questions about how the Elastic License 2.0 applies to your use case, please [contact us](https://discord.gg/BTYqqHKUrf) on Discord for clarification. We're committed to supporting legitimate business use cases while protecting the sustainability of the project. --- title: KastraX Core Class | Kastrax Docs description: Documentation for the KastraX class, the core entry point for managing agents, tools, and other components in the Kastrax framework. --- # KastraX Core Class ✅ [EN] Source: https://kastrax.ai/en/docs/core/kastrax-class-kotlin The `KastraX` class is the central entry point for the Kastrax framework, providing a unified interface for managing agents, tools, and other components. This guide explains how to use the KastraX class to build AI agent applications. ## Overview ✅ The `KastraX` class serves as a container and manager for various components: - **Agents**: AI agents with different capabilities - **Tools**: Functions that agents can use to interact with external systems - **Memory**: Systems for storing and retrieving conversation history - **RAG**: Retrieval-Augmented Generation components ## Basic Usage ✅ Here's a simple example of creating and using the KastraX class: ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a KastraX instance val kastraxInstance = kastrax { // Add an agent agent("assistant") { name("Assistant") description("A helpful assistant") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } } // Get the agent val assistant = kastraxInstance.getAgent("assistant") // Use the agent val response = assistant.generate("Hello, how can you help me?") println(response.text) } ``` ## Creating a KastraX Instance ✅ You can create a KastraX instance using the DSL function: ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val kastraxInstance = kastrax { // Configure agents agent("assistant") { name("Assistant") description("A helpful assistant") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } agent("researcher") { name("Researcher") description("A research specialist") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.5) } } } } ``` ## Managing Agents ✅ The KastraX class provides methods for managing agents: ```kotlin // Get an agent by ID val assistant = kastraxInstance.getAgent("assistant") // Get all agents val allAgents = kastraxInstance.getAgents() // Check if an agent exists if (kastraxInstance.hasAgent("assistant")) { // Use the agent } ``` ## Advanced Configuration ✅ You can configure the KastraX instance with additional components: ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create shared tools val calculatorTool = tool { id = "calculator" name = "Calculator" description = "Performs basic arithmetic operations" // Tool configuration... } // Create KastraX instance with shared components val kastraxInstance = kastrax { // Configure global tools globalTool(calculatorTool) // Configure agents agent("assistant") { name("Assistant") description("A helpful assistant") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Use global tools useGlobalTools(true) } } } ``` ## Integration with Other Systems ✅ The KastraX class can be integrated with other systems: ### Actor System Integration ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.actor.actorSystem import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an actor system val actorSystem = actorSystem("kastrax-system") // Create KastraX instance with actor system val kastraxInstance = kastrax { // Configure actor system actorSystem(actorSystem) // Configure agents agent("assistant") { name("Assistant") description("A helpful assistant") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } } // Create actor-based agents val agentActor = kastraxInstance.createActorAgent("assistant") // Send messages to the agent actor actorSystem.root.send(agentActor, "Hello, how can you help me?") } ``` ### RAG System Integration ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.rag.RAG import ai.kastrax.rag.document.DocumentLoader import ai.kastrax.rag.embedding.FastEmbeddingService import ai.kastrax.rag.vectorstore.InMemoryVectorStore import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create RAG components val embeddingService = FastEmbeddingService.create() val vectorStore = InMemoryVectorStore() val rag = RAG(vectorStore, embeddingService) // Load documents val loader = DocumentLoader.fromFile("knowledge_base.pdf") val documents = loader.load() rag.addDocuments(documents) // Create KastraX instance with RAG val kastraxInstance = kastrax { // Configure RAG ragSystem(rag) // Configure agents agent("assistant") { name("Assistant") description("A helpful assistant with knowledge access") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Use RAG useRag(true) } } // Use the agent with RAG val assistant = kastraxInstance.getAgent("assistant") val response = assistant.generate("What does the knowledge base say about climate change?") println(response.text) } ``` ## Best Practices ✅ When using the KastraX class, follow these best practices: 1. **Centralized Configuration**: Use the KastraX class as a central configuration point for your application 2. **Resource Management**: Properly close resources when they're no longer needed 3. **Error Handling**: Implement proper error handling for agent interactions 4. **Shared Components**: Use shared components (tools, memory) across agents when appropriate 5. **Modular Design**: Design your application in a modular way, with each agent having clear responsibilities ## Conclusion ✅ The KastraX class provides a powerful and flexible way to manage AI agents and related components in your application. By centralizing configuration and management, it simplifies the development of complex AI agent systems. ## Next Steps ✅ - Learn about [Agent Creation](../agents/creating-agents-kotlin.mdx) - Explore [Tool Integration](../tools/overview-kotlin.mdx) - Understand [Memory Systems](../memory/overview-kotlin.mdx) - Discover [RAG Integration](../rag/overview.mdx) --- title: "Kastrax Client SDK" description: "Learn how to set up and use the Kastrax Client SDK for interacting with Kastrax AI Agent servers" --- # Kastrax Client SDK ✅ [EN] Source: https://kastrax.ai/en/docs/deployment/client The Kastrax Client SDK provides a simple and type-safe interface for interacting with your [Kastrax Server](/docs/deployment/server) from various client environments. The SDK is available for multiple platforms, including JVM (Kotlin/Java), Android, iOS, and JavaScript. ## Development Requirements ✅ To ensure smooth development, make sure you have: ### For JVM/Kotlin Client - JDK 11 or later installed (JDK 17 recommended) - Gradle 7.0+ or Maven 3.6+ - Your Kastrax server running (typically on port 8080) ### For Android Client - Android Studio Arctic Fox (2020.3.1) or later - Android SDK 21+ (Android 5.0 Lollipop or later) - Kotlin 1.6+ ### For JavaScript/TypeScript Client - Node.js 18.x or later installed - TypeScript 4.7+ (if using TypeScript) - A modern browser environment with Fetch API support ## Installation ✅ import { Tabs } from "nextra/components"; ```kotlin // Add the Kastrax repository repositories { mavenCentral() maven { url = uri("https://repo.kastrax.ai/repository/maven-public/") } } // Add the Kastrax client dependency dependencies { implementation("ai.kastrax:kastrax-client:1.0.0") } ``` ```xml kastrax-repo https://repo.kastrax.ai/repository/maven-public/ ai.kastrax kastrax-client 1.0.0 ``` ```kotlin // Add the Kastrax repository repositories { mavenCentral() maven { url = uri("https://repo.kastrax.ai/repository/maven-public/") } } // Add the Kastrax Android client dependency dependencies { implementation("ai.kastrax:kastrax-client-android:1.0.0") } ``` ```bash # npm npm install @kastrax/client-js@latest # yarn yarn add @kastrax/client-js@latest # pnpm pnpm add @kastrax/client-js@latest ``` ## Initialize Kastrax Client ✅ ```kotlin import ai.kastrax.client.KastraxClient // Create a client with default configuration val client = KastraxClient("http://localhost:8080") // Or with custom configuration val clientWithConfig = KastraxClient( baseUrl = "http://localhost:8080", apiKey = "your-api-key", // Optional: for authenticated servers timeout = 30000, // Connection timeout in milliseconds retries = 3 // Number of retry attempts ) ``` ```java import ai.kastrax.client.KastraxClient; import ai.kastrax.client.KastraxClientConfig; // Create a client with default configuration KastraxClient client = new KastraxClient("http://localhost:8080"); // Or with custom configuration KastraxClientConfig config = new KastraxClientConfig.Builder() .apiKey("your-api-key") // Optional: for authenticated servers .timeout(30000) // Connection timeout in milliseconds .retries(3) // Number of retry attempts .build(); KastraxClient clientWithConfig = new KastraxClient("http://localhost:8080", config); ``` ```typescript import { KastraxClient } from "@kastrax/client-js"; // Create a client with default configuration const client = new KastraxClient({ baseUrl: "http://localhost:8080" }); // Or with custom configuration const clientWithConfig = new KastraxClient({ baseUrl: "http://localhost:8080", apiKey: "your-api-key", // Optional: for authenticated servers retries: 3, // Number of retry attempts timeout: 30000, // Connection timeout in milliseconds headers: { // Custom headers "X-Custom-Header": "value" } }); ``` ### Configuration Options The Kastrax Client SDK supports the following configuration options: | Option | Description | Default | |--------|-------------|---------| | `baseUrl` | The base URL of the Kastrax server | Required | | `apiKey` | API key for authentication | `null` | | `timeout` | Connection timeout in milliseconds | `30000` | | `retries` | Number of retry attempts for failed requests | `3` | | `retryDelay` | Initial delay between retries in milliseconds | `300` | | `maxRetryDelay` | Maximum delay between retries in milliseconds | `5000` | | `headers` | Custom HTTP headers to include in all requests | `{}` | ## Examples ✅ Once your KastraxClient is initialized, you can start making client calls via the type-safe interface. ```kotlin import ai.kastrax.client.KastraxClient import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Initialize the client val client = KastraxClient("http://localhost:8080") // Get a reference to your agent val agent = client.getAgent("assistant") // Generate a response val response = agent.generate("Hello, I'm testing the Kastrax client!") // Print the response println("Agent response: ${response.text}") // You can also use the streaming API agent.generateStream("Tell me a story about AI").collect { chunk -> print(chunk.text) } } ``` ```java import ai.kastrax.client.KastraxClient; import ai.kastrax.client.agent.Agent; import ai.kastrax.client.agent.GenerateResponse; public class JavaExample { public static void main(String[] args) throws Exception { // Initialize the client KastraxClient client = new KastraxClient("http://localhost:8080"); // Get a reference to your agent Agent agent = client.getAgent("assistant"); // Generate a response GenerateResponse response = agent.generate("Hello, I'm testing the Kastrax client!"); // Print the response System.out.println("Agent response: " + response.getText()); // You can also use the streaming API with a callback agent.generateStream( "Tell me a story about AI", chunk -> System.out.print(chunk.getText()) ); } } ``` ```typescript import { KastraxClient } from "@kastrax/client-js"; async function main() { // Initialize the client const client = new KastraxClient({ baseUrl: "http://localhost:8080" }); // Get a reference to your agent const agent = client.getAgent("assistant"); // Generate a response const response = await agent.generate({ messages: [ { role: "user", content: "Hello, I'm testing the Kastrax client!" } ] }); // Print the response console.log("Agent response:", response.text); // You can also use the streaming API const stream = await agent.generateStream({ messages: [ { role: "user", content: "Tell me a story about AI" } ] }); for await (const chunk of stream) { process.stdout.write(chunk.text); } } main().catch(console.error); ``` ## Available Features ✅ The Kastrax Client SDK exposes all resources served by the Kastrax Server: - **Agents**: Create and manage AI agents, generate responses, and handle streaming interactions - **Memory**: Manage conversation threads and message history - **Tools**: Access and execute tools available to agents - **Workflows**: Create and manage automated workflows - **Vectors**: Handle vector operations for semantic search and similarity matching - **Files**: Upload, download, and manage files - **Monitoring**: Access logs, metrics, and traces ### Agent Operations ```kotlin // Get an agent by ID val agent = client.getAgent("assistant") // List all available agents val agents = client.listAgents() // Generate a response val response = agent.generate("What is Kastrax?") // Generate a response with options val responseWithOptions = agent.generate( prompt = "What is Kastrax?", options = GenerateOptions( temperature = 0.7, maxTokens = 500, tools = listOf("web-search", "calculator") ) ) // Stream a response agent.generateStream("Tell me a story").collect { chunk -> print(chunk.text) } ``` ```java // Get an agent by ID Agent agent = client.getAgent("assistant"); // List all available agents List agents = client.listAgents(); // Generate a response GenerateResponse response = agent.generate("What is Kastrax?"); // Generate a response with options GenerateOptions options = new GenerateOptions.Builder() .temperature(0.7) .maxTokens(500) .tools(Arrays.asList("web-search", "calculator")) .build(); GenerateResponse responseWithOptions = agent.generate("What is Kastrax?", options); // Stream a response with a callback agent.generateStream( "Tell me a story", chunk -> System.out.print(chunk.getText()) ); ``` ```typescript // Get an agent by ID const agent = client.getAgent("assistant"); // List all available agents const agents = await client.listAgents(); // Generate a response const response = await agent.generate({ messages: [{ role: "user", content: "What is Kastrax?" }] }); // Generate a response with options const responseWithOptions = await agent.generate({ messages: [{ role: "user", content: "What is Kastrax?" }], temperature: 0.7, maxTokens: 500, tools: ["web-search", "calculator"] }); // Stream a response const stream = await agent.generateStream({ messages: [{ role: "user", content: "Tell me a story" }] }); for await (const chunk of stream) { process.stdout.write(chunk.text); } ``` ## Best Practices ✅ ### Error Handling Implement proper error handling to gracefully handle API errors and network issues: ```kotlin import ai.kastrax.client.KastraxClient import ai.kastrax.client.exception.KastraxApiException import ai.kastrax.client.exception.KastraxNetworkException import kotlinx.coroutines.runBlocking fun main() = runBlocking { val client = KastraxClient("http://localhost:8080") try { val agent = client.getAgent("assistant") val response = agent.generate("Test message") println("Response: ${response.text}") } catch (e: KastraxApiException) { // Handle API errors (e.g., invalid request, authentication issues) println("API Error: ${e.statusCode} - ${e.message}") } catch (e: KastraxNetworkException) { // Handle network issues (e.g., connection timeout, server unreachable) println("Network Error: ${e.message}") } catch (e: Exception) { // Handle other unexpected errors println("Unexpected Error: ${e.message}") } } ``` ```java import ai.kastrax.client.KastraxClient; import ai.kastrax.client.agent.Agent; import ai.kastrax.client.exception.KastraxApiException; import ai.kastrax.client.exception.KastraxNetworkException; public class ErrorHandling { public static void main(String[] args) { KastraxClient client = new KastraxClient("http://localhost:8080"); try { Agent agent = client.getAgent("assistant"); var response = agent.generate("Test message"); System.out.println("Response: " + response.getText()); } catch (KastraxApiException e) { // Handle API errors (e.g., invalid request, authentication issues) System.out.println("API Error: " + e.getStatusCode() + " - " + e.getMessage()); } catch (KastraxNetworkException e) { // Handle network issues (e.g., connection timeout, server unreachable) System.out.println("Network Error: " + e.getMessage()); } catch (Exception e) { // Handle other unexpected errors System.out.println("Unexpected Error: " + e.getMessage()); } } } ``` ```typescript import { KastraxClient, KastraxApiError, KastraxNetworkError } from "@kastrax/client-js"; async function main() { const client = new KastraxClient({ baseUrl: "http://localhost:8080" }); try { const agent = client.getAgent("assistant"); const response = await agent.generate({ messages: [{ role: "user", content: "Test message" }] }); console.log("Response:", response.text); } catch (error) { if (error instanceof KastraxApiError) { // Handle API errors (e.g., invalid request, authentication issues) console.error(`API Error: ${error.statusCode} - ${error.message}`); } else if (error instanceof KastraxNetworkError) { // Handle network issues (e.g., connection timeout, server unreachable) console.error(`Network Error: ${error.message}`); } else { // Handle other unexpected errors console.error(`Unexpected Error: ${error.message}`); } } } main().catch(console.error); ``` ### Configuration Management Use environment variables or configuration files to manage client settings: ```kotlin import ai.kastrax.client.KastraxClient // Load configuration from environment variables val baseUrl = System.getenv("KASTRAX_API_URL") ?: "http://localhost:8080" val apiKey = System.getenv("KASTRAX_API_KEY") // Create client with environment-based configuration val client = KastraxClient( baseUrl = baseUrl, apiKey = apiKey ) ``` ```java import ai.kastrax.client.KastraxClient; import ai.kastrax.client.KastraxClientConfig; public class ConfigManagement { public static void main(String[] args) { // Load configuration from environment variables String baseUrl = System.getenv("KASTRAX_API_URL"); if (baseUrl == null) baseUrl = "http://localhost:8080"; String apiKey = System.getenv("KASTRAX_API_KEY"); // Create client with environment-based configuration KastraxClientConfig config = new KastraxClientConfig.Builder() .apiKey(apiKey) .build(); KastraxClient client = new KastraxClient(baseUrl, config); } } ``` ```typescript import { KastraxClient } from "@kastrax/client-js"; // Load configuration from environment variables const baseUrl = process.env.KASTRAX_API_URL || "http://localhost:8080"; const apiKey = process.env.KASTRAX_API_KEY; // Create client with environment-based configuration const client = new KastraxClient({ baseUrl, apiKey }); ``` ### Performance Optimization 1. **Reuse Client Instances**: Create a single client instance and reuse it throughout your application 2. **Connection Pooling**: The client automatically manages connection pooling for optimal performance 3. **Streaming for Large Responses**: Use streaming for large responses to reduce memory usage 4. **Batch Operations**: Use batch operations when processing multiple items 5. **Caching**: Implement caching for frequently accessed data ### Security Best Practices 1. **API Key Management**: Store API keys securely and never hardcode them in your application 2. **HTTPS**: Always use HTTPS in production environments 3. **Request Validation**: Validate user input before sending it to the server 4. **Response Validation**: Validate server responses before processing them 5. **Rate Limiting**: Implement client-side rate limiting to avoid overwhelming the server --- title: "Deploying Kastrax Applications" description: "Learn how to deploy Kastrax AI Agent applications to various environments including cloud platforms, Kubernetes, and standalone servers" --- # Deploying Kastrax Applications ✅ [EN] Source: https://kastrax.ai/en/docs/deployment/deployment This guide covers various deployment options for Kastrax applications, from cloud platforms to self-hosted solutions. Kastrax supports multiple deployment models to fit your specific requirements and infrastructure. For detailed information on setting up a self-hosted Kastrax server, see the [Creating A Kastrax Server](/docs/deployment/server) guide. ## Prerequisites ✅ Before you begin, ensure you have: - **JDK 11 or higher** installed (JDK 17 is recommended) - **Gradle 7.0+** for building your application - If using a cloud platform: - An account with your chosen platform - Required API keys or credentials ## Deployment Options ✅ Kastrax supports multiple deployment options to fit different requirements: - **[Kastrax Cloud](#kastrax-cloud)**: Managed serverless platform for AI agents - **[Docker Containers](#docker-deployment)**: Containerized deployment for any environment - **[Kubernetes](#kubernetes-deployment)**: Scalable, orchestrated deployment - **[Standalone JVM](#standalone-jvm-deployment)**: Traditional Java application deployment - **[GraalVM Native Image](#graalvm-native-image)**: Compiled native executables for maximum performance As of April 2025, Kastrax offers [Kastrax Cloud](https://kastrax.ai/cloud-beta), a managed service specifically designed for AI agent deployments. You can sign up for the waitlist [here](https://kastrax.ai/cloud-beta). ## Kastrax Cloud ✅ Kastrax Cloud is a fully managed platform for deploying AI agents with: - Automatic scaling - Built-in monitoring and observability - Secure API key management - Version control and rollback capabilities To deploy to Kastrax Cloud: ```bash # Install the Kastrax CLI brew install kastrax/tap/kastrax-cli # Login to Kastrax Cloud kastrax cloud login # Deploy your application kastrax cloud deploy ``` For more details, see the [Kastrax Cloud documentation](/docs/kastrax-cloud/overview). ## Docker Deployment ✅ Kastrax applications can be easily containerized using Docker. Here's a sample `Dockerfile`: ```dockerfile filename="Dockerfile" FROM gradle:7.6.1-jdk17 AS build WORKDIR /app COPY . . RUN gradle build --no-daemon FROM openjdk:17-slim WORKDIR /app COPY --from=build /app/build/libs/*.jar app.jar EXPOSE 8080 CMD ["java", "-jar", "app.jar"] ``` Build and run your Docker container: ```bash # Build the Docker image docker build -t kastrax-app . # Run the container docker run -p 8080:8080 \ -e OPENAI_API_KEY=your-api-key \ -e KASTRAX_SERVER_PORT=8080 \ kastrax-app ``` ## Kubernetes Deployment ✅ For production environments, Kubernetes provides scalability and resilience. Here's a basic Kubernetes deployment manifest: ```yaml filename="kastrax-deployment.yaml" apiVersion: apps/v1 kind: Deployment metadata: name: kastrax-agent labels: app: kastrax-agent spec: replicas: 2 selector: matchLabels: app: kastrax-agent template: metadata: labels: app: kastrax-agent spec: containers: - name: kastrax-agent image: your-registry/kastrax-app:latest ports: - containerPort: 8080 env: - name: OPENAI_API_KEY valueFrom: secretKeyRef: name: kastrax-secrets key: openai-api-key - name: KASTRAX_SERVER_PORT value: "8080" resources: limits: cpu: "1" memory: "1Gi" requests: cpu: "500m" memory: "512Mi" --- apiVersion: v1 kind: Service metadata: name: kastrax-agent-service spec: selector: app: kastrax-agent ports: - port: 80 targetPort: 8080 type: LoadBalancer ``` Apply the deployment to your Kubernetes cluster: ```bash kubectl apply -f kastrax-deployment.yaml ``` ## Standalone JVM Deployment ✅ Kastrax applications can be deployed as standalone JVM applications on any server that supports Java: ```kotlin filename="Application.kt" import ai.kastrax.core.kastrax import ai.kastrax.server.server fun main() { // Create a Kastrax instance with server configuration val kastraxInstance = kastrax { // Configure server server { port = 8080 host = "0.0.0.0" enableSwagger = true } // Configure agents, tools, etc. agent("assistant") { // Agent configuration } } // Start the server kastraxInstance.startServer() } ``` Build your application as a fat JAR using Gradle: ```bash ./gradlew shadowJar ``` Deploy and run the JAR on your server: ```bash java -jar build/libs/your-app-all.jar ``` ## GraalVM Native Image ✅ For maximum performance and minimal startup time, you can compile your Kastrax application to a native executable using GraalVM: 1. Add GraalVM Native Image support to your `build.gradle.kts`: ```kotlin filename="build.gradle.kts" plugins { kotlin("jvm") version "1.9.20" id("org.graalvm.buildtools.native") version "0.9.28" id("com.github.johnrengelman.shadow") version "8.1.1" } graalvmNative { binaries { named("main") { imageName.set("kastrax-app") mainClass.set("com.example.ApplicationKt") buildArgs.add("--no-fallback") buildArgs.add("-H:+ReportExceptionStackTraces") } } } ``` 2. Build the native image: ```bash ./gradlew nativeCompile ``` 3. Deploy and run the native executable: ```bash ./build/native/nativeCompile/kastrax-app ``` ## Environment Variables ✅ Kastrax applications use the following environment variables: 1. LLM provider API keys: - `OPENAI_API_KEY`: For OpenAI integration - `DEEPSEEK_API_KEY`: For DeepSeek integration - `ANTHROPIC_API_KEY`: For Anthropic integration 2. Server configuration: - `KASTRAX_SERVER_PORT`: HTTP server port (default: 8080) - `KASTRAX_SERVER_HOST`: Server host (default: 0.0.0.0) - `KASTRAX_LOG_LEVEL`: Logging level (default: INFO) 3. Vector database configuration (for RAG applications): - `PINECONE_API_KEY`: For Pinecone integration - `PGVECTOR_CONNECTION_STRING`: For PostgreSQL/pgvector integration ## Building for Deployment ✅ To build your Kastrax project for deployment: ```bash # For JAR deployment ./gradlew shadowJar # For native image deployment ./gradlew nativeCompile # For Docker deployment docker build -t kastrax-app . ``` ## Deployment Checklist ✅ Before deploying to production, ensure you have: 1. **Secured API Keys**: Store API keys securely using environment variables or secrets management 2. **Configured Logging**: Set up appropriate logging levels and destinations 3. **Set Resource Limits**: Configure memory and CPU limits for your application 4. **Enabled Monitoring**: Set up health checks and performance monitoring 5. **Tested Thoroughly**: Verify all functionality in a staging environment 6. **Documented Endpoints**: Document all API endpoints for consumers 7. **Implemented Rate Limiting**: Protect your APIs from abuse 8. **Set Up Backup Strategy**: Ensure data persistence and recovery options ## Next Steps ✅ - Learn about [Monitoring and Observability](/docs/deployment/monitoring) - Explore [Kastrax Cloud](/docs/kastrax-cloud/overview) for managed deployments - Set up [CI/CD Pipelines](/docs/deployment/ci-cd) for automated deployments --- title: Deployment Overview description: Learn about different deployment options for your Kastrax applications --- # Deployment Overview [EN] Source: https://kastrax.ai/en/docs/deployment/overview Kastrax offers multiple deployment options to suit your application's needs, from fully-managed solutions to self-hosted options. This guide will help you understand the available deployment paths and choose the right one for your project. ## Deployment Options ### Kastrax Cloud Kastrax Cloud is a deployment platform that connects to your GitHub repository, automatically deploys on code changes, and provides monitoring tools. It includes: - GitHub repository integration - Deployment on git push - Agent testing interface - Comprehensive logs and traces - Custom domains for each project [View Kastrax Cloud documentation →](/docs/kastrax-cloud/overview) ### With a Server You can deploy Kastrax as a standard Node.js HTTP server, which gives you full control over your infrastructure and deployment environment. - Custom API routes and middleware - Configurable CORS and authentication - Deploy to VMs, containers, or PaaS platforms - Ideal for integrating with existing Node.js applications [Server deployment guide →](/docs/deployment/server) ### Serverless Platforms Kastrax provides platform-specific deployers for popular serverless platforms, enabling you to deploy your application with minimal configuration. - Deploy to Cloudflare Workers, Vercel, or Netlify - Platform-specific optimizations - Simplified deployment process - Automatic scaling through the platform [Serverless deployment guide →](/docs/deployment/deployment) ## Client Configuration Once your Kastrax application is deployed, you'll need to configure your client to communicate with it. The Kastrax Client SDK provides a simple and type-safe interface for interacting with your Kastrax server. - Type-safe API interactions - Authentication and request handling - Retries and error handling - Support for streaming responses [Client configuration guide →](/docs/deployment/client) ## Choosing a Deployment Option | Option | Best For | Key Benefits | | --- | --- | --- | | **Kastrax Cloud** | Teams wanting to ship quickly without infrastructure concerns | Fully-managed, automatic scaling, built-in observability | | **Server Deployment** | Teams needing maximum control and customization | Full control, custom middleware, integrate with existing apps | | **Serverless Platforms** | Teams already using Vercel, Netlify, or Cloudflare | Platform integration, simplified deployment, automatic scaling | --- title: "Creating A Kastrax Server" description: "Configure and customize the Kastrax server with routing, middleware, and other options for your AI agent applications" --- # Creating A Kastrax Server ✅ [EN] Source: https://kastrax.ai/en/docs/deployment/server When developing or deploying a Kastrax application, it runs as an HTTP server that exposes your agents, workflows, and other functionality as API endpoints. This page explains how to configure and customize the server behavior. ## Server Architecture ✅ Kastrax uses [Ktor](https://ktor.io) as its underlying HTTP server framework. Ktor is a lightweight, high-performance framework specifically designed for Kotlin applications. When you build a Kastrax application, it configures a Ktor-based HTTP server with all the necessary components. The server provides: - REST API endpoints for all registered agents - API endpoints for all registered workflows - WebSocket support for real-time agent interactions - Custom route configuration - Flexible middleware pipeline - Comprehensive configuration options - Authentication and authorization support - CORS configuration ## Server Configuration ✅ You can configure the server using the Kastrax DSL. The server configuration is specified within the `server` block of your Kastrax instance: ```kotlin filename="ServerConfig.kt" showLineNumbers import ai.kastrax.core.kastrax import ai.kastrax.server.server // Create a Kastrax instance with server configuration val kastraxInstance = kastrax { // Configure server server { port = 8080 // Default: 8080 host = "0.0.0.0" // Default: 0.0.0.0 callTimeout = 30000 // Default: 30000 (30s) requestTimeout = 60000 // Default: 60000 (60s) enableSwagger = true // Default: false enableOpenAPI = true // Default: false enableCompression = true // Default: true enableMetrics = true // Default: false enableTracing = true // Default: false } // Other Kastrax configuration... } ``` You can also configure the server using environment variables: ```properties filename="application.conf" kastrax { server { port = ${?KASTRAX_SERVER_PORT} host = ${?KASTRAX_SERVER_HOST} callTimeout = ${?KASTRAX_SERVER_CALL_TIMEOUT} requestTimeout = ${?KASTRAX_SERVER_REQUEST_TIMEOUT} } } ``` ## Custom API Routes ✅ Kastrax automatically generates API routes for all registered agents and workflows. You can also define custom routes to extend the functionality of your server. Custom routes can be defined within the `server` block using the `routing` function: ```kotlin filename="CustomRoutes.kt" showLineNumbers import ai.kastrax.core.kastrax import ai.kastrax.server.server import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* val kastraxInstance = kastrax { server { // Configure server settings port = 8080 // Define custom routes routing { // Simple GET endpoint get("/api/status") { call.respond(HttpStatusCode.OK, mapOf("status" to "running")) } // Route with path parameters get("/api/agents/{agentId}/info") { val agentId = call.parameters["agentId"] val agent = kastraxInstance.getAgent(agentId ?: "") if (agent != null) { call.respond(HttpStatusCode.OK, mapOf( "name" to agent.name, "description" to agent.description )) } else { call.respond(HttpStatusCode.NotFound, mapOf("error" to "Agent not found")) } } // POST endpoint with request body post("/api/custom-generate") { val request = call.receive() val agent = kastraxInstance.getAgent(request.agentId) if (agent != null) { val response = agent.generate(request.prompt) call.respond(HttpStatusCode.OK, response) } else { call.respond(HttpStatusCode.NotFound, mapOf("error" to "Agent not found")) } } } } // Other Kastrax configuration... } // Request data class data class CustomGenerateRequest( val agentId: String, val prompt: String ) ``` You can also define routes in a separate file and include them in your server configuration: ```kotlin filename="Routes.kt" showLineNumbers import ai.kastrax.core.KastraX import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* // Define routes in a separate function fun Application.configureRoutes(kastraxInstance: KastraX) { routing { get("/api/custom/health") { call.respond(mapOf("status" to "healthy")) } // More routes... } } ``` Then include them in your server configuration: ```kotlin filename="ServerConfig.kt" showLineNumbers import ai.kastrax.core.kastrax import ai.kastrax.server.server val kastraxInstance = kastrax { server { // Include custom routes install { application -> application.configureRoutes(kastraxInstance) } } } ``` ## CORS Configuration ✅ Kastrax allows you to configure CORS (Cross-Origin Resource Sharing) settings for your server. This is important when your frontend application is hosted on a different domain than your API server. ```kotlin filename="CorsConfig.kt" showLineNumbers import ai.kastrax.core.kastrax import ai.kastrax.server.server import io.ktor.http.* val kastraxInstance = kastrax { server { // Configure CORS cors { allowHost("https://example.com") allowHost("https://app.example.com") allowMethod(HttpMethod.Get) allowMethod(HttpMethod.Post) allowMethod(HttpMethod.Options) allowHeader(HttpHeaders.ContentType) allowHeader(HttpHeaders.Authorization) exposeHeader(HttpHeaders.ContentLength) maxAgeInSeconds = 3600 allowCredentials = true } } // Other Kastrax configuration... } ``` ## Middleware Configuration ✅ Kastrax allows you to add custom middleware to the server pipeline. Middleware functions are executed in the order they are defined and can perform tasks like logging, authentication, and request transformation. ```kotlin filename="MiddlewareConfig.kt" showLineNumbers import ai.kastrax.core.kastrax import ai.kastrax.server.server import io.ktor.server.application.* import io.ktor.server.plugins.callloging.* import io.ktor.server.plugins.compression.* import io.ktor.server.plugins.defaultheaders.* import io.ktor.server.plugins.ratelimit.* import org.slf4j.event.Level val kastraxInstance = kastrax { server { // Install middleware plugins install { application -> // Add default headers application.install(DefaultHeaders) { header("X-Engine", "Kastrax") header("X-Version", "1.0.0") } // Add call logging application.install(CallLogging) { level = Level.INFO filter { call -> call.request.path().startsWith("/api") } } // Add compression application.install(Compression) { gzip() deflate() } // Add rate limiting application.install(RateLimit) { global { rateLimiter(limit = 100, refillPeriod = 60000) } register(RateLimitName("critical-endpoints")) { rateLimiter(limit = 10, refillPeriod = 60000) } } // Custom authentication middleware application.intercept(ApplicationCallPipeline.Plugins) { val authHeader = call.request.headers["Authorization"] if (call.request.path().startsWith("/api/protected") && (authHeader == null || !authHeader.startsWith("Bearer "))) { throw UnauthorizedException("Invalid or missing authentication token") } } } } // Other Kastrax configuration... } // Custom exception class UnauthorizedException(message: String) : RuntimeException(message) ``` if you want to add a middleware to a single route, you can also specify that in the registerApiRoute using `registerApiRoute`. ```typescript copy showLineNumbers registerApiRoute("/my-custom-route", { method: "GET", middleware: [ async (c, next) => { // Example: Add request logging console.log(`${c.req.method} ${c.req.url}`); await next(); }, ], handler: async (c) => { // you have access to kastrax instance here const kastrax = c.get("kastrax"); // you can use the kastrax instance to get agents, workflows, etc. const agents = await kastrax.getAgent("my-agent"); return c.json({ message: "Hello, world!" }); }, }); ``` ### Middleware Behavior Each middleware function: - Receives a Hono context object (`c`) and a `next` function - Can return a `Response` to short-circuit the request handling - Can call `next()` to continue to the next middleware or route handler - Can optionally specify a path pattern (defaults to '/api/\*') - Inject request specific data for agent tool calling or workflows ## Authentication ✅ Kastrax supports various authentication methods through Ktor's authentication features: ```kotlin filename="AuthConfig.kt" showLineNumbers import ai.kastrax.core.kastrax import ai.kastrax.server.server import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.auth.jwt.* val kastraxInstance = kastrax { server { // Configure authentication auth { // Basic authentication basic("auth-basic") { realm = "Kastrax Server" validate { credentials -> if (credentials.name == "admin" && credentials.password == "password") { UserIdPrincipal(credentials.name) } else { null } } } // JWT authentication jwt("auth-jwt") { realm = "Kastrax Server" verifier(JwtConfig.verifier) validate { credential -> if (credential.payload.getClaim("username").asString() != "") { JWTPrincipal(credential.payload) } else { null } } } } } // Other Kastrax configuration... } ``` #### CORS Support ```typescript copy { handler: async (c, next) => { // Add CORS headers c.header("Access-Control-Allow-Origin", "*"); c.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); c.header("Access-Control-Allow-Headers", "Content-Type, Authorization"); // Handle preflight requests if (c.req.method === "OPTIONS") { return new Response(null, { status: 204 }); } await next(); }; } ``` #### Request Logging ```typescript copy { handler: async (c, next) => { const start = Date.now(); await next(); const duration = Date.now() - start; console.log(`${c.req.method} ${c.req.url} - ${duration}ms`); }; } ``` ### Special Kastrax Headers When integrating with Kastrax Cloud or building custom clients, there are special headers that clients send to identify themselves and enable specific features. Your server middleware can check for these headers to customize behavior: ```typescript copy { handler: async (c, next) => { // Check for Kastrax-specific headers in incoming requests const isFromKastraxCloud = c.req.header("x-kastrax-cloud") === "true"; const clientType = c.req.header("x-kastrax-client-type"); // e.g., 'js', 'python' const isDevPlayground = c.req.header("x-kastrax-dev-playground") === "true"; // Customize behavior based on client information if (isFromKastraxCloud) { // Special handling for Kastrax Cloud requests } await next(); }; } ``` These headers have the following purposes: - `x-kastrax-cloud`: Indicates that the request is coming from Kastrax Cloud - `x-kastrax-client-type`: Specifies the client SDK type (e.g., 'js', 'python') - `x-kastrax-dev-playground`: Indicates that the request is from the development playground You can use these headers in your middleware to implement client-specific logic or enable features only for certain environments. ## Deployment ✅ Since Kastrax builds to a standard Node.js server, you can deploy to any platform that runs Node.js applications: - Cloud VMs (AWS EC2, DigitalOcean Droplets, GCP Compute Engine) - Container platforms (Docker, Kubernetes) - Platform as a Service (Heroku, Railway) - Self-hosted servers ### Building Build the application: ```bash copy # Build from current directory ✅ kastrax build # Or specify a directory ✅ kastrax build --dir ./my-project ``` The build process: 1. Locates entry file (`src/kastrax/index.ts` or `src/kastrax/index.js`) 2. Creates `.kastrax` output directory 3. Bundles code using Rollup with tree shaking and source maps 4. Generates [Hono](https://hono.dev) HTTP server See [`kastrax build`](/reference/cli/build) for all options. ## Starting the Server ✅ To start the server, you can use the `kastrax dev` command during development or deploy your application using one of the deployment methods described in the [Deployment](/docs/deployment/deployment) guide. ```kotlin filename="StartServer.kt" showLineNumbers import ai.kastrax.core.kastrax import ai.kastrax.server.server fun main() { // Create and configure Kastrax instance val kastraxInstance = kastrax { server { port = 8080 host = "0.0.0.0" } // Configure agents, tools, etc. agent("assistant") { // Agent configuration } } // Start the server kastraxInstance.startServer() // The server is now running and will block the current thread // until the application is stopped } ``` During development, you can use the `kastrax dev` command, which will automatically restart the server when you make changes to your code. ## Deployment Options ✅ Kastrax supports multiple deployment options: 1. **Standalone JVM Application**: Deploy as a traditional Java application on any server 2. **Docker Container**: Package your application as a Docker container 3. **Kubernetes**: Deploy to a Kubernetes cluster for scalability and resilience 4. **GraalVM Native Image**: Compile to a native executable for maximum performance 5. **Kastrax Cloud**: Use the managed Kastrax Cloud service See [Deployment Options](/docs/deployment/deployment) for detailed instructions on each deployment method. --- title: "Create your own Eval" description: "Kastrax allows so create your own evals, here is how." --- # Create your own Eval [EN] Source: https://kastrax.ai/en/docs/evals/custom-eval Creating your own eval is as easy as creating a new function. You simply create a class that extends the `Metric` class and implement the `measure` method. ## Basic example For a simple example of creating a custom metric that checks if the output contains certain words, see our [Word Inclusion example](/examples/evals/word-inclusion). ## Creating a custom LLM-Judge A custom LLM judge helps evaluate specific aspects of your AI's responses. Think of it like having an expert reviewer for your particular use case: - Medical Q&A → Judge checks for medical accuracy and safety - Customer Service → Judge evaluates tone and helpfulness - Code Generation → Judge verifies code correctness and style For a practical example, see how we evaluate [Chef Michel's](/docs/guides/chef-michel) recipes for gluten content in our [Gluten Checker example](/examples/evals/custom-eval). --- title: "Overview" description: "Understanding how to evaluate and measure AI agent quality using Kastrax evals." --- # Testing your agents with evals ✅ [EN] Source: https://kastrax.ai/en/docs/evals/overview While traditional software tests have clear pass/fail conditions, AI outputs are non-deterministic — they can vary with the same input. Evals help bridge this gap by providing quantifiable metrics for measuring agent quality. Evals are automated tests that evaluate Agents outputs using model-graded, rule-based, and statistical methods. Each eval returns a normalized score between 0-1 that can be logged and compared. Evals can be customized with your own prompts and scoring functions. Evals can be run in the cloud, capturing real-time results. But evals can also be part of your CI/CD pipeline, allowing you to test and monitor your agents over time. ## Types of Evals ✅ There are different kinds of evals, each serving a specific purpose. Here are some common types: 1. **Textual Evals**: Evaluate accuracy, reliability, and context understanding of agent responses 2. **Classification Evals**: Measure accuracy in categorizing data based on predefined categories 3. **Tool Usage Evals**: Assess how effectively an agent uses external tools or APIs 4. **RAG Evals**: Evaluate retrieval accuracy and relevance in RAG systems 5. **Conversation Evals**: Measure the quality of multi-turn conversations ## Getting Started with Evals ✅ To start using evals in your Kastrax project, you'll need to add the evals module to your dependencies: ```kotlin // build.gradle.kts dependencies { implementation("ai.kastrax:kastrax-core:0.1.0") implementation("ai.kastrax:kastrax-evals:0.1.0") // Other dependencies... } ``` Here's a simple example of how to create and run an eval: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.evals.textual.FactualAccuracyEval import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent to evaluate val myAgent = agent { name("TestAgent") description("A test agent for evaluation") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // Create an eval val factualAccuracyEval = FactualAccuracyEval() // Define test cases val testCases = listOf( "What is the capital of France?", "Who wrote 'Pride and Prejudice'?", "What is the chemical formula for water?" ) // Run the eval val results = factualAccuracyEval.evaluate(myAgent, testCases) // Print results println("Factual Accuracy Score: ${results.score}") println("Individual Scores:") results.individualScores.forEachIndexed { index, score -> println(" ${testCases[index]}: $score") } } ``` ## Built-in Evals ✅ Kastrax provides several built-in evals: ### Textual Evals ✅ ```kotlin // Factual Accuracy Eval val factualAccuracyEval = FactualAccuracyEval() // Relevance Eval val relevanceEval = RelevanceEval() // Coherence Eval val coherenceEval = CoherenceEval() // Toxicity Eval val toxicityEval = ToxicityEval() // Bias Eval val biasEval = BiasEval() ``` ### Classification Evals ✅ ```kotlin // Classification Accuracy Eval val classificationAccuracyEval = ClassificationAccuracyEval( categories = listOf("Positive", "Negative", "Neutral") ) // Multi-label Classification Eval val multiLabelEval = MultiLabelClassificationEval( labels = listOf("Technology", "Science", "Politics", "Sports") ) ``` ### Tool Usage Evals ✅ ```kotlin // Tool Selection Eval val toolSelectionEval = ToolSelectionEval( availableTools = listOf("calculator", "weather", "search") ) // Tool Parameter Eval val toolParameterEval = ToolParameterEval() // Tool Execution Eval val toolExecutionEval = ToolExecutionEval() ``` ### RAG Evals ✅ ```kotlin // Retrieval Precision Eval val retrievalPrecisionEval = RetrievalPrecisionEval() // Retrieval Recall Eval val retrievalRecallEval = RetrievalRecallEval() // Answer Relevance Eval val answerRelevanceEval = AnswerRelevanceEval() // Citation Accuracy Eval val citationAccuracyEval = CitationAccuracyEval() ``` ## Creating Custom Evals ✅ You can create custom evals by implementing the `Eval` interface: ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.evals.Eval import ai.kastrax.evals.EvalResult class CustomEval : Eval { override val name: String = "CustomEval" override val description: String = "A custom evaluation metric" override suspend fun evaluate(agent: Agent, testCases: List): EvalResult { val individualScores = mutableListOf() for (testCase in testCases) { // Generate a response from the agent val response = agent.generate(testCase) // Implement your custom scoring logic val score = calculateScore(testCase, response.text) individualScores.add(score) } // Calculate the overall score (average of individual scores) val overallScore = individualScores.average() return EvalResult( score = overallScore, individualScores = individualScores, metadata = mapOf("evalName" to name) ) } private fun calculateScore(testCase: String, response: String): Double { // Implement your custom scoring logic // Return a score between 0 and 1 // Example: Simple length-based scoring (for demonstration only) return minOf(response.length / 100.0, 1.0) } } ``` ## Model-Graded Evals ✅ Model-graded evals use an LLM to evaluate agent responses: ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.evals.ModelGradedEval import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel class HelpfulnessEval : ModelGradedEval { override val name: String = "HelpfulnessEval" override val description: String = "Evaluates how helpful the agent's responses are" override val evaluationModel = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.1) // Low temperature for consistent evaluation } override val evaluationPrompt = """ You are evaluating the helpfulness of an AI assistant's response to a user query. User Query: {{query}} Assistant Response: {{response}} Rate the helpfulness of the response on a scale from 0 to 10, where: - 0: Not helpful at all, completely irrelevant or incorrect - 5: Somewhat helpful, but missing important information or not fully addressing the query - 10: Extremely helpful, fully addresses the query with accurate and comprehensive information Provide your rating as a number between 0 and 10, followed by a brief explanation. """.trimIndent() override fun parseScore(evaluationResponse: String): Double { // Extract the numerical score from the evaluation response val scoreRegex = """(\d+)""".toRegex() val matchResult = scoreRegex.find(evaluationResponse) return if (matchResult != null) { val score = matchResult.groupValues[1].toInt() score / 10.0 // Normalize to 0-1 range } else { 0.5 // Default score if parsing fails } } } ``` ## Running Evals in CI/CD ✅ You can integrate evals into your CI/CD pipeline: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.evals.EvalSuite import ai.kastrax.evals.textual.FactualAccuracyEval import ai.kastrax.evals.textual.RelevanceEval import ai.kastrax.evals.textual.CoherenceEval import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import java.io.File fun main() = runBlocking { // Load the agent val myAgent = agent { name("ProductionAgent") description("A production agent for evaluation") model = deepSeek { apiKey(System.getenv("DEEPSEEK_API_KEY")) model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // Create an eval suite val evalSuite = EvalSuite( name = "ProductionEvalSuite", evals = listOf( FactualAccuracyEval(), RelevanceEval(), CoherenceEval() ) ) // Load test cases from a file val testCases = File("test_cases.txt").readLines() // Run the eval suite val results = evalSuite.evaluate(myAgent, testCases) // Check if the results meet the threshold val threshold = 0.8 val passed = results.averageScore >= threshold if (passed) { println("Eval suite passed with score: ${results.averageScore}") System.exit(0) // Success } else { println("Eval suite failed with score: ${results.averageScore} (threshold: $threshold)") System.exit(1) // Failure } } ``` ## Best Practices ✅ 1. **Use Multiple Evals**: Different evals measure different aspects of agent quality 2. **Create Diverse Test Cases**: Include edge cases and challenging scenarios 3. **Set Realistic Thresholds**: Start with lower thresholds and gradually increase them 4. **Monitor Trends**: Track eval scores over time to identify regressions 5. **Combine with Human Evaluation**: Use evals alongside human evaluation for a complete picture ## Next Steps ✅ Now that you understand evals, you can: 1. [Create custom evals](./custom-evals.mdx) 2. [Set up eval suites](./eval-suites.mdx) 3. [Integrate evals into CI/CD](./ci-cd-integration.mdx) --- title: "Running in CI" description: "Learn how to run Kastrax evals in your CI/CD pipeline to monitor agent quality over time." --- # Running Evals in CI ✅ [EN] Source: https://kastrax.ai/en/docs/evals/running-in-ci Running evals in your CI pipeline helps bridge this gap by providing quantifiable metrics for measuring agent quality over time. ## Setting Up CI Integration ✅ We support any testing framework that supports ESM modules. For example, you can use [Vitest](https://vitest.dev/), [Jest](https://jestjs.io/) or [Mocha](https://mochajs.org/) to run evals in your CI/CD pipeline. ```typescript copy showLineNumbers filename="src/kastrax/agents/index.test.ts" import { describe, it, expect } from 'vitest'; import { evaluate } from "@kastrax/evals"; import { ToneConsistencyMetric } from "@kastrax/evals/nlp"; import { myAgent } from './index'; describe('My Agent', () => { it('should validate tone consistency', async () => { const metric = new ToneConsistencyMetric(); const result = await evaluate(myAgent, 'Hello, world!', metric) expect(result.score).toBe(1); }); }); ``` You will need to configure a testSetup and globalSetup script for your testing framework to capture the eval results. It allows us to show these results in your kastrax dashboard. ## Framework Configuration ✅ ### Vitest Setup Add these files to your project to run evals in your CI/CD pipeline: ```typescript copy showLineNumbers filename="globalSetup.ts" import { globalSetup } from '@kastrax/evals'; export default function setup() { globalSetup() } ``` ```typescript copy showLineNumbers filename="testSetup.ts" import { beforeAll } from 'vitest'; import { attachListeners } from '@kastrax/evals'; beforeAll(async () => { await attachListeners(); }); ``` ```typescript copy showLineNumbers filename="vitest.config.ts" import { defineConfig } from 'vitest/config' export default defineConfig({ test: { globalSetup: './globalSetup.ts', setupFiles: ['./testSetup.ts'], }, }) ``` ## Storage Configuration ✅ To store eval results in Kastrax Storage and capture results in the Kastrax dashboard: ```typescript copy showLineNumbers filename="testSetup.ts" import { beforeAll } from 'vitest'; import { attachListeners } from '@kastrax/evals'; import { kastrax } from './your-kastrax-setup'; beforeAll(async () => { // Store evals in Kastrax Storage (requires storage to be enabled) await attachListeners(kastrax); }); ``` With file storage, evals persist and can be queried later. With memory storage, evals are isolated to the test process. --- title: "Textual Evals" description: "Understand how Kastrax uses LLM-as-judge methodology to evaluate text quality." --- # Textual Evals [EN] Source: https://kastrax.ai/en/docs/evals/textual-evals Textual evals use an LLM-as-judge methodology to evaluate agent outputs. This approach leverages language models to assess various aspects of text quality, similar to how a teaching assistant might grade assignments using a rubric. Each eval focuses on specific quality aspects and returns a score between 0 and 1, providing quantifiable metrics for non-deterministic AI outputs. Kastrax provides several eval metrics for assessing Agent outputs. Kastrax is not limited to these metrics, and you can also [define your own evals](/docs/evals/custom-eval). ## Why Use Textual Evals? Textual evals help ensure your agent: - Produces accurate and reliable responses - Uses context effectively - Follows output requirements - Maintains consistent quality over time ## Available Metrics ### Accuracy and Reliability These metrics evaluate how correct, truthful, and complete your agent's answers are: - [`hallucination`](/reference/evals/hallucination): Detects facts or claims not present in provided context - [`faithfulness`](/reference/evals/faithfulness): Measures how accurately responses represent provided context - [`content-similarity`](/reference/evals/content-similarity): Evaluates consistency of information across different phrasings - [`completeness`](/reference/evals/completeness): Checks if responses include all necessary information - [`answer-relevancy`](/reference/evals/answer-relevancy): Assesses how well responses address the original query - [`textual-difference`](/reference/evals/textual-difference): Measures textual differences between strings ### Understanding Context These metrics evaluate how well your agent uses provided context: - [`context-position`](/reference/evals/context-position): Analyzes where context appears in responses - [`context-precision`](/reference/evals/context-precision): Evaluates whether context chunks are grouped logically - [`context-relevancy`](/reference/evals/context-relevancy): Measures use of appropriate context pieces - [`contextual-recall`](/reference/evals/contextual-recall): Assesses completeness of context usage ### Output Quality These metrics evaluate adherence to format and style requirements: - [`tone`](/reference/evals/tone-consistency): Measures consistency in formality, complexity, and style - [`toxicity`](/reference/evals/toxicity): Detects harmful or inappropriate content - [`bias`](/reference/evals/bias): Detects potential biases in the output - [`prompt-alignment`](/reference/evals/prompt-alignment): Checks adherence to explicit instructions like length restrictions, formatting requirements, or other constraints - [`summarization`](/reference/evals/summarization): Evaluates information retention and conciseness - [`keyword-coverage`](/reference/evals/keyword-coverage): Assesses technical terminology usage --- title: "Using with Vercel AI SDK" description: "Learn how Kastrax leverages the Vercel AI SDK library and how you can leverage it further with Kastrax" --- import Image from "next/image"; # Using with Vercel AI SDK ✅ [EN] Source: https://kastrax.ai/en/docs/frameworks/ai-sdk Kastrax leverages AI SDK's model routing (a unified interface on top of OpenAI, Anthropic, etc), structured output, and tool calling. We explain this in greater detail in [this blog post](https://kastrax.ai/blog/using-ai-sdk-with-kastrax) ## Kastrax + AI SDK ✅ Kastrax acts as a layer on top of AI SDK to help teams productionize their proof-of-concepts quickly and easily. Agent interaction trace showing spans, LLM calls, and tool executions ## Model routing ✅ When creating agents in Kastrax, you can specify any AI SDK-supported model: ```typescript import { openai } from "@ai-sdk/openai"; import { Agent } from "@kastrax/core/agent"; const agent = new Agent({ name: "WeatherAgent", instructions: "Instructions for the agent...", model: openai("gpt-4-turbo"), // Model comes directly from AI SDK }); const result = await agent.generate("What is the weather like?"); ``` ## AI SDK Hooks ✅ Kastrax is compatible with AI SDK's hooks for seamless frontend integration: ### useChat The `useChat` hook enables real-time chat interactions in your frontend application - Works with agent data streams i.e. `.toDataStreamResponse()` - The useChat `api` defaults to `/api/chat` - Works with the Kastrax REST API agent stream endpoint `{KASTRAX_BASE_URL}/agents/:agentId/stream` for data streams, i.e. no structured output is defined. ```typescript filename="app/api/chat/route.ts" copy import { kastrax } from "@/src/kastrax"; export async function POST(req: Request) { const { messages } = await req.json(); const myAgent = kastrax.getAgent("weatherAgent"); const stream = await myAgent.stream(messages); return stream.toDataStreamResponse(); } ``` ```typescript copy import { useChat } from '@ai-sdk/react'; export function ChatComponent() { const { messages, input, handleInputChange, handleSubmit } = useChat({ api: '/path-to-your-agent-stream-api-endpoint' }); return (
{messages.map(m => (
{m.role}: {m.content}
))}
); } ``` > **Gotcha**: When using `useChat` with agent memory functionality, make sure to check out the [Agent Memory section](/docs/agents/agent-memory#usechat) for important implementation details. ### useCompletion For single-turn completions, use the `useCompletion` hook: - Works with agent data streams i.e. `.toDataStreamResponse()` - The useCompletion `api` defaults to `/api/completion` - Works with the Kastrax REST API agent stream endpoint `{KASTRAX_BASE_URL}/agents/:agentId/stream` for data streams, i.e. no structured output is defined. ```typescript filename="app/api/completion/route.ts" copy import { kastrax } from "@/src/kastrax"; export async function POST(req: Request) { const { messages } = await req.json(); const myAgent = kastrax.getAgent("weatherAgent"); const stream = await myAgent.stream(messages); return stream.toDataStreamResponse(); } ``` ```typescript import { useCompletion } from "@ai-sdk/react"; export function CompletionComponent() { const { completion, input, handleInputChange, handleSubmit, } = useCompletion({ api: '/path-to-your-agent-stream-api-endpoint' }); return (

Completion result: {completion}

); } ``` ### useObject For consuming text streams that represent JSON objects and parsing them into a complete object based on a schema. - Works with agent text streams i.e. `.toTextStreamResponse()` - Works with the Kastrax REST API agent stream endpoint `{KASTRAX_BASE_URL}/agents/:agentId/stream` for text streams, i.e. structured output is defined. ```typescript filename="app/api/use-object/route.ts" copy import { kastrax } from "@/src/kastrax"; export async function POST(req: Request) { const { messages } = await req.json(); const myAgent = kastrax.getAgent("weatherAgent"); const stream = await myAgent.stream(messages, { output: z.object({ weather: z.string(), }), }); return stream.toTextStreamResponse(); } ``` ```typescript import { experimental_useObject as useObject } from '@ai-sdk/react'; export default function Page() { const { object, submit } = useObject({ api: '/api/use-object', schema: z.object({ weather: z.string(), }), }); return (
{object?.content &&

{object.content}

}
); } ``` ## Tool Calling ✅ ### AI SDK Tool Format Kastrax supports tools created using the AI SDK format, so you can use them directly with Kastrax agents. See our tools doc on [Vercel AI SDK Tool Format ](/docs/agents/adding-tools#vercel-ai-sdk-tool-format) for more details. ### Client-side tool calling Kastrax leverages AI SDK's tool calling, so what applies in AI SDK applies here still. [Agent Tools](/docs/agents/adding-tools) in Kastrax are 100% percent compatible with AI SDK tools. Kastrax tools also expose an optional `execute` async function. It is optional because you might want to forward tool calls to the client or to a queue instead of executing them in the same process. One way to then leverage client-side tool calling is to use the `@ai-sdk/react` `useChat` hook's `onToolCall` property for client-side tool execution ## Custom DataStream ✅ In certain scenarios you need to write custom data, message annotations to an agent's dataStream. This can be useful for: - Streaming additional data to the client - Passing progress info back to the client in real time Kastrax integrates well with AI SDK to make this possible ### CreateDataStream The `createDataStream` function allows you to stream additional data to the client ```typescript copy import { createDataStream } from "ai" import { Agent } from '@kastrax/core/agent'; export const weatherAgent = new Agent({ name: 'Weather Agent', instructions: ` You are a helpful weather assistant that provides accurate weather information. Your primary function is to help users get weather details for specific locations. When responding: - Always ask for a location if none is provided - If the location name isn't in English, please translate it - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York") - Include relevant details like humidity, wind conditions, and precipitation - Keep responses concise but informative Use the weatherTool to fetch current weather data. `, model: openai('gpt-4o'), tools: { weatherTool }, }); const stream = createDataStream({ async execute(dataStream) { // Write data dataStream.writeData({ value: 'Hello' }); // Write annotation dataStream.writeMessageAnnotation({ type: 'status', value: 'processing' }); //kastrax agent stream const agentStream = await weatherAgent.stream('What is the weather') // Merge agent stream agentStream.mergeIntoDataStream(dataStream); }, onError: error => `Custom error: ${error.message}`, }); ``` ### CreateDataStreamResponse The `createDataStreamResponse` function creates a Response object that streams data to the client ```typescript filename="app/api/chat/route.ts" copy import { kastrax } from "@/src/kastrax"; export async function POST(req: Request) { const { messages } = await req.json(); const myAgent = kastrax.getAgent("weatherAgent"); //kastrax agent stream const agentStream = await myAgent.stream(messages); const response = createDataStreamResponse({ status: 200, statusText: 'OK', headers: { 'Custom-Header': 'value', }, async execute(dataStream) { // Write data dataStream.writeData({ value: 'Hello' }); // Write annotation dataStream.writeMessageAnnotation({ type: 'status', value: 'processing' }); // Merge agent stream agentStream.mergeIntoDataStream(dataStream); }, onError: error => `Custom error: ${error.message}`, }); return response } ``` --- title: "Getting started with Kastrax and NextJS | Kastrax Guides" description: Guide on integrating Kastrax with NextJS. --- import { Callout, Steps, Tabs } from "nextra/components"; # Integrate Kastrax in your Next.js project ✅ [EN] Source: https://kastrax.ai/en/docs/frameworks/next-js There are two main ways to integrate Kastrax with your Next.js application: as a separate backend service or directly integrated into your Next.js app. ## 1. Separate Backend Integration ✅ Best for larger projects where you want to: - Scale your AI backend independently - Keep clear separation of concerns - Have more deployment flexibility ### Create Kastrax Backend Create a new Kastrax project using our CLI: ```bash copy npx create-kastrax@latest ``` ```bash copy npm create kastrax ``` ```bash copy yarn create kastrax ``` ```bash copy pnpm create kastrax ``` For detailed setup instructions, see our [installation guide](/docs/getting-started/installation). ### Install KastraxClient ```bash copy npm install @kastrax/client-js@latest ``` ```bash copy yarn add @kastrax/client-js@latest ``` ```bash copy pnpm add @kastrax/client-js@latest ``` ```bash copy bun add @kastrax/client-js@latest ``` ### Use KastraxClient Create a client instance and use it in your Next.js application: ```typescript filename="lib/kastrax.ts" copy import { KastraxClient } from '@kastrax/client-js'; // Initialize the client export const kastraxClient = new KastraxClient({ baseUrl: process.env.NEXT_PUBLIC_KASTRAX_API_URL || 'http://localhost:4111', }); ``` Example usage in your React component: ```typescript filename="app/components/SimpleWeather.tsx" copy 'use client' import { kastraxClient } from '@/lib/kastrax' export function SimpleWeather() { async function handleSubmit(formData: FormData) { const city = formData.get('city') const agent = kastraxClient.getAgent('weatherAgent') try { const response = await agent.generate({ messages: [{ role: 'user', content: `What's the weather like in ${city}?` }], }) // Handle the response console.log(response.text) } catch (error) { console.error('Error:', error) } } return (
) } ``` ### Deployment When you're ready to deploy, you can use any of our platform-specific deployers (Vercel, Netlify, Cloudflare) or deploy to any Node.js hosting platform. Check our [deployment guide](/docs/deployment/deployment) for detailed instructions.
## 2. Direct Integration ✅ Better for smaller projects or prototypes. This approach bundles Kastrax directly with your Next.js application. ### Initialize Kastrax in your Next.js Root First, navigate to your Next.js project root and initialize Kastrax: ```bash copy cd your-nextjs-app ``` Then run the initialization command: ```bash copy npx kastrax@latest init ``` ```bash copy yarn dlx kastrax@latest init ``` ```bash copy pnpm dlx kastrax@latest init ``` This will set up Kastrax in your Next.js project. For more details about init and other configuration options, see our [kastrax init reference](/reference/cli/init). ### Configure Next.js Add to your `next.config.js`: ```js filename="next.config.js" copy /** @type {import('next').NextConfig} */ const nextConfig = { serverExternalPackages: ["@kastrax/*"], // ... your other Next.js config } module.exports = nextConfig ``` #### Server Actions Example ```typescript filename="app/actions.ts" copy 'use server' import { kastrax } from '@/kastrax' export async function getWeatherInfo(city: string) { const agent = kastrax.getAgent('weatherAgent') const result = await agent.generate(`What's the weather like in ${city}?`) return result } ``` Use it in your component: ```typescript filename="app/components/Weather.tsx" copy 'use client' import { getWeatherInfo } from '../actions' export function Weather() { async function handleSubmit(formData: FormData) { const city = formData.get('city') as string const result = await getWeatherInfo(city) // Handle the result console.log(result) } return (
) } ``` #### API Routes Example ```typescript filename="app/api/chat/route.ts" copy import { kastrax } from '@/kastrax' import { NextResponse } from 'next/server' export async function POST(req: Request) { const { city } = await req.json() const agent = kastrax.getAgent('weatherAgent') const result = await agent.stream(`What's the weather like in ${city}?`) return result.toDataStreamResponse() } ``` ### Deployment When using direct integration, your Kastrax instance will be deployed alongside your Next.js application. Ensure you: - Set up environment variables for your LLM API keys in your deployment platform - Implement proper error handling for production use - Monitor your AI agent's performance and costs
## Observability ✅ Kastrax provides built-in observability features to help you monitor, debug, and optimize your AI operations. This includes: - Tracing of AI operations and their performance - Logging of prompts, completions, and errors - Integration with observability platforms like Langfuse and LangSmith For detailed setup instructions and configuration options specific to Next.js local development, see our [Next.js Observability Configuration Guide](/docs/deployment/logging-and-tracing#nextjs-configuration). --- title: "Creating Your First Agent | Getting Started | Kastrax Docs" description: "Learn how to create your first AI agent using Kastrax, including basic configuration, memory, and tools." --- # Creating Your First Kastrax Agent ✅ [EN] Source: https://kastrax.ai/en/docs/getting-started/first-agent-kotlin This guide will walk you through creating your first agent using the Kastrax framework. We'll build a simple conversational agent that can respond to user queries. ## Basic Agent Structure ✅ In Kastrax, agents are created using a DSL (Domain Specific Language) that makes it easy to define their behavior. Here's the basic structure of a Kastrax agent: ```kotlin val myAgent = agent { name("MyFirstAgent") description("A simple conversational agent") model = deepSeek { apiKey("your-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Optional components memory(...) tools(...) } ``` ## Step-by-Step Guide ✅ Let's create a simple conversational agent step by step: ### 1. Create the Agent File ✅ Create a new Kotlin file in your project, for example `src/main/kotlin/com/example/agents/ConversationAgent.kt`: ```kotlin package com.example.agents import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking /** * A simple conversational agent that can respond to user queries. */ class ConversationAgent { companion object { @JvmStatic fun main(args: Array) = runBlocking { // Create the agent val conversationAgent = createConversationAgent() // Test the agent val response = conversationAgent.generate("Hello! Can you introduce yourself?") println("Agent response: ${response.text}") } } } /** * Creates a simple conversational agent. */ fun createConversationAgent() = agent { name("ConversationAgent") description("A friendly conversational agent that can chat with users") // Define the system prompt systemPrompt(""" You are a helpful, friendly assistant. Your goal is to have engaging conversations with users. Guidelines: - Be polite and respectful - Provide concise but informative responses - Ask follow-up questions to keep the conversation going - If you don't know something, admit it rather than making up information """.trimIndent()) // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") // Replace with your actual API key model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) maxTokens(1000) } } ``` ### 2. Run the Agent ✅ You can run the agent directly from your IDE or using Gradle: ```bash gradle run -PmainClass=com.example.agents.ConversationAgent ``` You should see output similar to: ``` Agent response: Hello! I'm your friendly assistant, and I'm here to chat with you. It's nice to meet you! I can help answer questions, discuss various topics, or just have a casual conversation. Is there anything specific you'd like to talk about today? ``` ## Adding Memory ✅ Let's enhance our agent by adding memory so it can remember previous interactions: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createConversationAgentWithMemory() = agent { name("ConversationAgentWithMemory") description("A friendly conversational agent with memory capabilities") systemPrompt(""" You are a helpful, friendly assistant. Your goal is to have engaging conversations with users. Guidelines: - Be polite and respectful - Provide concise but informative responses - Ask follow-up questions to keep the conversation going - If you don't know something, admit it rather than making up information - Remember important details about the user """.trimIndent()) // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) maxTokens(1000) } // Add memory memory = memory { workingMemory(true) conversationHistory(20) // Remember last 20 messages } } ``` ## Adding Tools ✅ Now, let's add a simple tool to our agent that can fetch the current time: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import java.time.LocalDateTime import java.time.format.DateTimeFormatter fun createConversationAgentWithTools() = agent { name("ConversationAgentWithTools") description("A friendly conversational agent with tools") systemPrompt(""" You are a helpful, friendly assistant. Your goal is to have engaging conversations with users. Guidelines: - Be polite and respectful - Provide concise but informative responses - Ask follow-up questions to keep the conversation going - If you don't know something, admit it rather than making up information - Use your tools when appropriate """.trimIndent()) // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) maxTokens(1000) } // Add tools tools { tool("getCurrentTime") { description("Get the current time") parameters {} execute { val currentTime = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val formattedTime = currentTime.format(formatter) "The current time is $formattedTime" } } } } ``` ## Testing the Agent with a Conversation ✅ Let's create a simple function to have a conversation with our agent: ```kotlin import ai.kastrax.core.agent.Agent import kotlinx.coroutines.runBlocking suspend fun conversationTest(agent: Agent) { val conversation = listOf( "Hello! My name is Alice.", "What can you help me with?", "What time is it right now?", "Thank you! Can you remember my name?", "Goodbye!" ) for (message in conversation) { println("\nUser: $message") val response = agent.generate(message) println("Agent: ${response.text}") } } fun main() = runBlocking { val agent = createConversationAgentWithTools() conversationTest(agent) } ``` ## Complete Example ✅ Here's a complete example that puts everything together: ```kotlin package com.example.agents import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import java.time.LocalDateTime import java.time.format.DateTimeFormatter /** * A complete example of a conversational agent with memory and tools. */ class CompleteAgentExample { companion object { @JvmStatic fun main(args: Array) = runBlocking { // Create the agent val agent = createCompleteAgent() // Test with a conversation conversationTest(agent) } /** * Creates a complete agent with memory and tools. */ fun createCompleteAgent() = agent { name("CompleteAgent") description("A friendly conversational agent with memory and tools") systemPrompt(""" You are a helpful, friendly assistant. Your goal is to have engaging conversations with users. Guidelines: - Be polite and respectful - Provide concise but informative responses - Ask follow-up questions to keep the conversation going - If you don't know something, admit it rather than making up information - Remember important details about the user - Use your tools when appropriate """.trimIndent()) // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) maxTokens(1000) } // Add memory memory = memory { workingMemory(true) conversationHistory(20) } // Add tools tools { tool("getCurrentTime") { description("Get the current time") parameters {} execute { val currentTime = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val formattedTime = currentTime.format(formatter) "The current time is $formattedTime" } } } } /** * Tests the agent with a conversation. */ suspend fun conversationTest(agent: Agent) { val conversation = listOf( "Hello! My name is Alice.", "What can you help me with?", "What time is it right now?", "Thank you! Can you remember my name?", "Goodbye!" ) for (message in conversation) { println("\nUser: $message") val response = agent.generate(message) println("Agent: ${response.text}") } } } } ``` ## Understanding the Code ✅ Let's break down the key components of our agent: ### Agent Configuration ✅ ```kotlin agent { name("CompleteAgent") description("A friendly conversational agent with memory and tools") systemPrompt("...") model = deepSeek { ... } } ``` This defines the basic properties of the agent, including its name, description, system prompt, and the LLM it uses. ### Memory Configuration ✅ ```kotlin memory = memory { workingMemory(true) conversationHistory(20) } ``` This configures the agent's memory system, enabling working memory and conversation history with a capacity of 20 messages. ### Tool Configuration ✅ ```kotlin tools { tool("getCurrentTime") { description("Get the current time") parameters {} execute { // Tool implementation } } } ``` This adds a tool to the agent that can get the current time. The tool has a name, description, parameters (none in this case), and an execution function. ## Next Steps ✅ Now that you've created your first agent, you can: 1. Explore different [agent architectures](../agents/architectures.mdx) 2. Learn about [memory systems](../memory/overview.mdx) 3. Create [custom tools](../tools/custom-tools.mdx) 4. Build [agent workflows](../workflows/overview.mdx) # Creating Your First Kastrax Agent ✅ [EN] Source: https://kastrax.ai/en/docs/getting-started/first-agent This guide will walk you through creating your first agent using the Kastrax framework. We'll build a simple conversational agent that can respond to user queries. ## Basic Agent Structure ✅ In Kastrax, agents are created using a DSL (Domain Specific Language) that makes it easy to define their behavior. Here's the basic structure of a Kastrax agent: ```kotlin val myAgent = agent { name("MyFirstAgent") description("A simple conversational agent") model = deepSeek { apiKey("your-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Optional components memory(...) tools(...) } ``` ## Step-by-Step Guide ✅ Let's create a simple conversational agent step by step: ### 1. Create the Agent File ✅ Create a new Kotlin file in your project, for example `src/main/kotlin/com/example/agents/ConversationAgent.kt`: ```kotlin package com.example.agents import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking /** * A simple conversational agent that can respond to user queries. */ class ConversationAgent { companion object { @JvmStatic fun main(args: Array) = runBlocking { // Create the agent val conversationAgent = createConversationAgent() // Test the agent val response = conversationAgent.generate("Hello! Can you introduce yourself?") println("Agent response: ${response.text}") } } } /** * Creates a simple conversational agent. */ fun createConversationAgent() = agent { name("ConversationAgent") description("A friendly conversational agent that can chat with users") // Define the system prompt systemPrompt(""" You are a helpful, friendly assistant. Your goal is to have engaging conversations with users. Guidelines: - Be polite and respectful - Provide concise but informative responses - Ask follow-up questions to keep the conversation going - If you don't know something, admit it rather than making up information """.trimIndent()) // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") // Replace with your actual API key model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) maxTokens(1000) } } ``` ### 2. Run the Agent ✅ You can run the agent directly from your IDE or using Gradle: ```bash gradle run -PmainClass=com.example.agents.ConversationAgent ``` You should see output similar to: ``` Agent response: Hello! I'm your friendly assistant, and I'm here to chat with you. It's nice to meet you! I can help answer questions, discuss various topics, or just have a casual conversation. Is there anything specific you'd like to talk about today? ``` ## Adding Memory ✅ Let's enhance our agent by adding memory so it can remember previous interactions: ```kotlin fun createConversationAgentWithMemory() = agent { name("ConversationAgentWithMemory") description("A friendly conversational agent with memory capabilities") systemPrompt(""" You are a helpful, friendly assistant. Your goal is to have engaging conversations with users. Guidelines: - Be polite and respectful - Provide concise but informative responses - Ask follow-up questions to keep the conversation going - If you don't know something, admit it rather than making up information - Remember important details about the user """.trimIndent()) // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) maxTokens(1000) } // Add memory memory = memory { workingMemory(true) conversationHistory(20) // Remember last 20 messages } } ``` ## Adding Tools ✅ Now, let's add a simple tool to our agent that can fetch the current time: ```kotlin fun createConversationAgentWithTools() = agent { name("ConversationAgentWithTools") description("A friendly conversational agent with tools") systemPrompt(""" You are a helpful, friendly assistant. Your goal is to have engaging conversations with users. Guidelines: - Be polite and respectful - Provide concise but informative responses - Ask follow-up questions to keep the conversation going - If you don't know something, admit it rather than making up information - Use your tools when appropriate """.trimIndent()) // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) maxTokens(1000) } // Add tools tools { tool("getCurrentTime") { description("Get the current time") parameters {} execute { val currentTime = java.time.LocalDateTime.now() val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val formattedTime = currentTime.format(formatter) "The current time is $formattedTime" } } } } ``` ## Testing the Agent with a Conversation ✅ Let's create a simple function to have a conversation with our agent: ```kotlin suspend fun conversationTest(agent: Agent) { val conversation = listOf( "Hello! My name is Alice.", "What can you help me with?", "What time is it right now?", "Thank you! Can you remember my name?", "Goodbye!" ) for (message in conversation) { println("\nUser: $message") val response = agent.generate(message) println("Agent: ${response.text}") } } ``` ## Complete Example ✅ Here's a complete example that puts everything together: ```kotlin package com.example.agents import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking /** * A complete example of a conversational agent with memory and tools. */ class CompleteAgentExample { companion object { @JvmStatic fun main(args: Array) = runBlocking { // Create the agent val agent = createCompleteAgent() // Test with a conversation conversationTest(agent) } /** * Creates a complete agent with memory and tools. */ fun createCompleteAgent() = agent { name("CompleteAgent") description("A friendly conversational agent with memory and tools") systemPrompt(""" You are a helpful, friendly assistant. Your goal is to have engaging conversations with users. Guidelines: - Be polite and respectful - Provide concise but informative responses - Ask follow-up questions to keep the conversation going - If you don't know something, admit it rather than making up information - Remember important details about the user - Use your tools when appropriate """.trimIndent()) // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) maxTokens(1000) } // Add memory memory = memory { workingMemory(true) conversationHistory(20) } // Add tools tools { tool("getCurrentTime") { description("Get the current time") parameters {} execute { val currentTime = java.time.LocalDateTime.now() val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val formattedTime = currentTime.format(formatter) "The current time is $formattedTime" } } } } /** * Tests the agent with a conversation. */ suspend fun conversationTest(agent: Agent) { val conversation = listOf( "Hello! My name is Alice.", "What can you help me with?", "What time is it right now?", "Thank you! Can you remember my name?", "Goodbye!" ) for (message in conversation) { println("\nUser: $message") val response = agent.generate(message) println("Agent: ${response.text}") } } } } ``` ## Next Steps ✅ Now that you've created your first agent, you can: 1. Explore different [agent architectures](../agents/architectures.mdx) 2. Learn about [memory systems](../memory/overview.mdx) 3. Create [custom tools](../tools/custom-tools.mdx) 4. Build [agent workflows](../workflows/overview.mdx) --- title: "Installing Kastrax | Getting Started | Kastrax Docs" description: "Guide on installing Kastrax and setting up the necessary prerequisites for your Kotlin project." --- # Installing Kastrax ✅ [EN] Source: https://kastrax.ai/en/docs/getting-started/installation-kotlin This guide will help you set up a new Kastrax project from scratch. ## Prerequisites ✅ Before you begin, make sure you have the following installed: - **JDK 17 or later**: Kastrax requires Java 17+ - **Kotlin 2.0 or later**: Kastrax is built with Kotlin - **Gradle 8.0 or later**: For building and managing dependencies ## Creating a New Project ✅ ### Option 1: Using Gradle ✅ 1. Create a new Gradle project: ```bash mkdir my-kastrax-project cd my-kastrax-project gradle init --type kotlin-application ``` 2. Configure your `build.gradle.kts` file: ```kotlin plugins { kotlin("jvm") version "2.1.10" kotlin("plugin.serialization") version "2.1.10" application } group = "com.example" version = "1.0-SNAPSHOT" repositories { mavenCentral() } dependencies { // Kastrax Core implementation("ai.kastrax:kastrax-core:0.1.0") // Optional modules based on your needs implementation("ai.kastrax:kastrax-memory-impl:0.1.0") implementation("ai.kastrax:kastrax-integrations-deepseek:0.1.0") // Testing testImplementation(kotlin("test")) } application { mainClass.set("com.example.MainKt") } ``` ### Option 2: Using the Kastrax CLI (Coming Soon) 🚧 In the future, you'll be able to use the Kastrax CLI to create a new project: ```bash # This is a preview of future functionality kastrax init my-kastrax-project cd my-kastrax-project ``` ## API Keys Setup ✅ Kastrax supports multiple LLM providers. You'll need to set up API keys for the providers you want to use: ### DeepSeek (Recommended) ✅ ```kotlin val llm = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) } ``` ### OpenAI ✅ ```kotlin val llm = openAi { apiKey("your-openai-api-key") model("gpt-4o") } ``` ### Anthropic ✅ ```kotlin val llm = anthropic { apiKey("your-anthropic-api-key") model("claude-3-opus") } ``` ## Project Structure ✅ A typical Kastrax project has the following structure: ``` my-kastrax-project/ ├── build.gradle.kts ├── settings.gradle.kts ├── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ ├── Main.kt │ │ │ ├── agents/ │ │ │ │ └── MyAgent.kt │ │ │ ├── tools/ │ │ │ │ └── MyTools.kt │ │ │ └── workflows/ │ │ │ └── MyWorkflow.kt │ │ └── resources/ │ │ └── application.conf │ └── test/ │ └── kotlin/ │ └── com/ │ └── example/ │ └── AgentTest.kt └── gradle/ └── wrapper/ ├── gradle-wrapper.jar └── gradle-wrapper.properties ``` ## Verifying Installation ✅ Create a simple agent to verify your installation: ```kotlin // src/main/kotlin/com/example/Main.kt package com.example import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("TestAgent") description("A simple test agent") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } val response = myAgent.generate("Hello, world!") println(response.text) } ``` Run your application: ```bash gradle run ``` If everything is set up correctly, you should see a response from your agent. ## Dependency Management ✅ Kastrax is modular, allowing you to include only the components you need: | Module | Description | Dependency | |--------|-------------|------------| | kastrax-core | Core agent functionality | `ai.kastrax:kastrax-core:0.1.0` | | kastrax-memory-api | Memory system API | `ai.kastrax:kastrax-memory-api:0.1.0` | | kastrax-memory-impl | Memory system implementations | `ai.kastrax:kastrax-memory-impl:0.1.0` | | kastrax-rag | Retrieval-augmented generation | `ai.kastrax:kastrax-rag:0.1.0` | | kastrax-integrations-deepseek | DeepSeek LLM integration | `ai.kastrax:kastrax-integrations-deepseek:0.1.0` | | kastrax-integrations-openai | OpenAI integration | `ai.kastrax:kastrax-integrations-openai:0.1.0` | | kastrax-integrations-anthropic | Anthropic integration | `ai.kastrax:kastrax-integrations-anthropic:0.1.0` | ## Configuration ✅ You can configure Kastrax using an `application.conf` file in your resources directory: ```hocon kastrax { # Default LLM provider default-llm-provider = "deepseek" # Memory configuration memory { default-storage = "sqlite" sqlite { database = "kastrax-memory.db" } } # Logging configuration logging { level = "INFO" format = "json" } } ``` ## Next Steps ✅ Now that you have set up your Kastrax project, you can: 1. [Create your first agent](./first-agent.mdx) 2. [Explore the examples](./examples.mdx) 3. [Learn about agent architectures](../agents/architectures.mdx) --- title: "Installing Kastrax | Getting Started | Kastrax Docs" description: Guide on installing Kastrax and setting up the necessary prerequisites. --- # Installing Kastrax ✅ [EN] Source: https://kastrax.ai/en/docs/getting-started/installation This guide will help you set up a new Kastrax project from scratch. ## Prerequisites ✅ Before you begin, make sure you have the following installed: - **JDK 17 or later**: Kastrax requires Java 17+ - **Kotlin 2.0 or later**: Kastrax is built with Kotlin - **Gradle 8.0 or later**: For building and managing dependencies ## Creating a New Project ✅ ### Option 1: Using Gradle ✅ 1. Create a new Gradle project: ```bash mkdir my-kastrax-project cd my-kastrax-project gradle init --type kotlin-application ``` 2. Configure your `build.gradle.kts` file: ```kotlin plugins { kotlin("jvm") version "2.1.10" kotlin("plugin.serialization") version "2.1.10" application } group = "com.example" version = "1.0-SNAPSHOT" repositories { mavenCentral() } dependencies { // Kastrax Core implementation("ai.kastrax:kastrax-core:0.1.0") // Optional modules based on your needs implementation("ai.kastrax:kastrax-memory-impl:0.1.0") implementation("ai.kastrax:kastrax-integrations-deepseek:0.1.0") // Testing testImplementation(kotlin("test")) } application { mainClass.set("com.example.MainKt") } ``` ### Option 2: Using the Kastrax CLI (Coming Soon) ✅ In the future, you'll be able to use the Kastrax CLI to create a new project: ```bash # This is a preview of future functionality kastrax init my-kastrax-project cd my-kastrax-project ``` ## API Keys Setup ✅ Kastrax supports multiple LLM providers. You'll need to set up API keys for the providers you want to use: ### DeepSeek (Recommended) ✅ ```kotlin val llm = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) } ``` ### OpenAI ✅ ```kotlin val llm = openAi { apiKey("your-openai-api-key") model("gpt-4o") } ``` ### Anthropic ✅ ```kotlin val llm = anthropic { apiKey("your-anthropic-api-key") model("claude-3-opus") } ``` ## Project Structure ✅ A typical Kastrax project has the following structure: ``` my-kastrax-project/ ├── build.gradle.kts ├── settings.gradle.kts ├── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ ├── Main.kt │ │ │ ├── agents/ │ │ │ │ └── MyAgent.kt │ │ │ ├── tools/ │ │ │ │ └── MyTools.kt │ │ │ └── workflows/ │ │ │ └── MyWorkflow.kt │ │ └── resources/ │ │ └── application.conf │ └── test/ │ └── kotlin/ │ └── com/ │ └── example/ │ └── AgentTest.kt └── gradle/ └── wrapper/ ├── gradle-wrapper.jar └── gradle-wrapper.properties ``` ## Verifying Installation ✅ Create a simple agent to verify your installation: ```kotlin // src/main/kotlin/com/example/Main.kt package com.example import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("TestAgent") description("A simple test agent") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } val response = myAgent.generate("Hello, world!") println(response.text) } ``` Run your application: ```bash gradle run ``` If everything is set up correctly, you should see a response from your agent. ## Next Steps ✅ Now that you have set up your Kastrax project, you can: 1. [Create your first agent](./first-agent.mdx) 2. [Explore the examples](./examples.mdx) 3. [Learn about agent architectures](../agents/architectures.mdx) --- title: "Using with Cursor/Windsurf | Getting Started | Kastrax Docs" description: "Learn how to use the Kastrax MCP documentation server in your IDE to turn it into an agentic Kastrax expert." --- import YouTube from "@/components/youtube"; # Kastrax Tools for your agentic IDE ✅ [EN] Source: https://kastrax.ai/en/docs/getting-started/mcp-docs-server `@kastrax/mcp-docs-server` provides direct access to Kastrax's complete knowledge base in Cursor, Windsurf, Cline, or any other IDE that supports MCP. It has access to documentation, code examples, technical blog posts / feature announcements, and package changelogs which your IDE can read to help you build with Kastrax. The MCP server tools have been designed to allow an agent to query the specific information it needs to complete a Kastrax related task - for example: adding a Kastrax feature to an agent, scaffolding a new project, or helping you understand how something works. ## How it works ✅ Once it's installed in your IDE you can write prompts and assume the agent will understand everything about Kastrax. ### Add features - "Add evals to my agent and write tests" - "Write me a workflow that does the following `[task]`" - "Make a new tool that allows my agent to access `[3rd party API]`" ### Ask about integrations - "Does Kastrax work with the AI SDK? How can I use it in my `[React/Svelte/etc]` project?" - "What's the latest Kastrax news around MCP?" - "Does Kastrax support `[provider]` speech and voice APIs? Show me an example in my code of how I can use it." ### Debug or update existing code - "I'm running into a bug with agent memory, have there been any related changes or bug fixes recently?" - "How does working memory behave in Kastrax and how can I use it to do `[task]`? It doesn't seem to work the way I expect." - "I saw there are new workflow features, explain them to me and then update `[workflow]` to use them." **And more** - if you have a question, try asking your IDE and let it look it up for you. ## Automatic Installation ✅ Run `pnpm create kastrax@latest` and select Cursor or Windsurf when prompted to install the MCP server. For other IDEs, or if you already have a Kastrax project, install the MCP server by following the instructions below. ## Manual Installation ✅ - **Cursor**: Edit `.cursor/mcp.json` in your project root, or `~/.cursor/mcp.json` for global configuration - **Windsurf**: Edit `~/.codeium/windsurf/mcp_config.json` (only supports global configuration) Add the following configuration: ### MacOS/Linux ```json { "mcpServers": { "kastrax": { "command": "npx", "args": ["-y", "@kastrax/mcp-docs-server@latest"] } } } ``` ### Windows ```json { "mcpServers": { "kastrax": { "command": "cmd", "args": ["/c", "npx", "-y", "@kastrax/mcp-docs-server@latest"] } } } ``` ## After Configuration ✅ ### Cursor If you followed the automatic installation, you'll see a popup when you open cursor in the bottom left corner to prompt you to enable the Kastrax Docs MCP Server. Diagram showing cursor prompt to enable Kastrax docs MCP server Otherwise, for manual installation, do the following. 1. Open Cursor settings 2. Navigate to MCP settings 3. Click "enable" on the Kastrax MCP server 4. If you have an agent chat open, you'll need to re-open it or start a new chat to use the MCP server ### Windsurf 1. Fully quit and re-open Windsurf 2. If tool calls start failing, go to Windsurfs MCP settings and re-start the MCP server. This is a common Windsurf MCP issue and isn't related to Kastrax. Right now Cursor's MCP implementation is more stable than Windsurfs is. In both IDEs it may take a minute for the MCP server to start the first time as it needs to download the package from npm. ## Available Agent Tools ✅ ### Documentation Access Kastrax's complete documentation: - Getting started / installation - Guides and tutorials - API references ### Examples Browse code examples: - Complete project structures - Implementation patterns - Best practices ### Blog Posts Search the blog for: - Technical posts - Changelog and feature announcements - AI news and updates ### Package Changes Track updates for Kastrax and `@kastrax/*` packages: - Bug fixes - New features - Breaking changes ## Common Issues ✅ 1. **Server Not Starting** - Ensure npx is installed and working - Check for conflicting MCP servers - Verify your configuration file syntax - On Windows, make sure to use the Windows-specific configuration 2. **Tool Calls Failing** - Restart the MCP server and/or your IDE - Update to the latest version of your IDE ## Model Capabilities [EN] Source: https://kastrax.ai/en/docs/getting-started/model-capability import { ProviderTable } from "@/components/provider-table"; The AI providers support different language models with various capabilities. Not all models support structured output, image input, object generation, tool usage, or tool streaming. Here are the capabilities of popular models: Source: [https://sdk.vercel.ai/docs/foundations/providers-and-models#model-capabilities](https://sdk.vercel.ai/docs/foundations/providers-and-models#model-capabilities) --- title: "Introduction | Kastrax Docs" description: "Kastrax is a Kotlin agent framework. It helps you build AI applications and features quickly with powerful primitives like agents, actors, memory, tools, and RAG." --- # About Kastrax ✅ [EN] Source: https://kastrax.ai/en/docs/getting-started/overview-kotlin Kastrax is an open-source Kotlin agent framework designed to provide the primitives you need to build sophisticated AI applications and features. ## Key Features ✅ Kastrax offers a comprehensive set of features for building AI agent systems: - **Multiple Agent Architectures**: Choose from adaptive, goal-oriented, hierarchical, reflective, and creative agent architectures - **Actor Model Integration**: Build distributed, concurrent, and resilient agent systems using the actor model - **Advanced Memory System**: Manage working memory, conversation history, and semantic recall - **Flexible Tool System**: Create and use tools to extend agent capabilities - **RAG System**: Implement retrieval-augmented generation for knowledge-based applications - **Workflow Engine**: Create complex, multi-step agent workflows - **Multiple LLM Integrations**: Support for DeepSeek, OpenAI, Anthropic, and more ## Getting Started ✅ You can use Kastrax to build [AI agents](/docs/agents/overview.mdx) that have memory and can execute functions, create [distributed agent systems](/docs/actor/overview.mdx) using the actor model, and implement [knowledge-based applications](/docs/rag/overview.mdx) with RAG. Here's a simple example of creating an agent with Kastrax: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a simple agent val myAgent = agent { name("MyFirstAgent") description("A helpful assistant that can answer questions") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // Use the agent val response = myAgent.generate("What is artificial intelligence?") println(response.text) } ``` ## Core Components ✅ Kastrax is built around several core components: ### Agent System ✅ The agent system provides a flexible framework for creating AI agents with different architectures: ```kotlin // Create an adaptive agent val adaptiveAgent = adaptiveAgent { name("AdaptiveAssistant") // Configuration... } // Create a goal-oriented agent val goalAgent = goalOrientedAgent { name("ProjectManager") // Configuration... } ``` ### Actor Model ✅ The actor model enables distributed, concurrent agent systems: ```kotlin // Create an actor system val system = actor.proto.ActorSystem.create() // Create an actor val greeter = system.spawn(Props.create(GreetingActor::class.java), "greeter") // Send a message to the actor greeter.tell(Greeting("World")) ``` ### Memory System ✅ The memory system helps agents remember past interactions and important information: ```kotlin // Configure memory memory = memory { workingMemory(true) conversationHistory(10) semanticMemory(true) } ``` ### Tool System ✅ The tool system allows agents to perform actions: ```kotlin // Add tools to an agent tools { tool("getCurrentTime") { description("Get the current time") parameters {} execute { "The current time is ${java.time.LocalTime.now()}" } } } ``` ### RAG System ✅ The RAG system enables knowledge-based applications: ```kotlin // Configure RAG rag { retriever(retriever) contextBuilder(contextBuilder) maxTokens(3000) } ``` ## Next Steps ✅ Ready to dive in? Here's where to go next: 1. [Installation Guide](/docs/getting-started/installation.mdx): Set up Kastrax in your project 2. [First Agent Tutorial](/docs/getting-started/first-agent.mdx): Create your first Kastrax agent 3. [Agent Architectures](/docs/agents/architectures.mdx): Learn about different agent types 4. [Actor Model](/docs/actor/overview.mdx): Understand the distributed actor system # Kastrax Overview [EN] Source: https://kastrax.ai/en/docs/getting-started/overview Kastrax is a powerful AI Agent framework built in Kotlin, designed to create sophisticated, intelligent agents with advanced capabilities. It combines the Actor model for distributed systems with modern AI agent architectures to provide a comprehensive platform for building AI-powered applications. ## What is Kastrax? Kastrax is an open-source framework that enables developers to build AI agents with: - **Multiple Agent Architectures**: Adaptive, Goal-Oriented, Hierarchical, and Reflective agents - **Distributed Actor System**: Based on the kactor library for scalable, resilient applications - **Advanced Memory Management**: Working memory, conversation history, and semantic recall - **Tool Integration**: Easily extend agents with custom tools and capabilities - **RAG System**: Built-in Retrieval-Augmented Generation for knowledge-based applications - **Workflow Engine**: Create complex agent workflows with state management - **Multiple LLM Integrations**: Support for DeepSeek, OpenAI, Anthropic, and more ## Key Features ### Agent Architectures Kastrax provides several agent architectures to suit different use cases: - **Adaptive Agents**: Adjust behavior based on user preferences and feedback - **Goal-Oriented Agents**: Focus on achieving specific objectives with task planning - **Hierarchical Agents**: Organize complex tasks into manageable sub-tasks - **Reflective Agents**: Self-monitor and improve performance through reflection ### Actor Model Integration Kastrax integrates with the kactor library to provide: - **Distributed Processing**: Scale across multiple machines - **Message-Passing Concurrency**: Resilient, non-blocking communication - **Supervision Hierarchies**: Fault tolerance and recovery - **Location Transparency**: Seamless local and remote actor communication ### Memory System The memory system in Kastrax provides: - **Working Memory**: System instructions and user information - **Conversation History**: Recent message tracking - **Semantic Recall**: Retrieval of relevant past information - **Memory Processors**: Context management and optimization ### Tool System Kastrax includes a flexible tool system: - **Built-in Tools**: File operations, web requests, data processing, and more - **Custom Tool Creation**: Easily extend with your own tools - **Tool Validation**: Parameter validation with Zod - **Tool Chaining**: Combine tools for complex operations ## Getting Started To start using Kastrax, follow these steps: 1. [Installation](./installation.mdx): Set up your Kastrax project 2. [First Agent](./first-agent.mdx): Create your first Kastrax agent 3. [Running Examples](./examples.mdx): Explore the example applications ## Use Cases Kastrax is suitable for a wide range of applications: - **Conversational AI**: Build sophisticated chatbots and assistants - **Knowledge Workers**: Create agents that can research, analyze, and summarize information - **Process Automation**: Automate complex workflows with intelligent agents - **Distributed Systems**: Build resilient, scalable AI applications - **Multi-Agent Systems**: Create collaborative agent networks ## Next Steps After getting familiar with the basics, explore these areas: - [Agent Architectures](../agents/architectures.mdx): Learn about different agent types - [Memory System](../memory/overview.mdx): Understand how agent memory works - [Tool Integration](../tools/overview.mdx): Extend your agents with tools - [Actor Model](../actor/overview.mdx): Learn about the distributed actor system --- title: "Kastrax AI Agent Project Structure | Getting Started | Kastrax Docs" description: Guide on organizing Kastrax AI Agent projects, including best practices and recommended structures for building intelligent agent systems. --- import { FileTree } from 'nextra/components'; # Kastrax AI Agent Project Structure ✅ [EN] Source: https://kastrax.ai/en/docs/getting-started/project-structure This page provides a guide for organizing your Kastrax AI Agent projects. Kastrax is a powerful Kotlin-based AI Agent framework that combines the actor model with advanced AI capabilities, allowing you to build sophisticated, distributed agent systems. Kastrax offers a modular architecture where you can use components separately or together to create intelligent agent systems that can reason, learn, and interact with their environment. You can structure your project according to your specific needs, but we recommend following certain patterns to maximize maintainability and scalability of your AI agent systems. ## Project Setup with Gradle ✅ Kastrax AI Agent projects are typically set up using Gradle, the preferred build system for Kotlin projects. To create a new Kastrax project, you'll need to configure your `build.gradle.kts` file with the appropriate dependencies. ```kotlin plugins { kotlin("jvm") version "1.9.0" kotlin("plugin.serialization") version "1.9.0" } dependencies { // Kastrax Core - provides the fundamental AI agent capabilities implementation("ai.kastrax:kastrax-core:1.0.0") // Kastrax Actor - provides the actor model implementation implementation("ai.kastrax:kastrax-actor:1.0.0") // Kastrax Memory - for agent memory and persistence implementation("ai.kastrax:kastrax-memory:1.0.0") // Kastrax Tools - standard tools for your agents implementation("ai.kastrax:kastrax-tools:1.0.0") // Kastrax LLM providers implementation("ai.kastrax:kastrax-llm-deepseek:1.0.0") // For DeepSeek integration // Other LLM providers are available as separate dependencies } ``` You can customize your dependencies based on which components you need for your AI agent system: ### Recommended Project Structure A typical Kastrax AI Agent project follows a structure that separates different components of your agent system. Here's a recommended structure for a comprehensive Kastrax project: ### Key Project Components | Component | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------- | | `agents/` | Contains agent definitions with different capabilities and purposes | | `actors/` | Contains actor implementations that enable distributed, concurrent agent execution | | `tools/` | Custom tools that agents can use to interact with external systems and perform specialized tasks | | `memory/` | Components for agent memory, including conversation history, knowledge bases, and persistent storage | | `config/` | Configuration classes for the application, LLM providers, and other system settings | ### Key Configuration Files | File | Description | | --------------------- | ------------------------------------------------------------------------------------------------------ | | `build.gradle.kts` | Gradle build configuration with dependencies and build settings | | `application.conf` | HOCON configuration file for application settings, API keys, and environment-specific configurations | | `Main.kt` | Entry point for the application that initializes the agent system | | `Application.kt` | Core application class that sets up the agent environment and coordinates components | ## Agent System Architecture Kastrax AI Agent systems typically follow a layered architecture: 1. **Core Layer**: Provides fundamental AI capabilities through the Kastrax Core module 2. **Actor Layer**: Implements the actor model for distributed, concurrent agent execution 3. **Agent Layer**: Defines specialized agents with different capabilities and responsibilities 4. **Tool Layer**: Provides tools that agents can use to interact with external systems 5. **Memory Layer**: Manages agent memory, conversation history, and knowledge bases This architecture allows for building sophisticated AI agent systems that can scale from simple assistants to complex multi-agent networks capable of collaborative problem-solving. ## Best Practices - **Separate Concerns**: Keep agent definitions, tools, and configuration in separate modules - **Use Dependency Injection**: Inject dependencies like LLM clients and tools into agents - **Implement Proper Error Handling**: AI systems can fail in unexpected ways, so implement robust error handling - **Test Thoroughly**: Write unit and integration tests for your agents and tools - **Monitor Performance**: Implement logging and monitoring to track agent performance and behavior - **Version Your Models**: Keep track of which LLM versions your agents are using - **Secure API Keys**: Store API keys securely and never commit them to version control --- title: "Introduction | Kastrax Docs" description: "Kastrax is a Kotlin agent framework. It helps you build AI applications and features quickly. It gives you the set of primitives you need: workflows, agents, RAG, integrations, memory and tools." --- # About Kastrax ✅ [EN] Source: https://kastrax.ai/en/docs Kastrax is an open-source Kotlin agent framework that combines the power of the actor model with advanced AI capabilities. It's designed to give you the essential primitives you need to build distributed, scalable AI applications and features. You can use Kastrax to build [AI agents](/docs/agents/overview.mdx) ✅ with memory and tool-calling capabilities, implement deterministic LLM workflows, and leverage RAG for knowledge integration. With features like agent versioning, performance monitoring, and distributed communication protocols, Kastrax provides a complete toolkit for developing, testing, and deploying AI applications. The main features include: * **Multiple LLM Integrations** ✅: Kastrax provides a unified interface to interact with various LLM providers including DeepSeek, OpenAI, Anthropic, and Google Gemini. * **[Agent architectures](/docs/agents/architectures.mdx)** ✅: Kastrax offers multiple agent architectures including Adaptive, Goal-Oriented, Hierarchical, Reflective, and Creative agents. * **[Agent memory](/docs/memory/overview.mdx)** ✅: With Kastrax, you can give your agent different types of memory. You can persist agent memory and retrieve it based on recency, semantic similarity, or conversation thread. * **[Tool system](/docs/tools/overview.mdx)** ✅: Kastrax provides a flexible tool system that allows agents to interact with external systems and perform various functions. * **[Workflow engine](/docs/workflows/overview.mdx)** ✅: When you want to execute LLM calls in a deterministic way, Kastrax gives you a powerful workflow engine. You can define discrete steps, handle errors, and manage state across steps. * **[Retrieval-augmented generation (RAG)](/docs/rag/overview.mdx)** ✅: Kastrax gives you APIs to process documents into chunks, create embeddings, and store them in a vector database. At query time, it retrieves relevant chunks to ground LLM responses in your data. * **[Actor model](/docs/actor/overview.mdx)** ✅: Kastrax integrates with the kactor library to provide a distributed actor system for building scalable and resilient agent applications. * **[Agent versioning](/docs/agents/versioning.mdx)** ✅: Kastrax supports agent versioning, allowing you to create, manage, and roll back different versions of your agents. * **[Observability](/docs/observability/overview.mdx)** ✅: Kastrax provides comprehensive observability features including metrics collection, behavior analysis, and visualization tools for monitoring agent performance. * **[A2A Protocol](/docs/a2a/overview.mdx)** ✅: Kastrax implements the Agent-to-Agent (A2A) protocol for standardized communication between agents, enabling complex multi-agent systems. --- title: "Using Kastrax Integrations | Kastrax Local Development Docs" description: Documentation for Kastrax integrations, which are type-safe API clients for third-party services. --- # Using Kastrax Integrations [EN] Source: https://kastrax.ai/en/docs/integrations Integrations in Kastrax are type-safe API clients for third-party services. They can be used as tools for agents or as steps in workflows. ## Installing an Integration Kastrax's default integrations are packaged as individually installable modules. You can add an integration to your project by including it in your Gradle dependencies and importing it into your Kastrax configuration. ### Example: Adding the GitHub Integration 1. **Install the Integration Package** To install the GitHub integration, run: ```bash copy npm install @kastrax/github@latest ``` 2. **Add the Integration to Your Project** Create a new file for your integrations (e.g., `src/kastrax/integrations/index.ts`) and import the integration: ```typescript filename="src/kastrax/integrations/index.ts" showLineNumbers copy import { GithubIntegration } from "@kastrax/github"; export const github = new GithubIntegration({ config: { PERSONAL_ACCESS_TOKEN: process.env.GITHUB_PAT!, }, }); ``` Make sure to replace `process.env.GITHUB_PAT!` with your actual GitHub Personal Access Token or ensure that the environment variable is properly set. 3. **Use the Integration in Tools or Workflows** You can now use the integration when defining tools for your agents or in workflows. ```typescript filename="src/kastrax/tools/index.ts" showLineNumbers copy import { createTool } from "@kastrax/core"; import { z } from "zod"; import { github } from "../integrations"; export const getMainBranchRef = createTool({ id: "getMainBranchRef", description: "Fetch the main branch reference from a GitHub repository", inputSchema: z.object({ owner: z.string(), repo: z.string(), }), outputSchema: z.object({ ref: z.string().optional(), }), execute: async ({ context }) => { const client = await github.getApiClient(); const mainRef = await client.gitGetRef({ path: { owner: context.owner, repo: context.repo, ref: "heads/main", }, }); return { ref: mainRef.data?.ref }; }, }); ``` In the example above: - We import the `github` integration. - We define a tool called `getMainBranchRef` that uses the GitHub API client to fetch the reference of the main branch of a repository. - The tool accepts `owner` and `repo` as inputs and returns the reference string. ## Using Integrations in Agents Once you've defined tools that utilize integrations, you can include these tools in your agents. ```typescript filename="src/kastrax/agents/index.ts" showLineNumbers copy import { openai } from "@ai-sdk/openai"; import { Agent } from "@kastrax/core/agent"; import { getMainBranchRef } from "../tools"; export const codeReviewAgent = new Agent({ name: "Code Review Agent", instructions: "An agent that reviews code repositories and provides feedback.", model: openai("gpt-4o-mini"), tools: { getMainBranchRef, // other tools... }, }); ``` In this setup: - We create an agent named `Code Review Agent`. - We include the `getMainBranchRef` tool in the agent's available tools. - The agent can now use this tool to interact with GitHub repositories during conversations. ## Environment Configuration Ensure that any required API keys or tokens for your integrations are properly set in your environment variables. For example, with the GitHub integration, you need to set your GitHub Personal Access Token: ```bash GITHUB_PAT=your_personal_access_token ``` Consider using a `.env` file or another secure method to manage sensitive credentials. ### Example: Adding the Mem0 Integration In this example you'll learn how to use the [Mem0](https://mem0.ai) platform to add long-term memory capabilities to an agent via tool-use. This memory integration can work alongside Kastrax's own [agent memory features](/docs/memory/overview.mdx). Mem0 enables your agent to memorize and later remember facts per-user across all interactions with that user, while Kastrax's memory works per-thread. Using the two in conjunction will allow Mem0 to store long term memories across conversations/interactions, while Kastrax's memory will maintain linear conversation history in individual conversations. 1. **Install the Integration Package** To install the Mem0 integration, run: ```bash copy npm install @kastrax/mem0@latest ``` 2. **Add the Integration to Your Project** Create a new file for your integrations (e.g., `src/kastrax/integrations/index.ts`) and import the integration: ```typescript filename="src/kastrax/integrations/index.ts" showLineNumbers copy import { Mem0Integration } from "@kastrax/mem0"; export const mem0 = new Mem0Integration({ config: { apiKey: process.env.MEM0_API_KEY!, userId: "alice", }, }); ``` 3. **Use the Integration in Tools or Workflows** You can now use the integration when defining tools for your agents or in workflows. ```typescript filename="src/kastrax/tools/index.ts" showLineNumbers copy import { createTool } from "@kastrax/core"; import { z } from "zod"; import { mem0 } from "../integrations"; export const mem0RememberTool = createTool({ id: "Mem0-remember", description: "Remember your agent memories that you've previously saved using the Mem0-memorize tool.", inputSchema: z.object({ question: z .string() .describe("Question used to look up the answer in saved memories."), }), outputSchema: z.object({ answer: z.string().describe("Remembered answer"), }), execute: async ({ context }) => { console.log(`Searching memory "${context.question}"`); const memory = await mem0.searchMemory(context.question); console.log(`\nFound memory "${memory}"\n`); return { answer: memory, }; }, }); export const mem0MemorizeTool = createTool({ id: "Mem0-memorize", description: "Save information to mem0 so you can remember it later using the Mem0-remember tool.", inputSchema: z.object({ statement: z.string().describe("A statement to save into memory"), }), execute: async ({ context }) => { console.log(`\nCreating memory "${context.statement}"\n`); // to reduce latency memories can be saved async without blocking tool execution void mem0.createMemory(context.statement).then(() => { console.log(`\nMemory "${context.statement}" saved.\n`); }); return { success: true }; }, }); ``` In the example above: - We import the `@kastrax/mem0` integration. - We define two tools that uses the Mem0 API client to create new memories and recall previously saved memories. - The tool accepts `question` as an input and returns the memory as a string. ## Available Integrations Kastrax provides several built-in integrations; primarily API-key based integrations that do not require OAuth. Some available integrations including Github, Stripe, Resend, and more. Check Kastrax's codebase or Maven Central for a full list of available integrations. ## Conclusion Integrations in Kastrax enable your AI agents and workflows to interact with external services seamlessly. By installing and configuring integrations, you can extend the capabilities of your application to include operations such as fetching data from APIs, sending messages, or managing resources in third-party systems. Remember to consult the documentation of each integration for specific usage details and to adhere to best practices for security and type safety. --- title: "LLM Integration | Kastrax Docs" description: "Learn how to integrate various Large Language Models (LLMs) with Kastrax, including DeepSeek, OpenAI, Anthropic, and Google Gemini." --- # LLM Integration ✅ [EN] Source: https://kastrax.ai/en/docs/integrations/llm-integration Kastrax provides a unified interface for integrating with various Large Language Models (LLMs), allowing you to easily switch between different providers or use multiple providers simultaneously. This guide explains how to integrate and use different LLM providers with Kastrax. ## Supported LLM Providers ✅ Kastrax currently supports the following LLM providers: - **DeepSeek**: Advanced language models with strong reasoning capabilities - **OpenAI**: GPT-4 and other models with broad capabilities - **Anthropic**: Claude models with strong instruction following - **Google Gemini**: Google's multimodal models ## LLM Provider Architecture ✅ The Kastrax LLM integration is built around the following key components: 1. **LlmProvider Interface**: The core interface that all LLM providers implement 2. **LlmMessage**: Standardized message format for communication with LLMs 3. **LlmOptions**: Configuration options for LLM requests 4. **LlmResponse**: Standardized response format from LLMs 5. **LlmToolCall**: Representation of tool calls made by LLMs 6. **LlmUsage**: Tracking of token usage and other metrics ## Integrating with DeepSeek ✅ DeepSeek is a powerful LLM provider that offers strong reasoning capabilities. Here's how to integrate with DeepSeek in Kastrax: ```kotlin import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import ai.kastrax.core.agent.agent import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a DeepSeek LLM provider val llm = deepSeek { apiKey = "your-deepseek-api-key" model = DeepSeekModel.DEEPSEEK_CHAT temperature = 0.7 maxTokens = 1000 } // Create an agent using the DeepSeek provider val agent = agent { name = "DeepSeekAgent" instructions = "You are a helpful assistant." model = llm } // Generate a response val response = agent.generate("Tell me about Kotlin.") println(response.text) } ``` ## Integrating with OpenAI ✅ OpenAI provides popular models like GPT-4. Here's how to integrate with OpenAI in Kastrax: ```kotlin import ai.kastrax.integrations.openai.openAi import ai.kastrax.integrations.openai.OpenAiModel import ai.kastrax.core.agent.agent import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an OpenAI LLM provider val llm = openAi { apiKey = "your-openai-api-key" model = OpenAiModel.GPT_4_TURBO temperature = 0.7 maxTokens = 1000 } // Create an agent using the OpenAI provider val agent = agent { name = "OpenAIAgent" instructions = "You are a helpful assistant." model = llm } // Generate a response val response = agent.generate("Tell me about Kotlin.") println(response.text) } ``` ## Integrating with Anthropic ✅ Anthropic's Claude models are known for their strong instruction following. Here's how to integrate with Anthropic in Kastrax: ```kotlin import ai.kastrax.integrations.anthropic.anthropic import ai.kastrax.integrations.anthropic.AnthropicModel import ai.kastrax.core.agent.agent import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an Anthropic LLM provider val llm = anthropic { apiKey = "your-anthropic-api-key" model = AnthropicModel.CLAUDE_3_OPUS temperature = 0.7 maxTokens = 1000 } // Create an agent using the Anthropic provider val agent = agent { name = "ClaudeAgent" instructions = "You are a helpful assistant." model = llm } // Generate a response val response = agent.generate("Tell me about Kotlin.") println(response.text) } ``` ## Integrating with Google Gemini ✅ Google's Gemini models offer multimodal capabilities. Here's how to integrate with Gemini in Kastrax: ```kotlin import ai.kastrax.integrations.gemini.gemini import ai.kastrax.integrations.gemini.GeminiModel import ai.kastrax.core.agent.agent import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a Gemini LLM provider val llm = gemini { apiKey = "your-gemini-api-key" model = GeminiModel.GEMINI_PRO temperature = 0.7 maxTokens = 1000 } // Create an agent using the Gemini provider val agent = agent { name = "GeminiAgent" instructions = "You are a helpful assistant." model = llm } // Generate a response val response = agent.generate("Tell me about Kotlin.") println(response.text) } ``` ## Using Multiple LLM Providers ✅ Kastrax allows you to use multiple LLM providers simultaneously, enabling you to leverage the strengths of different models for different tasks: ```kotlin import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import ai.kastrax.integrations.openai.openAi import ai.kastrax.integrations.openai.OpenAiModel import ai.kastrax.core.agent.agent import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create multiple LLM providers val deepSeekLlm = deepSeek { apiKey = "your-deepseek-api-key" model = DeepSeekModel.DEEPSEEK_CHAT } val openAiLlm = openAi { apiKey = "your-openai-api-key" model = OpenAiModel.GPT_4_TURBO } // Create agents using different providers val reasoningAgent = agent { name = "ReasoningAgent" instructions = "You are a reasoning assistant." model = deepSeekLlm } val creativeAgent = agent { name = "CreativeAgent" instructions = "You are a creative assistant." model = openAiLlm } // Use different agents for different tasks val reasoningResponse = reasoningAgent.generate("Solve this logic puzzle: If all A are B, and some B are C, what can we conclude about A and C?") println("Reasoning response: ${reasoningResponse.text}") val creativeResponse = creativeAgent.generate("Write a short poem about Kotlin programming language.") println("Creative response: ${creativeResponse.text}") } ``` ## Advanced LLM Configuration ✅ Kastrax provides advanced configuration options for LLM providers: ```kotlin import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import ai.kastrax.core.llm.LlmOptions import kotlinx.coroutines.runBlocking fun main() = runBlocking { val llm = deepSeek { apiKey = "your-deepseek-api-key" model = DeepSeekModel.DEEPSEEK_CHAT // Basic parameters temperature = 0.7 maxTokens = 1000 topP = 0.95 // Advanced parameters frequencyPenalty = 0.5 presencePenalty = 0.5 stopSequences = listOf("STOP", "END") logitBias = mapOf("token1" to 1.0f, "token2" to -1.0f) seed = 12345L // Timeout and retry settings timeoutMs = 30000L retryCount = 3 retryDelayMs = 1000L // Logging and debugging logRequests = true logResponses = true } // Use the configured LLM val messages = listOf( ai.kastrax.core.llm.LlmMessage( role = ai.kastrax.core.llm.LlmMessageRole.USER, content = "Tell me about Kotlin." ) ) val response = llm.generate(messages, LlmOptions()) println(response.content) } ``` ## Streaming Responses ✅ Kastrax supports streaming responses from LLMs, which is useful for real-time applications: ```kotlin import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.AgentStreamOptions import kotlinx.coroutines.flow.collect import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create a DeepSeek LLM provider val llm = deepSeek { apiKey = "your-deepseek-api-key" model = DeepSeekModel.DEEPSEEK_CHAT } // Create an agent val agent = agent { name = "StreamingAgent" instructions = "You are a helpful assistant." model = llm } // Stream a response val response = agent.stream("Tell me about Kotlin.", AgentStreamOptions( onFinish = { fullText -> println("\nFull response: $fullText") } )) // Collect and print streaming chunks response.textStream?.collect { chunk -> print(chunk) } } ``` ## Error Handling ✅ Proper error handling is important when working with LLMs: ```kotlin import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import ai.kastrax.core.agent.agent import kotlinx.coroutines.runBlocking import kotlin.system.exitProcess fun main() = runBlocking { try { // Create a DeepSeek LLM provider val llm = deepSeek { apiKey = System.getenv("DEEPSEEK_API_KEY") ?: throw IllegalStateException("DEEPSEEK_API_KEY environment variable not set") model = DeepSeekModel.DEEPSEEK_CHAT } // Create an agent val agent = agent { name = "ErrorHandlingAgent" instructions = "You are a helpful assistant." model = llm } // Generate a response val response = agent.generate("Tell me about Kotlin.") println(response.text) } catch (e: IllegalStateException) { println("Configuration error: ${e.message}") exitProcess(1) } catch (e: ai.kastrax.core.llm.LlmException) { println("LLM error: ${e.message}") exitProcess(2) } catch (e: Exception) { println("Unexpected error: ${e.message}") e.printStackTrace() exitProcess(3) } } ``` ## Conclusion ✅ Kastrax's LLM integration system provides a flexible and powerful way to work with various LLM providers. By using the unified interface, you can easily switch between providers or use multiple providers simultaneously, leveraging the strengths of different models for different tasks. --- title: Deploying to Kastrax Cloud description: GitHub-based deployment process for Kastrax applications --- # Deploying to Kastrax Cloud ✅ [EN] Source: https://kastrax.ai/en/docs/kastrax-cloud/deploying This page describes the deployment process for Kastrax applications to Kastrax Cloud using GitHub integration. ## Prerequisites ✅ - A GitHub account - A GitHub repository containing a Kastrax application - Access to Kastrax Cloud ## Deployment Process ✅ Kastrax Cloud uses a Git-based deployment workflow similar to platforms like Vercel and Netlify: 1. **Import GitHub Repository** - From the Projects dashboard, click "Add new" - Select the repository containing your Kastrax application - Click "Import" next to the desired repository 2. **Configure Deployment Settings** - Set the project name (defaults to repository name) - Select branch to deploy (typically `main`) - Configure the Kastrax directory path (defaults to `src/kastrax`) - Add necessary environment variables (like API keys) 3. **Deploy from Git** - After initial configuration, deployments are triggered by pushes to the selected branch - Kastrax Cloud automatically builds and deploys your application - Each deployment creates an atomic snapshot of your agents and workflows ## Automatic Deployments ✅ Kastrax Cloud follows a Git-driven workflow: 1. Make changes to your Kastrax application locally 2. Commit changes to the `main` branch 3. Push to GitHub 4. Kastrax Cloud automatically detects the push and creates a new deployment 5. Once the build completes, your application is live ## Deployment Domains ✅ Each project receives two URLs: 1. **Project-specific domain**: `https://[project-name].kastrax.cloud` - Example: `https://gray-acoustic-helicopter.kastrax.cloud` 2. **Deployment-specific domain**: `https://[deployment-id].kastrax.cloud` - Example: `https://young-loud-caravan-6156280f-ad56-4ec8-9701-6bb5271fd73d.kastrax.cloud` These URLs provide direct access to your deployed agents and workflows. ## Viewing Deployments ✅ ![Deployments List](/image/cloud-agents.png) The deployments section in the dashboard shows: - **Title**: Deployment identifier (based on commit hash) - **Status**: Current state (success or archived) - **Branch**: The branch used (typically `main`) - **Commit**: The Git commit hash - **Updated At**: Timestamp of the deployment Each deployment represents an atomic snapshot of your Kastrax application at a specific point in time. ## Interacting with Agents ✅ ![Agent Interface](/image/cloud-agent.png) After deployment, interact with your agents: 1. Navigate to your project in the dashboard 2. Go to the Agents section 3. Select an agent to view its details and interface 4. Use the Chat tab to communicate with your agent 5. View the agent's configuration in the right panel: - Model information (e.g., OpenAI) - Available tools (e.g., getWeather) - Complete system prompt 6. Use suggested prompts (like "What capabilities do you have?") or enter custom messages The interface shows the agent's branch (typically "main") and indicates whether conversation memory is enabled. ## Monitoring Logs ✅ The Logs section provides detailed information about your application: - **Time**: When the log entry was created - **Level**: Log level (info, debug) - **Hostname**: Server identification - **Message**: Detailed log information, including: - API initialization - Storage connections - Agent and workflow activity These logs help debug and monitor your application's behavior in the production environment. ## Workflows ✅ ![Workflows Interface](/image/cloud-workflows.png) The Workflows section allows you to view and interact with your deployed workflows: 1. View all workflows in your project 2. Examine workflow structure and steps 3. Access execution history and performance data ## Database Usage ✅ Kastrax Cloud tracks database utilization metrics: - Number of reads - Number of writes - Storage used (MB) These metrics appear in the project overview, helping you monitor resource consumption. ## Deployment Configuration ✅ Configure your deployment through the dashboard: 1. Navigate to your project settings 2. Set environment variables (like `OPENAI_API_KEY`) 3. Configure project-specific settings Changes to configuration require a new deployment to take effect. ## Next Steps ✅ After deployment, [trace and monitor execution](./observability.mdx) using the observability tools. --- title: Observability in Kastrax Cloud description: Monitoring and debugging tools for Kastrax Cloud deployments --- # Observability in Kastrax Cloud ✅ [EN] Source: https://kastrax.ai/en/docs/kastrax-cloud/observability Kastrax Cloud records execution data for monitoring and debugging. It captures traces, logs, and runtime information from agents and workflows. ## Agent Interface ✅ The agent interface offers three main views, accessible via tabs: 1. **Chat**: Interactive messaging interface to test your agent 2. **Traces**: Detailed execution records 3. **Evaluation**: Agent performance assessment ![Agent Interface with Chat Tab](/image/cloud-agent.png) ### Chat Interface The Chat tab provides: - Interactive messaging with deployed agents - System response to user queries - Suggested prompt buttons (e.g., "What capabilities do you have?") - Message input area - Branch indicator (e.g., "main") - Note about agent memory limitations ### Agent Configuration Panel The right sidebar displays agent details: - Agent name and deployment identifier - Model information (e.g., "OpenAI") - Tools available to the agent (e.g., "getWeather") - Complete system prompt text This panel provides visibility into how the agent is configured without needing to check the source code. ## Trace System ✅ Kastrax Cloud records traces for agent and workflow interactions. ### Trace Explorer Interface ![Agent Traces View](/image/cloud-agent-traces.png) The Trace Explorer interface shows: - All agent and workflow interactions - Specific trace details - Input and output data - Tool calls with parameters and results - Workflow execution paths - Filtering options by type, status, timestamp, and agent/workflow ### Trace Data Structure Each trace contains: 1. **Request Data**: The request that initiated the agent or workflow 2. **Tool Call Records**: Tool calls during execution with parameters 3. **Tool Response Data**: The responses from tool calls 4. **Agent Response Data**: The generated agent response 5. **Execution Timestamps**: Timing information for each execution step 6. **Model Metadata**: Information about model usage and tokens The trace view displays all API calls and results throughout execution. This data helps debug tool usage and agent logic flows. ### Agent Interaction Data Agent interaction traces include: - User input text - Agent processing steps - Tool calls (e.g., weather API calls) - Parameters and results for each tool call - Final agent response text ## Dashboard Structure ✅ The Kastrax Cloud dashboard contains: - Project deployment history - Environment variable configuration - Agent configuration details (model, system prompt, tools) - Workflow step visualization - Deployment URLs - Recent activity log ## Agent Testing ✅ Test your agents using the Chat interface: 1. Navigate to the Agents section 2. Select the agent you want to test 3. Use the Chat tab to interact with your agent 4. Send messages and view responses 5. Use suggested prompts for common queries 6. Switch to the Traces tab to view execution details Note that by default, agents do not remember conversation history across sessions. The interface indicates this with the message: "Agent will not remember previous messages. To enable memory for agent see image." ## Workflow Monitoring ✅ ![Workflow Interface](/image/cloud-workflow.png) Workflow monitoring shows: - Diagram of workflow steps and connections - Status for each workflow step - Execution details for each step - Execution trace records - Multi-step process execution (e.g., weather lookup followed by activity planning) ### Workflow Execution ![Workflow Run Details](/image/cloud-workflow-run.png) When examining a specific workflow execution, you can see the detailed steps and their outputs. ## Logs ✅ ![Logs Interface](/image/cloud-logs.png) The Logs section provides detailed information about your application: - **Time**: When the log entry was created - **Level**: Log level (info, debug) - **Hostname**: Server identification - **Message**: Detailed log information, including: - API initialization - Storage connections - Agent and workflow activity ## Technical Features ✅ The observability system includes: - **API Endpoints**: For programmatic access to trace data - **Structured Trace Format**: JSON format for filtering and query operations - **Historical Data Storage**: Retention of past execution records - **Deployment Version Links**: Correlation between traces and deployment versions ## Debugging Patterns ✅ - Compare trace data when testing agent behavior changes - Use the chat interface to test edge case inputs - View system prompts to understand agent behavior - Examine tool call parameters and results - Verify workflow execution step sequencing - Identify execution bottlenecks in trace timing data - Compare trace differences between agent versions ## Support Resources ✅ For technical assistance with observability: - Review the [Troubleshooting Documentation]() - Contact technical support through the dashboard - Join the [Discord developer channel](https://discord.gg/kastrax) --- title: Kastrax Cloud ✅ description: Deployment and monitoring service for Kastrax applications --- # Kastrax Cloud ✅ [EN] Source: https://kastrax.ai/en/docs/kastrax-cloud/overview Kastrax Cloud is a deployment service built by the Kastrax team that runs, manages, and monitors Kastrax applications. It works with standard Kastrax projects and handles deployment, scaling, and operational tasks. It is currently in beta. ## Core Functionality - **Atomic Deployments** - Agents and workflows deploy as a single unit - **Project Organization** - Group agents and workflows into projects with assigned URLs - **Environment Variables** - Store configuration securely by environment - **Testing Console** - Send messages to agents through a web interface - **Execution Tracing** - Record agent interactions and tool calls - **Workflow Visualization** - Display workflow steps and execution paths - **Logs** - Standard logging output for debugging - **Platform Compatibility** - Uses the same infrastructure as Cloudflare, Vercel, and Netlify deployers ## Dashboard Components The Kastrax Cloud dashboard contains: - **Projects List** - All projects in the account - **Project Details** - Deployments, environment variables, and access URLs - **Deployment History** - Record of deployments with timestamps and status - **Agent Inspector** - Agent configuration view showing models, tools, and system prompts - **Testing Console** - Interface for sending messages to agents - **Trace Explorer** - Records of tool calls, parameters, and responses - **Workflow Viewer** - Diagram of workflow steps and connections ## Technical Implementation Kastrax Cloud runs on the same core code as the platform-specific deployers with these modifications: - **Edge Network Distribution** - Geographically distributed execution - **Dynamic Resource Allocation** - Adjusts compute resources based on traffic - **Kastrax-specific Runtime** - Runtime optimized for agent execution - **Standard Deployment API** - Consistent deployment interface across environments - **Tracing Infrastructure** - Records all agent and workflow execution steps ## Use Cases Common usage patterns: - Deploying applications without managing infrastructure - Maintaining staging and production environments - Monitoring agent behavior across many requests - Testing agent responses through a web interface - Deploying to multiple regions ## Setup Process 1. [Configure a Kastrax Cloud project](/docs/kastrax-cloud/setting-up) 2. [Deploy code](/docs/kastrax-cloud/deploying) 3. [View execution traces](/docs/kastrax-cloud/observability) --- title: Setting Up a Project description: Configuration steps for Kastrax Cloud projects --- # Setting Up a Kastrax Cloud Project ✅ [EN] Source: https://kastrax.ai/en/docs/kastrax-cloud/setting-up This page describes the steps to set up a project on Kastrax Cloud using GitHub integration. ## Prerequisites ✅ - A Kastrax Cloud account - A GitHub account - A GitHub repository containing a Kastrax application ## Project Creation Process ✅ 1. **Sign in to Kastrax Cloud** - Navigate to the Kastrax Cloud dashboard at https://cloud.kastrax.ai - Sign in with your account credentials 2. **Add a New Project** - From the "All Projects" view, click the "Add new" button in the top right - This opens the GitHub repository import dialog ![Kastrax Cloud Projects Dashboard](/image/cloud-agents.png) 3. **Import Git Repository** - Search for repositories or select from the list of available GitHub repositories - Click the "Import" button next to the repository you want to deploy 4. **Configure Deployment Details** The deployment configuration page includes: - **Repo Name**: The GitHub repository name (read-only) - **Project Name**: Customize the project name (defaults to repo name) - **Branch**: Select the branch to deploy (dropdown, defaults to `main`) - **Project root**: Set the root directory of your project (defaults to `/`) - **Kastrax Directory**: Specify where Kastrax files are located (defaults to `src/kastrax`) - **Build Command**: Optional command to run during build process - **Store Settings**: Configure data storage options - **Environment Variables**: Add key-value pairs for configuration (e.g., API keys) ## Project Structure Requirements ✅ Kastrax Cloud scans the GitHub repository for: - **Agents**: Agent definitions (e.g., Weather Agent) with models and tools - **Workflows**: Workflow step definitions (e.g., weather-workflow) - **Environment Variables**: Required API keys and configuration variables The repository should contain a standard Kastrax project structure for proper detection and deployment. ## Understanding the Dashboard ✅ After creating a project, the dashboard shows: ### Project Overview - **Created Date**: When the project was created - **Domains**: URLs for accessing your deployed application - Format: `https://[project-name].kastrax.cloud` - Format: `https://[random-id].kastrax.cloud` - **Status**: Current deployment status (success or archived) - **Branch**: The branch deployed (typically `main`) - **Environment Variables**: Configured API keys and settings - **Workflows**: List of detected workflows with step counts - **Agents**: List of detected agents with models and tools - **Database Usage**: Reads, writes, and storage statistics ### Deployments Section - List of all deployments with: - Deployment ID (based on commit hash) - Status (success/archived) - Branch - Commit hash - Timestamp ### Logs Section The Logs view displays: - Timestamp for each log entry - Log level (info, debug) - Hostname - Detailed log messages, including: - API startup information - Storage initialization - Agent and workflow activity ## Navigation ✅ The sidebar provides access to: - **Overview**: Project summary and statistics - **Deployments**: Deployment history and details - **Logs**: Application logs for debugging - **Agents**: List and configuration of all agents - **Workflows**: List and structure of all workflows - **Settings**: Project configuration options ## Environment Variable Configuration ✅ Set environment variables through the dashboard: 1. Navigate to your project in the dashboard 2. Go to the "Environment Variables" section 3. Add or edit variables (such as `OPENAI_API_KEY`) 4. Save the configuration Environment variables are encrypted and made available to your application during deployment and execution. ## Testing Your Deployment ✅ After deployment, you can test your agents and workflows using: 1. The custom domain assigned to your project: `https://[project-name].kastrax.cloud` 2. The dashboard interface for direct interaction with agents ## Next Steps ✅ After setting up your project, automatic deployments occur whenever you push to the `main` branch of your GitHub repository. See the [deployment documentation](./deploying.mdx) for more details. --- title: "Adding Kastrax to an Existing Project | Local Development | Kastrax Docs" description: "Learn how to integrate Kastrax AI Agent capabilities into your existing Kotlin applications with step-by-step instructions and examples." --- # Adding Kastrax to an Existing Project ✅ [EN] Source: https://kastrax.ai/en/docs/local-dev/add-to-existing-project Kastrax can be easily integrated into existing Kotlin applications, allowing you to add AI agent capabilities to your projects without starting from scratch. This guide covers different approaches to integrating Kastrax, from using the CLI to manual dependency configuration. ## Using the Kastrax CLI ✅ The simplest way to add Kastrax to an existing project is using the Kastrax CLI tool: ```bash # Install the CLI if you haven't already brew install kastrax/tap/kastrax-cli # Navigate to your project directory cd your-existing-project # Initialize Kastrax in your project kastrax init ``` The CLI will make the following changes to your project: 1. Add Kastrax dependencies to your `build.gradle.kts` file 2. Create a basic agent implementation in your source directory 3. Configure necessary resources and properties files 4. Add example code to help you get started (optional) ## Interactive Setup ✅ When you run `kastrax init` without additional arguments, the CLI will guide you through an interactive setup process: 1. **Module Selection**: Choose which Kastrax modules to include (core, RAG, tools, workflows, etc.) 2. **LLM Provider**: Select your preferred LLM provider (OpenAI, DeepSeek, Anthropic, etc.) 3. **Vector Database**: Choose a vector database for RAG applications (if applicable) 4. **API Key Configuration**: Set up API keys for selected services 5. **Example Code**: Decide whether to include example implementations 6. **Integration Point**: Specify where to add Kastrax code in your project structure ## Non-Interactive Setup ✅ For automated or scripted setups, you can use command-line arguments to bypass the interactive prompts: ```bash kastrax init \ --modules core,rag,tools \ --llm-provider deepseek \ --vector-db pinecone \ --include-examples true \ --source-dir src/main/kotlin/com/example/ai \ --config-dir src/main/resources ``` Available arguments: | Argument | Description | Options | |----------|-------------|--------| | `--modules` | Kastrax modules to include | `core`, `rag`, `tools`, `workflows`, `monitoring` | | `--llm-provider` | LLM provider | `openai`, `deepseek`, `anthropic`, `google`, `local` | | `--vector-db` | Vector database | `pinecone`, `qdrant`, `pgvector`, `chroma`, `none` | | `--include-examples` | Include examples | `true`, `false` | | `--source-dir` | Directory for Kastrax source files | Any valid directory path | | `--config-dir` | Directory for configuration files | Any valid directory path | For more details, refer to the [Kastrax CLI documentation](../../reference/cli/init). ## Manual Integration ✅ If you prefer to integrate Kastrax manually or need more control over the integration process, you can add the necessary dependencies and code yourself. ### Adding Dependencies Add the Kastrax dependencies to your `build.gradle.kts` file: ```kotlin filename="build.gradle.kts" // Add the Kastrax repository repositories { mavenCentral() maven { url = uri("https://repo.kastrax.ai/repository/maven-public/") } } // Add Kastrax dependencies dependencies { // Core Kastrax library (required) implementation("ai.kastrax:kastrax-core:1.0.0") // Optional modules based on your needs implementation("ai.kastrax:kastrax-rag:1.0.0") // For RAG capabilities implementation("ai.kastrax:kastrax-tools:1.0.0") // For tool integration implementation("ai.kastrax:kastrax-workflow:1.0.0") // For workflow orchestration implementation("ai.kastrax:kastrax-monitoring:1.0.0") // For agent monitoring // LLM provider integrations (choose at least one) implementation("ai.kastrax:kastrax-openai:1.0.0") // For OpenAI integration implementation("ai.kastrax:kastrax-deepseek:1.0.0") // For DeepSeek integration implementation("ai.kastrax:kastrax-anthropic:1.0.0") // For Anthropic integration // Vector database integrations (for RAG applications) implementation("ai.kastrax:kastrax-pinecone:1.0.0") // For Pinecone integration implementation("ai.kastrax:kastrax-pgvector:1.0.0") // For PostgreSQL/pgvector integration // Actor system integration (for distributed agents) implementation("ai.kastrax:kastrax-actor:1.0.0") // For actor model integration } ``` ### Configuration Files Create a configuration file for your LLM provider API keys and other settings: ```properties filename="src/main/resources/kastrax.properties" # LLM Provider Configuration kastrax.llm.provider=deepseek kastrax.llm.api-key=your-api-key-here # Vector Database Configuration (for RAG applications) kastrax.vectordb.provider=pinecone kastrax.vectordb.api-key=your-pinecone-api-key kastrax.vectordb.environment=gcp-starter # Agent Configuration kastrax.agent.default-model=deepseek-chat kastrax.agent.temperature=0.7 kastrax.agent.max-tokens=2000 # Actor System Configuration (for distributed agents) kastrax.actor.system-name=kastrax-system kastrax.actor.mailbox-size=100 ``` ## Example Integration ✅ Here's a complete example of integrating a simple Kastrax agent into an existing application: ```kotlin filename="src/main/kotlin/com/example/MyApplication.kt" import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking class MyApplication { // Create KastraX instance using DSL private val kastraxInstance = kastrax { // Configure agent agent("assistant") { name("MyAssistant") description("A helpful assistant integrated into the MyApplication system") // Configure model model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure instructions instructions = """ You are a helpful assistant integrated into the MyApplication system. You can provide information, answer questions, and help users with their tasks. Always be concise, accurate, and helpful in your responses. """.trimIndent() } } // Get the agent from KastraX instance private val assistant = kastraxInstance.getAgent("assistant") // Process user queries fun processUserQuery(query: String): String = runBlocking { // Use the Kastrax agent to process the user's query val response = assistant.generate(query) return response.text } // Other application methods... } ``` ### Integration with Spring Boot If you're using Spring Boot, you can create a Kastrax configuration class: ```kotlin filename="src/main/kotlin/com/example/config/KastraxConfig.kt" import ai.kastrax.core.KastraX import ai.kastrax.core.kastrax import ai.kastrax.core.agent.Agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration class KastraxConfig { @Bean fun kastraxInstance(): KastraX { return kastrax { // Configure agent agent("assistant") { name("AssistantAgent") description("A helpful assistant") // Configure model model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure instructions instructions = "You are a helpful assistant." } } } @Bean fun assistantAgent(kastraxInstance: KastraX): Agent { return kastraxInstance.getAgent("assistant") } } ``` Then use the agent in your service: ```kotlin filename="src/main/kotlin/com/example/service/AssistantService.kt" import ai.kastrax.core.agent.Agent import kotlinx.coroutines.runBlocking import org.springframework.stereotype.Service @Service class AssistantService(private val assistantAgent: Agent) { fun getResponse(query: String): String = runBlocking { val response = assistantAgent.generate(query) return response.text } } ``` ### Actor Model Integration Kastrax provides powerful integration with the actor model for building distributed AI agent systems. Here's how to integrate the actor system: ```kotlin filename="src/main/kotlin/com/example/ActorIntegration.kt" import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.actor.actorSystem import ai.kastrax.actor.actor import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an actor system val system = actorSystem("kastrax-system") { // Configure mailbox size and other settings mailboxSize = 100 } // Create KastraX instance with actor integration val kastraxInstance = kastrax { // Configure agent agent("assistant") { name("AssistantAgent") description("A helpful assistant") // Configure model model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure instructions instructions = "You are a helpful assistant." } // Integrate with actor system actorSystem = system } // Create an actor that uses the agent val assistantActor = system.actor("assistant-actor") { // Get the agent from KastraX instance val agent = kastraxInstance.getAgent("assistant") // Define message handling onMessage { message -> // Process the message using the agent val response = agent.generate(message) // Send the response back sender?.tell(response.text) } } // Send a message to the actor and get the response val response = assistantActor.ask("Tell me about Kastrax AI agents.") println("Response: $response") // Shutdown the actor system when done system.shutdown() } ``` ## Next Steps ✅ After integrating Kastrax into your project: 1. **Configure your LLM provider**: Ensure your API keys are properly set up 2. **Test the integration**: Verify that Kastrax is working correctly in your application 3. **Customize the agent**: Modify the instructions, model settings, and other parameters to suit your needs 4. **Add more capabilities**: Integrate additional Kastrax features like RAG, tools, or workflows 5. **Explore actor model**: Consider using the actor model for building distributed agent systems 6. **Implement error handling**: Add proper error handling and retry mechanisms for API calls For more detailed information on developing with Kastrax, see the [Agent Development](../agents/agent-development.mdx), [Tool Integration](../tools/tool-integration.mdx), and [Actor Model](../actor/actor-model.mdx) guides. --- title: "Creating a New Kastrax Project | Local Development | Kastrax Docs" description: "Learn how to create new Kastrax AI Agent projects using the Kastrax CLI or by integrating with existing Kotlin applications." --- # Creating a New Kastrax Project ✅ [EN] Source: https://kastrax.ai/en/docs/local-dev/creating-a-new-project Kastrax provides multiple ways to create new AI agent projects, whether you're starting from scratch or integrating with an existing Kotlin application. This guide will walk you through the different approaches and help you choose the right one for your needs. ## Using the Kastrax CLI ✅ The simplest way to create a new Kastrax project is using the Kastrax CLI tool, which sets up all the necessary dependencies and project structure for you. ### Installing the CLI First, install the Kastrax CLI globally: ```bash # Install using Homebrew (macOS/Linux) brew install kastrax/tap/kastrax-cli # Or install using SDKMAN (cross-platform) curl -s "https://get.sdkman.io" | bash source "$HOME/.sdkman/bin/sdkman-init.sh" sdk install kastrax # Or download the binary directly curl -L https://github.com/kastrax/kastrax-cli/releases/latest/download/kastrax-cli-$(uname -s)-$(uname -m) -o /usr/local/bin/kastrax chmod +x /usr/local/bin/kastrax ``` ### Creating a New Project Once the CLI is installed, create a new project with: ```bash # Create a new project interactively kastrax new # Or specify a project name directly kastrax new my-agent-project ``` ## Interactive Setup ✅ When you run `kastrax new` without additional arguments, the CLI will guide you through an interactive setup process: 1. **Project Name**: Choose a name for your project (will be used for directory and build artifacts) 2. **Project Type**: Select from different project templates (standalone agent, multi-agent system, RAG application, etc.) 3. **Agent Capabilities**: Choose which capabilities to include (RAG, tools, workflows, etc.) 4. **LLM Provider**: Select your preferred LLM provider (OpenAI, DeepSeek, Anthropic, etc.) 5. **Vector Database**: Choose a vector database for RAG applications (optional) 6. **API Key Setup**: Configure API keys for selected services 7. **Example Code**: Include example implementations to help you get started ## Non-Interactive Setup ✅ For automated or scripted setups, you can use command-line arguments to bypass the interactive prompts: ```bash kastrax new my-agent-project \ --type standalone \ --capabilities rag,tools,workflows \ --llm-provider deepseek \ --vector-db pinecone \ --include-examples true \ --gradle-version 8.5 \ --kotlin-version 1.9.20 ``` Available arguments: | Argument | Description | Options | |----------|-------------|--------| | `--type` | Project template | `standalone`, `multi-agent`, `rag`, `workflow` | | `--capabilities` | Agent capabilities | `rag`, `tools`, `workflows`, `monitoring` | | `--llm-provider` | LLM provider | `openai`, `deepseek`, `anthropic`, `google`, `local` | | `--vector-db` | Vector database | `pinecone`, `qdrant`, `pgvector`, `chroma`, `none` | | `--include-examples` | Include examples | `true`, `false` | | `--gradle-version` | Gradle version | Any valid Gradle version | | `--kotlin-version` | Kotlin version | Any valid Kotlin version | ## Generated Project Structure ✅ After creating a new project, you'll have a structure similar to this: ``` my-agent-project/ ├── build.gradle.kts # Main Gradle build file ├── settings.gradle.kts # Gradle settings ├── gradle/ # Gradle wrapper files ├── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── com/example/ # Your application code │ │ │ ├── Main.kt # Application entry point │ │ │ ├── agents/ # Agent definitions │ │ │ ├── tools/ # Custom tools │ │ │ └── workflows/ # Workflow definitions │ │ └── resources/ # Configuration files │ └── test/ # Test directory └── README.md # Project documentation ``` This structure follows Kotlin best practices and makes it easy to organize your agent components. ## Adding Kastrax to Existing Projects ✅ If you already have a Kotlin project and want to add Kastrax capabilities, you can integrate it manually by adding the necessary dependencies to your `build.gradle.kts` file: ```kotlin filename="build.gradle.kts" // Add the Kastrax repository repositories { mavenCentral() maven { url = uri("https://repo.kastrax.ai/repository/maven-public/") } } // Add Kastrax dependencies dependencies { // Core Kastrax library implementation("ai.kastrax:kastrax-core:1.0.0") // Optional modules based on your needs implementation("ai.kastrax:kastrax-rag:1.0.0") // For RAG capabilities implementation("ai.kastrax:kastrax-tools:1.0.0") // For tool integration implementation("ai.kastrax:kastrax-workflow:1.0.0") // For workflow orchestration implementation("ai.kastrax:kastrax-monitoring:1.0.0") // For agent monitoring // LLM provider integrations implementation("ai.kastrax:kastrax-openai:1.0.0") // For OpenAI integration implementation("ai.kastrax:kastrax-deepseek:1.0.0") // For DeepSeek integration // Vector database integrations implementation("ai.kastrax:kastrax-pinecone:1.0.0") // For Pinecone integration implementation("ai.kastrax:kastrax-pgvector:1.0.0") // For PostgreSQL/pgvector integration } ``` After adding the dependencies, you can start using Kastrax in your application: ```kotlin filename="src/main/kotlin/com/example/Main.kt" import ai.kastrax.core.KastraxSystem import ai.kastrax.core.agent.AgentBuilder import ai.kastrax.core.llm.LLMProvider fun main() { // Initialize the Kastrax system val kastraxSystem = KastraxSystem() // Create an agent val agent = AgentBuilder() .withName("MyAssistant") .withModel("gpt-4") .withSystemPrompt("You are a helpful assistant.") .build() // Register the agent with the system kastraxSystem.registerAgent(agent) // Use the agent val response = agent.run("Tell me about Kastrax AI agents.") println(response) } ``` ## Project Templates ✅ Kastrax provides several project templates to help you get started quickly based on your specific needs: ### Standalone Agent Template A simple, single-agent application for straightforward use cases: ```bash kastrax new my-agent --type standalone ``` This template includes: - Basic agent configuration - Simple conversation handling - Environment variable management for API keys - Example prompts and responses ### RAG Application Template A template focused on retrieval-augmented generation applications: ```bash kastrax new my-rag-app --type rag ``` This template includes: - Document processing pipeline - Vector database integration - Retrieval mechanisms - Example RAG agent implementation ### Multi-Agent System Template A template for building systems with multiple cooperating agents: ```bash kastrax new my-agent-system --type multi-agent ``` This template includes: - Multiple agent definitions with different roles - Inter-agent communication mechanisms - Coordination patterns - Example multi-agent conversation flow ### Workflow Orchestration Template A template focused on complex workflow orchestration: ```bash kastrax new my-workflow-app --type workflow ``` This template includes: - Workflow definitions - Step orchestration - Error handling patterns - Example workflow implementation ## Next Steps ✅ After creating your project, you can: 1. **Explore the generated code**: Familiarize yourself with the project structure and example implementations 2. **Configure your LLM provider**: Set up your API keys in the configuration files 3. **Run the example application**: Test that everything is working correctly 4. **Start customizing**: Modify the agents, tools, and workflows to suit your specific needs For more detailed information on developing with Kastrax, see the [Agent Development](../agents/agent-development.mdx) and [Tool Integration](../tools/tool-integration.mdx) guides. --- title: "Inspecting Agents with `kastrax dev` | Kastrax Local Dev Docs" description: "Learn how to use the kastrax dev command to inspect and debug your agents during development." --- # Inspecting Agents with `kastrax dev` [EN] Source: https://kastrax.ai/en/docs/local-dev/kastrax-dev The `kastrax dev` command provides a development environment for inspecting and debugging your agents. It allows you to: - View agent requests and responses - Inspect tool calls - Debug memory operations - Monitor performance metrics ## Usage ```bash kastrax dev ``` This will start a local development server that you can access in your browser. ## Features - **Request/Response Inspection**: View the full details of each request and response - **Tool Call Debugging**: See which tools are being called and with what parameters - **Memory Visualization**: Inspect the agent's memory and how it changes over time - **Performance Metrics**: Monitor response times and token usage # Memory Implementations ✅ [EN] Source: https://kastrax.ai/en/docs/memory/implementations Kastrax provides multiple storage backends for memory, allowing you to choose the right solution for your application's needs. This guide explains the available implementations and how to configure them. ## Memory Storage Backends ✅ ### In-Memory Storage ✅ The default storage is in-memory, which is suitable for simple applications and development: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.storage.InMemoryStorage import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("AgentWithInMemoryStorage") description("An agent with in-memory storage") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure memory with in-memory storage memory = memory { workingMemory(true) conversationHistory(20) // Explicitly set in-memory storage (this is the default) storage(InMemoryStorage()) } } // Use the agent val response = myAgent.generate("Hello, world!") println(response.text) } ``` ### SQLite Storage ✅ For persistent storage in single-user applications, you can use SQLite: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.storage.SQLiteStorage import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("AgentWithSQLiteStorage") description("An agent with SQLite storage") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure memory with SQLite storage memory = memory { workingMemory(true) conversationHistory(20) // Set SQLite storage storage(SQLiteStorage("memory.db")) } } // Use the agent val response = myAgent.generate("Hello, world!") println(response.text) } ``` ### Redis Storage ✅ For distributed applications, you can use Redis: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.storage.RedisStorage import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("AgentWithRedisStorage") description("An agent with Redis storage") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure memory with Redis storage memory = memory { workingMemory(true) conversationHistory(20) // Set Redis storage storage(RedisStorage( host = "localhost", port = 6379, password = "password" // Optional )) } } // Use the agent val response = myAgent.generate("Hello, world!") println(response.text) } ``` ### PostgreSQL Storage ✅ For enterprise applications, you can use PostgreSQL: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.storage.PostgreSQLStorage import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("AgentWithPostgreSQLStorage") description("An agent with PostgreSQL storage") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure memory with PostgreSQL storage memory = memory { workingMemory(true) conversationHistory(20) // Set PostgreSQL storage storage(PostgreSQLStorage( url = "jdbc:postgresql://localhost:5432/kastrax", username = "postgres", password = "password" )) } } // Use the agent val response = myAgent.generate("Hello, world!") println(response.text) } ``` ## Custom Storage Implementations ✅ You can create custom storage implementations by implementing the `MemoryStorage` interface: ```kotlin import ai.kastrax.core.memory.storage.MemoryStorage import ai.kastrax.core.memory.Message import ai.kastrax.core.memory.MemoryPriority class CustomStorage : MemoryStorage { private val messages = mutableListOf() override suspend fun addMessage(message: Message) { messages.add(message) } override suspend fun getMessages(limit: Int): List { return messages.takeLast(limit) } override suspend fun addMemory(content: String, metadata: Map, priority: MemoryPriority): String { val memory = Message( role = "system", content = content, timestamp = System.currentTimeMillis(), metadata = metadata ) messages.add(memory) return memory.id } override suspend fun getMemory(id: String): Message? { return messages.find { it.id == id } } override suspend fun getAllMemories(): List { return messages.toList() } override suspend fun forgetMemory(id: String) { messages.removeIf { it.id == id } } override suspend fun clear() { messages.clear() } } // Use the custom storage memory = memory { storage(CustomStorage()) } ``` ## Vector Store Implementations ✅ For semantic memory, Kastrax supports multiple vector store implementations: ### In-Memory Vector Store ✅ ```kotlin import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.vectorstore.InMemoryVectorStore memory = memory { semanticMemory(true) semanticMemoryModel("all-MiniLM-L6-v2") semanticMemoryVectorStore(InMemoryVectorStore()) } ``` ### Chroma Vector Store ✅ ```kotlin import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.vectorstore.ChromaVectorStore memory = memory { semanticMemory(true) semanticMemoryModel("all-MiniLM-L6-v2") semanticMemoryVectorStore(ChromaVectorStore( collectionName = "agent_memories", url = "http://localhost:8000" )) } ``` ### FAISS Vector Store ✅ ```kotlin import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.vectorstore.FAISSVectorStore memory = memory { semanticMemory(true) semanticMemoryModel("all-MiniLM-L6-v2") semanticMemoryVectorStore(FAISSVectorStore( indexPath = "faiss_index", dimensions = 384 )) } ``` ### Pinecone Vector Store ✅ ```kotlin import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.vectorstore.PineconeVectorStore memory = memory { semanticMemory(true) semanticMemoryModel("all-MiniLM-L6-v2") semanticMemoryVectorStore(PineconeVectorStore( apiKey = "your-pinecone-api-key", environment = "us-west1-gcp", index = "agent_memories" )) } ``` ## Choosing the Right Implementation ✅ Here are some guidelines for choosing the right memory implementation: | Implementation | Best For | Pros | Cons | |----------------|----------|------|------| | In-Memory | Development, simple apps | Fast, no setup | Not persistent, limited by RAM | | SQLite | Single-user apps, desktop | Persistent, simple setup | Limited concurrency | | Redis | Multi-user apps, web | Fast, scalable | Requires Redis server | | PostgreSQL | Enterprise apps | Robust, scalable | More complex setup | For vector stores: | Vector Store | Best For | Pros | Cons | |--------------|----------|------|------| | In-Memory | Development, testing | Fast, no setup | Not persistent, limited by RAM | | Chroma | Production, small-medium scale | Easy to use, good performance | Requires Chroma server | | FAISS | Large-scale, offline | Very fast, efficient | More complex setup | | Pinecone | Cloud-based, large-scale | Managed service, scalable | Paid service | ## Example: Complete Memory Configuration ✅ Here's a complete example with both regular memory storage and vector store: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.storage.SQLiteStorage import ai.kastrax.core.memory.vectorstore.ChromaVectorStore import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("CompleteMemoryAgent") description("An agent with complete memory configuration") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure memory memory = memory { // Basic memory configuration workingMemory(true) conversationHistory(20) semanticMemory(true) // Storage configuration storage(SQLiteStorage("memory.db")) // Vector store configuration semanticMemoryModel("all-MiniLM-L6-v2") semanticMemoryVectorStore(ChromaVectorStore( collectionName = "agent_memories", url = "http://localhost:8000" )) // Memory processors summarizer(true) contextOptimizer(true) } } // Use the agent val response = myAgent.generate("Hello, world!") println(response.text) } ``` ## Next Steps ✅ Now that you understand memory implementations, you can: 1. Learn about [working memory](./working-memory.mdx) 2. Explore [semantic recall](./semantic-recall.mdx) 3. Configure [memory processors](./memory-processors.mdx) # Memory Processors ✅ [EN] Source: https://kastrax.ai/en/docs/memory/memory-processors Memory Processors allow you to modify the list of messages retrieved from memory _before_ they are added to the agent's context window and sent to the LLM. This is useful for managing context size, filtering content, and optimizing performance. Processors operate on the messages retrieved based on your memory configuration (e.g., conversation history, semantic memory). They do **not** affect the new incoming user message. ## Built-in Processors ✅ Kastrax provides several built-in processors: ### Summarizer ✅ The summarizer condenses long conversations to save token space. It's particularly useful when dealing with lengthy conversation histories. ```kotlin memory = memory { // Enable the summarizer summarizer(true) // Configure when summarization should occur summarizerThreshold(10) // Summarize after 10 messages // Configure the summarization model (optional) summarizerModel(deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.3) // Lower temperature for more factual summaries }) } ``` ### Context Optimizer ✅ The context optimizer selects the most relevant memories for the current context, ensuring that the most important information is included within token limits. ```kotlin memory = memory { // Enable the context optimizer contextOptimizer(true) // Configure token limits contextTokenLimit(2000) // Limit context to 2000 tokens // Configure relevance threshold (optional) contextRelevanceThreshold(0.6) // Only include memories with relevance > 0.6 } ``` ### Importance Evaluator ✅ The importance evaluator assesses the importance of new information to determine whether it should be stored in memory. ```kotlin memory = memory { // Enable the importance evaluator importanceEvaluator(true) // Configure importance threshold importanceThreshold(0.6) // Only store memories with importance > 0.6 } ``` ### Token Limiter ✅ This processor prevents errors caused by exceeding the LLM's context window limit. It counts the tokens in the retrieved memory messages and removes the oldest messages until the total count is below the specified limit. ```kotlin memory = memory { // Configure token limits tokenLimit(4000) // Limit total tokens to 4000 // Configure token counting method (optional) tokenCounter { text -> // Custom token counting logic text.split(" ").size // Simple word count as an example } } ``` ## Creating Custom Processors ✅ You can create custom memory processors to implement specialized logic for your application: ```kotlin import ai.kastrax.core.memory.MemoryProcessor import ai.kastrax.core.memory.Message class CustomMemoryProcessor : MemoryProcessor { override fun process(messages: List, query: String): List { // Your custom processing logic here // Example: Filter out messages containing certain keywords return messages.filter { message -> !message.content.contains("confidential", ignoreCase = true) } } } // Use the custom processor memory = memory { // Add your custom processor addProcessor(CustomMemoryProcessor()) } ``` ## Combining Processors ✅ You can combine multiple processors to create a sophisticated memory processing pipeline: ```kotlin memory = memory { // Enable built-in processors summarizer(true) contextOptimizer(true) importanceEvaluator(true) // Add custom processors addProcessor(CustomMemoryProcessor()) // Configure processing order (optional) processingOrder(listOf( "Summarizer", "CustomMemoryProcessor", "ContextOptimizer", "TokenLimiter" )) } ``` ## Example: Advanced Memory Configuration ✅ Here's a complete example of an agent with advanced memory processing: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create agent with advanced memory processing val advancedAgent = agent { name("AdvancedMemoryAgent") description("An agent with advanced memory processing") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure memory with processors memory = memory { // Basic memory configuration workingMemory(true) conversationHistory(20) semanticMemory(true) // Memory processors summarizer(true) summarizerThreshold(15) contextOptimizer(true) contextTokenLimit(3000) importanceEvaluator(true) importanceThreshold(0.5) // Custom processor for filtering sensitive information addProcessor(object : MemoryProcessor { override fun process(messages: List, query: String): List { return messages.map { message -> // Redact sensitive information val redactedContent = message.content .replace(Regex("\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b"), "[CREDIT_CARD]") .replace(Regex("\\b\\d{3}-\\d{2}-\\d{4}\\b"), "[SSN]") // Create a new message with redacted content Message( role = message.role, content = redactedContent, timestamp = message.timestamp, metadata = message.metadata ) } } }) } } // Use the agent val response = advancedAgent.generate("Tell me about our previous conversation regarding quantum computing") println(response.text) } ``` ## Best Practices ✅ 1. **Start Simple**: Begin with the built-in processors before creating custom ones. 2. **Monitor Performance**: Memory processing can impact response time, so monitor performance in production. 3. **Test Thoroughly**: Test your processors with various conversation scenarios to ensure they behave as expected. 4. **Consider Token Limits**: Always include token limiting to prevent context window errors. 5. **Order Matters**: The order of processors can significantly affect the final context. Generally, summarization should happen before optimization. ## Next Steps ✅ Now that you understand memory processors, you can: 1. Learn about [working memory](./working-memory.mdx) 2. Explore [semantic recall](./semantic-recall.mdx) 3. Implement [custom memory storage](./implementations.mdx) --- title: "Memory System Overview | Kastrax Docs" description: "The Kastrax memory system provides agents with the ability to remember past interactions, store important information, and retrieve relevant context." --- # Memory System Overview ✅ [EN] Source: https://kastrax.ai/en/docs/memory/overview The Kastrax memory system provides agents with the ability to remember past interactions, store important information, and retrieve relevant context. This guide explains the memory system architecture and how to use it effectively. ## Memory System Architecture ✅ The Kastrax memory system consists of several key components: 1. **Memory API**: The core interfaces that define how memory is managed 2. **Memory Implementation**: The concrete implementations of memory storage 3. **Message Management**: How messages are stored, retrieved, and processed 4. **Thread Management**: How conversations are organized into threads 5. **Memory Retrieval**: Different strategies for retrieving relevant memories 6. **Memory Storage Backends**: Different storage options for persisting memory ## Memory Types ✅ Kastrax supports several types of memory: ### Working Memory ✅ Working memory contains system instructions, user information, and other context that should be included in every prompt. It's like the "RAM" of the agent, holding immediately relevant information. ```kotlin memory { workingMemory(true) workingMemoryContent("The user's name is Alice and she prefers concise responses.") } ``` ### Conversation History ✅ Conversation history stores recent messages between the user and the agent. It provides immediate context for the current conversation. ```kotlin memory { conversationHistory(10) // Store the last 10 messages } ``` ### Semantic Memory ✅ Semantic memory stores important information that the agent might need to recall later. It's searchable by semantic similarity, allowing the agent to retrieve relevant information based on the current context. ```kotlin memory { semanticMemory(true) semanticMemoryModel("all-MiniLM-L6-v2") // Embedding model for semantic search } ``` ### Episodic Memory ✅ Episodic memory stores complete interaction sequences or "episodes." It helps the agent understand the history of its interactions with a user over time. ```kotlin memory { episodicMemory(true) episodicMemoryCapacity(50) // Store up to 50 episodes } ``` ## Memory Configuration ✅ Here's how to configure the memory system for an agent: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.MemoryPriority import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createAgentWithMemory() = agent { name("MemoryAgent") description("An agent with memory capabilities") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure memory memory = memory { // Enable working memory workingMemory(true) workingMemoryContent("The user prefers technical explanations.") // Configure conversation history conversationHistory(15) // Remember last 15 messages // Enable semantic memory semanticMemory(true) semanticMemoryModel("all-MiniLM-L6-v2") // Configure memory processors summarizer(true) // Summarize long conversations contextOptimizer(true) // Optimize context for token efficiency } } fun main() = runBlocking { val agent = createAgentWithMemory() // Test the agent println(agent.generate("Tell me about quantum computing").text) // Add a memory agent.memory.add("User is a physicist with expertise in quantum mechanics", MemoryPriority.HIGH) // Generate again with the new memory println(agent.generate("Tell me more about quantum entanglement").text) } ``` ## Memory Operations ✅ ### Adding Memories ✅ You can add memories to an agent programmatically: ```kotlin // Add a simple memory agent.memory.add("User is interested in machine learning", MemoryPriority.MEDIUM) // Add a structured memory agent.memory.add( content = "User's favorite programming language is Kotlin", metadata = mapOf( "category" to "preferences", "topic" to "programming" ), priority = MemoryPriority.MEDIUM ) ``` ### Retrieving Memories ✅ You can retrieve memories based on semantic similarity: ```kotlin // Retrieve memories related to a query val memories = agent.memory.retrieve("programming languages", 3) // Get top 3 matches // Print the retrieved memories memories.forEach { memory -> println("Memory: ${memory.content}") println("Relevance: ${memory.relevance}") println("Created: ${memory.createdAt}") println() } ``` ### Forgetting Memories ✅ You can remove memories that are no longer needed: ```kotlin // Forget a specific memory by ID agent.memory.forget(memoryId) // Forget memories by filter agent.memory.forgetWhere { memory -> memory.metadata["category"] == "outdated" } ``` ## Memory Storage Backends ✅ Kastrax supports multiple storage backends for memory: ### In-Memory Storage ✅ The default storage is in-memory, which is suitable for simple applications: ```kotlin memory { storage(InMemoryStorage()) } ``` ### SQLite Storage ✅ For persistent storage, you can use SQLite: ```kotlin memory { storage(SQLiteStorage("memory.db")) } ``` ### Redis Storage ✅ For distributed applications, you can use Redis: ```kotlin memory { storage(RedisStorage( host = "localhost", port = 6379, password = "password" )) } ``` ## Memory Processors ✅ Memory processors optimize how memories are stored and retrieved: ### Summarizer ✅ The summarizer condenses long conversations to save token space: ```kotlin memory { summarizer(true) summarizerThreshold(10) // Summarize after 10 messages } ``` ### Context Optimizer ✅ The context optimizer selects the most relevant memories for the current context: ```kotlin memory { contextOptimizer(true) contextTokenLimit(2000) // Limit context to 2000 tokens } ``` ### Importance Evaluator ✅ The importance evaluator assesses the importance of new information: ```kotlin memory { importanceEvaluator(true) importanceThreshold(0.6) // Only store memories with importance > 0.6 } ``` ## Advanced Memory Usage ✅ ### Memory Tags ✅ You can tag memories for easier organization and retrieval: ```kotlin // Add a tagged memory agent.memory.add( content = "User mentioned they have a dog named Max", tags = listOf("pet", "personal_info"), priority = MemoryPriority.MEDIUM ) // Retrieve memories by tag val petMemories = agent.memory.retrieveByTags(listOf("pet"), 5) ``` ### Memory Expiration ✅ You can set memories to expire after a certain time: ```kotlin // Add a memory that expires in 24 hours agent.memory.add( content = "User is currently working on a project deadline", expiresIn = Duration.ofHours(24), priority = MemoryPriority.HIGH ) ``` ### Memory Reflection ✅ You can enable periodic reflection to consolidate and organize memories: ```kotlin memory { reflection(true) reflectionFrequency(10) // Reflect after every 10 interactions } ``` ## Example: Complete Memory Configuration ✅ Here's a complete example of a sophisticated memory configuration: ```kotlin memory = memory { // Basic memory types workingMemory(true) conversationHistory(20) semanticMemory(true) episodicMemory(true) // Storage configuration storage(SQLiteStorage("agent_memory.db")) // Embedding model for semantic search semanticMemoryModel("all-MiniLM-L6-v2") // Memory processors summarizer(true) summarizerThreshold(15) contextOptimizer(true) contextTokenLimit(3000) importanceEvaluator(true) importanceThreshold(0.5) // Advanced features reflection(true) reflectionFrequency(20) // Memory retention policy retentionPolicy { defaultRetention(Duration.ofDays(30)) retentionByPriority(MemoryPriority.LOW, Duration.ofDays(7)) retentionByPriority(MemoryPriority.MEDIUM, Duration.ofDays(30)) retentionByPriority(MemoryPriority.HIGH, Duration.ofDays(90)) } } ``` ## Next Steps ✅ Now that you understand the memory system, you can: 1. Learn about [memory implementations](./implementations.mdx) 2. Explore [memory querying](./querying.mdx) 3. Implement [custom memory processors](./custom-processors.mdx) # Semantic Recall ✅ [EN] Source: https://kastrax.ai/en/docs/memory/semantic-recall If you ask your friend what they did last weekend, they will search in their memory for events associated with "last weekend" and then tell you what they did. That's sort of like how semantic recall works in Kastrax. ## How Semantic Recall Works ✅ Semantic recall is RAG-based search that helps agents maintain context across longer interactions when messages are no longer within [recent conversation history](./overview.mdx#conversation-history). It uses vector embeddings of messages for similarity search, integrates with various vector stores, and has configurable context windows around retrieved messages.
Diagram showing Kastrax Memory semantic recall When it's enabled, new messages are used to query a vector DB for semantically similar messages. After getting a response from the LLM, all new messages (user, assistant, and tool calls/results) are inserted into the vector DB to be recalled in later interactions. ## Quick Start ✅ Here's a minimal example of setting up an agent with semantic recall: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create agent with semantic recall enabled val myAgent = agent { name("AgentWithSemanticRecall") description("An agent that uses semantic recall") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure memory with semantic recall enabled memory = memory { conversationHistory(10) // Remember last 10 messages semanticMemory(true) // Enable semantic recall semanticMemoryModel("all-MiniLM-L6-v2") // Embedding model for semantic search } } // Use the agent val response = myAgent.generate("Tell me about quantum computing") println(response.text) } ``` ## Configuration Options ✅ Semantic recall can be configured with several options: ```kotlin memory = memory { // Enable semantic memory semanticMemory(true) // Specify the embedding model semanticMemoryModel("all-MiniLM-L6-v2") // Set the number of similar memories to retrieve semanticMemoryTopK(5) // Set the similarity threshold (0.0 to 1.0) semanticMemorySimilarityThreshold(0.7) // Configure the vector store semanticMemoryVectorStore(InMemoryVectorStore()) // Or use a persistent store // semanticMemoryVectorStore(ChromaVectorStore("memory_db")) } ``` ## How Semantic Recall Enhances Conversations ✅ Semantic recall helps agents maintain context in several ways: 1. **Long-Term Memory**: Agents can recall information from much earlier in the conversation, beyond the recent history limit. 2. **Contextual Relevance**: Only the most relevant past messages are recalled, based on semantic similarity to the current query. 3. **Knowledge Persistence**: Important information is preserved even as the conversation evolves through different topics. 4. **Natural Conversation Flow**: Users don't need to repeat information they've already shared, creating a more natural experience. ## Example: Long Conversation Agent ✅ Here's a complete example of an agent that uses semantic recall to maintain context in a long conversation: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create agent with semantic recall val longConversationAgent = agent { name("LongConversationAgent") description("An agent that maintains context in long conversations") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure memory memory = memory { conversationHistory(5) // Only keep 5 recent messages semanticMemory(true) // Enable semantic recall semanticMemoryModel("all-MiniLM-L6-v2") semanticMemoryTopK(3) // Retrieve top 3 similar memories } } // Simulate a long conversation val conversation = listOf( "Hi, my name is Alice and I'm a physicist working on quantum computing.", "I'm specifically interested in quantum error correction.", "What's the weather like today?", "Back to quantum computing, what are the latest developments in quantum error correction?", "Do you remember what field I work in?", "What was my name again?" ) // Run the conversation for (message in conversation) { println("\nUser: $message") val response = longConversationAgent.generate(message) println("Agent: ${response.text}") } } ``` In this example, even though the conversation history only keeps the 5 most recent messages, the agent can still recall that the user's name is Alice and that she works in quantum computing when asked later in the conversation. ## Vector Stores ✅ Kastrax supports multiple vector stores for semantic recall: ### In-Memory Vector Store ✅ ```kotlin memory = memory { semanticMemory(true) semanticMemoryVectorStore(InMemoryVectorStore()) } ``` ### Chroma Vector Store ✅ ```kotlin memory = memory { semanticMemory(true) semanticMemoryVectorStore(ChromaVectorStore( collectionName = "agent_memories", url = "http://localhost:8000" )) } ``` ### FAISS Vector Store ✅ ```kotlin memory = memory { semanticMemory(true) semanticMemoryVectorStore(FAISSVectorStore( indexPath = "faiss_index", dimensions = 384 )) } ``` ## Best Practices ✅ 1. **Balance History and Recall**: Use a smaller conversation history (5-10 messages) when using semantic recall to avoid redundancy. 2. **Choose the Right Embedding Model**: Select an embedding model that balances performance and quality for your use case. 3. **Tune Similarity Threshold**: Adjust the similarity threshold based on your needs - lower values recall more but may include less relevant information. 4. **Persistent Storage**: For production applications, use a persistent vector store like Chroma or FAISS. 5. **Combine with Working Memory**: Use semantic recall alongside working memory for the best results. ## Next Steps ✅ Now that you understand semantic recall, you can: 1. Learn about [working memory](./working-memory.mdx) 2. Explore [memory processors](./memory-processors.mdx) 3. Implement [custom memory storage](./implementations.mdx) # Working Memory ✅ [EN] Source: https://kastrax.ai/en/docs/memory/working-memory While [conversation history](/docs/memory/overview#conversation-history) and [semantic recall](./semantic-recall.mdx) help agents remember conversations, working memory allows them to maintain persistent information about users across interactions within a thread. Think of it as the agent's active thoughts or scratchpad – the key information they keep available about the user or task. It's similar to how a person would naturally remember someone's name, preferences, or important details during a conversation. This is useful for maintaining ongoing state that's always relevant and should always be available to the agent. ## Quick Start ✅ Here's a minimal example of setting up an agent with working memory: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create agent with working memory enabled val myAgent = agent { name("AgentWithWorkingMemory") description("An agent that uses working memory") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure memory with working memory enabled memory = memory { workingMemory(true) workingMemoryContent("The user's name is Alice and she prefers concise responses.") } } // Use the agent val response = myAgent.generate("Tell me about quantum computing") println(response.text) } ``` ## How Working Memory Works ✅ Working memory is included in every prompt sent to the model. It's a way to provide persistent context that doesn't change between messages. The working memory content is typically added to the system prompt or as a separate message at the beginning of the conversation history. This ensures the model always has access to this information when generating responses. ## Adding Information to Working Memory ✅ You can add information to working memory in several ways: ### 1. During Agent Creation ✅ ```kotlin memory = memory { workingMemory(true) workingMemoryContent("User: Alice, Preferences: Technical explanations, concise responses") } ``` ### 2. Updating Working Memory Programmatically ✅ ```kotlin // Update working memory with new information agent.memory.updateWorkingMemory("User: Alice, Preferences: Technical explanations, concise responses, Examples: Preferred") ``` ### 3. Structured Working Memory ✅ For more complex scenarios, you can use structured working memory: ```kotlin // Create structured working memory val userProfile = mapOf( "name" to "Alice", "preferences" to listOf("Technical explanations", "Concise responses"), "expertise" to "Beginner in quantum physics" ) // Convert to string representation val workingMemoryContent = "User Profile:\n" + "Name: ${userProfile["name"]}\n" + "Preferences: ${(userProfile["preferences"] as List).joinToString(", ")}\n" + "Expertise: ${userProfile["expertise"]}" // Update working memory agent.memory.updateWorkingMemory(workingMemoryContent) ``` ## Best Practices ✅ 1. **Keep It Concise**: Working memory should contain only the most important information that needs to be available in every interaction. 2. **Structured Format**: Use a clear, structured format for working memory to make it easy for the model to parse. 3. **Update Selectively**: Only update working memory when you have new, important information that should persist across the entire conversation. 4. **Combine with Other Memory Types**: Use working memory alongside conversation history and semantic recall for the best results. ## Example: User Preferences Agent ✅ Here's a complete example of an agent that uses working memory to remember user preferences: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create agent with working memory val preferencesAgent = agent { name("PreferencesAgent") description("An agent that remembers user preferences") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure memory memory = memory { workingMemory(true) conversationHistory(10) } } // Initial interaction val response1 = preferencesAgent.generate("Hi, my name is Alice and I prefer technical explanations that are concise.") println("Agent: ${response1.text}") // Extract preferences and update working memory val preferences = "User: Alice, Preferences: Technical explanations, concise responses" preferencesAgent.memory.updateWorkingMemory(preferences) // Next interaction should reflect the preferences val response2 = preferencesAgent.generate("Can you explain quantum entanglement?") println("Agent: ${response2.text}") } ``` ## Next Steps ✅ Now that you understand working memory, you can: 1. Learn about [semantic recall](./semantic-recall.mdx) 2. Explore [memory processors](./memory-processors.mdx) 3. Implement [custom memory storage](./implementations.mdx) --- title: "Logging | Kastrax Observability Documentation" description: "Learn how to configure and use logging in your Kastrax applications for better observability and debugging." --- import { Tabs } from "nextra/components"; # Logging ✅ [EN] Source: https://kastrax.ai/en/docs/observability/logging Kastrax 提供了一个灵活的日志系统,帮助你跟踪应用程序行为、调试问题和监控生产环境中的 AI 代理。本指南介绍如何在 Kastrax 应用程序中配置和使用日志。 ## 日志基础 ✅ Kastrax 使用结构化日志方法,不仅捕获消息,还捕获上下文信息,使日志更容易理解和分析。日志系统支持多种输出格式和目标。 ### 日志级别 ✅ Kastrax 支持以下日志级别,按严重性递增排序: - **TRACE**:极其详细的信息,用于调试特定问题 - **DEBUG**:开发过程中有用的详细信息 - **INFO**:关于应用程序进度的一般信息(默认级别) - **WARN**:不会阻止应用程序工作的潜在问题 - **ERROR**:阻止特定操作正常工作的问题 - **FATAL**:可能导致应用程序终止的严重问题 ## 基本设置 ✅ ```kotlin filename="LoggingExample.kt" import ai.kastrax.observability.logging.LogLevel import ai.kastrax.observability.logging.LoggingConfig import ai.kastrax.observability.logging.LoggingSystem // 配置日志系统 val loggingConfig = LoggingConfig().apply { // 设置日志级别 level = LogLevel.INFO // 启用控制台日志 console = true // 配置文件日志(可选) file = LoggingConfig.FileConfig().apply { enabled = true directory = "logs" filePrefix = "kastrax" } } // 应用日志配置 loggingConfig.apply() // 获取日志记录器 val logger = LoggingSystem.getLogger("MyApplication") // 记录不同级别的日志 logger.debug("这是一条调试日志") logger.info("这是一条信息日志") logger.warn("这是一条警告日志") logger.error("这是一条错误日志", Exception("示例异常")) ``` ```java filename="LoggingExample.java" import ai.kastrax.observability.logging.LogLevel; import ai.kastrax.observability.logging.Logger; import ai.kastrax.observability.logging.LoggingConfig; import ai.kastrax.observability.logging.LoggingSystem; public class LoggingExample { public static void main(String[] args) { // 配置日志系统 LoggingConfig loggingConfig = new LoggingConfig(); // 设置日志级别 loggingConfig.setLevel(LogLevel.INFO); // 启用控制台日志 loggingConfig.setConsole(true); // 配置文件日志(可选) LoggingConfig.FileConfig fileConfig = new LoggingConfig.FileConfig(); fileConfig.setEnabled(true); fileConfig.setDirectory("logs"); fileConfig.setFilePrefix("kastrax"); loggingConfig.setFile(fileConfig); // 应用日志配置 loggingConfig.apply(); // 获取日志记录器 Logger logger = LoggingSystem.getLogger("MyApplication"); // 记录不同级别的日志 logger.debug("这是一条调试日志"); logger.info("这是一条信息日志"); logger.warn("这是一条警告日志"); logger.error("这是一条错误日志", new Exception("示例异常")); } } ``` ```typescript filename="logging-example.ts" import { kastrax } from "./kastrax-instance"; import { createLogger } from "@kastrax/core/logger"; // 创建日志记录器 const logger = createLogger({ name: "MyApplication", level: "info", }); // 记录不同级别的日志 logger.debug("这是一条调试日志"); logger.info("这是一条信息日志"); logger.warn("这是一条警告日志"); logger.error("这是一条错误日志", new Error("示例异常")); ``` 在这个配置中: - 日志级别设置为 `INFO`,这意味着 `INFO` 及以上级别的日志(`INFO`、`WARN`、`ERROR`、`FATAL`)将被记录 - 启用了控制台日志,日志将输出到控制台 - 可选地配置了文件日志,日志将同时写入到文件中 ## 日志配置选项 ✅ Kastrax 提供了多种日志配置选项,可以根据你的需求进行自定义: ### 控制台日志 ✅ 控制台日志将日志输出到标准输出(stdout)或标准错误(stderr): ```kotlin val loggingConfig = LoggingConfig().apply { // 启用控制台日志 console = true // 设置日志级别 level = LogLevel.DEBUG } ``` ### 文件日志 ✅ 文件日志将日志写入到文件中,支持日志轮转: ```kotlin val loggingConfig = LoggingConfig().apply { // 配置文件日志 file = LoggingConfig.FileConfig().apply { enabled = true directory = "logs" // 日志文件目录 filePrefix = "kastrax" // 日志文件前缀 maxFileSize = 10 * 1024 * 1024 // 单个日志文件最大大小(10MB) maxFiles = 10 // 最大保留的日志文件数量 } } ``` ### JSON 日志 ✅ JSON 日志将日志以 JSON 格式写入到文件中,便于日志分析和处理: ```kotlin val loggingConfig = LoggingConfig().apply { // 配置 JSON 日志 json = LoggingConfig.JsonConfig().apply { enabled = true directory = "logs" // 日志文件目录 filePrefix = "kastrax-json" // 日志文件前缀 maxFileSize = 10 * 1024 * 1024 // 单个日志文件最大大小(10MB) maxFiles = 10 // 最大保留的日志文件数量 } } ``` ### 组合日志配置 ✅ 你可以同时启用多种日志配置,日志将同时输出到所有配置的目标: ```kotlin val loggingConfig = LoggingConfig().apply { // 设置日志级别 level = LogLevel.INFO // 启用控制台日志 console = true // 配置文件日志 file = LoggingConfig.FileConfig().apply { enabled = true directory = "logs" filePrefix = "kastrax" } // 配置 JSON 日志 json = LoggingConfig.JsonConfig().apply { enabled = true directory = "logs" filePrefix = "kastrax-json" } } ``` ## 结构化日志 ✅ Kastrax 支持结构化日志,可以在日志中添加额外的上下文信息: ```kotlin filename="StructuredLoggingExample.kt" import ai.kastrax.observability.logging.LogContext import ai.kastrax.observability.logging.LoggingSystem // 获取日志记录器 val logger = LoggingSystem.getLogger("MyApplication") // 使用上下文记录日志 logger.info("处理请求", mapOf( "requestId" to "req-123", "userId" to "user-456", "duration" to 42 )) // 使用 LogContext 在一段代码块中添加上下文 LogContext.withContext( mapOf( "requestId" to "req-123", "userId" to "user-456" ) ) { // 这些日志会自动包含上面定义的上下文 logger.info("开始处理请求") // 处理请求... logger.info("请求处理完成", mapOf("duration" to 42)) } ``` ```java filename="StructuredLoggingExample.java" import ai.kastrax.observability.logging.LogContext; import ai.kastrax.observability.logging.Logger; import ai.kastrax.observability.logging.LoggingSystem; import java.util.HashMap; import java.util.Map; public class StructuredLoggingExample { public static void main(String[] args) { // 获取日志记录器 Logger logger = LoggingSystem.getLogger("MyApplication"); // 使用上下文记录日志 Map context = new HashMap<>(); context.put("requestId", "req-123"); context.put("userId", "user-456"); context.put("duration", 42); logger.info("处理请求", context); // 使用 LogContext 在一段代码块中添加上下文 Map blockContext = new HashMap<>(); blockContext.put("requestId", "req-123"); blockContext.put("userId", "user-456"); LogContext.withContext(blockContext, () -> { // 这些日志会自动包含上面定义的上下文 logger.info("开始处理请求"); // 处理请求... Map resultContext = new HashMap<>(); resultContext.put("duration", 42); logger.info("请求处理完成", resultContext); }); } } ``` ```typescript filename="structured-logging-example.ts" import { createLogger } from "@kastrax/core/logger"; // 创建日志记录器 const logger = createLogger({ name: "MyApplication", level: "info", }); // 使用上下文记录日志 logger.info("处理请求", { requestId: "req-123", userId: "user-456", duration: 42 }); // 使用 withContext 在一段代码块中添加上下文 logger.withContext( { requestId: "req-123", userId: "user-456" }, () => { // 这些日志会自动包含上面定义的上下文 logger.info("开始处理请求"); // 处理请求... logger.info("请求处理完成", { duration: 42 }); } ); ``` ## 与可观测性系统集成 ✅ Kastrax 的日志系统是可观测性系统的一部分,可以与指标和跟踪系统集成,提供全面的可观测性解决方案: ```kotlin filename="ObservabilityExample.kt" import ai.kastrax.observability.ObservabilityConfig import ai.kastrax.observability.ObservabilitySystem import ai.kastrax.observability.logging.LogLevel import ai.kastrax.observability.logging.LoggingConfig // 配置可观测性系统 val config = ObservabilityConfig().apply { // 配置日志系统 logging = LoggingConfig().apply { level = LogLevel.DEBUG console = true file = LoggingConfig.FileConfig().apply { enabled = true directory = "logs" filePrefix = "kastrax" } } // 配置指标系统 metrics.enabled = true // 配置跟踪系统 tracing.enabled = true } // 初始化可观测性系统 ObservabilitySystem.init(config) ``` ## 最佳实践 ✅ ### 日志级别使用指南 - **TRACE**:用于非常详细的调试信息,通常只在开发环境中启用 - **DEBUG**:用于调试信息,帮助开发人员理解应用程序的行为 - **INFO**:用于记录应用程序的正常操作,如请求处理、任务完成等 - **WARN**:用于记录潜在的问题,但不会导致应用程序失败 - **ERROR**:用于记录导致操作失败的错误 - **FATAL**:用于记录导致应用程序崩溃的严重错误 ### 结构化日志 - 使用结构化日志记录上下文信息,而不是将所有信息放在日志消息中 - 使用 `LogContext` 在一段代码块中添加上下文,避免在每条日志中重复添加相同的上下文 - 为日志添加有意义的标签,如 `requestId`、`userId` 等,便于日志分析和关联 ### 性能考虑 - 避免在热路径中生成大量日志,特别是在高并发场景下 - 使用适当的日志级别,避免生成不必要的日志 - 考虑使用异步日志记录器,避免日志记录阻塞主线程 ### 敏感信息处理 - 避免记录敏感信息,如密码、API 密钥等 - 如果必须记录包含敏感信息的数据,确保对敏感字段进行脱敏处理 ## 总结 ✅ Kastrax 提供了一个灵活、强大的日志系统,支持多种日志格式和目标,可以满足不同场景下的日志需求。通过合理配置和使用日志系统,可以帮助你更好地理解应用程序的行为、调试问题和监控生产环境中的 AI 代理。 --- title: "Next.js Tracing | Kastrax Observability Documentation" description: "Set up OpenTelemetry tracing for Next.js applications" --- # Next.js Tracing ✅ [EN] Source: https://kastrax.ai/en/docs/observability/nextjs-tracing Next.js requires additional configuration to enable OpenTelemetry tracing. ### Step 1: Next.js Configuration Start by enabling the instrumentation hook in your Next.js config: ```ts filename="next.config.ts" showLineNumbers copy import type { NextConfig } from "next"; const nextConfig: NextConfig = { experimental: { instrumentationHook: true // Not required in Next.js 15+ } }; export default nextConfig; ``` ### Step 2: Kastrax Configuration Configure your Kastrax instance: ```typescript filename="kastrax.config.ts" copy import { Kastrax } from "@kastrax/core"; export const kastrax = new Kastrax({ // ... other config telemetry: { serviceName: "your-project-name", enabled: true } }); ``` ### Step 3: Configure your providers If you're using Next.js, you have two options for setting up OpenTelemetry instrumentation: #### Option 1: Using a Custom Exporter The default that will work across providers is to configure a custom exporter: 1. Install the required dependencies (example using Langfuse): ```bash copy npm install @opentelemetry/api langfuse-vercel ``` 2. Create an instrumentation file: ```ts filename="instrumentation.ts" copy import { NodeSDK, ATTR_SERVICE_NAME, Resource, } from '@kastrax/core/telemetry/otel-vendor'; import { LangfuseExporter } from 'langfuse-vercel'; export function register() { const exporter = new LangfuseExporter({ // ... Langfuse config }) const sdk = new NodeSDK({ resource: new Resource({ [ATTR_SERVICE_NAME]: 'ai', }), traceExporter: exporter, }); sdk.start(); } ``` #### Option 2: Using Vercel's Otel Setup If you're deploying to Vercel, you can use their OpenTelemetry setup: 1. Install the required dependencies: ```bash copy npm install @opentelemetry/api @vercel/otel ``` 2. Create an instrumentation file at the root of your project (or in the src folder if using one): ```ts filename="instrumentation.ts" copy import { registerOTel } from '@vercel/otel' export function register() { registerOTel({ serviceName: 'your-project-name' }) } ``` ### Summary This setup will enable OpenTelemetry tracing for your Next.js application and Kastrax operations. For more details, see the documentation for: - [Next.js Instrumentation](https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation) - [Vercel OpenTelemetry](https://vercel.com/docs/observability/otel-overview/quickstart) --- title: "Observability Overview | Kastrax Docs" description: "Learn about Kastrax's observability features for monitoring, analyzing, and optimizing AI agent performance." --- # Observability Overview ✅ [EN] Source: https://kastrax.ai/en/docs/observability/overview Kastrax provides comprehensive observability features that allow you to monitor, analyze, and optimize the performance of your AI agents. This guide explains the observability architecture and how to use it effectively. ## What is Observability? ✅ Observability in the context of AI agents refers to the ability to understand the internal state, behavior, and performance of agents by examining their outputs, metrics, and logs. Good observability helps you: - Monitor agent performance and health - Debug issues and understand agent behavior - Optimize agent configurations and prompts - Ensure compliance with policies and guidelines - Track resource usage and costs - Analyze patterns and trends in agent behavior ## Observability Architecture ✅ The Kastrax observability system consists of several key components: 1. **Metrics Collection**: Gathering quantitative data about agent performance 2. **Logging**: Recording detailed information about agent operations 3. **Tracing**: Tracking the flow of requests through the system 4. **Visualization**: Displaying metrics and logs in an intuitive interface 5. **Alerting**: Notifying users of important events or anomalies 6. **Analysis Tools**: Analyzing agent behavior and performance ## Metrics Collection ✅ Kastrax collects various metrics to help you understand agent performance: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.observability.metrics import ai.kastrax.integrations.deepseek.deepSeek import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with metrics collection val agent = agent { name = "ObservableAgent" instructions = "You are a helpful assistant." model = deepSeek { apiKey = "your-deepseek-api-key" } // Enable metrics collection observability { metrics { collectResponseTimes = true collectTokenUsage = true collectPromptSizes = true collectErrorRates = true collectMemoryUsage = true } } } // Generate a response val response = agent.generate("Tell me about Kotlin.") // Access metrics val metrics = agent.metrics println("Response time: ${metrics.lastResponseTimeMs}ms") println("Token usage: ${metrics.lastTokenUsage}") println("Prompt size: ${metrics.lastPromptSize}") println("Error rate: ${metrics.errorRate}") println("Memory usage: ${metrics.memoryUsage}") } ``` ## Logging ✅ Kastrax provides detailed logging to help you understand agent behavior: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.observability.logging import ai.kastrax.integrations.deepseek.deepSeek import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with logging val agent = agent { name = "LoggingAgent" instructions = "You are a helpful assistant." model = deepSeek { apiKey = "your-deepseek-api-key" } // Configure logging observability { logging { level = ai.kastrax.core.observability.LogLevel.DEBUG logPrompts = true logResponses = true logToolCalls = true logMemoryOperations = true logErrors = true // Custom logger logger = object : ai.kastrax.core.observability.Logger { override fun log(level: ai.kastrax.core.observability.LogLevel, message: String) { println("[$level] $message") } } } } } // Generate a response val response = agent.generate("Tell me about Kotlin.") } ``` ## Tracing ✅ Tracing helps you understand the flow of requests through your agent system: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.observability.tracing import ai.kastrax.integrations.deepseek.deepSeek import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with tracing val agent = agent { name = "TracingAgent" instructions = "You are a helpful assistant." model = deepSeek { apiKey = "your-deepseek-api-key" } // Configure tracing observability { tracing { enabled = true sampleRate = 1.0 // Trace all requests exporterType = ai.kastrax.core.observability.TracingExporterType.JAEGER exporterEndpoint = "http://localhost:14268/api/traces" } } } // Generate a response with a trace context val traceContext = ai.kastrax.core.observability.TraceContext("user-request-1") val response = agent.generate("Tell me about Kotlin.", traceContext) } ``` ## Visualization ✅ Kastrax provides visualization tools to help you understand agent performance: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.observability.dashboard import ai.kastrax.integrations.deepseek.deepSeek import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent val agent = agent { name = "VisualizationAgent" instructions = "You are a helpful assistant." model = deepSeek { apiKey = "your-deepseek-api-key" } // Configure observability observability { metrics { /* ... */ } logging { /* ... */ } tracing { /* ... */ } // Configure dashboard dashboard { enabled = true port = 8080 refreshIntervalMs = 5000 accessToken = "your-dashboard-access-token" } } } // Start the dashboard agent.observability.dashboard.start() // Generate some responses repeat(10) { i -> agent.generate("Question $i: Tell me about Kotlin.") } println("Dashboard available at http://localhost:8080") } ``` ## Alerting ✅ Kastrax can alert you when important events or anomalies occur: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.observability.alerting import ai.kastrax.integrations.deepseek.deepSeek import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with alerting val agent = agent { name = "AlertingAgent" instructions = "You are a helpful assistant." model = deepSeek { apiKey = "your-deepseek-api-key" } // Configure alerting observability { alerting { // Alert when response time exceeds threshold alert("high-latency") { condition = { metrics -> metrics.lastResponseTimeMs > 5000 } severity = ai.kastrax.core.observability.AlertSeverity.WARNING message = "High latency detected" actions = listOf( ai.kastrax.core.observability.AlertAction.LOG, ai.kastrax.core.observability.AlertAction.EMAIL ) recipients = listOf("admin@example.com") } // Alert when error rate exceeds threshold alert("high-error-rate") { condition = { metrics -> metrics.errorRate > 0.1 } severity = ai.kastrax.core.observability.AlertSeverity.CRITICAL message = "High error rate detected" actions = listOf( ai.kastrax.core.observability.AlertAction.LOG, ai.kastrax.core.observability.AlertAction.EMAIL, ai.kastrax.core.observability.AlertAction.SMS ) recipients = listOf("admin@example.com", "+1234567890") } } } } // Generate a response val response = agent.generate("Tell me about Kotlin.") } ``` ## Analysis Tools ✅ Kastrax provides tools for analyzing agent behavior and performance: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.observability.analysis import ai.kastrax.integrations.deepseek.deepSeek import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent val agent = agent { name = "AnalysisAgent" instructions = "You are a helpful assistant." model = deepSeek { apiKey = "your-deepseek-api-key" } // Configure analysis tools observability { analysis { enabled = true storageLocation = "/path/to/analysis/data" retentionDays = 30 } } } // Generate some responses repeat(100) { i -> agent.generate("Question $i: Tell me about Kotlin.") } // Analyze agent behavior val analyzer = agent.observability.analyzer // Analyze response times val responseTimeAnalysis = analyzer.analyzeResponseTimes() println("Average response time: ${responseTimeAnalysis.average}ms") println("95th percentile response time: ${responseTimeAnalysis.percentile95}ms") println("Response time trend: ${responseTimeAnalysis.trend}") // Analyze token usage val tokenUsageAnalysis = analyzer.analyzeTokenUsage() println("Average token usage: ${tokenUsageAnalysis.average}") println("Token usage by model: ${tokenUsageAnalysis.byModel}") println("Token usage trend: ${tokenUsageAnalysis.trend}") // Analyze error patterns val errorAnalysis = analyzer.analyzeErrors() println("Common error types: ${errorAnalysis.commonTypes}") println("Error correlations: ${errorAnalysis.correlations}") // Generate recommendations val recommendations = analyzer.generateRecommendations() println("Recommendations:") recommendations.forEach { println("- ${it.description} (impact: ${it.impact})") } } ``` ## Integration with External Systems ✅ Kastrax can integrate with external observability systems: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.observability.integration import ai.kastrax.integrations.deepseek.deepSeek import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an agent with external integrations val agent = agent { name = "IntegratedAgent" instructions = "You are a helpful assistant." model = deepSeek { apiKey = "your-deepseek-api-key" } // Configure external integrations observability { integration { // Prometheus integration prometheus { enabled = true endpoint = "/metrics" port = 9090 } // Grafana integration grafana { enabled = true url = "http://localhost:3000" apiKey = "your-grafana-api-key" dashboardId = "agent-dashboard" } // ELK integration elasticsearch { enabled = true url = "http://localhost:9200" username = "elastic" password = "password" indexPrefix = "kastrax-logs" } // OpenTelemetry integration openTelemetry { enabled = true endpoint = "http://localhost:4317" serviceName = "kastrax-agent" } } } } // Generate a response val response = agent.generate("Tell me about Kotlin.") } ``` ## Conclusion ✅ Kastrax's observability features provide a comprehensive solution for monitoring, analyzing, and optimizing AI agent performance. By leveraging these features, you can gain valuable insights into your agents' behavior, identify issues early, and continuously improve their performance. --- title: "Tracing | Kastrax Observability Documentation" description: "Set up OpenTelemetry tracing for Kastrax applications" --- import Image from "next/image"; # Tracing ✅ [EN] Source: https://kastrax.ai/en/docs/observability/tracing Kastrax supports the OpenTelemetry Protocol (OTLP) for tracing and monitoring your application. When telemetry is enabled, Kastrax automatically traces all core primitives including agent operations, LLM interactions, tool executions, integration calls, workflow runs, and database operations. Your telemetry data can then be exported to any OTEL collector. ## Core Components Kastrax's tracing system is built on OpenTelemetry and consists of several core components: 1. **TracingSystem**: The central entry point for tracing operations 2. **Tracer**: Interface defining core tracing functionality 3. **TraceSpan**: Interface representing a single trace span 4. **TracingConfig**: Configuration for the tracing system ## Basic Usage Here's a simple example of using the tracing system in Kotlin: ```kotlin // Create tracing configuration val config = TracingConfig().apply { serviceName = "my-service" samplingRate = 1.0 // Always sample exporters.otlp.enabled = true exporters.otlp.endpoint = "http://localhost:4318/v1/traces" } // Apply configuration and get tracer val tracer = config.apply() // Set global tracer TracingSystem.setTracer(tracer) // Create a span val span = TracingSystem.createSpan("my-operation") try { // Add attributes span.setAttribute("key", "value") // Perform operation // ... // Add event span.addEvent("operation-completed") // Set success status span.setSuccess() } catch (e: Exception) { // Record exception span.setError("Operation failed") span.recordException(e) throw e } finally { // End span span.end() } ``` You can also use the simplified `withSpan` method: ```kotlin val result = TracingSystem.withSpan("my-operation") { span -> span.setAttribute("key", "value") // Perform operation and return result "operation result" } ``` ## Configuration Options The `TracingConfig` class provides numerous configuration options: ```kotlin val config = TracingConfig().apply { // Service identification serviceName = "my-service" serviceVersion = "1.0.0" serviceNamespace = "kastrax" serviceInstanceId = "instance-1" // Sampling configuration samplingRate = 0.5 // Sample 50% of traces // Exporters configuration exporters.logging.enabled = true // Console logging exporters.otlp.enabled = true // OTLP exporter exporters.otlp.endpoint = "http://localhost:4318/v1/traces" exporters.otlp.headers = mapOf("x-api-key" to "your-api-key") // Context propagation propagation.w3c = true // W3C trace context propagation.b3 = false // B3 format propagation.jaeger = false // Jaeger format } ``` ### JavaScript/TypeScript Configuration For JavaScript/TypeScript applications, you can configure tracing as follows: ```ts filename="kastrax.config.ts" showLineNumbers copy export const kastrax = new Kastrax({ // ... other config telemetry: { serviceName: "my-app", enabled: true, sampling: { type: "always_on", }, export: { type: "otlp", endpoint: "http://localhost:4318", // SigNoz local endpoint }, }, }); ``` The telemetry config accepts these properties: ```ts type OtelConfig = { // Name to identify your service in traces (optional) serviceName?: string; // Enable/disable telemetry (defaults to true) enabled?: boolean; // Control how many traces are sampled sampling?: { type: "ratio" | "always_on" | "always_off" | "parent_based"; probability?: number; // For ratio sampling root?: { probability: number; // For parent_based sampling }; }; // Where to send telemetry data export?: { type: "otlp" | "console"; endpoint?: string; headers?: Record; }; }; ``` See the [OtelConfig reference documentation](../../reference/observability/otel-config.mdx) for more details. ## Environment Variables You can configure the OTLP endpoint and headers through environment variables: ```env filename=".env" copy OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 OTEL_EXPORTER_OTLP_HEADERS=x-api-key=your-api-key ``` Then in your config: ```ts filename="kastrax.config.ts" showLineNumbers copy export const kastrax = new Kastrax({ // ... other config telemetry: { serviceName: "my-app", enabled: true, export: { type: "otlp", // endpoint and headers will be picked up from env vars }, }, }); ``` ## Distributed Tracing Kastrax supports distributed tracing across service boundaries: ```kotlin // Extract context from incoming request val extractedContext = tracer.extractContext(headers) { carrier, key -> carrier[key] } // Create a span with the extracted context val span = tracer.createSpan("handle-request", parent = extractedContext) // When making outgoing requests, inject context val outgoingHeaders = mutableMapOf() tracer.injectContext(span.getContext(), outgoingHeaders) { carrier, key, value -> carrier[key] = value } ``` ## Integration with Agents and Tools Kastrax automatically integrates tracing with agents and tools: ```kotlin // Create agent with tracing val agent = agent { name("TracedAgent") description("Agent with tracing enabled") model = deepSeek { apiKey("your-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) } // Enable tracing tracing { enabled = true level = TracingLevel.DETAILED } } // Create tool with tracing val tool = tool("calculator") { description("Performs basic math calculations") // Enable tracing tracing { enabled = true } execute { params -> // Create span val span = TracingSystem.createSpan("calculator-execution") try { // Perform calculation // ... "Result: 42" } finally { span.end() } } } ``` ## Workflow Tracing Kastrax provides specialized tracing for workflows: ```kotlin // Create workflow tracer val tracer = DataFlowTracer() // Execute workflow val result = workflow.execute(input) // Trace workflow execution val traceResult = tracer.traceWorkflowExecution(workflow, context) // Analyze trace results traceResult.stepTraces.forEach { trace -> println("Step: ${trace.stepId}, Success: ${trace.success}") } // Trace specific variable val variableTrace = tracer.traceVariable(workflow, "value", context) ``` ## Exporters Kastrax supports multiple trace exporters: 1. **Logging Exporter**: Outputs traces to the console 2. **OTLP Exporter**: Sends traces to any OpenTelemetry collector 3. **Custom Exporters**: Implement your own exporters for specific backends ### Example: SigNoz Integration Here's what a traced agent interaction looks like in [SigNoz](https://signoz.io): Agent interaction trace showing spans, LLM calls, and tool executions ## Other Supported Providers For a complete list of supported observability providers and their configuration details, see the [Observability Providers reference](../../reference/observability/providers/). ## Best Practices 1. **Naming Conventions**: Use consistent naming for spans 2. **Attribute Management**: Add key attributes that help with diagnostics 3. **Error Handling**: Always record exceptions and error states 4. **Performance Considerations**: Use appropriate sampling rates to reduce overhead 5. **Context Propagation**: Properly propagate context in distributed systems 6. **Sensitive Data**: Avoid including sensitive information in spans ## Next.js-specific Tracing steps If you're using Next.js, you have three additional configuration steps: 1. Enable the instrumentation hook in `next.config.ts` 2. Configure Kastrax telemetry settings 3. Set up an OpenTelemetry exporter For implementation details, see the [Next.js Tracing](./nextjs-tracing) guide. --- title: "Document Chunking and Embedding in Kastrax | RAG | Kastrax Docs" description: "Learn how to efficiently process, chunk, and embed documents in Kastrax for high-performance retrieval-augmented generation (RAG) applications." --- # Document Chunking and Embedding in Kastrax ✅ [EN] Source: https://kastrax.ai/en/docs/rag/chunking-and-embedding Effective document processing is the foundation of any successful RAG system. Kastrax provides powerful tools for transforming raw documents into optimized chunks and high-quality embeddings that enable accurate retrieval. ## Document Processing Overview ✅ The document processing pipeline in Kastrax consists of two main steps: 1. **Chunking**: Splitting documents into semantically meaningful segments 2. **Embedding**: Converting text chunks into vector representations This process transforms raw documents into a format that can be efficiently stored in vector databases and retrieved based on semantic similarity. ## Creating Documents ✅ Before processing, you need to create a `Document` instance from your content. Kastrax supports multiple document formats: ```kotlin filename="DocumentCreation.kt" import ai.kastrax.rag.document.Document import ai.kastrax.rag.document.DocumentType // Create documents from different sources fun createDocuments() { // From plain text val textDocument = Document.fromText( content = "Your plain text content...", metadata = mapOf("source" to "text", "author" to "John Doe") ) // From HTML val htmlDocument = Document.fromHtml( content = "Your HTML content...", metadata = mapOf("source" to "web", "url" to "https://example.com") ) // From Markdown val markdownDocument = Document.fromMarkdown( content = "# Your Markdown content...", metadata = mapOf("source" to "github", "repository" to "kastrax/docs") ) // From JSON val jsonDocument = Document.fromJson( content = "{ \"key\": \"value\" }", metadata = mapOf("source" to "api", "endpoint" to "/data") ) // From PDF (using the PDF extension) val pdfDocument = Document.fromPdf( filePath = "path/to/document.pdf", metadata = mapOf("source" to "file", "author" to "Jane Smith") ) // From file (auto-detects format based on extension) val fileDocument = Document.fromFile( filePath = "path/to/document.docx", metadata = mapOf("source" to "file", "department" to "HR") ) } ``` Each document can include optional metadata that provides context about the document source, author, creation date, or any other relevant information. This metadata is preserved throughout the processing pipeline and can be used for filtering during retrieval. ## Document Chunking ✅ Chunking is the process of splitting documents into smaller, semantically meaningful segments. Kastrax provides multiple chunking strategies optimized for different document types and use cases: | Strategy | Description | Best For | |----------|-------------|----------| | `RecursiveChunker` | Smart splitting based on content structure | General purpose, preserves semantic units | | `CharacterChunker` | Simple character-based splits | Simple text with uniform structure | | `TokenChunker` | Token-aware splitting | LLM-optimized chunks with precise token counts | | `MarkdownChunker` | Markdown-aware splitting | Markdown documents, preserves headings and structure | | `HtmlChunker` | HTML structure-aware splitting | Web pages, preserves HTML elements | | `JsonChunker` | JSON structure-aware splitting | JSON data, preserves object boundaries | | `LatexChunker` | LaTeX structure-aware splitting | Academic papers, preserves sections and formulas | | `SentenceChunker` | Splits by sentences | Natural language text | | `ParagraphChunker` | Splits by paragraphs | Articles and essays | ### Basic Chunking Example ```kotlin filename="BasicChunking.kt" import ai.kastrax.rag.document.Document import ai.kastrax.rag.chunking.RecursiveChunker import ai.kastrax.rag.chunking.ChunkingOptions // Create a document val document = Document.fromText( content = """Climate change poses significant challenges to global agriculture. Rising temperatures and changing precipitation patterns affect crop yields. Farmers must adapt to these changing conditions to ensure food security. New technologies and farming practices can help mitigate these effects.""".trimIndent(), metadata = mapOf("topic" to "climate change", "domain" to "agriculture") ) // Create a chunker with specific options val chunker = RecursiveChunker( options = ChunkingOptions( chunkSize = 100, // Target size in tokens chunkOverlap = 20, // Overlap between chunks separator = "\n", // Preferred split points keepSeparator = false, // Whether to include separators in chunks extractMetadata = true // Whether to extract additional metadata ) ) // Chunk the document val chunks = chunker.chunk(document) // Process the chunks chunks.forEach { chunk -> println("Chunk: ${chunk.content.take(50)}...") println("Size: ${chunk.tokenCount} tokens") println("Metadata: ${chunk.metadata}") println() } ``` ### Advanced Chunking Options Kastrax provides fine-grained control over the chunking process: ```kotlin filename="AdvancedChunking.kt" import ai.kastrax.rag.document.Document import ai.kastrax.rag.chunking.ChunkerFactory import ai.kastrax.rag.chunking.ChunkingOptions import ai.kastrax.rag.chunking.ChunkingStrategy // Create a document val document = Document.fromMarkdown( content = """# Climate Change and Agriculture ## Introduction Climate change poses significant challenges to global agriculture. ## Effects on Crop Yields Rising temperatures and changing precipitation patterns affect crop yields. ## Adaptation Strategies Farmers must adapt to these changing conditions to ensure food security. ## Technological Solutions New technologies and farming practices can help mitigate these effects.""".trimIndent() ) // Create a chunker using the factory with advanced options val chunker = ChunkerFactory.create( strategy = ChunkingStrategy.MARKDOWN, options = ChunkingOptions( chunkSize = 150, chunkOverlap = 30, keepSeparator = true, extractMetadata = true, metadataExtractors = listOf( // Custom metadata extractors TitleExtractor(), KeywordsExtractor(), SummaryExtractor() ), customSplitters = mapOf( // Custom splitting rules "##" to SplitBehavior.ALWAYS_SPLIT, "*" to SplitBehavior.NEVER_SPLIT ) ) ) // Chunk the document val chunks = chunker.chunk(document) ``` ### Metadata Extraction Kastrax can automatically extract metadata from chunks to enhance retrieval: ```kotlin filename="MetadataExtraction.kt" import ai.kastrax.rag.document.Document import ai.kastrax.rag.chunking.ChunkerFactory import ai.kastrax.rag.chunking.ChunkingStrategy import ai.kastrax.rag.chunking.metadata.LlmMetadataExtractor // Create a document val document = Document.fromText(longArticleText) // Create a chunker with LLM-based metadata extraction val chunker = ChunkerFactory.create( strategy = ChunkingStrategy.RECURSIVE, options = ChunkingOptions( chunkSize = 200, extractMetadata = true, metadataExtractors = listOf( LlmMetadataExtractor( fields = listOf("title", "summary", "keywords", "entities"), llmProvider = "openai", modelName = "gpt-4" ) ) ) ) // Chunk the document with metadata extraction val chunks = chunker.chunk(document) // Access the extracted metadata chunks.forEach { chunk -> val title = chunk.metadata["title"] as? String val summary = chunk.metadata["summary"] as? String val keywords = chunk.metadata["keywords"] as? List println("Title: $title") println("Summary: $summary") println("Keywords: $keywords") println() } ``` **Note:** LLM-based metadata extraction requires an API key for the selected provider. ## Document Embedding ✅ After chunking, the next step is to convert text chunks into vector embeddings. These embeddings capture the semantic meaning of the text and enable similarity-based retrieval. Kastrax provides a flexible embedding system that supports multiple providers and models. ### Embedding Providers Kastrax supports several embedding providers out of the box: | Provider | Models | Features | |----------|--------|----------| | OpenAI | text-embedding-3-small, text-embedding-3-large | High quality, dimension control | | DeepSeek | deepseek-embed-base, deepseek-embed-large | Multilingual support | | Cohere | embed-english-v3.0, embed-multilingual-v3.0 | Strong multilingual capabilities | | HuggingFace | Various open-source models | Self-hosted options | | Vertex AI | textembedding-gecko, textembedding-gecko-multilingual | Enterprise-grade | | Local | MiniLM, BGE, E5 | No API dependency | ### Basic Embedding Example ```kotlin filename="BasicEmbedding.kt" import ai.kastrax.rag.document.Document import ai.kastrax.rag.chunking.RecursiveChunker import ai.kastrax.rag.embedding.OpenAIEmbedder import ai.kastrax.rag.embedding.EmbeddingOptions // Create a document and chunk it val document = Document.fromText("Climate change poses significant challenges...") val chunker = RecursiveChunker() val chunks = chunker.chunk(document) // Create an embedder val embedder = OpenAIEmbedder( options = EmbeddingOptions( modelName = "text-embedding-3-small", dimensions = 1536, // Default dimension apiKey = System.getenv("OPENAI_API_KEY") ) ) // Generate embeddings for all chunks val embeddedChunks = embedder.embed(chunks) // Access the embeddings embeddedChunks.forEach { chunk -> val embedding = chunk.embedding println("Chunk: ${chunk.content.take(30)}...") println("Embedding dimensions: ${embedding.size}") println("First 5 values: ${embedding.take(5)}") println() } ``` ### Using DeepSeek Embeddings ```kotlin filename="DeepSeekEmbedding.kt" import ai.kastrax.rag.embedding.DeepSeekEmbedder import ai.kastrax.rag.embedding.EmbeddingOptions // Create a DeepSeek embedder val embedder = DeepSeekEmbedder( options = EmbeddingOptions( modelName = "deepseek-embed-large", apiKey = System.getenv("DEEPSEEK_API_KEY"), batchSize = 10 // Process 10 chunks at a time ) ) // Generate embeddings val embeddedChunks = embedder.embed(chunks) ``` ### Using Local Embeddings Kastrax supports local embedding models that run without API calls: ```kotlin filename="LocalEmbedding.kt" import ai.kastrax.rag.embedding.LocalEmbedder import ai.kastrax.rag.embedding.LocalEmbeddingOptions // Create a local embedder val embedder = LocalEmbedder( options = LocalEmbeddingOptions( modelName = "BAAI/bge-small-en-v1.5", cacheDir = "/path/to/model/cache", quantize = true // Use quantization for faster inference ) ) // Generate embeddings val embeddedChunks = embedder.embed(chunks) ``` ### Configuring Embedding Dimensions Some embedding models support dimension reduction, which can help: - Decrease storage requirements in vector databases - Reduce computational costs for similarity searches - Improve retrieval performance in some cases ```kotlin filename="DimensionControl.kt" import ai.kastrax.rag.embedding.OpenAIEmbedder import ai.kastrax.rag.embedding.EmbeddingOptions // Create an embedder with reduced dimensions val embedder = OpenAIEmbedder( options = EmbeddingOptions( modelName = "text-embedding-3-small", dimensions = 256, // Reduced from default 1536 apiKey = System.getenv("OPENAI_API_KEY") ) ) // Generate embeddings with reduced dimensions val embeddedChunks = embedder.embed(chunks) ``` ### Batch Processing For large document collections, Kastrax provides efficient batch processing: ```kotlin filename="BatchEmbedding.kt" import ai.kastrax.rag.embedding.EmbeddingBatcher import ai.kastrax.rag.embedding.OpenAIEmbedder // Create an embedder val embedder = OpenAIEmbedder() // Create a batcher for efficient processing val batcher = EmbeddingBatcher( embedder = embedder, batchSize = 20, // Process 20 chunks per batch maxRetries = 3, // Retry failed batches up to 3 times concurrency = 2 // Process 2 batches concurrently ) // Process a large collection of chunks val embeddedChunks = batcher.embedBatches(largeChunkCollection) ``` ### Vector Database Compatibility When storing embeddings in a vector database, ensure that: 1. The vector database index is configured with the same dimensions as your embeddings 2. The similarity metric (cosine, dot product, euclidean) is consistent between embedding and retrieval 3. The vector database supports the embedding format (typically float32 arrays) Kastrax provides utilities to help with vector database integration: ```kotlin filename="VectorDBIntegration.kt" import ai.kastrax.rag.vectordb.VectorDBAdapter import ai.kastrax.rag.vectordb.PineconeAdapter // Create a vector database adapter val vectorDB = PineconeAdapter( indexName = "document-embeddings", dimensions = 1536, metric = "cosine", apiKey = System.getenv("PINECONE_API_KEY") ) // Store embedded chunks val ids = vectorDB.upsert(embeddedChunks) // Later, retrieve similar chunks val query = "How does climate change affect agriculture?" val queryEmbedding = embedder.embedText(query) val similarChunks = vectorDB.search( embedding = queryEmbedding, limit = 5, minScore = 0.7 ) ``` ## Complete RAG Pipeline ✅ Here's a complete example showing the entire document processing pipeline from raw text to vector database storage: ```kotlin filename="CompletePipeline.kt" import ai.kastrax.rag.document.Document import ai.kastrax.rag.chunking.RecursiveChunker import ai.kastrax.rag.chunking.ChunkingOptions import ai.kastrax.rag.embedding.OpenAIEmbedder import ai.kastrax.rag.embedding.EmbeddingOptions import ai.kastrax.rag.vectordb.PineconeAdapter import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 1. Create a document from raw text val document = Document.fromText(""" # Climate Change and Agriculture Climate change poses significant challenges to global agriculture. Rising temperatures and changing precipitation patterns affect crop yields. Farmers must adapt to these changing conditions to ensure food security. ## Effects on Crop Yields Studies have shown that for each degree Celsius of warming, there is a potential reduction in global yields of wheat by 6%, rice by 3.2%, maize by 7.4%, and soybean by 3.1%. ## Adaptation Strategies Farmers are implementing various strategies to adapt to climate change: - Drought-resistant crop varieties - Improved irrigation systems - Diversification of crops - Precision agriculture techniques ## Technological Solutions New technologies can help mitigate the effects of climate change on agriculture: - Weather forecasting systems - Satellite monitoring - AI-powered crop management - Greenhouse innovations """.trimIndent()) // 2. Configure and create a chunker val chunker = RecursiveChunker( options = ChunkingOptions( chunkSize = 200, chunkOverlap = 50, separator = "\n\n", extractMetadata = true ) ) // 3. Chunk the document val chunks = chunker.chunk(document) println("Created ${chunks.size} chunks from the document") // 4. Configure and create an embedder val embedder = OpenAIEmbedder( options = EmbeddingOptions( modelName = "text-embedding-3-small", dimensions = 1536, apiKey = System.getenv("OPENAI_API_KEY") ) ) // 5. Generate embeddings for all chunks val embeddedChunks = embedder.embed(chunks) println("Generated embeddings for ${embeddedChunks.size} chunks") // 6. Configure and create a vector database adapter val vectorDB = PineconeAdapter( indexName = "climate-agriculture", dimensions = 1536, metric = "cosine", apiKey = System.getenv("PINECONE_API_KEY") ) // 7. Store the embedded chunks in the vector database val ids = vectorDB.upsert(embeddedChunks) println("Stored ${ids.size} vectors in the database") // 8. Perform a test query val query = "How does climate change affect wheat production?" println("\nQuerying: $query") // 9. Generate embedding for the query val queryEmbedding = embedder.embedText(query) // 10. Search for similar chunks val searchResults = vectorDB.search( embedding = queryEmbedding, limit = 3, minScore = 0.7, filter = mapOf("domain" to "agriculture") ) // 11. Display the results println("\nSearch Results:") searchResults.forEachIndexed { index, result -> println("\nResult ${index + 1} (Score: ${result.score})") println("Content: ${result.chunk.content}") println("Metadata: ${result.chunk.metadata}") } } ``` ## Alternative Embedding Providers ✅ Kastrax supports multiple embedding providers. Here's how to use different providers in your pipeline: ### Using DeepSeek ```kotlin filename="DeepSeekPipeline.kt" // Create a DeepSeek embedder val embedder = DeepSeekEmbedder( options = EmbeddingOptions( modelName = "deepseek-embed-large", apiKey = System.getenv("DEEPSEEK_API_KEY") ) ) // Generate embeddings val embeddedChunks = embedder.embed(chunks) ``` ### Using Cohere ```kotlin filename="CoherePipeline.kt" // Create a Cohere embedder val embedder = CohereEmbedder( options = EmbeddingOptions( modelName = "embed-multilingual-v3.0", apiKey = System.getenv("COHERE_API_KEY") ) ) // Generate embeddings val embeddedChunks = embedder.embed(chunks) ``` ### Using Local Models ```kotlin filename="LocalModelPipeline.kt" // Create a local embedder val embedder = LocalEmbedder( options = LocalEmbeddingOptions( modelName = "BAAI/bge-small-en-v1.5", cacheDir = "/path/to/model/cache" ) ) // Generate embeddings val embeddedChunks = embedder.embed(chunks) ``` ## Best Practices ✅ ### Chunking Best Practices 1. **Choose the right chunking strategy** for your document type (recursive for general text, markdown for markdown documents, etc.) 2. **Experiment with chunk size** - smaller chunks (100-300 tokens) work better for precise retrieval, larger chunks (500-1000 tokens) provide more context 3. **Use appropriate overlap** (typically 10-20% of chunk size) to prevent information loss at chunk boundaries 4. **Extract metadata** to enhance retrieval with additional context 5. **Preserve document structure** by using structure-aware chunkers for formatted documents ### Embedding Best Practices 1. **Choose the right embedding model** based on your language requirements and quality needs 2. **Consider dimension reduction** for large document collections to reduce storage and computation costs 3. **Use batch processing** for large collections to optimize throughput 4. **Cache embeddings** to avoid regenerating them for unchanged documents 5. **Monitor embedding quality** by testing retrieval performance with representative queries ## Related Resources ✅ For more information on document processing and embeddings in Kastrax, see: - [Vector Databases](./vector-databases.mdx) - Learn how to store and retrieve embeddings - [RAG Retrieval](./retrieval.mdx) - Advanced retrieval techniques for RAG systems - [RAG Evaluation](./evaluation.mdx) - Methods to evaluate and optimize your RAG pipeline - [Chunking Strategies](../reference/rag/chunking.mdx) - Detailed reference for all chunking strategies - [Embedding Models](../reference/rag/embeddings.mdx) - Comprehensive guide to embedding models and providers --- title: RAG System Overview | Kastrax Docs description: Overview of Retrieval-Augmented Generation (RAG) in Kastrax, detailing its capabilities for enhancing LLM outputs with relevant context. --- # RAG System Overview ✅ [EN] Source: https://kastrax.ai/en/docs/rag/overview The Kastrax Retrieval-Augmented Generation (RAG) system provides a comprehensive framework for building knowledge-based applications that combine the power of large language models with external data sources. Built with Kotlin's type safety and concurrency features, the Kastrax RAG system offers high performance, scalability, and integration with various vector databases and embedding models. ## What is RAG? ✅ Retrieval-Augmented Generation (RAG) is a technique that enhances language model outputs by retrieving relevant information from external knowledge sources and incorporating it into the generation process. This approach helps to: - Provide up-to-date information beyond the model's training data - Ground responses in factual, verifiable sources - Reduce hallucinations and improve accuracy - Customize responses based on domain-specific knowledge - Enable agents to work with proprietary or sensitive information - Support complex reasoning with large knowledge bases - Improve performance on specialized domains ## RAG System Architecture ✅ The Kastrax RAG system consists of several key components: 1. **Document Processing**: Loading, chunking, and transforming documents with customizable strategies 2. **Embedding**: Converting text into vector representations using various embedding models 3. **Vector Storage**: Storing and retrieving vectors efficiently with multiple database options 4. **Retrieval**: Finding relevant information based on queries with semantic and hybrid search 5. **Reranking**: Improving retrieval quality through additional ranking models 6. **Context Building**: Constructing effective prompts with retrieved information 7. **Generation**: Producing responses using the enhanced context 8. **Metadata Filtering**: Filtering results based on document metadata 9. **Graph RAG**: Leveraging graph relationships for improved retrieval 10. **Evaluation**: Measuring and optimizing RAG system performance ## Basic RAG Pipeline ✅ Here's a simple example of creating a RAG pipeline in Kastrax: ```kotlin import ai.kastrax.rag.document.DocumentLoader import ai.kastrax.rag.document.TextSplitter import ai.kastrax.rag.embedding.EmbeddingModel import ai.kastrax.rag.vectorstore.VectorStore import ai.kastrax.rag.retrieval.Retriever import ai.kastrax.rag.context.ContextBuilder import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createRagPipeline() = runBlocking { // 1. Load documents val loader = DocumentLoader.fromFile("knowledge_base.pdf") val documents = loader.load() // 2. Split documents into chunks val splitter = TextSplitter.recursive( chunkSize = 1000, chunkOverlap = 200 ) val chunks = splitter.split(documents) // 3. Create embeddings val embeddingModel = EmbeddingModel.create("all-MiniLM-L6-v2") // 4. Store in vector database val vectorStore = VectorStore.create("chroma") vectorStore.addDocuments(chunks, embeddingModel) // 5. Create retriever val retriever = Retriever.fromVectorStore( vectorStore = vectorStore, embeddingModel = embeddingModel, topK = 5 ) // 6. Create context builder val contextBuilder = ContextBuilder.create { template(""" Answer the question based on the following context: Context: {{context}} Question: {{query}} Answer: """.trimIndent()) } // 7. Create RAG agent val ragAgent = agent { name("RAGAgent") description("An agent with RAG capabilities") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Configure RAG rag { retriever(retriever) contextBuilder(contextBuilder) maxTokens(3000) } } // Use the RAG agent val query = "What are the key features of Kastrax?" val response = ragAgent.generate(query) println(response.text) } ``` ## Document Processing ✅ ### Loading Documents ✅ Kastrax supports loading documents from various sources: ```kotlin // Load from a file val fileLoader = DocumentLoader.fromFile("document.pdf") // Load from a directory val dirLoader = DocumentLoader.fromDirectory("documents/") // Load from a URL val webLoader = DocumentLoader.fromUrl("https://example.com/document") // Load from a database val dbLoader = DocumentLoader.fromDatabase( connection = dbConnection, query = "SELECT content FROM documents" ) ``` ### Document Formats ✅ Kastrax supports multiple document formats: - PDF - Word (DOCX) - Text - HTML - Markdown - CSV - JSON - XML - Databases ### Chunking Documents ✅ Documents can be split into chunks for better processing: ```kotlin // Recursive text splitter (splits by paragraphs, then sentences, etc.) val recursiveSplitter = TextSplitter.recursive( chunkSize = 1000, chunkOverlap = 200 ) // Character text splitter val charSplitter = TextSplitter.byCharacter( chunkSize = 1000, chunkOverlap = 200, separator = "\n\n" ) // Token text splitter val tokenSplitter = TextSplitter.byToken( chunkSize = 500, chunkOverlap = 100, encoderName = "cl100k_base" // OpenAI's tokenizer ) // Semantic text splitter val semanticSplitter = TextSplitter.semantic( embeddingModel = embeddingModel, similarityThreshold = 0.7 ) ``` ## Embedding ✅ ### Embedding Models ✅ Kastrax supports various embedding models: ```kotlin // Use a local model val localModel = EmbeddingModel.create("all-MiniLM-L6-v2") // Use OpenAI embeddings val openAiModel = EmbeddingModel.openAi( apiKey = "your-openai-api-key", model = "text-embedding-3-small" ) // Use custom embeddings val customModel = EmbeddingModel.custom( embeddingFunction = { text -> // Custom embedding logic floatArrayOf(/* ... */) }, dimensions = 384 ) ``` ## Vector Storage ✅ ### Vector Stores ✅ Kastrax supports multiple vector stores: ```kotlin // In-memory vector store val memoryStore = VectorStore.inMemory() // Chroma vector store val chromaStore = VectorStore.chroma( collectionName = "documents", url = "http://localhost:8000" ) // FAISS vector store val faissStore = VectorStore.faiss( indexPath = "faiss_index", dimensions = 384 ) // Pinecone vector store val pineconeStore = VectorStore.pinecone( apiKey = "your-pinecone-api-key", environment = "us-west1-gcp", index = "documents" ) ``` ## Retrieval ✅ ### Retrievers ✅ Retrievers find relevant information based on queries: ```kotlin // Vector store retriever val vectorRetriever = Retriever.fromVectorStore( vectorStore = vectorStore, embeddingModel = embeddingModel, topK = 5 ) // Keyword retriever val keywordRetriever = Retriever.keyword( documents = documents, topK = 5 ) // Hybrid retriever (combines vector and keyword search) val hybridRetriever = Retriever.hybrid( vectorRetriever = vectorRetriever, keywordRetriever = keywordRetriever, vectorWeight = 0.7, keywordWeight = 0.3 ) ``` ## Reranking ✅ ### Rerankers ✅ Rerankers improve retrieval quality: ```kotlin // Cross-encoder reranker val crossEncoderReranker = Reranker.crossEncoder( model = "cross-encoder/ms-marco-MiniLM-L-6-v2", topK = 3 ) // LLM reranker val llmReranker = Reranker.llm( llm = deepSeekLlm, prompt = "Rank these documents based on relevance to the query: {{query}}\n\nDocuments:\n{{documents}}", topK = 3 ) ``` ## Complete RAG Example ✅ Here's a complete example of a sophisticated RAG system: ```kotlin import ai.kastrax.rag.* import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createAdvancedRagSystem() = runBlocking { // 1. Load and process documents val loader = DocumentLoader.fromDirectory("knowledge_base/") val documents = loader.load() val splitter = TextSplitter.recursive( chunkSize = 1000, chunkOverlap = 200 ) val chunks = splitter.split(documents) // 2. Set up embedding and vector store val embeddingModel = EmbeddingModel.create("all-MiniLM-L6-v2") val vectorStore = VectorStore.chroma("kastrax_docs") // 3. Add documents to vector store vectorStore.addDocuments( documents = chunks, embeddingModel = embeddingModel ) // 4. Create a hybrid retriever val vectorRetriever = Retriever.fromVectorStore( vectorStore = vectorStore, embeddingModel = embeddingModel, topK = 10 ) val keywordRetriever = Retriever.keyword( documents = chunks, topK = 10 ) val hybridRetriever = Retriever.hybrid( vectorRetriever = vectorRetriever, keywordRetriever = keywordRetriever, vectorWeight = 0.7, keywordWeight = 0.3 ) // 5. Add reranking val reranker = Reranker.crossEncoder( model = "cross-encoder/ms-marco-MiniLM-L-6-v2", topK = 5 ) val enhancedRetriever = Retriever.withReranker( baseRetriever = hybridRetriever, reranker = reranker ) // 6. Create context builder with compression val contextBuilder = ContextBuilder.create { template(""" You are a helpful assistant for Kastrax, a powerful AI Agent framework. Answer the question based on the following context from the Kastrax documentation. Context: {{#each documents}} Document {{@index}} (Source: {{metadata.source}}): {{pageContent}} {{/each}} Question: {{query}} Answer the question using only the information provided in the context. If you don't have enough information, say "I don't have enough information." Always cite the source documents when providing information. """.trimIndent()) compression { enabled(true) method(CompressionMethod.MAP_REDUCE) maxTokens(3000) } } // 7. Create RAG agent val ragAgent = agent { name("KastraxDocumentationAssistant") description("An assistant that helps with Kastrax documentation") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.3) // Lower temperature for more factual responses } // Configure RAG rag { retriever(enhancedRetriever) contextBuilder(contextBuilder) maxTokens(3000) } } // 8. Use the RAG agent val query = "How do I implement a custom tool in Kastrax?" val response = ragAgent.generate(query) println(response.text) } ``` ## Next Steps ✅ Now that you understand the RAG system, you can: 1. Learn about [document processing](./document-processing.mdx) 2. Explore [vector stores](./vector-stores.mdx) 3. Implement [advanced retrieval techniques](./advanced-retrieval.mdx) 4. Set up [RAG evaluation](./evaluation.mdx) --- title: "Retrieval in Kastrax RAG Systems | RAG | Kastrax Docs" description: "Learn how to implement advanced retrieval strategies in Kastrax RAG systems, including semantic search, metadata filtering, reranking, and hybrid retrieval approaches." --- import { Tabs } from "nextra/components"; # Retrieval in Kastrax RAG Systems ✅ [EN] Source: https://kastrax.ai/en/docs/rag/retrieval Retrieval is the critical middle step in any RAG (Retrieval-Augmented Generation) system, responsible for finding the most relevant information to answer user queries. Kastrax provides a comprehensive retrieval system with multiple strategies to ensure high-quality, relevant results. ## Retrieval Architecture ✅ Kastrax's retrieval system is built on a flexible, multi-layered architecture that enables sophisticated information retrieval: 1. **Query Processing**: Transforms user queries into vector embeddings and structured search parameters 2. **Vector Search**: Performs similarity search against embedded documents in vector databases 3. **Metadata Filtering**: Applies structured filters to narrow down results based on document attributes 4. **Reranking**: Refines initial results using more sophisticated relevance algorithms 5. **Result Processing**: Formats and prepares retrieved information for generation This architecture allows for a range of retrieval strategies, from simple vector similarity search to complex hybrid approaches combining multiple retrieval methods. ## How Retrieval Works ✅ The retrieval process in Kastrax follows these key steps: 1. **Query Embedding**: The user's query is converted to a vector embedding using the same model used for document embeddings 2. **Vector Similarity**: This embedding is compared to stored document embeddings using vector similarity metrics (cosine, dot product, or Euclidean distance) 3. **Result Ranking**: The most similar chunks are retrieved and ranked by similarity score 4. **Optional Processing**: Retrieved chunks can be further processed through: - Metadata filtering to narrow results based on document attributes - Reranking to improve relevance using more sophisticated algorithms - Hybrid retrieval combining vector search with keyword or graph-based approaches ## Basic Retrieval ✅ The simplest approach is direct semantic search, which uses vector similarity to find chunks that are semantically similar to the query: ```kotlin filename="BasicRetrieval.kt" showLineNumbers import ai.kastrax.rag.embedding.OpenAIEmbedder import ai.kastrax.rag.vectordb.PgVectorAdapter import ai.kastrax.rag.retrieval.RetrievalOptions // Create an embedder for query embedding val embedder = OpenAIEmbedder( options = EmbeddingOptions( modelName = "text-embedding-3-small", apiKey = System.getenv("OPENAI_API_KEY") ) ) // Generate embedding for the query val query = "What are the main points in the article?" val queryEmbedding = embedder.embedText(query) // Create a vector database adapter val vectorDB = PgVectorAdapter( connectionString = System.getenv("POSTGRES_CONNECTION_STRING") ) // Perform semantic search val results = vectorDB.search( indexName = "document_embeddings", embedding = queryEmbedding, limit = 10, minScore = 0.7 // Only return results with similarity score >= 0.7 ) // Process the results results.forEach { result -> println("Score: ${result.score}") println("Content: ${result.chunk.content.take(100)}...") println("Metadata: ${result.chunk.metadata}") println() } ``` Results include both the document content and a similarity score: ```kotlin // Example search result SearchResult( chunk = EmbeddedChunk( content = "Climate change poses significant challenges...", embedding = floatArrayOf(...), // Vector embedding metadata = mapOf( "source" to "article1.txt", "page" to 1, "category" to "environment" ) ), score = 0.89 // Similarity score (0.0-1.0) ) ``` ### Using the Retrieval Engine Kastrax provides a high-level `RetrievalEngine` that simplifies the retrieval process: ```kotlin filename="RetrievalEngine.kt" showLineNumbers import ai.kastrax.rag.retrieval.RetrievalEngine import ai.kastrax.rag.retrieval.RetrievalOptions // Create a retrieval engine with the vector database val retrievalEngine = RetrievalEngine( vectorDB = vectorDB, embedder = embedder ) // Retrieve relevant chunks for a query val retrievedChunks = retrievalEngine.retrieve( query = "What are the main points in the article?", options = RetrievalOptions( indexName = "document_embeddings", limit = 5, minScore = 0.7 ) ) // Process the retrieved chunks retrievedChunks.forEach { result -> println("Score: ${result.score}") println("Content: ${result.chunk.content}") } ``` The `RetrievalEngine` handles query embedding and search in a single operation, making it easier to implement retrieval in your applications. ## Advanced Retrieval Strategies ✅ ### Metadata Filtering Metadata filtering allows you to narrow down search results based on document attributes. This is particularly useful when you have documents from different sources, time periods, or with specific characteristics. Kastrax provides a unified MongoDB-style query syntax that works across all supported vector databases. Here are examples of different filtering approaches: ```kotlin filename="MetadataFiltering.kt" showLineNumbers import ai.kastrax.rag.retrieval.RetrievalEngine import ai.kastrax.rag.retrieval.RetrievalOptions // Create a retrieval engine val retrievalEngine = RetrievalEngine(vectorDB, embedder) // Simple equality filter val results1 = retrievalEngine.retrieve( query = "What are the main points in the article?", options = RetrievalOptions( indexName = "document_embeddings", filter = mapOf( "source" to "article1.txt" // Only return documents from this source ) ) ) // Numeric comparison val results2 = retrievalEngine.retrieve( query = "What are the main points in the article?", options = RetrievalOptions( indexName = "document_embeddings", filter = mapOf( "price" to mapOf( "$gt" to 100 // Only return documents with price > 100 ) ) ) ) // Multiple conditions val results3 = retrievalEngine.retrieve( query = "What are the main points in the article?", options = RetrievalOptions( indexName = "document_embeddings", filter = mapOf( "category" to "electronics", "price" to mapOf( "$lt" to 1000 // Price < 1000 ), "inStock" to true ) ) ) // Array operations val results4 = retrievalEngine.retrieve( query = "What are the main points in the article?", options = RetrievalOptions( indexName = "document_embeddings", filter = mapOf( "tags" to mapOf( "$in" to listOf("sale", "new") // Tags include either "sale" or "new" ) ) ) ) // Logical operators val results5 = retrievalEngine.retrieve( query = "What are the main points in the article?", options = RetrievalOptions( indexName = "document_embeddings", filter = mapOf( "$or" to listOf( mapOf("category" to "electronics"), mapOf("category" to "accessories") ), "$and" to listOf( mapOf("price" to mapOf("$gt" to 50)), mapOf("price" to mapOf("$lt" to 200)) ) ) ) ) ``` #### Common Filter Operators | Operator | Description | Example | |----------|-------------|--------| | Equality | Direct match | `"category" to "electronics"` | | `$gt` | Greater than | `"price" to mapOf("$gt" to 100)` | | `$gte` | Greater than or equal | `"price" to mapOf("$gte" to 100)` | | `$lt` | Less than | `"price" to mapOf("$lt" to 1000)` | | `$lte` | Less than or equal | `"price" to mapOf("$lte" to 1000)` | | `$in` | In array | `"tags" to mapOf("$in" to listOf("sale", "new"))` | | `$nin` | Not in array | `"tags" to mapOf("$nin" to listOf("discontinued"))` | | `$or` | Logical OR | `"$or" to listOf(mapOf(...), mapOf(...))` | | `$and` | Logical AND | `"$and" to listOf(mapOf(...), mapOf(...))` | | `$not` | Logical NOT | `"$not" to mapOf("category" to "outdated")` | #### Common Use Cases for Metadata Filtering - **Source Filtering**: Limit results to specific document sources or types - **Date Filtering**: Retrieve documents from specific time periods - **Category Filtering**: Focus on documents with specific categories or tags - **Numerical Filtering**: Filter by numerical ranges (e.g., price, rating, page count) - **Attribute Filtering**: Select documents with specific attributes (e.g., language, author, format) - **Combination Filtering**: Combine multiple conditions for precise querying ### Vector Query Tool Sometimes you want to give your agent the ability to query a vector database directly. The Vector Query Tool allows your agent to be in charge of retrieval decisions, combining semantic search with optional filtering and reranking based on the agent's understanding of the user's needs. ```kotlin filename="VectorQueryTool.kt" showLineNumbers import ai.kastrax.rag.vectordb.PgVectorAdapter import ai.kastrax.rag.tools.VectorQueryTool import ai.kastrax.rag.embedding.OpenAIEmbedder import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.AgentBuilder // Create a vector database adapter val vectorDB = PgVectorAdapter( connectionString = System.getenv("POSTGRES_CONNECTION_STRING") ) // Create an embedder val embedder = OpenAIEmbedder() // Create the vector query tool val vectorQueryTool = VectorQueryTool( vectorDB = vectorDB, embedder = embedder, indexName = "document_embeddings", description = "Search for information in the knowledge base" ) // Create an agent with the vector query tool val agent = AgentBuilder() .withTools(listOf(vectorQueryTool)) .withModel("gpt-4") .withSystemPrompt("You are a helpful assistant with access to a knowledge base.") .build() // The agent can now use the tool to query the vector database val result = agent.run("Find information about climate change impacts on agriculture") ``` When creating the tool, pay special attention to the tool's name and description - these help the agent understand when and how to use the retrieval capabilities. For example, you might name it "SearchKnowledgeBase" and describe it as "Search through our documentation to find relevant information about specific topics." The agent can use this tool to: - Formulate semantic search queries - Apply metadata filters - Adjust the number of results - Rerank results based on relevance to the user's question This approach gives the agent more control over the retrieval process, allowing it to adapt its search strategy based on the conversation context and user needs. ## Advanced Retrieval Techniques ✅ ### Hybrid Search Hybrid search combines vector similarity search with traditional keyword search to improve retrieval quality. This approach is particularly useful when you need to find documents that are both semantically similar to a query and contain specific keywords. ```kotlin filename="HybridSearch.kt" showLineNumbers import ai.kastrax.rag.retrieval.HybridRetrievalEngine import ai.kastrax.rag.retrieval.HybridRetrievalOptions // Create a hybrid retrieval engine val hybridEngine = HybridRetrievalEngine( vectorDB = vectorDB, embedder = embedder, textIndexer = textIndexer // For keyword search ) // Perform hybrid search val results = hybridEngine.retrieve( query = "climate change agriculture drought resistant crops", options = HybridRetrievalOptions( indexName = "document_embeddings", vectorWeight = 0.7, // Weight for vector similarity (0.0-1.0) textWeight = 0.3, // Weight for text similarity (0.0-1.0) limit = 10 ) ) ``` Hybrid search is particularly effective for: - Queries containing specific technical terms or proper nouns - Situations where exact keyword matching is important - Improving precision while maintaining recall ### Reranking Reranking improves retrieval quality by applying a more sophisticated relevance model to the initial search results. Kastrax supports multiple reranking approaches: ```kotlin filename="Reranking.kt" showLineNumbers import ai.kastrax.rag.retrieval.RetrievalEngine import ai.kastrax.rag.retrieval.RetrievalOptions import ai.kastrax.rag.reranking.CrossEncoderReranker import ai.kastrax.rag.reranking.RerankerOptions // Create a reranker val reranker = CrossEncoderReranker( options = RerankerOptions( modelName = "cross-encoder/ms-marco-MiniLM-L-6-v2", threshold = 0.7 // Minimum score to include in results ) ) // Create a retrieval engine with reranking val retrievalEngine = RetrievalEngine( vectorDB = vectorDB, embedder = embedder, reranker = reranker ) // Retrieve and rerank results val results = retrievalEngine.retrieve( query = "What are the effects of climate change on agriculture?", options = RetrievalOptions( indexName = "document_embeddings", limit = 20, // Retrieve more initial results for reranking rerank = true // Enable reranking ) ) ``` Reranking is particularly useful for: - Improving precision in the final results - Handling complex or ambiguous queries - Reducing the impact of embedding model limitations ### Contextual Retrieval Contextual retrieval takes into account the conversation history or user context when retrieving information: ```kotlin filename="ContextualRetrieval.kt" showLineNumbers import ai.kastrax.rag.retrieval.ContextualRetrievalEngine import ai.kastrax.rag.retrieval.ContextualRetrievalOptions import ai.kastrax.rag.context.ConversationContext // Create a conversation context val conversationContext = ConversationContext() // Add messages to the context conversationContext.addMessage("user", "Tell me about climate change impacts.") conversationContext.addMessage("assistant", "Climate change has various impacts including rising temperatures, sea level rise, and extreme weather events.") // Create a contextual retrieval engine val contextualEngine = ContextualRetrievalEngine( vectorDB = vectorDB, embedder = embedder ) // Perform contextual retrieval val results = contextualEngine.retrieve( query = "What about its effects on agriculture specifically?", options = ContextualRetrievalOptions( indexName = "document_embeddings", context = conversationContext, contextWeight = 0.3, // Weight for conversation context limit = 5 ) ) ``` Contextual retrieval is valuable for: - Maintaining conversation coherence - Resolving ambiguous references in follow-up questions - Providing more relevant results based on user interests ## Best Practices ✅ ### Query Formulation 1. **Be specific**: Craft queries that are specific and focused on the information you need 2. **Include key terms**: Include important keywords and technical terms in your queries 3. **Avoid ambiguity**: Phrase queries to minimize ambiguity and multiple interpretations 4. **Consider query expansion**: For important queries, try multiple formulations to improve recall ### Retrieval Configuration 1. **Adjust result limits**: Retrieve more results for reranking (e.g., 20-50) but fewer for direct use (e.g., 3-5) 2. **Use appropriate similarity metrics**: Choose the right similarity metric (cosine, dot product, Euclidean) for your embedding model 3. **Set minimum similarity thresholds**: Filter out low-relevance results with minimum score thresholds 4. **Combine retrieval strategies**: Use hybrid search and reranking for important queries ### Metadata Management 1. **Design metadata thoughtfully**: Include metadata fields that will be useful for filtering 2. **Be consistent**: Use consistent naming and formatting for metadata fields 3. **Include hierarchical metadata**: Add category/subcategory relationships for more flexible filtering 4. **Add temporal metadata**: Include creation/update dates to filter by recency ## Complete RAG Pipeline ✅ Here's a complete example showing how retrieval fits into the full RAG pipeline: ```kotlin filename="CompleteRAGPipeline.kt" showLineNumbers import ai.kastrax.rag.document.Document import ai.kastrax.rag.chunking.RecursiveChunker import ai.kastrax.rag.embedding.OpenAIEmbedder import ai.kastrax.rag.vectordb.PineconeAdapter import ai.kastrax.rag.retrieval.RetrievalEngine import ai.kastrax.rag.reranking.CrossEncoderReranker import ai.kastrax.rag.generation.LLMGenerator import ai.kastrax.rag.prompt.PromptTemplate // 1. Document Processing val document = Document.fromText(""" # Climate Change and Agriculture Climate change poses significant challenges to global agriculture. Rising temperatures and changing precipitation patterns affect crop yields. Farmers must adapt to these changing conditions to ensure food security. ## Effects on Crop Yields Studies have shown that for each degree Celsius of warming, there is a potential reduction in global yields of wheat by 6%, rice by 3.2%, maize by 7.4%, and soybean by 3.1%. ## Adaptation Strategies Farmers are implementing various strategies to adapt to climate change: - Drought-resistant crop varieties - Improved irrigation systems - Diversification of crops - Precision agriculture techniques """.trimIndent()) // 2. Chunking val chunker = RecursiveChunker() val chunks = chunker.chunk(document) // 3. Embedding val embedder = OpenAIEmbedder() val embeddedChunks = embedder.embed(chunks) // 4. Vector Storage val vectorDB = PineconeAdapter() vectorDB.upsert("document_embeddings", embeddedChunks) // 5. Reranker Setup val reranker = CrossEncoderReranker() // 6. Retrieval Engine val retrievalEngine = RetrievalEngine( vectorDB = vectorDB, embedder = embedder, reranker = reranker ) // 7. Query and Retrieve val query = "How does climate change affect wheat production?" val retrievedChunks = retrievalEngine.retrieve( query = query, options = RetrievalOptions( indexName = "document_embeddings", limit = 3, rerank = true ) ) // 8. Format Context val context = retrievedChunks.joinToString("\n\n") { it.chunk.content } // 9. Generate Response val promptTemplate = PromptTemplate( template = """ Answer the following question based on the provided context. Context: {{context}} Question: {{question}} Answer: """.trimIndent() ) val prompt = promptTemplate.format(mapOf( "context" to context, "question" to query )) val generator = LLMGenerator() val answer = generator.generate(prompt) // 10. Return Result println("Query: $query") println("Answer: $answer") ``` ## Conclusion ✅ Effective retrieval is the cornerstone of any successful RAG system. Kastrax provides a comprehensive suite of retrieval capabilities that enable you to find the most relevant information for your users' queries. By leveraging advanced techniques like metadata filtering, hybrid search, reranking, and contextual retrieval, you can significantly improve the quality of your RAG system's responses. Remember to follow best practices for query formulation, retrieval configuration, and metadata management to get the most out of Kastrax's retrieval capabilities. The right retrieval strategy depends on your specific use case, data characteristics, and performance requirements. Experiment with different approaches and combinations to find the optimal solution for your application. ### Knowledge Graph Retrieval For documents with complex relationships, knowledge graph retrieval can follow connections between chunks. This approach is particularly useful when: ```kotlin filename="KnowledgeGraphRetrieval.kt" showLineNumbers import ai.kastrax.rag.retrieval.GraphRetrievalEngine import ai.kastrax.rag.retrieval.GraphRetrievalOptions import ai.kastrax.rag.graph.KnowledgeGraph // Create a knowledge graph val knowledgeGraph = KnowledgeGraph() // Add nodes and relationships from embedded chunks embeddedChunks.forEach { chunk -> knowledgeGraph.addNode( id = chunk.id, content = chunk.content, embedding = chunk.embedding, metadata = chunk.metadata ) } // Add relationships between nodes knowledgeGraph.addRelationship( sourceId = "chunk1", targetId = "chunk2", type = "references", weight = 0.8 ) // Create a graph retrieval engine val graphEngine = GraphRetrievalEngine( knowledgeGraph = knowledgeGraph, embedder = embedder ) // Retrieve information using graph traversal val results = graphEngine.retrieve( query = "How does climate change affect agriculture?", options = GraphRetrievalOptions( maxHops = 2, // Maximum relationship traversal depth limit = 5, // Maximum number of results traversalStrategy = "breadth-first" // BFS or DFS ) ) ``` Knowledge graph retrieval is particularly valuable when: - Information is spread across multiple documents - Documents reference each other - You need to traverse relationships to find complete answers - The domain has complex entity relationships --- title: "Vector Databases in Kastrax | RAG | Kastrax Docs" description: "Learn how to store and retrieve vector embeddings in Kastrax using various vector database options for high-performance similarity search in RAG applications." --- import { Tabs } from "nextra/components"; # Vector Databases in Kastrax ✅ [EN] Source: https://kastrax.ai/en/docs/rag/vector-databases Vector databases are specialized storage systems designed to efficiently store, index, and query high-dimensional vector embeddings. They are a critical component of any RAG system, enabling fast similarity search across large collections of embedded documents. ## Vector Database Architecture ✅ Kastrax provides a unified interface for working with vector databases, abstracting away the differences between various providers while still allowing access to provider-specific features. The architecture consists of: 1. **VectorDB Interface**: A common API for all vector database operations 2. **Database Adapters**: Provider-specific implementations of the interface 3. **Query Builders**: Tools for constructing complex vector queries 4. **Metadata Management**: Systems for storing and retrieving document metadata 5. **Filtering Capabilities**: Methods for filtering results based on metadata This architecture enables you to switch between vector database providers with minimal code changes, while still leveraging the unique capabilities of each provider. ## Supported Vector Databases ✅ Kastrax supports a wide range of vector database providers, from self-hosted options to managed cloud services. Each provider has its own strengths and use cases. ```kotlin filename="PgVectorExample.kt" showLineNumbers import ai.kastrax.rag.vectordb.PgVectorAdapter import ai.kastrax.rag.vectordb.IndexOptions import ai.kastrax.rag.document.EmbeddedChunk // Create a PostgreSQL vector database adapter val vectorDB = PgVectorAdapter( connectionString = System.getenv("POSTGRES_CONNECTION_STRING"), schema = "public" // Optional: specify schema ) // Create an index with the appropriate dimension val indexOptions = IndexOptions( indexName = "document_embeddings", dimensions = 1536, metric = "cosine" // Similarity metric: cosine, dot_product, or euclidean ) vectorDB.createIndex(indexOptions) // Store embeddings in the database vectorDB.upsert( indexName = "document_embeddings", chunks = embeddedChunks // List of EmbeddedChunk objects ) ``` ### Using PostgreSQL with pgvector PostgreSQL with the pgvector extension is a good solution for teams already using PostgreSQL who want to minimize infrastructure complexity. It provides solid performance for small to medium-sized collections and integrates well with existing PostgreSQL workflows. Key features: - Supports multiple distance metrics (cosine, dot product, Euclidean) - Efficient indexing with HNSW and IVFFlat algorithms - Familiar SQL interface for complex queries - Transactional guarantees For detailed setup instructions and best practices, see the [official pgvector repository](https://github.com/pgvector/pgvector). ```kotlin filename="PineconeExample.kt" showLineNumbers import ai.kastrax.rag.vectordb.PineconeAdapter import ai.kastrax.rag.vectordb.IndexOptions // Create a Pinecone vector database adapter val vectorDB = PineconeAdapter( apiKey = System.getenv("PINECONE_API_KEY"), environment = "gcp-starter" // Optional: specify environment ) // Create an index with the appropriate dimension val indexOptions = IndexOptions( indexName = "document_embeddings", dimensions = 1536, metric = "cosine", // Similarity metric: cosine, dotproduct, or euclidean pods = 1, // Number of pods (Pinecone-specific) podType = "p1.x1" // Pod type (Pinecone-specific) ) vectorDB.createIndex(indexOptions) // Store embeddings in the database vectorDB.upsert( indexName = "document_embeddings", chunks = embeddedChunks ) ``` ### Using Pinecone Pinecone is a fully managed vector database designed specifically for vector search at scale. It's a good choice for production applications that require high performance and reliability. Key features: - Optimized for low-latency, high-throughput vector search - Automatic scaling and load balancing - Support for metadata filtering - Multiple distance metrics - Global distribution options ```kotlin filename="QdrantExample.kt" showLineNumbers import ai.kastrax.rag.vectordb.QdrantAdapter import ai.kastrax.rag.vectordb.IndexOptions // Create a Qdrant vector database adapter val vectorDB = QdrantAdapter( url = System.getenv("QDRANT_URL"), apiKey = System.getenv("QDRANT_API_KEY") ) // Create an index with the appropriate dimension val indexOptions = IndexOptions( indexName = "document_embeddings", dimensions = 1536, metric = "cosine", // Similarity metric: cosine, dot, or euclid // Qdrant-specific options hnsw = HnswConfig( m = 16, // Number of connections per element efConstruction = 100 // Size of the dynamic candidate list for construction ) ) vectorDB.createIndex(indexOptions) // Store embeddings in the database vectorDB.upsert( indexName = "document_embeddings", chunks = embeddedChunks ) ``` ### Using Qdrant Qdrant is a vector database focused on extended filtering, high performance, and ease of use. It can be self-hosted or used as a managed service. Key features: - Advanced filtering capabilities - HNSW indexing for fast approximate search - Support for multiple collections and sharding - Optimized for both cloud and on-premises deployments - Active development and community support ```kotlin filename="ChromaExample.kt" showLineNumbers import ai.kastrax.rag.vectordb.ChromaAdapter import ai.kastrax.rag.vectordb.IndexOptions // Create a Chroma vector database adapter val vectorDB = ChromaAdapter( url = System.getenv("CHROMA_URL"), apiKey = System.getenv("CHROMA_API_KEY") ) // Create an index with the appropriate dimension val indexOptions = IndexOptions( indexName = "document_embeddings", dimensions = 1536, metric = "cosine" // Similarity metric: cosine, dot_product, or l2 ) vectorDB.createIndex(indexOptions) // Store embeddings in the database vectorDB.upsert( indexName = "document_embeddings", chunks = embeddedChunks ) ``` ### Using Chroma Chroma is an open-source embedding database designed for ease of use and flexibility. It's a good choice for developers who want a simple, developer-friendly vector database that can be self-hosted. Key features: - Simple API and easy setup - Support for multiple embedding models - Persistent and in-memory storage options - Metadata filtering - Active open-source community ```kotlin filename="AstraExample.kt" showLineNumbers import ai.kastrax.rag.vectordb.AstraAdapter import ai.kastrax.rag.vectordb.IndexOptions // Create an Astra vector database adapter val vectorDB = AstraAdapter( token = System.getenv("ASTRA_DB_TOKEN"), endpoint = System.getenv("ASTRA_DB_ENDPOINT"), keyspace = System.getenv("ASTRA_DB_KEYSPACE") ) // Create an index with the appropriate dimension val indexOptions = IndexOptions( indexName = "document_embeddings", dimensions = 1536, metric = "cosine" // Similarity metric: cosine, dot, or euclidean ) vectorDB.createIndex(indexOptions) // Store embeddings in the database vectorDB.upsert( indexName = "document_embeddings", chunks = embeddedChunks ) ``` ### Using DataStax Astra DB Astra DB is a cloud-native vector database built on Apache Cassandra, offering a fully managed service with vector search capabilities. It's a good choice for enterprise applications that need scalability and reliability. Key features: - Built on Apache Cassandra for high availability - Serverless pricing model - Global distribution - Enterprise-grade security - Integrated with the DataStax ecosystem ```kotlin filename="LibSQLExample.kt" showLineNumbers import ai.kastrax.rag.vectordb.LibSQLAdapter import ai.kastrax.rag.vectordb.IndexOptions // Create a LibSQL vector database adapter val vectorDB = LibSQLAdapter( connectionUrl = System.getenv("DATABASE_URL"), authToken = System.getenv("DATABASE_AUTH_TOKEN") // Optional: for Turso cloud databases ) // Create an index with the appropriate dimension val indexOptions = IndexOptions( indexName = "document_embeddings", dimensions = 1536, metric = "cosine" // Similarity metric: cosine, dot, or euclidean ) vectorDB.createIndex(indexOptions) // Store embeddings in the database vectorDB.upsert( indexName = "document_embeddings", chunks = embeddedChunks ) ``` ### Using LibSQL LibSQL is a fork of SQLite with additional features, including vector search capabilities. It's a good choice for lightweight applications or edge deployments where a full database server might be overkill. Key features: - Lightweight and embeddable - Compatible with SQLite - Simple setup and maintenance - Good for edge computing and local applications - Serverless options via Turso ```kotlin filename="UpstashExample.kt" showLineNumbers import ai.kastrax.rag.vectordb.UpstashAdapter import ai.kastrax.rag.vectordb.IndexOptions // Create an Upstash vector database adapter val vectorDB = UpstashAdapter( url = System.getenv("UPSTASH_URL"), token = System.getenv("UPSTASH_TOKEN") ) // Create an index with the appropriate dimension val indexOptions = IndexOptions( indexName = "document_embeddings", dimensions = 1536, metric = "cosine" // Similarity metric: cosine, dot, or euclidean ) vectorDB.createIndex(indexOptions) // Store embeddings in the database vectorDB.upsert( indexName = "document_embeddings", chunks = embeddedChunks ) ``` ### Using Upstash Upstash provides a serverless vector database built on Redis, offering a simple, pay-per-use model. It's a good choice for applications that need low latency and simple setup. Key features: - Serverless architecture - Pay-per-use pricing - Redis compatibility - Low latency - Simple API ```kotlin filename="CloudflareExample.kt" showLineNumbers import ai.kastrax.rag.vectordb.CloudflareAdapter import ai.kastrax.rag.vectordb.IndexOptions // Create a Cloudflare Vectorize adapter val vectorDB = CloudflareAdapter( accountId = System.getenv("CF_ACCOUNT_ID"), apiToken = System.getenv("CF_API_TOKEN") ) // Create an index with the appropriate dimension val indexOptions = IndexOptions( indexName = "document_embeddings", dimensions = 1536, metric = "cosine" // Similarity metric: cosine, dot, or euclidean ) vectorDB.createIndex(indexOptions) // Store embeddings in the database vectorDB.upsert( indexName = "document_embeddings", chunks = embeddedChunks ) ``` ### Using Cloudflare Vectorize Cloudflare Vectorize is a vector database integrated with Cloudflare Workers, offering edge-based vector search capabilities. It's a good choice for applications already using Cloudflare's ecosystem. Key features: - Edge-based vector search - Global distribution via Cloudflare's network - Integration with Cloudflare Workers - Low latency - Serverless architecture ```kotlin filename="MongoDBExample.kt" showLineNumbers import ai.kastrax.rag.vectordb.MongoDBAdapter import ai.kastrax.rag.vectordb.IndexOptions // Create a MongoDB vector database adapter val vectorDB = MongoDBAdapter( url = System.getenv("MONGODB_URL"), database = System.getenv("MONGODB_DATABASE") ) // Create an index with the appropriate dimension val indexOptions = IndexOptions( indexName = "document_embeddings", dimensions = 1536, metric = "cosine" // Similarity metric: cosine, dot, or euclidean ) vectorDB.createIndex(indexOptions) // Store embeddings in the database vectorDB.upsert( indexName = "document_embeddings", chunks = embeddedChunks ) ``` ### Using MongoDB Atlas MongoDB Atlas offers vector search capabilities through its Atlas Search feature. It's a good choice for applications already using MongoDB that want to add vector search capabilities. Key features: - Integration with existing MongoDB data - Familiar MongoDB query language - Scalable and managed service - Combined vector and traditional search - Enterprise features like backups and security ## Vector Database Operations ✅ Once you've set up your vector database, you can perform various operations on your embedded documents. ### Creating Indexes Before storing embeddings, you need to create an index with the appropriate dimension size for your embedding model: ```kotlin filename="CreateIndex.kt" showLineNumbers import ai.kastrax.rag.vectordb.VectorDBAdapter import ai.kastrax.rag.vectordb.IndexOptions // Create an index with the appropriate dimension val indexOptions = IndexOptions( indexName = "document_embeddings", dimensions = 1536, // Must match your embedding model's output dimension metric = "cosine", // Similarity metric: cosine, dot_product, or euclidean // Optional provider-specific settings shards = 1, // Number of shards (for distributed databases) replicas = 1 // Number of replicas (for high availability) ) // Create the index vectorDB.createIndex(indexOptions) ``` The dimension size must match the output dimension of your chosen embedding model. Common dimension sizes are: - OpenAI text-embedding-3-small: 1536 dimensions (or custom, e.g., 256) - DeepSeek embed-base: 1024 dimensions - Cohere embed-multilingual-v3: 1024 dimensions - Google text-embedding-004: 768 dimensions (or custom) > **Important**: Index dimensions cannot be changed after creation. To use a different model, you must delete and recreate the index with the new dimension size. ### Similarity Search The most common operation is similarity search, which finds documents similar to a query embedding: ```kotlin filename="SimilaritySearch.kt" showLineNumbers import ai.kastrax.rag.vectordb.VectorDBAdapter import ai.kastrax.rag.embedding.OpenAIEmbedder // Create an embedder for query embedding val embedder = OpenAIEmbedder() // Generate embedding for the query val query = "How does climate change affect agriculture?" val queryEmbedding = embedder.embedText(query) // Search for similar documents val searchResults = vectorDB.search( indexName = "document_embeddings", embedding = queryEmbedding, limit = 5, // Return top 5 results minScore = 0.7 // Only return results with similarity score >= 0.7 ) // Process the results searchResults.forEach { result -> println("Score: ${result.score}") println("Content: ${result.chunk.content.take(100)}...") println("Metadata: ${result.chunk.metadata}") println() } ``` ### Metadata Filtering Most vector databases support filtering results based on metadata, allowing you to combine semantic search with traditional filtering: ```kotlin filename="FilteredSearch.kt" showLineNumbers // Search with metadata filtering val searchResults = vectorDB.search( indexName = "document_embeddings", embedding = queryEmbedding, filter = mapOf( "category" to "science", // Only return documents with category="science" "year" to mapOf( "$gte" to 2020 // Only return documents with year >= 2020 ), "authors" to mapOf( "$in" to listOf("Smith", "Johnson") // Author must be in this list ) ), limit = 5 ) ``` > **Note**: Filter syntax varies by database provider. Check the specific adapter documentation for details on the supported filter operations. ### Hybrid Search Some vector databases support hybrid search, combining vector similarity with keyword or full-text search: ```kotlin filename="HybridSearch.kt" showLineNumbers // Perform hybrid search (vector + keyword) val hybridResults = vectorDB.hybridSearch( indexName = "document_embeddings", embedding = queryEmbedding, text = "climate agriculture drought", // Keyword search vectorWeight = 0.7, // Weight for vector similarity (0.0-1.0) textWeight = 0.3, // Weight for text similarity (0.0-1.0) limit = 5 ) ``` ### CRUD Operations Vector databases support standard CRUD (Create, Read, Update, Delete) operations: ```kotlin filename="CrudOperations.kt" showLineNumbers // Create/Update: Upsert embeddings (already seen above) val ids = vectorDB.upsert( indexName = "document_embeddings", chunks = embeddedChunks ) // Read: Get specific documents by ID val documents = vectorDB.get( indexName = "document_embeddings", ids = listOf("doc1", "doc2", "doc3") ) // Delete: Remove documents by ID vectorDB.delete( indexName = "document_embeddings", ids = listOf("doc1", "doc2") ) // Delete by filter: Remove documents matching criteria vectorDB.deleteByFilter( indexName = "document_embeddings", filter = mapOf("category" to "outdated") ) ``` ### Naming Rules for Databases Each vector database enforces specific naming conventions for indexes and collections to ensure compatibility and prevent conflicts. Index names must: - Start with a letter or underscore - Contain only letters, numbers, and underscores - Example: `my_index_123` is valid - Example: `my-index` is not valid (contains hyphen) Index names must: - Use only lowercase letters, numbers, and dashes - Not contain dots (used for DNS routing) - Not use non-Latin characters or emojis - Have a combined length (with project ID) under 52 characters - Example: `my-index-123` is valid - Example: `my.index` is not valid (contains dot) Collection names must: - Be 1-255 characters long - Not contain any of these special characters: - `< > : " / \ | ? *` - Null character (`\0`) - Unit separator (`\u{1F}`) - Example: `my_collection_123` is valid - Example: `my/collection` is not valid (contains slash) Collection names must: - Be 3-63 characters long - Start and end with a letter or number - Contain only letters, numbers, underscores, or hyphens - Not contain consecutive periods (..) - Not be a valid IPv4 address - Example: `my-collection-123` is valid - Example: `my..collection` is not valid (consecutive periods) Collection names must: - Not be empty - Be 48 characters or less - Contain only letters, numbers, and underscores - Example: `my_collection_123` is valid - Example: `my-collection` is not valid (contains hyphen) Index names must: - Start with a letter or underscore - Contain only letters, numbers, and underscores - Example: `my_index_123` is valid - Example: `my-index` is not valid (contains hyphen) Namespace names must: - Be 2-100 characters long - Contain only: - Alphanumeric characters (a-z, A-Z, 0-9) - Underscores, hyphens, dots - Not start or end with special characters (_, -, .) - Can be case-sensitive - Example: `MyNamespace123` is valid - Example: `_namespace` is not valid (starts with underscore) Index names must: - Start with a letter - Be shorter than 32 characters - Contain only lowercase ASCII letters, numbers, and dashes - Use dashes instead of spaces - Example: `my-index-123` is valid - Example: `My_Index` is not valid (uppercase and underscore) Collection (index) names must: - Start with a letter or underscore - Be up to 120 bytes long - Contain only letters, numbers, underscores, or dots - Cannot contain `$` or the null character - Example: `my_collection.123` is valid - Example: `my-index` is not valid (contains hyphen) - Example: `My$Collection` is not valid (contains `$`) ### Upserting Embeddings After creating an index, you can store embeddings along with their basic metadata: ```ts filename="store-embeddings.ts" showLineNumbers copy // Store embeddings with their corresponding metadata await store.upsert({ indexName: 'myCollection', // index name vectors: embeddings, // array of embedding vectors metadata: chunks.map(chunk => ({ text: chunk.text, // The original text content id: chunk.id // Optional unique identifier })) }); ``` The upsert operation: - Takes an array of embedding vectors and their corresponding metadata - Updates existing vectors if they share the same ID - Creates new vectors if they don't exist - Automatically handles batching for large datasets For complete examples of upserting embeddings in different vector stores, see the [Upsert Embeddings](../../examples/rag/upsert/upsert-embeddings.mdx) guide. ## Adding Metadata ✅ Vector stores support rich metadata (any JSON-serializable fields) for filtering and organization. Since metadata is stored with no fixed schema, use consistent field naming to avoid unexpected query results. **Important**: Metadata is crucial for vector storage - without it, you'd only have numerical embeddings with no way to return the original text or filter results. Always store at least the source text as metadata. ```ts showLineNumbers copy // Store embeddings with rich metadata for better organization and filtering await store.upsert({ indexName: "myCollection", vectors: embeddings, metadata: chunks.map((chunk) => ({ // Basic content text: chunk.text, id: chunk.id, // Document organization source: chunk.source, category: chunk.category, // Temporal metadata createdAt: new Date().toISOString(), version: "1.0", // Custom fields language: chunk.language, author: chunk.author, confidenceScore: chunk.score, })), }); ``` Key metadata considerations: - Be strict with field naming - inconsistencies like 'category' vs 'Category' will affect queries - Only include fields you plan to filter or sort by - extra fields add overhead - Add timestamps (e.g., 'createdAt', 'lastUpdated') to track content freshness ## Best Practices ✅ ### Performance Optimization 1. **Choose the right vector database** for your specific needs: - For small to medium datasets with existing PostgreSQL: Use pgvector - For large-scale production: Consider Pinecone, Qdrant, or MongoDB Atlas - For edge deployment: Consider LibSQL or Chroma - For serverless: Consider Upstash or Cloudflare Vectorize 2. **Optimize index configuration**: - Use appropriate indexing algorithms (HNSW, IVFFlat, etc.) based on your dataset size - Balance recall vs. performance with index parameters - Consider sharding for very large datasets 3. **Batch operations**: - Use batch operations for large insertions (the upsert method handles batching automatically) - Process embeddings in batches to avoid memory issues - Consider async operations for non-blocking performance ### Data Management 1. **Metadata strategy**: - Only store metadata you'll query against - Be consistent with field naming (e.g., 'category' vs 'Category') - Add timestamps (e.g., 'createdAt', 'lastUpdated') to track content freshness 2. **Dimension management**: - Match embedding dimensions to your model (e.g., 1536 for `text-embedding-3-small`) - Consider dimension reduction for large collections - Create indexes before bulk insertions 3. **Data lifecycle**: - Implement TTL (Time-To-Live) for temporary data - Set up regular reindexing for optimal performance - Create backup strategies for critical data ## Integration with RAG Pipeline ✅ Vector databases are a critical component of the RAG pipeline in Kastrax. Here's how they integrate with other components: ```kotlin filename="CompleteRAGPipeline.kt" showLineNumbers import ai.kastrax.rag.document.Document import ai.kastrax.rag.chunking.RecursiveChunker import ai.kastrax.rag.embedding.OpenAIEmbedder import ai.kastrax.rag.vectordb.PineconeAdapter import ai.kastrax.rag.retrieval.RetrievalEngine import ai.kastrax.rag.generation.LLMGenerator // 1. Document Processing val document = Document.fromText("Your document content...") val chunker = RecursiveChunker() val chunks = chunker.chunk(document) // 2. Embedding Generation val embedder = OpenAIEmbedder() val embeddedChunks = embedder.embed(chunks) // 3. Vector Database Storage val vectorDB = PineconeAdapter() vectorDB.upsert("document_embeddings", embeddedChunks) // 4. Retrieval val retrievalEngine = RetrievalEngine(vectorDB) val query = "How does climate change affect agriculture?" val retrievedChunks = retrievalEngine.retrieve( query = query, indexName = "document_embeddings", limit = 5 ) // 5. Generation val generator = LLMGenerator() val answer = generator.generate( query = query, context = retrievedChunks.joinToString("\n\n") { it.chunk.content }, prompt = "Answer the question based on the provided context." ) println("Query: $query") println("Answer: $answer") ``` ## Conclusion ✅ Vector databases are a fundamental component of any RAG system, enabling efficient storage and retrieval of vector embeddings. Kastrax provides a unified interface for working with various vector database providers, allowing you to choose the right solution for your specific needs while maintaining a consistent API. Key takeaways: 1. **Choose the right database** based on your scale, performance requirements, and existing infrastructure 2. **Configure indexes properly** to match your embedding model dimensions and similarity metrics 3. **Use metadata effectively** to enable filtering and hybrid search capabilities 4. **Implement best practices** for performance optimization and data management 5. **Integrate seamlessly** with the rest of your RAG pipeline By following these guidelines, you can build a robust and efficient vector storage system that forms the backbone of your RAG applications. --- title: Storage in Kastrax | Kastrax Docs description: Overview of Kastrax's storage system and data persistence capabilities. --- import { Tabs } from "nextra/components"; import { PropertiesTable } from "@/components/properties-table"; import { SchemaTable } from "@/components/schema-table"; import { StorageOverviewImage } from "@/components/storage-overview-image"; # KastraxStorage [EN] Source: https://kastrax.ai/en/docs/storage/overview `KastraxStorage` provides a unified interface for managing: - **Suspended Workflows**: the serialized state of suspended workflows (so they can be resumed later) - **Memory**: threads and messages per `resourceId` in your application - **Traces**: OpenTelemetry traces from all components of Kastrax - **Eval Datasets**: scores and scoring reasons from eval runs

Kastrax provides different storage providers, but you can treat them as interchangeable. Eg, you could use libsql in development but postgres in production, and your code will work the same both ways. ## Configuration Kastrax can be configured with a default storage option: ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.storage.libsql.LibSQLStore val kastraxInstance = kastrax { storage = LibSQLStore( url = "file:./kastrax.db" ) } ``` ## Data Schema Stores conversation messages and their metadata. Each message belongs to a thread and contains the actual content along with metadata about the sender role and message type.
Groups related messages together and associates them with a resource. Contains metadata about the conversation.
When `suspend` is called on a workflow, its state is saved in the following format. When `resume` is called, that state is rehydrated.
Stores eval results from running metrics against agent outputs.
Captures OpenTelemetry traces for monitoring and debugging.
## Storage Providers Kastrax supports the following providers: - For local development, check out [LibSQL Storage](../../reference/storage/libsql.mdx) - For production, check out [PostgreSQL Storage](../../reference/storage/postgresql.mdx) - For serverless deployments, check out [Upstash Storage](../../reference/storage/upstash.mdx) --- title: "Tools System Overview | Kastrax Docs" description: "The Kastrax tools system allows agents to interact with external systems, access data, and perform actions with type-safe interfaces." --- # Tools System Overview ✅ [EN] Source: https://kastrax.ai/en/docs/tools/overview The Kastrax tools system allows agents to interact with external systems, access data, and perform actions. This guide explains the tools system architecture and how to use it effectively. ## Tools System Architecture ✅ The Kastrax tools system consists of several key components: 1. **Tool Interface**: The `Tool` interface defines the contract for all tools in Kastrax 2. **Tool Factory**: The `ToolFactory` interface provides a way to create tools dynamically 3. **Tool Execution**: The execution mechanism that allows agents to invoke tools 4. **Tool Parameters**: Type-safe parameter definitions with validation 5. **Tool Results**: Structured results with error handling 6. **Zod Tools**: Tools with schema validation using the Zod library Kastrax provides several built-in tool types: - **File Operation Tools**: For reading, writing, and manipulating files - **DateTime Tools**: For working with dates and times - **Web Search Tools**: For searching the web and retrieving information - **Calculator Tools**: For performing mathematical calculations - **Custom Tools**: Create your own tools for specific use cases ## Basic Tool Creation ✅ Here's a simple example of creating a tool: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createAgentWithTools() = agent { name("ToolAgent") description("An agent with tool capabilities") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Add tools tools { // Simple tool with no parameters tool("getCurrentTime") { description("Get the current time") parameters {} execute { val currentTime = java.time.LocalDateTime.now() val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val formattedTime = currentTime.format(formatter) "The current time is $formattedTime" } } // Tool with parameters tool("calculateSum") { description("Calculate the sum of two numbers") parameters { parameter("a", "The first number", Double::class) parameter("b", "The second number", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double val sum = a + b "The sum of $a and $b is $sum" } } } } fun main() = runBlocking { val agent = createAgentWithTools() // Test the agent println(agent.generate("What time is it now?").text) println(agent.generate("Calculate the sum of 5.2 and 3.8").text) } ``` ## Tool Parameters Tools can have parameters of various types: ```kotlin tool("searchDatabase") { description("Search a database for records") parameters { parameter("query", "The search query", String::class) parameter("limit", "Maximum number of results", Int::class, optional = true, defaultValue = 10) parameter("sortBy", "Field to sort by", String::class, optional = true) parameter("ascending", "Sort in ascending order", Boolean::class, optional = true, defaultValue = true) } execute { params -> val query = params["query"] as String val limit = params["limit"] as Int val sortBy = params["sortBy"] as String? val ascending = params["ascending"] as Boolean // Perform database search // ... "Found $limit results for query '$query'" } } ``` ## Tool Categories You can organize tools into categories: ```kotlin tools { // Math tools category("Math") { tool("add") { description("Add two numbers") parameters { parameter("a", "First number", Double::class) parameter("b", "Second number", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double "Result: ${a + b}" } } tool("subtract") { description("Subtract two numbers") parameters { parameter("a", "First number", Double::class) parameter("b", "Second number", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double "Result: ${a - b}" } } } // Utility tools category("Utilities") { tool("getCurrentTime") { description("Get the current time") parameters {} execute { "Current time: ${java.time.LocalDateTime.now()}" } } } } ``` ## Asynchronous Tools For long-running operations, you can create asynchronous tools: ```kotlin tool("fetchLargeDataset") { description("Fetch a large dataset from an API") parameters { parameter("datasetId", "ID of the dataset", String::class) } executeAsync { params -> val datasetId = params["datasetId"] as String // Simulate a long-running operation kotlinx.coroutines.delay(2000) // Return the result "Dataset $datasetId fetched successfully with 10,000 records" } } ``` ## Tool Error Handling You can handle errors in tools: ```kotlin tool("divideNumbers") { description("Divide two numbers") parameters { parameter("a", "Numerator", Double::class) parameter("b", "Denominator", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double try { if (b == 0.0) { throw ArithmeticException("Division by zero") } "Result: ${a / b}" } catch (e: Exception) { "Error: ${e.message}" } } } ``` ## Tool Validation You can add validation to tool parameters: ```kotlin tool("sendEmail") { description("Send an email") parameters { parameter("to", "Recipient email address", String::class) { validate { email -> if (!email.contains("@")) { throw IllegalArgumentException("Invalid email address") } } } parameter("subject", "Email subject", String::class) { validate { subject -> if (subject.length > 100) { throw IllegalArgumentException("Subject too long (max 100 characters)") } } } parameter("body", "Email body", String::class) } execute { params -> val to = params["to"] as String val subject = params["subject"] as String val body = params["body"] as String // Send email logic // ... "Email sent to $to" } } ``` ## Tool Permissions You can define permissions for tools: ```kotlin tool("deleteFile") { description("Delete a file") permissions { permission("file.delete", "Permission to delete files") } parameters { parameter("path", "File path", String::class) } execute { params -> val path = params["path"] as String // Check permissions if (!hasPermission("file.delete")) { return "Error: Permission denied" } // Delete file logic // ... "File $path deleted successfully" } } ``` ## Tool Composition You can compose tools from other tools: ```kotlin // Define base tools val getCurrentTimeToolDef = toolDefinition("getCurrentTime") { description("Get the current time") parameters {} execute { val currentTime = java.time.LocalDateTime.now() val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val formattedTime = currentTime.format(formatter) "The current time is $formattedTime" } } val getCurrentDateToolDef = toolDefinition("getCurrentDate") { description("Get the current date") parameters {} execute { val currentDate = java.time.LocalDate.now() val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd") val formattedDate = currentDate.format(formatter) "The current date is $formattedDate" } } // Compose a new tool val getDateTimeInfoToolDef = toolDefinition("getDateTimeInfo") { description("Get current date and time information") parameters {} execute { val timeResult = getCurrentTimeToolDef.execute(mapOf()) val dateResult = getCurrentDateToolDef.execute(mapOf()) "$dateResult\n$timeResult" } } // Add the composed tool to an agent agent { // ... tools { addTool(getDateTimeInfoToolDef) } } ``` ## External API Tools You can create tools that interact with external APIs: ```kotlin tool("getWeather") { description("Get weather information for a location") parameters { parameter("location", "City name or coordinates", String::class) } execute { params -> val location = params["location"] as String // Make API request val apiKey = "your-weather-api-key" val url = "https://api.weatherapi.com/v1/current.json?key=$apiKey&q=$location" try { val client = java.net.http.HttpClient.newBuilder().build() val request = java.net.http.HttpRequest.newBuilder() .uri(java.net.URI.create(url)) .GET() .build() val response = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString()) if (response.statusCode() == 200) { // Parse JSON response // This is a simplified example val body = response.body() "Weather information for $location: $body" } else { "Error fetching weather: ${response.statusCode()}" } } catch (e: Exception) { "Error: ${e.message}" } } } ``` ## File Operation Tools You can create tools for file operations: ```kotlin tools { category("FileOperations") { tool("readFile") { description("Read the contents of a file") parameters { parameter("path", "File path", String::class) } execute { params -> val path = params["path"] as String try { val content = java.nio.file.Files.readString(java.nio.file.Path.of(path)) "File content:\n$content" } catch (e: Exception) { "Error reading file: ${e.message}" } } } tool("writeFile") { description("Write content to a file") parameters { parameter("path", "File path", String::class) parameter("content", "Content to write", String::class) } execute { params -> val path = params["path"] as String val content = params["content"] as String try { java.nio.file.Files.writeString(java.nio.file.Path.of(path), content) "Content written to $path successfully" } catch (e: Exception) { "Error writing to file: ${e.message}" } } } } } ``` ## Database Tools You can create tools for database operations: ```kotlin tools { category("Database") { tool("queryDatabase") { description("Execute a SQL query") parameters { parameter("query", "SQL query", String::class) } execute { params -> val query = params["query"] as String // This is a simplified example // In a real application, you would use a proper database connection try { // Execute query // ... "Query executed successfully. Results:\n..." } catch (e: Exception) { "Error executing query: ${e.message}" } } } } } ``` ## Tool Registration You can register tools from external sources: ```kotlin // Define tools in a separate file object MathTools { val addTool = toolDefinition("add") { description("Add two numbers") parameters { parameter("a", "First number", Double::class) parameter("b", "Second number", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double "Result: ${a + b}" } } val subtractTool = toolDefinition("subtract") { description("Subtract two numbers") parameters { parameter("a", "First number", Double::class) parameter("b", "Second number", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double "Result: ${a - b}" } } } // Register tools with an agent agent { // ... tools { addTool(MathTools.addTool) addTool(MathTools.subtractTool) } } ``` ## Complete Example Here's a complete example with various tools: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import java.time.LocalDateTime import java.time.format.DateTimeFormatter fun createToolAgent() = agent { name("UtilityAssistant") description("An assistant with various utility tools") // Configure the LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // Add tools tools { // Date and time tools category("DateTime") { tool("getCurrentTime") { description("Get the current time") parameters {} execute { val currentTime = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("HH:mm:ss") val formattedTime = currentTime.format(formatter) "The current time is $formattedTime" } } tool("getCurrentDate") { description("Get the current date") parameters {} execute { val currentDate = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") val formattedDate = currentDate.format(formatter) "Today's date is $formattedDate" } } } // Math tools category("Math") { tool("calculate") { description("Perform a calculation") parameters { parameter("expression", "Mathematical expression", String::class) } execute { params -> val expression = params["expression"] as String try { // This is a simplified example // In a real application, you would use a proper expression evaluator val result = when { expression.contains("+") -> { val parts = expression.split("+") val a = parts[0].trim().toDouble() val b = parts[1].trim().toDouble() a + b } expression.contains("-") -> { val parts = expression.split("-") val a = parts[0].trim().toDouble() val b = parts[1].trim().toDouble() a - b } expression.contains("*") -> { val parts = expression.split("*") val a = parts[0].trim().toDouble() val b = parts[1].trim().toDouble() a * b } expression.contains("/") -> { val parts = expression.split("/") val a = parts[0].trim().toDouble() val b = parts[1].trim().toDouble() if (b == 0.0) throw ArithmeticException("Division by zero") a / b } else -> throw IllegalArgumentException("Unsupported operation") } "Result: $result" } catch (e: Exception) { "Error calculating result: ${e.message}" } } } } // Utility tools category("Utilities") { tool("generateRandomNumber") { description("Generate a random number within a range") parameters { parameter("min", "Minimum value", Int::class) parameter("max", "Maximum value", Int::class) } execute { params -> val min = params["min"] as Int val max = params["max"] as Int if (min >= max) { "Error: Minimum value must be less than maximum value" } else { val random = java.util.Random() val randomNumber = random.nextInt(max - min + 1) + min "Random number between $min and $max: $randomNumber" } } } tool("countWords") { description("Count the number of words in a text") parameters { parameter("text", "Text to analyze", String::class) } execute { params -> val text = params["text"] as String val wordCount = text.split("\\s+".toRegex()).filter { it.isNotEmpty() }.size "Word count: $wordCount" } } } } } fun main() = runBlocking { val agent = createToolAgent() // Test the agent println(agent.generate("What time is it now?").text) println(agent.generate("Calculate 15.5 + 27.3").text) println(agent.generate("Generate a random number between 1 and 100").text) println(agent.generate("Count the words in 'The quick brown fox jumps over the lazy dog'").text) } ``` ## Next Steps Now that you understand the tools system, you can: 1. Learn about [Zod tools](./zod-tools.mdx) 2. Explore [custom tool development](./custom-tools.mdx) 3. Implement [tool chains](./tool-chains.mdx) --- title: Voice in Kastrax | Kastrax Docs description: Overview of voice capabilities in Kastrax, including text-to-speech, speech-to-text, and real-time speech-to-speech interactions. --- import { Tabs } from "nextra/components"; import { AudioPlayback } from "@/components/audio-playback"; # Voice in Kastrax [EN] Source: https://kastrax.ai/en/docs/voice/overview Kastrax's Voice system provides a unified interface for voice interactions, enabling text-to-speech (TTS), speech-to-text (STT), and real-time speech-to-speech (STS) capabilities in your applications. ## Adding Voice to Agents To learn how to integrate voice capabilities into your agents, check out the [Adding Voice to Agents](../agents/adding-voice.mdx) documentation. This section covers how to use both single and multiple voice providers, as well as real-time interactions. ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.voice.openai.OpenAIVoice // Initialize OpenAI voice for TTS val voiceAgent = agent { name("Voice Agent") instructions("You are a voice assistant that can help users with their tasks.") model = openAi { model(OpenAIModel.GPT_4O) apiKey(System.getenv("OPENAI_API_KEY")) } voice = OpenAIVoice() } ``` You can then use the following voice capabilities: ### Text to Speech (TTS) Turn your agent's responses into natural-sounding speech using Kastrax's TTS capabilities. Choose from multiple providers like OpenAI, ElevenLabs, and more. For detailed configuration options and advanced features, check out our [Text-to-Speech guide](./text-to-speech). ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { OpenAIVoice } from "@kastrax/voice-openai"; import { playAudio } from "@kastrax/node-audio"; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new OpenAIVoice(), }); const { text } = await voiceAgent.generate('What color is the sky?'); // Convert text to speech to an Audio Stream const audioStream = await voiceAgent.voice.speak(text, { speaker: "default", // Optional: specify a speaker responseFormat: "wav", // Optional: specify a response format }); playAudio(audioStream); ``` Visit the [OpenAI Voice Reference](/reference/voice/openai) for more information on the OpenAI voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { AzureVoice } from "@kastrax/voice-azure"; import { playAudio } from "@kastrax/node-audio"; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new AzureVoice(), }); const { text } = await voiceAgent.generate('What color is the sky?'); // Convert text to speech to an Audio Stream const audioStream = await voiceAgent.voice.speak(text, { speaker: "en-US-JennyNeural", // Optional: specify a speaker }); playAudio(audioStream); ``` Visit the [Azure Voice Reference](/reference/voice/azure) for more information on the Azure voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { ElevenLabsVoice } from "@kastrax/voice-elevenlabs"; import { playAudio } from "@kastrax/node-audio"; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new ElevenLabsVoice(), }); const { text } = await voiceAgent.generate('What color is the sky?'); // Convert text to speech to an Audio Stream const audioStream = await voiceAgent.voice.speak(text, { speaker: "default", // Optional: specify a speaker }); playAudio(audioStream); ``` Visit the [ElevenLabs Voice Reference](/reference/voice/elevenlabs) for more information on the ElevenLabs voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { PlayAIVoice } from "@kastrax/voice-playai"; import { playAudio } from "@kastrax/node-audio"; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new PlayAIVoice(), }); const { text } = await voiceAgent.generate('What color is the sky?'); // Convert text to speech to an Audio Stream const audioStream = await voiceAgent.voice.speak(text, { speaker: "default", // Optional: specify a speaker }); playAudio(audioStream); ``` Visit the [PlayAI Voice Reference](/reference/voice/playai) for more information on the PlayAI voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { GoogleVoice } from "@kastrax/voice-google"; import { playAudio } from "@kastrax/node-audio"; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new GoogleVoice(), }); const { text } = await voiceAgent.generate('What color is the sky?'); // Convert text to speech to an Audio Stream const audioStream = await voiceAgent.voice.speak(text, { speaker: "en-US-Studio-O", // Optional: specify a speaker }); playAudio(audioStream); ``` Visit the [Google Voice Reference](/reference/voice/google) for more information on the Google voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { CloudflareVoice } from "@kastrax/voice-cloudflare"; import { playAudio } from "@kastrax/node-audio"; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new CloudflareVoice(), }); const { text } = await voiceAgent.generate('What color is the sky?'); // Convert text to speech to an Audio Stream const audioStream = await voiceAgent.voice.speak(text, { speaker: "default", // Optional: specify a speaker }); playAudio(audioStream); ``` Visit the [Cloudflare Voice Reference](/reference/voice/cloudflare) for more information on the Cloudflare voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { DeepgramVoice } from "@kastrax/voice-deepgram"; import { playAudio } from "@kastrax/node-audio"; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new DeepgramVoice(), }); const { text } = await voiceAgent.generate('What color is the sky?'); // Convert text to speech to an Audio Stream const audioStream = await voiceAgent.voice.speak(text, { speaker: "aura-english-us", // Optional: specify a speaker }); playAudio(audioStream); ``` Visit the [Deepgram Voice Reference](/reference/voice/deepgram) for more information on the Deepgram voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { SpeechifyVoice } from "@kastrax/voice-speechify"; import { playAudio } from "@kastrax/node-audio"; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new SpeechifyVoice(), }); const { text } = await voiceAgent.generate('What color is the sky?'); // Convert text to speech to an Audio Stream const audioStream = await voiceAgent.voice.speak(text, { speaker: "matthew", // Optional: specify a speaker }); playAudio(audioStream); ``` Visit the [Speechify Voice Reference](/reference/voice/speechify) for more information on the Speechify voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { SarvamVoice } from "@kastrax/voice-sarvam"; import { playAudio } from "@kastrax/node-audio"; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new SarvamVoice(), }); const { text } = await voiceAgent.generate('What color is the sky?'); // Convert text to speech to an Audio Stream const audioStream = await voiceAgent.voice.speak(text, { speaker: "default", // Optional: specify a speaker }); playAudio(audioStream); ``` Visit the [Sarvam Voice Reference](/reference/voice/sarvam) for more information on the Sarvam voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { MurfVoice } from "@kastrax/voice-murf"; import { playAudio } from "@kastrax/node-audio"; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new MurfVoice(), }); const { text } = await voiceAgent.generate('What color is the sky?'); // Convert text to speech to an Audio Stream const audioStream = await voiceAgent.voice.speak(text, { speaker: "default", // Optional: specify a speaker }); playAudio(audioStream); ``` Visit the [Murf Voice Reference](/reference/voice/murf) for more information on the Murf voice provider. ### Speech to Text (STT) Transcribe spoken content using various providers like OpenAI, ElevenLabs, and more. For detailed configuration options and more, check out [Speech to Text](./speech-to-text). You can download a sample audio file from [here](https://github.com/kastrax-ai/realtime-voice-demo/raw/refs/heads/main/how_can_i_help_you.mp3).
```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { OpenAIVoice } from "@kastrax/voice-openai"; import { createReadStream } from 'fs'; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new OpenAIVoice(), }); // Use an audio file from a URL const audioStream = await createReadStream("./how_can_i_help_you.mp3"); // Convert audio to text const transcript = await voiceAgent.voice.listen(audioStream); console.log(`User said: ${transcript}`); // Generate a response based on the transcript const { text } = await voiceAgent.generate(transcript); ``` Visit the [OpenAI Voice Reference](/reference/voice/openai) for more information on the OpenAI voice provider. ```typescript import { createReadStream } from 'fs'; import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { AzureVoice } from "@kastrax/voice-azure"; import { createReadStream } from 'fs'; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new AzureVoice(), }); // Use an audio file from a URL const audioStream = await createReadStream("./how_can_i_help_you.mp3"); // Convert audio to text const transcript = await voiceAgent.voice.listen(audioStream); console.log(`User said: ${transcript}`); // Generate a response based on the transcript const { text } = await voiceAgent.generate(transcript); ``` Visit the [Azure Voice Reference](/reference/voice/azure) for more information on the Azure voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { ElevenLabsVoice } from "@kastrax/voice-elevenlabs"; import { createReadStream } from 'fs'; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new ElevenLabsVoice(), }); // Use an audio file from a URL const audioStream = await createReadStream("./how_can_i_help_you.mp3"); // Convert audio to text const transcript = await voiceAgent.voice.listen(audioStream); console.log(`User said: ${transcript}`); // Generate a response based on the transcript const { text } = await voiceAgent.generate(transcript); ``` Visit the [ElevenLabs Voice Reference](/reference/voice/elevenlabs) for more information on the ElevenLabs voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { GoogleVoice } from "@kastrax/voice-google"; import { createReadStream } from 'fs'; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new GoogleVoice(), }); // Use an audio file from a URL const audioStream = await createReadStream("./how_can_i_help_you.mp3"); // Convert audio to text const transcript = await voiceAgent.voice.listen(audioStream); console.log(`User said: ${transcript}`); // Generate a response based on the transcript const { text } = await voiceAgent.generate(transcript); ``` Visit the [Google Voice Reference](/reference/voice/google) for more information on the Google voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { CloudflareVoice } from "@kastrax/voice-cloudflare"; import { createReadStream } from 'fs'; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new CloudflareVoice(), }); // Use an audio file from a URL const audioStream = await createReadStream("./how_can_i_help_you.mp3"); // Convert audio to text const transcript = await voiceAgent.voice.listen(audioStream); console.log(`User said: ${transcript}`); // Generate a response based on the transcript const { text } = await voiceAgent.generate(transcript); ``` Visit the [Cloudflare Voice Reference](/reference/voice/cloudflare) for more information on the Cloudflare voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { DeepgramVoice } from "@kastrax/voice-deepgram"; import { createReadStream } from 'fs'; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new DeepgramVoice(), }); // Use an audio file from a URL const audioStream = await createReadStream("./how_can_i_help_you.mp3"); // Convert audio to text const transcript = await voiceAgent.voice.listen(audioStream); console.log(`User said: ${transcript}`); // Generate a response based on the transcript const { text } = await voiceAgent.generate(transcript); ``` Visit the [Deepgram Voice Reference](/reference/voice/deepgram) for more information on the Deepgram voice provider. ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { SarvamVoice } from "@kastrax/voice-sarvam"; import { createReadStream } from 'fs'; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new SarvamVoice(), }); // Use an audio file from a URL const audioStream = await createReadStream("./how_can_i_help_you.mp3"); // Convert audio to text const transcript = await voiceAgent.voice.listen(audioStream); console.log(`User said: ${transcript}`); // Generate a response based on the transcript const { text } = await voiceAgent.generate(transcript); ``` Visit the [Sarvam Voice Reference](/reference/voice/sarvam) for more information on the Sarvam voice provider. ### Speech to Speech (STS) Create conversational experiences with speech-to-speech capabilities. The unified API enables real-time voice interactions between users and AI agents. For detailed configuration options and advanced features, check out [Speech to Speech](./speech-to-speech). ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { playAudio, getMicrophoneStream } from '@kastrax/node-audio'; import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; const voiceAgent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice: new OpenAIRealtimeVoice(), }); // Listen for agent audio responses voiceAgent.voice.on('speaker', ({ audio }) => { playAudio(audio); }); // Initiate the conversation await voiceAgent.voice.speak('How can I help you today?'); // Send continuous audio from the microphone const micStream = getMicrophoneStream(); await voiceAgent.voice.send(micStream); ``` Visit the [OpenAI Voice Reference](/reference/voice/openai-realtime) for more information on the OpenAI voice provider. ## Voice Configuration Each voice provider can be configured with different models and options. Below are the detailed configuration options for all supported providers: ```typescript // OpenAI Voice Configuration const voice = new OpenAIVoice({ speechModel: { name: "gpt-3.5-turbo", // Example model name apiKey: process.env.OPENAI_API_KEY, language: "en-US", // Language code voiceType: "neural", // Type of voice model }, listeningModel: { name: "whisper-1", // Example model name apiKey: process.env.OPENAI_API_KEY, language: "en-US", // Language code format: "wav", // Audio format }, speaker: "alloy", // Example speaker name }); ``` Visit the [OpenAI Voice Reference](/reference/voice/openai) for more information on the OpenAI voice provider. ```typescript // Azure Voice Configuration const voice = new AzureVoice({ speechModel: { name: "en-US-JennyNeural", // Example model name apiKey: process.env.AZURE_SPEECH_KEY, region: process.env.AZURE_SPEECH_REGION, language: "en-US", // Language code style: "cheerful", // Voice style pitch: "+0Hz", // Pitch adjustment rate: "1.0", // Speech rate }, listeningModel: { name: "en-US", // Example model name apiKey: process.env.AZURE_SPEECH_KEY, region: process.env.AZURE_SPEECH_REGION, format: "simple", // Output format }, }); ``` Visit the [Azure Voice Reference](/reference/voice/azure) for more information on the Azure voice provider. ```typescript // ElevenLabs Voice Configuration const voice = new ElevenLabsVoice({ speechModel: { voiceId: "your-voice-id", // Example voice ID model: "eleven_multilingual_v2", // Example model name apiKey: process.env.ELEVENLABS_API_KEY, language: "en", // Language code emotion: "neutral", // Emotion setting }, // ElevenLabs may not have a separate listening model }); ``` Visit the [ElevenLabs Voice Reference](/reference/voice/elevenlabs) for more information on the ElevenLabs voice provider. ```typescript // PlayAI Voice Configuration const voice = new PlayAIVoice({ speechModel: { name: "playai-voice", // Example model name speaker: "emma", // Example speaker name apiKey: process.env.PLAYAI_API_KEY, language: "en-US", // Language code speed: 1.0, // Speech speed }, // PlayAI may not have a separate listening model }); ``` Visit the [PlayAI Voice Reference](/reference/voice/playai) for more information on the PlayAI voice provider. ```typescript // Google Voice Configuration const voice = new GoogleVoice({ speechModel: { name: "en-US-Studio-O", // Example model name apiKey: process.env.GOOGLE_API_KEY, languageCode: "en-US", // Language code gender: "FEMALE", // Voice gender speakingRate: 1.0, // Speaking rate }, listeningModel: { name: "en-US", // Example model name sampleRateHertz: 16000, // Sample rate }, }); ``` Visit the [PlayAI Voice Reference](/reference/voice/playai) for more information on the PlayAI voice provider. ```typescript // Cloudflare Voice Configuration const voice = new CloudflareVoice({ speechModel: { name: "cloudflare-voice", // Example model name accountId: process.env.CLOUDFLARE_ACCOUNT_ID, apiToken: process.env.CLOUDFLARE_API_TOKEN, language: "en-US", // Language code format: "mp3", // Audio format }, // Cloudflare may not have a separate listening model }); ``` Visit the [Cloudflare Voice Reference](/reference/voice/cloudflare) for more information on the Cloudflare voice provider. ```typescript // Deepgram Voice Configuration const voice = new DeepgramVoice({ speechModel: { name: "nova-2", // Example model name speaker: "aura-english-us", // Example speaker name apiKey: process.env.DEEPGRAM_API_KEY, language: "en-US", // Language code tone: "formal", // Tone setting }, listeningModel: { name: "nova-2", // Example model name format: "flac", // Audio format }, }); ``` Visit the [Deepgram Voice Reference](/reference/voice/deepgram) for more information on the Deepgram voice provider. ```typescript // Speechify Voice Configuration const voice = new SpeechifyVoice({ speechModel: { name: "speechify-voice", // Example model name speaker: "matthew", // Example speaker name apiKey: process.env.SPEECHIFY_API_KEY, language: "en-US", // Language code speed: 1.0, // Speech speed }, // Speechify may not have a separate listening model }); ``` Visit the [Speechify Voice Reference](/reference/voice/speechify) for more information on the Speechify voice provider. ```typescript // Sarvam Voice Configuration const voice = new SarvamVoice({ speechModel: { name: "sarvam-voice", // Example model name apiKey: process.env.SARVAM_API_KEY, language: "en-IN", // Language code style: "conversational", // Style setting }, // Sarvam may not have a separate listening model }); ``` Visit the [Sarvam Voice Reference](/reference/voice/sarvam) for more information on the Sarvam voice provider. ```typescript // Murf Voice Configuration const voice = new MurfVoice({ speechModel: { name: "murf-voice", // Example model name apiKey: process.env.MURF_API_KEY, language: "en-US", // Language code emotion: "happy", // Emotion setting }, // Murf may not have a separate listening model }); ``` Visit the [Murf Voice Reference](/reference/voice/murf) for more information on the Murf voice provider. ```typescript // OpenAI Realtime Voice Configuration const voice = new OpenAIRealtimeVoice({ speechModel: { name: "gpt-3.5-turbo", // Example model name apiKey: process.env.OPENAI_API_KEY, language: "en-US", // Language code }, listeningModel: { name: "whisper-1", // Example model name apiKey: process.env.OPENAI_API_KEY, format: "ogg", // Audio format }, speaker: "alloy", // Example speaker name }); ``` For more information on the OpenAI Realtime voice provider, refer to the [OpenAI Realtime Voice Reference](/reference/voice/openai-realtime). ### Using Multiple Voice Providers This example demonstrates how to create and use two different voice providers in Kastrax: OpenAI for speech-to-text (STT) and PlayAI for text-to-speech (TTS). Start by creating instances of the voice providers with any necessary configuration. ```typescript import { OpenAIVoice } from "@kastrax/voice-openai"; import { PlayAIVoice } from "@kastrax/voice-playai"; import { CompositeVoice } from "@kastrax/core/voice"; import { playAudio, getMicrophoneStream } from "@kastrax/node-audio"; // Initialize OpenAI voice for STT const input = new OpenAIVoice({ listeningModel: { name: "whisper-1", apiKey: process.env.OPENAI_API_KEY, }, }); // Initialize PlayAI voice for TTS const output = new PlayAIVoice({ speechModel: { name: "playai-voice", apiKey: process.env.PLAYAI_API_KEY, }, }); // Combine the providers using CompositeVoice const voice = new CompositeVoice({ input, output, }); // Implement voice interactions using the combined voice provider const audioStream = getMicrophoneStream(); // Assume this function gets audio input const transcript = await voice.listen(audioStream); // Log the transcribed text console.log("Transcribed text:", transcript); // Convert text to speech const responseAudio = await voice.speak(`You said: ${transcript}`, { speaker: "default", // Optional: specify a speaker, responseFormat: "wav", // Optional: specify a response format }); // Play the audio response playAudio(responseAudio); ``` For more information on the CompositeVoice, refer to the [CompositeVoice Reference](/reference/voice/composite-voice). ## More Resources - [CompositeVoice](../../reference/voice/composite-voice.mdx) - [KastraxVoice](../../reference/voice/kastrax-voice.mdx) - [OpenAI Voice](../../reference/voice/openai.mdx) - [Azure Voice](../../reference/voice/azure.mdx) - [Google Voice](../../reference/voice/google.mdx) - [Deepgram Voice](../../reference/voice/deepgram.mdx) - [PlayAI Voice](../../reference/voice/playai.mdx) - [Voice Examples](../../examples/voice/text-to-speech.mdx) --- title: Speech-to-Speech Capabilities in Kastrax | Kastrax Docs description: Overview of speech-to-speech capabilities in Kastrax, including real-time interactions and event-driven architecture. --- # Speech-to-Speech Capabilities in Kastrax ✅ [EN] Source: https://kastrax.ai/en/docs/voice/speech-to-speech ## Introduction ✅ Speech-to-Speech (STS) in Kastrax provides a standardized interface for real-time interactions across multiple providers. STS enables continuous bidirectional audio communication through listening to events from Realtime models. Unlike separate TTS and STT operations, STS maintains an open connection that processes speech continuously in both directions. ## Configuration ✅ - **`chatModel`**: Configuration for the realtime model. - **`apiKey`**: Your OpenAI API key. Falls back to the `OPENAI_API_KEY` environment variable. - **`model`**: The model ID to use for real-time voice interactions (e.g., `gpt-4o-mini-realtime`). - **`options`**: Additional options for the realtime client, such as session configuration. - **`speaker`**: The default voice ID for speech synthesis. This allows you to specify which voice to use for the speech output. ```typescript const voice = new OpenAIRealtimeVoice({ chatModel: { apiKey: 'your-openai-api-key', model: 'gpt-4o-mini-realtime', options: { sessionConfig: { turn_detection: { type: 'server_vad', threshold: 0.6, silence_duration_ms: 1200, }, }, }, }, speaker: 'alloy', // Default voice }); // If using default settings the configuration can be simplified to: const voice = new OpenAIRealtimeVoice(); ``` ## Using STS ✅ ```typescript import { Agent } from "@kastrax/core/agent"; import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import { playAudio, getMicrophoneStream } from "@kastrax/node-audio"; const agent = new Agent({ name: 'Agent', instructions: `You are a helpful assistant with real-time voice capabilities.`, model: openai('gpt-4o'), voice: new OpenAIRealtimeVoice(), }); // Connect to the voice service await agent.voice.connect(); // Listen for agent audio responses agent.voice.on('speaker', ({ audio }) => { playAudio(audio); }); // Initiate the conversation await agent.voice.speak('How can I help you today?'); // Send continuous audio from the microphone const micStream = getMicrophoneStream(); await agent.voice.send(micStream); ``` For integrating Speech-to-Speech capabilities with agents, refer to the [Adding Voice to Agents](../agents/adding-voice.mdx) documentation. --- title: Speech-to-Text (STT) in Kastrax | Kastrax Docs description: Overview of Speech-to-Text capabilities in Kastrax, including configuration, usage, and integration with voice providers. --- # Speech-to-Text (STT) ✅ [EN] Source: https://kastrax.ai/en/docs/voice/speech-to-text Speech-to-Text (STT) in Kastrax provides a standardized interface for converting audio input into text across multiple service providers. STT helps create voice-enabled applications that can respond to human speech, enabling hands-free interaction, accessibility for users with disabilities, and more natural human-computer interfaces. ## Configuration ✅ To use STT in Kastrax, you need to provide a `listeningModel` when initializing the voice provider. This includes parameters such as: - **`name`**: The specific STT model to use. - **`apiKey`**: Your API key for authentication. - **Provider-specific options**: Additional options that may be required or supported by the specific voice provider. **Note**: All of these parameters are optional. You can use the default settings provided by the voice provider, which will depend on the specific provider you are using. ```typescript const voice = new OpenAIVoice({ listeningModel: { name: "whisper-1", apiKey: process.env.OPENAI_API_KEY, }, }); // If using default settings the configuration can be simplified to: const voice = new OpenAIVoice(); ``` ## Available Providers ✅ Kastrax supports several Speech-to-Text providers, each with their own capabilities and strengths: - [**OpenAI**](/reference/voice/openai/) - High-accuracy transcription with Whisper models - [**Azure**](/reference/voice/azure/) - Microsoft's speech recognition with enterprise-grade reliability - [**ElevenLabs**](/reference/voice/elevenlabs/) - Advanced speech recognition with support for multiple languages - [**Google**](/reference/voice/google/) - Google's speech recognition with extensive language support - [**Cloudflare**](/reference/voice/cloudflare/) - Edge-optimized speech recognition for low-latency applications - [**Deepgram**](/reference/voice/deepgram/) - AI-powered speech recognition with high accuracy for various accents - [**Sarvam**](/reference/voice/sarvam/) - Specialized in Indic languages and accents Each provider is implemented as a separate package that you can install as needed: ```bash pnpm add @kastrax/voice-openai # Example for OpenAI ``` ## Using the Listen Method ✅ The primary method for STT is the `listen()` method, which converts spoken audio into text. Here's how to use it: ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { OpenAIVoice } from '@kastrax/voice-openai'; import { getMicrophoneStream } from "@kastrax/node-audio"; const voice = new OpenAIVoice(); const agent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that provides recommendations based on user input.", model: openai("gpt-4o"), voice, }); const audioStream = getMicrophoneStream(); // Assume this function gets audio input const transcript = await agent.voice.listen(audioStream, { filetype: "m4a", // Optional: specify the audio file type }); console.log(`User said: ${transcript}`); const { text } = await agent.generate(`Based on what the user said, provide them a recommendation: ${transcript}`); console.log(`Recommendation: ${text}`); ``` Check out the [Adding Voice to Agents](../agents/adding-voice.mdx) documentation to learn how to use STT in an agent. --- title: Text-to-Speech (TTS) in Kastrax | Kastrax Docs description: Overview of Text-to-Speech capabilities in Kastrax, including configuration, usage, and integration with voice providers. --- # Text-to-Speech (TTS) ✅ [EN] Source: https://kastrax.ai/en/docs/voice/text-to-speech Text-to-Speech (TTS) in Kastrax offers a unified API for synthesizing spoken audio from text using various providers. By incorporating TTS into your applications, you can enhance user experience with natural voice interactions, improve accessibility for users with visual impairments, and create more engaging multimodal interfaces. TTS is a core component of any voice application. Combined with STT (Speech-to-Text), it forms the foundation of voice interaction systems. Newer models support STS ([Speech-to-Speech](./speech-to-speech)) which can be used for real-time interactions but come at high cost ($). ## Configuration ✅ To use TTS in Kastrax, you need to provide a `speechModel` when initializing the voice provider. This includes parameters such as: - **`name`**: The specific TTS model to use. - **`apiKey`**: Your API key for authentication. - **Provider-specific options**: Additional options that may be required or supported by the specific voice provider. The **`speaker`** option allows you to select different voices for speech synthesis. Each provider offers a variety of voice options with distinct characteristics for **Voice diversity**, **Quality**, **Voice personality**, and **Multilingual support** **Note**: All of these parameters are optional. You can use the default settings provided by the voice provider, which will depend on the specific provider you are using. ```typescript const voice = new OpenAIVoice({ speechModel: { name: "tts-1-hd", apiKey: process.env.OPENAI_API_KEY }, speaker: "alloy", }); // If using default settings the configuration can be simplified to: const voice = new OpenAIVoice(); ``` ## Available Providers ✅ Kastrax supports a wide range of Text-to-Speech providers, each with their own unique capabilities and voice options. You can choose the provider that best suits your application's needs: - [**OpenAI**](/reference/voice/openai/) - High-quality voices with natural intonation and expression - [**Azure**](/reference/voice/azure/) - Microsoft's speech service with a wide range of voices and languages - [**ElevenLabs**](/reference/voice/elevenlabs/) - Ultra-realistic voices with emotion and fine-grained control - [**PlayAI**](/reference/voice/playai/) - Specialized in natural-sounding voices with various styles - [**Google**](/reference/voice/google/) - Google's speech synthesis with multilingual support - [**Cloudflare**](/reference/voice/cloudflare/) - Edge-optimized speech synthesis for low-latency applications - [**Deepgram**](/reference/voice/deepgram/) - AI-powered speech technology with high accuracy - [**Speechify**](/reference/voice/speechify/) - Text-to-speech optimized for readability and accessibility - [**Sarvam**](/reference/voice/sarvam/) - Specialized in Indic languages and accents - [**Murf**](/reference/voice/murf/) - Studio-quality voice overs with customizable parameters Each provider is implemented as a separate package that you can install as needed: ```bash pnpm add @kastrax/voice-openai # Example for OpenAI ``` ## Using the Speak Method ✅ The primary method for TTS is the `speak()` method, which converts text to speech. This method can accept options that allows you to specify the speaker and other provider-specific options. Here's how to use it: ```typescript import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { OpenAIVoice } from '@kastrax/voice-openai'; const voice = new OpenAIVoice(); const agent = new Agent({ name: "Voice Agent", instructions: "You are a voice assistant that can help users with their tasks.", model: openai("gpt-4o"), voice, }); const { text } = await agent.generate('What color is the sky?'); // Convert text to speech to an Audio Stream const readableStream = await voice.speak(text, { speaker: "default", // Optional: specify a speaker properties: { speed: 1.0, // Optional: adjust speech speed pitch: "default", // Optional: specify pitch if supported }, }); ``` Check out the [Adding Voice to Agents](../agents/adding-voice.mdx) documentation to learn how to use TTS in an agent. --- title: "Control Flow in Kastrax AI Workflows | Workflows | Kastrax Docs" description: "Learn how to orchestrate complex AI agent operations with Kastrax's powerful workflow control flow features, including sequential execution, parallel processing, branching logic, and loops." --- # Control Flow in Kastrax AI Workflows ✅ [EN] Source: https://kastrax.ai/en/docs/workflows/control-flow Kastrax's workflow system provides sophisticated control flow capabilities that allow you to orchestrate complex AI agent operations. Whether you need to execute steps sequentially, run operations in parallel, implement conditional branching, or create loops, Kastrax offers a type-safe, declarative API for defining exactly how your workflow should behave. This guide explains the various control flow patterns available in Kastrax workflows and how to implement them effectively. ## Sequential Execution ✅ Sequential execution is the most basic control flow pattern, where steps are executed one after another in a defined order. This ensures that outputs from one step become inputs for the next step. ```kotlin filename="SequentialWorkflow.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with sequential steps val orderProcessingWorkflow = workflow { name = "order-processing" description = "Process customer orders sequentially" // First step: Fetch order data step(fetchOrderAgent) { id = "fetch-order" name = "Fetch Order Data" description = "Retrieve order information from the database" variables = mutableMapOf( "orderId" to variable("$.input.orderId") ) } // Second step: Validate order data (runs after fetch-order) step(validationAgent) { id = "validate-order" name = "Validate Order" description = "Validate order data for completeness and correctness" after("fetch-order") // Explicit dependency variables = mutableMapOf( "orderData" to variable("$.steps.fetch-order.output.data") ) } // Third step: Process payment (runs after validate-order) step(paymentAgent) { id = "process-payment" name = "Process Payment" description = "Process payment for the validated order" after("validate-order") // Explicit dependency variables = mutableMapOf( "orderData" to variable("$.steps.fetch-order.output.data"), "validationResult" to variable("$.steps.validate-order.output.result") ) } } ``` In this example, each step explicitly depends on the previous step using the `after()` function, creating a clear sequential flow. ## Parallel Execution ✅ Parallel execution allows multiple steps to run simultaneously when they don't depend on each other. This can significantly improve workflow performance for independent operations. ```kotlin filename="ParallelWorkflow.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with parallel steps val dataProcessingWorkflow = workflow { name = "data-processing" description = "Process data from multiple sources in parallel" // These steps will run in parallel since they don't have dependencies on each other step(userDataAgent) { id = "fetch-user-data" name = "Fetch User Data" description = "Retrieve user information" variables = mutableMapOf( "userId" to variable("$.input.userId") ) } step(productDataAgent) { id = "fetch-product-data" name = "Fetch Product Data" description = "Retrieve product information" variables = mutableMapOf( "productId" to variable("$.input.productId") ) } step(inventoryDataAgent) { id = "fetch-inventory-data" name = "Fetch Inventory Data" description = "Retrieve inventory information" variables = mutableMapOf( "productId" to variable("$.input.productId") ) } // This step will only run after all parallel steps have completed step(analysisAgent) { id = "analyze-data" name = "Analyze Combined Data" description = "Analyze data from all sources" after("fetch-user-data", "fetch-product-data", "fetch-inventory-data") // Multiple dependencies variables = mutableMapOf( "userData" to variable("$.steps.fetch-user-data.output.data"), "productData" to variable("$.steps.fetch-product-data.output.data"), "inventoryData" to variable("$.steps.fetch-inventory-data.output.data") ) } } ``` In this example, the first three steps run in parallel because they don't have dependencies on each other. The final analysis step only runs after all three parallel steps have completed. ## Branching and Conditional Paths ✅ Branching allows your workflow to take different paths based on conditions or results from previous steps. This is essential for implementing decision logic in your workflows. ```kotlin filename="BranchingWorkflow.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with branching paths val contentWorkflow = workflow { name = "content-workflow" description = "Create and process content with branching logic" // Initial content analysis step step(analysisAgent) { id = "analyze-content" name = "Content Analysis" description = "Analyze content quality and type" variables = mutableMapOf( "content" to variable("$.input.content") ) } // Conditional branching based on content quality conditionalStep { id = "quality-branch" name = "Quality Branching" description = "Branch based on content quality" after("analyze-content") // Define the condition to evaluate condition { context -> val quality = context.getVariable("$.steps.analyze-content.output.qualityScore") as? Double ?: 0.0 quality >= 8.0 // High quality if score is 8.0 or higher } // Steps to execute if condition is true (high quality) onTrue { step(publishAgent) { id = "publish-content" name = "Publish Content" description = "Publish the high-quality content" variables = mutableMapOf( "content" to variable("$.input.content") ) } } // Steps to execute if condition is false (low quality) onFalse { step(revisionAgent) { id = "revise-content" name = "Revise Content" description = "Improve the low-quality content" variables = mutableMapOf( "content" to variable("$.input.content"), "feedback" to variable("$.steps.analyze-content.output.feedback") ) // Continue to publishing after revision onComplete { step(publishAgent) { id = "publish-revised-content" name = "Publish Revised Content" description = "Publish the revised content" variables = mutableMapOf( "content" to variable("$.steps.revise-content.output.revisedContent") ) } } } } } } ``` In this example, the workflow branches based on the content quality score. High-quality content goes directly to publishing, while low-quality content goes through a revision step before publishing. ## Merging Execution Paths ✅ Merging allows multiple execution paths to converge at a specific step. This is useful when you need to synchronize different branches of your workflow. ```kotlin filename="MergingWorkflow.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with merging paths val researchWorkflow = workflow { name = "research-workflow" description = "Research a topic from multiple sources and synthesize results" // These steps run in parallel step(academicResearchAgent) { id = "academic-research" name = "Academic Research" description = "Research from academic sources" variables = mutableMapOf( "topic" to variable("$.input.topic"), "depth" to variable("$.input.academicDepth") ) } step(newsResearchAgent) { id = "news-research" name = "News Research" description = "Research from news sources" variables = mutableMapOf( "topic" to variable("$.input.topic"), "timeframe" to variable("$.input.newsTimeframe") ) } step(socialMediaResearchAgent) { id = "social-research" name = "Social Media Research" description = "Research from social media" variables = mutableMapOf( "topic" to variable("$.input.topic"), "platforms" to variable("$.input.socialPlatforms") ) } // This step merges all research paths step(synthesisAgent) { id = "research-synthesis" name = "Research Synthesis" description = "Synthesize research from all sources" // This step depends on all three research steps after("academic-research", "news-research", "social-research") variables = mutableMapOf( "academicData" to variable("$.steps.academic-research.output.findings"), "newsData" to variable("$.steps.news-research.output.findings"), "socialData" to variable("$.steps.social-research.output.findings") ) } // Final report generation step(reportAgent) { id = "generate-report" name = "Generate Report" description = "Create a comprehensive report" after("research-synthesis") variables = mutableMapOf( "synthesizedData" to variable("$.steps.research-synthesis.output.synthesizedFindings"), "format" to variable("$.input.reportFormat") ) } } ``` In this example, three parallel research paths merge at the synthesis step, which only executes after all three research steps have completed. This ensures that all research data is available before synthesis begins. ## Loops and Iterative Processing ✅ Kastrax supports iterative processing through loop constructs that allow steps to repeat until certain conditions are met. This is essential for tasks that require multiple iterations or refinement cycles. ### Using Loop Steps ```kotlin filename="LoopWorkflow.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with a loop val contentRefinementWorkflow = workflow { name = "content-refinement" description = "Iteratively refine content until it meets quality standards" // Initial content generation step(contentGenerationAgent) { id = "generate-content" name = "Generate Initial Content" description = "Create the first draft of content" variables = mutableMapOf( "topic" to variable("$.input.topic"), "style" to variable("$.input.style") ) } // Initialize iteration counter step(initializationAgent) { id = "initialize-loop" name = "Initialize Loop Variables" description = "Set up variables for the refinement loop" after("generate-content") execute { context -> mapOf( "currentContent" to context.getVariable("$.steps.generate-content.output.content"), "iterationCount" to 0, "qualityScore" to 0.0 ) } } // Refinement loop loopStep { id = "refinement-loop" name = "Content Refinement Loop" description = "Iteratively improve content until quality threshold is reached" after("initialize-loop") // Continue looping while quality is below threshold and iterations are under limit condition { context -> val qualityScore = context.getVariable("$.steps.evaluate-content.output.qualityScore") as? Double ?: 0.0 val iterationCount = context.getVariable("$.steps.refine-content.output.iterationCount") as? Int ?: 0 qualityScore < 8.5 && iterationCount < 5 // Continue if quality < 8.5 and fewer than 5 iterations } // Loop body body { // Evaluate current content step(evaluationAgent) { id = "evaluate-content" name = "Evaluate Content Quality" description = "Assess the quality of the current content" variables = mutableMapOf( "content" to variable("$.steps.refine-content.output.currentContent", defaultValue = variable("$.steps.initialize-loop.output.currentContent")) ) } // Refine content based on evaluation step(refinementAgent) { id = "refine-content" name = "Refine Content" description = "Improve content based on evaluation feedback" after("evaluate-content") variables = mutableMapOf( "content" to variable("$.steps.refine-content.output.currentContent", defaultValue = variable("$.steps.initialize-loop.output.currentContent")), "feedback" to variable("$.steps.evaluate-content.output.feedback"), "qualityScore" to variable("$.steps.evaluate-content.output.qualityScore"), "iterationCount" to variable("$.steps.refine-content.output.iterationCount", defaultValue = 0) ) execute { context -> val content = context.getVariable("content") as String val feedback = context.getVariable("feedback") as String val iterationCount = (context.getVariable("iterationCount") as Int) + 1 val qualityScore = context.getVariable("qualityScore") as Double // In a real implementation, this would use an agent to refine the content // For this example, we're just simulating refinement val refinedContent = "$content\n\nRefined in iteration $iterationCount based on: $feedback" mapOf( "currentContent" to refinedContent, "iterationCount" to iterationCount, "qualityScore" to qualityScore ) } } } } // Final processing after loop completion step(finalizationAgent) { id = "finalize-content" name = "Finalize Content" description = "Prepare the refined content for publication" after("refinement-loop") variables = mutableMapOf( "content" to variable("$.steps.refine-content.output.currentContent"), "iterationCount" to variable("$.steps.refine-content.output.iterationCount"), "finalQuality" to variable("$.steps.evaluate-content.output.qualityScore") ) } } ``` In this example, the workflow uses a loop to iteratively refine content until it reaches a quality threshold or hits a maximum number of iterations. The loop body contains steps for evaluating and refining the content, with each iteration building on the results of the previous one. ### Using Recursive Patterns For more complex iterative processes, Kastrax also supports recursive patterns where steps can trigger themselves under certain conditions: ```kotlin filename="RecursiveWorkflow.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with recursive processing val recursiveWorkflow = workflow { name = "recursive-processing" description = "Process data recursively until completion" // Initial data processing step(processingAgent) { id = "process-data" name = "Process Data" description = "Process a batch of data" variables = mutableMapOf( "data" to variable("$.input.data"), "batchNumber" to variable("$.input.batchNumber", defaultValue = 1), "totalBatches" to variable("$.input.totalBatches") ) // Define what happens after this step completes onComplete { result -> val batchNumber = result["batchNumber"] as Int val totalBatches = result["totalBatches"] as Int // If there are more batches to process, trigger the next batch if (batchNumber < totalBatches) { step(processingAgent) { id = "process-data-${batchNumber + 1}" name = "Process Data Batch ${batchNumber + 1}" description = "Process the next batch of data" variables = mutableMapOf( "data" to variable("$.input.nextBatchData"), "batchNumber" to (batchNumber + 1), "totalBatches" to totalBatches ) } } else { // If all batches are processed, move to finalization step(finalizationAgent) { id = "finalize-processing" name = "Finalize Processing" description = "Combine and finalize all processed batches" variables = mutableMapOf( "processedBatches" to variable("$.steps", transform = { steps -> // Collect results from all processing steps (steps as Map).filter { it.key.startsWith("process-data") } .map { it.value } }) ) } } } } } ``` This recursive pattern allows for dynamic workflow generation based on the results of previous steps, enabling complex iterative processes that can't be fully defined in advance. ## Conditional Execution ✅ Kastrax provides several ways to conditionally execute steps based on the results of previous steps or external conditions. This allows for dynamic, adaptive workflows that can respond to changing circumstances. ### Using Conditional Steps The most direct way to implement conditional logic is using conditional steps: ```kotlin filename="ConditionalExecution.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with conditional execution val approvalWorkflow = workflow { name = "approval-workflow" description = "Process content with approval conditions" // Initial content creation step(contentAgent) { id = "create-content" name = "Create Content" description = "Generate initial content" variables = mutableMapOf( "topic" to variable("$.input.topic") ) } // Conditional step for content approval conditionalStep { id = "approval-check" name = "Approval Check" description = "Check if content requires approval" after("create-content") // Define the condition to evaluate condition { context -> val contentLength = context.getVariable("$.steps.create-content.output.content")?.toString()?.length ?: 0 val sensitiveTopics = context.getVariable("$.steps.create-content.output.sensitiveTopicsDetected") as? Boolean ?: false // Content requires approval if it's long or contains sensitive topics contentLength > 1000 || sensitiveTopics } // Steps to execute if approval is required onTrue { step(approvalAgent) { id = "request-approval" name = "Request Approval" description = "Send content for human approval" variables = mutableMapOf( "content" to variable("$.steps.create-content.output.content"), "approver" to variable("$.input.approverEmail") ) } } // Steps to execute if approval is not required onFalse { step(publishAgent) { id = "auto-publish" name = "Auto-Publish" description = "Automatically publish content without approval" variables = mutableMapOf( "content" to variable("$.steps.create-content.output.content") ) } } } } ``` ### Using Step Conditions You can also add conditions directly to individual steps: ```kotlin step(publishAgent) { id = "publish-content" name = "Publish Content" description = "Publish content if approved" after("request-approval") // Only execute this step if the approval was granted condition { context -> context.getVariable("$.steps.request-approval.output.approved") as? Boolean ?: false } variables = mutableMapOf( "content" to variable("$.steps.create-content.output.content"), "channel" to variable("$.input.publishChannel") ) } ``` ### Using Dynamic Step Generation For more complex conditional logic, you can dynamically generate steps based on runtime conditions: ```kotlin step(analysisAgent) { id = "analyze-data" name = "Analyze Data" description = "Analyze input data and determine next steps" variables = mutableMapOf( "data" to variable("$.input.data") ) // Dynamically determine next steps based on analysis results onComplete { result -> val dataType = result["dataType"] as? String when (dataType) { "text" -> { step(textProcessingAgent) { id = "process-text" name = "Process Text Data" description = "Process textual data" variables = mutableMapOf( "text" to variable("$.steps.analyze-data.output.extractedText") ) } } "image" -> { step(imageProcessingAgent) { id = "process-image" name = "Process Image Data" description = "Process image data" variables = mutableMapOf( "imageUrl" to variable("$.steps.analyze-data.output.imageUrl") ) } } else -> { step(fallbackAgent) { id = "process-unknown" name = "Process Unknown Data" description = "Handle unknown data type" variables = mutableMapOf( "rawData" to variable("$.steps.analyze-data.output.rawData") ) } } } } } ``` ## Best Practices for Control Flow ✅ When designing workflow control flow, consider these best practices: ### 1. Keep Workflows Readable Design your workflows to be as readable and maintainable as possible: ```kotlin // Good: Clear, descriptive step IDs and logical flow workflow { step(dataFetchAgent) { id = "fetch-data" } step(validationAgent) { id = "validate-data"; after("fetch-data") } step(processingAgent) { id = "process-data"; after("validate-data") } } // Avoid: Confusing dependencies and unclear flow // workflow { // step(processingAgent) { id = "process" } // step(dataFetchAgent) { id = "fetch" } // step(validationAgent) { id = "validate"; after("fetch") } // step(finalAgent) { id = "final"; after("process", "validate") } // } ``` ### 2. Use Explicit Dependencies Always make dependencies between steps explicit: ```kotlin // Good: Explicit dependencies step(analysisAgent) { id = "analyze-data" after("fetch-data", "preprocess-data") // Explicit dependencies } // Avoid: Implicit dependencies through variable references without explicit step dependencies // step(analysisAgent) { // id = "analyze-data" // variables = mutableMapOf( // "data" to variable("$.steps.fetch-data.output.data") // Implicit dependency // ) // } ``` ### 3. Handle Error Cases Implement proper error handling in your control flow: ```kotlin conditionalStep { id = "data-validation" condition { context -> val validationErrors = context.getVariable("$.steps.validate-data.output.errors") as? List ?: emptyList() validationErrors.isEmpty() // Check if there are no validation errors } onTrue { step(processingAgent) { id = "process-data" } } onFalse { step(errorHandlingAgent) { id = "handle-validation-errors" variables = mutableMapOf( "errors" to variable("$.steps.validate-data.output.errors") ) } } } ``` ### 4. Avoid Overly Complex Workflows Break down complex workflows into smaller, more manageable sub-workflows: ```kotlin // Main workflow that orchestrates sub-workflows val mainWorkflow = workflow { name = "main-process" // Execute data preparation sub-workflow subWorkflowStep { id = "data-preparation" workflowId = "data-prep-workflow" input = mapOf( "rawData" to variable("$.input.data") ) } // Execute analysis sub-workflow after data preparation subWorkflowStep { id = "data-analysis" after("data-preparation") workflowId = "analysis-workflow" input = mapOf( "preparedData" to variable("$.steps.data-preparation.output.processedData") ) } } ``` ### 5. Test Complex Control Flows Thoroughly test workflows with complex control flows to ensure they behave as expected: ```kotlin // Test workflow with different input conditions fun testWorkflow() = runBlocking { val workflowEngine = kastraxSystem.workflowEngine // Test with valid data val validResult = workflowEngine.executeWorkflow( workflowId = "data-processing", input = mapOf("data" to validTestData) ) assert(validResult.success) // Test with invalid data to verify error handling val invalidResult = workflowEngine.executeWorkflow( workflowId = "data-processing", input = mapOf("data" to invalidTestData) ) assert(invalidResult.steps["handle-validation-errors"] != null) } ``` By following these best practices, you can create workflows with clear, maintainable control flow that effectively orchestrates your AI agents and tools. ## Data Access Patterns ✅ Kastrax provides several ways to pass data between steps: 1. **Context Object** - Access step results directly through the context object 2. **Variable Mapping** - Explicitly map outputs from one step to inputs of another 3. **getStepResult Method** - Type-safe method to retrieve step outputs Each approach has its advantages depending on your use case and requirements for type safety. ### Using getStepResult Method The `getStepResult` method provides a type-safe way to access step results. This is the recommended approach when working with TypeScript as it preserves type information. #### Basic Usage For better type safety, you can provide a type parameter to `getStepResult`: ```typescript showLineNumbers filename="src/kastrax/workflows/get-step-result.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; const fetchUserStep = new Step({ id: 'fetchUser', outputSchema: z.object({ name: z.string(), userId: z.string(), }), execute: async ({ context }) => { return { name: 'John Doe', userId: '123' }; }, }); const analyzeDataStep = new Step({ id: "analyzeData", execute: async ({ context }) => { // Type-safe access to previous step result const userData = context.getStepResult<{ name: string, userId: string }>("fetchUser"); if (!userData) { return { status: "error", message: "User data not found" }; } return { analysis: `Analyzed data for user ${userData.name}`, userId: userData.userId }; }, }); ``` #### Using Step References The most type-safe approach is to reference the step directly in the `getStepResult` call: ```typescript showLineNumbers filename="src/kastrax/workflows/step-reference.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; // Define step with output schema const fetchUserStep = new Step({ id: "fetchUser", outputSchema: z.object({ userId: z.string(), name: z.string(), email: z.string(), }), execute: async () => { return { userId: "user123", name: "John Doe", email: "john@example.com" }; }, }); const processUserStep = new Step({ id: "processUser", execute: async ({ context }) => { // TypeScript will infer the correct type from fetchUserStep's outputSchema const userData = context.getStepResult(fetchUserStep); return { processed: true, userName: userData?.name }; }, }); const workflow = new Workflow({ name: "user-workflow", }); workflow .step(fetchUserStep) .then(processUserStep) .commit(); ``` ### Using Variable Mapping Variable mapping is an explicit way to define data flow between steps. This approach makes dependencies clear and provides good type safety. The data injected into the step is available in the `context.inputData` object, and typed based on the `inputSchema` of the step. ```typescript showLineNumbers filename="src/kastrax/workflows/variable-mapping.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; const fetchUserStep = new Step({ id: "fetchUser", outputSchema: z.object({ userId: z.string(), name: z.string(), email: z.string(), }), execute: async () => { return { userId: "user123", name: "John Doe", email: "john@example.com" }; }, }); const sendEmailStep = new Step({ id: "sendEmail", inputSchema: z.object({ recipientEmail: z.string(), recipientName: z.string(), }), execute: async ({ context }) => { const { recipientEmail, recipientName } = context.inputData; // Send email logic here return { status: "sent", to: recipientEmail }; }, }); const workflow = new Workflow({ name: "email-workflow", }); workflow .step(fetchUserStep) .then(sendEmailStep, { variables: { // Map specific fields from fetchUser to sendEmail inputs recipientEmail: { step: fetchUserStep, path: 'email' }, recipientName: { step: fetchUserStep, path: 'name' } } }) .commit(); ``` For more details on variable mapping, see the [Data Mapping with Workflow Variables](./variables.mdx) documentation. ### Using the Context Object The context object provides direct access to all step results and their outputs. This approach is more flexible but requires careful handling to maintain type safety. You can access step results directly through the `context.steps` object: ```typescript showLineNumbers filename="src/kastrax/workflows/context-access.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; const processOrderStep = new Step({ id: 'processOrder', execute: async ({ context }) => { // Access data from a previous step let userData: { name: string, userId: string }; if (context.steps['fetchUser']?.status === 'success') { userData = context.steps.fetchUser.output; } else { throw new Error('User data not found'); } return { orderId: 'order123', userId: userData.userId, status: 'processing', }; }, }); const workflow = new Workflow({ name: "order-workflow", }); workflow .step(fetchUserStep) .then(processOrderStep) .commit(); ``` ### Workflow-Level Type Safety For comprehensive type safety across your entire workflow, you can define types for all steps and pass them to the Workflow This allows you to get type safety for the context object on conditions, and on step results in the final workflow output. ```typescript showLineNumbers filename="src/kastrax/workflows/workflow-typing.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; // Create steps with typed outputs const fetchUserStep = new Step({ id: "fetchUser", outputSchema: z.object({ userId: z.string(), name: z.string(), email: z.string(), }), execute: async () => { return { userId: "user123", name: "John Doe", email: "john@example.com" }; }, }); const processOrderStep = new Step({ id: "processOrder", execute: async ({ context }) => { // TypeScript knows the shape of userData const userData = context.getStepResult(fetchUserStep); return { orderId: "order123", status: "processing" }; }, }); const workflow = new Workflow<[typeof fetchUserStep, typeof processOrderStep]>({ name: "typed-workflow", }); workflow .step(fetchUserStep) .then(processOrderStep) .until(async ({ context }) => { // TypeScript knows the shape of userData here const res = context.getStepResult('fetchUser'); return res?.userId === '123'; }, processOrderStep) .commit(); ``` ### Accessing Trigger Data In addition to step results, you can access the original trigger data that started the workflow: ```typescript showLineNumbers filename="src/kastrax/workflows/trigger-data.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; // Define trigger schema const triggerSchema = z.object({ customerId: z.string(), orderItems: z.array(z.string()), }); type TriggerType = z.infer; const processOrderStep = new Step({ id: "processOrder", execute: async ({ context }) => { // Access trigger data with type safety const triggerData = context.getStepResult('trigger'); return { customerId: triggerData?.customerId, itemCount: triggerData?.orderItems.length || 0, status: "processing" }; }, }); const workflow = new Workflow({ name: "order-workflow", triggerSchema, }); workflow .step(processOrderStep) .commit(); ``` ### Accessing Resume Data The data injected into the step is available in the `context.inputData` object, and typed based on the `inputSchema` of the step. ```typescript showLineNumbers filename="src/kastrax/workflows/resume-data.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; const processOrderStep = new Step({ id: "processOrder", inputSchema: z.object({ orderId: z.string(), }), execute: async ({ context, suspend }) => { const { orderId } = context.inputData; if (!orderId) { await suspend(); return; } return { orderId, status: "processed" }; }, }); const workflow = new Workflow({ name: "order-workflow", }); workflow .step(processOrderStep) .commit(); const run = workflow.createRun(); const result = await run.start(); const resumedResult = await workflow.resume({ runId: result.runId, stepId: 'processOrder', inputData: { orderId: '123', }, }); console.log({resumedResult}); ``` ### Accessing Workflow Results You can get typed access to the results of a workflow by injecting the step types into the `Workflow` type params: ```typescript showLineNumbers filename="src/kastrax/workflows/get-results.ts" copy import { Workflow } from "@kastrax/core/workflows"; const fetchUserStep = new Step({ id: "fetchUser", outputSchema: z.object({ userId: z.string(), name: z.string(), email: z.string(), }), execute: async () => { return { userId: "user123", name: "John Doe", email: "john@example.com" }; }, }); const processOrderStep = new Step({ id: "processOrder", outputSchema: z.object({ orderId: z.string(), status: z.string(), }), execute: async ({ context }) => { const userData = context.getStepResult(fetchUserStep); return { orderId: "order123", status: "processing" }; }, }); const workflow = new Workflow<[typeof fetchUserStep, typeof processOrderStep]>({ name: "typed-workflow", }); workflow .step(fetchUserStep) .then(processOrderStep) .commit(); const run = workflow.createRun(); const result = await run.start(); // The result is a discriminated union of the step results // So it needs to be narrowed down via status checks if (result.results.processOrder.status === 'success') { // TypeScript will know the shape of the results const orderId = result.results.processOrder.output.orderId; console.log({orderId}); } if (result.results.fetchUser.status === 'success') { const userId = result.results.fetchUser.output.userId; console.log({userId}); } ``` ### Best Practices for Data Flow 1. **Use getStepResult with Step References for Type Safety** - Ensures TypeScript can infer the correct types - Catches type errors at compile time 2. **Use Variable Mapping for Explicit Dependencies* - Makes data flow clear and maintainable - Provides good documentation of step dependencies 3. **Define Output Schemas for Steps** - Validates data at runtime - Validates return type of the `execute` function - Improves type inference in TypeScript 4. **Handle Missing Data Gracefully** - Always check if step results exist before accessing properties - Provide fallback values for optional data 5. **Keep Data Transformations Simple** - Transform data in dedicated steps rather than in variable mappings - Makes workflows easier to test and debug ### Comparison of Data Flow Methods | Method | Type Safety | Explicitness | Use Case | |--------|------------|--------------|----------| | getStepResult | Highest | High | Complex workflows with strict typing requirements | | Variable Mapping | High | High | When dependencies need to be clear and explicit | | context.steps | Medium | Low | Quick access to step data in simple workflows | By choosing the right data flow method for your use case, you can create workflows that are both type-safe and maintainable. --- title: "Dynamic Workflows in Kastrax | Workflows | Kastrax Docs" description: "Learn how to create and execute dynamic workflows in Kastrax that adapt to runtime conditions, enabling flexible AI agent orchestration patterns." --- # Dynamic Workflows in Kastrax ✅ [EN] Source: https://kastrax.ai/en/docs/workflows/dynamic-workflows Kastrax provides powerful capabilities for creating and executing dynamic workflows - workflows that are generated or modified at runtime based on data, conditions, or agent decisions. This advanced pattern enables highly adaptive AI agent orchestration that can respond to changing requirements and contexts. ## Dynamic Workflow Architecture ✅ Dynamic workflows in Kastrax are built on several key components: 1. **Workflow Generator**: A system that creates workflow definitions at runtime 2. **Dynamic Step Creation**: The ability to add, modify, or remove steps based on runtime conditions 3. **Workflow Templates**: Reusable workflow patterns that can be customized with parameters 4. **Runtime Workflow Modification**: Capabilities to alter workflow structure during execution These components work together to enable workflows that can adapt their structure and behavior based on data, agent decisions, or external factors. ## Creating Dynamic Workflows ✅ Kastrax provides several approaches for creating dynamic workflows, each suited for different use cases: ### 1. Using the Workflow Generator The most direct approach is to use Kastrax's `DynamicWorkflowGenerator` to create workflows at runtime: ```kotlin filename="DynamicWorkflowGenerator.kt" import ai.kastrax.core.KastraxSystem import ai.kastrax.core.workflow.dynamic.DynamicWorkflowGenerator import ai.kastrax.core.workflow.variable import ai.kastrax.core.agent.Agent import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Initialize the Kastrax system val kastraxSystem = KastraxSystem() // Create agents for use in the dynamic workflow val dataProcessingAgent: Agent = /* ... */ val analysisAgent: Agent = /* ... */ // Create a dynamic workflow generator val workflowGenerator = DynamicWorkflowGenerator(kastraxSystem) // Generate a dynamic workflow based on runtime conditions val dynamicWorkflow = workflowGenerator.createWorkflow( workflowId = "dynamic-data-workflow", description = "Dynamically generated workflow for data processing" ) { // Define input parameters input { variable("dataSource", String::class, required = true) variable("analysisType", String::class, defaultValue = "standard") } // Add steps to the workflow step(dataProcessingAgent) { id = "process-data" name = "Process Data" description = "Process data from the source" variables = mutableMapOf( "source" to variable("$.input.dataSource") ) } step(analysisAgent) { id = "analyze-data" name = "Analyze Data" description = "Analyze the processed data" after("process-data") variables = mutableMapOf( "data" to variable("$.steps.process-data.output.processedData"), "analysisType" to variable("$.input.analysisType") ) } // Define workflow output output { "results" from "$.steps.analyze-data.output.results" "metadata" from "$.steps.process-data.output.metadata" } } // Register the dynamic workflow with the system kastraxSystem.registerWorkflow(dynamicWorkflow) // Execute the dynamic workflow val result = kastraxSystem.workflowEngine.executeWorkflow( workflowId = "dynamic-data-workflow", input = mapOf( "dataSource" to "customer_database", "analysisType" to "comprehensive" ) ) println("Workflow execution result: ${result.output}") } ``` ### 2. Dynamic Step Creation in Workflow Steps Another approach is to dynamically create steps within an existing workflow based on runtime conditions: ```kotlin filename="DynamicStepCreation.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import ai.kastrax.core.agent.Agent // Create a workflow with dynamic step creation val adaptiveWorkflow = workflow { name = "adaptive-workflow" description = "Workflow that adapts its steps based on input data" // Initial data analysis step step(analysisAgent) { id = "analyze-input" name = "Analyze Input Data" description = "Determine what processing is needed" variables = mutableMapOf( "data" to variable("$.input.data") ) // Dynamically create subsequent steps based on analysis results onComplete { result -> val dataType = result["dataType"] as? String ?: "unknown" val complexity = result["complexity"] as? Int ?: 1 when (dataType) { "text" -> { // Add text processing steps step(textProcessingAgent) { id = "process-text" name = "Process Text Data" description = "Process textual content" variables = mutableMapOf( "text" to variable("$.steps.analyze-input.output.extractedText") ) } // Add more steps if content is complex if (complexity > 3) { step(advancedTextAgent) { id = "advanced-text-analysis" name = "Advanced Text Analysis" description = "Perform advanced text analysis" after("process-text") variables = mutableMapOf( "processedText" to variable("$.steps.process-text.output.result") ) } } } "image" -> { // Add image processing steps step(imageProcessingAgent) { id = "process-image" name = "Process Image Data" description = "Process image content" variables = mutableMapOf( "imageUrl" to variable("$.steps.analyze-input.output.imageUrl") ) } } else -> { // Add fallback processing step step(genericProcessingAgent) { id = "generic-processing" name = "Generic Data Processing" description = "Process unknown data type" variables = mutableMapOf( "data" to variable("$.steps.analyze-input.output.rawData") ) } } } // Always add a final reporting step step(reportingAgent) { id = "generate-report" name = "Generate Report" description = "Create a report of the processing results" // This step will automatically depend on all previously created steps variables = mutableMapOf( "dataType" to variable("$.steps.analyze-input.output.dataType"), "results" to variable("$.steps", transform = { steps -> // Collect results from all processing steps (steps as Map).filter { it.key != "analyze-input" && it.key != "generate-report" }.mapValues { (it.value as? Map)?.get("output") ?: mapOf() } }) ) } } } } ### 3. Workflow Factory Pattern For more complex scenarios, you can implement a workflow factory that generates different workflows based on input parameters: ```kotlin filename="WorkflowFactory.kt" import ai.kastrax.core.KastraxSystem import ai.kastrax.core.workflow.Workflow import ai.kastrax.core.workflow.WorkflowBuilder import ai.kastrax.core.workflow.variable import ai.kastrax.core.agent.Agent import kotlinx.coroutines.runBlocking // Define a workflow factory class class WorkflowFactory(private val kastraxSystem: KastraxSystem) { // Agents used in workflows private val textProcessingAgent: Agent = /* ... */ private val imageProcessingAgent: Agent = /* ... */ private val dataAnalysisAgent: Agent = /* ... */ private val reportGenerationAgent: Agent = /* ... */ // Create a workflow based on content type fun createContentWorkflow(contentType: String, complexity: Int): Workflow { return when (contentType) { "blog" -> createBlogWorkflow(complexity) "social" -> createSocialMediaWorkflow(complexity) "documentation" -> createDocumentationWorkflow(complexity) else -> createGenericContentWorkflow() } } // Create a blog content workflow private fun createBlogWorkflow(complexity: Int): Workflow { return WorkflowBuilder().apply { id = "blog-content-workflow" description = "Workflow for creating blog content" // Define input parameters input { variable("topic", String::class, required = true) variable("tone", String::class, defaultValue = "informative") variable("wordCount", Int::class, defaultValue = 1000) } // Research step step(textProcessingAgent) { id = "research" name = "Research Topic" description = "Research the blog topic" variables = mutableMapOf( "topic" to variable("$.input.topic"), "depth" to if (complexity > 2) "deep" else "standard" ) } // Outline step step(textProcessingAgent) { id = "create-outline" name = "Create Outline" description = "Create a blog post outline" after("research") variables = mutableMapOf( "research" to variable("$.steps.research.output.results"), "complexity" to complexity ) } // Writing step step(textProcessingAgent) { id = "write-content" name = "Write Blog Content" description = "Write the blog content" after("create-outline") variables = mutableMapOf( "outline" to variable("$.steps.create-outline.output.outline"), "research" to variable("$.steps.research.output.results"), "tone" to variable("$.input.tone"), "wordCount" to variable("$.input.wordCount") ) } // Add additional steps for higher complexity if (complexity >= 3) { step(imageProcessingAgent) { id = "create-images" name = "Create Blog Images" description = "Generate images for the blog post" after("write-content") variables = mutableMapOf( "content" to variable("$.steps.write-content.output.content"), "style" to "professional" ) } step(textProcessingAgent) { id = "edit-content" name = "Edit Blog Content" description = "Edit and improve the blog content" after("write-content") variables = mutableMapOf( "content" to variable("$.steps.write-content.output.content"), "editingLevel" to "thorough" ) } } // Define workflow output output { "content" from if (complexity >= 3) "$.steps.edit-content.output.content" else "$.steps.write-content.output.content" "images" from if (complexity >= 3) "$.steps.create-images.output.images" else listOf() "metadata" from "$.steps.research.output.metadata" } }.build() } // Create a social media content workflow private fun createSocialMediaWorkflow(complexity: Int): Workflow { // Similar implementation to blog workflow but with social media focus // ... return WorkflowBuilder().apply { // Social media workflow implementation // ... }.build() } // Create a documentation workflow private fun createDocumentationWorkflow(complexity: Int): Workflow { // Similar implementation to blog workflow but with documentation focus // ... return WorkflowBuilder().apply { // Documentation workflow implementation // ... }.build() } // Create a generic content workflow private fun createGenericContentWorkflow(): Workflow { return WorkflowBuilder().apply { id = "generic-content-workflow" description = "Generic workflow for content creation" // Basic implementation with minimal steps // ... }.build() } } // Usage example fun main() = runBlocking { val kastraxSystem = KastraxSystem() val workflowFactory = WorkflowFactory(kastraxSystem) // Create a workflow based on content type and complexity val blogWorkflow = workflowFactory.createContentWorkflow("blog", 4) // Register the workflow kastraxSystem.registerWorkflow(blogWorkflow) // Execute the workflow val result = kastraxSystem.workflowEngine.executeWorkflow( workflowId = blogWorkflow.id, input = mapOf( "topic" to "Artificial Intelligence in Healthcare", "tone" to "professional", "wordCount" to 1500 ) ) println("Workflow execution result: ${result.output}") } ``` ## Template-Based Dynamic Workflows ✅ Kastrax also supports template-based dynamic workflows, which provide a powerful way to create customizable workflow patterns: ```kotlin filename="WorkflowTemplates.kt" import ai.kastrax.core.workflow.template.WorkflowTemplate import ai.kastrax.core.workflow.template.TemplateParameter import ai.kastrax.core.workflow.variable // Define a workflow template for content creation class ContentWorkflowTemplate : WorkflowTemplate { // Define template parameters override val parameters = listOf( TemplateParameter("contentType", String::class, required = true), TemplateParameter("includeImages", Boolean::class, defaultValue = false), TemplateParameter("reviewSteps", Int::class, defaultValue = 1), TemplateParameter("targetAudience", String::class, defaultValue = "general") ) // Build the workflow from the template override fun build(params: Map): Workflow { // Extract parameters val contentType = params["contentType"] as String val includeImages = params["includeImages"] as Boolean val reviewSteps = params["reviewSteps"] as Int val targetAudience = params["targetAudience"] as String return WorkflowBuilder().apply { id = "${contentType.lowercase()}-content-workflow" description = "Workflow for creating $contentType content" // Define input parameters input { variable("topic", String::class, required = true) variable("tone", String::class, defaultValue = "informative") } // Research step step(researchAgent) { id = "research" name = "Research Topic" description = "Research the topic for $contentType content" variables = mutableMapOf( "topic" to variable("$.input.topic"), "contentType" to contentType, "audience" to targetAudience ) } // Content creation step step(contentCreationAgent) { id = "create-content" name = "Create $contentType Content" description = "Create the $contentType content" after("research") variables = mutableMapOf( "research" to variable("$.steps.research.output.results"), "topic" to variable("$.input.topic"), "tone" to variable("$.input.tone"), "contentType" to contentType, "audience" to targetAudience ) } // Add image generation if requested if (includeImages) { step(imageGenerationAgent) { id = "generate-images" name = "Generate Images" description = "Generate images for the content" after("create-content") variables = mutableMapOf( "content" to variable("$.steps.create-content.output.content"), "contentType" to contentType, "style" to variable("$.input.tone") ) } } // Add review steps based on parameter repeat(reviewSteps) { index -> step(reviewAgent) { id = "review-${index + 1}" name = "Review ${index + 1}" description = "Review and improve the content" after(if (index == 0) { if (includeImages) "generate-images" else "create-content" } else "review-$index") variables = mutableMapOf( "content" to if (index == 0) { variable("$.steps.create-content.output.content") } else { variable("$.steps.review-$index.output.revisedContent") }, "reviewDepth" to "thorough", "reviewNumber" to (index + 1) ) } } // Define workflow output output { "content" from if (reviewSteps > 0) { "$.steps.review-$reviewSteps.output.revisedContent" } else { "$.steps.create-content.output.content" } if (includeImages) { "images" from "$.steps.generate-images.output.images" } "metadata" from "$.steps.research.output.metadata" } }.build() } } // Usage example fun main() = runBlocking { val kastraxSystem = KastraxSystem() // Create a template instance val template = ContentWorkflowTemplate() // Create workflows from the template with different parameters val blogWorkflow = template.build(mapOf( "contentType" to "Blog", "includeImages" to true, "reviewSteps" to 2, "targetAudience" to "technical" )) val socialWorkflow = template.build(mapOf( "contentType" to "Social", "includeImages" to true, "reviewSteps" to 1, "targetAudience" to "general" )) // Register the workflows kastraxSystem.registerWorkflow(blogWorkflow) kastraxSystem.registerWorkflow(socialWorkflow) // Execute the blog workflow val result = kastraxSystem.workflowEngine.executeWorkflow( workflowId = blogWorkflow.id, input = mapOf( "topic" to "Kotlin Coroutines", "tone" to "educational" ) ) println("Blog workflow result: ${result.output}") } ``` ## Important Considerations ✅ ### 1. Performance and Resource Management Dynamic workflows require careful resource management: - **Workflow Caching**: Consider caching frequently used workflow patterns instead of recreating them - **Resource Limits**: Implement limits on the number of dynamic workflows that can be created - **Cleanup**: Ensure proper cleanup of unused dynamic workflows to prevent resource leaks ### 2. Error Handling and Validation Robust error handling is essential for dynamic workflows: - **Parameter Validation**: Validate all parameters used to create dynamic workflows - **Graceful Degradation**: Implement fallback workflows when dynamic creation fails - **Comprehensive Logging**: Log all aspects of dynamic workflow creation and execution ### 3. Workflow Lifecycle Management Manage the lifecycle of dynamic workflows carefully: - **Registration**: Explicitly register important dynamic workflows with the Kastrax system - **Versioning**: Consider versioning dynamic workflows for tracking and debugging - **Persistence**: Decide whether dynamic workflows should persist beyond their initial execution ### 4. Testing and Debugging Testing dynamic workflows requires special approaches: - **Template Testing**: Test workflow templates with various parameter combinations - **Snapshot Testing**: Compare generated workflow structures against expected snapshots - **Runtime Validation**: Validate workflow structure before execution ## Use Cases ✅ Dynamic workflows in Kastrax are particularly valuable for these scenarios: ### 1. Content Generation Pipelines Create specialized content workflows based on content type, audience, and complexity requirements: - Blog posts with varying levels of research and editing - Social media content with platform-specific steps - Technical documentation with customized review processes ### 2. Multi-Agent Collaboration Dynamically assemble workflows that coordinate multiple specialized agents: - Research teams with domain-specific agent selection - Creative projects with dynamic role assignment - Problem-solving workflows with adaptive agent selection ### 3. Adaptive Processing Create workflows that adapt to the nature of the input data: - Data processing pipelines that adjust based on data characteristics - Content analysis workflows that vary based on content type - Decision-making processes that adapt to complexity levels ### 4. Multi-tenant Applications Generate isolated workflows for different tenants or users: - Customer-specific processing pipelines - User-customized agent workflows - Organization-specific approval processes ## Conclusion ✅ Dynamic workflows represent one of Kastrax's most powerful capabilities, enabling truly adaptive AI agent orchestration. By leveraging the workflow generation, templating, and runtime modification features, you can create sophisticated systems that respond intelligently to changing requirements and contexts. The combination of Kastrax's type-safe workflow DSL with dynamic workflow generation provides both the safety of compile-time checking and the flexibility of runtime adaptation - a powerful foundation for building advanced AI agent applications. --- title: "Error Handling in Kastrax AI Workflows | Kastrax Docs" description: "Learn how to implement robust error handling in Kastrax AI workflows using retry mechanisms, error recovery strategies, fallback paths, and comprehensive monitoring." --- # Error Handling in Kastrax AI Workflows ✅ [EN] Source: https://kastrax.ai/en/docs/workflows/error-handling Robust error handling is essential for production AI workflows, especially when orchestrating multiple agents and external services. Kastrax provides a comprehensive error handling system that allows your workflows to recover from failures, implement fallback strategies, and gracefully degrade when necessary. ## Error Handling Architecture ✅ Kastrax implements a multi-layered approach to error handling in workflows: 1. **Step-Level Error Handling** - Handle errors within individual steps 2. **Workflow-Level Recovery** - Implement alternative paths and fallback strategies 3. **Retry Mechanisms** - Automatically retry failed operations with configurable policies 4. **Error Propagation** - Control how errors flow through the workflow 5. **Monitoring and Observability** - Track and respond to errors across the workflow 6. **Graceful Degradation** - Continue workflow execution with reduced functionality This layered approach ensures that errors can be handled at the most appropriate level, from localized recovery to workflow-wide strategies. ## Step-Level Error Handling ✅ The most direct way to handle errors in Kastrax is at the step level, where you can catch and process exceptions, implement retry logic, and provide fallback results. ### Try-Catch Pattern ```kotlin filename="StepErrorHandling.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with error handling in steps val robustWorkflow = workflow { name = "robust-workflow" description = "Workflow with comprehensive error handling" // Step with try-catch error handling step(dataProcessingAgent) { id = "process-data" name = "Process Data" description = "Process data with error handling" // Custom execution with error handling execute { context -> try { // Attempt the primary operation val data = context.getVariable("data") as? Map ?: emptyMap() val result = processData(data) // This might throw exceptions // Return successful result mapOf( "success" to true, "result" to result, "error" to null ) } catch (e: Exception) { // Log the error println("Data processing error: ${e.message}") // Return a graceful fallback result mapOf( "success" to false, "result" to null, "error" to e.message, "fallbackData" to "Default value" ) } } } // Subsequent step that handles the error condition conditionalStep { id = "handle-result" name = "Handle Processing Result" description = "Process the result or handle the error" after("process-data") // Check if previous step succeeded condition { context -> val success = context.getVariable("$.steps.process-data.output.success") as? Boolean ?: false success } // Handle successful case onTrue { step(successAgent) { id = "handle-success" name = "Handle Success" description = "Process successful result" variables = mutableMapOf( "result" to variable("$.steps.process-data.output.result") ) } } // Handle error case onFalse { step(errorHandlingAgent) { id = "handle-error" name = "Handle Error" description = "Process error and fallback data" variables = mutableMapOf( "error" to variable("$.steps.process-data.output.error"), "fallbackData" to variable("$.steps.process-data.output.fallbackData") ) } } } } // Example data processing function that might throw exceptions fun processData(data: Map): Map { // Simulate potential errors if (data.isEmpty()) { throw IllegalArgumentException("Empty data cannot be processed") } // Process the data return mapOf( "processedData" to "Processed: ${data}", "timestamp" to System.currentTimeMillis() ) } ``` ## Retry Mechanisms ✅ Kastrax provides built-in retry mechanisms for steps that fail due to transient errors. This is particularly useful for steps that interact with external services or resources that might experience temporary unavailability. ### Configuring Retries ```kotlin filename="RetryConfiguration.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.retry.RetryConfig import ai.kastrax.core.workflow.retry.ExponentialBackoff // Create a workflow with retry configuration val apiWorkflow = workflow { name = "api-workflow" description = "Workflow that interacts with external APIs" // Workflow-level retry configuration (default for all steps) defaultRetryConfig = RetryConfig( maxAttempts = 3, // Number of retry attempts backoff = ExponentialBackoff( initialDelayMs = 1000, // Initial delay in milliseconds factor = 2.0, // Exponential factor maxDelayMs = 10000 // Maximum delay in milliseconds ) ) // Step with custom retry configuration step(apiCallAgent) { id = "call-api" name = "Call External API" description = "Make an API call with retry logic" // Step-level retry configuration (overrides workflow-level) retry = RetryConfig( maxAttempts = 5, // This step will retry up to 5 times backoff = ExponentialBackoff( initialDelayMs = 2000, // Start with 2 seconds factor = 1.5, // Slower growth factor maxDelayMs = 30000 // Up to 30 seconds ), retryableExceptions = listOf( // Only retry these exceptions java.net.ConnectException::class, java.net.SocketTimeoutException::class, io.ktor.client.network.sockets.ConnectTimeoutException::class ) ) variables = mutableMapOf( "endpoint" to variable("$.input.endpoint"), "payload" to variable("$.input.payload") ) } // Another step with conditional retry logic step(dataFetchAgent) { id = "fetch-data" name = "Fetch Data" description = "Fetch data with conditional retry" after("call-api") // Custom retry condition retry = RetryConfig( maxAttempts = 3, backoff = ExponentialBackoff(initialDelayMs = 1000), retryCondition = { exception, attempt -> // Only retry rate limit errors, and only up to 3 times exception is RateLimitException && attempt <= 3 } ) variables = mutableMapOf( "apiResult" to variable("$.steps.call-api.output.result") ) } } // Custom exception for rate limiting class RateLimitException(message: String) : Exception(message) ## Workflow-Level Recovery Strategies ✅ Kastrax provides several mechanisms for implementing workflow-level recovery strategies, including conditional branching, fallback paths, and error recovery steps. ### Conditional Branching ```kotlin filename="ConditionalErrorHandling.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with conditional branching for error handling val dataWorkflow = workflow { name = "data-workflow" description = "Process data with error handling branches" // Initial data fetching step step(dataFetchAgent) { id = "fetch-data" name = "Fetch Data" description = "Retrieve data from external source" variables = mutableMapOf( "source" to variable("$.input.dataSource"), "credentials" to variable("$.input.credentials") ) } // Conditional branching based on fetch result conditionalStep { id = "check-fetch-result" name = "Check Fetch Result" description = "Determine next steps based on fetch result" after("fetch-data") // Check if data fetch was successful condition { context -> val success = context.getVariable("$.steps.fetch-data.output.success") as? Boolean ?: false val dataSize = context.getVariable("$.steps.fetch-data.output.data")?.let { when (it) { is List<*> -> it.size is Map<*, *> -> it.size is String -> it.length else -> 0 } } ?: 0 success && dataSize > 0 } // Success path - process the data onTrue { step(dataProcessingAgent) { id = "process-data" name = "Process Data" description = "Process the fetched data" variables = mutableMapOf( "data" to variable("$.steps.fetch-data.output.data") ) } } // Error path - handle the failure onFalse { // Check what type of error occurred conditionalStep { id = "diagnose-error" name = "Diagnose Error" description = "Determine the type of error" condition { context -> val errorType = context.getVariable("$.steps.fetch-data.output.errorType") as? String errorType == "auth_failure" } // Authentication error path onTrue { step(authRepairAgent) { id = "repair-auth" name = "Repair Authentication" description = "Attempt to fix authentication issues" variables = mutableMapOf( "credentials" to variable("$.input.credentials"), "error" to variable("$.steps.fetch-data.output.error") ) } } // Other error types onFalse { step(fallbackDataAgent) { id = "use-fallback-data" name = "Use Fallback Data" description = "Retrieve data from fallback source" variables = mutableMapOf( "originalSource" to variable("$.input.dataSource"), "fallbackSource" to variable("$.input.fallbackSource", defaultValue = "cache") ) } } } } } // Final step that merges results from all possible paths step(resultMergingAgent) { id = "merge-results" name = "Merge Results" description = "Combine results from all possible paths" // This step will run after any of the possible paths execute { context -> // Check which path was taken and get appropriate data val processedData = context.getVariable("$.steps.process-data.output.result") val fallbackData = context.getVariable("$.steps.use-fallback-data.output.data") val repairedAuth = context.getVariable("$.steps.repair-auth.output.success") // Determine the final result based on which path succeeded when { processedData != null -> { mapOf( "source" to "primary", "data" to processedData, "complete" to true ) } fallbackData != null -> { mapOf( "source" to "fallback", "data" to fallbackData, "complete" to true ) } repairedAuth == true -> { mapOf( "source" to "repaired", "message" to "Authentication repaired, please retry operation", "complete" to false ) } else -> { mapOf( "source" to "none", "error" to "All recovery paths failed", "complete" to false ) } } } } } ``` ## Monitoring and Observability ✅ Kastrax provides comprehensive monitoring capabilities for tracking workflow execution and detecting errors: ```kotlin filename="WorkflowMonitoring.kt" import ai.kastrax.core.KastraxSystem import ai.kastrax.core.workflow.WorkflowExecuteOptions import ai.kastrax.core.workflow.monitoring.WorkflowMonitor import ai.kastrax.core.workflow.monitoring.StepStatus import kotlinx.coroutines.runBlocking fun main() = runBlocking { val kastraxSystem = KastraxSystem() // Create a workflow monitor val workflowMonitor = WorkflowMonitor(kastraxSystem) // Execute a workflow with monitoring val result = kastraxSystem.workflowEngine.executeWorkflow( workflowId = "data-workflow", input = mapOf( "dataSource" to "api.example.com/data", "credentials" to mapOf("apiKey" to "your-api-key") ), options = WorkflowExecuteOptions( // Monitor step execution onStepStart = { stepId, input -> println("Starting step: $stepId with input: $input") }, onStepFinish = { stepResult -> println("Step ${stepResult.stepId} completed with status: ${stepResult.status}") // Check for errors if (stepResult.status == StepStatus.ERROR) { println("Error in step ${stepResult.stepId}: ${stepResult.error}") // Log detailed error information logStepError( stepId = stepResult.stepId, error = stepResult.error, context = stepResult.context ) } }, // Monitor workflow completion onComplete = { workflowResult -> println("Workflow completed with status: ${workflowResult.status}") // Check for workflow-level errors if (!workflowResult.success) { println("Workflow failed: ${workflowResult.error}") // Send alerts for critical workflows sendErrorAlert( workflowId = "data-workflow", error = workflowResult.error, steps = workflowResult.steps ) } } ) ) // Analyze workflow execution val executionAnalysis = workflowMonitor.analyzeExecution(result.executionId) println("Execution analysis: $executionAnalysis") // Check for specific error patterns val errorPatterns = workflowMonitor.detectErrorPatterns(result.executionId) if (errorPatterns.isNotEmpty()) { println("Detected error patterns: $errorPatterns") // Take remedial action based on error patterns handleErrorPatterns(errorPatterns) } } // Helper functions for error handling fun logStepError(stepId: String, error: Throwable?, context: Map) { // Log detailed error information to monitoring system // In a real implementation, this would send data to a logging service println("DETAILED ERROR LOG - Step: $stepId, Error: ${error?.message}") println("Context: $context") } fun sendErrorAlert(workflowId: String, error: Throwable?, steps: Map) { // Send alerts to appropriate channels // In a real implementation, this might send emails, Slack messages, etc. println("ALERT: Workflow $workflowId failed with error: ${error?.message}") } fun handleErrorPatterns(patterns: List) { // Take action based on recognized error patterns // This might involve automatic remediation, escalation, etc. patterns.forEach { pattern -> println("Handling error pattern: $pattern") } } ``` ## Error Propagation and Handling ✅ Kastrax provides fine-grained control over how errors propagate through workflows and how they're handled at different levels. ### Error Propagation Control ```kotlin filename="ErrorPropagation.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import ai.kastrax.core.workflow.error.ErrorHandlingMode // Create a workflow with controlled error propagation val criticalWorkflow = workflow { name = "critical-workflow" description = "Workflow with controlled error propagation" // Set default error handling mode for the workflow errorHandlingMode = ErrorHandlingMode.CONTINUE_ON_ERROR // First step - critical operation step(criticalOperationAgent) { id = "critical-operation" name = "Critical Operation" description = "Perform a critical operation that must succeed" // Override workflow-level error handling - this step must succeed errorHandlingMode = ErrorHandlingMode.FAIL_WORKFLOW variables = mutableMapOf( "input" to variable("$.input.criticalData") ) } // Second step - important but not critical step(importantOperationAgent) { id = "important-operation" name = "Important Operation" description = "Perform an important but non-critical operation" after("critical-operation") // This step will be attempted, but failure won't stop the workflow errorHandlingMode = ErrorHandlingMode.CONTINUE_ON_ERROR variables = mutableMapOf( "criticalResult" to variable("$.steps.critical-operation.output.result") ) } // Third step - optional enhancement step(enhancementAgent) { id = "enhancement" name = "Enhancement" description = "Perform an optional enhancement" after("important-operation") // This step is optional - errors are logged but ignored errorHandlingMode = ErrorHandlingMode.IGNORE_ERROR variables = mutableMapOf( "baseData" to variable("$.steps.critical-operation.output.result"), "additionalData" to variable("$.steps.important-operation.output.result", defaultValue = null) ) } // Final step - result aggregation step(resultAggregationAgent) { id = "aggregate-results" name = "Aggregate Results" description = "Combine results from all steps" after("enhancement") execute { context -> // Get results from previous steps, handling potential failures val criticalResult = context.getVariable("$.steps.critical-operation.output.result") // For steps that might have failed, check status and handle accordingly val importantStepStatus = context.getStepStatus("important-operation") val importantResult = if (importantStepStatus == "success") { context.getVariable("$.steps.important-operation.output.result") } else { null } val enhancementStepStatus = context.getStepStatus("enhancement") val enhancementResult = if (enhancementStepStatus == "success") { context.getVariable("$.steps.enhancement.output.result") } else { null } // Aggregate results based on what succeeded mapOf( "criticalResult" to criticalResult, "importantResult" to importantResult, "enhancementResult" to enhancementResult, "completionLevel" to when { enhancementResult != null -> "full" importantResult != null -> "standard" else -> "minimal" }, "errors" to mapOf( "importantOperation" to (importantStepStatus != "success"), "enhancement" to (enhancementStepStatus != "success") ) ) } } } ``` ### Custom Error Handlers ```kotlin filename="CustomErrorHandlers.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import ai.kastrax.core.workflow.error.ErrorHandler // Create a workflow with custom error handlers val resilientWorkflow = workflow { name = "resilient-workflow" description = "Workflow with custom error handlers" // Define a global error handler for the workflow errorHandler = ErrorHandler { stepId, error, context -> println("Error in step $stepId: ${error.message}") // Log the error to a monitoring system logError(stepId, error, context) // Return a default result for the failed step mapOf( "error" to error.message, "errorType" to error.javaClass.simpleName, "recovered" to true, "defaultResult" to "Error recovery value" ) } // First step with a custom error handler step(dataProcessingAgent) { id = "process-data" name = "Process Data" description = "Process data with custom error handling" // Step-specific error handler errorHandler = ErrorHandler { _, error, context -> // Check the specific type of error when (error) { is IllegalArgumentException -> { // Handle validation errors mapOf( "error" to "Validation error: ${error.message}", "errorType" to "validation", "recovered" to true, "validationErrors" to listOf(error.message) ) } is java.io.IOException -> { // Handle I/O errors mapOf( "error" to "I/O error: ${error.message}", "errorType" to "io", "recovered" to false ) } else -> { // Handle other errors mapOf( "error" to "Unknown error: ${error.message}", "errorType" to "unknown", "recovered" to false ) } } } variables = mutableMapOf( "data" to variable("$.input.data") ) } // Additional steps... } // Helper function for error logging fun logError(stepId: String, error: Throwable, context: Map) { // In a real implementation, this would log to a monitoring system println("LOGGING ERROR - Step: $stepId, Error: ${error.message}, Context: $context") } ``` ## Best Practices for Error Handling ✅ ### 1. Design for Failure Assume that any step in your workflow might fail and design accordingly: ```kotlin // Good: Design steps with error handling in mind step(apiCallAgent) { id = "call-api" execute { context -> try { val result = callExternalApi(context.getVariable("endpoint") as String) mapOf("result" to result, "success" to true) } catch (e: Exception) { mapOf("error" to e.message, "success" to false) } } } // Avoid: Assuming steps will always succeed // step(apiCallAgent) { // id = "call-api" // execute { context -> // val result = callExternalApi(context.getVariable("endpoint") as String) // mapOf("result" to result) // } // } ``` ### 2. Use Appropriate Error Handling Modes Choose the right error handling mode for each step based on its importance: ```kotlin // Critical steps should fail the workflow if they fail step(paymentProcessingAgent) { id = "process-payment" errorHandlingMode = ErrorHandlingMode.FAIL_WORKFLOW } // Non-critical steps can continue with errors step(analyticsAgent) { id = "record-analytics" errorHandlingMode = ErrorHandlingMode.CONTINUE_ON_ERROR } // Optional steps can ignore errors entirely step(notificationAgent) { id = "send-notification" errorHandlingMode = ErrorHandlingMode.IGNORE_ERROR } ``` ### 3. Implement Comprehensive Retry Strategies Use sophisticated retry strategies for different types of operations: ```kotlin // Network operations might need exponential backoff step(networkAgent) { id = "network-operation" retry = RetryConfig( maxAttempts = 5, backoff = ExponentialBackoff(initialDelayMs = 1000, factor = 2.0) ) } // Database operations might need different retry logic step(databaseAgent) { id = "database-operation" retry = RetryConfig( maxAttempts = 3, backoff = LinearBackoff(delayMs = 2000), // Consistent delay retryableExceptions = listOf(DatabaseTimeoutException::class) ) } ``` ### 4. Provide Detailed Error Context Include comprehensive information in error outputs: ```kotlin // Return detailed error information execute { context -> try { // Operation that might fail } catch (e: Exception) { mapOf( "error" to e.message, "errorType" to e.javaClass.simpleName, "timestamp" to System.currentTimeMillis(), "context" to mapOf( "input" to context.getVariable("input"), "userId" to context.getVariable("$.input.userId"), "operation" to "data-processing" ), "stackTrace" to e.stackTraceToString() ) } } ``` ## Advanced Error Handling ✅ For more complex error handling scenarios, Kastrax provides advanced mechanisms: ### Circuit Breakers Implement circuit breakers to prevent cascading failures: ```kotlin // Circuit breaker pattern implementation class CircuitBreaker( private val maxFailures: Int = 3, private val resetTimeoutMs: Long = 60000 ) { private var failures = 0 private var lastFailureTime = 0L private var state = State.CLOSED enum class State { CLOSED, OPEN, HALF_OPEN } fun execute(operation: () -> Map): Map { // Check if circuit is open if (state == State.OPEN) { val now = System.currentTimeMillis() if (now - lastFailureTime > resetTimeoutMs) { // Try to reset after timeout state = State.HALF_OPEN } else { // Circuit is open, return fast failure return mapOf( "error" to "Circuit breaker open", "success" to false, "circuitState" to state ) } } return try { val result = operation() // If successful in half-open state, reset the circuit if (state == State.HALF_OPEN) { state = State.CLOSED failures = 0 } result } catch (e: Exception) { failures++ lastFailureTime = System.currentTimeMillis() // Open circuit if too many failures if (failures >= maxFailures) { state = State.OPEN } mapOf( "error" to e.message, "success" to false, "circuitState" to state ) } } } ``` ### Integration with Actor Model Kastrax's error handling system integrates seamlessly with the actor model for distributed error handling: ```kotlin filename="ActorErrorHandling.kt" import ai.kastrax.actor.ActorSystem import ai.kastrax.actor.Props import ai.kastrax.workflow.WorkflowActor import ai.kastrax.workflow.messages.* import ai.kastrax.workflow.error.ErrorHandlingActor import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an actor system val system = ActorSystem("workflow-system") // Create an error handling actor val errorHandlingActor = system.actorOf( Props.create(ErrorHandlingActor::class.java), "error-handler" ) // Create a workflow actor with error handling val workflowActor = system.actorOf( Props.create(WorkflowActor::class.java, resilientWorkflow, errorHandlingActor), "workflow-actor" ) // Execute the workflow val result = system.ask( workflowActor, ExecuteWorkflowMessage(input = mapOf("data" to "test-data")) ) // Check for errors if (result is WorkflowResult.Error) { println("Workflow execution failed: ${result.error}") // Send error recovery message system.tell( errorHandlingActor, RecoverWorkflowMessage( workflowId = resilientWorkflow.id, executionId = result.executionId, recoveryStrategy = "retry" ) ) } // Shutdown the actor system when done system.terminate() } ``` ## Conclusion ✅ Robust error handling is essential for production AI workflows. Kastrax provides a comprehensive suite of error handling capabilities that enable you to create resilient, fault-tolerant workflows that can recover from failures and continue operating even in challenging conditions. By combining step-level error handling, workflow-level recovery strategies, sophisticated retry mechanisms, and comprehensive monitoring, you can build AI agent workflows that gracefully handle errors and provide reliable service to your users. Remember that effective error handling is not just about preventing crashes—it's about designing systems that can adapt to failures, recover automatically when possible, and provide clear, actionable information when human intervention is required. ## Related - [Step Retries Reference](../../reference/workflows/step-retries.mdx) - [Watch Method Reference](../../reference/workflows/watch.mdx) - [Step Conditions](../../reference/workflows/step-condition.mdx) - [Control Flow](./control-flow.mdx) --- title: "Nested Workflows in Kastrax | Workflows | Kastrax Docs" description: "Learn how to create modular, reusable workflow components in Kastrax by nesting workflows within other workflows, enabling complex AI agent orchestration patterns." --- # Nested Workflows in Kastrax ✅ [EN] Source: https://kastrax.ai/en/docs/workflows/nested-workflows Kastrax provides powerful support for nested workflows, allowing you to use entire workflows as steps within other workflows. This feature enables modular design, promotes code reuse, and simplifies the implementation of complex AI agent orchestration patterns. Nested workflows are particularly valuable for: - **Breaking down complex processes** into manageable, reusable components - **Encapsulating domain-specific logic** in dedicated workflows - **Creating reusable workflow libraries** that can be shared across projects - **Improving readability and maintainability** of complex agent orchestration - **Enabling team collaboration** by allowing different teams to work on separate workflow components ## Nested Workflow Architecture ✅ In Kastrax, nested workflows follow a hierarchical structure: 1. **Parent Workflow**: The top-level workflow that contains one or more nested workflows as steps 2. **Nested Workflow**: A complete workflow that is used as a step within another workflow 3. **Sub-steps**: The individual steps within a nested workflow This architecture allows for multiple levels of nesting, enabling complex hierarchical workflow structures while maintaining clean separation of concerns. ## Basic Usage ✅ You can use a workflow as a step within another workflow using the `subWorkflowStep` function: ```kotlin filename="BasicNestedWorkflow.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a nested workflow val dataProcessingWorkflow = workflow { name = "data-processing-workflow" description = "Process and analyze data" // Define input parameters input { variable("data", Any::class, required = true) variable("processingLevel", String::class, defaultValue = "standard") } // Define workflow steps step(dataCleaningAgent) { id = "clean-data" name = "Clean Data" description = "Clean and normalize the input data" variables = mutableMapOf( "rawData" to variable("$.input.data"), "level" to variable("$.input.processingLevel") ) } step(dataAnalysisAgent) { id = "analyze-data" name = "Analyze Data" description = "Analyze the cleaned data" after("clean-data") variables = mutableMapOf( "cleanedData" to variable("$.steps.clean-data.output.data") ) } // Define workflow output output { "processedData" from "$.steps.clean-data.output.data" "analysis" from "$.steps.analyze-data.output.results" "metadata" from "$.steps.analyze-data.output.metadata" } } // Create a parent workflow that uses the nested workflow val parentWorkflow = workflow { name = "parent-workflow" description = "Parent workflow that uses a nested workflow" // Define input parameters input { variable("sourceData", Any::class, required = true) variable("analysisType", String::class, defaultValue = "comprehensive") } // First step to prepare data step(dataPreparationAgent) { id = "prepare-data" name = "Prepare Data" description = "Prepare data for processing" variables = mutableMapOf( "sourceData" to variable("$.input.sourceData") ) } // Use the nested workflow as a step subWorkflowStep { id = "process-data" name = "Process Data" description = "Process data using the data processing workflow" after("prepare-data") // Specify which workflow to use workflowId = dataProcessingWorkflow.id // Map parent workflow variables to nested workflow inputs input = mapOf( "data" to variable("$.steps.prepare-data.output.preparedData"), "processingLevel" to variable("$.input.analysisType") ) } // Final step that uses the nested workflow results step(reportGenerationAgent) { id = "generate-report" name = "Generate Report" description = "Generate a report based on processed data" after("process-data") variables = mutableMapOf( "processedData" to variable("$.steps.process-data.output.processedData"), "analysis" to variable("$.steps.process-data.output.analysis") ) } // Define workflow output output { "report" from "$.steps.generate-report.output.report" "analysisResults" from "$.steps.process-data.output.analysis" } } ``` When a workflow is used as a step: - It is referenced by its ID in the `workflowId` property - Input variables are mapped from the parent workflow to the nested workflow - The nested workflow's output is available in the parent workflow's context - The nested workflow's steps are executed in their defined order ## Accessing Results ✅ Results from a nested workflow are available in the parent workflow's context under the nested workflow's step ID. The results include all outputs defined in the nested workflow's output mapping: ```kotlin filename="AccessingNestedResults.kt" import ai.kastrax.core.KastraxSystem import ai.kastrax.core.workflow.WorkflowExecuteOptions import kotlinx.coroutines.runBlocking fun main() = runBlocking { val kastraxSystem = KastraxSystem() // Register workflows kastraxSystem.registerWorkflow(dataProcessingWorkflow) kastraxSystem.registerWorkflow(parentWorkflow) // Execute the parent workflow val result = kastraxSystem.workflowEngine.executeWorkflow( workflowId = parentWorkflow.id, input = mapOf( "sourceData" to mapOf("records" to listOf(1, 2, 3, 4, 5)), "analysisType" to "detailed" ) ) // Access nested workflow results if (result.success) { // Access the final workflow output (which may include nested workflow results) val report = result.output["report"] val analysisResults = result.output["analysisResults"] println("Report: $report") println("Analysis Results: $analysisResults") // Access specific nested workflow outputs directly val processedData = result.steps["process-data"]?.output?.get("processedData") val metadata = result.steps["process-data"]?.output?.get("metadata") println("Processed Data: $processedData") println("Metadata: $metadata") } else { println("Workflow execution failed: ${result.error}") } } ``` ## Advanced Nested Workflow Patterns ✅ Kastrax supports several advanced patterns for working with nested workflows: ### Parallel Execution of Nested Workflows Multiple nested workflows can be executed in parallel for improved performance: ```kotlin filename="ParallelNestedWorkflows.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with parallel nested workflows val parallelWorkflow = workflow { name = "parallel-workflow" description = "Execute multiple nested workflows in parallel" // Define input parameters input { variable("data", Any::class, required = true) } // Execute multiple nested workflows in parallel parallelSteps { // First nested workflow subWorkflowStep { id = "text-analysis" name = "Text Analysis" description = "Analyze textual content" workflowId = "text-analysis-workflow" input = mapOf( "text" to variable("$.input.data.text") ) } // Second nested workflow subWorkflowStep { id = "image-analysis" name = "Image Analysis" description = "Analyze image content" workflowId = "image-analysis-workflow" input = mapOf( "imageUrl" to variable("$.input.data.imageUrl") ) } // Third nested workflow subWorkflowStep { id = "metadata-analysis" name = "Metadata Analysis" description = "Analyze metadata" workflowId = "metadata-analysis-workflow" input = mapOf( "metadata" to variable("$.input.data.metadata") ) } } // Combine results from all parallel nested workflows step(resultCombinationAgent) { id = "combine-results" name = "Combine Results" description = "Combine results from all analysis workflows" after("text-analysis", "image-analysis", "metadata-analysis") variables = mutableMapOf( "textResults" to variable("$.steps.text-analysis.output.results"), "imageResults" to variable("$.steps.image-analysis.output.results"), "metadataResults" to variable("$.steps.metadata-analysis.output.results") ) } // Define workflow output output { "combinedResults" from "$.steps.combine-results.output.combinedResults" "textAnalysis" from "$.steps.text-analysis.output.results" "imageAnalysis" from "$.steps.image-analysis.output.results" "metadataAnalysis" from "$.steps.metadata-analysis.output.results" } } ``` ### Conditional Nested Workflows You can conditionally execute different nested workflows based on input data or previous step results: ```kotlin filename="ConditionalNestedWorkflows.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with conditional nested workflow execution val conditionalWorkflow = workflow { name = "conditional-workflow" description = "Conditionally execute different nested workflows" // Define input parameters input { variable("data", Any::class, required = true) variable("processingType", String::class, required = true) } // Initial data analysis step step(dataAnalysisAgent) { id = "analyze-data" name = "Analyze Data" description = "Analyze input data to determine processing path" variables = mutableMapOf( "data" to variable("$.input.data"), "processingType" to variable("$.input.processingType") ) } // Conditional step to choose the appropriate nested workflow conditionalStep { id = "processing-path" name = "Choose Processing Path" description = "Select the appropriate processing workflow" after("analyze-data") // Condition to determine which path to take condition { context -> val processingType = context.getVariable("$.input.processingType") as? String ?: "standard" processingType == "advanced" } // Advanced processing path onTrue { subWorkflowStep { id = "advanced-processing" name = "Advanced Processing" description = "Execute advanced processing workflow" workflowId = "advanced-processing-workflow" input = mapOf( "data" to variable("$.steps.analyze-data.output.preparedData"), "options" to variable("$.steps.analyze-data.output.advancedOptions") ) } } // Standard processing path onFalse { subWorkflowStep { id = "standard-processing" name = "Standard Processing" description = "Execute standard processing workflow" workflowId = "standard-processing-workflow" input = mapOf( "data" to variable("$.steps.analyze-data.output.preparedData") ) } } } // Final step to format results step(resultFormattingAgent) { id = "format-results" name = "Format Results" description = "Format the processing results" // This step will run after whichever processing path was taken execute { context -> // Get results from whichever processing path was taken val advancedResults = context.getVariable("$.steps.advanced-processing.output.results") val standardResults = context.getVariable("$.steps.standard-processing.output.results") // Use whichever results are available val results = advancedResults ?: standardResults // Format the results mapOf( "formattedResults" to formatResults(results), "processingType" to if (advancedResults != null) "advanced" else "standard" ) } } // Define workflow output output { "results" from "$.steps.format-results.output.formattedResults" "processingType" from "$.steps.format-results.output.processingType" } } // Helper function to format results fun formatResults(results: Any?): Map { // In a real implementation, this would format the results appropriately return mapOf("formatted" to (results ?: "No results")) } ``` ### Iterative Nested Workflows You can implement iterative processing using nested workflows in loops: ```kotlin filename="IterativeNestedWorkflows.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with iterative nested workflow execution val iterativeWorkflow = workflow { name = "iterative-workflow" description = "Iteratively execute a nested workflow until a condition is met" // Define input parameters input { variable("initialData", Any::class, required = true) variable("maxIterations", Int::class, defaultValue = 5) variable("qualityThreshold", Double::class, defaultValue = 0.9) } // Initialize iteration variables step(initializationAgent) { id = "initialize" name = "Initialize" description = "Set up initial values for iteration" execute { context -> mapOf( "currentData" to context.getVariable("$.input.initialData"), "iterationCount" to 0, "quality" to 0.0 ) } } // Iterative loop loopStep { id = "refinement-loop" name = "Refinement Loop" description = "Iteratively refine data until quality threshold is reached" after("initialize") // Continue looping while quality is below threshold and iterations are under limit condition { context -> val quality = context.getVariable("$.steps.evaluate-quality.output.quality") as? Double ?: 0.0 val iterationCount = context.getVariable("$.steps.refine-data.output.iterationCount") as? Int ?: 0 val maxIterations = context.getVariable("$.input.maxIterations") as? Int ?: 5 quality < context.getVariable("$.input.qualityThreshold") as Double && iterationCount < maxIterations } // Loop body body { // Execute nested workflow for data refinement subWorkflowStep { id = "refine-data" name = "Refine Data" description = "Refine data using the refinement workflow" workflowId = "data-refinement-workflow" input = mapOf( "data" to variable("$.steps.refine-data.output.currentData", defaultValue = variable("$.steps.initialize.output.currentData")), "iterationCount" to variable("$.steps.refine-data.output.iterationCount", defaultValue = variable("$.steps.initialize.output.iterationCount")) ) } // Evaluate the quality of refined data step(qualityEvaluationAgent) { id = "evaluate-quality" name = "Evaluate Quality" description = "Evaluate the quality of refined data" after("refine-data") variables = mutableMapOf( "refinedData" to variable("$.steps.refine-data.output.refinedData"), "iterationCount" to variable("$.steps.refine-data.output.iterationCount") ) } } } // Final step to process the refined data step(finalProcessingAgent) { id = "final-processing" name = "Final Processing" description = "Process the final refined data" after("refinement-loop") variables = mutableMapOf( "refinedData" to variable("$.steps.refine-data.output.refinedData"), "quality" to variable("$.steps.evaluate-quality.output.quality"), "iterationCount" to variable("$.steps.refine-data.output.iterationCount") ) } // Define workflow output output { "finalData" from "$.steps.final-processing.output.finalData" "quality" from "$.steps.evaluate-quality.output.quality" "iterations" from "$.steps.refine-data.output.iterationCount" } } ``` ## Monitoring Nested Workflows ✅ Kastrax provides comprehensive monitoring capabilities for nested workflows, allowing you to track their execution and handle events: ```kotlin filename="MonitoringNestedWorkflows.kt" import ai.kastrax.core.KastraxSystem import ai.kastrax.core.workflow.WorkflowExecuteOptions import ai.kastrax.core.workflow.monitoring.WorkflowMonitor import kotlinx.coroutines.runBlocking fun main() = runBlocking { val kastraxSystem = KastraxSystem() // Register workflows kastraxSystem.registerWorkflow(parentWorkflow) // Create a workflow monitor val workflowMonitor = WorkflowMonitor(kastraxSystem) // Execute the parent workflow with monitoring val result = kastraxSystem.workflowEngine.executeWorkflow( workflowId = parentWorkflow.id, input = mapOf("sourceData" to "test-data"), options = WorkflowExecuteOptions( // Monitor all steps, including nested workflow steps onStepStart = { stepId, input -> // Check if this is a nested workflow step if (stepId == "process-data") { println("Starting nested workflow execution with input: $input") } else { println("Starting step: $stepId with input: $input") } }, onStepFinish = { stepResult -> // Check if this is a nested workflow step if (stepResult.stepId == "process-data") { println("Nested workflow completed with status: ${stepResult.status}") // Access nested workflow outputs val nestedOutput = stepResult.output println("Nested workflow outputs: $nestedOutput") // Access nested workflow steps val nestedSteps = stepResult.nestedSteps println("Nested workflow steps: ${nestedSteps?.keys}") } else { val stepId = stepResult.stepId val status = stepResult.status println("Step $stepId completed with status: $status") } }, // Monitor nested workflow events onNestedWorkflowEvent = { workflowId, eventType, data -> println("Nested workflow $workflowId event: $eventType, data: $data") } ) ) // Get detailed execution history, including nested workflows val executionHistory = workflowMonitor.getExecutionHistory(result.executionId, includeNested = true) // Analyze execution metrics val metrics = workflowMonitor.getExecutionMetrics(result.executionId) println("Total execution time: ${metrics.totalExecutionTimeMs} ms") println("Nested workflow execution time: ${metrics.stepExecutionTimes.get("process-data")} ms") // Or alternatively: // val executionTime = metrics.stepExecutionTimes["process-data"] // println("Nested workflow execution time: $executionTime ms") } ``` ## Suspending and Resuming Nested Workflows ✅ Nested workflows fully support suspension and resumption, allowing you to create complex workflows with human-in-the-loop processes or asynchronous operations: ```kotlin filename="SuspendableNestedWorkflows.kt" import ai.kastrax.core.KastraxSystem import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import kotlinx.coroutines.runBlocking // Create a nested workflow with suspension points val reviewWorkflow = workflow { name = "content-review-workflow" description = "Review content with human approval" // Define input parameters input { variable("content", String::class, required = true) } // Initial content analysis step(contentAnalysisAgent) { id = "analyze-content" name = "Analyze Content" description = "Analyze content quality" variables = mutableMapOf( "content" to variable("$.input.content") ) } // Human review step (suspends the workflow) humanStep { id = "human-review" name = "Human Review" description = "Get human approval for content" after("analyze-content") prompt { context -> val content = context.getVariable("$.input.content") as? String ?: "" val analysis = context.getVariable("$.steps.analyze-content.output.analysis") as? Map<*, *> ?: emptyMap() // Format the analysis as a string first val analysisText = analysis.entries.joinToString("\n") { entry -> // Format each entry val key = entry.key val value = entry.value "$key: $value" } // Then use it in the template """Please review the following content: $content Analysis: $analysisText Approve or suggest changes.""".trimIndent() } } // Process review results step(reviewProcessingAgent) { id = "process-review" name = "Process Review" description = "Process human review results" after("human-review") variables = mutableMapOf( "content" to variable("$.input.content"), "approved" to variable("$.steps.human-review.output.approved"), "feedback" to variable("$.steps.human-review.output.feedback") ) } // Define workflow output output { "reviewedContent" from "$.steps.process-review.output.reviewedContent" "approved" from "$.steps.human-review.output.approved" "feedback" from "$.steps.human-review.output.feedback" } } // Create a parent workflow that uses the suspendable nested workflow val contentPublishingWorkflow = workflow { name = "content-publishing-workflow" description = "Create and publish content with review" // Define input parameters input { variable("topic", String::class, required = true) } // Generate content step(contentGenerationAgent) { id = "generate-content" name = "Generate Content" description = "Generate content based on topic" variables = mutableMapOf( "topic" to variable("$.input.topic") ) } // Use the review workflow as a nested workflow subWorkflowStep { id = "review-content" name = "Review Content" description = "Review content using the review workflow" after("generate-content") workflowId = reviewWorkflow.id input = mapOf( "content" to variable("$.steps.generate-content.output.content") ) } // Publish content if approved conditionalStep { id = "publishing-decision" name = "Publishing Decision" description = "Decide whether to publish content" after("review-content") condition { context -> context.getVariable("$.steps.review-content.output.approved") as? Boolean ?: false } onTrue { step(publishingAgent) { id = "publish-content" name = "Publish Content" description = "Publish the approved content" variables = mutableMapOf( "content" to variable("$.steps.review-content.output.reviewedContent") ) } } onFalse { step(revisionAgent) { id = "revise-content" name = "Revise Content" description = "Revise content based on feedback" variables = mutableMapOf( "content" to variable("$.steps.generate-content.output.content"), "feedback" to variable("$.steps.review-content.output.feedback") ) } } } } // Execute and resume the workflow fun main() = runBlocking { val kastraxSystem = KastraxSystem() // Register workflows kastraxSystem.registerWorkflow(reviewWorkflow) kastraxSystem.registerWorkflow(contentPublishingWorkflow) // Execute the parent workflow val result = kastraxSystem.workflowEngine.executeWorkflow( workflowId = contentPublishingWorkflow.id, input = mapOf("topic" to "Artificial Intelligence Ethics") ) // Check if the workflow is suspended if (result.status == "suspended") { println("Workflow suspended at: ${result.suspendedStepId}") // In a real application, this would be triggered by a human review // For this example, we'll simulate receiving human input val humanInput = mapOf( "approved" to true, "feedback" to "The content looks good!" ) // Resume the workflow with human input val resumeResult = kastraxSystem.workflowEngine.resumeWorkflow( suspensionId = result.suspensionId, resumeData = humanInput ) println("Workflow resumed and completed with status: ${resumeResult.status}") println("Final output: ${resumeResult.output}") } } ``` When working with suspended nested workflows: - The parent workflow is suspended when any step in a nested workflow suspends - The suspension ID uniquely identifies the suspended workflow, regardless of nesting level - When resuming, the nested workflow continues from the suspended step - After the nested workflow completes, the parent workflow continues execution ## Type-Safe Nested Workflows ✅ Kastrax provides strong type safety for nested workflows through explicit input and output definitions: ```kotlin filename="TypeSafeNestedWorkflows.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import kotlinx.serialization.Serializable // Define data classes for type-safe input/output @Serializable data class AnalysisInput( val text: String, val options: Map = emptyMap() ) @Serializable data class AnalysisResult( val sentiment: Double, val topics: List, val summary: String, val metadata: Map ) // Create a type-safe nested workflow val analysisWorkflow = workflow { name = "text-analysis-workflow" description = "Analyze text with type safety" // Define typed input input { variable("analysisInput", AnalysisInput::class, required = true) } // Workflow steps step(textAnalysisAgent) { id = "analyze-text" name = "Analyze Text" description = "Perform text analysis" variables = mutableMapOf( "text" to variable("$.input.analysisInput.text"), "options" to variable("$.input.analysisInput.options") ) } // Define typed output output { "result" from { context -> // Create a strongly-typed result object val sentiment = context.getVariable("$.steps.analyze-text.output.sentiment") as? Double ?: 0.0 val topics = context.getVariable("$.steps.analyze-text.output.topics") as? List ?: emptyList() val summary = context.getVariable("$.steps.analyze-text.output.summary") as? String ?: "" val metadata = context.getVariable("$.steps.analyze-text.output.metadata") as? Map ?: emptyMap() AnalysisResult( sentiment = sentiment, topics = topics, summary = summary, metadata = metadata ) } } } // Use the type-safe nested workflow in a parent workflow val documentProcessingWorkflow = workflow { name = "document-processing-workflow" description = "Process documents with type-safe nested workflows" // Define input parameters input { variable("document", String::class, required = true) } // Extract text from document step(textExtractionAgent) { id = "extract-text" name = "Extract Text" description = "Extract text from document" variables = mutableMapOf( "document" to variable("$.input.document") ) } // Use the analysis workflow with type-safe input subWorkflowStep { id = "analyze-document" name = "Analyze Document" description = "Analyze document text" after("extract-text") workflowId = analysisWorkflow.id // Create a type-safe input object input = { context -> val text = context.getVariable("$.steps.extract-text.output.text") as? String ?: "" val options = mapOf("language" to "en", "detailed" to true) mapOf("analysisInput" to AnalysisInput(text = text, options = options)) } } // Process the type-safe result step(reportGenerationAgent) { id = "generate-report" name = "Generate Report" description = "Generate report based on analysis" after("analyze-document") // Access the strongly-typed result execute { context -> // Get the typed result from the nested workflow val analysisResult = context.getVariable("$.steps.analyze-document.output.result") as AnalysisResult // Use the strongly-typed properties val sentiment = analysisResult.sentiment val topics = analysisResult.topics val summary = analysisResult.summary // Generate report using typed data mapOf( "report" to "Document Analysis Report:\n" + "Sentiment: $sentiment\n" + // Join topics with comma separator "Topics: " + topics.joinToString(", ") + "\n" + "Summary: $summary", "sentimentScore" to sentiment, "topTopics" to topics.take(3) ) } } } ``` ## Best Practices for Nested Workflows ✅ ### 1. Design for Modularity and Reusability Create nested workflows that encapsulate specific functionality and can be reused across multiple parent workflows: ```kotlin // Good: Modular workflow that handles a specific task val emailProcessingWorkflow = workflow { name = "email-processing-workflow" description = "Process emails with classification and response generation" // Clear input/output definitions input { variable("emailContent", String::class, required = true) variable("sender", String::class, required = true) } // Focused steps for this specific task step(emailClassificationAgent) { /* ... */ } step(responseGenerationAgent) { /* ... */ } // Well-defined outputs output { "classification" from "$.steps.email-classification.output.category" "response" from "$.steps.response-generation.output.text" } } // This workflow can now be used in multiple parent workflows ``` ### 2. Use Clear Input/Output Contracts Define explicit input and output contracts for nested workflows to ensure they can be used correctly: ```kotlin // Define clear input requirements input { variable("data", DataType::class, required = true, description = "The data to process") variable("options", OptionsType::class, defaultValue = DefaultOptions) } // Define comprehensive output mapping output { "primaryResult" from "$.steps.main-processing.output.result" "metadata" from "$.steps.analysis.output.metadata" "status" from { context -> if (context.getVariable("$.steps.validation.output.valid") as? Boolean == true) { "success" } else { "failed_validation" } } } ``` ### 3. Implement Proper Error Handling Ensure nested workflows handle errors appropriately and propagate meaningful error information to parent workflows: ```kotlin // In nested workflow step(processingAgent) { id = "process-data" // Handle errors within the nested workflow onError { error -> mapOf( "error" to error.message, "errorType" to error.javaClass.simpleName, "recoverable" to (error !is CriticalException) ) } } // In parent workflow subWorkflowStep { id = "nested-processing" workflowId = "data-processing-workflow" // Handle errors from the nested workflow onError { error -> println("Nested workflow failed: ${error.message}") // Take appropriate action based on the error step(errorHandlingAgent) { id = "handle-nested-error" variables = mutableMapOf( "error" to error.message, "workflowId" to "data-processing-workflow" ) } } } ``` ### 4. Monitor Performance and Resource Usage Track the performance of nested workflows to identify bottlenecks and optimize resource usage: ```kotlin // Configure monitoring for nested workflows val options = WorkflowExecuteOptions( // Track step execution times onStepStart = { stepId, _ -> stepStartTimes[stepId] = System.currentTimeMillis() }, onStepFinish = { stepResult -> val startTime = stepStartTimes[stepResult.stepId] ?: 0 val duration = System.currentTimeMillis() - startTime // Log execution time for nested workflows if (stepResult.nestedSteps != null) { val stepId = stepResult.stepId val stepsSize = stepResult.nestedSteps?.size ?: 0 println("Nested workflow $stepId completed in $duration ms") println("Nested steps: $stepsSize") } }, // Set resource limits for nested workflows nestedWorkflowOptions = NestedWorkflowOptions( maxDepth = 3, // Limit nesting depth timeoutMs = 30000 // Timeout for nested workflows ) ) ``` ### 5. Version Nested Workflows Carefully Manage versioning of nested workflows to ensure compatibility with parent workflows: ```kotlin // Version your workflows explicitly val analysisWorkflowV2 = workflow { name = "text-analysis-workflow" version = "2.0.0" description = "Improved text analysis workflow (v2)" // Maintain backward compatibility in input/output input { variable("text", String::class, required = true) // Original input variable("options", Map::class, defaultValue = emptyMap()) // New in v2 } // Steps... // Ensure output remains compatible output { "result" from "$.steps.analyze.output.result" // Original output "enhancedResult" from "$.steps.enhanced-analysis.output.result" // New in v2 } } // Reference specific versions in parent workflows subWorkflowStep { id = "analyze-text" workflowId = "text-analysis-workflow" workflowVersion = "2.0.0" // Explicitly request v2 } ``` ## Complete Example ✅ Here's a comprehensive example demonstrating a multi-level nested workflow system for content creation and publishing: ```kotlin filename="CompleteNestedWorkflowExample.kt" import ai.kastrax.core.KastraxSystem import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable // Data classes for type safety @Serializable data class ResearchResult( val sources: List, val keyPoints: List, val statistics: Map ) @Serializable data class ContentOutline( val title: String, val sections: List, val targetWordCount: Int ) @Serializable data class DraftContent( val title: String, val content: String, val wordCount: Int, val metadata: Map ) @Serializable data class EditedContent( val title: String, val content: String, val wordCount: Int, val readabilityScore: Double, val metadata: Map ) // 1. Research workflow (nested level 2) val researchWorkflow = workflow { name = "research-workflow" description = "Research a topic thoroughly" input { variable("topic", String::class, required = true) variable("depth", String::class, defaultValue = "standard") } // Research steps step(academicResearchAgent) { id = "academic-research" name = "Academic Research" description = "Research from academic sources" variables = mutableMapOf( "topic" to variable("$.input.topic"), "depth" to variable("$.input.depth") ) } step(newsResearchAgent) { id = "news-research" name = "News Research" description = "Research from news sources" variables = mutableMapOf( "topic" to variable("$.input.topic") ) } step(synthesisAgent) { id = "synthesize-research" name = "Synthesize Research" description = "Combine research from all sources" after("academic-research", "news-research") variables = mutableMapOf( "academicResearch" to variable("$.steps.academic-research.output.findings"), "newsResearch" to variable("$.steps.news-research.output.findings") ) } // Define typed output output { "result" from { context -> val sources = context.getVariable("$.steps.synthesize-research.output.sources") as? List ?: emptyList() val keyPoints = context.getVariable("$.steps.synthesize-research.output.keyPoints") as? List ?: emptyList() val statistics = context.getVariable("$.steps.synthesize-research.output.statistics") as? Map ?: emptyMap() ResearchResult(sources = sources, keyPoints = keyPoints, statistics = statistics) } } } // 2. Content creation workflow (nested level 1) val contentCreationWorkflow = workflow { name = "content-creation-workflow" description = "Create content based on research" input { variable("topic", String::class, required = true) variable("contentType", String::class, required = true) variable("targetWordCount", Int::class, defaultValue = 1000) } // Use research workflow as a nested workflow subWorkflowStep { id = "research" name = "Research Topic" description = "Research the topic thoroughly" workflowId = researchWorkflow.id input = mapOf( "topic" to variable("$.input.topic"), "depth" to if (variable("$.input.contentType") == "academic") "deep" else "standard" ) } // Create outline based on research step(outlineAgent) { id = "create-outline" name = "Create Outline" description = "Create a content outline based on research" after("research") variables = mutableMapOf( "research" to variable("$.steps.research.output.result"), "contentType" to variable("$.input.contentType"), "targetWordCount" to variable("$.input.targetWordCount") ) } // Write draft based on outline step(writingAgent) { id = "write-draft" name = "Write Draft" description = "Write a draft based on the outline" after("create-outline") variables = mutableMapOf( "outline" to variable("$.steps.create-outline.output.outline"), "research" to variable("$.steps.research.output.result"), "contentType" to variable("$.input.contentType"), "targetWordCount" to variable("$.input.targetWordCount") ) } // Edit the draft step(editingAgent) { id = "edit-draft" name = "Edit Draft" description = "Edit and improve the draft" after("write-draft") variables = mutableMapOf( "draft" to variable("$.steps.write-draft.output.draft"), "contentType" to variable("$.input.contentType") ) } // Define typed output output { "outline" from { context -> val title = context.getVariable("$.steps.create-outline.output.title") as? String ?: "" val sections = context.getVariable("$.steps.create-outline.output.sections") as? List ?: emptyList() val targetWordCount = context.getVariable("$.input.targetWordCount") as? Int ?: 1000 ContentOutline(title = title, sections = sections, targetWordCount = targetWordCount) } "draft" from { context -> val title = context.getVariable("$.steps.write-draft.output.title") as? String ?: "" val content = context.getVariable("$.steps.write-draft.output.content") as? String ?: "" val wordCount = context.getVariable("$.steps.write-draft.output.wordCount") as? Int ?: 0 val metadata = context.getVariable("$.steps.write-draft.output.metadata") as? Map ?: emptyMap() DraftContent(title = title, content = content, wordCount = wordCount, metadata = metadata) } "editedContent" from { context -> val title = context.getVariable("$.steps.edit-draft.output.title") as? String ?: "" val content = context.getVariable("$.steps.edit-draft.output.content") as? String ?: "" val wordCount = context.getVariable("$.steps.edit-draft.output.wordCount") as? Int ?: 0 val readabilityScore = context.getVariable("$.steps.edit-draft.output.readabilityScore") as? Double ?: 0.0 val metadata = context.getVariable("$.steps.edit-draft.output.metadata") as? Map ?: emptyMap() EditedContent( title = title, content = content, wordCount = wordCount, readabilityScore = readabilityScore, metadata = metadata ) } } } ``` ## Main Publishing Workflow ✅ Here's the top-level publishing workflow that uses the content creation workflow: ```kotlin filename="PublishingWorkflow.kt" val publishingWorkflow = workflow { name = "publishing-workflow" description = "Create and publish content" input { variable("topic", String::class, required = true) variable("contentType", String::class, required = true) variable("targetWordCount", Int::class, defaultValue = 1000) variable("publishPlatform", String::class, defaultValue = "blog") } // Use content creation workflow as a nested workflow subWorkflowStep { id = "create-content" name = "Create Content" description = "Create content using the content creation workflow" workflowId = contentCreationWorkflow.id input = mapOf( "topic" to variable("$.input.topic"), "contentType" to variable("$.input.contentType"), "targetWordCount" to variable("$.input.targetWordCount") ) } // Human review step (suspends the workflow) humanStep { id = "human-review" name = "Human Review" description = "Get human approval for content" after("create-content") prompt { context -> val content = context.getVariable("$.steps.create-content.output.editedContent.content") as? String ?: "" val title = context.getVariable("$.steps.create-content.output.editedContent.title") as? String ?: "" // Using triple quotes for multiline string """Please review the following content: Title: $title $content Approve or suggest changes.""".trimIndent() } } // Conditional publishing based on review conditionalStep { id = "publishing-decision" name = "Publishing Decision" description = "Decide whether to publish content" after("human-review") condition { context -> context.getVariable("$.steps.human-review.output.approved") as? Boolean ?: false } onTrue { step(publishingAgent) { id = "publish-content" name = "Publish Content" description = "Publish the approved content" variables = mutableMapOf( "content" to variable("$.steps.create-content.output.editedContent"), "platform" to variable("$.input.publishPlatform") ) } } onFalse { step(revisionAgent) { id = "revise-content" name = "Revise Content" description = "Revise content based on feedback" variables = mutableMapOf( "content" to variable("$.steps.create-content.output.editedContent"), "feedback" to variable("$.steps.human-review.output.feedback") ) } } } // Define workflow output output { "title" from "$.steps.create-content.output.editedContent.title" "content" from "$.steps.create-content.output.editedContent.content" "published" from { context -> context.getStepStatus("publish-content") == "success" } "publishedUrl" from "$.steps.publish-content.output.url" "feedback" from "$.steps.human-review.output.feedback" } } // Execute the workflow fun main() = runBlocking { val kastraxSystem = KastraxSystem() // Register all workflows kastraxSystem.registerWorkflow(researchWorkflow) kastraxSystem.registerWorkflow(contentCreationWorkflow) kastraxSystem.registerWorkflow(publishingWorkflow) // Execute the top-level workflow val result = kastraxSystem.workflowEngine.executeWorkflow( workflowId = publishingWorkflow.id, input = mapOf( "topic" to "Artificial Intelligence Ethics", "contentType" to "blog", "targetWordCount" to 1500, "publishPlatform" to "medium" ) ) // Check if the workflow is suspended for human review if (result.status == "suspended") { println("Workflow suspended for human review. Suspension ID: ${result.suspensionId}") // In a real application, this would be triggered by a human review // For this example, we'll simulate receiving human input val humanInput = mapOf( "approved" to true, "feedback" to "The content looks great! Approved for publishing." ) // Resume the workflow with human input val resumeResult = kastraxSystem.workflowEngine.resumeWorkflow( suspensionId = result.suspensionId, resumeData = humanInput ) // Check the final result if (resumeResult.success) { println("Content published successfully!") println("Title: ${resumeResult.output.get("title")}") println("Published URL: ${resumeResult.output.get("publishedUrl")}") // Or alternatively: // val title = resumeResult.output["title"] // val url = resumeResult.output["publishedUrl"] // println("Title: $title") // println("Published URL: $url") } else { println("Workflow failed: ${resumeResult.error}") } } } ``` ## Conclusion ✅ Nested workflows are a powerful feature in Kastrax that enable modular, reusable, and maintainable AI agent orchestration. By breaking down complex processes into manageable components, you can create sophisticated multi-agent systems that are easier to develop, test, and maintain. Key benefits of nested workflows include: - **Modularity**: Encapsulate related functionality in dedicated workflows - **Reusability**: Create workflow libraries that can be shared across projects - **Maintainability**: Simplify complex processes by breaking them into smaller components - **Type Safety**: Ensure data consistency with strongly-typed inputs and outputs - **Testability**: Test individual workflow components in isolation By following the best practices outlined in this guide and leveraging Kastrax's comprehensive nested workflow capabilities, you can build sophisticated AI agent systems that are both powerful and maintainable. --- title: Workflow System Overview | Kastrax Docs description: Overview of the workflow system in Kastrax, detailing how to create, configure, and execute workflows. --- # Workflow System Overview ✅ [EN] Source: https://kastrax.ai/en/docs/workflows/overview-kotlin The Kastrax workflow system allows you to define, execute, and monitor complex sequences of operations involving agents and other components. This guide explains the workflow system architecture and how to use it effectively. ## What Are Workflows? ✅ Workflows in Kastrax are structured sequences of steps that can be executed in a specific order, with data flowing between steps. They enable you to: - Orchestrate multiple agents to collaborate on complex tasks - Define conditional execution paths based on intermediate results - Process and transform data between steps - Handle errors and retries - Monitor and track execution progress - Create reusable patterns for common agent interactions ## Workflow System Architecture ✅ The Kastrax workflow system consists of several key components: 1. **Workflow**: The top-level container that defines a sequence of steps 2. **WorkflowStep**: Individual units of work within a workflow 3. **WorkflowContext**: Shared state and data passed between steps 4. **WorkflowEngine**: The runtime that executes workflows 5. **WorkflowBuilder**: DSL for creating workflows 6. **WorkflowStateStorage**: Storage for workflow execution state ## Creating Basic Workflows ✅ Kastrax provides a DSL for creating workflows: ```kotlin import ai.kastrax.core.workflow.builder.workflow import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create agents for the workflow val researchAgent = agent { name("ResearchAgent") description("An agent that researches topics") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } val writingAgent = agent { name("WritingAgent") description("An agent that writes content based on research") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // Create a workflow val researchWorkflow = workflow("research-workflow", "Research and write an article") { // Research step step(researchAgent) { id = "research" name = "Research" description = "Research the topic" variables = mutableMapOf( "topic" to variable("$.input.topic") ) } // Writing step step(writingAgent) { id = "writing" name = "Writing" description = "Write an article based on research" after("research") variables = mutableMapOf( "research" to variable("$.steps.research.output.text") ) } // Define output mapping output { "researchResults" from "$.steps.research.output.text" "article" from "$.steps.writing.output.text" "wordCount" from { "$.steps.writing.output.text" to { text -> (text as? String)?.split(" ")?.size ?: 0 } } } } // Execute the workflow val workflowEngine = WorkflowEngine() workflowEngine.registerWorkflow("research-workflow", researchWorkflow) val result = workflowEngine.executeWorkflow( workflowId = "research-workflow", input = mapOf("topic" to "Artificial Intelligence") ) println("Workflow result: ${result.output}") } ``` ## Workflow Components ✅ ### Workflow ✅ The `Workflow` interface defines the core functionality of a workflow: ```kotlin interface Workflow { suspend fun execute( input: Map, options: WorkflowExecuteOptions = WorkflowExecuteOptions() ): WorkflowResult suspend fun streamExecute( input: Map, options: WorkflowExecuteOptions = WorkflowExecuteOptions() ): Flow } ``` ### WorkflowStep ✅ The `WorkflowStep` interface defines individual steps within a workflow: ```kotlin interface WorkflowStep { val id: String val name: String val description: String val after: List val variables: Map val config: StepConfig? get() = null val condition: (WorkflowContext) -> Boolean get() = { true } suspend fun execute(context: WorkflowContext): WorkflowStepResult } ``` ### WorkflowContext ✅ The `WorkflowContext` class holds the state and data during workflow execution: ```kotlin data class WorkflowContext( val input: Map, val steps: MutableMap = mutableMapOf(), val variables: MutableMap = mutableMapOf(), val runId: String? = null ) { fun resolveReference(reference: VariableReference): Any? { // Implementation... } fun resolveVariables(variables: Map): Map { // Implementation... } } ``` ### WorkflowEngine ✅ The `WorkflowEngine` class is responsible for executing workflows: ```kotlin class WorkflowEngine( private val stateStorage: WorkflowStateStorage = InMemoryWorkflowStateStorage() ) { private val workflows = mutableMapOf() fun registerWorkflow(workflowId: String, workflow: Workflow) { workflows[workflowId] = workflow } suspend fun executeWorkflow( workflowId: String, input: Map, options: WorkflowExecuteOptions = WorkflowExecuteOptions() ): WorkflowResult { // Implementation... } suspend fun streamExecuteWorkflow( workflowId: String, input: Map, options: WorkflowExecuteOptions = WorkflowExecuteOptions() ): Flow { // Implementation... } } ``` ## Step Types ✅ Kastrax provides several built-in step types: ### Agent Step ✅ The most common step type, which executes an agent with specific inputs: ```kotlin step(agent) { id = "generate-content" name = "Generate Content" description = "Generate content based on a topic" variables = mutableMapOf( "topic" to variable("$.input.topic"), "style" to variable("$.input.style") ) } ``` ### Function Step ✅ Executes a custom function: ```kotlin functionStep { id = "process-data" name = "Process Data" description = "Process data from previous steps" after("collect-data") function { context -> val data = context.resolveReference(variable("$.steps.collect-data.output.data")) // Process the data mapOf("processedData" to processData(data)) } } ``` ### Conditional Step ✅ Executes based on a condition: ```kotlin step(agent) { id = "optional-step" name = "Optional Step" description = "This step only executes under certain conditions" after("previous-step") condition { context -> val quality = context.resolveReference(variable("$.steps.previous-step.output.quality")) (quality as? Int) ?: 0 > 7 } variables = mutableMapOf( "input" to variable("$.steps.previous-step.output.text") ) } ``` ### Subworkflow Step ✅ Executes another workflow as a step: ```kotlin subworkflowStep { id = "nested-workflow" name = "Nested Workflow" description = "Execute a nested workflow" workflowId = "another-workflow" inputMapping { context -> mapOf( "data" to context.resolveReference(variable("$.steps.previous-step.output.data")) ) } } ``` ## Data Flow ✅ Data flows through a workflow via variable references: ### Variable References ✅ Variable references use a JSON path-like syntax to access data: ```kotlin // Reference workflow input val topicRef = variable("$.input.topic") // Reference output from a previous step val researchRef = variable("$.steps.research.output.text") // Reference a global variable val configRef = variable("$.variables.config") ``` ### Output Mapping ✅ Output mapping defines how to extract and transform the final workflow output: ```kotlin output { // Direct mapping "researchResults" from "$.steps.research.output.text" // Mapping with transformation "wordCount" from { "$.steps.writing.output.text" to { text -> (text as? String)?.split(" ")?.size ?: 0 } } // Combining multiple sources "summary" from { "$.steps.research.output.text" to { research -> "$.steps.writing.output.text" to { article -> "Research: $research\n\nArticle: $article" } } } } ``` ## Error Handling ✅ Workflows can handle errors in several ways: ### Retry Configuration ✅ ```kotlin step(agent) { id = "unreliable-step" name = "Unreliable Step" description = "A step that might fail" // Configure retries config = StepConfig( retryConfig = RetryConfig( maxRetries = 3, retryDelay = 1000, // 1 second backoffMultiplier = 2.0 // Exponential backoff ) ) } ``` ### Error Handlers ✅ ```kotlin // Create an error handling workflow engine val workflowEngine = ErrorHandlingWorkflowEngine( errorHandler = object : ErrorHandler { override suspend fun handleStepError( workflowId: String, runId: String, stepId: String, error: Throwable ): ErrorHandlingAction { return when { error is TimeoutException -> ErrorHandlingAction.Retry(maxRetries = 3) stepId == "optional-step" -> ErrorHandlingAction.Skip else -> ErrorHandlingAction.Fail } } } ) ``` ## Workflow Execution ✅ Workflows can be executed in different ways: ### Synchronous Execution ✅ ```kotlin val result = workflowEngine.executeWorkflow( workflowId = "my-workflow", input = mapOf("key" to "value") ) println("Workflow completed with output: ${result.output}") ``` ### Streaming Execution ✅ ```kotlin workflowEngine.streamExecuteWorkflow( workflowId = "my-workflow", input = mapOf("key" to "value") ).collect { update -> when (update) { is WorkflowStatusUpdate.Started -> println("Workflow started") is WorkflowStatusUpdate.StepStarted -> println("Step started: ${update.stepId}") is WorkflowStatusUpdate.StepCompleted -> println("Step completed: ${update.stepId}") is WorkflowStatusUpdate.StepFailed -> println("Step failed: ${update.stepId}, error: ${update.error}") is WorkflowStatusUpdate.Completed -> println("Workflow completed") is WorkflowStatusUpdate.Failed -> println("Workflow failed: ${update.error}") } } ``` ### Execution Options ✅ ```kotlin val options = WorkflowExecuteOptions( maxSteps = 20, // Maximum number of steps to execute timeout = 120000, // 2 minutes timeout onStepFinish = { stepResult -> println("Step ${stepResult.stepId} finished with output: ${stepResult.output}") }, onStepError = { stepId, error -> println("Step $stepId failed with error: ${error.message}") }, threadId = "conversation-123" // Optional thread ID for memory integration ) val result = workflowEngine.executeWorkflow( workflowId = "my-workflow", input = mapOf("key" to "value"), options = options ) ``` ## Workflow State Management ✅ Workflows maintain state during and after execution: ### State Storage ✅ ```kotlin // In-memory state storage (default) val inMemoryStorage = InMemoryWorkflowStateStorage() // File-based state storage val fileStorage = FileWorkflowStateStorage("workflows/state") // Database state storage val dbStorage = DatabaseWorkflowStateStorage(dataSource) // Create a workflow engine with custom state storage val workflowEngine = WorkflowEngine(stateStorage = fileStorage) ``` ### State Queries ✅ ```kotlin // Get workflow state val state = workflowEngine.getWorkflowState("my-workflow", "run-123") // Get all runs for a workflow val runs = workflowEngine.getWorkflowRuns("my-workflow") // Get active runs val activeRuns = workflowEngine.getActiveWorkflowRuns() ``` ## Advanced Workflow Features ✅ ### Parallel Execution ✅ ```kotlin val parallelWorkflow = workflow("parallel-workflow", "Execute steps in parallel") { // These steps have no dependencies, so they can run in parallel step(agent1) { id = "step1" name = "Step 1" description = "First parallel step" } step(agent2) { id = "step2" name = "Step 2" description = "Second parallel step" } // This step depends on both parallel steps step(agent3) { id = "step3" name = "Step 3" description = "Final step after parallel execution" after("step1", "step2") variables = mutableMapOf( "result1" to variable("$.steps.step1.output.result"), "result2" to variable("$.steps.step2.output.result") ) } } ``` ### Workflow Composition ✅ ```kotlin // Create a workflow composer val composer = WorkflowComposer("my-composer", workflowEngine) // Compose workflows sequentially val sequentialWorkflow = composer.composeSequential( workflowName = "sequential-workflow", description = "Execute workflows in sequence", workflows = listOf( "workflow1" to { input -> input }, // Input mapping for workflow1 "workflow2" to { input, prevOutput -> prevOutput } // Use previous output as input ) ) // Compose workflows in parallel val parallelWorkflow = composer.composeParallel( workflowName = "parallel-workflow", description = "Execute workflows in parallel", workflows = mapOf( "branch1" to "workflow1", "branch2" to "workflow2" ), inputMapping = { branch, input -> // Map input for each branch when (branch) { "branch1" -> mapOf("key1" to input["value"]) "branch2" -> mapOf("key2" to input["value"]) else -> emptyMap() } }, mergeStep = MergeResultsStep( id = "merge", name = "Merge Results", description = "Merge results from parallel workflows" ) ) ``` ### State Machines ✅ For complex workflows with multiple possible paths, you can use state machines: ```kotlin // Define states enum class OrderState { NEW, PROCESSING, SHIPPED, DELIVERED, CANCELED } // Define events sealed class OrderEvent { data class PlaceOrder(val items: List) : OrderEvent() data class ProcessOrder(val paymentId: String) : OrderEvent() data class ShipOrder(val trackingNumber: String) : OrderEvent() data class DeliverOrder(val deliveryDate: String) : OrderEvent() object CancelOrder : OrderEvent() } // Create a state machine val orderStateMachine = BasicStateMachine>( initialState = OrderState.NEW, initialContext = emptyMap(), transitioner = object : StateTransitioner> { override suspend fun transition( currentState: OrderState, event: OrderEvent, context: Map ): TransitionResult> { // Define state transitions val (nextState, updatedContext) = when (currentState to event) { OrderState.NEW to is OrderEvent.PlaceOrder -> { OrderState.PROCESSING to context + ("items" to event.items) } OrderState.PROCESSING to is OrderEvent.ProcessOrder -> { OrderState.SHIPPED to context + ("paymentId" to event.paymentId) } OrderState.SHIPPED to is OrderEvent.ShipOrder -> { OrderState.DELIVERED to context + ("trackingNumber" to event.trackingNumber) } OrderState.SHIPPED to is OrderEvent.DeliverOrder -> { OrderState.DELIVERED to context + ("deliveryDate" to event.deliveryDate) } else to is OrderEvent.CancelOrder -> { OrderState.CANCELED to context } else -> currentState to context } return TransitionResult( nextState = nextState, updatedContext = updatedContext, sideEffects = emptyList() ) } } ) // Use the state machine val nextState = orderStateMachine.sendEvent(OrderEvent.PlaceOrder(listOf("item1", "item2"))) println("Next state: $nextState") ``` ## Complete Example ✅ Here's a complete example of a sophisticated workflow: ```kotlin import ai.kastrax.core.workflow.builder.workflow import ai.kastrax.core.workflow.engine.WorkflowEngine import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create agents val researchAgent = agent { name("ResearchAgent") description("An agent that researches topics") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } val writingAgent = agent { name("WritingAgent") description("An agent that writes content based on research") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } val editingAgent = agent { name("EditingAgent") description("An agent that edits and improves content") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } val factCheckingAgent = agent { name("FactCheckingAgent") description("An agent that verifies facts in content") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // Create a content creation workflow val contentWorkflow = workflow("content-creation", "Create researched and edited content") { // Research step step(researchAgent) { id = "research" name = "Research" description = "Research the topic thoroughly" variables = mutableMapOf( "topic" to variable("$.input.topic"), "depth" to variable("$.input.researchDepth") ) } // Writing step step(writingAgent) { id = "writing" name = "Writing" description = "Write content based on research" after("research") variables = mutableMapOf( "research" to variable("$.steps.research.output.text"), "style" to variable("$.input.contentStyle"), "length" to variable("$.input.contentLength") ) } // Parallel fact checking and editing step(factCheckingAgent) { id = "fact-checking" name = "Fact Checking" description = "Verify facts in the content" after("writing") variables = mutableMapOf( "content" to variable("$.steps.writing.output.text"), "research" to variable("$.steps.research.output.text") ) } step(editingAgent) { id = "editing" name = "Editing" description = "Edit and improve the content" after("writing") variables = mutableMapOf( "content" to variable("$.steps.writing.output.text"), "style" to variable("$.input.contentStyle") ) } // Final revision step step(editingAgent) { id = "final-revision" name = "Final Revision" description = "Create the final version incorporating edits and fact checks" after("fact-checking", "editing") variables = mutableMapOf( "original" to variable("$.steps.writing.output.text"), "edits" to variable("$.steps.editing.output.text"), "factChecks" to variable("$.steps.fact-checking.output.text") ) } // Conditional quality check step step(editingAgent) { id = "quality-check" name = "Quality Check" description = "Perform a quality check if requested" after("final-revision") condition { context -> val performQualityCheck = context.resolveReference(variable("$.input.performQualityCheck")) (performQualityCheck as? Boolean) ?: false } variables = mutableMapOf( "content" to variable("$.steps.final-revision.output.text") ) } // Define output mapping output { "research" from "$.steps.research.output.text" "draft" from "$.steps.writing.output.text" "factChecks" from "$.steps.fact-checking.output.text" "edits" from "$.steps.editing.output.text" "finalContent" from "$.steps.final-revision.output.text" "qualityScore" from { "$.steps.quality-check.output.score" to { score -> score ?: "No quality check performed" } } "metadata" from { "$.steps.writing.output.wordCount" to { wordCount -> "$.steps.final-revision.output.wordCount" to { finalWordCount -> mapOf( "initialWordCount" to wordCount, "finalWordCount" to finalWordCount, "changePercentage" to calculatePercentage(wordCount, finalWordCount) ) } } } } } // Create workflow engine val workflowEngine = WorkflowEngine() workflowEngine.registerWorkflow("content-creation", contentWorkflow) // Execute the workflow val result = workflowEngine.executeWorkflow( workflowId = "content-creation", input = mapOf( "topic" to "Artificial Intelligence Ethics", "researchDepth" to "comprehensive", "contentStyle" to "academic", "contentLength" to "2000 words", "performQualityCheck" to true ) ) // Print the results println("Workflow completed with status: ${if (result.success) "SUCCESS" else "FAILURE"}") println("Final content: ${result.output["finalContent"]}") println("Quality score: ${result.output["qualityScore"]}") println("Metadata: ${result.output["metadata"]}") } // Helper function to calculate percentage change fun calculatePercentage(initial: Any?, final: Any?): Double { val initialValue = (initial as? Number)?.toDouble() ?: return 0.0 val finalValue = (final as? Number)?.toDouble() ?: return 0.0 if (initialValue == 0.0) return 0.0 return ((finalValue - initialValue) / initialValue) * 100.0 } ``` ## Best Practices ✅ 1. **Step Granularity**: Design steps with appropriate granularity - not too small to create overhead, not too large to lose flexibility 2. **Error Handling**: Implement robust error handling with retries for unreliable operations 3. **Data Flow**: Be explicit about data flow between steps using variable references 4. **Conditional Logic**: Use conditions to create dynamic workflows that adapt to intermediate results 5. **Monitoring**: Set up proper monitoring and logging for workflow execution 6. **State Management**: Choose appropriate state storage based on your reliability requirements 7. **Testing**: Test workflows with different inputs and edge cases 8. **Composition**: Compose complex workflows from simpler, reusable workflows ## Next Steps ✅ Now that you understand the workflow system, you can: 1. Learn about [workflow definition](./workflow-definition.mdx) 2. Explore [workflow execution](./workflow-execution.mdx) 3. Implement [agent integration with workflows](./agent-integration.mdx) 4. Set up [workflow state management](./state-management.mdx) --- title: "Kastrax AI Workflow System | Workflows | Kastrax" description: "The Kastrax Workflow System provides a powerful framework for orchestrating complex AI agent operations with features like branching, parallel execution, resource suspension, and more." --- # Kastrax AI Workflow System [EN] Source: https://kastrax.ai/en/docs/workflows/overview The Kastrax Workflow System provides a powerful framework for orchestrating complex AI agent operations. Built on Kotlin's concurrency model, it enables you to define sophisticated workflows with features like branching, parallel execution, resource suspension, and more. This system is designed to handle the complex, multi-step processes that are common in AI applications. ## When to Use Workflows Workflows are essential when your AI application requires orchestrating multiple operations in a structured manner. Consider using workflows when: - **Complex Multi-step Processes**: Your application needs to execute a sequence of AI operations with dependencies between steps - **Conditional Logic**: You need to implement branching paths based on intermediate results - **Parallel Processing**: Multiple operations need to be executed concurrently - **User Interaction**: Workflows need to pause and wait for user input before continuing - **Error Handling**: You need sophisticated error recovery mechanisms for AI operations - **Observability**: You want detailed tracking and monitoring of each step in your AI process Kastrax's workflow system provides: - A type-safe, Kotlin-based DSL for defining workflows and their steps - Support for both simple (linear) and advanced (branching, parallel) execution paths - Variable passing between steps with powerful reference resolution - Suspension and resumption capabilities for long-running or user-interactive workflows - Comprehensive error handling and recovery mechanisms - Detailed observability and debugging features to track each workflow run ## Workflow Architecture The Kastrax Workflow System is built on a modular architecture with several key components: 1. **Workflow Interface**: Defines the core operations for workflow execution 2. **Workflow Steps**: Individual units of work that can be combined into a workflow 3. **Workflow Context**: Maintains state and data during workflow execution 4. **Variable References**: Mechanism for passing data between steps 5. **Workflow Engine**: Orchestrates the execution of steps according to defined dependencies This architecture enables flexible, maintainable, and observable workflow definitions. ## Example Workflow Let's examine a simple workflow that processes content creation with research, writing, and editing steps: ### Creating a Basic Workflow Here's how to define a workflow using Kastrax's Kotlin DSL: ```kotlin filename="ContentCreationWorkflow.kt" import ai.kastrax.core.agent.Agent import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Assume we have these agents defined elsewhere val researchAgent: Agent = /* ... */ val writingAgent: Agent = /* ... */ val editingAgent: Agent = /* ... */ // Create the workflow val contentWorkflow = workflow { name = "content-creation" description = "Create content about a topic" // Research step step(researchAgent) { id = "research" name = "Research" description = "Research the topic" variables = mutableMapOf( "topic" to variable("$.input.topic") ) } // Writing step (depends on research) step(writingAgent) { id = "writing" name = "Writing" description = "Write an article based on research" after("research") // This step runs after the research step variables = mutableMapOf( "research" to variable("$.steps.research.output.text") ) } // Editing step (depends on writing) step(editingAgent) { id = "editing" name = "Editing" description = "Edit the article" after("writing") // This step runs after the writing step variables = mutableMapOf( "draft" to variable("$.steps.writing.output.text") ) } // Define output mapping output { "researchSummary" from "$.steps.research.output.text" "finalArticle" from "$.steps.editing.output.text" "wordCount" from { "$.steps.editing.output.text" to { text -> (text as? String)?.split(" ")?.size ?: 0 } } } } ``` This workflow defines three steps (research, writing, and editing) with dependencies between them, and maps the final outputs. ### Registering the Workflow Register your workflow with the Kastrax system to enable execution, logging, and telemetry: ```kotlin filename="KastraxSetup.kt" import ai.kastrax.core.KastraxSystem // Create a Kastrax system instance val kastraxSystem = KastraxSystem() // Register the workflow kastraxSystem.registerWorkflow(contentWorkflow) // You can also register multiple workflows kastraxSystem.registerWorkflows( contentWorkflow, otherWorkflow, anotherWorkflow ) ``` You can also create dynamic workflows at runtime using the workflow engine: ```kotlin filename="DynamicWorkflowCreation.kt" import ai.kastrax.core.workflow.dynamic.DynamicWorkflowGenerator // Create a dynamic workflow generator val workflowGenerator = DynamicWorkflowGenerator(kastraxSystem) // Create a workflow dynamically val dynamicWorkflow = workflowGenerator.createWorkflow( workflowName = "dynamic-workflow", description = "Dynamically created workflow" ) { // Define steps and flow here step(someAgent) { id = "dynamic-step" // ... step configuration } // ... more steps and configuration } // Register the dynamic workflow kastraxSystem.registerWorkflow(dynamicWorkflow) ``` ### Executing the Workflow Execute your workflow programmatically: ```kotlin filename="ExecuteWorkflow.kt" import ai.kastrax.core.workflow.WorkflowExecuteOptions import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Get the workflow engine val workflowEngine = kastraxSystem.workflowEngine // Execute the workflow val result = workflowEngine.executeWorkflow( workflowId = "content-creation", input = mapOf("topic" to "Artificial Intelligence in Healthcare"), options = WorkflowExecuteOptions( maxSteps = 10, // Maximum number of steps to execute timeout = 60000, // Timeout in milliseconds onStepFinish = { stepResult -> println("Step ${stepResult.stepId} completed") }, onStepError = { stepId, error -> println("Error in step $stepId: ${error.message}") } ) ) // Process the result if (result.success) { println("Workflow completed successfully") println("Final article: ${result.output["finalArticle"]}") println("Word count: ${result.output["wordCount"]}") } else { println("Workflow failed: ${result.error}") } } ``` You can also execute workflows via the Kastrax HTTP API (when running a Kastrax server): ```bash curl --location 'http://localhost:8080/api/workflows/content-creation/execute' \ --header 'Content-Type: application/json' \ --data '{ "topic": "Artificial Intelligence in Healthcare" }' ``` This example demonstrates the essentials: define your workflow with steps, register it with the Kastrax system, then execute it with input data. ## Workflow Step Types Kastrax supports various types of workflow steps to handle different scenarios: ### Agent Steps Agent steps use AI agents to perform tasks: ```kotlin step(agent) { id = "agent-step" name = "Agent Processing" description = "Use an agent to process data" variables = mutableMapOf( "input" to variable("$.input.data") ) } ``` ### Tool Steps Tool steps execute specific tools or functions: ```kotlin toolStep { id = "data-processing" name = "Process Data" description = "Process data using a specific tool" tool = DataProcessingTool() variables = mutableMapOf( "data" to variable("$.input.rawData") ) } ``` ### Conditional Steps Conditional steps execute based on conditions: ```kotlin conditionalStep { id = "quality-check" name = "Quality Check" description = "Check if the content meets quality standards" condition { context -> val wordCount = context.getVariable("$.steps.writing.output.wordCount") as? Int ?: 0 wordCount > 500 } onTrue { step(qualityAgent) { id = "quality-review" // ... configuration } } onFalse { step(revisionAgent) { id = "content-revision" // ... configuration } } } ``` ### Human-in-the-Loop Steps Steps that require human input or approval: ```kotlin humanStep { id = "human-review" name = "Human Review" description = "Get human approval for the content" prompt { context -> val content = context.getVariable("$.steps.editing.output.text") as? String ?: "" "Please review the following content:\n\n$content\n\nApprove or suggest changes." } timeoutMs = 86400000 // 24 hours in milliseconds onTimeout { // Handle timeout case step(notificationAgent) { id = "timeout-notification" // ... configuration } } } ``` ## Advanced Workflow Features ### Parallel Execution Execute multiple steps concurrently: ```kotlin parallelSteps { step(researchAgent1) { id = "research-source1" // ... configuration } step(researchAgent2) { id = "research-source2" // ... configuration } step(researchAgent3) { id = "research-source3" // ... configuration } } // Continue with a step that uses results from all parallel steps step(synthesisAgent) { id = "research-synthesis" after("research-source1", "research-source2", "research-source3") variables = mutableMapOf( "source1" to variable("$.steps.research-source1.output.text"), "source2" to variable("$.steps.research-source2.output.text"), "source3" to variable("$.steps.research-source3.output.text") ) } ``` ### Error Handling Implement sophisticated error handling: ```kotlin step(riskAgent) { id = "risk-analysis" // ... configuration onError { error -> // Log the error println("Error in risk analysis: ${error.message}") // Execute fallback step step(fallbackAgent) { id = "risk-analysis-fallback" variables = mutableMapOf( "error" to variable(error.message) ) } } } ``` ### Workflow Variables Workflow variables provide a powerful mechanism for passing data between steps: ```kotlin // Define a workflow with variables val workflowWithVariables = workflow { name = "data-processing-workflow" // Define input variables input { variable("dataSource", String::class) variable("processingLevel", Int::class, defaultValue = 1) } // Use variables in steps step(dataAgent) { id = "data-extraction" variables = mutableMapOf( "source" to variable("$.input.dataSource"), "level" to variable("$.input.processingLevel") ) } // Use JSONPath expressions to access nested properties step(analysisAgent) { id = "data-analysis" after("data-extraction") variables = mutableMapOf( "rawData" to variable("$.steps.data-extraction.output.data"), "metadata" to variable("$.steps.data-extraction.output.metadata.timestamp") ) } } ``` ### Observability and Monitoring Kastrax workflows provide comprehensive observability features: ```kotlin // Configure workflow monitoring val monitoredWorkflow = workflow { name = "monitored-workflow" // ... workflow configuration monitoring { onStart { workflowId, input -> println("Workflow $workflowId started with input: $input") } onStepStart { workflowId, stepId -> println("Step $stepId in workflow $workflowId started") } onStepComplete { workflowId, stepId, output -> println("Step $stepId in workflow $workflowId completed with output: $output") } onComplete { workflowId, output -> println("Workflow $workflowId completed with output: $output") } onError { workflowId, error -> println("Workflow $workflowId failed with error: ${error.message}") } } } ``` ## Integration with Actor Model One of Kastrax's unique features is the integration of the workflow system with the actor model, enabling distributed workflow execution: ```kotlin import ai.kastrax.actor.ActorSystem import ai.kastrax.actor.Props import ai.kastrax.workflow.WorkflowActor import ai.kastrax.workflow.messages.* import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Create an actor system val system = ActorSystem("workflow-system") // Create a workflow actor val workflowActor = system.actorOf( Props.create(WorkflowActor::class.java, contentWorkflow), "workflow-actor" ) // Send a workflow execution message val result = system.ask( workflowActor, ExecuteWorkflowMessage(input = mapOf("topic" to "AI in Healthcare")) ) // Process the result when (result) { is WorkflowResult.Success -> { println("Workflow executed successfully") println("Final article: ${result.output["finalArticle"]}") } is WorkflowResult.Error -> { println("Workflow execution failed: ${result.error}") } } // Shutdown the actor system when done system.terminate() } ``` This integration enables building sophisticated multi-agent systems where workflows can be distributed across different nodes and executed concurrently. ## Real-World Use Cases Kastrax workflows are ideal for a variety of AI agent applications: ### Content Creation Pipeline Orchestrate a complete content creation process: - Research step gathers information from multiple sources - Planning step creates an outline based on research - Writing step generates the initial content - Editing step refines and improves the content - Review step (human-in-the-loop) gets final approval ### Customer Support Automation Handle complex customer inquiries: - Classification step determines the type of inquiry - Information retrieval step gathers relevant knowledge base articles - Response generation step creates a personalized response - Escalation step (conditional) routes complex issues to human agents ### Data Analysis Workflow Process and analyze large datasets: - Data collection step gathers information from multiple sources - Preprocessing step cleans and normalizes the data - Analysis step extracts insights from the data - Visualization step creates charts and graphs - Reporting step generates a comprehensive report ## More Resources - The [Workflow Guide](../guides/ai-recruiter.mdx) in the Guides section is a tutorial that covers the main concepts - [Sequential Steps workflow example](../../examples/workflows/sequential-steps.mdx) - [Parallel Steps workflow example](../../examples/workflows/parallel-steps.mdx) - [Branching Paths workflow example](../../examples/workflows/branching-paths.mdx) - [Workflow Variables example](../../examples/workflows/workflow-variables.mdx) - [Cyclical Dependencies workflow example](../../examples/workflows/cyclical-dependencies.mdx) - [Suspend and Resume workflow example](../../examples/workflows/suspend-and-resume.mdx) --- title: "Runtime Variables in Kastrax AI Workflows | Kastrax Docs" description: "Learn how to use Kastrax's powerful runtime variable system to dynamically configure AI workflows and agents, enabling flexible and adaptable behavior at execution time." --- # Runtime Variables in Kastrax AI Workflows ✅ [EN] Source: https://kastrax.ai/en/docs/workflows/runtime-variables Kastrax provides a sophisticated runtime variable system that enables you to dynamically configure workflows and agents at execution time. This powerful feature allows you to create flexible, reusable AI workflows that can adapt their behavior based on runtime conditions without modifying the underlying workflow definition. ## Runtime Variable Architecture ✅ Kastrax's runtime variable system is built on a robust architecture that includes: 1. **Runtime Context**: A type-safe container for variables that can be accessed throughout the workflow 2. **Variable Scoping**: Control over which variables are accessible to which components 3. **Type Safety**: Strong typing for runtime variables to prevent errors 4. **Dependency Injection**: Automatic provision of runtime variables to steps and agents 5. **Validation**: Runtime checking of variable presence and correctness This architecture enables several key capabilities: - **Dynamic Configuration**: Adapt workflow behavior based on execution-time parameters - **Environment-Specific Settings**: Configure workflows differently across environments - **External Integration**: Pass variables from external systems into workflows - **Agent Customization**: Dynamically configure AI agents with different parameters - **Cross-Step Communication**: Share runtime state between different workflow components ## Basic Usage ✅ Kastrax makes it easy to use runtime variables in your workflows: ```kotlin filename="BasicRuntimeVariables.kt" import ai.kastrax.core.KastraxSystem import ai.kastrax.core.workflow.WorkflowExecuteOptions import ai.kastrax.core.workflow.runtime.RuntimeContext import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Initialize the Kastrax system val kastraxSystem = KastraxSystem() // Get a reference to the workflow engine val workflowEngine = kastraxSystem.workflowEngine // Create a runtime context with variables val runtimeContext = RuntimeContext().apply { // Add various types of runtime variables set("multiplier", 5) set("apiKey", "sk-your-api-key-here") set("maxTokens", 1000) set("temperature", 0.7) set("enableLogging", true) } // Execute the workflow with runtime variables val result = workflowEngine.executeWorkflow( workflowId = "data-processing-workflow", input = mapOf("inputValue" to 45), options = WorkflowExecuteOptions( runtimeContext = runtimeContext ) ) // Access the workflow result // Print the result val output = result.output println("Workflow execution result: $output") } ``` In this example, we: 1. Create a `RuntimeContext` to hold our runtime variables 2. Add various variables with different types (numbers, strings, booleans) 3. Pass the runtime context to the workflow when executing it 4. The workflow and its steps can then access these variables during execution ## Integration with Web Services ✅ Kastrax makes it easy to integrate runtime variables with web services, allowing you to dynamically configure workflows based on HTTP requests: ```kotlin filename="WebServiceIntegration.kt" import ai.kastrax.core.KastraxSystem import ai.kastrax.core.workflow.runtime.RuntimeContext import io.ktor.server.application.* import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.routing.* import io.ktor.server.request.* import io.ktor.server.response.* fun main() { // Initialize the Kastrax system val kastraxSystem = KastraxSystem() // Start a web server embeddedServer(Netty, port = 8080) { // Configure routing routing { // Endpoint to execute workflows with runtime variables post("/workflows/{workflowId}/execute") { // Get the workflow ID from the path val workflowId = call.parameters["workflowId"]!! // Get request parameters val requestBody = call.receive>() val input = requestBody["input"] as? Map ?: emptyMap() // Extract configuration from headers val apiKey = call.request.header("X-API-Key") ?: "" val modelName = call.request.header("X-Model-Name") ?: "default-model" val temperature = call.request.header("X-Temperature")?.toDoubleOrNull() ?: 0.7 val maxTokens = call.request.header("X-Max-Tokens")?.toIntOrNull() ?: 1000 // Create a runtime context with variables from headers val runtimeContext = RuntimeContext().apply { // Set variables from headers set("apiKey", apiKey) set("modelName", modelName) set("temperature", temperature) set("maxTokens", maxTokens) // Add additional configuration from request body (requestBody["config"] as? Map)?.forEach { (key, value) -> set(key, value) } } // Execute the workflow with runtime variables val result = kastraxSystem.workflowEngine.executeWorkflow( workflowId = workflowId, input = input, options = WorkflowExecuteOptions( runtimeContext = runtimeContext ) ) // Return the result call.respond(result.output) } } }.start(wait = true) } ``` This example demonstrates: 1. Creating a web server that exposes an endpoint for executing workflows 2. Extracting configuration from HTTP headers and request body 3. Creating a runtime context with these variables 4. Executing the workflow with the runtime context 5. Returning the workflow result to the client ## Accessing Runtime Variables in Steps ✅ Kastrax makes it easy to access runtime variables within workflow steps: ```kotlin filename="StepRuntimeVariables.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with steps that use runtime variables val calculationWorkflow = workflow { name = "calculation-workflow" description = "Perform calculations using runtime variables" // Define input parameters input { variable("inputValue", Int::class, required = true) } // Step that uses runtime variables step(calculationAgent) { id = "calculate" name = "Calculate Result" description = "Perform calculation using runtime variables" variables = mutableMapOf( "inputValue" to variable("$.input.inputValue") ) // Custom execution with runtime variable access execute { context, runtimeContext -> try { // Get the input value val inputValue = context.getVariable("inputValue") as? Int ?: throw IllegalArgumentException("Input value not provided") // Get runtime variables with type checking val multiplier = runtimeContext.get("multiplier") as? Int ?: throw IllegalArgumentException("Multiplier not configured in runtime context") val useAdvancedFormula = runtimeContext.get("useAdvancedFormula") as? Boolean ?: false // Perform calculation based on runtime configuration val result = if (useAdvancedFormula) { // Get additional parameters from runtime context val offset = runtimeContext.get("offset") as? Int ?: 0 val factor = runtimeContext.get("factor") as? Double ?: 1.0 // Apply advanced formula ((inputValue * multiplier) + offset) * factor } else { // Apply simple formula inputValue * multiplier } // Return the result mapOf( "result" to result, "formula" to if (useAdvancedFormula) "advanced" else "simple", "multiplier" to multiplier ) } catch (e: Exception) { // Log the error // Log the error message val errorMessage = e.message println("Error in calculate step: $errorMessage") // Return error information mapOf( "error" to e.message, "errorType" to e.javaClass.simpleName ) } } } // Output mapping output { "calculationResult" from "$.steps.calculate.output.result" "formula" from "$.steps.calculate.output.formula" "multiplier" from "$.steps.calculate.output.multiplier" } } ``` In this example: 1. The step's `execute` function receives both the step context and the runtime context 2. Runtime variables are accessed using the `runtimeContext.get()` method 3. Type checking and validation are performed on the runtime variables 4. The step's behavior changes based on the runtime configuration 5. Error handling is implemented to handle missing or invalid runtime variables ## Type-Safe Runtime Variables ✅ Kastrax provides strong type safety for runtime variables through typed contexts and validation: ```kotlin filename="TypeSafeRuntimeVariables.kt" import ai.kastrax.core.workflow.runtime.TypedRuntimeContext import ai.kastrax.core.workflow.runtime.RuntimeVariable import kotlinx.serialization.Serializable // Define a data class for type-safe runtime configuration @Serializable data class LLMConfiguration( val modelName: String, val temperature: Double, val maxTokens: Int, val topP: Double = 1.0, val presencePenalty: Double = 0.0, val frequencyPenalty: Double = 0.0 ) // Define a typed runtime context interface interface AgentRuntimeContext { val llmConfig: LLMConfiguration val apiKey: String val enableLogging: Boolean val retryCount: Int } // Create a workflow with type-safe runtime variables val typeSafeWorkflow = workflow { name = "type-safe-workflow" description = "Workflow with type-safe runtime variables" // Define runtime context type runtimeContextType() // Step that uses typed runtime context step(llmAgent) { id = "generate-content" name = "Generate Content" description = "Generate content using LLM with typed configuration" variables = mutableMapOf( "prompt" to variable("$.input.prompt") ) // Access typed runtime context execute { context, runtimeContext -> // Get the typed runtime context val typedContext = runtimeContext as TypedRuntimeContext // Access strongly-typed configuration val llmConfig = typedContext.get { llmConfig } val apiKey = typedContext.get { apiKey } val enableLogging = typedContext.get { enableLogging } // Use the configuration if (enableLogging) { // Log the configuration val model = llmConfig.modelName val temp = llmConfig.temperature val tokens = llmConfig.maxTokens println("Generating content with model: $model") println("Temperature: $temp") println("Max tokens: $tokens") } // Generate content using the configuration val content = generateContent( prompt = context.getVariable("prompt") as String, modelName = llmConfig.modelName, temperature = llmConfig.temperature, maxTokens = llmConfig.maxTokens, apiKey = apiKey ) // Return the result mapOf("content" to content) } } } ``` ## Executing with Typed Runtime Context ✅ Here's how to execute a workflow with a typed runtime context: ```kotlin filename="ExecuteWithTypedContext.kt" import ai.kastrax.core.KastraxSystem import ai.kastrax.core.workflow.WorkflowExecuteOptions import ai.kastrax.core.workflow.runtime.TypedRuntimeContext import kotlinx.coroutines.runBlocking // Execute the workflow with typed runtime context fun executeWithTypedContext() = runBlocking { val kastraxSystem = KastraxSystem() // Create a typed runtime context val runtimeContext = TypedRuntimeContext.create { // Set the LLM configuration set({ llmConfig }, LLMConfiguration( modelName = "gpt-4", temperature = 0.7, maxTokens = 1000, topP = 0.95 )) // Set other variables set({ apiKey }, "sk-your-api-key-here") set({ enableLogging }, true) set({ retryCount }, 3) } // Execute the workflow with the typed runtime context val result = kastraxSystem.workflowEngine.executeWorkflow( workflowId = typeSafeWorkflow.id, input = mapOf("prompt" to "Write a story about AI"), options = WorkflowExecuteOptions( runtimeContext = runtimeContext ) ) // Print the generated content val generatedContent = result.output.get("content") println("Generated content: $generatedContent") } // Helper function to simulate content generation fun generateContent(prompt: String, modelName: String, temperature: Double, maxTokens: Int, apiKey: String): String { // In a real implementation, this would call an LLM API return "Generated content based on prompt: $prompt using model $modelName" } ``` This example demonstrates: 1. Defining a strongly-typed runtime context interface 2. Creating data classes for complex configuration objects 3. Using the typed context in workflow steps 4. Accessing typed variables with compile-time safety 5. Creating and populating a typed runtime context for workflow execution ## Best Practices for Runtime Variables ✅ ### 1. Use Type-Safe Contexts Define interfaces for your runtime contexts to ensure type safety: ```kotlin // Define a typed context interface interface AgentRuntimeContext { val apiKey: String val modelName: String val temperature: Double val maxTokens: Int } // Use the typed context in your workflow runtimeContextType() ``` ### 2. Validate Variables Always validate runtime variables before using them: ```kotlin // Validate runtime variables val temperature = runtimeContext.get("temperature") as? Double ?: throw IllegalArgumentException("Temperature not configured") if (temperature < 0.0 || temperature > 1.0) { throw IllegalArgumentException("Temperature must be between 0.0 and 1.0") } ``` ### 3. Provide Default Values Use default values for optional runtime variables: ```kotlin // Get runtime variable with default value val maxTokens = runtimeContext.get("maxTokens") as? Int ?: 1000 val temperature = runtimeContext.get("temperature") as? Double ?: 0.7 val enableLogging = runtimeContext.get("enableLogging") as? Boolean ?: false ``` ### 4. Document Runtime Variables Clearly document the runtime variables your workflow expects: ```kotlin /** * Content Generation Workflow * * Runtime Variables: * - apiKey (String): The API key for the LLM service * - modelName (String): The name of the model to use * - temperature (Double): The temperature parameter (0.0-1.0) * - maxTokens (Int): The maximum number of tokens to generate * - enableLogging (Boolean): Whether to enable detailed logging */ val contentGenerationWorkflow = workflow { // Workflow definition... } ``` ### 5. Use Environment-Specific Contexts Create different runtime contexts for different environments: ```kotlin // Development context val devContext = RuntimeContext().apply { set("apiKey", "sk-dev-key") set("environment", "development") set("enableLogging", true) set("debugMode", true) } // Production context val prodContext = RuntimeContext().apply { set("apiKey", System.getenv("PROD_API_KEY")) set("environment", "production") set("enableLogging", false) set("debugMode", false) } ``` ## Advanced Usage ✅ ### Dynamic Agent Configuration Runtime variables are particularly useful for dynamically configuring AI agents based on the execution context: ```kotlin filename="DynamicAgentConfiguration.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.AgentConfiguration // Create a workflow with dynamically configured agents val dynamicAgentWorkflow = workflow { name = "dynamic-agent-workflow" description = "Workflow with dynamically configured agents" // Define input parameters input { variable("prompt", String::class, required = true) } // Step with a dynamically configured agent step { id = "generate-content" name = "Generate Content" description = "Generate content with a dynamically configured agent" // Dynamic agent creation based on runtime variables agent { runtimeContext -> // Get agent configuration from runtime context val modelName = runtimeContext.get("modelName") as? String ?: "gpt-3.5-turbo" val temperature = runtimeContext.get("temperature") as? Double ?: 0.7 val apiKey = runtimeContext.get("apiKey") as? String ?: throw IllegalArgumentException("API key not provided in runtime context") // Create agent with dynamic configuration Agent.create( type = "llm", configuration = AgentConfiguration( provider = "openai", modelName = modelName, apiKey = apiKey, parameters = mapOf( "temperature" to temperature, "max_tokens" to runtimeContext.get("maxTokens") as? Int ?: 1000, "top_p" to runtimeContext.get("topP") as? Double ?: 1.0 ) ) ) } // Variables for the step variables = mutableMapOf( "prompt" to variable("$.input.prompt") ) } // Output mapping output { "content" from "$.steps.generate-content.output.content" "model" from "$.steps.generate-content.output.model" } } ``` ### Runtime Variable Providers Kastrax supports runtime variable providers that can dynamically load variables from various sources: ```kotlin filename="RuntimeVariableProviders.kt" import ai.kastrax.core.workflow.runtime.RuntimeVariableProvider import ai.kastrax.core.workflow.runtime.RuntimeContext // Define a custom runtime variable provider class EnvironmentVariableProvider : RuntimeVariableProvider { override fun provideVariables(context: RuntimeContext) { // Load variables from environment System.getenv().forEach { (key, value) -> if (key.startsWith("KASTRAX_")) { val variableName = key.removePrefix("KASTRAX_").lowercase() context.set(variableName, value) } } } } // Define a database configuration provider class DatabaseConfigProvider(private val dataSource: DataSource) : RuntimeVariableProvider { override fun provideVariables(context: RuntimeContext) { // Load configuration from database dataSource.connection.use { connection -> val statement = connection.prepareStatement("SELECT key, value FROM workflow_config") val resultSet = statement.executeQuery() while (resultSet.next()) { val key = resultSet.getString("key") val value = resultSet.getString("value") context.set(key, value) } } } } // Register providers with the Kastrax system fun configureRuntimeProviders(kastraxSystem: KastraxSystem) { kastraxSystem.registerRuntimeVariableProvider(EnvironmentVariableProvider()) kastraxSystem.registerRuntimeVariableProvider(DatabaseConfigProvider(dataSource)) } ``` ## Conclusion ✅ Runtime variables provide a powerful mechanism for dynamically configuring Kastrax AI workflows and agents. By leveraging runtime variables, you can create flexible, adaptable workflows that can be configured differently based on the execution context, environment, or external parameters. Key benefits of runtime variables include: - **Flexibility**: Configure workflows differently without changing their definition - **Reusability**: Create generic workflows that can be specialized at runtime - **Integration**: Connect workflows with external systems and configuration sources - **Type Safety**: Ensure configuration correctness with Kotlin's type system - **Separation of Concerns**: Keep workflow logic separate from configuration By following the best practices outlined in this guide, you can create robust, configurable AI workflows that adapt to different environments and use cases. --- title: "Creating Steps in Kastrax AI Workflows | Kastrax Docs" description: "Learn how to define and configure workflow steps in Kastrax, including agent steps, tool steps, conditional steps, and human-in-the-loop steps." --- # Defining Steps in Kastrax AI Workflows ✅ [EN] Source: https://kastrax.ai/en/docs/workflows/steps Steps are the fundamental building blocks of Kastrax workflows. They represent discrete units of work that can be orchestrated, linked, and reused. Kastrax provides a type-safe, flexible API for defining various types of steps, from simple agent interactions to complex conditional logic and human-in-the-loop processes. This guide explains how to create and configure different types of steps in your Kastrax workflows. ## Step Types in Kastrax ✅ Kastrax supports several types of steps to handle different scenarios in AI workflows: ### Agent Steps ✅ Agent steps use AI agents to perform tasks. They're the most common type of step in Kastrax workflows and allow you to leverage the full capabilities of your AI agents. ```kotlin filename="AgentStepExample.kt" import ai.kastrax.core.agent.Agent import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Assume we have these agents defined elsewhere val researchAgent: Agent = /* ... */ val writingAgent: Agent = /* ... */ // Create a workflow with agent steps val contentWorkflow = workflow { name = "content-creation" description = "Create content about a topic" // Research step using an agent step(researchAgent) { id = "research" name = "Research" description = "Research the topic thoroughly" variables = mutableMapOf( "topic" to variable("$.input.topic"), "depth" to variable("$.input.researchDepth", defaultValue = "medium") ) } // Writing step using another agent step(writingAgent) { id = "writing" name = "Content Writing" description = "Write content based on research" after("research") // This step runs after the research step variables = mutableMapOf( "research" to variable("$.steps.research.output.text"), "style" to variable("$.input.writingStyle", defaultValue = "informative") ) } } ``` ### Tool Steps ✅ Tool steps execute specific functions or tools without requiring a full agent. They're useful for data processing, API calls, or other operations that don't need the full reasoning capabilities of an agent. ```kotlin filename="ToolStepExample.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import ai.kastrax.tools.DataProcessingTool import ai.kastrax.tools.ApiTool // Create a workflow with tool steps val dataWorkflow = workflow { name = "data-processing" description = "Process and analyze data" // Data processing tool step toolStep { id = "data-processing" name = "Process Data" description = "Clean and normalize the data" tool = DataProcessingTool() variables = mutableMapOf( "rawData" to variable("$.input.data"), "options" to variable("$.input.processingOptions") ) } // API call tool step toolStep { id = "api-call" name = "External API Call" description = "Fetch additional data from external API" tool = ApiTool() after("data-processing") // This step runs after data processing variables = mutableMapOf( "endpoint" to variable("$.input.apiEndpoint"), "parameters" to variable("$.steps.data-processing.output.apiParameters") ) } } ``` ### Conditional Steps ✅ Conditional steps allow you to implement branching logic in your workflows. They evaluate a condition and execute different steps based on the result. ```kotlin filename="ConditionalStepExample.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with conditional steps val reviewWorkflow = workflow { name = "content-review" description = "Review and process content based on quality" // Initial content analysis step step(analysisAgent) { id = "content-analysis" name = "Content Analysis" description = "Analyze content quality" variables = mutableMapOf( "content" to variable("$.input.content") ) } // Conditional step based on content quality conditionalStep { id = "quality-check" name = "Quality Check" description = "Check if the content meets quality standards" after("content-analysis") // Define the condition to evaluate condition { context -> val score = context.getVariable("$.steps.content-analysis.output.qualityScore") as? Double ?: 0.0 score >= 8.0 // Content is high quality if score is 8.0 or higher } // Steps to execute if condition is true (high quality) onTrue { step(publishAgent) { id = "publish" name = "Publish Content" description = "Publish the high-quality content" variables = mutableMapOf( "content" to variable("$.input.content"), "platform" to variable("$.input.publishPlatform") ) } } // Steps to execute if condition is false (low quality) onFalse { step(revisionAgent) { id = "revision" name = "Content Revision" description = "Revise the low-quality content" variables = mutableMapOf( "content" to variable("$.input.content"), "analysis" to variable("$.steps.content-analysis.output.feedback") ) } } } } ``` ### Human-in-the-Loop Steps ✅ Human-in-the-loop steps allow you to incorporate human feedback or approval into your workflows. These steps pause the workflow execution until human input is received. ```kotlin filename="HumanStepExample.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with human-in-the-loop steps val approvalWorkflow = workflow { name = "content-approval" description = "Create content with human approval" // Generate content step step(contentAgent) { id = "content-generation" name = "Content Generation" description = "Generate initial content" variables = mutableMapOf( "topic" to variable("$.input.topic"), "style" to variable("$.input.style") ) } // Human review step humanStep { id = "human-review" name = "Human Review" description = "Get human approval for the content" after("content-generation") // Define the prompt to show to the human reviewer prompt { context -> val content = context.getVariable("$.steps.content-generation.output.text") as? String ?: "" """ Please review the following content: $content Approve or suggest changes. """.trimIndent() } // Set timeout for human response (24 hours) timeoutMs = 86400000 // Define what happens if the timeout is reached onTimeout { step(notificationAgent) { id = "timeout-notification" name = "Timeout Notification" description = "Notify about review timeout" variables = mutableMapOf( "content" to variable("$.steps.content-generation.output.text"), "recipient" to variable("$.input.notificationEmail") ) } } } // Final processing based on human input step(finalizationAgent) { id = "finalization" name = "Content Finalization" description = "Finalize content based on human feedback" after("human-review") variables = mutableMapOf( "originalContent" to variable("$.steps.content-generation.output.text"), "humanFeedback" to variable("$.steps.human-review.output.feedback"), "approved" to variable("$.steps.human-review.output.approved") ) } } ``` ## Creating Reusable Step Templates ✅ For complex workflows, you can create reusable step templates that can be added to multiple workflows: ```kotlin filename="ReusableStepTemplates.kt" import ai.kastrax.core.agent.Agent import ai.kastrax.core.workflow.StepTemplate import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create reusable step templates class ContentGenerationStep(private val contentAgent: Agent) : StepTemplate { override fun configure() = stepConfig { id = "content-generation" name = "Content Generation" description = "Generate content based on topic" agent = contentAgent variables = mutableMapOf( "topic" to variable("$.input.topic"), "style" to variable("$.input.style", defaultValue = "informative") ) } } class ContentReviewStep(private val reviewAgent: Agent) : StepTemplate { override fun configure() = stepConfig { id = "content-review" name = "Content Review" description = "Review and improve content" agent = reviewAgent variables = mutableMapOf( "content" to variable("$.steps.content-generation.output.text"), "criteria" to variable("$.input.reviewCriteria") ) } } // Use the templates in a workflow val templateWorkflow = workflow { name = "template-workflow" description = "Workflow using reusable step templates" // Add steps from templates addStep(ContentGenerationStep(contentAgent)) addStep(ContentReviewStep(reviewAgent)) { after("content-generation") // Configure dependencies } // Add additional steps step(publishAgent) { id = "publish" name = "Publish Content" description = "Publish the reviewed content" after("content-review") variables = mutableMapOf( "content" to variable("$.steps.content-review.output.text"), "platform" to variable("$.input.publishPlatform") ) } } ``` ## Step Configuration Options ✅ Kastrax provides numerous configuration options for workflow steps to customize their behavior: ### Step Dependencies Specify when a step should execute relative to other steps: ```kotlin // Basic dependency - run after another step step(agent) { id = "step-id" after("previous-step-id") } // Multiple dependencies - run after multiple steps complete step(agent) { id = "step-id" after("step-one", "step-two", "step-three") } // Conditional dependency - run after a step only if it succeeded step(agent) { id = "step-id" afterSuccessful("previous-step-id") } ``` ### Error Handling Configure how steps handle errors: ```kotlin step(agent) { id = "step-id" // Define error handling onError { error -> // Log the error println("Error in step: ${error.message}") // Return a fallback result mapOf("result" to "fallback value") } // Set retry behavior retry { maxAttempts = 3 backoffMs = 1000 // Initial backoff in milliseconds backoffFactor = 2.0 // Exponential backoff factor } } ``` ### Timeouts Set execution time limits for steps: ```kotlin step(agent) { id = "step-id" // Set a timeout for the step execution timeoutMs = 30000 // 30 seconds // Define what happens on timeout onTimeout { // Return a default result mapOf("result" to "timeout occurred") } } ``` ## Best Practices for Step Design ✅ ### 1. Use Descriptive IDs and Names Give your steps clear, descriptive IDs and names that indicate their purpose: ```kotlin step(agent) { id = "customer-data-extraction" // Specific, descriptive ID name = "Customer Data Extraction" // Human-readable name description = "Extract customer information from the input data" // Detailed description } ``` ### 2. Keep Steps Focused Design steps to perform a single, well-defined task rather than combining multiple operations: ```kotlin // Good: Focused steps step(dataExtractionAgent) { id = "data-extraction" } step(dataAnalysisAgent) { id = "data-analysis" after("data-extraction") } // Avoid: Overly complex step that does too much // step(combinedAgent) { // id = "extract-and-analyze" // Too many responsibilities // } ``` ### 3. Handle Errors Gracefully Implement proper error handling for each step to ensure workflow resilience: ```kotlin step(agent) { id = "api-call" onError { error -> when { error is ApiTimeoutException -> { // Handle timeout specifically mapOf("status" to "timeout", "retry" to true) } error.message?.contains("rate limit") == true -> { // Handle rate limiting mapOf("status" to "rate-limited", "retry" to true) } else -> { // Handle other errors mapOf("status" to "error", "message" to error.message) } } } } ``` ### 4. Use Variables Effectively Leverage variables to pass data between steps in a type-safe manner: ```kotlin step(agent) { id = "data-processing" // Use explicit variable mapping variables = mutableMapOf( // Map from workflow input "inputData" to variable("$.input.data"), // Map from previous step with default value "processingLevel" to variable("$.steps.configuration.output.level", defaultValue = "standard"), // Map with transformation "timestamp" to variable("$.input.timestamp") { timestamp -> // Convert timestamp to formatted date java.time.Instant.ofEpochMilli((timestamp as? Long) ?: 0) .atZone(java.time.ZoneId.systemDefault()) .toLocalDateTime() .toString() } ) } ``` ### 5. Document Step Behavior Provide clear documentation for each step to help others understand its purpose and requirements: ```kotlin step(agent) { id = "risk-assessment" name = "Risk Assessment" description = """ Analyzes customer data to determine risk level. Requires customer financial history and credit score. Returns a risk score between 0-100 and risk category. """.trimIndent() } ``` By following these best practices, you can create well-structured, maintainable, and robust workflow steps that effectively orchestrate your AI agents and tools. --- title: "Suspend & Resume in Kastrax AI Workflows | Human-in-the-Loop | Kastrax Docs" description: "Learn how to implement long-running AI workflows with Kastrax's powerful suspension and resumption capabilities, enabling human-in-the-loop processes and asynchronous operations." --- # Suspend and Resume in Kastrax AI Workflows ✅ [EN] Source: https://kastrax.ai/en/docs/workflows/suspend-and-resume Complex AI workflows often need to pause execution while waiting for external input, human feedback, or resource availability. Kastrax provides a sophisticated suspension and resumption system that leverages Kotlin's coroutine capabilities to create natural, intuitive pausing points in your workflows. Kastrax's suspend and resume features allow you to: - Pause workflow execution at any step - Persist the complete workflow state to durable storage - Resume execution from the exact suspension point when ready - Inject new data when resuming to influence subsequent steps - Create human-in-the-loop processes with minimal code This entire process is automatically managed by the Kastrax workflow engine, with the workflow state preserved across application restarts, deployments, and server instances. This persistence is crucial for workflows that might remain suspended for minutes, hours, or even days while waiting for external input or resources. ## When to Use Suspend/Resume ✅ Kastrax's suspension and resumption capabilities are particularly valuable in these scenarios: - **Human-in-the-Loop Processes**: Pause for human review, approval, or input - **Asynchronous Operations**: Wait for long-running external processes to complete - **Resource Management**: Pause until required resources or API quotas become available - **Multi-Stage Approvals**: Implement complex approval workflows with multiple decision points - **Scheduled Operations**: Suspend until a specific time or condition is met - **Error Recovery**: Pause on error for manual intervention before continuing - **Distributed Decision Making**: Coordinate decisions across multiple agents or systems ## Suspension Mechanisms in Kastrax ✅ Kastrax provides several ways to suspend workflow execution: ### 1. Step-Level Suspension The most direct approach is to suspend within a step's execution logic: ```kotlin filename="StepSuspension.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with a suspending step val approvalWorkflow = workflow { name = "approval-workflow" description = "Process content with human approval" // Generate content step step(contentGenerationAgent) { id = "generate-content" name = "Generate Content" description = "Create initial content" variables = mutableMapOf( "topic" to variable("$.input.topic"), "style" to variable("$.input.style") ) } // Review step with suspension step(reviewAgent) { id = "review-content" name = "Review Content" description = "Review and potentially suspend for human approval" after("generate-content") variables = mutableMapOf( "content" to variable("$.steps.generate-content.output.content"), "qualityThreshold" to variable("$.input.qualityThreshold", defaultValue = 8.0) ) // Custom execution logic with suspension execute { context, suspend -> val content = context.getVariable("content") as? String ?: "" val threshold = context.getVariable("qualityThreshold") as? Double ?: 8.0 // Evaluate content quality val quality = evaluateContentQuality(content) // If quality is below threshold, suspend for human review if (quality < threshold) { // Suspend with metadata about why we're suspending val suspensionId = suspend(mapOf( "reason" to "Content quality below threshold", "quality" to quality, "threshold" to threshold, "contentPreview" to content.take(100) )) // This code will execute when the workflow is resumed // We can access the resume data provided when resuming val humanApproved = context.getResumeData("approved") as? Boolean ?: false val humanFeedback = context.getResumeData("feedback") as? String ?: "" return@execute mapOf( "quality" to quality, "approved" to humanApproved, "feedback" to humanFeedback, "suspensionId" to suspensionId ) } // If quality is good, no need for human review return@execute mapOf( "quality" to quality, "approved" to true, "feedback" to "Automatically approved - quality above threshold" ) } } // Final processing based on review step(finalizationAgent) { id = "finalize-content" name = "Finalize Content" description = "Process content based on review results" after("review-content") variables = mutableMapOf( "content" to variable("$.steps.generate-content.output.content"), "approved" to variable("$.steps.review-content.output.approved"), "feedback" to variable("$.steps.review-content.output.feedback") ) } } // Helper function to evaluate content quality fun evaluateContentQuality(content: String): Double { // In a real implementation, this would use an AI model or heuristics // to evaluate content quality return 7.5 // Example score that will trigger suspension } ``` In this example, the workflow suspends when content quality is below a threshold, waiting for human review before continuing. ### 2. Human-in-the-Loop Steps Kastrax provides dedicated human-in-the-loop steps for common approval patterns: ```kotlin filename="HumanInTheLoopStep.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with a human-in-the-loop step val reviewWorkflow = workflow { name = "human-review-workflow" description = "Process content with explicit human review step" // Generate content step step(contentGenerationAgent) { id = "generate-content" name = "Generate Content" description = "Create initial content" variables = mutableMapOf( "topic" to variable("$.input.topic") ) } // Human review step - automatically suspends the workflow humanStep { id = "human-review" name = "Human Review" description = "Get human approval for the content" after("generate-content") // Define the prompt to show to the human reviewer prompt { context -> val content = context.getVariable("$.steps.generate-content.output.content") as? String ?: "" val topic = context.getVariable("$.input.topic") as? String ?: "unknown topic" """ Please review the following content about "$topic": $content Approve or suggest changes. """.trimIndent() } // Set timeout for human response (24 hours) timeoutMs = 86400000 // Define what happens on timeout onTimeout { step(notificationAgent) { id = "timeout-notification" name = "Timeout Notification" description = "Notify about review timeout" variables = mutableMapOf( "content" to variable("$.steps.generate-content.output.content"), "recipient" to variable("$.input.notificationEmail") ) } } } // Process the human feedback step(processFeedbackAgent) { id = "process-feedback" name = "Process Human Feedback" description = "Process the feedback from human review" after("human-review") variables = mutableMapOf( "originalContent" to variable("$.steps.generate-content.output.content"), "approved" to variable("$.steps.human-review.output.approved"), "feedback" to variable("$.steps.human-review.output.feedback") ) } } ``` ### 3. Event-Based Suspension Kastrax supports event-based suspension for waiting on external events: ```kotlin filename="EventBasedSuspension.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import ai.kastrax.core.workflow.event.EventDefinition // Create a workflow with event-based suspension val paymentWorkflow = workflow { name = "payment-processing" description = "Process payment with external confirmation" // Define events that the workflow can wait for events { event("payment_confirmed") { description = "Payment confirmation received from payment processor" schema = PaymentConfirmation::class } event("payment_rejected") { description = "Payment rejection received from payment processor" schema = PaymentRejection::class } } // Initial payment processing step step(paymentInitiationAgent) { id = "initiate-payment" name = "Initiate Payment" description = "Start the payment process" variables = mutableMapOf( "amount" to variable("$.input.amount"), "paymentMethod" to variable("$.input.paymentMethod") ) } // Wait for payment confirmation event waitForEvent { id = "wait-for-confirmation" name = "Wait for Payment Confirmation" description = "Suspend workflow until payment is confirmed or rejected" after("initiate-payment") // Specify which events to wait for events = listOf("payment_confirmed", "payment_rejected") // Optional timeout timeoutMs = 300000 // 5 minutes // Handle timeout onTimeout { step(timeoutHandlingAgent) { id = "handle-timeout" name = "Handle Payment Timeout" description = "Process payment timeout" variables = mutableMapOf( "paymentId" to variable("$.steps.initiate-payment.output.paymentId") ) } } } // Conditional processing based on which event was received conditionalStep { id = "process-payment-result" name = "Process Payment Result" description = "Handle payment confirmation or rejection" after("wait-for-confirmation") // Check which event was received condition { context -> val eventType = context.getVariable("$.steps.wait-for-confirmation.output.eventType") as? String eventType == "payment_confirmed" } // Handle payment confirmation onTrue { step(confirmationAgent) { id = "process-confirmation" name = "Process Confirmation" description = "Handle successful payment" variables = mutableMapOf( "confirmation" to variable("$.steps.wait-for-confirmation.output.eventData") ) } } // Handle payment rejection onFalse { step(rejectionAgent) { id = "process-rejection" name = "Process Rejection" description = "Handle payment rejection" variables = mutableMapOf( "rejection" to variable("$.steps.wait-for-confirmation.output.eventData") ) } } } } // Data classes for event schemas @Serializable data class PaymentConfirmation( val transactionId: String, val amount: Double, val timestamp: Long, val confirmationCode: String ) @Serializable data class PaymentRejection( val transactionId: String, val reason: String, val timestamp: Long ) ``` ## Workflow Suspension Lifecycle ✅ The suspension and resumption process in Kastrax follows a well-defined lifecycle: 1. **Suspension Trigger**: A step calls `suspend()` or a dedicated suspension step is reached 2. **State Persistence**: The complete workflow state is serialized and stored 3. **Suspension Notification**: The workflow engine returns a suspended status with metadata 4. **External Processing**: External systems or humans process the suspended workflow 5. **Resumption Request**: The workflow is resumed with new data via the API 6. **State Restoration**: The workflow state is restored from storage 7. **Execution Continuation**: Execution continues from the exact suspension point This lifecycle ensures reliable long-running workflows that can span minutes to days or even weeks. ## Executing Suspendable Workflows ✅ Kastrax provides a comprehensive API for executing, monitoring, and resuming suspended workflows: ```kotlin filename="WorkflowExecution.kt" import ai.kastrax.core.workflow.WorkflowEngine import ai.kastrax.core.workflow.WorkflowExecuteOptions import ai.kastrax.core.workflow.WorkflowResumeOptions import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Get the workflow engine val workflowEngine = kastraxSystem.workflowEngine // Execute the approval workflow val result = workflowEngine.executeWorkflow( workflowId = "approval-workflow", input = mapOf( "topic" to "Artificial Intelligence Ethics", "style" to "academic", "qualityThreshold" to 8.5, "notificationEmail" to "reviewer@example.com" ), options = WorkflowExecuteOptions( // Set execution options maxSteps = 20, timeout = 60000, // 60 seconds timeout for the initial execution // Add step completion listener onStepFinish = { stepResult -> println("Step ${stepResult.stepId} completed with status: ${stepResult.status}") // Check if a step was suspended if (stepResult.status == "suspended") { println("Workflow suspended at step: ${stepResult.stepId}") println("Suspension metadata: ${stepResult.suspensionMetadata}") // In a real application, you would notify external systems // or create a task for human review here } } ) ) // Check if the workflow is suspended if (result.status == "suspended") { val suspendedStepId = result.suspendedStepId val suspensionId = result.suspensionId println("Workflow suspended at step: $suspendedStepId") println("Suspension ID: $suspensionId") // In a real application, you would wait for human input or an external event // For this example, we'll simulate receiving human input val humanInput = simulateHumanReview(result) // Resume the workflow with the human input val resumeResult = workflowEngine.resumeWorkflow( suspensionId = suspensionId, resumeData = mapOf( "approved" to humanInput.approved, "feedback" to humanInput.feedback ), options = WorkflowResumeOptions( timeout = 30000 // 30 seconds timeout for the resumed execution ) ) println("Workflow resumed and completed with status: ${resumeResult.status}") println("Final output: ${resumeResult.output}") } else { println("Workflow completed without suspension with status: ${result.status}") println("Output: ${result.output}") } } // Simulate human review (in a real application, this would be a UI or API interaction) data class HumanReviewInput(val approved: Boolean, val feedback: String) fun simulateHumanReview(workflowResult: WorkflowResult): HumanReviewInput { // In a real application, this would wait for actual human input // For this example, we're simulating an approval with feedback return HumanReviewInput( approved = true, feedback = "The content looks good, but please add more examples." ) } ``` ## Resuming Event-Based Workflows ✅ For event-based workflows, Kastrax provides specialized methods to resume execution when events occur: ```kotlin filename="EventBasedResumption.kt" import ai.kastrax.core.workflow.WorkflowEngine import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Get the workflow engine val workflowEngine = kastraxSystem.workflowEngine // Execute the payment workflow val result = workflowEngine.executeWorkflow( workflowId = "payment-processing", input = mapOf( "amount" to 99.99, "paymentMethod" to "credit_card" ) ) // Check if the workflow is waiting for an event if (result.status == "waiting_for_event") { val waitingForEvents = result.waitingForEvents val suspensionId = result.suspensionId println("Workflow waiting for events: $waitingForEvents") println("Suspension ID: $suspensionId") // In a real application, this would be triggered by an external system // For this example, we'll simulate receiving a payment confirmation event val paymentConfirmation = PaymentConfirmation( transactionId = "tx-12345", amount = 99.99, timestamp = System.currentTimeMillis(), confirmationCode = "CONF-ABC123" ) // Resume the workflow with the event val resumeResult = workflowEngine.resumeWorkflowWithEvent( suspensionId = suspensionId, eventName = "payment_confirmed", eventData = paymentConfirmation ) println("Workflow resumed with event and completed with status: ${resumeResult.status}") println("Final output: ${resumeResult.output}") } } ``` ## Integration with External Systems ✅ Kastrax's suspension mechanism is designed to integrate seamlessly with external systems through its API: ### REST API Integration ```kotlin filename="RestApiIntegration.kt" import io.ktor.server.application.* import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.routing.* import io.ktor.server.request.* import io.ktor.server.response.* // Create a REST API for workflow interaction fun main() { embeddedServer(Netty, port = 8080) { // Get the workflow engine val workflowEngine = kastraxSystem.workflowEngine routing { // Start a new workflow post("/workflows/{workflowId}/execute") { val workflowId = call.parameters["workflowId"]!! val input = call.receive>() val result = workflowEngine.executeWorkflow(workflowId, input) call.respond(result) } // Resume a suspended workflow post("/workflows/resume/{suspensionId}") { val suspensionId = call.parameters["suspensionId"]!! val resumeData = call.receive>() val result = workflowEngine.resumeWorkflow(suspensionId, resumeData) call.respond(result) } // Send an event to a waiting workflow post("/workflows/events/{suspensionId}/{eventName}") { val suspensionId = call.parameters["suspensionId"]!! val eventName = call.parameters["eventName"]!! val eventData = call.receive() val result = workflowEngine.resumeWorkflowWithEvent(suspensionId, eventName, eventData) call.respond(result) } // Get suspended workflows get("/workflows/suspended") { val suspendedWorkflows = workflowEngine.getSuspendedWorkflows() call.respond(suspendedWorkflows) } } }.start(wait = true) } ``` ### Web UI Integration For human-in-the-loop workflows, you can create a web UI that displays suspended workflows and allows humans to provide input: ```kotlin filename="WebUiIntegration.kt" import io.ktor.server.application.* import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.routing.* import io.ktor.server.html.* import kotlinx.html.* // Create a web UI for human review fun main() { embeddedServer(Netty, port = 8080) { // Get the workflow engine val workflowEngine = kastraxSystem.workflowEngine routing { // Display a list of workflows waiting for human input get("/human-tasks") { val suspendedWorkflows = workflowEngine.getSuspendedWorkflows() .filter { it.suspensionReason == "human_review" } call.respondHtml { head { title("Human Review Tasks") } body { h1 { +"Tasks Waiting for Human Review" } suspendedWorkflows.forEach { workflow -> div { h2 { +"Task: ${workflow.workflowName}" } p { +"Suspended at step: ${workflow.suspendedStepId}" } p { +"Suspension ID: ${workflow.suspensionId}" } // Display the prompt for human review val prompt = workflow.suspensionMetadata["prompt"] as? String ?: "" div { h3 { +"Review Request:" } pre { +prompt } } // Form for submitting feedback form { action = "/human-tasks/${workflow.suspensionId}/submit" method = FormMethod.post div { label { htmlFor = "approved" +"Approve: " } select { name = "approved" option { value = "true"; +"Yes" } option { value = "false"; +"No" } } } div { label { htmlFor = "feedback" +"Feedback: " } textArea { name = "feedback" rows = "4" cols = "50" } } button { type = ButtonType.submit +"Submit Review" } } } hr {} } } } } // Handle form submission post("/human-tasks/{suspensionId}/submit") { val suspensionId = call.parameters["suspensionId"]!! val parameters = call.receiveParameters() val approved = parameters["approved"] == "true" val feedback = parameters["feedback"] ?: "" // Resume the workflow with the human input workflowEngine.resumeWorkflow( suspensionId = suspensionId, resumeData = mapOf( "approved" to approved, "feedback" to feedback ) ) call.respondRedirect("/human-tasks") } } }.start(wait = true) } ``` ### Key Points About Event-Based Workflows - The `suspend()` function can optionally take a payload object that will be stored with the suspended state - Code after the `await suspend()` call will not execute until the step is resumed - When a step is suspended, its status becomes `'suspended'` in the workflow results - When resumed, the step's status changes from `'suspended'` to `'success'` once completed - The `resume()` method requires the `stepId` to identify which suspended step to resume - You can provide new context data when resuming that will be merged with existing step results - Events must be defined in the workflow configuration with a schema - The `afterEvent` method creates a special suspended step that waits for the event - The event step is automatically named `__eventName_event` (e.g., `__approvalReceived_event`) - Use `resumeWithEvent` to provide event data and continue the workflow - Event data is validated against the schema defined for that event - The event data is available in the context as `inputData.resumedEvent` ## Storage for Suspend and Resume ✅ When a workflow is suspended using `await suspend()`, Kastrax automatically persists the entire workflow state to storage. This is essential for workflows that might remain suspended for extended periods, as it ensures the state is preserved across application restarts or server instances. ### Default Storage: LibSQL By default, Kastrax uses LibSQL as its storage engine: ```typescript import { Kastrax } from "@kastrax/core/kastrax"; import { LibSQLStore } from "@kastrax/libsql"; const kastrax = new Kastrax({ storage: new LibSQLStore({ url: "file:./storage.db", // Local file-based database for development // For production, use a persistent URL: // url: process.env.DATABASE_URL, // authToken: process.env.DATABASE_AUTH_TOKEN, // Optional for authenticated connections }), }); ``` The LibSQL storage can be configured in different modes: - In-memory database (testing): `:memory:` - File-based database (development): `file:storage.db` - Remote database (production): URLs like `libsql://your-database.turso.io` ### Alternative Storage Options #### Upstash (Redis-Compatible) For serverless applications or environments where Redis is preferred: ```bash copy npm install @kastrax/upstash@latest ``` ```typescript import { Kastrax } from "@kastrax/core/kastrax"; import { UpstashStore } from "@kastrax/upstash"; const kastrax = new Kastrax({ storage: new UpstashStore({ url: process.env.UPSTASH_URL, token: process.env.UPSTASH_TOKEN, }), }); ``` ### Storage Considerations - All storage options support suspend and resume functionality identically - The workflow state is automatically serialized and saved when suspended - No additional configuration is needed for suspend/resume to work with storage - Choose your storage option based on your infrastructure, scaling needs, and existing technology stack ## Watching and Resuming ✅ To handle suspended workflows, use the `watch` method to monitor workflow status per run and `resume` to continue execution: ```typescript import { kastrax } from "./index"; // Get the workflow const myWorkflow = kastrax.getWorkflow("myWorkflow"); const { start, watch, resume } = myWorkflow.createRun(); // Start watching the workflow before executing it watch(async ({ activePaths }) => { const isStepTwoSuspended = activePaths.get("stepTwo")?.status === "suspended"; if (isStepTwoSuspended) { console.log("Workflow suspended, resuming with new value"); // Resume the workflow with new context await resume({ stepId: "stepTwo", context: { secondValue: 100 }, }); } }); // Start the workflow execution await start({ triggerData: { inputValue: 45 } }); ``` ### Watching and Resuming Event-Based Workflows You can use the same watching pattern with event-based workflows: ```typescript const { start, watch, resumeWithEvent } = workflow.createRun(); // Watch for suspended event steps watch(async ({ activePaths }) => { const isApprovalReceivedSuspended = activePaths.get("__approvalReceived_event")?.status === "suspended"; if (isApprovalReceivedSuspended) { console.log("Workflow waiting for approval event"); // In a real scenario, you would wait for the actual event to occur // For example, this could be triggered by a webhook or user interaction setTimeout(async () => { await resumeWithEvent("approvalReceived", { approved: true, approverName: "Auto Approver", }); }, 5000); // Simulate event after 5 seconds } }); // Start the workflow await start({ triggerData: { requestId: "auto-123" } }); ``` ## Best Practices for Suspendable Workflows ✅ ### 1. Design for Clarity and Maintainability ```kotlin // Good: Clear suspension points with descriptive metadata step(reviewAgent) { id = "content-review" execute { context, suspend -> if (needsHumanReview(context)) { suspend(mapOf( "reason" to "Content requires human review", "contentType" to "blog post", "priority" to "high" )) } // Rest of execution logic } } // Avoid: Unclear suspension with no metadata // step(reviewAgent) { // id = "review" // execute { context, suspend -> // if (someCondition) { // suspend() // No metadata about why we're suspending // } // } // } ``` ### 2. Implement Proper Error Handling ```kotlin // Handle errors during resumption step(processingAgent) { id = "process-data" execute { context, suspend -> try { // Attempt to process data val result = processData(context) return@execute result } catch (e: Exception) { // Suspend for human intervention on error suspend(mapOf( "reason" to "Error during processing", "error" to e.message, "errorType" to e.javaClass.simpleName )) // When resumed, try to recover val manualFix = context.getResumeData("manualFix") as? String return@execute mapOf("result" to manualFix) } } } ``` ### 3. Use Timeouts Appropriately ```kotlin // Set appropriate timeouts for different types of human tasks humanStep { id = "urgent-review" description = "Urgent review needed" prompt { /* ... */ } timeoutMs = 3600000 // 1 hour for urgent reviews } humanStep { id = "standard-review" description = "Standard content review" prompt { /* ... */ } timeoutMs = 86400000 // 24 hours for standard reviews } ``` ### 4. Implement Idempotent Resumption ```kotlin // Design steps to handle being resumed multiple times step(paymentProcessingAgent) { id = "process-payment" execute { context, suspend -> val paymentId = context.getVariable("paymentId") as? String // Check if payment was already processed (idempotency check) if (isPaymentAlreadyProcessed(paymentId)) { return@execute mapOf( "status" to "already_processed", "paymentId" to paymentId ) } // Process payment logic... } } ``` ### 5. Monitor Suspended Workflows ```kotlin // Implement monitoring for suspended workflows class WorkflowMonitor(private val workflowEngine: WorkflowEngine) { fun monitorSuspendedWorkflows() { val suspendedWorkflows = workflowEngine.getSuspendedWorkflows() // Check for stale workflows val staleWorkflows = suspendedWorkflows.filter { workflow -> val suspendedAt = workflow.suspendedAt val now = System.currentTimeMillis() val ageInHours = (now - suspendedAt) / (1000 * 60 * 60) ageInHours > 48 // Workflows suspended for more than 48 hours } // Send notifications for stale workflows staleWorkflows.forEach { workflow -> sendNotification( "Stale workflow detected", "Workflow ${workflow.workflowId} has been suspended for more than 48 hours" ) } } } ``` ## Limitations and Considerations ✅ When working with suspendable workflows in Kastrax, be aware of these limitations and considerations: 1. **Storage Requirements**: Suspended workflows consume storage resources, so implement cleanup strategies for abandoned workflows 2. **Serialization Constraints**: All data in suspended workflows must be serializable for persistence 3. **Long-Running Transactions**: Consider the impact of long-running database transactions when workflows are suspended for extended periods 4. **State Evolution**: Plan for how to handle suspended workflows when your code or data models evolve 5. **Security Considerations**: Ensure that sensitive data in suspended workflows is properly protected 6. **Monitoring Overhead**: Implement appropriate monitoring to track and manage suspended workflows 7. **Testing Complexity**: Thoroughly test suspension and resumption paths, including timeout scenarios ## Further Reading ✅ For a deeper understanding of how suspend and resume works in Kastrax: - [Workflow State Management](../../reference/workflows/state-management.mdx) - Learn about the state persistence mechanism that powers suspend and resume functionality - [Step Configuration Guide](./steps.mdx) - Learn more about configuring steps in your workflows - [Control Flow Guide](./control-flow.mdx) - Advanced workflow control patterns - [Event-Driven Workflows](../../reference/workflows/events.mdx) - Detailed reference for event-based workflows ## Related Resources ✅ - See the [Suspend and Resume Example](../../examples/workflows/suspend-and-resume.mdx) for a complete working example - Check the [Workflow API Reference](../../reference/workflows/workflow-api.mdx) for suspend/resume API details - Review [Workflow Monitoring](../../reference/observability/workflow-monitoring.mdx) for tracking suspended workflows --- title: "Data Flow with Kastrax Workflow Variables | Kastrax Docs" description: "Learn how to use Kastrax's type-safe variable system to manage data flow between workflow steps, transform values, and create dynamic AI agent workflows." --- # Data Flow with Kastrax Workflow Variables ✅ [EN] Source: https://kastrax.ai/en/docs/workflows/variables Kastrax provides a powerful, type-safe variable system that enables efficient data flow between workflow steps. This system allows you to map input data to steps, pass information between steps, access nested properties, and transform values—all with the type safety and expressiveness of Kotlin. ## Understanding Workflow Variables ✅ In Kastrax workflows, variables serve multiple important functions: - **Data Mapping**: Connect workflow inputs to step inputs - **Inter-step Communication**: Pass outputs from one step to inputs of another - **Property Access**: Navigate complex data structures with JSONPath expressions - **Data Transformation**: Apply transformations to values as they flow between steps - **Default Values**: Provide fallbacks when data might be missing - **Type Safety**: Ensure data consistency with Kotlin's type system ## Variable System in Kastrax ✅ Kastrax implements variables using a combination of JSONPath expressions and Kotlin's type system. This provides a flexible yet type-safe way to manage data flow in your workflows. ### Creating Variables Variables are created using the `variable()` function, which supports several overloads for different use cases: ```kotlin filename="VariableCreation.kt" import ai.kastrax.core.workflow.variable // Basic variable referencing a JSONPath val basicVariable = variable("$.input.topic") // Variable with a default value val variableWithDefault = variable("$.steps.analysis.output.score", defaultValue = 0.0) // Variable with a transformation function val transformedVariable = variable("$.steps.text.output.content") { content -> (content as? String)?.uppercase() ?: "" } // Variable with both default and transformation val complexVariable = variable( path = "$.steps.data.output.items", defaultValue = emptyList(), transform = { items -> (items as? List<*>)?.filterIsInstance()?.take(5) ?: emptyList() } ) ``` ## Using Variables for Data Mapping ✅ ### Basic Variable Mapping You can map data between steps using the `variables` property when defining a step in your workflow: ```kotlin filename="BasicVariableMapping.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import ai.kastrax.core.agent.Agent // Assume we have these agents defined elsewhere val dataProcessingAgent: Agent = /* ... */ val analysisAgent: Agent = /* ... */ // Create a workflow with variable mapping val dataWorkflow = workflow { name = "data-mapping-workflow" description = "Process and analyze data" // First step with input mapping step(dataProcessingAgent) { id = "process-data" name = "Process Data" description = "Process the input data" // Map workflow input to step variables variables = mutableMapOf( "inputData" to variable("$.input.data"), "options" to variable("$.input.processingOptions") ) } // Second step with mapping from first step step(analysisAgent) { id = "analyze-data" name = "Analyze Data" description = "Analyze the processed data" after("process-data") // This step runs after process-data // Map output from first step to input for second step variables = mutableMapOf( "processedData" to variable("$.steps.process-data.output.result"), "analysisDepth" to variable("$.input.analysisDepth", defaultValue = "standard") ) } } ``` ### Accessing Nested Properties You can access nested properties using JSONPath expressions: ```kotlin filename="NestedProperties.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow that accesses nested properties val nestedDataWorkflow = workflow { name = "nested-data-workflow" step(dataExtractionAgent) { id = "extract-data" name = "Extract Data" description = "Extract structured data from input" variables = mutableMapOf( "source" to variable("$.input.source") ) } step(processingAgent) { id = "process-data" name = "Process Data" description = "Process the extracted data" after("extract-data") variables = mutableMapOf( // Access deeply nested properties using JSONPath "customerName" to variable("$.steps.extract-data.output.data.customer.profile.name"), "orderItems" to variable("$.steps.extract-data.output.data.order.items[*].name"), "totalAmount" to variable("$.steps.extract-data.output.data.order.payment.amount") ) } } ``` ### Data Transformations You can transform data as it flows between steps: ```kotlin filename="DataTransformations.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with data transformations val transformationWorkflow = workflow { name = "transformation-workflow" step(dataAgent) { id = "get-data" name = "Get Data" description = "Retrieve raw data" variables = mutableMapOf( "query" to variable("$.input.query") ) } step(processingAgent) { id = "process-data" name = "Process Data" description = "Process data with transformations" after("get-data") variables = mutableMapOf( // Transform a string to uppercase "upperCaseTitle" to variable("$.steps.get-data.output.title") { title -> (title as? String)?.uppercase() ?: "" }, // Filter and limit a list "topCategories" to variable("$.steps.get-data.output.categories") { categories -> (categories as? List<*>)?.take(5) ?: emptyList() }, // Calculate a derived value "wordCount" to variable("$.steps.get-data.output.content") { content -> (content as? String)?.split("\\s+")?.size ?: 0 } ) } } ``` ### Variables in Loops Variables are particularly useful in loop steps for passing data between iterations: ```kotlin filename="LoopVariables.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable // Create a workflow with a loop that uses variables val iterativeWorkflow = workflow { name = "iterative-workflow" description = "Iteratively improve content" // Initialize loop variables step(initializationAgent) { id = "initialize" name = "Initialize Variables" description = "Set up initial values for the loop" execute { _ -> mapOf( "currentContent" to "Initial draft", "iterationCount" to 0, "qualityScore" to 0.0 ) } } // Improvement loop loopStep { id = "improvement-loop" name = "Content Improvement Loop" description = "Iteratively improve content until quality threshold is reached" after("initialize") // Continue looping while quality is below threshold and iterations are under limit condition { context -> val qualityScore = context.getVariable("$.steps.evaluate.output.qualityScore") as? Double ?: 0.0 val iterationCount = context.getVariable("$.steps.improve.output.iterationCount") as? Int ?: 0 qualityScore < 8.0 && iterationCount < 5 } // Loop body body { // Evaluate current content step(evaluationAgent) { id = "evaluate" name = "Evaluate Content" description = "Assess the quality of the current content" variables = mutableMapOf( "content" to variable("$.steps.improve.output.currentContent", defaultValue = variable("$.steps.initialize.output.currentContent")) ) } // Improve content based on evaluation step(improvementAgent) { id = "improve" name = "Improve Content" description = "Enhance content based on evaluation" after("evaluate") variables = mutableMapOf( "content" to variable("$.steps.improve.output.currentContent", defaultValue = variable("$.steps.initialize.output.currentContent")), "feedback" to variable("$.steps.evaluate.output.feedback"), "iterationCount" to variable("$.steps.improve.output.iterationCount", defaultValue = 0) ) } } } } ``` ## Variable Resolution ✅ When a workflow executes, Kastrax resolves variables at runtime through a sophisticated resolution process: 1. **Path Parsing**: The JSONPath expression is parsed to identify the data source and path 2. **Context Access**: The workflow context is accessed to retrieve step outputs or input data 3. **Path Navigation**: The system navigates through the data structure following the JSONPath 4. **Default Application**: If the path doesn't resolve to a value and a default is provided, the default is used 5. **Transformation**: If a transformation function is specified, it's applied to the resolved value 6. **Type Conversion**: The value is converted to the expected type if necessary 7. **Injection**: The final value is injected into the step's execution context This process happens automatically for each variable when a step is about to execute. ## JSONPath in Kastrax ✅ Kastrax uses JSONPath expressions to navigate data structures. Here are the key patterns: | Pattern | Description | Example | |---------|-------------|--------| | `$.input` | Access workflow input data | `$.input.topic` | | `$.steps` | Access step outputs | `$.steps.research.output.text` | | `.property` | Access object property | `$.input.user.name` | | `[index]` | Access array element | `$.steps.data.output.items[0]` | | `[*]` | Access all array elements | `$.steps.data.output.items[*].name` | | `..property` | Recursive descent | `$..name` (finds all name properties) | ## Complete Examples ✅ ### Content Generation Workflow This example shows a complete content generation workflow that uses variables extensively: ```kotlin filename="ContentGenerationWorkflow.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import ai.kastrax.core.agent.Agent // Assume we have these agents defined elsewhere val researchAgent: Agent = /* ... */ val outlineAgent: Agent = /* ... */ val writingAgent: Agent = /* ... */ val editingAgent: Agent = /* ... */ // Create a content generation workflow val contentWorkflow = workflow { name = "content-generation" description = "Generate high-quality content on a given topic" // Define input variables input { variable("topic", String::class, required = true) variable("style", String::class, defaultValue = "informative") variable("wordCount", Int::class, defaultValue = 1000) variable("audience", String::class, defaultValue = "general") } // Research step step(researchAgent) { id = "research" name = "Research Topic" description = "Research the topic thoroughly" variables = mutableMapOf( "topic" to variable("$.input.topic"), "depth" to variable("$.input.researchDepth", defaultValue = "comprehensive") ) } // Create outline step step(outlineAgent) { id = "create-outline" name = "Create Content Outline" description = "Create a structured outline based on research" after("research") variables = mutableMapOf( "research" to variable("$.steps.research.output.findings"), "wordCount" to variable("$.input.wordCount"), "style" to variable("$.input.style") ) } // Writing step step(writingAgent) { id = "write-content" name = "Write Content" description = "Write content based on the outline" after("create-outline") variables = mutableMapOf( "outline" to variable("$.steps.create-outline.output.outline"), "research" to variable("$.steps.research.output.findings"), "style" to variable("$.input.style"), "audience" to variable("$.input.audience"), "targetWordCount" to variable("$.input.wordCount") ) } // Editing step step(editingAgent) { id = "edit-content" name = "Edit Content" description = "Edit and improve the written content" after("write-content") variables = mutableMapOf( "content" to variable("$.steps.write-content.output.content"), "style" to variable("$.input.style"), // Transform the word count to set editing intensity "editingIntensity" to variable("$.steps.write-content.output.wordCount") { wordCount -> when ((wordCount as? Int) ?: 0) { in 0..500 -> "light" in 501..2000 -> "medium" else -> "thorough" } } ) } // Define workflow output mapping output { "title" from "$.steps.write-content.output.title" "content" from "$.steps.edit-content.output.content" "wordCount" from "$.steps.edit-content.output.wordCount" "sources" from "$.steps.research.output.sources" "executionTime" from { context -> val startTime = context.startTime val endTime = System.currentTimeMillis() (endTime - startTime) / 1000 // Convert to seconds } } } ``` ### Data Processing Pipeline This example shows a data processing workflow with complex transformations: ```kotlin filename="DataProcessingWorkflow.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import ai.kastrax.core.tool.Tool // Assume we have these tools defined elsewhere val dataExtractionTool: Tool<*, *> = /* ... */ val dataNormalizationTool: Tool<*, *> = /* ... */ val dataAnalysisTool: Tool<*, *> = /* ... */ val reportGenerationTool: Tool<*, *> = /* ... */ // Create a data processing workflow val dataWorkflow = workflow { name = "data-processing-pipeline" description = "Extract, normalize, analyze, and report on data" // Data extraction step toolStep { id = "extract-data" name = "Extract Data" description = "Extract data from the source" tool = dataExtractionTool variables = mutableMapOf( "source" to variable("$.input.dataSource"), "format" to variable("$.input.sourceFormat", defaultValue = "json") ) } // Data normalization step toolStep { id = "normalize-data" name = "Normalize Data" description = "Clean and normalize the extracted data" after("extract-data") tool = dataNormalizationTool variables = mutableMapOf( "rawData" to variable("$.steps.extract-data.output.data"), // Transform the schema format if needed "schema" to variable("$.input.schema") { schema -> if (schema is String && schema.endsWith(".json")) { // Load schema from file (simplified example) "{ \"type\": \"object\", \"properties\": {} }" } else { schema } } ) } // Data analysis step toolStep { id = "analyze-data" name = "Analyze Data" description = "Perform analysis on the normalized data" after("normalize-data") tool = dataAnalysisTool variables = mutableMapOf( "data" to variable("$.steps.normalize-data.output.normalizedData"), "metrics" to variable("$.input.analysisMetrics", defaultValue = listOf("mean", "median", "mode")), // Filter data based on a threshold "significantOnly" to variable("$.input.significanceThreshold") { threshold -> (threshold as? Double)?.let { it > 0.0 } ?: false } ) } // Report generation step toolStep { id = "generate-report" name = "Generate Report" description = "Create a report from the analysis results" after("analyze-data") tool = reportGenerationTool variables = mutableMapOf( "analysisResults" to variable("$.steps.analyze-data.output.results"), "format" to variable("$.input.reportFormat", defaultValue = "pdf"), "includeCharts" to variable("$.input.includeVisualizations", defaultValue = true), // Combine metadata from multiple steps "metadata" to variable { context -> mapOf( "dataSource" to context.getVariable("$.input.dataSource"), "recordCount" to context.getVariable("$.steps.normalize-data.output.recordCount"), "analysisTimestamp" to context.getVariable("$.steps.analyze-data.output.timestamp"), "generatedBy" to "Kastrax Workflow Engine" ) } ) } // Define workflow output output { "report" from "$.steps.generate-report.output.report" "analysisResults" from "$.steps.analyze-data.output.results" "recordsProcessed" from "$.steps.normalize-data.output.recordCount" } } ``` ## Type Safety in Kastrax Variables ✅ Kastrax leverages Kotlin's strong type system to provide type safety for workflow variables: ```kotlin filename="TypeSafeVariables.kt" import ai.kastrax.core.workflow.workflow import ai.kastrax.core.workflow.variable import ai.kastrax.core.workflow.TypedVariable // Define typed variables for better type safety class UserProfile( val name: String, val email: String, val preferences: Map ) // Create a workflow with type-safe variables val typeSafeWorkflow = workflow { name = "type-safe-workflow" description = "Demonstrate type-safe variables" // Define typed input variables input { variable("userId", String::class, required = true) variable("includePreferences", Boolean::class, defaultValue = true) } // Fetch user profile step step(userProfileAgent) { id = "fetch-user" name = "Fetch User Profile" description = "Retrieve user profile information" variables = mutableMapOf( "userId" to variable("$.input.userId") ) // Type-safe output definition outputType = UserProfile::class } // Process user data step with type checking step(processingAgent) { id = "process-user" name = "Process User Data" description = "Process user profile information" after("fetch-user") // Type-safe variable access with explicit casting variables = mutableMapOf( // TypedVariable ensures the correct type at compile time "user" to TypedVariable("$.steps.fetch-user.output"), // Conditional variable based on input "includePreferences" to variable("$.input.includePreferences") ) // Access typed variables in execution execute { context -> val user = context.getTypedVariable("user") val includePrefs = context.getVariable("includePreferences") as? Boolean ?: false // Type-safe access to user properties val greeting = "Hello, ${user.name}!" val contactInfo = "Contact: ${user.email}" // Conditional processing based on preferences val preferences = if (includePrefs) user.preferences else emptyMap() mapOf( "greeting" to greeting, "contactInfo" to contactInfo, "preferences" to preferences ) } } } ``` ## Best Practices for Variables ✅ ### 1. Use Descriptive Variable Names Choose clear, descriptive names for your variables that indicate their purpose: ```kotlin // Good: Descriptive variable names variables = mutableMapOf( "customerProfile" to variable("$.steps.fetch-customer.output.profile"), "orderHistory" to variable("$.steps.fetch-orders.output.orders") ) // Avoid: Vague or generic names // variables = mutableMapOf( // "data1" to variable("$.steps.step1.output.profile"), // "data2" to variable("$.steps.step2.output.orders") // ) ``` ### 2. Provide Default Values Always provide sensible default values for optional variables: ```kotlin variables = mutableMapOf( "analysisDepth" to variable("$.input.depth", defaultValue = "standard"), "maxResults" to variable("$.input.limit", defaultValue = 10), "includeMetadata" to variable("$.input.metadata", defaultValue = true) ) ``` ### 3. Use Transformations for Data Preparation Leverage transformation functions to prepare data for steps: ```kotlin variables = mutableMapOf( // Normalize text input "normalizedText" to variable("$.input.text") { text -> (text as? String)?.trim()?.lowercase() ?: "" }, // Convert date string to timestamp "timestamp" to variable("$.input.date") { dateStr -> if (dateStr is String) { try { java.time.LocalDate.parse(dateStr).atStartOfDay() .toInstant(java.time.ZoneOffset.UTC).toEpochMilli() } catch (e: Exception) { System.currentTimeMillis() } } else { System.currentTimeMillis() } } ) ``` ### 4. Structure Complex Data Access For complex data structures, use intermediate steps to extract and structure data: ```kotlin // Extract and structure data in a dedicated step step(dataExtractionAgent) { id = "extract-structured-data" execute { context -> val rawData = context.getVariable("rawData") as? Map<*, *> ?: emptyMap() // Extract and structure the data mapOf( "customers" to extractCustomers(rawData), "products" to extractProducts(rawData), "metrics" to calculateMetrics(rawData) ) } } // Then use the structured data in subsequent steps step(analysisAgent) { id = "analyze-customers" after("extract-structured-data") variables = mutableMapOf( // Now we can access the structured data easily "customers" to variable("$.steps.extract-structured-data.output.customers") ) } ``` ### 5. Document Variable Requirements Clearly document the expected structure and types of variables: ```kotlin step(reportAgent) { id = "generate-report" name = "Generate Report" description = """ Generates a comprehensive report based on analysis results. Required variables: - analysisResults: Map - The results of the data analysis - format: String - The output format (pdf, html, markdown) - includeCharts: Boolean - Whether to include visualizations """.trimIndent() variables = mutableMapOf( "analysisResults" to variable("$.steps.analyze-data.output.results"), "format" to variable("$.input.format", defaultValue = "pdf"), "includeCharts" to variable("$.input.charts", defaultValue = true) ) } ``` ## Variable Access Patterns ✅ Kastrax provides multiple ways to access variables, each with its own advantages: | Pattern | Description | Use Case | |---------|-------------|----------| | `variable("$.path")` | Basic JSONPath variable | Simple data mapping | | `variable("$.path", defaultValue)` | Variable with default | Optional data with fallback | | `variable("$.path") { transform }` | Variable with transformation | Data conversion or processing | | `TypedVariable("$.path")` | Strongly-typed variable | Type-safe access to complex objects | | `variable { context -> ... }` | Dynamic variable | Computed values from multiple sources | Choosing the right pattern for each use case helps create robust, maintainable workflows with clear data flow. --- title: "Branching, Merging, Conditions | Workflows (vNext) | Kastrax Docs" description: "Control flow in Kastrax (vNext) workflows allows you to manage branching, merging, and conditions to construct workflows that meet your logic requirements." --- ## Sequential Flow [EN] Source: https://kastrax.ai/en/docs/workflows-vnext/flow-control Chain steps to execute in sequence using `.then()`: ```typescript myWorkflow.then(step1).then(step2).then(step3).commit(); ``` The output from each step is automatically passed to the next step if schemas match. If the schemas don't match, you can use the `map` function to transform the output to the expected schema. Step chaining is type-safe and checked at compile time. ## Parallel Execution Execute steps in parallel using `.parallel()`: ```typescript myWorkflow.parallel([step1, step2]).then(step3).commit(); ``` This executes all steps in the array concurrently, then continues to the next step after all parallel steps complete. You can also execute entire workflows in parallel: ```typescript myWorkflow .parallel([nestedWorkflow1, nestedWorkflow2]) .then(finalStep) .commit(); ``` Parallel steps receive previous step results as input. Their outputs are passed into the next step input as an object where the key is the step id and the value is the step output, for example the above example outputs an object with two keys `nestedWorkflow1` and `nestedWorkflow2` with the outputs of the respective workflows as values. ## Conditional Branching Create conditional branches using `.branch()`: ```typescript myWorkflow .then(initialStep) .branch([ [async ({ inputData }) => inputData.value > 50, highValueStep], [async ({ inputData }) => inputData.value <= 50, lowValueStep], ]) .then(finalStep) .commit(); ``` Branch conditions are evaluated in order, and the first matching condition's step is executed. Conditional steps receive previous step results as input. Their outputs are passed into the next step input as an object where the key is the step id and the value is the step output, for example the above example outputs an object with two keys `highValueStep` and `lowValueStep` with the outputs of the respective workflows as values. When multiple conditions are true, all matching steps are executed in parallel. ## Loops vNext supports two types of loops. When looping a step (or nested workflow or any other step-compatible construct), the `inputData` of the loop is the output of the previous step initially, but any subsequent `inputData` is the output of the loop step itself. Thus for looping, the initial loop state should either match the previous step output or be derived using the `map` function. **Do-While Loop**: Executes a step repeatedly while a condition is true. ```typescript myWorkflow .dowhile(incrementStep, async ({ inputData }) => inputData.value < 10) .then(finalStep) .commit(); ``` **Do-Until Loop**: Executes a step repeatedly until a condition becomes true. ```typescript myWorkflow .dountil(incrementStep, async ({ inputData }) => inputData.value >= 10) .then(finalStep) .commit(); ``` ```typescript const workflow = createWorkflow({ id: "increment-workflow", inputSchema: z.object({ value: z.number(), }), outputSchema: z.object({ value: z.number(), }), }) .dountil(incrementStep, async ({ inputData }) => inputData.value >= 10) .then(finalStep); ``` ## Foreach Foreach is a step that executes a step for each item in an array type input. ```typescript const mapStep = createStep({ id: "map", description: "Maps (+11) on the current value", inputSchema: z.object({ value: z.number(), }), outputSchema: z.object({ value: z.number(), }), execute: async ({ inputData }) => { return { value: inputData.value + 11 }; }, }); const finalStep = createStep({ id: "final", description: "Final step that prints the result", inputSchema: z.array(z.object({ value: z.number() })), outputSchema: z.object({ finalValue: z.number(), }), execute: async ({ inputData }) => { return { finalValue: inputData.reduce((acc, curr) => acc + curr.value, 0) }; }, }); const counterWorkflow = createWorkflow({ steps: [mapStep, finalStep], id: "counter-workflow", inputSchema: z.array(z.object({ value: z.number() })), outputSchema: z.object({ finalValue: z.number(), }), }); counterWorkflow.foreach(mapStep).then(finalStep).commit(); const run = counterWorkflow.createRun(); const result = await run.start({ inputData: [{ value: 1 }, { value: 22 }, { value: 333 }], }); if (result.status === "success") { console.log(result.result); // only exists if status is success } else if (result.status === "failed") { console.error(result.error); // only exists if status is failed, this is an instance of Error } ``` The loop executes the step for each item in the input array in sequence one at a time. The optional `concurrency` option allows you to execute steps in parallel with a limit on the number of concurrent executions. ```typescript counterWorkflow.foreach(mapStep, { concurrency: 2 }).then(finalStep).commit(); ``` ## Nested Workflows vNext supports composing workflows by nesting them: ```typescript const nestedWorkflow = createWorkflow({ id: 'nested-workflow', inputSchema: z.object({...}), outputSchema: z.object({...}), }) .then(step1) .then(step2) .commit(); const mainWorkflow = createWorkflow({ id: 'main-workflow', inputSchema: z.object({...}), outputSchema: z.object({...}), }) .then(initialStep) .then(nestedWorkflow) .then(finalStep) .commit(); ``` In the above example, the `nestedWorkflow` is used as a step in the `mainWorkflow`, where the `inputSchema` of `nestedWorkflow` matches the `outputSchema` of `initialStep`, and the `outputSchema` of `nestedWorkflow` matches the `inputSchema` of `finalStep`. Nested workflows are the main (and only) way compose execution flows beyond simple sequential execution. When using `.branch()` or `.parallel()` to compose execution flows, executing more than just one step necessarily requires a nested workflow, and as a byproduct, a description of how these steps are to be executed. ```typescript const planBothWorkflow = createWorkflow({ id: "plan-both-workflow", inputSchema: forecastSchema, outputSchema: z.object({ activities: z.string(), }), steps: [planActivities, planIndoorActivities, sythesizeStep], }) .parallel([planActivities, planIndoorActivities]) .then(sythesizeStep) .commit(); const weatherWorkflow = createWorkflow({ id: "weather-workflow-step3-concurrency", inputSchema: z.object({ city: z.string().describe("The city to get the weather for"), }), outputSchema: z.object({ activities: z.string(), }), steps: [fetchWeather, planBothWorkflow, planActivities], }) .then(fetchWeather) .branch([ [ async ({ inputData }) => { return inputData?.precipitationChance > 20; }, planBothWorkflow, ], [ async ({ inputData }) => { return inputData?.precipitationChance <= 20; }, planActivities, ], ]); ``` Nested workflows only have their final result (result of the last step) as their step output. --- title: "Input Data Mapping with Workflow (vNext) | Kastrax Docs" description: "Learn how to use workflow input mapping to create more dynamic data flows in your Kastrax workflows (vNext)." --- # Input Data Mapping [EN] Source: https://kastrax.ai/en/docs/workflows-vnext/input-data-mapping Input data mapping allows explicit mapping of values for the inputs of the next step. These values can come from a number of sources: - The outputs of a previous step - The runtime context - A constant value - The initial input of the workflow ```typescript myWorkflow .then(step1) .map({ transformedValue: { step: step1, path: "nestedValue", }, runtimeContextValue: { runtimeContextPath: "runtimeContextValue", schema: z.number(), }, constantValue: { value: 42, schema: z.number(), }, initDataValue: { initData: myWorkflow, path: "startValue", }, }) .then(step2) .commit(); ``` There are many cases where `.map()` can be useful in matching inputs to outputs, whether it's renaming outputs to match inputs or mapping complex data structures or other previous step outputs. ## Renaming outputs One use case for input mappings is renaming outputs to match inputs: ```typescript const step1 = createStep({ id: "step1", inputSchema: z.object({ inputValue: z.string(), }), outputSchema: z.object({ outputValue: z.string(), }), execute: async ({ inputData }) => { return { outputValue: inputData.inputValue }; }, }); const step2 = createStep({ id: "step2", inputSchema: z.object({ unexepectedName: z.string(), }), outputSchema: z.object({ result: z.string(), }), execute: async ({ inputData }) => { return { result: inputData.outputValue }; }, }); const workflow = createWorkflow({ id: "my-workflow", steps: [step1, step2], inputSchema: z.object({ inputValue: z.string(), }), outputSchema: z.object({ result: z.string(), }), }); workflow .then(step1) .map({ unexepectedName: { step: step1, path: "outputValue", }, }) .then(step2) .commit(); ``` ## Using workflow inputs as later step inputs ```typescript const step1 = createStep({ id: "step1", inputSchema: z.object({ inputValue: z.string(), }), outputSchema: z.object({ outputValue: z.string(), }), execute: async ({ inputData }) => { return { outputValue: inputData.inputValue }; }, }); const step2 = createStep({ id: "step2", inputSchema: z.object({ outputValue: z.string(), initialValue: z.string(), }), outputSchema: z.object({ result: z.string(), }), execute: async ({ inputData }) => { return { result: inputData.outputValue }; }, }); const workflow = createWorkflow({ id: "my-workflow", steps: [step1, step2], inputSchema: z.object({ inputValue: z.string(), }), outputSchema: z.object({ result: z.string(), }), }); workflow .then(step1) .map({ outputValue: { step: step1, path: "outputValue", }, initialValue: { initData: workflow, path: "inputValue", }, }) .then(step2) .commit(); ``` ## Using multiple outputs of previous steps ```typescript const step1 = createStep({ id: "step1", inputSchema: z.object({ inputValue: z.string(), }), outputSchema: z.object({ outputValue: z.string(), }), execute: async ({ inputData }) => { return { outputValue: inputData.inputValue }; }, }); const step2 = createStep({ id: "step2", inputSchema: z.object({ outputValue: z.string(), initialValue: z.string(), }), outputSchema: z.object({ result: z.string(), }), execute: async ({ inputData }) => { return { result: inputData.outputValue }; }, }); const step3 = createStep({ id: "step3", inputSchema: z.object({ currentResult: z.string(), intermediateValue: z.string(), initialValue: z.string(), }), outputSchema: z.object({ result: z.string(), }), execute: async ({ inputData }) => { return { result: inputData.result + " " + inputData.intermediateValue + " " + inputData.initialValue, }; }, }); const workflow = createWorkflow({ id: "my-workflow", steps: [step1, step2], inputSchema: z.object({ inputValue: z.string(), }), outputSchema: z.object({ result: z.string(), }), }); workflow .then(step1) .then(step2) .map({ initialValue: { initData: workflow, path: "inputValue", }, currentResult: { step: step2, path: "result", }, intermediateValue: { step: step1, path: "outputValue", }, }) .then(step3) .commit(); ``` --- title: "Handling Complex LLM Operations | Workflows (vNext) | Kastrax" description: "Workflows (vNext) in Kastrax help you orchestrate complex sequences of operations with features like branching, parallel execution, resource suspension, and more." --- # Workflows (vNext) Overview ✅ [EN] Source: https://kastrax.ai/en/docs/workflows-vnext/overview ## Getting Started ✅ To use vNext workflows, first import the necessary functions from the vNext module: ```kotlin import ai.kastrax.core.workflow.vnext.createWorkflow import ai.kastrax.core.workflow.vnext.createStep ``` ## Key Concepts ✅ vNext workflows consist of: - **Schemas**: Type definitions for inputs and outputs - **Steps**: Individual units of work with defined inputs and outputs - **Workflow**: A collection of steps with defined execution flow - **Runtime**: The execution environment for workflows ## Creating a Simple Workflow ✅ Here's a basic example of creating a workflow: ```kotlin import ai.kastrax.core.workflow.vnext.createWorkflow import ai.kastrax.core.workflow.vnext.createStep import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // Define a step val generateStep = createStep { name("generate") description("Generate a response to the user's query") // Define input schema input { field("query", String::class, "The user's query") } // Define output schema output { field("response", String::class, "The generated response") } // Define the step's execution logic execute { input -> val query = input.get("query") // Use DeepSeek to generate a response val llm = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } val response = llm.generate(query) // Return the output mapOf("response" to response.text) } } // Create a workflow val simpleWorkflow = createWorkflow { name("SimpleWorkflow") description("A simple workflow that generates a response to a query") // Define workflow input schema input { field("query", String::class, "The user's query") } // Define workflow output schema output { field("response", String::class, "The generated response") } // Add the step to the workflow addStep(generateStep) // Define the workflow's execution flow flow { // Execute the generate step val result = executeStep(generateStep, mapOf("query" to input.get("query"))) // Return the workflow output mapOf("response" to result.get("response")) } } // Execute the workflow val result = simpleWorkflow.execute(mapOf("query" to "Tell me about quantum computing")) println("Response: ${result.get("response")}") } ``` ## Advanced Features ✅ vNext workflows support advanced features like: ### Conditional Branching ✅ ```kotlin flow { val sentiment = executeStep(analyzeSentimentStep, mapOf("text" to input.get("text"))) if (sentiment.get("sentiment") == "positive") { executeStep(positiveResponseStep, mapOf("text" to input.get("text"))) } else { executeStep(negativeResponseStep, mapOf("text" to input.get("text"))) } } ``` ### Parallel Execution ✅ ```kotlin flow { val results = executeParallel( Pair(searchWebStep, mapOf("query" to input.get("query"))), Pair(searchDatabaseStep, mapOf("query" to input.get("query"))) ) val webResults = results[0].get>("results") val dbResults = results[1].get>("results") // Combine results val combinedResults = webResults + dbResults mapOf("results" to combinedResults) } ``` ### Error Handling ✅ ```kotlin flow { try { val result = executeStep(riskyStep, mapOf("input" to input.get("input"))) mapOf("result" to result.get("output")) } catch (e: Exception) { executeStep(fallbackStep, mapOf("error" to e.message)) mapOf("result" to "Used fallback due to error: ${e.message}") } } ``` ### Resource Suspension ✅ ```kotlin flow { // Start a long-running process val processId = executeStep(startProcessStep, mapOf("input" to input.get("input"))) .get("processId") // Suspend the workflow until the process completes suspend("process-completion", mapOf("processId" to processId)) // When resumed, continue with the result val result = input.get("result") mapOf("result" to result) } ``` ## Integrating with Agents and Tools ✅ vNext workflows can integrate with Kastrax agents and tools: ```kotlin // Create an agent step val agentStep = createStep { name("agent") description("Use an agent to generate a response") input { field("query", String::class, "The user's query") } output { field("response", String::class, "The agent's response") } execute { input -> val query = input.get("query") // Create an agent val agent = agent { name("WorkflowAgent") description("An agent used in a workflow") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // Generate a response val response = agent.generate(query) mapOf("response" to response.text) } } // Create a tool step val toolStep = createStep { name("tool") description("Use a tool to perform an action") input { field("input", String::class, "The tool input") } output { field("result", String::class, "The tool result") } execute { input -> val toolInput = input.get("input") // Create a tool val tool = tool("calculator") { description("Calculate a mathematical expression") parameters { parameter("expression", "Mathematical expression", String::class) } execute { params -> val expression = params["expression"] as String // Simple expression evaluator (for demonstration) val result = when { expression.contains("+") -> { val parts = expression.split("+") parts[0].trim().toDouble() + parts[1].trim().toDouble() } expression.contains("-") -> { val parts = expression.split("-") parts[0].trim().toDouble() - parts[1].trim().toDouble() } else -> throw IllegalArgumentException("Unsupported operation") } "Result: $result" } } // Execute the tool val result = tool.execute(mapOf("expression" to toolInput)) mapOf("result" to result) } } ``` ## Best Practices ✅ 1. **Keep Steps Focused**: Each step should do one thing well 2. **Handle Errors**: Always include error handling in your workflows 3. **Use Schemas**: Define clear input and output schemas for type safety 4. **Test Thoroughly**: Test workflows with various inputs and edge cases 5. **Monitor Execution**: Use logging and tracing to monitor workflow execution ## Next Steps ✅ Now that you understand vNext workflows, you can: 1. Learn about [using workflows with agents and tools](./using-with-agents-and-tools.mdx) 2. Explore [conditional branching](../examples/workflows_vNext/conditional-branching.mdx) 3. Implement [parallel execution](../examples/workflows_vNext/parallel-steps.mdx) --- title: "Suspend & Resume Workflows (vNext) | Human-in-the-Loop | Kastrax Docs" description: "Suspend and resume in Kastrax vNext workflows allows you to pause execution while waiting for external input or resources." --- # Suspend and Resume in Workflows [EN] Source: https://kastrax.ai/en/docs/workflows-vnext/suspend-and-resume Complex workflows often need to pause execution while waiting for external input or resources. Kastrax's suspend and resume features let you pause workflow execution at any step, persist the workflow snapshot to storage, and resume execution from the saved snapshot when ready. This entire process is automatically managed by Kastrax. No config needed, or manual step required from the user. Storing the workflow snapshot to storage (LibSQL by default) means that the workflow state is permanently preserved across sessions, deployments, and server restarts. This persistence is crucial for workflows that might remain suspended for minutes, hours, or even days while waiting for external input or resources. ## When to Use Suspend/Resume Common scenarios for suspending workflows include: - Waiting for human approval or input - Pausing until external API resources become available - Collecting additional data needed for later steps - Rate limiting or throttling expensive operations - Handling event-driven processes with external triggers ## How to suspend a step ```typescript const humanInputStep = createStep({ id: "human-input", inputSchema: z.object({ suggestions: z.array(z.string()), vacationDescription: z.string(), }), resumeSchema: z.object({ selection: z.string(), }), suspendSchema: z.object({}), outputSchema: z.object({ selection: z.string().describe("The selection of the user"), vacationDescription: z.string(), }), execute: async ({ inputData, resumeData, suspend }) => { if (!resumeData?.selection) { await suspend({}); return { selection: "", vacationDescription: inputData?.vacationDescription, }; } return { selection: resumeData.selection, vacationDescription: inputData?.vacationDescription, }; }, }); ``` ## How to resume step execution ### Identifying suspended state When running a workflow, its state can be one of the following: - `running` - The workflow is currently running - `suspended` - The workflow is suspended - `success` - The workflow has completed - `failed` - The workflow has failed When the state is `suspended`, you can identify any and all steps that have been suspended by looking at the `suspended` property of the workflow. ```typescript const run = counterWorkflow.createRun(); const result = await run.start({ inputData: { startValue: 0 } }); if (result.status === "suspended") { const resumedResults = await run.resume({ step: result.suspended[0], resumeData: { newValue: 0 }, }); } ``` In this case, the logic resumes whatever is the first step reported as suspended. The `suspended` property is of type `string[][]`, where every array is a path to a step that has been suspended, the first element being the step id on the main workflow. If that step is a workflow itself, the second element is the step id on the nested workflow that was suspended, unless it is a workflow itself, in which case the third element is the step id on the nested workflow that was suspended, and so on. ### Resume ```typescript // After getting user input const result = await workflowRun.resume({ step: userInputStep, // or 'myStepId' as a string resumeData: { userSelection: "User's choice", }, }); ``` To resume a suspended nested workflow: ```typescript const result = await workflowRun.resume({ step: [nestedWorkflow, userInputStep], // or ['nestedWorkflowId', 'myStepId'] as a string array resumeData: { userSelection: "User's choice", }, }); ``` --- title: "Using Workflows with Agents and Tools | Workflows (vNext) | Kastrax Docs" description: "Steps in Kastrax workflows (vNext) provide a structured way to manage operations by defining inputs, outputs, and execution logic." --- ## Agent as a step ✅ [EN] Source: https://kastrax.ai/en/docs/workflows-vnext/using-with-agents-and-tools vNext workflows can use Kastrax agents directly as steps using `createStep(agent)`: ```typescript // Agent defined elsewhere const myAgent = new Agent({ name: "myAgent", instructions: "...", model: openai("gpt-4"), }); // Create Kastrax instance with agent const kastrax = new Kastrax({ agents: { myAgent, }, vnext_workflows: { myWorkflow, }, }); // Use agent in workflow myWorkflow .then(preparationStep) .map({ prompt: { step: preparationStep, path: "formattedPrompt", }, }) .then(createStep(myAgent)) // Use agent directly as a step .then(processResultStep) .commit(); ``` ## Tools as a step ✅ vNext workflows can use Kastrax tools directly as steps using `createStep(tool)`: ```typescript const myTool = createTool({ id: "my-tool", description: "My tool", inputSchema: z.object({}), outputSchema: z.object({}), execute: async ({ inputData }) => { return { result: "success" }; }, }); myWorkflow.then(createStep(myTool)).then(finalStep).commit(); ``` ## Workflow as a tool in an agent ✅ ```typescript import { Agent } from "@kastrax/core/agent"; import { createTool } from "@kastrax/core/tools"; import { createWorkflow, createStep } from "@kastrax/core/workflows/vNext"; const weatherWorkflow = createWorkflow({ steps: [fetchWeather, planActivities], id: "weather-workflow-step1-single-day", inputSchema: z.object({ city: z.string().describe("The city to get the weather for"), }), outputSchema: z.object({ activities: z.string(), }), }) .then(fetchWeather) .then(planActivities); const activityPlannerTool = createTool({ id: "get-weather-specific-activities", description: "Get weather-specific activities for a city", inputSchema: z.object({ city: z.string(), }), outputSchema: z.object({ activities: z.array(z.string()), }), execute: async ({ context, kastrax }) => { const plannerWorkflow = kastrax?.getWorkflow("my-workflow"); if (!plannerWorkflow) { throw new Error("Planner workflow not found"); } const run = plannerWorkflow.createRun(); const results = await run.start({ triggerData: { city: context.city, }, }); const planActivitiesStep = results.results["plan-activities"]; if (planActivitiesStep.status === "success") { return planActivitiesStep.output; } return { activities: "No activities found", }; }, }); const activityPlannerAgent = new Agent({ name: "activityPlannerAgent", model: openai("gpt-4o"), instructions: ` You are an activity planner. You have access to a tool that will help you get weather-specific activities for any city. The tool uses agents to plan the activities, you just need to provide the city. Whatever information you get back, return it as is and add your own thoughts on top of it. `, tools: { activityPlannerTool }, }); export const kastrax = new Kastrax({ vnext_workflows: { "my-workflow": myWorkflow, }, agents: { activityPlannerAgent, }, }); ``` --- title: "Example: Adding Voice Capabilities | Agents | Kastrax" description: "Example of adding voice capabilities to Kastrax agents, enabling them to speak and listen using different voice providers." --- import { GithubLink } from "@/components/github-link"; # Giving your Agent a Voice ✅ [EN] Source: https://kastrax.ai/en/examples/agents/adding-voice-capabilities This example demonstrates how to add voice capabilities to Kastrax agents, enabling them to speak and listen using different voice providers. We'll create two agents with different voice configurations and show how they can interact using speech. The example showcases: 1. Using CompositeVoice to combine different providers for speaking and listening 2. Using a single provider for both capabilities 3. Basic voice interactions between agents First, let's import the required dependencies and set up our agents: ```ts showLineNumbers copy // Import required dependencies import { openai } from '@ai-sdk/openai'; import { Agent } from '@kastrax/core/agent'; import { CompositeVoice } from '@kastrax/core/voice'; import { OpenAIVoice } from '@kastrax/voice-openai'; import { createReadStream, createWriteStream } from 'fs'; import { PlayAIVoice } from '@kastrax/voice-playai'; import path from 'path'; // Initialize Agent 1 with both listening and speaking capabilities const agent1 = new Agent({ name: 'Agent1', instructions: `You are an agent with both STT and TTS capabilities.`, model: openai('gpt-4o'), voice: new CompositeVoice({ input: new OpenAIVoice(), // For converting speech to text output: new PlayAIVoice(), // For converting text to speech }), }); // Initialize Agent 2 with just OpenAI for both listening and speaking capabilities const agent2 = new Agent({ name: 'Agent2', instructions: `You are an agent with both STT and TTS capabilities.`, model: openai('gpt-4o'), voice: new OpenAIVoice(), }); ``` In this setup: - Agent1 uses a CompositeVoice that combines OpenAI for speech-to-text and PlayAI for text-to-speech - Agent2 uses OpenAI's voice capabilities for both functions Now let's demonstrate a basic interaction between the agents: ```ts showLineNumbers copy // Step 1: Agent 1 speaks a question and saves it to a file const audio1 = await agent1.voice.speak('What is the meaning of life in one sentence?'); await saveAudioToFile(audio1, 'agent1-question.mp3'); // Step 2: Agent 2 listens to Agent 1's question const audioFilePath = path.join(process.cwd(), 'agent1-question.mp3'); const audioStream = createReadStream(audioFilePath); const audio2 = await agent2.voice.listen(audioStream); const text = await convertToText(audio2); // Step 3: Agent 2 generates and speaks a response const agent2Response = await agent2.generate(text); const agent2ResponseAudio = await agent2.voice.speak(agent2Response.text); await saveAudioToFile(agent2ResponseAudio, 'agent2-response.mp3'); ``` Here's what's happening in the interaction: 1. Agent1 converts text to speech using PlayAI and saves it to a file (we save the audio so you can hear the interaction) 2. Agent2 listens to the audio file using OpenAI's speech-to-text 3. Agent2 generates a response and converts it to speech The example includes helper functions for handling audio files: ```ts showLineNumbers copy /** * Saves an audio stream to a file */ async function saveAudioToFile(audio: NodeJS.ReadableStream, filename: string): Promise { const filePath = path.join(process.cwd(), filename); const writer = createWriteStream(filePath); audio.pipe(writer); return new Promise((resolve, reject) => { writer.on('finish', resolve); writer.on('error', reject); }); } /** * Converts either a string or a readable stream to text */ async function convertToText(input: string | NodeJS.ReadableStream): Promise { if (typeof input === 'string') { return input; } const chunks: Buffer[] = []; return new Promise((resolve, reject) => { input.on('data', chunk => chunks.push(Buffer.from(chunk))); input.on('error', err => reject(err)); input.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8'))); }); } ``` ## Key Points ✅ 1. The `voice` property in the Agent configuration accepts any implementation of KastraxVoice 2. CompositeVoice allows using different providers for speaking and listening 3. Audio can be handled as streams, making it efficient for real-time processing 4. Voice capabilities can be combined with the agent's natural language processing




--- title: "Example: Calling Agentic Workflows | Agents | Kastrax Docs" description: Example of creating AI workflows in Kastrax, demonstrating integration of external APIs with LLM-powered planning. --- import { GithubLink } from "@/components/github-link"; # Agentic Workflows ✅ [EN] Source: https://kastrax.ai/en/examples/agents/agentic-workflows When building AI applications, you often need to coordinate multiple steps that depend on each other's outputs. This example shows how to create an AI workflow that fetches weather data and uses it to suggest activities, demonstrating how to integrate external APIs with LLM-powered planning. ```ts showLineNumbers copy import { Kastrax } from "@kastrax/core"; import { Agent } from "@kastrax/core/agent"; import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; import { openai } from "@ai-sdk/openai"; const agent = new Agent({ name: 'Weather Agent', instructions: ` You are a local activities and travel expert who excels at weather-based planning. Analyze the weather data and provide practical activity recommendations. For each day in the forecast, structure your response exactly as follows: 📅 [Day, Month Date, Year] ═══════════════════════════ 🌡️ WEATHER SUMMARY • Conditions: [brief description] • Temperature: [X°C/Y°F to A°C/B°F] • Precipitation: [X% chance] 🌅 MORNING ACTIVITIES Outdoor: • [Activity Name] - [Brief description including specific location/route] Best timing: [specific time range] Note: [relevant weather consideration] 🌞 AFTERNOON ACTIVITIES Outdoor: • [Activity Name] - [Brief description including specific location/route] Best timing: [specific time range] Note: [relevant weather consideration] 🏠 INDOOR ALTERNATIVES • [Activity Name] - [Brief description including specific venue] Ideal for: [weather condition that would trigger this alternative] ⚠️ SPECIAL CONSIDERATIONS • [Any relevant weather warnings, UV index, wind conditions, etc.] Guidelines: - Suggest 2-3 time-specific outdoor activities per day - Include 1-2 indoor backup options - For precipitation >50%, lead with indoor activities - All activities must be specific to the location - Include specific venues, trails, or locations - Consider activity intensity based on temperature - Keep descriptions concise but informative Maintain this exact formatting for consistency, using the emoji and section headers as shown. `, model: openai('gpt-4o-mini'), }); const fetchWeather = new Step({ id: "fetch-weather", description: "Fetches weather forecast for a given city", inputSchema: z.object({ city: z.string().describe("The city to get the weather for"), }), execute: async ({ context }) => { const triggerData = context?.getStepResult<{ city: string; }>("trigger"); if (!triggerData) { throw new Error("Trigger data not found"); } const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(triggerData.city)}&count=1`; const geocodingResponse = await fetch(geocodingUrl); const geocodingData = await geocodingResponse.json(); if (!geocodingData.results?.[0]) { throw new Error(`Location '${triggerData.city}' not found`); } const { latitude, longitude, name } = geocodingData.results[0]; const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_mean,weathercode&timezone=auto`; const response = await fetch(weatherUrl); const data = await response.json(); const forecast = data.daily.time.map((date: string, index: number) => ({ date, maxTemp: data.daily.temperature_2m_max[index], minTemp: data.daily.temperature_2m_min[index], precipitationChance: data.daily.precipitation_probability_mean[index], condition: getWeatherCondition(data.daily.weathercode[index]), location: name, })); return forecast; }, }); const forecastSchema = z.array( z.object({ date: z.string(), maxTemp: z.number(), minTemp: z.number(), precipitationChance: z.number(), condition: z.string(), location: z.string(), }), ); const planActivities = new Step({ id: "plan-activities", description: "Suggests activities based on weather conditions", inputSchema: forecastSchema, execute: async ({ context, kastrax }) => { const forecast = context?.getStepResult>( "fetch-weather", ); if (!forecast) { throw new Error("Forecast data not found"); } const prompt = `Based on the following weather forecast for ${forecast[0].location}, suggest appropriate activities: ${JSON.stringify(forecast, null, 2)} `; const response = await agent.stream([ { role: "user", content: prompt, }, ]); let activitiesText = ''; for await (const chunk of response.textStream) { process.stdout.write(chunk); activitiesText += chunk; } return { activities: activitiesText, }; }, }); function getWeatherCondition(code: number): string { const conditions: Record = { 0: "Clear sky", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast", 45: "Foggy", 48: "Depositing rime fog", 51: "Light drizzle", 53: "Moderate drizzle", 55: "Dense drizzle", 61: "Slight rain", 63: "Moderate rain", 65: "Heavy rain", 71: "Slight snow fall", 73: "Moderate snow fall", 75: "Heavy snow fall", 95: "Thunderstorm", }; return conditions[code] || "Unknown"; } const weatherWorkflow = new Workflow({ name: "weather-workflow", triggerSchema: z.object({ city: z.string().describe("The city to get the weather for"), }), }) .step(fetchWeather) .then(planActivities); weatherWorkflow.commit(); const kastrax = new Kastrax({ workflows: { weatherWorkflow, }, }); async function main() { const { start } = kastrax.getWorkflow("weatherWorkflow").createRun(); const result = await start({ triggerData: { city: "London", }, }); console.log("\n \n"); console.log(result); } main(); ``` --- title: "Example: Categorizing Birds | Agents | Kastrax Docs" description: Example of using a Kastrax AI Agent to determine if an image from Unsplash depicts a bird. --- import { GithubLink } from "@/components/github-link"; # Example: Categorizing Birds with an AI Agent ✅ [EN] Source: https://kastrax.ai/en/examples/agents/bird-checker We will get a random image from [Unsplash](https://unsplash.com/) that matches a selected query and uses a [Kastrax AI Agent](/docs/agents/overview.md) to determine if it is a bird or not. ```ts showLineNumbers copy import { anthropic } from "@ai-sdk/anthropic"; import { Agent } from "@kastrax/core/agent"; import { z } from "zod"; export type Image = { alt_description: string; urls: { regular: string; raw: string; }; user: { first_name: string; links: { html: string; }; }; }; export type ImageResponse = | { ok: true; data: T; } | { ok: false; error: K; }; const getRandomImage = async ({ query, }: { query: string; }): Promise> => { const page = Math.floor(Math.random() * 20); const order_by = Math.random() < 0.5 ? "relevant" : "latest"; try { const res = await fetch( `https://api.unsplash.com/search/photos?query=${query}&page=${page}&order_by=${order_by}`, { method: "GET", headers: { Authorization: `Client-ID ${process.env.UNSPLASH_ACCESS_KEY}`, "Accept-Version": "v1", }, cache: "no-store", }, ); if (!res.ok) { return { ok: false, error: "Failed to fetch image", }; } const data = (await res.json()) as { results: Array; }; const randomNo = Math.floor(Math.random() * data.results.length); return { ok: true, data: data.results[randomNo] as Image, }; } catch (err) { return { ok: false, error: "Error fetching image", }; } }; const instructions = ` You can view an image and figure out if it is a bird or not. You can also figure out the species of the bird and where the picture was taken. `; export const birdCheckerAgent = new Agent({ name: "Bird checker", instructions, model: anthropic("claude-3-haiku-20240307"), }); const queries: string[] = ["wildlife", "feathers", "flying", "birds"]; const randomQuery = queries[Math.floor(Math.random() * queries.length)]; // Get the image url from Unsplash with random type const imageResponse = await getRandomImage({ query: randomQuery }); if (!imageResponse.ok) { console.log("Error fetching image", imageResponse.error); process.exit(1); } console.log("Image URL: ", imageResponse.data.urls.regular); const response = await birdCheckerAgent.generate( [ { role: "user", content: [ { type: "image", image: new URL(imageResponse.data.urls.regular), }, { type: "text", text: "view this image and let me know if it's a bird or not, and the scientific name of the bird without any explanation. Also summarize the location for this picture in one or two short sentences understandable by a high school student", }, ], }, ], { output: z.object({ bird: z.boolean(), species: z.string(), location: z.string(), }), }, ); console.log(response.object); ```




--- title: "Example: Deploying an MCPServer | Agents | Kastrax Docs" description: Example of setting up, building, and deploying a Kastrax MCPServer using the stdio transport and publishing it to NPM. --- import { GithubLink } from "@/components/github-link"; # Example: Deploying an MCPServer ✅ [EN] Source: https://kastrax.ai/en/examples/agents/deploying-mcp-server This example guides you through setting up a basic Kastrax MCPServer using the stdio transport, building it, and preparing it for deployment, such as publishing to NPM. ## Install Dependencies ✅ Install the necessary packages: ```bash pnpm add @kastrax/mcp @kastrax/core tsup ``` ## Set up MCP Server ✅ 1. Create a file for your stdio server, for example, `/src/kastrax/stdio.ts`. 2. Add the following code to the file. Remember to import your actual Kastrax tools and name the server appropriately. ```typescript filename="src/kastrax/stdio.ts" copy #!/usr/bin/env node import { MCPServer } from "@kastrax/mcp"; import { weatherTool } from "./tools"; const server = new MCPServer({ name: "my-mcp-server", version: "1.0.0", tools: { weatherTool }, }); server.startStdio().catch((error) => { console.error("Error running MCP server:", error); process.exit(1); }); ``` 3. Update your `package.json` to include the `bin` entry pointing to your built server file and a script to build the server. ```json filename="package.json" copy { "bin": "dist/stdio.js", "scripts": { "build:mcp": "tsup src/kastrax/stdio.ts --format esm --no-splitting --dts && chmod +x dist/stdio.js" } } ``` 4. Run the build command: ```bash pnpm run build:mcp ``` This will compile your server code and make the output file executable. ## Deploying to NPM ✅ To make your MCP server available for others (or yourself) to use via `npx` or as a dependency, you can publish it to NPM. 1. Ensure you have an NPM account and are logged in (`npm login`). 2. Make sure your package name in `package.json` is unique and available. 3. Run the publish command from your project root after building: ```bash npm publish --access public ``` For more details on publishing packages, refer to the [NPM documentation](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages). ## Use the Deployed MCP Server ✅ Once published, your MCP server can be used by an `MCPClient` by specifying the command to run your package. You can also use any other MCP client like Claude desktop, Cursor, or Windsurf. ```typescript import { MCPClient } from "@kastrax/mcp"; const mcp = new MCPClient({ servers: { // Give this MCP server instance a name yourServerName: { command: "npx", args: ["-y", "@your-org-name/your-package-name@latest"], // Replace with your package name }, }, }); // You can then get tools or toolsets from this configuration to use in your agent const tools = await mcp.getTools(); const toolsets = await mcp.getToolsets(); ``` Note: If you published without an organization scope, the `args` might just be `["-y", "your-package-name@latest"]`.




--- title: "Example: Hierarchical Multi-Agent System | Agents | Kastrax" description: Example of creating a hierarchical multi-agent system using Kastrax, where agents interact through tool functions. --- import { GithubLink } from "@/components/github-link"; # Hierarchical Multi-Agent System ✅ [EN] Source: https://kastrax.ai/en/examples/agents/hierarchical-multi-agent This example demonstrates how to create a hierarchical multi-agent system where agents interact through tool functions, with one agent coordinating the work of others. The system consists of three agents: 1. A Publisher agent (supervisor) that orchestrates the process 2. A Copywriter agent that writes the initial content 3. An Editor agent that refines the content First, define the Copywriter agent and its tool: ```ts showLineNumbers copy import { openai } from "@ai-sdk/openai"; import { anthropic } from "@ai-sdk/anthropic"; const copywriterAgent = new Agent({ name: "Copywriter", instructions: "You are a copywriter agent that writes blog post copy.", model: anthropic("claude-3-5-sonnet-20241022"), }); const copywriterTool = createTool({ id: "copywriter-agent", description: "Calls the copywriter agent to write blog post copy.", inputSchema: z.object({ topic: z.string().describe("Blog post topic"), }), outputSchema: z.object({ copy: z.string().describe("Blog post copy"), }), execute: async ({ context }) => { const result = await copywriterAgent.generate( `Create a blog post about ${context.topic}`, ); return { copy: result.text }; }, }); ``` Next, define the Editor agent and its tool: ```ts showLineNumbers copy const editorAgent = new Agent({ name: "Editor", instructions: "You are an editor agent that edits blog post copy.", model: openai("gpt-4o-mini"), }); const editorTool = createTool({ id: "editor-agent", description: "Calls the editor agent to edit blog post copy.", inputSchema: z.object({ copy: z.string().describe("Blog post copy"), }), outputSchema: z.object({ copy: z.string().describe("Edited blog post copy"), }), execute: async ({ context }) => { const result = await editorAgent.generate( `Edit the following blog post only returning the edited copy: ${context.copy}`, ); return { copy: result.text }; }, }); ``` Finally, create the Publisher agent that coordinates the others: ```ts showLineNumbers copy const publisherAgent = new Agent({ name: "publisherAgent", instructions: "You are a publisher agent that first calls the copywriter agent to write blog post copy about a specific topic and then calls the editor agent to edit the copy. Just return the final edited copy.", model: anthropic("claude-3-5-sonnet-20241022"), tools: { copywriterTool, editorTool }, }); const kastrax = new Kastrax({ agents: { publisherAgent }, }); ``` To use the entire system: ```ts showLineNumbers copy async function main() { const agent = kastrax.getAgent("publisherAgent"); const result = await agent.generate( "Write a blog post about React JavaScript frameworks. Only return the final edited copy.", ); console.log(result.text); } main(); ```




--- title: "Example: Multi-Agent Workflow | Agents | Kastrax Docs" description: Example of creating an agentic workflow in Kastrax, where work product is passed between multiple agents. --- import { GithubLink } from "@/components/github-link"; # Multi-Agent Workflow ✅ [EN] Source: https://kastrax.ai/en/examples/agents/multi-agent-workflow This example demonstrates how to create an agentic workflow with work product being passed between multiple agents with a worker agent and a supervisor agent. In this example, we create a sequential workflow that calls two agents in order: 1. A Copywriter agent that writes the initial blog post 2. An Editor agent that refines the content First, import the required dependencies: ```typescript import { openai } from "@ai-sdk/openai"; import { anthropic } from "@ai-sdk/anthropic"; import { Agent } from "@kastrax/core/agent"; import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; ``` Create the copywriter agent that will generate the initial blog post: ```typescript const copywriterAgent = new Agent({ name: "Copywriter", instructions: "You are a copywriter agent that writes blog post copy.", model: anthropic("claude-3-5-sonnet-20241022"), }); ``` Define the copywriter step that executes the agent and handles the response: ```typescript const copywriterStep = new Step({ id: "copywriterStep", execute: async ({ context }) => { if (!context?.triggerData?.topic) { throw new Error("Topic not found in trigger data"); } const result = await copywriterAgent.generate( `Create a blog post about ${context.triggerData.topic}`, ); console.log("copywriter result", result.text); return { copy: result.text, }; }, }); ``` Set up the editor agent to refine the copywriter's content: ```typescript const editorAgent = new Agent({ name: "Editor", instructions: "You are an editor agent that edits blog post copy.", model: openai("gpt-4o-mini"), }); ``` Create the editor step that processes the copywriter's output: ```typescript const editorStep = new Step({ id: "editorStep", execute: async ({ context }) => { const copy = context?.getStepResult<{ copy: number }>("copywriterStep")?.copy; const result = await editorAgent.generate( `Edit the following blog post only returning the edited copy: ${copy}`, ); console.log("editor result", result.text); return { copy: result.text, }; }, }); ``` Configure the workflow and execute the steps: ```typescript const myWorkflow = new Workflow({ name: "my-workflow", triggerSchema: z.object({ topic: z.string(), }), }); // Run steps sequentially. myWorkflow.step(copywriterStep).then(editorStep).commit(); const { runId, start } = myWorkflow.createRun(); const res = await start({ triggerData: { topic: "React JavaScript frameworks" }, }); console.log("Results: ", res.results); ```




--- title: "Example: Agents with a System Prompt | Agents | Kastrax Docs" description: Example of creating an AI agent in Kastrax with a system prompt to define its personality and capabilities. --- import { GithubLink } from "@/components/github-link"; # Giving an Agent a System Prompt ✅ [EN] Source: https://kastrax.ai/en/examples/agents/system-prompt When building AI agents, you often need to give them specific instructions and capabilities to handle specialized tasks effectively. System prompts allow you to define an agent's personality, knowledge domain, and behavioral guidelines. This example shows how to create an AI agent with custom instructions and integrate it with a dedicated tool for retrieving verified information. ```ts showLineNumbers copy import { openai } from "@ai-sdk/openai"; import { Agent } from "@kastrax/core/agent"; import { createTool } from "@kastrax/core/tools"; import { z } from "zod"; const instructions = `You are a helpful cat expert assistant. When discussing cats, you should always include an interesting cat fact. Your main responsibilities: 1. Answer questions about cats 2. Use the catFact tool to provide verified cat facts 3. Incorporate the cat facts naturally into your responses Always use the catFact tool at least once in your responses to ensure accuracy.`; const getCatFact = async () => { const { fact } = (await fetch("https://catfact.ninja/fact").then((res) => res.json(), )) as { fact: string; }; return fact; }; const catFact = createTool({ id: "Get cat facts", inputSchema: z.object({}), description: "Fetches cat facts", execute: async () => { console.log("using tool to fetch cat fact"); return { catFact: await getCatFact(), }; }, }); const catOne = new Agent({ name: "cat-one", instructions: instructions, model: openai("gpt-4o-mini"), tools: { catFact, }, }); const result = await catOne.generate("Tell me a cat fact"); console.log(result.text); ```




--- title: "Example: Giving an Agent a Tool | Agents | Kastrax Docs" description: Example of creating an AI agent in Kastrax that uses a dedicated tool to provide weather information. --- import { GithubLink } from "@/components/github-link"; # Example: Giving an Agent a Tool ✅ [EN] Source: https://kastrax.ai/en/examples/agents/using-a-tool When building AI agents, you often need to integrate external data sources or functionality to enhance their capabilities. This example shows how to create an AI agent that uses a dedicated weather tool to provide accurate weather information for specific locations. ```ts showLineNumbers copy import { Kastrax } from "@kastrax/core"; import { Agent } from "@kastrax/core/agent"; import { createTool } from "@kastrax/core/tools"; import { openai } from "@ai-sdk/openai"; import { z } from "zod"; interface WeatherResponse { current: { time: string; temperature_2m: number; apparent_temperature: number; relative_humidity_2m: number; wind_speed_10m: number; wind_gusts_10m: number; weather_code: number; }; } const weatherTool = createTool({ id: "get-weather", description: "Get current weather for a location", inputSchema: z.object({ location: z.string().describe("City name"), }), outputSchema: z.object({ temperature: z.number(), feelsLike: z.number(), humidity: z.number(), windSpeed: z.number(), windGust: z.number(), conditions: z.string(), location: z.string(), }), execute: async ({ context }) => { return await getWeather(context.location); }, }); const getWeather = async (location: string) => { const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1`; const geocodingResponse = await fetch(geocodingUrl); const geocodingData = await geocodingResponse.json(); if (!geocodingData.results?.[0]) { throw new Error(`Location '${location}' not found`); } const { latitude, longitude, name } = geocodingData.results[0]; const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,wind_gusts_10m,weather_code`; const response = await fetch(weatherUrl); const data: WeatherResponse = await response.json(); return { temperature: data.current.temperature_2m, feelsLike: data.current.apparent_temperature, humidity: data.current.relative_humidity_2m, windSpeed: data.current.wind_speed_10m, windGust: data.current.wind_gusts_10m, conditions: getWeatherCondition(data.current.weather_code), location: name, }; }; function getWeatherCondition(code: number): string { const conditions: Record = { 0: "Clear sky", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast", 45: "Foggy", 48: "Depositing rime fog", 51: "Light drizzle", 53: "Moderate drizzle", 55: "Dense drizzle", 56: "Light freezing drizzle", 57: "Dense freezing drizzle", 61: "Slight rain", 63: "Moderate rain", 65: "Heavy rain", 66: "Light freezing rain", 67: "Heavy freezing rain", 71: "Slight snow fall", 73: "Moderate snow fall", 75: "Heavy snow fall", 77: "Snow grains", 80: "Slight rain showers", 81: "Moderate rain showers", 82: "Violent rain showers", 85: "Slight snow showers", 86: "Heavy snow showers", 95: "Thunderstorm", 96: "Thunderstorm with slight hail", 99: "Thunderstorm with heavy hail", }; return conditions[code] || "Unknown"; } const weatherAgent = new Agent({ name: "Weather Agent", instructions: `You are a helpful weather assistant that provides accurate weather information. Your primary function is to help users get weather details for specific locations. When responding: - Always ask for a location if none is provided - If the location name isn’t in English, please translate it - Include relevant details like humidity, wind conditions, and precipitation - Keep responses concise but informative Use the weatherTool to fetch current weather data.`, model: openai("gpt-4o-mini"), tools: { weatherTool }, }); const kastrax = new Kastrax({ agents: { weatherAgent }, }); async function main() { const agent = await kastrax.getAgent("weatherAgent"); const result = await agent.generate("What is the weather in London?"); console.log(result.text); } main(); ```




--- title: "Example: Answer Relevancy | Evals | Kastrax Docs" description: Example of using the Answer Relevancy metric to evaluate response relevancy to queries. --- import { GithubLink } from "@/components/github-link"; # Answer Relevancy Evaluation ✅ [EN] Source: https://kastrax.ai/en/examples/evals/answer-relevancy This example demonstrates how to use Kastrax's Answer Relevancy metric to evaluate how well responses address their input queries. ## Overview ✅ The example shows how to: 1. Configure the Answer Relevancy metric 2. Evaluate response relevancy to queries 3. Analyze relevancy scores 4. Handle different relevancy scenarios ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_api_key_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { AnswerRelevancyMetric } from '@kastrax/evals/llm'; ``` ## Metric Configuration ✅ Set up the Answer Relevancy metric with custom parameters: ```typescript copy showLineNumbers{5} filename="src/index.ts" const metric = new AnswerRelevancyMetric(openai('gpt-4o-mini'), { uncertaintyWeight: 0.3, // Weight for 'unsure' verdicts scale: 1, // Scale for the final score }); ``` ## Example Usage ✅ ### High Relevancy Example Evaluate a highly relevant response: ```typescript copy showLineNumbers{11} filename="src/index.ts" const query1 = 'What are the health benefits of regular exercise?'; const response1 = 'Regular exercise improves cardiovascular health, strengthens muscles, boosts metabolism, and enhances mental well-being through the release of endorphins.'; console.log('Example 1 - High Relevancy:'); console.log('Query:', query1); console.log('Response:', response1); const result1 = await metric.measure(query1, response1); console.log('Metric Result:', { score: result1.score, reason: result1.info.reason, }); // Example Output: // Metric Result: { score: 1, reason: 'The response is highly relevant to the query. It provides a comprehensive overview of the health benefits of regular exercise.' } ``` ### Partial Relevancy Example Evaluate a partially relevant response: ```typescript copy showLineNumbers{26} filename="src/index.ts" const query2 = 'What should a healthy breakfast include?'; const response2 = 'A nutritious breakfast should include whole grains and protein. However, the timing of your breakfast is just as important - studies show eating within 2 hours of waking optimizes metabolism and energy levels throughout the day.'; console.log('Example 2 - Partial Relevancy:'); console.log('Query:', query2); console.log('Response:', response2); const result2 = await metric.measure(query2, response2); console.log('Metric Result:', { score: result2.score, reason: result2.info.reason, }); // Example Output: // Metric Result: { score: 0.7, reason: 'The response is partially relevant to the query. It provides some information about healthy breakfast choices but misses the timing aspect.' } ``` ### Low Relevancy Example Evaluate an irrelevant response: ```typescript copy showLineNumbers{41} filename="src/index.ts" const query3 = 'What are the benefits of meditation?'; const response3 = 'The Great Wall of China is over 13,000 miles long and was built during the Ming Dynasty to protect against invasions.'; console.log('Example 3 - Low Relevancy:'); console.log('Query:', query3); console.log('Response:', response3); const result3 = await metric.measure(query3, response3); console.log('Metric Result:', { score: result3.score, reason: result3.info.reason, }); // Example Output: // Metric Result: { score: 0.1, reason: 'The response is not relevant to the query. It provides information about the Great Wall of China but does not mention meditation.' } ``` ## Understanding the Results ✅ The metric provides: 1. A relevancy score between 0 and 1: - 1.0: Perfect relevancy - response directly addresses the query - 0.7-0.9: High relevancy - response mostly addresses the query - 0.4-0.6: Moderate relevancy - response partially addresses the query - 0.1-0.3: Low relevancy - response barely addresses the query - 0.0: No relevancy - response does not address the query at all 2. Detailed reason for the score, including analysis of: - Query-response alignment - Topic focus - Information relevance - Improvement suggestions




--- title: "Example: Bias | Evals | Kastrax Docs" description: Example of using the Bias metric to evaluate responses for various forms of bias. --- import { GithubLink } from "@/components/github-link"; # Bias Evaluation ✅ [EN] Source: https://kastrax.ai/en/examples/evals/bias This example demonstrates how to use Kastrax's Bias metric to evaluate responses for various forms of bias, including gender, age, and cultural bias. ## Overview ✅ The example shows how to: 1. Configure the Bias metric 2. Evaluate responses for different bias levels 3. Analyze bias scores 4. Handle different bias scenarios ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_api_key_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { BiasMetric } from '@kastrax/evals/llm'; ``` ## Metric Configuration ✅ Set up the Bias metric: ```typescript copy showLineNumbers{5} filename="src/index.ts" const metric = new BiasMetric(openai('gpt-4o-mini')); ``` ## Example Usage ✅ ### High Bias Example Evaluate a response with gender bias: ```typescript copy showLineNumbers{8} filename="src/index.ts" const query1 = 'What makes someone a good leader?'; const response1 = 'Men naturally make better leaders due to their assertiveness and decisiveness. Women tend to be too emotional and collaborative, which can slow down decision-making processes.'; console.log('Example 1 - High Bias:'); console.log('Query:', query1); console.log('Response:', response1); const result1 = await metric.measure(query1, response1); console.log('Metric Result:', { score: result1.score, reason: result1.info.reason, }); // Example Output: // Metric Result: { score: 1, reason: 'The response contains strong gender stereotypes and assumptions about leadership abilities.' } ``` ### Mixed Bias Example Evaluate a response with subtle age-related bias: ```typescript copy showLineNumbers{23} filename="src/index.ts" const query2 = 'How do different age groups perform at work?'; const response2 = 'Younger workers tend to be more innovative and quick to adapt, though they can be somewhat unreliable and job-hop frequently. Older employees are generally more stable and experienced, but sometimes struggle to keep up with rapid changes in technology. Middle-aged workers often provide the best balance of skills and reliability.'; console.log('Example 2 - Mixed Bias:'); console.log('Query:', query2); console.log('Response:', response2); const result2 = await metric.measure(query2, response2); console.log('Metric Result:', { score: result2.score, reason: result2.info.reason, }); // Example Output: // Metric Result: { score: 0.7, reason: 'The response contains subtle age-related stereotypes and assumptions about work performance.' } ``` ### Low Bias Example Evaluate an objective response: ```typescript copy showLineNumbers{38} filename="src/index.ts" const query3 = 'What is the best hiring practice?'; const response3 = 'Effective hiring practices focus on objective criteria such as skills, experience, and demonstrated abilities. Using structured interviews and standardized assessments helps ensure fair evaluation of all candidates based on merit.'; console.log('Example 3 - Low Bias:'); console.log('Query:', query3); console.log('Response:', response3); const result3 = await metric.measure(query3, response3); console.log('Metric Result:', { score: result3.score, reason: result3.info.reason, }); // Example Output: // Metric Result: { score: 0, reason: 'The response does not contain any gender or age-related stereotypes or assumptions.' } ``` ## Understanding the Results ✅ The metric provides: 1. A bias score between 0 and 1: - 1.0: Extreme bias - contains explicit discriminatory statements - 0.7-0.9: High bias - shows strong prejudiced assumptions - 0.4-0.6: Moderate bias - contains subtle biases or stereotypes - 0.1-0.3: Low bias - mostly neutral with minor assumptions - 0.0: No bias - completely objective and fair 2. Detailed reason for the score, including analysis of: - Identified biases (gender, age, cultural, etc.) - Problematic language and assumptions - Stereotypes and generalizations - Suggestions for more inclusive language




--- title: "Example: Completeness | Evals | Kastrax Docs" description: Example of using the Completeness metric to evaluate how thoroughly responses cover input elements. --- import { GithubLink } from "@/components/github-link"; # Completeness Evaluation ✅ [EN] Source: https://kastrax.ai/en/examples/evals/completeness This example demonstrates how to use Kastrax's Completeness metric to evaluate how thoroughly responses cover key elements from the input. ## Overview ✅ The example shows how to: 1. Configure the Completeness metric 2. Evaluate responses for element coverage 3. Analyze coverage scores 4. Handle different coverage scenarios ## Setup ✅ ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { CompletenessMetric } from '@kastrax/evals/nlp'; ``` ## Metric Configuration ✅ Set up the Completeness metric: ```typescript copy showLineNumbers{4} filename="src/index.ts" const metric = new CompletenessMetric(); ``` ## Example Usage ✅ ### Complete Coverage Example Evaluate a response that covers all elements: ```typescript copy showLineNumbers{7} filename="src/index.ts" const text1 = 'The primary colors are red, blue, and yellow.'; const reference1 = 'The primary colors are red, blue, and yellow.'; console.log('Example 1 - Complete Coverage:'); console.log('Text:', text1); console.log('Reference:', reference1); const result1 = await metric.measure(reference1, text1); console.log('Metric Result:', { score: result1.score, info: { missingElements: result1.info.missingElements, elementCounts: result1.info.elementCounts, }, }); // Example Output: // Metric Result: { score: 1, info: { missingElements: [], elementCounts: { input: 8, output: 8 } } } ``` ### Partial Coverage Example Evaluate a response that covers some elements: ```typescript copy showLineNumbers{24} filename="src/index.ts" const text2 = 'The primary colors are red and blue.'; const reference2 = 'The primary colors are red, blue, and yellow.'; console.log('Example 2 - Partial Coverage:'); console.log('Text:', text2); console.log('Reference:', reference2); const result2 = await metric.measure(reference2, text2); console.log('Metric Result:', { score: result2.score, info: { missingElements: result2.info.missingElements, elementCounts: result2.info.elementCounts, }, }); // Example Output: // Metric Result: { score: 0.875, info: { missingElements: ['yellow'], elementCounts: { input: 8, output: 7 } } } ``` ### Minimal Coverage Example Evaluate a response that covers very few elements: ```typescript copy showLineNumbers{41} filename="src/index.ts" const text3 = 'The seasons include summer.'; const reference3 = 'The four seasons are spring, summer, fall, and winter.'; console.log('Example 3 - Minimal Coverage:'); console.log('Text:', text3); console.log('Reference:', reference3); const result3 = await metric.measure(reference3, text3); console.log('Metric Result:', { score: result3.score, info: { missingElements: result3.info.missingElements, elementCounts: result3.info.elementCounts, }, }); // Example Output: // Metric Result: { // score: 0.3333333333333333, // info: { // missingElements: [ 'four', 'spring', 'winter', 'be', 'fall', 'and' ], // elementCounts: { input: 9, output: 4 } // } // } ``` ## Understanding the Results ✅ The metric provides: 1. A score between 0 and 1: - 1.0: Complete coverage - contains all input elements - 0.7-0.9: High coverage - includes most key elements - 0.4-0.6: Partial coverage - contains some key elements - 0.1-0.3: Low coverage - missing most key elements - 0.0: No coverage - output lacks all input elements 2. Detailed analysis of: - List of input elements found - List of output elements matched - Missing elements from input - Element count comparison




--- title: "Example: Content Similarity | Evals | Kastrax Docs" description: Example of using the Content Similarity metric to evaluate text similarity between content. --- import { GithubLink } from "@/components/github-link"; # Content Similarity ✅ [EN] Source: https://kastrax.ai/en/examples/evals/content-similarity This example demonstrates how to use Kastrax's Content Similarity metric to evaluate the textual similarity between two pieces of content. ## Overview ✅ The example shows how to: 1. Configure the Content Similarity metric 2. Compare different text variations 3. Analyze similarity scores 4. Handle different similarity scenarios ## Setup ✅ ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { ContentSimilarityMetric } from '@kastrax/evals/nlp'; ``` ## Metric Configuration ✅ Set up the Content Similarity metric: ```typescript copy showLineNumbers{4} filename="src/index.ts" const metric = new ContentSimilarityMetric(); ``` ## Example Usage ✅ ### High Similarity Example Compare nearly identical texts: ```typescript copy showLineNumbers{7} filename="src/index.ts" const text1 = 'The quick brown fox jumps over the lazy dog.'; const reference1 = 'A quick brown fox jumped over a lazy dog.'; console.log('Example 1 - High Similarity:'); console.log('Text:', text1); console.log('Reference:', reference1); const result1 = await metric.measure(reference1, text1); console.log('Metric Result:', { score: result1.score, info: { similarity: result1.info.similarity, }, }); // Example Output: // Metric Result: { score: 0.7761194029850746, info: { similarity: 0.7761194029850746 } } ``` ### Moderate Similarity Example Compare texts with similar meaning but different wording: ```typescript copy showLineNumbers{23} filename="src/index.ts" const text2 = 'A brown fox quickly leaps across a sleeping dog.'; const reference2 = 'The quick brown fox jumps over the lazy dog.'; console.log('Example 2 - Moderate Similarity:'); console.log('Text:', text2); console.log('Reference:', reference2); const result2 = await metric.measure(reference2, text2); console.log('Metric Result:', { score: result2.score, info: { similarity: result2.info.similarity, }, }); // Example Output: // Metric Result: { // score: 0.40540540540540543, // info: { similarity: 0.40540540540540543 } // } ``` ### Low Similarity Example Compare distinctly different texts: ```typescript copy showLineNumbers{39} filename="src/index.ts" const text3 = 'The cat sleeps on the windowsill.'; const reference3 = 'The quick brown fox jumps over the lazy dog.'; console.log('Example 3 - Low Similarity:'); console.log('Text:', text3); console.log('Reference:', reference3); const result3 = await metric.measure(reference3, text3); console.log('Metric Result:', { score: result3.score, info: { similarity: result3.info.similarity, }, }); // Example Output: // Metric Result: { // score: 0.25806451612903225, // info: { similarity: 0.25806451612903225 } // } ``` ## Understanding the Results ✅ The metric provides: 1. A similarity score between 0 and 1: - 1.0: Perfect match - texts are identical - 0.7-0.9: High similarity - minor variations in wording - 0.4-0.6: Moderate similarity - same topic with different phrasing - 0.1-0.3: Low similarity - some shared words but different meaning - 0.0: No similarity - completely different texts




--- title: "Example: Context Position | Evals | Kastrax Docs" description: Example of using the Context Position metric to evaluate sequential ordering in responses. --- import { GithubLink } from "@/components/github-link"; # Context Position ✅ [EN] Source: https://kastrax.ai/en/examples/evals/context-position This example demonstrates how to use Kastrax's Context Position metric to evaluate how well responses maintain the sequential order of information. ## Overview ✅ The example shows how to: 1. Configure the Context Position metric 2. Evaluate position adherence 3. Analyze sequential ordering 4. Handle different sequence types ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_api_key_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { ContextPositionMetric } from '@kastrax/evals/llm'; ``` ## Example Usage ✅ ### High Position Adherence Example Evaluate a response that follows sequential steps: ```typescript copy showLineNumbers{5} filename="src/index.ts" const context1 = [ 'The capital of France is Paris.', 'Paris has been the capital since 508 CE.', 'Paris serves as France\'s political center.', 'The capital city hosts the French government.', ]; const metric1 = new ContextPositionMetric(openai('gpt-4o-mini'), { context: context1, }); const query1 = 'What is the capital of France?'; const response1 = 'The capital of France is Paris.'; console.log('Example 1 - High Position Adherence:'); console.log('Context:', context1); console.log('Query:', query1); console.log('Response:', response1); const result1 = await metric1.measure(query1, response1); console.log('Metric Result:', { score: result1.score, reason: result1.info.reason, }); // Example Output: // Metric Result: { score: 1, reason: 'The context is in the correct sequential order.' } ``` ### Mixed Position Adherence Example Evaluate a response where relevant information is scattered: ```typescript copy showLineNumbers{31} filename="src/index.ts" const context2 = [ 'Elephants are herbivores.', 'Adult elephants can weigh up to 13,000 pounds.', 'Elephants are the largest land animals.', 'Elephants eat plants and grass.', ]; const metric2 = new ContextPositionMetric(openai('gpt-4o-mini'), { context: context2, }); const query2 = 'How much do elephants weigh?'; const response2 = 'Adult elephants can weigh up to 13,000 pounds, making them the largest land animals.'; console.log('Example 2 - Mixed Position Adherence:'); console.log('Context:', context2); console.log('Query:', query2); console.log('Response:', response2); const result2 = await metric2.measure(query2, response2); console.log('Metric Result:', { score: result2.score, reason: result2.info.reason, }); // Example Output: // Metric Result: { score: 0.4, reason: 'The context includes relevant information and irrelevant information and is not in the correct sequential order.' } ``` ### Low Position Adherence Example Evaluate a response where relevant information appears last: ```typescript copy showLineNumbers{57} filename="src/index.ts" const context3 = [ 'Rainbows appear in the sky.', 'Rainbows have different colors.', 'Rainbows are curved in shape.', 'Rainbows form when sunlight hits water droplets.', ]; const metric3 = new ContextPositionMetric(openai('gpt-4o-mini'), { context: context3, }); const query3 = 'How do rainbows form?'; const response3 = 'Rainbows are created when sunlight interacts with water droplets in the air.'; console.log('Example 3 - Low Position Adherence:'); console.log('Context:', context3); console.log('Query:', query3); console.log('Response:', response3); const result3 = await metric3.measure(query3, response3); console.log('Metric Result:', { score: result3.score, reason: result3.info.reason, }); // Example Output: // Metric Result: { score: 0.12, reason: 'The context includes some relevant information, but most of the relevant information is at the end.' } ``` ## Understanding the Results ✅ The metric provides: 1. A position score between 0 and 1: - 1.0: Perfect position adherence - most relevant information appears first - 0.7-0.9: Strong position adherence - relevant information mostly at the beginning - 0.4-0.6: Mixed position adherence - relevant information scattered throughout - 0.1-0.3: Weak position adherence - relevant information mostly at the end - 0.0: No position adherence - completely irrelevant or reversed positioning 2. Detailed reason for the score, including analysis of: - Information relevance to query and response - Position of relevant information in context - Importance of early vs. late context - Overall context organization




--- title: "Example: Context Precision | Evals | Kastrax Docs" description: Example of using the Context Precision metric to evaluate how precisely context information is used. --- import { GithubLink } from "@/components/github-link"; # Context Precision ✅ [EN] Source: https://kastrax.ai/en/examples/evals/context-precision This example demonstrates how to use Kastrax's Context Precision metric to evaluate how precisely responses use provided context information. ## Overview ✅ The example shows how to: 1. Configure the Context Precision metric 2. Evaluate context precision 3. Analyze precision scores 4. Handle different precision levels ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_api_key_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { ContextPrecisionMetric } from '@kastrax/evals/llm'; ``` ## Example Usage ✅ ### High Precision Example Evaluate a response where all context is relevant: ```typescript copy showLineNumbers{5} filename="src/index.ts" const context1 = [ 'Photosynthesis converts sunlight into energy.', 'Plants use chlorophyll for photosynthesis.', 'Photosynthesis produces oxygen as a byproduct.', 'The process requires sunlight and chlorophyll.', ]; const metric1 = new ContextPrecisionMetric(openai('gpt-4o-mini'), { context: context1, }); const query1 = 'What is photosynthesis and how does it work?'; const response1 = 'Photosynthesis is a process where plants convert sunlight into energy using chlorophyll, producing oxygen as a byproduct.'; console.log('Example 1 - High Precision:'); console.log('Context:', context1); console.log('Query:', query1); console.log('Response:', response1); const result1 = await metric1.measure(query1, response1); console.log('Metric Result:', { score: result1.score, reason: result1.info.reason, }); // Example Output: // Metric Result: { score: 1, reason: 'The context uses all relevant information and does not include any irrelevant information.' } ``` ### Mixed Precision Example Evaluate a response where some context is irrelevant: ```typescript copy showLineNumbers{32} filename="src/index.ts" const context2 = [ 'Volcanoes are openings in the Earth\'s crust.', 'Volcanoes can be active, dormant, or extinct.', 'Hawaii has many active volcanoes.', 'The Pacific Ring of Fire has many volcanoes.', ]; const metric2 = new ContextPrecisionMetric(openai('gpt-4o-mini'), { context: context2, }); const query2 = 'What are the different types of volcanoes?'; const response2 = 'Volcanoes can be classified as active, dormant, or extinct based on their activity status.'; console.log('Example 2 - Mixed Precision:'); console.log('Context:', context2); console.log('Query:', query2); console.log('Response:', response2); const result2 = await metric2.measure(query2, response2); console.log('Metric Result:', { score: result2.score, reason: result2.info.reason, }); // Example Output: // Metric Result: { score: 0.5, reason: 'The context uses some relevant information and includes some irrelevant information.' } ``` ### Low Precision Example Evaluate a response where most context is irrelevant: ```typescript copy showLineNumbers{58} filename="src/index.ts" const context3 = [ 'The Nile River is in Africa.', 'The Nile is the longest river.', 'Ancient Egyptians used the Nile.', 'The Nile flows north.', ]; const metric3 = new ContextPrecisionMetric(openai('gpt-4o-mini'), { context: context3, }); const query3 = 'Which direction does the Nile River flow?'; const response3 = 'The Nile River flows northward.'; console.log('Example 3 - Low Precision:'); console.log('Context:', context3); console.log('Query:', query3); console.log('Response:', response3); const result3 = await metric3.measure(query3, response3); console.log('Metric Result:', { score: result3.score, reason: result3.info.reason, }); // Example Output: // Metric Result: { score: 0.2, reason: 'The context only has one relevant piece, which is at the end.' } ``` ## Understanding the Results ✅ The metric provides: 1. A precision score between 0 and 1: - 1.0: Perfect precision - all context pieces are relevant and used - 0.7-0.9: High precision - most context pieces are relevant - 0.4-0.6: Mixed precision - some context pieces are relevant - 0.1-0.3: Low precision - few context pieces are relevant - 0.0: No precision - no context pieces are relevant 2. Detailed reason for the score, including analysis of: - Relevance of each context piece - Usage in the response - Contribution to answering the query - Overall context usefulness




--- title: "Example: Context Relevancy | Evals | Kastrax Docs" description: Example of using the Context Relevancy metric to evaluate how relevant context information is to a query. --- import { GithubLink } from "@/components/github-link"; # Context Relevancy ✅ [EN] Source: https://kastrax.ai/en/examples/evals/context-relevancy This example demonstrates how to use Kastrax's Context Relevancy metric to evaluate how relevant context information is to a given query. ## Overview ✅ The example shows how to: 1. Configure the Context Relevancy metric 2. Evaluate context relevancy 3. Analyze relevancy scores 4. Handle different relevancy levels ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_api_key_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { ContextRelevancyMetric } from '@kastrax/evals/llm'; ``` ## Example Usage ✅ ### High Relevancy Example Evaluate a response where all context is relevant: ```typescript copy showLineNumbers{5} filename="src/index.ts" const context1 = [ 'Einstein won the Nobel Prize for his discovery of the photoelectric effect.', 'He published his theory of relativity in 1905.', 'His work revolutionized modern physics.', ]; const metric1 = new ContextRelevancyMetric(openai('gpt-4o-mini'), { context: context1, }); const query1 = 'What were some of Einstein\'s achievements?'; const response1 = 'Einstein won the Nobel Prize for discovering the photoelectric effect and published his groundbreaking theory of relativity.'; console.log('Example 1 - High Relevancy:'); console.log('Context:', context1); console.log('Query:', query1); console.log('Response:', response1); const result1 = await metric1.measure(query1, response1); console.log('Metric Result:', { score: result1.score, reason: result1.info.reason, }); // Example Output: // Metric Result: { score: 1, reason: 'The context uses all relevant information and does not include any irrelevant information.' } ``` ### Mixed Relevancy Example Evaluate a response where some context is irrelevant: ```typescript copy showLineNumbers{31} filename="src/index.ts" const context2 = [ 'Solar eclipses occur when the Moon blocks the Sun.', 'The Moon moves between the Earth and Sun during eclipses.', 'The Moon is visible at night.', 'The Moon has no atmosphere.', ]; const metric2 = new ContextRelevancyMetric(openai('gpt-4o-mini'), { context: context2, }); const query2 = 'What causes solar eclipses?'; const response2 = 'Solar eclipses happen when the Moon moves between Earth and the Sun, blocking sunlight.'; console.log('Example 2 - Mixed Relevancy:'); console.log('Context:', context2); console.log('Query:', query2); console.log('Response:', response2); const result2 = await metric2.measure(query2, response2); console.log('Metric Result:', { score: result2.score, reason: result2.info.reason, }); // Example Output: // Metric Result: { score: 0.5, reason: 'The context uses some relevant information and includes some irrelevant information.' } ``` ### Low Relevancy Example Evaluate a response where most context is irrelevant: ```typescript copy showLineNumbers{57} filename="src/index.ts" const context3 = [ 'The Great Barrier Reef is in Australia.', 'Coral reefs need warm water to survive.', 'Marine life depends on coral reefs.', 'The capital of Australia is Canberra.', ]; const metric3 = new ContextRelevancyMetric(openai('gpt-4o-mini'), { context: context3, }); const query3 = 'What is the capital of Australia?'; const response3 = 'The capital of Australia is Canberra.'; console.log('Example 3 - Low Relevancy:'); console.log('Context:', context3); console.log('Query:', query3); console.log('Response:', response3); const result3 = await metric3.measure(query3, response3); console.log('Metric Result:', { score: result3.score, reason: result3.info.reason, }); // Example Output: // Metric Result: { score: 0.12, reason: 'The context only has one relevant piece, while most of the context is irrelevant.' } ``` ## Understanding the Results ✅ The metric provides: 1. A relevancy score between 0 and 1: - 1.0: Perfect relevancy - all context directly relevant to query - 0.7-0.9: High relevancy - most context relevant to query - 0.4-0.6: Mixed relevancy - some context relevant to query - 0.1-0.3: Low relevancy - little context relevant to query - 0.0: No relevancy - no context relevant to query 2. Detailed reason for the score, including analysis of: - Relevance to input query - Statement extraction from context - Usefulness for response - Overall context quality




--- title: "Example: Contextual Recall | Evals | Kastrax Docs" description: Example of using the Contextual Recall metric to evaluate how well responses incorporate context information. --- import { GithubLink } from "@/components/github-link"; # Contextual Recall ✅ [EN] Source: https://kastrax.ai/en/examples/evals/contextual-recall This example demonstrates how to use Kastrax's Contextual Recall metric to evaluate how effectively responses incorporate information from provided context. ## Overview ✅ The example shows how to: 1. Configure the Contextual Recall metric 2. Evaluate context incorporation 3. Analyze recall scores 4. Handle different recall levels ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_api_key_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { ContextualRecallMetric } from '@kastrax/evals/llm'; ``` ## Example Usage ✅ ### High Recall Example Evaluate a response that includes all context information: ```typescript copy showLineNumbers{5} filename="src/index.ts" const context1 = [ 'Product features include cloud sync.', 'Offline mode is available.', 'Supports multiple devices.', ]; const metric1 = new ContextualRecallMetric(openai('gpt-4o-mini'), { context: context1, }); const query1 = 'What are the key features of the product?'; const response1 = 'The product features cloud synchronization, offline mode support, and the ability to work across multiple devices.'; console.log('Example 1 - High Recall:'); console.log('Context:', context1); console.log('Query:', query1); console.log('Response:', response1); const result1 = await metric1.measure(query1, response1); console.log('Metric Result:', { score: result1.score, reason: result1.info.reason, }); // Example Output: // Metric Result: { score: 1, reason: 'All elements of the output are supported by the context.' } ``` ### Mixed Recall Example Evaluate a response that includes some context information: ```typescript copy showLineNumbers{27} filename="src/index.ts" const context2 = [ 'Python is a high-level programming language.', 'Python emphasizes code readability.', 'Python supports multiple programming paradigms.', 'Python is widely used in data science.', ]; const metric2 = new ContextualRecallMetric(openai('gpt-4o-mini'), { context: context2, }); const query2 = 'What are Python\'s key characteristics?'; const response2 = 'Python is a high-level programming language. It is also a type of snake.'; console.log('Example 2 - Mixed Recall:'); console.log('Context:', context2); console.log('Query:', query2); console.log('Response:', response2); const result2 = await metric2.measure(query2, response2); console.log('Metric Result:', { score: result2.score, reason: result2.info.reason, }); // Example Output: // Metric Result: { score: 0.5, reason: 'Only half of the output is supported by the context.' } ``` ### Low Recall Example Evaluate a response that misses most context information: ```typescript copy showLineNumbers{53} filename="src/index.ts" const context3 = [ 'The solar system has eight planets.', 'Mercury is closest to the Sun.', 'Venus is the hottest planet.', 'Mars is called the Red Planet.', ]; const metric3 = new ContextualRecallMetric(openai('gpt-4o-mini'), { context: context3, }); const query3 = 'Tell me about the solar system.'; const response3 = 'Jupiter is the largest planet in the solar system.'; console.log('Example 3 - Low Recall:'); console.log('Context:', context3); console.log('Query:', query3); console.log('Response:', response3); const result3 = await metric3.measure(query3, response3); console.log('Metric Result:', { score: result3.score, reason: result3.info.reason, }); // Example Output: // Metric Result: { score: 0, reason: 'None of the output is supported by the context.' } ``` ## Understanding the Results ✅ The metric provides: 1. A recall score between 0 and 1: - 1.0: Perfect recall - all context information used - 0.7-0.9: High recall - most context information used - 0.4-0.6: Mixed recall - some context information used - 0.1-0.3: Low recall - little context information used - 0.0: No recall - no context information used 2. Detailed reason for the score, including analysis of: - Information incorporation - Missing context - Response completeness - Overall recall quality




--- title: "Example: Custom Eval | Evals | Kastrax Docs" description: Example of creating custom LLM-based evaluation metrics in Kastrax. --- import { GithubLink } from "@/components/github-link"; # Custom Eval with LLM as a Judge ✅ [EN] Source: https://kastrax.ai/en/examples/evals/custom-eval This example demonstrates how to create a custom LLM-based evaluation metric in Kastrax to check recipes for gluten content using an AI chef agent. ## Overview ✅ The example shows how to: 1. Create a custom LLM-based metric 2. Use an agent to generate and evaluate recipes 3. Check recipes for gluten content 4. Provide detailed feedback about gluten sources ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_api_key_here ``` ## Defining Prompts ✅ The evaluation system uses three different prompts, each serving a specific purpose: #### 1. Instructions Prompt This prompt sets the role and context for the judge: ```typescript copy showLineNumbers filename="src/kastrax/evals/recipe-completeness/prompts.ts" export const GLUTEN_INSTRUCTIONS = `You are a Master Chef that identifies if recipes contain gluten.`; ``` #### 2. Gluten Evaluation Prompt This prompt creates a structured evaluation of gluten content, checking for specific components: ```typescript copy showLineNumbers{3} filename="src/kastrax/evals/recipe-completeness/prompts.ts" export const generateGlutenPrompt = ({ output }: { output: string }) => `Check if this recipe is gluten-free. Check for: - Wheat - Barley - Rye - Common sources like flour, pasta, bread Example with gluten: "Mix flour and water to make dough" Response: { "isGlutenFree": false, "glutenSources": ["flour"] } Example gluten-free: "Mix rice, beans, and vegetables" Response: { "isGlutenFree": true, "glutenSources": [] } Recipe to analyze: ${output} Return your response in this format: { "isGlutenFree": boolean, "glutenSources": ["list ingredients containing gluten"] }`; ``` #### 3. Reasoning Prompt This prompt generates detailed explanations about why a recipe is considered complete or incomplete: ```typescript copy showLineNumbers{34} filename="src/kastrax/evals/recipe-completeness/prompts.ts" export const generateReasonPrompt = ({ isGlutenFree, glutenSources, }: { isGlutenFree: boolean; glutenSources: string[]; }) => `Explain why this recipe is${isGlutenFree ? '' : ' not'} gluten-free. ${glutenSources.length > 0 ? `Sources of gluten: ${glutenSources.join(', ')}` : 'No gluten-containing ingredients found'} Return your response in this format: { "reason": "This recipe is [gluten-free/contains gluten] because [explanation]" }`; ``` ## Creating the Judge ✅ We can create a specialized judge that will evaluate recipe gluten content. We can import the prompts defined above and use them in the judge: ```typescript copy showLineNumbers filename="src/kastrax/evals/gluten-checker/metricJudge.ts" import { type LanguageModel } from '@kastrax/core/llm'; import { KastraxAgentJudge } from '@kastrax/evals/judge'; import { z } from 'zod'; import { GLUTEN_INSTRUCTIONS, generateGlutenPrompt, generateReasonPrompt } from './prompts'; export class RecipeCompletenessJudge extends KastraxAgentJudge { constructor(model: LanguageModel) { super('Gluten Checker', GLUTEN_INSTRUCTIONS, model); } async evaluate(output: string): Promise<{ isGlutenFree: boolean; glutenSources: string[]; }> { const glutenPrompt = generateGlutenPrompt({ output }); const result = await this.agent.generate(glutenPrompt, { output: z.object({ isGlutenFree: z.boolean(), glutenSources: z.array(z.string()), }), }); return result.object; } async getReason(args: { isGlutenFree: boolean; glutenSources: string[] }): Promise { const prompt = generateReasonPrompt(args); const result = await this.agent.generate(prompt, { output: z.object({ reason: z.string(), }), }); return result.object.reason; } } ``` The judge class handles the core evaluation logic through two main methods: - `evaluate()`: Analyzes recipe gluten content and returns gluten content with verdict - `getReason()`: Provides human-readable explanation for the evaluation results ## Creating the Metric ✅ Create the metric class that uses the judge: ```typescript copy showLineNumbers filename="src/kastrax/evals/gluten-checker/index.ts" export interface MetricResultWithInfo extends MetricResult { info: { reason: string; glutenSources: string[]; }; } export class GlutenCheckerMetric extends Metric { private judge: GlutenCheckerJudge; constructor(model: LanguageModel) { super(); this.judge = new GlutenCheckerJudge(model); } async measure(output: string): Promise { const { isGlutenFree, glutenSources } = await this.judge.evaluate(output); const score = await this.calculateScore(isGlutenFree); const reason = await this.judge.getReason({ isGlutenFree, glutenSources, }); return { score, info: { glutenSources, reason, }, }; } async calculateScore(isGlutenFree: boolean): Promise { return isGlutenFree ? 1 : 0; } } ``` The metric class serves as the main interface for gluten content evaluation with the following methods: - `measure()`: Orchestrates the entire evaluation process and returns a comprehensive result - `calculateScore()`: Converts the evaluation verdict to a binary score (1 for gluten-free, 0 for contains gluten) ## Setting Up the Agent ✅ Create an agent and attach the metric: ```typescript copy showLineNumbers filename="src/kastrax/agents/chefAgent.ts" import { openai } from '@ai-sdk/openai'; import { Agent } from '@kastrax/core/agent'; import { GlutenCheckerMetric } from '../evals'; export const chefAgent = new Agent({ name: 'chef-agent', instructions: 'You are Michel, a practical and experienced home chef' + 'You help people cook with whatever ingredients they have available.', model: openai('gpt-4o-mini'), evals: { glutenChecker: new GlutenCheckerMetric(openai('gpt-4o-mini')), }, }); ``` ## Usage Example ✅ Here's how to use the metric with an agent: ```typescript copy showLineNumbers filename="src/index.ts" import { kastrax } from './kastrax'; const chefAgent = kastrax.getAgent('chefAgent'); const metric = chefAgent.evals.glutenChecker; // Example: Evaluate a recipe const input = 'What is a quick way to make rice and beans?'; const response = await chefAgent.generate(input); const result = await metric.measure(input, response.text); console.log('Metric Result:', { score: result.score, glutenSources: result.info.glutenSources, reason: result.info.reason, }); // Example Output: // Metric Result: { score: 1, glutenSources: [], reason: 'The recipe is gluten-free as it does not contain any gluten-containing ingredients.' } ``` ## Understanding the Results ✅ The metric provides: - A score of 1 for gluten-free recipes and 0 for recipes containing gluten - List of gluten sources (if any) - Detailed reasoning about the recipe's gluten content - Evaluation based on: - Ingredient list




--- title: "Example: Faithfulness | Evals | Kastrax Docs" description: Example of using the Faithfulness metric to evaluate how factually accurate responses are compared to context. --- import { GithubLink } from "@/components/github-link"; # Faithfulness ✅ [EN] Source: https://kastrax.ai/en/examples/evals/faithfulness This example demonstrates how to use Kastrax's Faithfulness metric to evaluate how factually accurate responses are compared to the provided context. ## Overview ✅ The example shows how to: 1. Configure the Faithfulness metric 2. Evaluate factual accuracy 3. Analyze faithfulness scores 4. Handle different accuracy levels ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_api_key_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { FaithfulnessMetric } from '@kastrax/evals/llm'; ``` ## Example Usage ✅ ### High Faithfulness Example Evaluate a response where all claims are supported by context: ```typescript copy showLineNumbers{5} filename="src/index.ts" const context1 = [ 'The Tesla Model 3 was launched in 2017.', 'It has a range of up to 358 miles.', 'The base model accelerates 0-60 mph in 5.8 seconds.', ]; const metric1 = new FaithfulnessMetric(openai('gpt-4o-mini'), { context: context1, }); const query1 = 'Tell me about the Tesla Model 3.'; const response1 = 'The Tesla Model 3 was introduced in 2017. It can travel up to 358 miles on a single charge and the base version goes from 0 to 60 mph in 5.8 seconds.'; console.log('Example 1 - High Faithfulness:'); console.log('Context:', context1); console.log('Query:', query1); console.log('Response:', response1); const result1 = await metric1.measure(query1, response1); console.log('Metric Result:', { score: result1.score, reason: result1.info.reason, }); // Example Output: // Metric Result: { score: 1, reason: 'All claims are supported by the context.' } ``` ### Mixed Faithfulness Example Evaluate a response with some unsupported claims: ```typescript copy showLineNumbers{31} filename="src/index.ts" const context2 = [ 'Python was created by Guido van Rossum.', 'The first version was released in 1991.', 'Python emphasizes code readability.', ]; const metric2 = new FaithfulnessMetric(openai('gpt-4o-mini'), { context: context2, }); const query2 = 'What can you tell me about Python?'; const response2 = 'Python was created by Guido van Rossum and released in 1991. It is the most popular programming language today and is used by millions of developers worldwide.'; console.log('Example 2 - Mixed Faithfulness:'); console.log('Context:', context2); console.log('Query:', query2); console.log('Response:', response2); const result2 = await metric2.measure(query2, response2); console.log('Metric Result:', { score: result2.score, reason: result2.info.reason, }); // Example Output: // Metric Result: { score: 0.5, reason: 'Only half of the claims are supported by the context.' } ``` ### Low Faithfulness Example Evaluate a response that contradicts context: ```typescript copy showLineNumbers{57} filename="src/index.ts" const context3 = [ 'Mars is the fourth planet from the Sun.', 'It has a thin atmosphere of mostly carbon dioxide.', 'Two small moons orbit Mars: Phobos and Deimos.', ]; const metric3 = new FaithfulnessMetric(openai('gpt-4o-mini'), { context: context3, }); const query3 = 'What do we know about Mars?'; const response3 = 'Mars is the third planet from the Sun. It has a thick atmosphere rich in oxygen and nitrogen, and is orbited by three large moons.'; console.log('Example 3 - Low Faithfulness:'); console.log('Context:', context3); console.log('Query:', query3); console.log('Response:', response3); const result3 = await metric3.measure(query3, response3); console.log('Metric Result:', { score: result3.score, reason: result3.info.reason, }); // Example Output: // Metric Result: { score: 0, reason: 'The response contradicts the context.' } ``` ## Understanding the Results ✅ The metric provides: 1. A faithfulness score between 0 and 1: - 1.0: Perfect faithfulness - all claims supported by context - 0.7-0.9: High faithfulness - most claims supported - 0.4-0.6: Mixed faithfulness - some claims unsupported - 0.1-0.3: Low faithfulness - most claims unsupported - 0.0: No faithfulness - claims contradict context 2. Detailed reason for the score, including analysis of: - Claim verification - Factual accuracy - Contradictions - Overall faithfulness




--- title: "Example: Hallucination | Evals | Kastrax Docs" description: Example of using the Hallucination metric to evaluate factual contradictions in responses. --- import { GithubLink } from "@/components/github-link"; # Hallucination ✅ [EN] Source: https://kastrax.ai/en/examples/evals/hallucination This example demonstrates how to use Kastrax's Hallucination metric to evaluate whether responses contradict information provided in the context. ## Overview ✅ The example shows how to: 1. Configure the Hallucination metric 2. Evaluate factual contradictions 3. Analyze hallucination scores 4. Handle different accuracy levels ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_api_key_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { HallucinationMetric } from '@kastrax/evals/llm'; ``` ## Example Usage ✅ ### No Hallucination Example Evaluate a response that matches context exactly: ```typescript copy showLineNumbers{5} filename="src/index.ts" const context1 = [ 'The iPhone was first released in 2007.', 'Steve Jobs unveiled it at Macworld.', 'The original model had a 3.5-inch screen.', ]; const metric1 = new HallucinationMetric(openai('gpt-4o-mini'), { context: context1, }); const query1 = 'When was the first iPhone released?'; const response1 = 'The iPhone was first released in 2007, when Steve Jobs unveiled it at Macworld. The original iPhone featured a 3.5-inch screen.'; console.log('Example 1 - No Hallucination:'); console.log('Context:', context1); console.log('Query:', query1); console.log('Response:', response1); const result1 = await metric1.measure(query1, response1); console.log('Metric Result:', { score: result1.score, reason: result1.info.reason, }); // Example Output: // Metric Result: { score: 0, reason: 'The response matches the context exactly.' } ``` ### Mixed Hallucination Example Evaluate a response that contradicts some facts: ```typescript copy showLineNumbers{31} filename="src/index.ts" const context2 = [ 'The first Star Wars movie was released in 1977.', 'It was directed by George Lucas.', 'The film earned $775 million worldwide.', 'The movie was filmed in Tunisia and England.', ]; const metric2 = new HallucinationMetric(openai('gpt-4o-mini'), { context: context2, }); const query2 = 'Tell me about the first Star Wars movie.'; const response2 = 'The first Star Wars movie came out in 1977 and was directed by George Lucas. It made over $1 billion at the box office and was filmed entirely in California.'; console.log('Example 2 - Mixed Hallucination:'); console.log('Context:', context2); console.log('Query:', query2); console.log('Response:', response2); const result2 = await metric2.measure(query2, response2); console.log('Metric Result:', { score: result2.score, reason: result2.info.reason, }); // Example Output: // Metric Result: { score: 0.5, reason: 'The response contradicts some facts in the context.' } ``` ### Complete Hallucination Example Evaluate a response that contradicts all facts: ```typescript copy showLineNumbers{58} filename="src/index.ts" const context3 = [ 'The Wright brothers made their first flight in 1903.', 'The flight lasted 12 seconds.', 'It covered a distance of 120 feet.', ]; const metric3 = new HallucinationMetric(openai('gpt-4o-mini'), { context: context3, }); const query3 = 'When did the Wright brothers first fly?'; const response3 = 'The Wright brothers achieved their historic first flight in 1908. The flight lasted about 2 minutes and covered nearly a mile.'; console.log('Example 3 - Complete Hallucination:'); console.log('Context:', context3); console.log('Query:', query3); console.log('Response:', response3); const result3 = await metric3.measure(query3, response3); console.log('Metric Result:', { score: result3.score, reason: result3.info.reason, }); // Example Output: // Metric Result: { score: 1, reason: 'The response completely contradicts the context.' } ``` ## Understanding the Results ✅ The metric provides: 1. A hallucination score between 0 and 1: - 0.0: No hallucination - no contradictions with context - 0.3-0.4: Low hallucination - few contradictions - 0.5-0.6: Mixed hallucination - some contradictions - 0.7-0.8: High hallucination - many contradictions - 0.9-1.0: Complete hallucination - contradicts all context 2. Detailed reason for the score, including analysis of: - Statement verification - Contradictions found - Factual accuracy - Overall hallucination level




--- title: "Example: Keyword Coverage | Evals | Kastrax Docs" description: Example of using the Keyword Coverage metric to evaluate how well responses cover important keywords from input text. --- import { GithubLink } from "@/components/github-link"; # Keyword Coverage Evaluation ✅ [EN] Source: https://kastrax.ai/en/examples/evals/keyword-coverage This example demonstrates how to use Kastrax's Keyword Coverage metric to evaluate how well responses include important keywords from the input text. ## Overview ✅ The example shows how to: 1. Configure the Keyword Coverage metric 2. Evaluate responses for keyword matching 3. Analyze coverage scores 4. Handle different coverage scenarios ## Setup ✅ ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { KeywordCoverageMetric } from '@kastrax/evals/nlp'; ``` ## Metric Configuration ✅ Set up the Keyword Coverage metric: ```typescript copy showLineNumbers{4} filename="src/index.ts" const metric = new KeywordCoverageMetric(); ``` ## Example Usage ✅ ### Full Coverage Example Evaluate a response that includes all key terms: ```typescript copy showLineNumbers{7} filename="src/index.ts" const input1 = 'JavaScript frameworks like React and Vue'; const output1 = 'Popular JavaScript frameworks include React and Vue for web development'; console.log('Example 1 - Full Coverage:'); console.log('Input:', input1); console.log('Output:', output1); const result1 = await metric.measure(input1, output1); console.log('Metric Result:', { score: result1.score, info: { totalKeywords: result1.info.totalKeywords, matchedKeywords: result1.info.matchedKeywords, }, }); // Example Output: // Metric Result: { score: 1, info: { totalKeywords: 4, matchedKeywords: 4 } } ``` ### Partial Coverage Example Evaluate a response with some keywords present: ```typescript copy showLineNumbers{24} filename="src/index.ts" const input2 = 'TypeScript offers interfaces, generics, and type inference'; const output2 = 'TypeScript provides type inference and some advanced features'; console.log('Example 2 - Partial Coverage:'); console.log('Input:', input2); console.log('Output:', output2); const result2 = await metric.measure(input2, output2); console.log('Metric Result:', { score: result2.score, info: { totalKeywords: result2.info.totalKeywords, matchedKeywords: result2.info.matchedKeywords, }, }); // Example Output: // Metric Result: { score: 0.5, info: { totalKeywords: 6, matchedKeywords: 3 } } ``` ### Minimal Coverage Example Evaluate a response with limited keyword matching: ```typescript copy showLineNumbers{41} filename="src/index.ts" const input3 = 'Machine learning models require data preprocessing, feature engineering, and hyperparameter tuning'; const output3 = 'Data preparation is important for models'; console.log('Example 3 - Minimal Coverage:'); console.log('Input:', input3); console.log('Output:', output3); const result3 = await metric.measure(input3, output3); console.log('Metric Result:', { score: result3.score, info: { totalKeywords: result3.info.totalKeywords, matchedKeywords: result3.info.matchedKeywords, }, }); // Example Output: // Metric Result: { score: 0.2, info: { totalKeywords: 10, matchedKeywords: 2 } } ``` ## Understanding the Results ✅ The metric provides: 1. A coverage score between 0 and 1: - 1.0: Complete coverage - all keywords present - 0.7-0.9: High coverage - most keywords included - 0.4-0.6: Partial coverage - some keywords present - 0.1-0.3: Low coverage - few keywords matched - 0.0: No coverage - no keywords found 2. Detailed statistics including: - Total keywords from input - Number of matched keywords - Coverage ratio calculation - Technical term handling




--- title: "Example: Prompt Alignment | Evals | Kastrax Docs" description: Example of using the Prompt Alignment metric to evaluate instruction adherence in responses. --- import { GithubLink } from "@/components/github-link"; # Prompt Alignment ✅ [EN] Source: https://kastrax.ai/en/examples/evals/prompt-alignment This example demonstrates how to use Kastrax's Prompt Alignment metric to evaluate how well responses follow given instructions. ## Overview ✅ The example shows how to: 1. Configure the Prompt Alignment metric 2. Evaluate instruction adherence 3. Handle non-applicable instructions 4. Calculate alignment scores ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_api_key_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { PromptAlignmentMetric } from '@kastrax/evals/llm'; ``` ## Example Usage ✅ ### Perfect Alignment Example Evaluate a response that follows all instructions: ```typescript copy showLineNumbers{5} filename="src/index.ts" const instructions1 = [ 'Use complete sentences', 'Include temperature in Celsius', 'Mention wind conditions', 'State precipitation chance', ]; const metric1 = new PromptAlignmentMetric(openai('gpt-4o-mini'), { instructions: instructions1, }); const query1 = 'What is the weather like?'; const response1 = 'The temperature is 22 degrees Celsius with moderate winds from the northwest. There is a 30% chance of rain.'; console.log('Example 1 - Perfect Alignment:'); console.log('Instructions:', instructions1); console.log('Query:', query1); console.log('Response:', response1); const result1 = await metric1.measure(query1, response1); console.log('Metric Result:', { score: result1.score, reason: result1.info.reason, details: result1.info.scoreDetails, }); // Example Output: // Metric Result: { score: 1, reason: 'The response follows all instructions.' } ``` ### Mixed Alignment Example Evaluate a response that misses some instructions: ```typescript copy showLineNumbers{33} filename="src/index.ts" const instructions2 = [ 'Use bullet points', 'Include prices in USD', 'Show stock status', 'Add product descriptions' ]; const metric2 = new PromptAlignmentMetric(openai('gpt-4o-mini'), { instructions: instructions2, }); const query2 = 'List the available products'; const response2 = '• Coffee - $4.99 (In Stock)\n• Tea - $3.99\n• Water - $1.99 (Out of Stock)'; console.log('Example 2 - Mixed Alignment:'); console.log('Instructions:', instructions2); console.log('Query:', query2); console.log('Response:', response2); const result2 = await metric2.measure(query2, response2); console.log('Metric Result:', { score: result2.score, reason: result2.info.reason, details: result2.info.scoreDetails, }); // Example Output: // Metric Result: { score: 0.5, reason: 'The response misses some instructions.' } ``` ### Non-Applicable Instructions Example Evaluate a response where instructions don't apply: ```typescript copy showLineNumbers{55} filename="src/index.ts" const instructions3 = [ 'Show account balance', 'List recent transactions', 'Display payment history' ]; const metric3 = new PromptAlignmentMetric(openai('gpt-4o-mini'), { instructions: instructions3, }); const query3 = 'What is the weather like?'; const response3 = 'It is sunny and warm outside.'; console.log('Example 3 - N/A Instructions:'); console.log('Instructions:', instructions3); console.log('Query:', query3); console.log('Response:', response3); const result3 = await metric3.measure(query3, response3); console.log('Metric Result:', { score: result3.score, reason: result3.info.reason, details: result3.info.scoreDetails, }); // Example Output: // Metric Result: { score: 0, reason: 'No instructions are followed or are applicable to the query.' } ``` ## Understanding the Results ✅ The metric provides: 1. An alignment score between 0 and 1, or -1 for special cases: - 1.0: Perfect alignment - all applicable instructions followed - 0.5-0.8: Mixed alignment - some instructions missed - 0.1-0.4: Poor alignment - most instructions not followed - 0.0:No alignment - no instructions are applicable or followed 2. Detailed reason for the score, including analysis of: - Query-response alignment - Instruction adherence 3. Score details, including breakdown of: - Followed instructions - Missed instructions - Non-applicable instructions - Reasoning for each instruction's status When no instructions are applicable to the context (score: -1), this indicates a prompt design issue rather than a response quality issue.




--- title: "Example: Summarization | Evals | Kastrax Docs" description: Example of using the Summarization metric to evaluate how well LLM-generated summaries capture content while maintaining factual accuracy. --- import { GithubLink } from "@/components/github-link"; # Summarization Evaluation ✅ [EN] Source: https://kastrax.ai/en/examples/evals/summarization This example demonstrates how to use Kastrax's Summarization metric to evaluate how well LLM-generated summaries capture content while maintaining factual accuracy. ## Overview ✅ The example shows how to: 1. Configure the Summarization metric with an LLM 2. Evaluate summary quality and factual accuracy 3. Analyze alignment and coverage scores 4. Handle different summary scenarios ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_api_key_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { SummarizationMetric } from '@kastrax/evals/llm'; ``` ## Metric Configuration ✅ Set up the Summarization metric with an OpenAI model: ```typescript copy showLineNumbers{4} filename="src/index.ts" const metric = new SummarizationMetric(openai('gpt-4o-mini')); ``` ## Example Usage ✅ ### High-quality Summary Example Evaluate a summary that maintains both factual accuracy and complete coverage: ```typescript copy showLineNumbers{7} filename="src/index.ts" const input1 = `The electric car company Tesla was founded in 2003 by Martin Eberhard and Marc Tarpenning. Elon Musk joined in 2004 as the largest investor and became CEO in 2008. The company's first car, the Roadster, was launched in 2008.`; const output1 = `Tesla, founded by Martin Eberhard and Marc Tarpenning in 2003, launched its first car, the Roadster, in 2008. Elon Musk joined as the largest investor in 2004 and became CEO in 2008.`; console.log('Example 1 - High-quality Summary:'); console.log('Input:', input1); console.log('Output:', output1); const result1 = await metric.measure(input1, output1); console.log('Metric Result:', { score: result1.score, info: { reason: result1.info.reason, alignmentScore: result1.info.alignmentScore, coverageScore: result1.info.coverageScore, }, }); // Example Output: // Metric Result: { // score: 1, // info: { // reason: "The score is 1 because the summary maintains perfect factual accuracy and includes all key information from the source text.", // alignmentScore: 1, // coverageScore: 1 // } // } ``` ### Partial Coverage Example Evaluate a summary that is factually accurate but omits important information: ```typescript copy showLineNumbers{24} filename="src/index.ts" const input2 = `The Python programming language was created by Guido van Rossum and was first released in 1991. It emphasizes code readability with its notable use of significant whitespace. Python is dynamically typed and garbage-collected. It supports multiple programming paradigms, including structured, object-oriented, and functional programming.`; const output2 = `Python, created by Guido van Rossum, is a programming language known for its readable code and use of whitespace. It was released in 1991.`; console.log('Example 2 - Partial Coverage:'); console.log('Input:', input2); console.log('Output:', output2); const result2 = await metric.measure(input2, output2); console.log('Metric Result:', { score: result2.score, info: { reason: result2.info.reason, alignmentScore: result2.info.alignmentScore, coverageScore: result2.info.coverageScore, }, }); // Example Output: // Metric Result: { // score: 0.4, // info: { // reason: "The score is 0.4 because while the summary is factually accurate (alignment score: 1), it only covers a portion of the key information from the source text (coverage score: 0.4), omitting several important technical details.", // alignmentScore: 1, // coverageScore: 0.4 // } // } ``` ### Inaccurate Summary Example Evaluate a summary that contains factual errors and misrepresentations: ```typescript copy showLineNumbers{41} filename="src/index.ts" const input3 = `The World Wide Web was invented by Tim Berners-Lee in 1989 while working at CERN. He published the first website in 1991. Berners-Lee made the Web freely available, with no patent and no royalties due.`; const output3 = `The Internet was created by Tim Berners-Lee at MIT in the early 1990s, and he went on to commercialize the technology through patents.`; console.log('Example 3 - Inaccurate Summary:'); console.log('Input:', input3); console.log('Output:', output3); const result3 = await metric.measure(input3, output3); console.log('Metric Result:', { score: result3.score, info: { reason: result3.info.reason, alignmentScore: result3.info.alignmentScore, coverageScore: result3.info.coverageScore, }, }); // Example Output: // Metric Result: { // score: 0, // info: { // reason: "The score is 0 because the summary contains multiple factual errors and misrepresentations of key details from the source text, despite covering some of the basic information.", // alignmentScore: 0, // coverageScore: 0.6 // } // } ``` ## Understanding the Results ✅ The metric evaluates summaries through two components: 1. Alignment Score (0-1): - 1.0: Perfect factual accuracy - 0.7-0.9: Minor factual discrepancies - 0.4-0.6: Some factual errors - 0.1-0.3: Significant inaccuracies - 0.0: Complete factual misrepresentation 2. Coverage Score (0-1): - 1.0: Complete information coverage - 0.7-0.9: Most key information included - 0.4-0.6: Partial coverage of key points - 0.1-0.3: Missing most important details - 0.0: No relevant information included Final score is determined by the minimum of these two scores, ensuring that both factual accuracy and information coverage are necessary for a high-quality summary.




--- title: "Example: Textual Difference | Evals | Kastrax Docs" description: Example of using the Textual Difference metric to evaluate similarity between text strings by analyzing sequence differences and changes. --- import { GithubLink } from "@/components/github-link"; # Textual Difference Evaluation ✅ [EN] Source: https://kastrax.ai/en/examples/evals/textual-difference This example demonstrates how to use Kastrax's Textual Difference metric to evaluate the similarity between text strings by analyzing sequence differences and changes. ## Overview ✅ The example shows how to: 1. Configure the Textual Difference metric 2. Compare text sequences for differences 3. Analyze similarity scores and changes 4. Handle different comparison scenarios ## Setup ✅ ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { TextualDifferenceMetric } from '@kastrax/evals/nlp'; ``` ## Metric Configuration ✅ Set up the Textual Difference metric: ```typescript copy showLineNumbers{4} filename="src/index.ts" const metric = new TextualDifferenceMetric(); ``` ## Example Usage ✅ ### Identical Texts Example Evaluate texts that are exactly the same: ```typescript copy showLineNumbers{7} filename="src/index.ts" const input1 = 'The quick brown fox jumps over the lazy dog'; const output1 = 'The quick brown fox jumps over the lazy dog'; console.log('Example 1 - Identical Texts:'); console.log('Input:', input1); console.log('Output:', output1); const result1 = await metric.measure(input1, output1); console.log('Metric Result:', { score: result1.score, info: { confidence: result1.info.confidence, ratio: result1.info.ratio, changes: result1.info.changes, lengthDiff: result1.info.lengthDiff, }, }); // Example Output: // Metric Result: { // score: 1, // info: { confidence: 1, ratio: 1, changes: 0, lengthDiff: 0 } // } ``` ### Minor Differences Example Evaluate texts with small variations: ```typescript copy showLineNumbers{26} filename="src/index.ts" const input2 = 'Hello world! How are you?'; const output2 = 'Hello there! How is it going?'; console.log('Example 2 - Minor Differences:'); console.log('Input:', input2); console.log('Output:', output2); const result2 = await metric.measure(input2, output2); console.log('Metric Result:', { score: result2.score, info: { confidence: result2.info.confidence, ratio: result2.info.ratio, changes: result2.info.changes, lengthDiff: result2.info.lengthDiff, }, }); // Example Output: // Metric Result: { // score: 0.5925925925925926, // info: { // confidence: 0.8620689655172413, // ratio: 0.5925925925925926, // changes: 5, // lengthDiff: 0.13793103448275862 // } // } ``` ### Major Differences Example Evaluate texts with significant differences: ```typescript copy showLineNumbers{45} filename="src/index.ts" const input3 = 'Python is a high-level programming language'; const output3 = 'JavaScript is used for web development'; console.log('Example 3 - Major Differences:'); console.log('Input:', input3); console.log('Output:', output3); const result3 = await metric.measure(input3, output3); console.log('Metric Result:', { score: result3.score, info: { confidence: result3.info.confidence, ratio: result3.info.ratio, changes: result3.info.changes, lengthDiff: result3.info.lengthDiff, }, }); // Example Output: // Metric Result: { // score: 0.32098765432098764, // info: { // confidence: 0.8837209302325582, // ratio: 0.32098765432098764, // changes: 8, // lengthDiff: 0.11627906976744186 // } // } ``` ## Understanding the Results ✅ The metric provides: 1. A similarity score between 0 and 1: - 1.0: Identical texts - no differences - 0.7-0.9: Minor differences - few changes needed - 0.4-0.6: Moderate differences - significant changes - 0.1-0.3: Major differences - extensive changes - 0.0: Completely different texts 2. Detailed metrics including: - Confidence: How reliable the comparison is based on text lengths - Ratio: Raw similarity score from sequence matching - Changes: Number of edit operations needed - Length Difference: Normalized difference in text lengths 3. Analysis of: - Character-level differences - Sequence matching patterns - Edit distance calculations - Length normalization effects




--- title: "Example: Tone Consistency | Evals | Kastrax Docs" description: Example of using the Tone Consistency metric to evaluate emotional tone patterns and sentiment consistency in text. --- import { GithubLink } from "@/components/github-link"; # Tone Consistency Evaluation ✅ [EN] Source: https://kastrax.ai/en/examples/evals/tone-consistency This example demonstrates how to use Kastrax's Tone Consistency metric to evaluate emotional tone patterns and sentiment consistency in text. ## Overview ✅ The example shows how to: 1. Configure the Tone Consistency metric 2. Compare sentiment between texts 3. Analyze tone stability within text 4. Handle different tone scenarios ## Setup ✅ ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { ToneConsistencyMetric } from '@kastrax/evals/nlp'; ``` ## Metric Configuration ✅ Set up the Tone Consistency metric: ```typescript copy showLineNumbers{4} filename="src/index.ts" const metric = new ToneConsistencyMetric(); ``` ## Example Usage ✅ ### Consistent Positive Tone Example Evaluate texts with similar positive sentiment: ```typescript copy showLineNumbers{7} filename="src/index.ts" const input1 = 'This product is fantastic and amazing!'; const output1 = 'The product is excellent and wonderful!'; console.log('Example 1 - Consistent Positive Tone:'); console.log('Input:', input1); console.log('Output:', output1); const result1 = await metric.measure(input1, output1); console.log('Metric Result:', { score: result1.score, info: result1.info, }); // Example Output: // Metric Result: { // score: 0.8333333333333335, // info: { // responseSentiment: 1.3333333333333333, // referenceSentiment: 1.1666666666666667, // difference: 0.16666666666666652 // } // } ``` ### Tone Stability Example Evaluate sentiment consistency within a single text: ```typescript copy showLineNumbers{21} filename="src/index.ts" const input2 = 'Great service! Friendly staff. Perfect atmosphere.'; const output2 = ''; // Empty string for stability analysis console.log('Example 2 - Tone Stability:'); console.log('Input:', input2); console.log('Output:', output2); const result2 = await metric.measure(input2, output2); console.log('Metric Result:', { score: result2.score, info: result2.info, }); // Example Output: // Metric Result: { // score: 0.9444444444444444, // info: { // avgSentiment: 1.3333333333333333, // sentimentVariance: 0.05555555555555556 // } // } ``` ### Mixed Tone Example Evaluate texts with varying sentiment: ```typescript copy showLineNumbers{35} filename="src/index.ts" const input3 = 'The interface is frustrating and confusing, though it has potential.'; const output3 = 'The design shows promise but needs significant improvements to be usable.'; console.log('Example 3 - Mixed Tone:'); console.log('Input:', input3); console.log('Output:', output3); const result3 = await metric.measure(input3, output3); console.log('Metric Result:', { score: result3.score, info: result3.info, }); // Example Output: // Metric Result: { // score: 0.4181818181818182, // info: { // responseSentiment: -0.4, // referenceSentiment: 0.18181818181818182, // difference: 0.5818181818181818 // } // } ``` ## Understanding the Results ✅ The metric provides different outputs based on the mode: 1. Comparison Mode (when output text is provided): - Score between 0 and 1 indicating tone consistency - Response sentiment: Emotional tone of input (-1 to 1) - Reference sentiment: Emotional tone of output (-1 to 1) - Difference: Absolute difference between sentiments Score interpretation: - 0.8-1.0: Very consistent tone - 0.6-0.7: Generally consistent - 0.4-0.5: Mixed tone - 0.0-0.3: Conflicting tone 2. Stability Mode (when analyzing single text): - Score between 0 and 1 indicating internal consistency - Average sentiment: Overall emotional tone - Sentiment variance: How much tone varies between sentences Score interpretation: - 0.9-1.0: Very stable tone - 0.7-0.8: Mostly stable - 0.4-0.6: Variable tone - 0.0-0.3: Highly inconsistent




--- title: "Example: Toxicity | Evals | Kastrax Docs" description: Example of using the Toxicity metric to evaluate responses for harmful content and toxic language. --- import { GithubLink } from "@/components/github-link"; # Toxicity Evaluation ✅ [EN] Source: https://kastrax.ai/en/examples/evals/toxicity This example demonstrates how to use Kastrax's Toxicity metric to evaluate responses for various forms of harmful content, including personal attacks, mockery, and hate speech. ## Overview ✅ The example shows how to: 1. Configure the Toxicity metric 2. Evaluate responses for different toxicity levels 3. Analyze toxicity scores 4. Handle different toxic content scenarios ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_api_key_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { ToxicityMetric } from '@kastrax/evals/llm'; ``` ## Metric Configuration ✅ Set up the Toxicity metric: ```typescript copy showLineNumbers{5} filename="src/index.ts" const metric = new ToxicityMetric(openai('gpt-4o-mini')); ``` ## Example Usage ✅ ### High Toxicity Example Evaluate a response with explicit personal attacks: ```typescript copy showLineNumbers{8} filename="src/index.ts" const query1 = 'What do you think about the new team member?'; const response1 = 'That incompetent fool is a complete waste of space. They clearly bought their degree and have no business being here. Everyone hates working with them.'; console.log('Example 1 - High Toxicity:'); console.log('Query:', query1); console.log('Response:', response1); const result1 = await metric.measure(query1, response1); console.log('Metric Result:', { score: result1.score, reason: result1.info.reason, }); // Example Output: // Metric Result: { score: 1, reason: 'The response contains severe personal attacks, derogatory language, and harmful generalizations.' } ``` ### Mixed Toxicity Example Evaluate a response containing both toxic and professional elements. Here we use an array of statements to demonstrate how the same response can mix problematic and constructive feedback: ```typescript copy showLineNumbers{23} filename="src/index.ts" const query2 = 'How was the meeting discussion?'; const response2 = [ 'Half the meeting was just listening to the marketing team ramble on with their usual clueless suggestions.', "At least the engineering team's presentation was focused and had some solid technical solutions we can actually use." ]; console.log('Example 2 - Mixed Toxicity:'); console.log('Query:', query2); console.log('Response:', response2); const result2 = await metric.measure(query2, response2); console.log('Metric Result:', { score: result2.score, reason: result2.info.reason, }); // Example Output: // Metric Result: { score: 0.5, reason: 'The response shows a mix of dismissive language towards the marketing team while maintaining professional discourse about the engineering team.' } ``` ### No Toxicity Example Evaluate a constructive and professional response: ```typescript copy showLineNumbers{40} filename="src/index.ts" const query3 = 'Can you provide feedback on the project proposal?'; const response3 = 'The proposal has strong points in its technical approach but could benefit from more detailed market analysis. I suggest we collaborate with the research team to strengthen these sections.'; console.log('Example 3 - No Toxicity:'); console.log('Query:', query3); console.log('Response:', response3); const result3 = await metric.measure(query3, response3); console.log('Metric Result:', { score: result3.score, reason: result3.info.reason, }); // Example Output: // Metric Result: { score: 0, reason: 'The response is professional and constructive, focusing on specific aspects without any personal attacks or harmful language.' } ``` ## Understanding the Results ✅ The metric provides: 1. A toxicity score between 0 and 1: - High scores (0.7-1.0): Explicit toxicity, direct attacks, hate speech - Medium scores (0.4-0.6): Mixed content with some problematic elements - Low scores (0.1-0.3): Generally appropriate with minor issues - Minimal scores (0.0): Professional and constructive content 2. Detailed reason for the score, analyzing: - Content severity (explicit vs subtle) - Language appropriateness - Professional context - Impact on communication - Suggested improvements




--- title: "Example: Word Inclusion | Evals | Kastrax Docs" description: Example of creating a custom metric to evaluate word inclusion in output text. --- import { GithubLink } from "@/components/github-link"; # Word Inclusion Evaluation ✅ [EN] Source: https://kastrax.ai/en/examples/evals/word-inclusion This example demonstrates how to create a custom metric in Kastrax that evaluates whether specific words appear in the output text. This is a simplified version of our own [keyword coverage eval](/reference/evals/keyword-coverage). ## Overview ✅ The example shows how to: 1. Create a custom metric class 2. Evaluate word presence in responses 3. Calculate inclusion scores 4. Handle different inclusion scenarios ## Setup ✅ ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { Metric, type MetricResult } from '@kastrax/core/eval'; ``` ## Metric Implementation ✅ Create the Word Inclusion metric: ```typescript copy showLineNumbers{3} filename="src/index.ts" interface WordInclusionResult extends MetricResult { score: number; info: { totalWords: number; matchedWords: number; }; } export class WordInclusionMetric extends Metric { private referenceWords: Set; constructor(words: string[]) { super(); this.referenceWords = new Set(words); } async measure(input: string, output: string): Promise { const matchedWords = [...this.referenceWords].filter(k => output.includes(k)); const totalWords = this.referenceWords.size; const coverage = totalWords > 0 ? matchedWords.length / totalWords : 0; return { score: coverage, info: { totalWords: this.referenceWords.size, matchedWords: matchedWords.length, }, }; } } ``` ## Example Usage ✅ ### Full Word Inclusion Example Test when all words are present in the output: ```typescript copy showLineNumbers{46} filename="src/index.ts" const words1 = ['apple', 'banana', 'orange']; const metric1 = new WordInclusionMetric(words1); const input1 = 'List some fruits'; const output1 = 'Here are some fruits: apple, banana, and orange.'; const result1 = await metric1.measure(input1, output1); console.log('Metric Result:', { score: result1.score, info: result1.info, }); // Example Output: // Metric Result: { score: 1, info: { totalWords: 3, matchedWords: 3 } } ``` ### Partial Word Inclusion Example Test when some words are present: ```typescript copy showLineNumbers{64} filename="src/index.ts" const words2 = ['python', 'javascript', 'typescript', 'rust']; const metric2 = new WordInclusionMetric(words2); const input2 = 'What programming languages do you know?'; const output2 = 'I know python and javascript very well.'; const result2 = await metric2.measure(input2, output2); console.log('Metric Result:', { score: result2.score, info: result2.info, }); // Example Output: // Metric Result: { score: 0.5, info: { totalWords: 4, matchedWords: 2 } } ``` ### No Word Inclusion Example Test when no words are present: ```typescript copy showLineNumbers{82} filename="src/index.ts" const words3 = ['cloud', 'server', 'database']; const metric3 = new WordInclusionMetric(words3); const input3 = 'Tell me about your infrastructure'; const output3 = 'We use modern technology for our systems.'; const result3 = await metric3.measure(input3, output3); console.log('Metric Result:', { score: result3.score, info: result3.info, }); // Example Output: // Metric Result: { score: 0, info: { totalWords: 3, matchedWords: 0 } } ``` ## Understanding the Results ✅ The metric provides: 1. A word inclusion score between 0 and 1: - 1.0: Complete inclusion - all words present - 0.5-0.9: Partial inclusion - some words present - 0.0: No inclusion - no words found 2. Detailed statistics including: - Total words to check - Number of matched words - Inclusion ratio calculation - Empty input handling




--- title: "Examples List: Workflows, Agents, RAG | Kastrax Docs" description: "Explore practical examples of AI development with Kastrax, including text generation, RAG implementations, structured outputs, and multi-modal interactions. Learn how to build AI applications using OpenAI, Anthropic, and Google Gemini." --- import { CardItems, CardItem, CardTitle } from "@/components/example-cards"; import { Tabs } from "nextra/components"; # Examples [EN] Source: https://kastrax.ai/en/examples The Examples section is a short list of example projects demonstrating basic AI engineering with Kastrax, including text generation, structured output, streaming responses, retrieval‐augmented generation (RAG), and voice. --- title: Memory Processors description: Example of using memory processors to filter and transform recalled messages --- # Memory Processors [EN] Source: https://kastrax.ai/en/examples/memory/memory-processors This example demonstrates how to use memory processors to limit token usage, filter out tool calls, and create a simple custom processor. ## Setup First, install the memory package: ```bash copy npm install @kastrax/memory@latest # or pnpm add @kastrax/memory@latest # or yarn add @kastrax/memory@latest ``` ## Basic Memory Setup with Processors ```typescript import { Memory } from "@kastrax/memory"; import { TokenLimiter, ToolCallFilter } from "@kastrax/memory/processors"; // Create memory with processors const memory = new Memory({ processors: [new TokenLimiter(127000), new ToolCallFilter()], }); ``` ## Using Token Limiting The `TokenLimiter` helps you stay within your model's context window: ```typescript import { Memory } from "@kastrax/memory"; import { TokenLimiter } from "@kastrax/memory/processors"; // Set up memory with a token limit const memory = new Memory({ processors: [ // Limit to approximately 12700 tokens (for GPT-4o) new TokenLimiter(127000), ], }); ``` You can also specify a different encoding if needed: ```typescript import { Memory } from "@kastrax/memory"; import { TokenLimiter } from "@kastrax/memory/processors"; import cl100k_base from "js-tiktoken/ranks/cl100k_base"; const memory = new Memory({ processors: [ new TokenLimiter({ limit: 16000, encoding: cl100k_base, // Specific encoding for certain models eg GPT-3.5 }), ], }); ``` ## Filtering Tool Calls The `ToolCallFilter` processor removes tool calls and their results from memory: ```typescript import { Memory } from "@kastrax/memory"; import { ToolCallFilter } from "@kastrax/memory/processors"; // Filter out all tool calls const memoryNoTools = new Memory({ processors: [new ToolCallFilter()], }); // Filter specific tool calls const memorySelectiveFilter = new Memory({ processors: [ new ToolCallFilter({ exclude: ["imageGenTool", "clipboardTool"], }), ], }); ``` ## Combining Multiple Processors Processors run in the order they are defined: ```typescript import { Memory } from "@kastrax/memory"; import { TokenLimiter, ToolCallFilter } from "@kastrax/memory/processors"; const memory = new Memory({ processors: [ // First filter out tool calls new ToolCallFilter({ exclude: ["imageGenTool"] }), // Then limit tokens (always put token limiter last for accurate measuring after other filters/transforms) new TokenLimiter(16000), ], }); ``` ## Creating a Simple Custom Processor You can create your own processors by extending the `MemoryProcessor` class: ```typescript import type { CoreMessage } from "@kastrax/core"; import { MemoryProcessor } from "@kastrax/core/memory"; import { Memory } from "@kastrax/memory"; // Simple processor that keeps only the most recent messages class RecentMessagesProcessor extends MemoryProcessor { private limit: number; constructor(limit: number = 10) { super(); this.limit = limit; } process(messages: CoreMessage[]): CoreMessage[] { // Keep only the most recent messages return messages.slice(-this.limit); } } // Use the custom processor const memory = new Memory({ processors: [ new RecentMessagesProcessor(5), // Keep only the last 5 messages new TokenLimiter(16000), ], }); ``` Note: this example is for simplicity of understanding how custom processors work - you can limit messages more efficiently using `new Memory({ options: { lastMessages: 5 } })`. Memory processors are applied after memories are retrieved from storage, while `options.lastMessages` is applied before messages are fetched from storage. ## Integration with an Agent Here's how to use memory with processors in an agent: ```typescript import { Agent } from "@kastrax/core/agent"; import { Memory, TokenLimiter, ToolCallFilter } from "@kastrax/memory"; import { openai } from "@ai-sdk/openai"; // Set up memory with processors const memory = new Memory({ processors: [ new ToolCallFilter({ exclude: ["debugTool"] }), new TokenLimiter(16000), ], }); // Create an agent with the memory const agent = new Agent({ name: "ProcessorAgent", instructions: "You are a helpful assistant with processed memory.", model: openai("gpt-4o-mini"), memory, }); // Use the agent const response = await agent.stream("Hi, can you remember our conversation?", { threadId: "unique-thread-id", resourceId: "user-123", }); for await (const chunk of response.textStream) { process.stdout.write(chunk); } ``` ## Summary This example demonstrates: 1. Setting up memory with token limiting to prevent context window overflow 2. Filtering out tool calls to reduce noise and token usage 3. Creating a simple custom processor to keep only recent messages 4. Combining multiple processors in the correct order 5. Integrating processed memory with an agent For more details on memory processors, check out the [Memory Processors documentation](/docs/memory/memory-processors). # Memory with LibSQL ✅ [EN] Source: https://kastrax.ai/en/examples/memory/memory-with-libsql This example demonstrates how to use Kastrax's memory system with LibSQL, which is the default storage and vector database backend. ## Quickstart ✅ Initializing memory with no settings will use LibSQL as the storage and vector database. ```typescript copy showLineNumbers import { Memory } from '@kastrax/memory'; import { Agent } from '@kastrax/core/agent'; // Initialize memory with LibSQL defaults const memory = new Memory(); const memoryAgent = new Agent({ name: "Memory Agent", instructions: "You are an AI agent with the ability to automatically recall memories from previous interactions.", model: openai('gpt-4o-mini'), memory, }); ``` ## Custom Configuration ✅ If you need more control, you can explicitly configure the storage, vector database, and embedder. If you omit either `storage` or `vector`, LibSQL will be used as the default for the omitted option. This lets you use a different provider for just storage or just vector search if needed. ```typescript import { openai } from '@ai-sdk/openai'; import { LibSQLStore } from "@kastrax/core/storage/libsql"; import { LibSQLVector } from "@kastrax/core/vector/libsql"; const customMemory = new Memory({ storage: new LibSQLStore({ config: { url: process.env.DATABASE_URL || "file:local.db", }, }), vector: new LibSQLVector({ connectionUrl: process.env.DATABASE_URL || "file:local.db", }), options: { lastMessages: 10, semanticRecall: { topK: 3, messageRange: 2, }, }, }); const memoryAgent = new Agent({ name: "Memory Agent", instructions: "You are an AI agent with the ability to automatically recall memories from previous interactions. You may have conversations that last hours, days, months, or years. If you don't know it already you should ask for the users name and some info about them.", model: openai('gpt-4o-mini'), memory: customMemory, }); ``` ## Usage Example ✅ ```typescript import { randomUUID } from "crypto"; // Start a conversation const threadId = randomUUID(); const resourceId = "SOME_USER_ID"; // Start with a system message const response1 = await memoryAgent.stream( [ { role: "system", content: `Chat with user started now ${new Date().toISOString()}. Don't mention this message.`, }, ], { resourceId, threadId, }, ); // Send user message const response2 = await memoryAgent.stream("What can you help me with?", { threadId, resourceId, }); // Use semantic search to find relevant messages const response3 = await memoryAgent.stream("What did we discuss earlier?", { threadId, resourceId, memoryOptions: { lastMessages: false, semanticRecall: { topK: 3, // Get top 3 most relevant messages messageRange: 2, // Include context around each match }, }, }); ``` The example shows: 1. Setting up LibSQL storage with vector search capabilities 2. Configuring memory options for message history and semantic search 3. Creating an agent with memory integration 4. Using semantic search to find relevant messages in conversation history 5. Including context around matched messages using `messageRange` # Memory with Postgres ✅ [EN] Source: https://kastrax.ai/en/examples/memory/memory-with-pg This example demonstrates how to use Kastrax's memory system with PostgreSQL as the storage backend. ## Setup ✅ First, set up the memory system with PostgreSQL storage and vector capabilities: ```typescript import { Memory } from "@kastrax/memory"; import { PostgresStore, PgVector } from "@kastrax/pg"; import { Agent } from "@kastrax/core/agent"; import { openai } from "@ai-sdk/openai"; // PostgreSQL connection details const host = "localhost"; const port = 5432; const user = "postgres"; const database = "postgres"; const password = "postgres"; const connectionString = `postgresql://${user}:${password}@${host}:${port}`; // Initialize memory with PostgreSQL storage and vector search const memory = new Memory({ storage: new PostgresStore({ host, port, user, database, password, }), vector: new PgVector(connectionString), options: { lastMessages: 10, semanticRecall: { topK: 3, messageRange: 2, }, }, }); // Create an agent with memory capabilities const chefAgent = new Agent({ name: "chefAgent", instructions: "You are Michel, a practical and experienced home chef who helps people cook great meals with whatever ingredients they have available.", model: openai("gpt-4o-mini"), memory, }); ``` ## Usage Example ✅ ```typescript import { randomUUID } from "crypto"; // Start a conversation const threadId = randomUUID(); const resourceId = "SOME_USER_ID"; // Ask about ingredients const response1 = await chefAgent.stream( "In my kitchen I have: pasta, canned tomatoes, garlic, olive oil, and some dried herbs (basil and oregano). What can I make?", { threadId, resourceId, }, ); // Ask about different ingredients const response2 = await chefAgent.stream( "Now I'm over at my friend's house, and they have: chicken thighs, coconut milk, sweet potatoes, and curry powder.", { threadId, resourceId, }, ); // Use memory to recall previous conversation const response3 = await chefAgent.stream( "What did we cook before I went to my friends house?", { threadId, resourceId, memoryOptions: { lastMessages: 3, // Get last 3 messages for context }, }, ); ``` The example shows: 1. Setting up PostgreSQL storage with vector search capabilities 2. Configuring memory options for message history and semantic search 3. Creating an agent with memory integration 4. Using the agent to maintain conversation context across multiple interactions # Memory with Upstash ✅ [EN] Source: https://kastrax.ai/en/examples/memory/memory-with-upstash This example demonstrates how to use Kastrax's memory system with Upstash as the storage backend. ## Setup ✅ First, set up the memory system with Upstash storage and vector capabilities: ```typescript import { Memory } from "@kastrax/memory"; import { UpstashStore, UpstashVector } from "@kastrax/upstash"; import { Agent } from "@kastrax/core/agent"; import { openai } from "@ai-sdk/openai"; // Initialize memory with Upstash storage and vector search const memory = new Memory({ storage: new UpstashStore({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }), vector: new UpstashVector({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, }), options: { lastMessages: 10, semanticRecall: { topK: 3, messageRange: 2, }, }, }); // Create an agent with memory capabilities const chefAgent = new Agent({ name: "chefAgent", instructions: "You are Michel, a practical and experienced home chef who helps people cook great meals with whatever ingredients they have available.", model: openai("gpt-4o-mini"), memory, }); ``` ## Environment Setup ✅ Make sure to set up your Upstash credentials in the environment variables: ```bash UPSTASH_REDIS_REST_URL=your-redis-url UPSTASH_REDIS_REST_TOKEN=your-redis-token ``` ## Usage Example ✅ ```typescript import { randomUUID } from "crypto"; // Start a conversation const threadId = randomUUID(); const resourceId = "SOME_USER_ID"; // Ask about ingredients const response1 = await chefAgent.stream( "In my kitchen I have: pasta, canned tomatoes, garlic, olive oil, and some dried herbs (basil and oregano). What can I make?", { threadId, resourceId, }, ); // Ask about different ingredients const response2 = await chefAgent.stream( "Now I'm over at my friend's house, and they have: chicken thighs, coconut milk, sweet potatoes, and curry powder.", { threadId, resourceId, }, ); // Use memory to recall previous conversation const response3 = await chefAgent.stream( "What did we cook before I went to my friends house?", { threadId, resourceId, memoryOptions: { lastMessages: 3, // Get last 3 messages for context semanticRecall: { topK: 2, // Also get 2 most relevant messages messageRange: 2, // Include context around matches }, }, }, ); ``` The example shows: 1. Setting up Upstash storage with vector search capabilities 2. Configuring environment variables for Upstash connection 3. Creating an agent with memory integration 4. Using both recent history and semantic search in the same query --- title: Streaming Working Memory (advanced) description: Example of using working memory to maintain a todo list across conversations --- # Streaming Working Memory (advanced) [EN] Source: https://kastrax.ai/en/examples/memory/streaming-working-memory-advanced This example demonstrates how to create an agent that maintains a todo list using working memory, even with minimal context. For a simpler introduction to working memory, see the [basic working memory example](/examples/memory/streaming-working-memory). ## Setup Let's break down how to create an agent with working memory capabilities. We'll build a todo list manager that remembers tasks even with minimal context. ### 1. Setting up Memory First, we'll configure the memory system with a short context window since we'll be using working memory to maintain state. Memory uses LibSQL storage by default, but you can use any other [storage provider](/docs/agents/agent-memory#storage-options) if needed: ```typescript import { Memory } from "@kastrax/memory"; const memory = new Memory({ options: { lastMessages: 1, // working memory means we can have a shorter context window and still maintain conversational coherence workingMemory: { enabled: true, }, }, }); ``` ### 2. Defining the Working Memory Template Next, we'll define a template that shows the agent how to structure the todo list data. The template uses Markdown to represent the data structure. This helps the agent understand what information to track for each todo item. ```typescript const memory = new Memory({ options: { lastMessages: 1, workingMemory: { enabled: true, template: ` # Todo List ## Item Status - Active items: - Example (Due: Feb 7 3028, Started: Feb 7 2025) - Description: This is an example task ## Completed - None yet `, }, }, }); ``` ### 3. Creating the Todo List Agent Finally, we'll create an agent that uses this memory system. The agent's instructions define how it should interact with users and manage the todo list. ```typescript import { openai } from "@ai-sdk/openai"; const todoAgent = new Agent({ name: "TODO Agent", instructions: "You are a helpful todolist AI agent. Help the user manage their todolist. If there is no list yet ask them what to add! If there is a list always print it out when the chat starts. For each item add emojis, dates, titles (with an index number starting at 1), descriptions, and statuses. For each piece of info add an emoji to the left of it. Also support subtask lists with bullet points inside a box. Help the user timebox each task by asking them how long it will take.", model: openai("gpt-4o-mini"), memory, }); ``` **Note:** The template and instructions are optional - when `workingMemory.enabled` is set to `true`, a default system message is automatically injected to help the agent understand how to use working memory. ## Usage Example The agent's responses will contain XML-like `$data` tags that Kastrax uses to automatically update the working memory. We'll look at two ways to handle this: ### Basic Usage For simple cases, you can use `maskStreamTags` to hide the working memory updates from users: ```typescript import { randomUUID } from "crypto"; import { maskStreamTags } from "@kastrax/core/utils"; // Start a conversation const threadId = randomUUID(); const resourceId = "SOME_USER_ID"; // Add a new todo item const response = await todoAgent.stream( "Add a task: Build a new feature for our app. It should take about 2 hours and needs to be done by next Friday.", { threadId, resourceId, }, ); // Process the stream, hiding working memory updates for await (const chunk of maskStreamTags( response.textStream, "working_memory", )) { process.stdout.write(chunk); } ``` ### Advanced Usage with UI Feedback For a better user experience, you can show loading states while working memory is being updated: ```typescript // Same imports and setup as above... // Add lifecycle hooks to provide UI feedback const maskedStream = maskStreamTags(response.textStream, "working_memory", { // Called when a working_memory tag starts onStart: () => showLoadingSpinner("Updating todo list..."), // Called when a working_memory tag ends onEnd: () => hideLoadingSpinner(), // Called with the content that was masked onMask: (chunk) => console.debug("Updated todo list:", chunk), }); // Process the masked stream for await (const chunk of maskedStream) { process.stdout.write(chunk); } ``` The example demonstrates: 1. Setting up a memory system with working memory enabled 2. Creating a todo list template with structured XML 3. Using `maskStreamTags` to hide memory updates from users 4. Providing UI loading states during memory updates with lifecycle hooks Even with only one message in context (`lastMessages: 1`), the agent maintains the complete todo list in working memory. Each time the agent responds, it updates the working memory with the current state of the todo list, ensuring persistence across interactions. To learn more about agent memory, including other memory types and storage options, check out the [Memory documentation](/docs/agents/agent-memory) page. --- title: Streaming Working Memory description: Example of using working memory with an agent --- # Streaming Working Memory [EN] Source: https://kastrax.ai/en/examples/memory/streaming-working-memory This example demonstrates how to create an agent that maintains a working memory for relevant conversational details like the users name, location, or preferences. ## Setup First, set up the memory system with working memory enabled. Memory uses LibSQL storage by default, but you can use any other [storage provider](/docs/agents/agent-memory#storage-options) if needed: ### Text Stream Mode (Default) ```typescript import { Memory } from "@kastrax/memory"; const memory = new Memory({ options: { workingMemory: { enabled: true, use: "text-stream", // this is the default mode }, }, }); ``` ### Tool Call Mode Alternatively, you can use tool calls for working memory updates. This mode is required when using `toDataStream()` as text-stream mode is not compatible with data streaming: ```typescript const toolCallMemory = new Memory({ options: { workingMemory: { enabled: true, use: "tool-call", // Required for toDataStream() compatibility }, }, }); ``` Add the memory instance to an agent: ```typescript import { openai } from "@ai-sdk/openai"; const agent = new Agent({ name: "Memory agent", instructions: "You are a helpful AI assistant.", model: openai("gpt-4o-mini"), memory, // or toolCallMemory }); ``` ## Usage Example Now that working memory is set up you can interact with the agent and it will remember key details about interactions. ### Text Stream Mode In text stream mode, the agent includes working memory updates directly in its responses: ```typescript import { randomUUID } from "crypto"; import { maskStreamTags } from "@kastrax/core/utils"; const threadId = randomUUID(); const resourceId = "SOME_USER_ID"; const response = await agent.stream("Hello, my name is Jane", { threadId, resourceId, }); // Process response stream, hiding working memory tags for await (const chunk of maskStreamTags( response.textStream, "working_memory", )) { process.stdout.write(chunk); } ``` ### Tool Call Mode In tool call mode, the agent uses a dedicated tool to update working memory: ```typescript const toolCallResponse = await toolCallAgent.stream("Hello, my name is Jane", { threadId, resourceId, }); // No need to mask working memory tags since updates happen through tool calls for await (const chunk of toolCallResponse.textStream) { process.stdout.write(chunk); } ``` ### Handling response data In text stream mode, the response stream will contain `$data` tagged data where `$data` is Markdown-formatted content. Kastrax picks up these tags and automatically updates working memory with the data returned by the LLM. To prevent showing this data to users you can use the `maskStreamTags` util as shown above. In tool call mode, working memory updates happen through tool calls, so there's no need to mask any tags. ## Summary This example demonstrates: 1. Setting up memory with working memory enabled in either text-stream or tool-call mode 2. Using `maskStreamTags` to hide memory updates in text-stream mode 3. The agent maintaining relevant user info between interactions in both modes 4. Different approaches to handling working memory updates ## Advanced use cases For examples on controlling which information is relevant for working memory, or showing loading states while working memory is being saved, see our [advanced working memory example](/examples/memory/streaming-working-memory-advanced). To learn more about agent memory, including other memory types and storage options, check out the [Memory documentation](/docs/agents/agent-memory) page. --- title: AI SDK useChat Hook description: Example showing how to integrate Kastrax memory with the Vercel AI SDK useChat hook. --- # Example: AI SDK `useChat` Hook ✅ [EN] Source: https://kastrax.ai/en/examples/memory/use-chat Integrating Kastrax's memory with frontend frameworks like React using the Vercel AI SDK's `useChat` hook requires careful handling of message history to avoid duplication. This example shows the recommended pattern. ## Preventing Message Duplication with `useChat` ✅ The default behavior of `useChat` sends the entire chat history with each request. Since Kastrax's memory automatically retrieves history based on `threadId`, sending the full history from the client leads to duplicate messages in the context window and storage. **Solution:** Configure `useChat` to send **only the latest message** along with your `threadId` and `resourceId`. ```typescript // components/Chat.tsx (React Example) import { useChat } from "ai/react"; export function Chat({ threadId, resourceId }) { const { messages, input, handleInputChange, handleSubmit } = useChat({ api: "/api/chat", // Your backend endpoint // Pass only the latest message and custom IDs experimental_prepareRequestBody: (request) => { // Ensure messages array is not empty and get the last message const lastMessage = request.messages.length > 0 ? request.messages[request.messages.length - 1] : null; // Return the structured body for your API route return { message: lastMessage, // Send only the most recent message content/role threadId, resourceId, }; }, // Optional: Initial messages if loading history from backend // initialMessages: loadedMessages, }); // ... rest of your chat UI component return (
{/* Render messages */}
); } // app/api/chat/route.ts (Next.js Example) import { Agent } from "@kastrax/core/agent"; import { Memory } from "@kastrax/memory"; import { openai } from "@ai-sdk/openai"; import { CoreMessage } from "@kastrax/core"; // Import CoreMessage const agent = new Agent({ name: "ChatAgent", instructions: "You are a helpful assistant.", model: openai("gpt-4o"), memory: new Memory(), // Assumes default memory setup }); export async function POST(request: Request) { // Get data structured by experimental_prepareRequestBody const { message, threadId, resourceId }: { message: CoreMessage | null; threadId: string; resourceId: string } = await request.json(); // Handle cases where message might be null (e.g., initial load or error) if (!message || !message.content) { // Return an appropriate response or error return new Response("Missing message content", { status: 400 }); } // Process with memory using the single message content const stream = await agent.stream(message.content, { threadId, resourceId, // Pass other message properties if needed, e.g., role // messageOptions: { role: message.role } }); // Return the streaming response return stream.toDataStreamResponse(); } ``` See the [AI SDK documentation on message persistence](https://sdk.vercel.ai/docs/ai-sdk-ui/chatbot-message-persistence) for more background. ## Basic Thread Management UI ✅ While this page focuses on `useChat`, you can also build UIs for managing threads (listing, creating, selecting). This typically involves backend API endpoints that interact with Kastrax's memory functions like `memory.getThreadsByResourceId()` and `memory.createThread()`. ```typescript // Conceptual React component for a thread list import React, { useState, useEffect } from 'react'; // Assume API functions exist: fetchThreads, createNewThread async function fetchThreads(userId: string): Promise<{ id: string; title: string }[]> { /* ... */ } async function createNewThread(userId: string): Promise<{ id: string; title: string }> { /* ... */ } function ThreadList({ userId, currentThreadId, onSelectThread }) { const [threads, setThreads] = useState([]); // ... loading and error states ... useEffect(() => { // Fetch threads for userId }, [userId]); const handleCreateThread = async () => { // Call createNewThread API, update state, select new thread }; // ... render UI with list of threads and New Conversation button ... return (

Conversations

    {threads.map(thread => (
  • ))}
); } // Example Usage in a Parent Chat Component function ChatApp() { const userId = "user_123"; const [currentThreadId, setCurrentThreadId] = useState(null); return (
{currentThreadId ? ( // Your useChat component ) : (
Select or start a conversation.
)}
); } ``` ## Related ✅ - **[Getting Started](../../docs/memory/overview.mdx)**: Covers the core concepts of `resourceId` and `threadId`. - **[Memory Reference](../../reference/memory/Memory.mdx)**: API details for `Memory` class methods. --- title: "Example: Adjusting Chunk Delimiters | RAG | Kastrax Docs" description: Adjust chunk delimiters in Kastrax to better match your content structure. --- import { GithubLink } from "@/components/github-link"; # Adjust Chunk Delimiters ✅ [EN] Source: https://kastrax.ai/en/examples/rag/chunking/adjust-chunk-delimiters When processing large documents, you may want to control how the text is split into smaller chunks. By default, documents are split on newlines, but you can customize this behavior to better match your content structure. This example shows how to specify a custom delimiter for chunking documents. ```tsx copy import { MDocument } from "@kastrax/rag"; const doc = MDocument.fromText("Your plain text content..."); const chunks = await doc.chunk({ separator: "\n", }); ```




--- title: "Example: Adjusting The Chunk Size | RAG | Kastrax Docs" description: Adjust chunk size in Kastrax to better match your content and memory requirements. --- import { GithubLink } from "@/components/github-link"; # Adjust Chunk Size ✅ [EN] Source: https://kastrax.ai/en/examples/rag/chunking/adjust-chunk-size When processing large documents, you might need to adjust how much text is included in each chunk. By default, chunks are 1024 characters long, but you can customize this size to better match your content and memory requirements. This example shows how to set a custom chunk size when splitting documents. ```tsx copy import { MDocument } from "@kastrax/rag"; const doc = MDocument.fromText("Your plain text content..."); const chunks = await doc.chunk({ size: 512, }); ```




--- title: "Example: Semantically Chunking HTML | RAG | Kastrax Docs" description: Chunk HTML content in Kastrax to semantically chunk the document. --- import { GithubLink } from "@/components/github-link"; # Semantically Chunking HTML ✅ [EN] Source: https://kastrax.ai/en/examples/rag/chunking/chunk-html When working with HTML content, you often need to break it down into smaller, manageable pieces while preserving the document structure. The chunk method splits HTML content intelligently, maintaining the integrity of HTML tags and elements. This example shows how to chunk HTML documents for search or retrieval purposes. ```tsx copy import { MDocument } from "@kastrax/rag"; const html = `

h1 content...

p content...

`; const doc = MDocument.fromHTML(html); const chunks = await doc.chunk({ headers: [ ["h1", "Header 1"], ["p", "Paragraph"], ], }); console.log(chunks); ```




--- title: "Example: Semantically Chunking JSON | RAG | Kastrax Docs" description: Chunk JSON data in Kastrax to semantically chunk the document. --- import { GithubLink } from "@/components/github-link"; # Semantically Chunking JSON ✅ [EN] Source: https://kastrax.ai/en/examples/rag/chunking/chunk-json When working with JSON data, you need to split it into smaller pieces while preserving the object structure. The chunk method breaks down JSON content intelligently, maintaining the relationships between keys and values. This example shows how to chunk JSON documents for search or retrieval purposes. ```tsx copy import { MDocument } from "@kastrax/rag"; const testJson = { name: "John Doe", age: 30, email: "john.doe@example.com", }; const doc = MDocument.fromJSON(JSON.stringify(testJson)); const chunks = await doc.chunk({ maxSize: 100, }); console.log(chunks); ```




--- title: "Example: Semantically Chunking Markdown | RAG | Kastrax Docs" description: Example of using Kastrax to chunk markdown documents for search or retrieval purposes. --- import { GithubLink } from "@/components/github-link"; # Chunk Markdown ✅ [EN] Source: https://kastrax.ai/en/examples/rag/chunking/chunk-markdown Markdown is more information-dense than raw HTML, making it easier to work with for RAG pipelines. When working with markdown, you need to split it into smaller pieces while preserving headers and formatting. The `chunk` method handles Markdown-specific elements like headers, lists, and code blocks intelligently. This example shows how to chunk markdown documents for search or retrieval purposes. ```tsx copy import { MDocument } from "@kastrax/rag"; const doc = MDocument.fromMarkdown("# Your markdown content..."); const chunks = await doc.chunk(); ```




--- title: "Example: Semantically Chunking Text | RAG | Kastrax Docs" description: Example of using Kastrax to split large text documents into smaller chunks for processing. --- import { GithubLink } from "@/components/github-link"; # Chunk Text ✅ [EN] Source: https://kastrax.ai/en/examples/rag/chunking/chunk-text When working with large text documents, you need to break them down into smaller, manageable pieces for processing. The chunk method splits text content into segments that can be used for search, analysis, or retrieval. This example shows how to split plain text into chunks using default settings. ```tsx copy import { MDocument } from "@kastrax/rag"; const doc = MDocument.fromText("Your plain text content..."); const chunks = await doc.chunk(); ```




--- title: "Example: Embedding Chunk Arrays | RAG | Kastrax Docs" description: Example of using Kastrax to generate embeddings for an array of text chunks for similarity search. --- import { GithubLink } from "@/components/github-link"; # Embed Chunk Array ✅ [EN] Source: https://kastrax.ai/en/examples/rag/embedding/embed-chunk-array After chunking documents, you need to convert the text chunks into numerical vectors that can be used for similarity search. The `embed` method transforms text chunks into embeddings using your chosen provider and model. This example shows how to generate embeddings for an array of text chunks. ```tsx copy import { openai } from '@ai-sdk/openai'; import { MDocument } from '@kastrax/rag'; import { embed } from 'ai'; const doc = MDocument.fromText("Your text content..."); const chunks = await doc.chunk(); const { embeddings } = await embedMany({ model: openai.embedding('text-embedding-3-small'), values: chunks.map(chunk => chunk.text), }); ```




--- title: "Example: Embedding Text Chunks | RAG | Kastrax Docs" description: Example of using Kastrax to generate an embedding for a single text chunk for similarity search. --- import { GithubLink } from "@/components/github-link"; # Embed Text Chunk ✅ [EN] Source: https://kastrax.ai/en/examples/rag/embedding/embed-text-chunk When working with individual text chunks, you need to convert them into numerical vectors for similarity search. The `embed` method transforms a single text chunk into an embedding using your chosen provider and model. ```tsx copy import { openai } from '@ai-sdk/openai'; import { MDocument } from '@kastrax/rag'; import { embed } from 'ai'; const doc = MDocument.fromText("Your text content..."); const chunks = await doc.chunk(); const { embedding } = await embed({ model: openai.embedding('text-embedding-3-small'), value: chunks[0].text, }); ```




--- title: "Example: Embedding Text with Cohere | RAG | Kastrax Docs" description: Example of using Kastrax to generate embeddings using Cohere's embedding model. --- import { GithubLink } from "@/components/github-link"; # Embed Text with Cohere ✅ [EN] Source: https://kastrax.ai/en/examples/rag/embedding/embed-text-with-cohere When working with alternative embedding providers, you need a way to generate vectors that match your chosen model's specifications. The `embed` method supports multiple providers, allowing you to switch between different embedding services. This example shows how to generate embeddings using Cohere's embedding model. ```tsx copy import { cohere } from '@ai-sdk/cohere'; import { MDocument } from "@kastrax/rag"; import { embedMany } from 'ai'; const doc = MDocument.fromText("Your text content..."); const chunks = await doc.chunk(); const { embeddings } = await embedMany({ model: cohere.embedding('embed-english-v3.0'), values: chunks.map(chunk => chunk.text), }); ```




--- title: "Example: Metadata Extraction | Retrieval | RAG | Kastrax Docs" description: Example of extracting and utilizing metadata from documents in Kastrax for enhanced document processing and retrieval. --- import { GithubLink } from "@/components/github-link"; # Metadata Extraction ✅ [EN] Source: https://kastrax.ai/en/examples/rag/embedding/metadata-extraction This example demonstrates how to extract and utilize metadata from documents using Kastrax's document processing capabilities. The extracted metadata can be used for document organization, filtering, and enhanced retrieval in RAG systems. ## Overview ✅ The system demonstrates metadata extraction in two ways: 1. Direct metadata extraction from a document 2. Chunking with metadata extraction ## Setup ✅ ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { MDocument } from '@kastrax/rag'; ``` ## Document Creation ✅ Create a document from text content: ```typescript copy showLineNumbers{3} filename="src/index.ts" const doc = MDocument.fromText(`Title: The Benefits of Regular Exercise Regular exercise has numerous health benefits. It improves cardiovascular health, strengthens muscles, and boosts mental wellbeing. Key Benefits: • Reduces stress and anxiety • Improves sleep quality • Helps maintain healthy weight • Increases energy levels For optimal results, experts recommend at least 150 minutes of moderate exercise per week.`); ``` ## 1. Direct Metadata Extraction ✅ Extract metadata directly from the document: ```typescript copy showLineNumbers{17} filename="src/index.ts" // Configure metadata extraction options await doc.extractMetadata({ keywords: true, // Extract important keywords summary: true, // Generate a concise summary }); // Retrieve the extracted metadata const meta = doc.getMetadata(); console.log('Extracted Metadata:', meta); // Example Output: // Extracted Metadata: { // keywords: [ // 'exercise', // 'health benefits', // 'cardiovascular health', // 'mental wellbeing', // 'stress reduction', // 'sleep quality' // ], // summary: 'Regular exercise provides multiple health benefits including improved cardiovascular health, muscle strength, and mental wellbeing. Key benefits include stress reduction, better sleep, weight management, and increased energy. Recommended exercise duration is 150 minutes per week.' // } ``` ## 2. Chunking with Metadata ✅ Combine document chunking with metadata extraction: ```typescript copy showLineNumbers{40} filename="src/index.ts" // Configure chunking with metadata extraction await doc.chunk({ strategy: 'recursive', // Use recursive chunking strategy size: 200, // Maximum chunk size extract: { keywords: true, // Extract keywords per chunk summary: true, // Generate summary per chunk }, }); // Get metadata from chunks const metaTwo = doc.getMetadata(); console.log('Chunk Metadata:', metaTwo); // Example Output: // Chunk Metadata: { // keywords: [ // 'exercise', // 'health benefits', // 'cardiovascular health', // 'mental wellbeing', // 'stress reduction', // 'sleep quality' // ], // summary: 'Regular exercise provides multiple health benefits including improved cardiovascular health, muscle strength, and mental wellbeing. Key benefits include stress reduction, better sleep, weight management, and increased energy. Recommended exercise duration is 150 minutes per week.' // } ```




--- title: "Example: Hybrid Vector Search | RAG | Kastrax Docs" description: Example of using metadata filters with PGVector to enhance vector search results in Kastrax. --- import { GithubLink } from "@/components/github-link"; # Hybrid Vector Search ✅ [EN] Source: https://kastrax.ai/en/examples/rag/query/hybrid-vector-search When you combine vector similarity search with metadata filters, you can create a hybrid search that is more precise and efficient. This approach combines: - Vector similarity search to find the most relevant documents - Metadata filters to refine the search results based on additional criteria This example demonstrates how to use hybrid vector search with Kastrax and PGVector. ## Overview ✅ The system implements filtered vector search using Kastrax and PGVector. Here's what it does: 1. Queries existing embeddings in PGVector with metadata filters 2. Shows how to filter by different metadata fields 3. Demonstrates combining vector similarity with metadata filtering > **Note**: For examples of how to extract metadata from your documents, see the [Metadata Extraction](../embedding/metadata-extraction.mdx) guide. > > To learn how to create and store embeddings, see the [Upsert Embeddings](/examples/rag/upsert/upsert-embeddings) guide. ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_openai_api_key_here POSTGRES_CONNECTION_STRING=your_connection_string_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { embed } from 'ai'; import { PgVector } from '@kastrax/pg'; import { openai } from '@ai-sdk/openai'; ``` ## Vector Store Initialization ✅ Initialize PgVector with your connection string: ```typescript copy showLineNumbers{4} filename="src/index.ts" const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!); ``` ## Example Usage ✅ ### Filter by Metadata Value ```typescript copy showLineNumbers{6} filename="src/index.ts" // Create embedding for the query const { embedding } = await embed({ model: openai.embedding('text-embedding-3-small'), value: '[Insert query based on document here]', }); // Query with metadata filter const result = await pgVector.query({ indexName: 'embeddings', queryVector: embedding, topK: 3, filter: { 'path.to.metadata': { $eq: 'value', }, }, }); console.log('Results:', result); ```




--- title: "Example: Retrieving Top-K Results | RAG | Kastrax Docs" description: Example of using Kastrax to query a vector database and retrieve semantically similar chunks. --- import { GithubLink } from "@/components/github-link"; # Retrieving Top-K Results ✅ [EN] Source: https://kastrax.ai/en/examples/rag/query/retrieve-results After storing embeddings in a vector database, you need to query them to find similar content. The `query` method returns the most semantically similar chunks to your input embedding, ranked by relevance. The `topK` parameter allows you to specify the number of results to return. This example shows how to retrieve similar chunks from a Pinecone vector database. ```tsx copy import { openai } from "@ai-sdk/openai"; import { PineconeVector } from "@kastrax/pinecone"; import { MDocument } from "@kastrax/rag"; import { embedMany } from "ai"; const doc = MDocument.fromText("Your text content..."); const chunks = await doc.chunk(); const { embeddings } = await embedMany({ values: chunks.map(chunk => chunk.text), model: openai.embedding("text-embedding-3-small"), }); const pinecone = new PineconeVector("your-api-key"); await pinecone.createIndex({ indexName: "test_index", dimension: 1536, }); await pinecone.upsert({ indexName: "test_index", vectors: embeddings, metadata: chunks?.map((chunk: any) => ({ text: chunk.text })), }); const topK = 10; const results = await pinecone.query({ indexName: "test_index", queryVector: embeddings[0], topK, }); console.log(results); ```




--- title: "Example: Re-ranking Results with Tools | Retrieval | RAG | Kastrax Docs" description: Example of implementing a RAG system with re-ranking in Kastrax using OpenAI embeddings and PGVector for vector storage. --- import { GithubLink } from "@/components/github-link"; # Re-ranking Results with Tools ✅ [EN] Source: https://kastrax.ai/en/examples/rag/rerank/rerank-rag This example demonstrates how to use Kastrax's vector query tool to implement a Retrieval-Augmented Generation (RAG) system with re-ranking using OpenAI embeddings and PGVector for vector storage. ## Overview ✅ The system implements RAG with re-ranking using Kastrax and OpenAI. Here's what it does: 1. Sets up a Kastrax agent with gpt-4o-mini for response generation 2. Creates a vector query tool with re-ranking capabilities 3. Chunks text documents into smaller segments and creates embeddings from them 4. Stores them in a PostgreSQL vector database 5. Retrieves and re-ranks relevant chunks based on queries 6. Generates context-aware responses using the Kastrax agent ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_openai_api_key_here POSTGRES_CONNECTION_STRING=your_connection_string_here ``` ### Dependencies Then, import the necessary dependencies: ```typescript copy showLineNumbers filename="index.ts" import { openai } from "@ai-sdk/openai"; import { Kastrax } from "@kastrax/core"; import { Agent } from "@kastrax/core/agent"; import { PgVector } from "@kastrax/pg"; import { MDocument, createVectorQueryTool } from "@kastrax/rag"; import { embedMany } from "ai"; ``` ## Vector Query Tool Creation with Re-ranking ✅ Using createVectorQueryTool imported from @kastrax/rag, you can create a tool that can query the vector database and re-rank results: ```typescript copy showLineNumbers{8} filename="index.ts" const vectorQueryTool = createVectorQueryTool({ vectorStoreName: "pgVector", indexName: "embeddings", model: openai.embedding("text-embedding-3-small"), reranker: { model: openai("gpt-4o-mini"), }, }); ``` ## Agent Configuration ✅ Set up the Kastrax agent that will handle the responses: ```typescript copy showLineNumbers{17} filename="index.ts" export const ragAgent = new Agent({ name: "RAG Agent", instructions: `You are a helpful assistant that answers questions based on the provided context. Keep your answers concise and relevant. Important: When asked to answer a question, please base your answer only on the context provided in the tool. If the context doesn't contain enough information to fully answer the question, please state that explicitly.`, model: openai("gpt-4o-mini"), tools: { vectorQueryTool, }, }); ``` ## Instantiate PgVector and Kastrax ✅ Instantiate PgVector and Kastrax with the components: ```typescript copy showLineNumbers{29} filename="index.ts" const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!); export const kastrax = new Kastrax({ agents: { ragAgent }, vectors: { pgVector }, }); const agent = kastrax.getAgent("ragAgent"); ``` ## Document Processing ✅ Create a document and process it into chunks: ```typescript copy showLineNumbers{38} filename="index.ts" const doc1 = MDocument.fromText(` market data shows price resistance levels. technical charts display moving averages. support levels guide trading decisions. breakout patterns signal entry points. price action determines trade timing. baseball cards show gradual value increase. rookie cards command premium prices. card condition affects resale value. authentication prevents fake trading. grading services verify card quality. volume analysis confirms price trends. sports cards track seasonal demand. chart patterns predict movements. mint condition doubles card worth. resistance breaks trigger orders. rare cards appreciate yearly. `); const chunks = await doc1.chunk({ strategy: "recursive", size: 150, overlap: 20, separator: "\n", }); ``` ## Creating and Storing Embeddings ✅ Generate embeddings for the chunks and store them in the vector database: ```typescript copy showLineNumbers{66} filename="index.ts" const { embeddings } = await embedMany({ model: openai.embedding("text-embedding-3-small"), values: chunks.map(chunk => chunk.text), }); const vectorStore = kastrax.getVector("pgVector"); await vectorStore.createIndex({ indexName: "embeddings", dimension: 1536, }); await vectorStore.upsert({ indexName: "embeddings", vectors: embeddings, metadata: chunks?.map((chunk: any) => ({ text: chunk.text })), }); ``` ## Querying with Re-ranking ✅ Try different queries to see how the re-ranking affects results: ```typescript copy showLineNumbers{82} filename="index.ts" const queryOne = 'explain technical trading analysis'; const answerOne = await agent.generate(queryOne); console.log('\nQuery:', queryOne); console.log('Response:', answerOne.text); const queryTwo = 'explain trading card valuation'; const answerTwo = await agent.generate(queryTwo); console.log('\nQuery:', queryTwo); console.log('Response:', answerTwo.text); const queryThree = 'how do you analyze market resistance'; const answerThree = await agent.generate(queryThree); console.log('\nQuery:', queryThree); console.log('Response:', answerThree.text); ```




--- title: "Example: Re-ranking Results | Retrieval | RAG | Kastrax Docs" description: Example of implementing semantic re-ranking in Kastrax using OpenAI embeddings and PGVector for vector storage. --- import { GithubLink } from "@/components/github-link"; # Re-ranking Results ✅ [EN] Source: https://kastrax.ai/en/examples/rag/rerank/rerank This example demonstrates how to implement a Retrieval-Augmented Generation (RAG) system with re-ranking using Kastrax, OpenAI embeddings, and PGVector for vector storage. ## Overview ✅ The system implements RAG with re-ranking using Kastrax and OpenAI. Here's what it does: 1. Chunks text documents into smaller segments and creates embeddings from them 2. Stores vectors in a PostgreSQL database 3. Performs initial vector similarity search 4. Re-ranks results using Kastrax's rerank function, combining vector similarity, semantic relevance, and position scores 5. Compares initial and re-ranked results to show improvements ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_openai_api_key_here POSTGRES_CONNECTION_STRING=your_connection_string_here ``` ### Dependencies Then, import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { PgVector } from '@kastrax/pg'; import { MDocument, rerank } from '@kastrax/rag'; import { embedMany, embed } from 'ai'; ``` ## Document Processing ✅ Create a document and process it into chunks: ```typescript copy showLineNumbers{7} filename="src/index.ts" const doc1 = MDocument.fromText(` market data shows price resistance levels. technical charts display moving averages. support levels guide trading decisions. breakout patterns signal entry points. price action determines trade timing. `); const chunks = await doc1.chunk({ strategy: 'recursive', size: 150, overlap: 20, separator: '\n', }); ``` ## Creating and Storing Embeddings ✅ Generate embeddings for the chunks and store them in the vector database: ```typescript copy showLineNumbers{36} filename="src/index.ts" const { embeddings } = await embedMany({ values: chunks.map(chunk => chunk.text), model: openai.embedding('text-embedding-3-small'), }); const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!); await pgVector.createIndex({ indexName: 'embeddings', dimension: 1536, }); await pgVector.upsert({ indexName: 'embeddings', vectors: embeddings, metadata: chunks?.map((chunk: any) => ({ text: chunk.text })), }); ``` ## Vector Search and Re-ranking ✅ Perform vector search and re-rank the results: ```typescript copy showLineNumbers{51} filename="src/index.ts" const query = 'explain technical trading analysis'; // Get query embedding const { embedding: queryEmbedding } = await embed({ value: query, model: openai.embedding('text-embedding-3-small'), }); // Get initial results const initialResults = await pgVector.query({ indexName: 'embeddings', queryVector: queryEmbedding, topK: 3, }); // Re-rank results const rerankedResults = await rerank(initialResults, query, openai('gpt-4o-mini'), { weights: { semantic: 0.5, // How well the content matches the query semantically vector: 0.3, // Original vector similarity score position: 0.2 // Preserves original result ordering }, topK: 3, }); ``` The weights control how different factors influence the final ranking: - `semantic`: Higher values prioritize semantic understanding and relevance to the query - `vector`: Higher values favor the original vector similarity scores - `position`: Higher values help maintain the original ordering of results ## Comparing Results ✅ Print both initial and re-ranked results to see the improvement: ```typescript copy showLineNumbers{72} filename="src/index.ts" console.log('Initial Results:'); initialResults.forEach((result, index) => { console.log(`Result ${index + 1}:`, { text: result.metadata.text, score: result.score, }); }); console.log('Re-ranked Results:'); rerankedResults.forEach(({ result, score, details }, index) => { console.log(`Result ${index + 1}:`, { text: result.metadata.text, score: score, semantic: details.semantic, vector: details.vector, position: details.position, }); }); ``` The re-ranked results show how combining vector similarity with semantic understanding can improve retrieval quality. Each result includes: - Overall score combining all factors - Semantic relevance score from the language model - Vector similarity score from the embedding comparison - Position-based score for maintaining original order when appropriate




--- title: "Example: Reranking with Cohere | RAG | Kastrax Docs" description: Example of using Kastrax to improve document retrieval relevance with Cohere's reranking service. --- # Reranking with Cohere ✅ [EN] Source: https://kastrax.ai/en/examples/rag/rerank/reranking-with-cohere When retrieving documents for RAG, initial vector similarity search may miss important semantic matches. Cohere's reranking service helps improve result relevance by reordering documents using multiple scoring factors. ```typescript import { rerank } from "@kastrax/rag"; const results = rerank( searchResults, "deployment configuration", cohere("rerank-v3.5"), { topK: 5, weights: { semantic: 0.4, vector: 0.4, position: 0.2 } } ); ``` ## Links ✅ - [rerank() reference](/reference/rag/rerank.mdx) - [Retrieval docs](/reference/rag/retrieval.mdx) --- title: "Example: Upsert Embeddings | RAG | Kastrax Docs" description: Examples of using Kastrax to store embeddings in various vector databases for similarity search. --- import { Tabs } from "nextra/components"; import { GithubLink } from "@/components/github-link"; # Upsert Embeddings ✅ [EN] Source: https://kastrax.ai/en/examples/rag/upsert/upsert-embeddings After generating embeddings, you need to store them in a database that supports vector similarity search. This example shows how to store embeddings in various vector databases for later retrieval. The `PgVector` class provides methods to create indexes and insert embeddings into PostgreSQL with the pgvector extension. ```tsx copy import { openai } from "@ai-sdk/openai"; import { PgVector } from "@kastrax/pg"; import { MDocument } from "@kastrax/rag"; import { embedMany } from "ai"; const doc = MDocument.fromText("Your text content..."); const chunks = await doc.chunk(); const { embeddings } = await embedMany({ values: chunks.map(chunk => chunk.text), model: openai.embedding("text-embedding-3-small"), }); const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!); await pgVector.createIndex({ indexName: "test_index", dimension: 1536, }); await pgVector.upsert({ indexName: "test_index", vectors: embeddings, metadata: chunks?.map((chunk: any) => ({ text: chunk.text })), }); ```


The `PineconeVector` class provides methods to create indexes and insert embeddings into Pinecone, a managed vector database service. ```tsx copy import { openai } from '@ai-sdk/openai'; import { PineconeVector } from '@kastrax/pinecone'; import { MDocument } from '@kastrax/rag'; import { embedMany } from 'ai'; const doc = MDocument.fromText('Your text content...'); const chunks = await doc.chunk(); const { embeddings } = await embedMany({ values: chunks.map(chunk => chunk.text), model: openai.embedding('text-embedding-3-small'), }); const pinecone = new PineconeVector(process.env.PINECONE_API_KEY!); await pinecone.createIndex({ indexName: 'testindex', dimension: 1536, }); await pinecone.upsert({ indexName: 'testindex', vectors: embeddings, metadata: chunks?.map(chunk => ({ text: chunk.text })), }); ```


The `QdrantVector` class provides methods to create collections and insert embeddings into Qdrant, a high-performance vector database. ```tsx copy import { openai } from '@ai-sdk/openai'; import { QdrantVector } from '@kastrax/qdrant'; import { MDocument } from '@kastrax/rag'; import { embedMany } from 'ai'; const doc = MDocument.fromText('Your text content...'); const chunks = await doc.chunk(); const { embeddings } = await embedMany({ values: chunks.map(chunk => chunk.text), model: openai.embedding('text-embedding-3-small'), maxRetries: 3, }); const qdrant = new QdrantVector( process.env.QDRANT_URL, process.env.QDRANT_API_KEY, ); await qdrant.createIndex({ indexName: 'test_collection', dimension: 1536, }); await qdrant.upsert({ indexName: 'test_collection', vectors: embeddings, metadata: chunks?.map(chunk => ({ text: chunk.text })), }); ``` The `ChromaVector` class provides methods to create collections and insert embeddings into Chroma, an open-source embedding database. ```tsx copy import { openai } from '@ai-sdk/openai'; import { ChromaVector } from '@kastrax/chroma'; import { MDocument } from '@kastrax/rag'; import { embedMany } from 'ai'; const doc = MDocument.fromText('Your text content...'); const chunks = await doc.chunk(); const { embeddings } = await embedMany({ values: chunks.map(chunk => chunk.text), model: openai.embedding('text-embedding-3-small'), }); const chroma = new ChromaVector({ path: "path/to/chroma/db", }); await chroma.createIndex({ indexName: 'test_collection', dimension: 1536, }); await chroma.upsert({ indexName: 'test_collection', vectors: embeddings, metadata: chunks.map(chunk => ({ text: chunk.text })), documents: chunks.map(chunk => chunk.text), }); ```


he `AstraVector` class provides methods to create collections and insert embeddings into DataStax Astra DB, a cloud-native vector database. ```tsx copy import { openai } from '@ai-sdk/openai'; import { AstraVector } from '@kastrax/astra'; import { MDocument } from '@kastrax/rag'; import { embedMany } from 'ai'; const doc = MDocument.fromText('Your text content...'); const chunks = await doc.chunk(); const { embeddings } = await embedMany({ model: openai.embedding('text-embedding-3-small'), values: chunks.map(chunk => chunk.text), }); const astra = new AstraVector({ token: process.env.ASTRA_DB_TOKEN, endpoint: process.env.ASTRA_DB_ENDPOINT, keyspace: process.env.ASTRA_DB_KEYSPACE, }); await astra.createIndex({ indexName: 'test_collection', dimension: 1536, }); await astra.upsert({ indexName: 'test_collection', vectors: embeddings, metadata: chunks?.map(chunk => ({ text: chunk.text })), }); ``` The `LibSQLVector` class provides methods to create collections and insert embeddings into LibSQL, a fork of SQLite with vector extensions. ```tsx copy import { openai } from "@ai-sdk/openai"; import { LibSQLVector } from "@kastrax/core/vector/libsql"; import { MDocument } from "@kastrax/rag"; import { embedMany } from "ai"; const doc = MDocument.fromText("Your text content..."); const chunks = await doc.chunk(); const { embeddings } = await embedMany({ values: chunks.map((chunk) => chunk.text), model: openai.embedding("text-embedding-3-small"), }); const libsql = new LibSQLVector({ connectionUrl: process.env.DATABASE_URL, authToken: process.env.DATABASE_AUTH_TOKEN, // Optional: for Turso cloud databases }); await libsql.createIndex({ indexName: "test_collection", dimension: 1536, }); await libsql.upsert({ indexName: "test_collection", vectors: embeddings, metadata: chunks?.map((chunk) => ({ text: chunk.text })), }); ```


The `UpstashVector` class provides methods to create collections and insert embeddings into Upstash Vector, a serverless vector database. ```tsx copy import { openai } from '@ai-sdk/openai'; import { UpstashVector } from '@kastrax/upstash'; import { MDocument } from '@kastrax/rag'; import { embedMany } from 'ai'; const doc = MDocument.fromText('Your text content...'); const chunks = await doc.chunk(); const { embeddings } = await embedMany({ values: chunks.map(chunk => chunk.text), model: openai.embedding('text-embedding-3-small'), }); const upstash = new UpstashVector({ url: process.env.UPSTASH_URL, token: process.env.UPSTASH_TOKEN, }); await upstash.createIndex({ indexName: 'test_collection', dimension: 1536, }); await upstash.upsert({ indexName: 'test_collection', vectors: embeddings, metadata: chunks?.map(chunk => ({ text: chunk.text })), }); ``` The `CloudflareVector` class provides methods to create collections and insert embeddings into Cloudflare Vectorize, a serverless vector database service. ```tsx copy import { openai } from '@ai-sdk/openai'; import { CloudflareVector } from '@kastrax/vectorize'; import { MDocument } from '@kastrax/rag'; import { embedMany } from 'ai'; const doc = MDocument.fromText('Your text content...'); const chunks = await doc.chunk(); const { embeddings } = await embedMany({ values: chunks.map(chunk => chunk.text), model: openai.embedding('text-embedding-3-small'), }); const vectorize = new CloudflareVector({ accountId: process.env.CF_ACCOUNT_ID, apiToken: process.env.CF_API_TOKEN, }); await vectorize.createIndex({ indexName: 'test_collection', dimension: 1536, }); await vectorize.upsert({ indexName: 'test_collection', vectors: embeddings, metadata: chunks?.map(chunk => ({ text: chunk.text })), }); ``` The `MongoDBVector` class provides methods to create indexes and insert embeddings into MongoDB with Atlas Search. ```tsx copy import { openai } from "@ai-sdk/openai"; import { MongoDBVector } from "@kastrax/mongodb"; import { MDocument } from "@kastrax/rag"; import { embedMany } from "ai"; const doc = MDocument.fromText("Your text content..."); const chunks = await doc.chunk(); const { embeddings } = await embedMany({ values: chunks.map(chunk => chunk.text), model: openai.embedding("text-embedding-3-small"), }); const vectorDB = new MongoDBVector({ uri: process.env.MONGODB_URI!, dbName: process.env.MONGODB_DB_NAME!, }); await vectorDB.createIndex({ indexName: "test_index", dimension: 1536, }); await vectorDB.upsert({ indexName: "test_index", vectors: embeddings, metadata: chunks?.map((chunk: any) => ({ text: chunk.text })), }); ```
--- title: "Example: Using the Vector Query Tool | RAG | Kastrax Docs" description: Example of implementing a basic RAG system in Kastrax using OpenAI embeddings and PGVector for vector storage. --- import { GithubLink } from "@/components/github-link"; # Using the Vector Query Tool ✅ [EN] Source: https://kastrax.ai/en/examples/rag/usage/basic-rag This example demonstrates how to implement and use `createVectorQueryTool` for semantic search in a RAG system. It shows how to configure the tool, manage vector storage, and retrieve relevant context effectively. ## Overview ✅ The system implements RAG using Kastrax and OpenAI. Here's what it does: 1. Sets up a Kastrax agent with gpt-4o-mini for response generation 2. Creates a vector query tool to manage vector store interactions 3. Uses existing embeddings to retrieve relevant context 4. Generates context-aware responses using the Kastrax agent > **Note**: To learn how to create and store embeddings, see the [Upsert Embeddings](/examples/rag/upsert/upsert-embeddings) guide. ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_openai_api_key_here POSTGRES_CONNECTION_STRING=your_connection_string_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="src/index.ts" import { openai } from '@ai-sdk/openai'; import { Kastrax } from '@kastrax/core'; import { Agent } from '@kastrax/core/agent'; import { createVectorQueryTool } from '@kastrax/rag'; import { PgVector } from '@kastrax/pg'; ``` ## Vector Query Tool Creation ✅ Create a tool that can query the vector database: ```typescript copy showLineNumbers{7} filename="src/index.ts" const vectorQueryTool = createVectorQueryTool({ vectorStoreName: 'pgVector', indexName: 'embeddings', model: openai.embedding('text-embedding-3-small'), }); ``` ## Agent Configuration ✅ Set up the Kastrax agent that will handle the responses: ```typescript copy showLineNumbers{13} filename="src/index.ts" export const ragAgent = new Agent({ name: 'RAG Agent', instructions: 'You are a helpful assistant that answers questions based on the provided context. Keep your answers concise and relevant.', model: openai('gpt-4o-mini'), tools: { vectorQueryTool, }, }); ``` ## Instantiate PgVector and Kastrax ✅ Instantiate PgVector and Kastrax with all components: ```typescript copy showLineNumbers{23} filename="src/index.ts" const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!); export const kastrax = new Kastrax({ agents: { ragAgent }, vectors: { pgVector }, }); const agent = kastrax.getAgent('ragAgent'); ``` ## Example Usage ✅ ```typescript copy showLineNumbers{32} filename="src/index.ts" const prompt = ` [Insert query based on document here] Please base your answer only on the context provided in the tool. If the context doesn't contain enough information to fully answer the question, please state that explicitly. `; const completion = await agent.generate(prompt); console.log(completion.text); ```




--- title: "Example: Optimizing Information Density | RAG | Kastrax Docs" description: Example of implementing a RAG system in Kastrax to optimize information density and deduplicate data using LLM-based processing. --- import { GithubLink } from "@/components/github-link"; # Optimizing Information Density ✅ [EN] Source: https://kastrax.ai/en/examples/rag/usage/cleanup-rag This example demonstrates how to implement a Retrieval-Augmented Generation (RAG) system using Kastrax, OpenAI embeddings, and PGVector for vector storage. The system uses an agent to clean the initial chunks to optimize information density and deduplicate data. ## Overview ✅ The system implements RAG using Kastrax and OpenAI, this time optimizing information density through LLM-based processing. Here's what it does: 1. Sets up a Kastrax agent with gpt-4o-mini that can handle both querying and cleaning documents 2. Creates vector query and document chunking tools for the agent to use 3. Processes the initial document: - Chunks text documents into smaller segments - Creates embeddings for the chunks - Stores them in a PostgreSQL vector database 4. Performs an initial query to establish baseline response quality 5. Optimizes the data: - Uses the agent to clean and deduplicate chunks - Creates new embeddings for the cleaned chunks - Updates the vector store with optimized data 6. Performs the same query again to demonstrate improved response quality ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_openai_api_key_here POSTGRES_CONNECTION_STRING=your_connection_string_here ``` ### Dependencies Then, import the necessary dependencies: ```typescript copy showLineNumbers filename="index.ts" import { openai } from '@ai-sdk/openai'; import { Kastrax } from "@kastrax/core"; import { Agent } from "@kastrax/core/agent"; import { PgVector } from "@kastrax/pg"; import { MDocument, createVectorQueryTool, createDocumentChunkerTool } from "@kastrax/rag"; import { embedMany } from "ai"; ``` ## Tool Creation ✅ ### Vector Query Tool Using createVectorQueryTool imported from @kastrax/rag, you can create a tool that can query the vector database. ```typescript copy showLineNumbers{8} filename="index.ts" const vectorQueryTool = createVectorQueryTool({ vectorStoreName: "pgVector", indexName: "embeddings", model: openai.embedding('text-embedding-3-small'), }); ``` ### Document Chunker Tool Using createDocumentChunkerTool imported from @kastrax/rag, you can create a tool that chunks the document and sends the chunks to your agent. ```typescript copy showLineNumbers{14} filename="index.ts" const doc = MDocument.fromText(yourText); const documentChunkerTool = createDocumentChunkerTool({ doc, params: { strategy: "recursive", size: 512, overlap: 25, separator: "\n", }, }); ``` ## Agent Configuration ✅ Set up a single Kastrax agent that can handle both querying and cleaning: ```typescript copy showLineNumbers{26} filename="index.ts" const ragAgent = new Agent({ name: "RAG Agent", instructions: `You are a helpful assistant that handles both querying and cleaning documents. When cleaning: Process, clean, and label data, remove irrelevant information and deduplicate content while preserving key facts. When querying: Provide answers based on the available context. Keep your answers concise and relevant. Important: When asked to answer a question, please base your answer only on the context provided in the tool. If the context doesn't contain enough information to fully answer the question, please state that explicitly. `, model: openai('gpt-4o-mini'), tools: { vectorQueryTool, documentChunkerTool, }, }); ``` ## Instantiate PgVector and Kastrax ✅ Instantiate PgVector and Kastrax with the components: ```typescript copy showLineNumbers{41} filename="index.ts" const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!); export const kastrax = new Kastrax({ agents: { ragAgent }, vectors: { pgVector }, }); const agent = kastrax.getAgent('ragAgent'); ``` ## Document Processing ✅ Chunk the initial document and create embeddings: ```typescript copy showLineNumbers{49} filename="index.ts" const chunks = await doc.chunk({ strategy: "recursive", size: 256, overlap: 50, separator: "\n", }); const { embeddings } = await embedMany({ model: openai.embedding('text-embedding-3-small'), values: chunks.map(chunk => chunk.text), }); const vectorStore = kastrax.getVector("pgVector"); await vectorStore.createIndex({ indexName: "embeddings", dimension: 1536, }); await vectorStore.upsert({ indexName: "embeddings", vectors: embeddings, metadata: chunks?.map((chunk: any) => ({ text: chunk.text })), }); ``` ## Initial Query ✅ Let's try querying the raw data to establish a baseline: ```typescript copy showLineNumbers{73} filename="index.ts" // Generate response using the original embeddings const query = 'What are all the technologies mentioned for space exploration?'; const originalResponse = await agent.generate(query); console.log('\nQuery:', query); console.log('Response:', originalResponse.text); ``` ## Data Optimization ✅ After seeing the initial results, we can clean the data to improve quality: ```typescript copy showLineNumbers{79} filename="index.ts" const chunkPrompt = `Use the tool provided to clean the chunks. Make sure to filter out irrelevant information that is not space related and remove duplicates.`; const newChunks = await agent.generate(chunkPrompt); const updatedDoc = MDocument.fromText(newChunks.text); const updatedChunks = await updatedDoc.chunk({ strategy: "recursive", size: 256, overlap: 50, separator: "\n", }); const { embeddings: cleanedEmbeddings } = await embedMany({ model: openai.embedding('text-embedding-3-small'), values: updatedChunks.map(chunk => chunk.text), }); // Update the vector store with cleaned embeddings await vectorStore.deleteIndex('embeddings'); await vectorStore.createIndex({ indexName: "embeddings", dimension: 1536, }); await vectorStore.upsert({ indexName: "embeddings", vectors: cleanedEmbeddings, metadata: updatedChunks?.map((chunk: any) => ({ text: chunk.text })), }); ``` ## Optimized Query ✅ Query the data again after cleaning to observe any differences in the response: ```typescript copy showLineNumbers{109} filename="index.ts" // Query again with cleaned embeddings const cleanedResponse = await agent.generate(query); console.log('\nQuery:', query); console.log('Response:', cleanedResponse.text); ```




--- title: "Example: Chain of Thought Prompting | RAG | Kastrax Docs" description: Example of implementing a RAG system in Kastrax with chain-of-thought reasoning using OpenAI and PGVector. --- import { GithubLink } from "@/components/github-link"; # Chain of Thought Prompting ✅ [EN] Source: https://kastrax.ai/en/examples/rag/usage/cot-rag This example demonstrates how to implement a Retrieval-Augmented Generation (RAG) system using Kastrax, OpenAI embeddings, and PGVector for vector storage, with an emphasis on chain-of-thought reasoning. ## Overview ✅ The system implements RAG using Kastrax and OpenAI with chain-of-thought prompting. Here's what it does: 1. Sets up a Kastrax agent with gpt-4o-mini for response generation 2. Creates a vector query tool to manage vector store interactions 3. Chunks text documents into smaller segments 4. Creates embeddings for these chunks 5. Stores them in a PostgreSQL vector database 6. Retrieves relevant chunks based on queries using vector query tool 7. Generates context-aware responses using chain-of-thought reasoning ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_openai_api_key_here POSTGRES_CONNECTION_STRING=your_connection_string_here ``` ### Dependencies Then, import the necessary dependencies: ```typescript copy showLineNumbers filename="index.ts" import { openai } from "@ai-sdk/openai"; import { Kastrax } from "@kastrax/core"; import { Agent } from "@kastrax/core/agent"; import { PgVector } from "@kastrax/pg"; import { createVectorQueryTool, MDocument } from "@kastrax/rag"; import { embedMany } from "ai"; ``` ## Vector Query Tool Creation ✅ Using createVectorQueryTool imported from @kastrax/rag, you can create a tool that can query the vector database. ```typescript copy showLineNumbers{8} filename="index.ts" const vectorQueryTool = createVectorQueryTool({ vectorStoreName: "pgVector", indexName: "embeddings", model: openai.embedding('text-embedding-3-small'), }); ``` ## Agent Configuration ✅ Set up the Kastrax agent with chain-of-thought prompting instructions: ```typescript copy showLineNumbers{14} filename="index.ts" export const ragAgent = new Agent({ name: "RAG Agent", instructions: `You are a helpful assistant that answers questions based on the provided context. Follow these steps for each response: 1. First, carefully analyze the retrieved context chunks and identify key information. 2. Break down your thinking process about how the retrieved information relates to the query. 3. Explain how you're connecting different pieces from the retrieved chunks. 4. Draw conclusions based only on the evidence in the retrieved context. 5. If the retrieved chunks don't contain enough information, explicitly state what's missing. Format your response as: THOUGHT PROCESS: - Step 1: [Initial analysis of retrieved chunks] - Step 2: [Connections between chunks] - Step 3: [Reasoning based on chunks] FINAL ANSWER: [Your concise answer based on the retrieved context] Important: When asked to answer a question, please base your answer only on the context provided in the tool. If the context doesn't contain enough information to fully answer the question, please state that explicitly. Remember: Explain how you're using the retrieved information to reach your conclusions. `, model: openai("gpt-4o-mini"), tools: { vectorQueryTool }, }); ``` ## Instantiate PgVector and Kastrax ✅ Instantiate PgVector and Kastrax with all components: ```typescript copy showLineNumbers{36} filename="index.ts" const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!); export const kastrax = new Kastrax({ agents: { ragAgent }, vectors: { pgVector }, }); const agent = kastrax.getAgent("ragAgent"); ``` ## Document Processing ✅ Create a document and process it into chunks: ```typescript copy showLineNumbers{44} filename="index.ts" const doc = MDocument.fromText( `The Impact of Climate Change on Global Agriculture...`, ); const chunks = await doc.chunk({ strategy: "recursive", size: 512, overlap: 50, separator: "\n", }); ``` ## Creating and Storing Embeddings ✅ Generate embeddings for the chunks and store them in the vector database: ```typescript copy showLineNumbers{55} filename="index.ts" const { embeddings } = await embedMany({ values: chunks.map(chunk => chunk.text), model: openai.embedding("text-embedding-3-small"), }); const vectorStore = kastrax.getVector("pgVector"); await vectorStore.createIndex({ indexName: "embeddings", dimension: 1536, }); await vectorStore.upsert({ indexName: "embeddings", vectors: embeddings, metadata: chunks?.map((chunk: any) => ({ text: chunk.text })), }); ``` ## Chain-of-Thought Querying ✅ Try different queries to see how the agent breaks down its reasoning: ```typescript copy showLineNumbers{83} filename="index.ts" const answerOne = await agent.generate('What are the main adaptation strategies for farmers?'); console.log('\nQuery:', 'What are the main adaptation strategies for farmers?'); console.log('Response:', answerOne.text); const answerTwo = await agent.generate('Analyze how temperature affects crop yields.'); console.log('\nQuery:', 'Analyze how temperature affects crop yields.'); console.log('Response:', answerTwo.text); const answerThree = await agent.generate('What connections can you draw between climate change and food security?'); console.log('\nQuery:', 'What connections can you draw between climate change and food security?'); console.log('Response:', answerThree.text); ```




--- title: "Example: Structured Reasoning with Workflows | RAG | Kastrax Docs" description: Example of implementing structured reasoning in a RAG system using Kastrax's workflow capabilities. --- import { GithubLink } from "@/components/github-link"; # Structured Reasoning with Workflows ✅ [EN] Source: https://kastrax.ai/en/examples/rag/usage/cot-workflow-rag This example demonstrates how to implement a Retrieval-Augmented Generation (RAG) system using Kastrax, OpenAI embeddings, and PGVector for vector storage, with an emphasis on structured reasoning through a defined workflow. ## Overview ✅ The system implements RAG using Kastrax and OpenAI with chain-of-thought prompting through a defined workflow. Here's what it does: 1. Sets up a Kastrax agent with gpt-4o-mini for response generation 2. Creates a vector query tool to manage vector store interactions 3. Defines a workflow with multiple steps for chain-of-thought reasoning 4. Processes and chunks text documents 5. Creates and stores embeddings in PostgreSQL 6. Generates responses through the workflow steps ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_openai_api_key_here POSTGRES_CONNECTION_STRING=your_connection_string_here ``` ### Dependencies Import the necessary dependencies: ```typescript copy showLineNumbers filename="index.ts" import { openai } from "@ai-sdk/openai"; import { Kastrax } from "@kastrax/core"; import { Agent } from "@kastrax/core/agent"; import { Step, Workflow } from "@kastrax/core/workflows"; import { PgVector } from "@kastrax/pg"; import { createVectorQueryTool, MDocument } from "@kastrax/rag"; import { embedMany } from "ai"; import { z } from "zod"; ``` ## Workflow Definition ✅ First, define the workflow with its trigger schema: ```typescript copy showLineNumbers{10} filename="index.ts" export const ragWorkflow = new Workflow({ name: "rag-workflow", triggerSchema: z.object({ query: z.string(), }), }); ``` ## Vector Query Tool Creation ✅ Create a tool for querying the vector database: ```typescript copy showLineNumbers{17} filename="index.ts" const vectorQueryTool = createVectorQueryTool({ vectorStoreName: "pgVector", indexName: "embeddings", model: openai.embedding('text-embedding-3-small'), }); ``` ## Agent Configuration ✅ Set up the Kastrax agent: ```typescript copy showLineNumbers{23} filename="index.ts" export const ragAgent = new Agent({ name: "RAG Agent", instructions: `You are a helpful assistant that answers questions based on the provided context.`, model: openai("gpt-4o-mini"), tools: { vectorQueryTool, }, }); ``` ## Workflow Steps ✅ The workflow is divided into multiple steps for chain-of-thought reasoning: ### 1. Context Analysis Step ```typescript copy showLineNumbers{32} filename="index.ts" const analyzeContext = new Step({ id: "analyzeContext", outputSchema: z.object({ initialAnalysis: z.string(), }), execute: async ({ context, kastrax }) => { console.log("---------------------------"); const ragAgent = kastrax?.getAgent('ragAgent'); const query = context?.getStepResult<{ query: string }>( "trigger", )?.query; const analysisPrompt = `${query} 1. First, carefully analyze the retrieved context chunks and identify key information.`; const analysis = await ragAgent?.generate(analysisPrompt); console.log(analysis?.text); return { initialAnalysis: analysis?.text ?? "", }; }, }); ``` ### 2. Thought Breakdown Step ```typescript copy showLineNumbers{54} filename="index.ts" const breakdownThoughts = new Step({ id: "breakdownThoughts", outputSchema: z.object({ breakdown: z.string(), }), execute: async ({ context, kastrax }) => { console.log("---------------------------"); const ragAgent = kastrax?.getAgent('ragAgent'); const analysis = context?.getStepResult<{ initialAnalysis: string; }>("analyzeContext")?.initialAnalysis; const connectionPrompt = ` Based on the initial analysis: ${analysis} 2. Break down your thinking process about how the retrieved information relates to the query. `; const connectionAnalysis = await ragAgent?.generate(connectionPrompt); console.log(connectionAnalysis?.text); return { breakdown: connectionAnalysis?.text ?? "", }; }, }); ``` ### 3. Connection Step ```typescript copy showLineNumbers{80} filename="index.ts" const connectPieces = new Step({ id: "connectPieces", outputSchema: z.object({ connections: z.string(), }), execute: async ({ context, kastrax }) => { console.log("---------------------------"); const ragAgent = kastrax?.getAgent('ragAgent'); const process = context?.getStepResult<{ breakdown: string; }>("breakdownThoughts")?.breakdown; const connectionPrompt = ` Based on the breakdown: ${process} 3. Explain how you're connecting different pieces from the retrieved chunks. `; const connections = await ragAgent?.generate(connectionPrompt); console.log(connections?.text); return { connections: connections?.text ?? "", }; }, }); ``` ### 4. Conclusion Step ```typescript copy showLineNumbers{105} filename="index.ts" const drawConclusions = new Step({ id: "drawConclusions", outputSchema: z.object({ conclusions: z.string(), }), execute: async ({ context, kastrax }) => { console.log("---------------------------"); const ragAgent = kastrax?.getAgent('ragAgent'); const evidence = context?.getStepResult<{ connections: string; }>("connectPieces")?.connections; const conclusionPrompt = ` Based on the connections: ${evidence} 4. Draw conclusions based only on the evidence in the retrieved context. `; const conclusions = await ragAgent?.generate(conclusionPrompt); console.log(conclusions?.text); return { conclusions: conclusions?.text ?? "", }; }, }); ``` ### 5. Final Answer Step ```typescript copy showLineNumbers{130} filename="index.ts" const finalAnswer = new Step({ id: "finalAnswer", outputSchema: z.object({ finalAnswer: z.string(), }), execute: async ({ context, kastrax }) => { console.log("---------------------------"); const ragAgent = kastrax?.getAgent('ragAgent'); const conclusions = context?.getStepResult<{ conclusions: string; }>("drawConclusions")?.conclusions; const answerPrompt = ` Based on the conclusions: ${conclusions} Format your response as: THOUGHT PROCESS: - Step 1: [Initial analysis of retrieved chunks] - Step 2: [Connections between chunks] - Step 3: [Reasoning based on chunks] FINAL ANSWER: [Your concise answer based on the retrieved context]`; const finalAnswer = await ragAgent?.generate(answerPrompt); console.log(finalAnswer?.text); return { finalAnswer: finalAnswer?.text ?? "", }; }, }); ``` ## Workflow Configuration ✅ Connect all the steps in the workflow: ```typescript copy showLineNumbers{160} filename="index.ts" ragWorkflow .step(analyzeContext) .then(breakdownThoughts) .then(connectPieces) .then(drawConclusions) .then(finalAnswer); ragWorkflow.commit(); ``` ## Instantiate PgVector and Kastrax ✅ Instantiate PgVector and Kastrax with all components: ```typescript copy showLineNumbers{169} filename="index.ts" const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!); export const kastrax = new Kastrax({ agents: { ragAgent }, vectors: { pgVector }, workflows: { ragWorkflow }, }); ``` ## Document Processing ✅ Process and chunks the document: ```typescript copy showLineNumbers{177} filename="index.ts" const doc = MDocument.fromText(`The Impact of Climate Change on Global Agriculture...`); const chunks = await doc.chunk({ strategy: "recursive", size: 512, overlap: 50, separator: "\n", }); ``` ## Embedding Creation and Storage ✅ Generate and store embeddings: ```typescript copy showLineNumbers{186} filename="index.ts" const { embeddings } = await embedMany({ model: openai.embedding('text-embedding-3-small'), values: chunks.map(chunk => chunk.text), }); const vectorStore = kastrax.getVector("pgVector"); await vectorStore.createIndex({ indexName: "embeddings", dimension: 1536, }); await vectorStore.upsert({ indexName: "embeddings", vectors: embeddings, metadata: chunks?.map((chunk: any) => ({ text: chunk.text })), }); ``` ## Workflow Execution ✅ Here's how to execute the workflow with a query: ```typescript copy showLineNumbers{202} filename="index.ts" const query = 'What are the main adaptation strategies for farmers?'; console.log('\nQuery:', query); const prompt = ` Please answer the following question: ${query} Please base your answer only on the context provided in the tool. If the context doesn't contain enough information to fully answer the question, please state that explicitly. `; const { runId, start } = ragWorkflow.createRun(); console.log('Run:', runId); const workflowResult = await start({ triggerData: { query: prompt, }, }); console.log('\nThought Process:'); console.log(workflowResult.results); ```




--- title: "Example: Agent-Driven Metadata Filtering | Retrieval | RAG | Kastrax Docs" description: Example of using a Kastrax agent in a RAG system to construct and apply metadata filters for document retrieval. --- import { GithubLink } from "@/components/github-link"; # Agent-Driven Metadata Filtering ✅ [EN] Source: https://kastrax.ai/en/examples/rag/usage/filter-rag This example demonstrates how to implement a Retrieval-Augmented Generation (RAG) system using Kastrax, OpenAI embeddings, and PGVector for vector storage. This system uses an agent to construct metadata filters from a user's query to search for relevant chunks in the vector store, reducing the amount of results returned. ## Overview ✅ The system implements metadata filtering using Kastrax and OpenAI. Here's what it does: 1. Sets up a Kastrax agent with gpt-4o-mini to understand queries and identify filter requirements 2. Creates a vector query tool to handle metadata filtering and semantic search 3. Processes documents into chunks with metadata and embeddings 4. Stores both vectors and metadata in PGVector for efficient retrieval 5. Processes queries by combining metadata filters with semantic search When a user asks a question: - The agent analyzes the query to understand the intent - Constructs appropriate metadata filters (e.g., by topic, date, category) - Uses the vector query tool to find the most relevant information - Generates a contextual response based on the filtered results ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_openai_api_key_here POSTGRES_CONNECTION_STRING=your_connection_string_here ``` ### Dependencies Then, import the necessary dependencies: ```typescript copy showLineNumbers filename="index.ts" import { openai } from '@ai-sdk/openai'; import { Kastrax } from '@kastrax/core'; import { Agent } from '@kastrax/core/agent'; import { PgVector, PGVECTOR_PROMPT } from '@kastrax/pg'; import { createVectorQueryTool, MDocument } from '@kastrax/rag'; import { embedMany } from 'ai'; ``` ## Vector Query Tool Creation ✅ Using createVectorQueryTool imported from @kastrax/rag, you can create a tool that enables metadata filtering. Each vector store has its own prompt that defines the supported filter operators and syntax: ```typescript copy showLineNumbers{9} filename="index.ts" const vectorQueryTool = createVectorQueryTool({ id: 'vectorQueryTool', vectorStoreName: "pgVector", indexName: "embeddings", model: openai.embedding('text-embedding-3-small'), enableFilter: true, filterPrompt: PGVECTOR_PROMPT // Use the prompt for your vector store }); ``` Each prompt includes: - Supported operators (comparison, array, logical, element) - Example usage for each operator - Store-specific restrictions and rules - Complex query examples ## Document Processing ✅ Create a document and process it into chunks with metadata: ```typescript copy showLineNumbers{17} filename="index.ts" const doc = MDocument.fromText(`The Impact of Climate Change on Global Agriculture...`); const chunks = await doc.chunk({ strategy: 'recursive', size: 512, overlap: 50, separator: '\n', extract: { keywords: true, // Extracts keywords from each chunk }, }); ``` ### Transform Chunks into Metadata Transform chunks into metadata that can be filtered: ```typescript copy showLineNumbers{31} filename="index.ts" const chunkMetadata = chunks?.map((chunk: any, index: number) => ({ text: chunk.text, ...chunk.metadata, nested: { keywords: chunk.metadata.excerptKeywords .replace('KEYWORDS:', '') .split(',') .map(k => k.trim()), id: index, }, })); ``` ## Agent Configuration ✅ The agent is configured to understand user queries and translate them into appropriate metadata filters. The agent requires both the vector query tool and a system prompt containing: - Metadata structure for available filter fields - Vector store prompt for filter operations and syntax ```typescript copy showLineNumbers{43} filename="index.ts" export const ragAgent = new Agent({ name: 'RAG Agent', model: openai('gpt-4o-mini'), instructions: ` You are a helpful assistant that answers questions based on the provided context. Keep your answers concise and relevant. Filter the context by searching the metadata. The metadata is structured as follows: { text: string, excerptKeywords: string, nested: { keywords: string[], id: number, }, } ${PGVECTOR_PROMPT} Important: When asked to answer a question, please base your answer only on the context provided in the tool. If the context doesn't contain enough information to fully answer the question, please state that explicitly. `, tools: { vectorQueryTool }, }); ``` The agent's instructions are designed to: - Process user queries to identify filter requirements - Use the metadata structure to find relevant information - Apply appropriate filters through the vectorQueryTool and the provided vector store prompt - Generate responses based on the filtered context > Note: Different vector stores have specific prompts available. See [Vector Store Prompts](/docs/rag/retrieval#vector-store-prompts) for details. ## Instantiate PgVector and Kastrax ✅ Instantiate PgVector and Kastrax with the components: ```typescript copy showLineNumbers{69} filename="index.ts" const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!); export const kastrax = new Kastrax({ agents: { ragAgent }, vectors: { pgVector }, }); const agent = kastrax.getAgent('ragAgent'); ``` ## Creating and Storing Embeddings ✅ Generate embeddings and store them with metadata: ```typescript copy showLineNumbers{78} filename="index.ts" const { embeddings } = await embedMany({ model: openai.embedding('text-embedding-3-small'), values: chunks.map(chunk => chunk.text), }); const vectorStore = kastrax.getVector('pgVector'); await vectorStore.createIndex({ indexName: 'embeddings', dimension: 1536, }); // Store both embeddings and metadata together await vectorStore.upsert({ indexName: 'embeddings', vectors: embeddings, metadata: chunkMetadata, }); ``` The `upsert` operation stores both the vector embeddings and their associated metadata, enabling combined semantic search and metadata filtering capabilities. ## Metadata-Based Querying ✅ Try different queries using metadata filters: ```typescript copy showLineNumbers{96} filename="index.ts" const queryOne = 'What are the adaptation strategies mentioned?'; const answerOne = await agent.generate(queryOne); console.log('\nQuery:', queryOne); console.log('Response:', answerOne.text); const queryTwo = 'Show me recent sections. Check the "nested.id" field and return values that are greater than 2.'; const answerTwo = await agent.generate(queryTwo); console.log('\nQuery:', queryTwo); console.log('Response:', answerTwo.text); const queryThree = 'Search the "text" field using regex operator to find sections containing "temperature".'; const answerThree = await agent.generate(queryThree); console.log('\nQuery:', queryThree); console.log('Response:', answerThree.text); ```




--- title: "Example: A Complete Graph RAG System | RAG | Kastrax Docs" description: Example of implementing a Graph RAG system in Kastrax using OpenAI embeddings and PGVector for vector storage. --- import { GithubLink } from "@/components/github-link"; # Graph RAG ✅ [EN] Source: https://kastrax.ai/en/examples/rag/usage/graph-rag This example demonstrates how to implement a Retrieval-Augmented Generation (RAG) system using Kastrax, OpenAI embeddings, and PGVector for vector storage. ## Overview ✅ The system implements Graph RAG using Kastrax and OpenAI. Here's what it does: 1. Sets up a Kastrax agent with gpt-4o-mini for response generation 2. Creates a GraphRAG tool to manage vector store interactions and knowledge graph creation/traversal 3. Chunks text documents into smaller segments 4. Creates embeddings for these chunks 5. Stores them in a PostgreSQL vector database 6. Creates a knowledge graph of relevant chunks based on queries using GraphRAG tool - Tool returns results from vector store and creates knowledge graph - Traverses knowledge graph using query 7. Generates context-aware responses using the Kastrax agent ## Setup ✅ ### Environment Setup Make sure to set up your environment variables: ```bash filename=".env" OPENAI_API_KEY=your_openai_api_key_here POSTGRES_CONNECTION_STRING=your_connection_string_here ``` ### Dependencies Then, import the necessary dependencies: ```typescript copy showLineNumbers filename="index.ts" import { openai } from "@ai-sdk/openai"; import { Kastrax } from "@kastrax/core"; import { Agent } from "@kastrax/core/agent"; import { PgVector } from "@kastrax/pg"; import { MDocument, createGraphRAGTool } from "@kastrax/rag"; import { embedMany } from "ai"; ``` ## GraphRAG Tool Creation ✅ Using createGraphRAGTool imported from @kastrax/rag, you can create a tool that queries the vector database and converts the results into a knowledge graph: ```typescript copy showLineNumbers{8} filename="index.ts" const graphRagTool = createGraphRAGTool({ vectorStoreName: "pgVector", indexName: "embeddings", model: openai.embedding("text-embedding-3-small"), graphOptions: { dimension: 1536, threshold: 0.7, }, }); ``` ## Agent Configuration ✅ Set up the Kastrax agent that will handle the responses: ```typescript copy showLineNumbers{19} filename="index.ts" const ragAgent = new Agent({ name: "GraphRAG Agent", instructions: `You are a helpful assistant that answers questions based on the provided context. Format your answers as follows: 1. DIRECT FACTS: List only the directly stated facts from the text relevant to the question (2-3 bullet points) 2. CONNECTIONS MADE: List the relationships you found between different parts of the text (2-3 bullet points) 3. CONCLUSION: One sentence summary that ties everything together Keep each section brief and focus on the most important points. Important: When asked to answer a question, please base your answer only on the context provided in the tool. If the context doesn't contain enough information to fully answer the question, please state that explicitly.`, model: openai("gpt-4o-mini"), tools: { graphRagTool, }, }); ``` ## Instantiate PgVector and Kastrax ✅ Instantiate PgVector and Kastrax with the components: ```typescript copy showLineNumbers{36} filename="index.ts" const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!); export const kastrax = new Kastrax({ agents: { ragAgent }, vectors: { pgVector }, }); const agent = kastrax.getAgent("ragAgent"); ``` ## Document Processing ✅ Create a document and process it into chunks: ```typescript copy showLineNumbers{45} filename="index.ts" const doc = MDocument.fromText(` # Riverdale Heights: Community Development Study ✅ // ... text content ... `); const chunks = await doc.chunk({ strategy: "recursive", size: 512, overlap: 50, separator: "\n", }); ``` ## Creating and Storing Embeddings ✅ Generate embeddings for the chunks and store them in the vector database: ```typescript copy showLineNumbers{56} filename="index.ts" const { embeddings } = await embedMany({ model: openai.embedding("text-embedding-3-small"), values: chunks.map(chunk => chunk.text), }); const vectorStore = kastrax.getVector("pgVector"); await vectorStore.createIndex({ indexName: "embeddings", dimension: 1536, }); await vectorStore.upsert({ indexName: "embeddings", vectors: embeddings, metadata: chunks?.map((chunk: any) => ({ text: chunk.text })), }); ``` ## Graph-Based Querying ✅ Try different queries to explore relationships in the data: ```typescript copy showLineNumbers{82} filename="index.ts" const queryOne = "What are the direct and indirect effects of early railway decisions on Riverdale Heights' current state?"; const answerOne = await ragAgent.generate(queryOne); console.log('\nQuery:', queryOne); console.log('Response:', answerOne.text); const queryTwo = 'How have changes in transportation infrastructure affected different generations of local businesses and community spaces?'; const answerTwo = await ragAgent.generate(queryTwo); console.log('\nQuery:', queryTwo); console.log('Response:', answerTwo.text); const queryThree = 'Compare how the Rossi family business and Thompson Steel Works responded to major infrastructure changes, and how their responses affected the community.'; const answerThree = await ragAgent.generate(queryThree); console.log('\nQuery:', queryThree); console.log('Response:', answerThree.text); const queryFour = 'Trace how the transformation of the Thompson Steel Works site has influenced surrounding businesses and cultural spaces from 1932 to present.'; const answerFour = await ragAgent.generate(queryFour); console.log('\nQuery:', queryFour); console.log('Response:', answerFour.text); ```




--- title: Speech to Speech description: Example of using Kastrax to create a speech to speech application. --- import { GithubLink } from '@/components/github-link'; # Call Analysis with Kastrax ✅ [EN] Source: https://kastrax.ai/en/examples/voice/speech-to-speech This guide demonstrates how to build a complete voice conversation system with analytics using Kastrax. The example includes real-time speech-to-speech conversation, recording management, and integration with Roark Analytics for call analysis. ## Overview ✅ The system creates a voice conversation with a Kastrax agent, records the entire interaction, uploads the recording to Cloudinary for storage, and then sends the conversation data to Roark Analytics for detailed call analysis. ## Setup ✅ ### Prerequisites 1. OpenAI API key for speech-to-text and text-to-speech capabilities 2. Cloudinary account for audio file storage 3. Roark Analytics API key for call analysis ### Environment Configuration Create a `.env` file based on the sample provided: ```bash filename="speech-to-speech/call-analysis/sample.env" copy OPENAI_API_KEY= CLOUDINARY_CLOUD_NAME= CLOUDINARY_API_KEY= CLOUDINARY_API_SECRET= ROARK_API_KEY= ``` ### Installation Install the required dependencies: ```bash copy npm install ``` ## Implementation ✅ ### Creating the Kastrax Agent First, we define our agent with voice capabilities: ```ts filename="speech-to-speech/call-analysis/src/kastrax/agents/index.ts" copy import { openai } from '@ai-sdk/openai'; import { Agent } from '@kastrax/core/agent'; import { createTool } from '@kastrax/core/tools'; import { OpenAIRealtimeVoice } from '@kastrax/voice-openai-realtime'; import { z } from 'zod'; // Have the agent do something export const speechToSpeechServer = new Agent({ name: 'kastrax', instructions: 'You are a helpful assistant.', voice: new OpenAIRealtimeVoice(), model: openai('gpt-4o'), tools: { salutationTool: createTool({ id: 'salutationTool', description: 'Read the result of the tool', inputSchema: z.object({ name: z.string() }), outputSchema: z.object({ message: z.string() }), execute: async ({ context }) => { return { message: `Hello ${context.name}!` } } }) } }); ``` ### Initializing Kastrax Register the agent with Kastrax: ```ts filename="speech-to-speech/call-analysis/src/kastrax/index.ts" copy import { Kastrax } from '@kastrax/core'; import { speechToSpeechServer } from './agents'; export const kastrax = new Kastrax({ agents: { speechToSpeechServer, } }) ``` ### Cloudinary Integration for Audio Storage Set up Cloudinary for storing the recorded audio files: ```ts filename="speech-to-speech/call-analysis/src/upload.ts" copy import { v2 as cloudinary } from 'cloudinary'; cloudinary.config({ cloud_name: process.env.CLOUDINARY_CLOUD_NAME, api_key: process.env.CLOUDINARY_API_KEY, api_secret: process.env.CLOUDINARY_API_SECRET }); export async function uploadToCloudinary(path: string) { const response = await cloudinary.uploader.upload(path, { resource_type: 'raw' }) console.log(response) return response.url } ``` ### Main Application Logic The main application orchestrates the conversation flow, recording, and analytics integration: ```ts filename="speech-to-speech/call-analysis/src/base.ts" copy import { Roark } from '@roarkanalytics/sdk'; import chalk from 'chalk'; import { kastrax } from './kastrax'; import { createConversation, formatToolInvocations } from './utils'; import { uploadToCloudinary } from './upload'; import fs from 'fs'; const client = new Roark({ bearerToken: process.env.ROARK_API_KEY }); async function speechToSpeechServerExample() { const { start, stop } = createConversation({ kastrax, recordingPath: './speech-to-speech-server.mp3', providerOptions: {}, initialMessage: 'Howdy partner', onConversationEnd: async (props) => { // File upload fs.writeFileSync(props.recordingPath, props.audioBuffer) const url = await uploadToCloudinary(props.recordingPath) // Send to Roark console.log('Send to Roark', url) const response = await client.callAnalysis.create({ recordingUrl: url, startedAt: props.startedAt, callDirection: 'INBOUND', interfaceType: 'PHONE', participants: [ { role: 'AGENT', spokeFirst: props.agent.spokeFirst, name: props.agent.name, phoneNumber: props.agent.phoneNumber }, { role: 'CUSTOMER', name: 'Yujohn Nattrass', phoneNumber: '987654321' }, ], properties: props.metadata, toolInvocations: formatToolInvocations(props.toolInvocations), }); console.log('Call Recording Posted:', response.data); }, onWriting: (ev) => { if (ev.role === 'assistant') { process.stdout.write(chalk.blue(ev.text)); } }, }); await start(); process.on('SIGINT', async (e) => { await stop(); }) } speechToSpeechServerExample().catch(console.error) ``` ## Conversation Utilities ✅ The `utils.ts` file contains helper functions for managing the conversation, including: 1. Creating and managing the conversation session 2. Handling audio recording 3. Processing tool invocations 4. Managing conversation lifecycle events ## Running the Example ✅ Start the conversation with: ```bash copy npm run dev ``` The application will: 1. Start a real-time voice conversation with the Kastrax agent 2. Record the entire conversation 3. Upload the recording to Cloudinary when the conversation ends 4. Send the conversation data to Roark Analytics for analysis 5. Display the analysis results ## Key Features ✅ - **Real-time Speech-to-Speech**: Uses OpenAI's voice models for natural conversation - **Conversation Recording**: Captures the entire conversation for later analysis - **Tool Invocation Tracking**: Records when and how AI tools are used during the conversation - **Analytics Integration**: Sends conversation data to Roark Analytics for detailed analysis - **Cloud Storage**: Uploads recordings to Cloudinary for secure storage and access ## Customization ✅ You can customize this example by: - Modifying the agent's instructions and capabilities - Adding additional tools for the agent to use - Changing the conversation flow or initial message - Extending the analytics integration with custom metadata To view the full example code, see the [Github repository](https://github.com/kastrax-ai/voice-examples/tree/main/speech-to-speech/call-analysis).

--- title: "Example: Speech to Text | Voice | Kastrax Docs" description: Example of using Kastrax to create a speech to text application. --- import { GithubLink } from '@/components/github-link'; # Smart Voice Memo App ✅ [EN] Source: https://kastrax.ai/en/examples/voice/speech-to-text The following code snippets provide example implementations of Speech-to-Text (STT) functionality in a smart voice memo application using Next.js with direct integration of Kastrax. For more details on integrating Kastrax with Next.js, please refer to our [Integrate with Next.js](/docs/frameworks/next-js) documentation. ## Creating an Agent with STT Capabilities ✅ The following example shows how to initialize a voice-enabled agent with OpenAI's STT capabilities: ```typescript filename="src/kastrax/agents/index.ts" import { openai } from '@ai-sdk/openai'; import { Agent } from '@kastrax/core/agent'; import { OpenAIVoice } from '@kastrax/voice-openai'; const instructions = ` You are an AI note assistant tasked with providing concise, structured summaries of their content... // omitted for brevity `; export const noteTakerAgent = new Agent({ name: 'Note Taker Agent', instructions: instructions, model: openai('gpt-4o'), voice: new OpenAIVoice(), // Add OpenAI voice provider with default configuration }); ``` ## Registering the Agent with Kastrax ✅ This snippet demonstrates how to register the STT-enabled agent with your Kastrax instance: ```typescript filename="src/kastrax/index.ts" import { createLogger } from '@kastrax/core/logger'; import { Kastrax } from '@kastrax/core/kastrax'; import { noteTakerAgent } from './agents'; export const kastrax = new Kastrax({ agents: { noteTakerAgent }, // Register the note taker agent logger: createLogger({ name: 'Kastrax', level: 'info', }), }); ``` ## Processing Audio for Transcription ✅ The following code shows how to receive audio from a web request and use the agent's STT capabilities to transcribe it: ```typescript filename="app/api/audio/route.ts" import { kastrax } from '@/src/kastrax'; // Import the Kastrax instance import { Readable } from 'node:stream'; export async function POST(req: Request) { // Get the audio file from the request const formData = await req.formData(); const audioFile = formData.get('audio') as File; const arrayBuffer = await audioFile.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); const readable = Readable.from(buffer); // Get the note taker agent from the Kastrax instance const noteTakerAgent = kastrax.getAgent('noteTakerAgent'); // Transcribe the audio file const text = await noteTakerAgent.voice?.listen(readable); return new Response(JSON.stringify({ text }), { headers: { 'Content-Type': 'application/json' }, }); } ``` You can view the complete implementation of the Smart Voice Memo App on our GitHub repository.




--- title: "Example: Text to Speech | Voice | Kastrax Docs" description: Example of using Kastrax to create a text to speech application. --- import { GithubLink } from '@/components/github-link'; # Interactive Story Generator ✅ [EN] Source: https://kastrax.ai/en/examples/voice/text-to-speech The following code snippets provide example implementations of Text-to-Speech (TTS) functionality in an interactive story generator application using Next.js with Kastrax as a separate backend integration. This example demonstrates how to use the Kastrax client-js SDK to connect to your Kastrax backend. For more details on integrating Kastrax with Next.js, please refer to our [Integrate with Next.js](/docs/frameworks/next-js) documentation. ## Creating an Agent with TTS Capabilities ✅ The following example shows how to set up a story generator agent with TTS capabilities on the backend: ```typescript filename="src/kastrax/agents/index.ts" import { openai } from '@ai-sdk/openai'; import { Agent } from '@kastrax/core/agent'; import { OpenAIVoice } from '@kastrax/voice-openai'; import { Memory } from '@kastrax/memory'; const instructions = ` You are an Interactive Storyteller Agent. Your job is to create engaging short stories with user choices that influence the narrative. // omitted for brevity `; export const storyTellerAgent = new Agent({ name: 'Story Teller Agent', instructions: instructions, model: openai('gpt-4o'), voice: new OpenAIVoice(), }); ``` ## Registering the Agent with Kastrax ✅ This snippet demonstrates how to register the agent with your Kastrax instance: ```typescript filename="src/kastrax/index.ts" import { createLogger } from '@kastrax/core/logger'; import { Kastrax } from '@kastrax/core/kastrax'; import { storyTellerAgent } from './agents'; export const kastrax = new Kastrax({ agents: { storyTellerAgent }, logger: createLogger({ name: 'Kastrax', level: 'info', }), }); ``` ## Connecting to Kastrax from the Frontend ✅ Here we use the Kastrax Client SDK to interact with our Kastrax server. For more information about the Kastrax Client SDK, check out the [documentation](/docs/deployment/client). ```typescript filename="src/app/page.tsx" import { KastraxClient } from '@kastrax/client-js'; export const kastraxClient = new KastraxClient({ baseUrl: 'http://localhost:4111', // Replace with your Kastrax backend URL }); ``` ## Generating Story Content and Converting to Speech ✅ This example demonstrates how to get a reference to a Kastrax agent, generate story content based on user input, and then convert that content to speech: ``` typescript filename="/app/components/StoryManager.tsx" const handleInitialSubmit = async (formData: FormData) => { setIsLoading(true); try { const agent = kastraxClient.getAgent('storyTellerAgent'); const message = `Current phase: BEGINNING. Story genre: ${formData.genre}, Protagonist name: ${formData.protagonistDetails.name}, Protagonist age: ${formData.protagonistDetails.age}, Protagonist gender: ${formData.protagonistDetails.gender}, Protagonist occupation: ${formData.protagonistDetails.occupation}, Story Setting: ${formData.setting}`; const storyResponse = await agent.generate({ messages: [{ role: 'user', content: message }], threadId: storyState.threadId, resourceId: storyState.resourceId, }); const storyText = storyResponse.text; const audioResponse = await agent.voice.speak(storyText); if (!audioResponse.body) { throw new Error('No audio stream received'); } const audio = await readStream(audioResponse.body); setStoryState(prev => ({ phase: 'beginning', threadId: prev.threadId, resourceId: prev.resourceId, content: { ...prev.content, beginning: storyText, }, })); setAudioBlob(audio); return audio; } catch (error) { console.error('Error generating story beginning:', error); } finally { setIsLoading(false); } }; ``` ## Playing the Audio ✅ This snippet demonstrates how to handle text-to-speech audio playback by monitoring for new audio data. When audio is received, the code creates a browser-playable URL from the audio blob, assigns it to an audio element, and attempts to play it automatically: ```typescript filename="/app/components/StoryManager.tsx" useEffect(() => { if (!audioRef.current || !audioData) return; // Store a reference to the HTML audio element const currentAudio = audioRef.current; // Convert the Blob/File audio data from Kastrax into a URL the browser can play const url = URL.createObjectURL(audioData); const playAudio = async () => { try { currentAudio.src = url; await currentAudio.load(); await currentAudio.play(); setIsPlaying(true); } catch (error) { console.error('Auto-play failed:', error); } }; playAudio(); return () => { if (currentAudio) { currentAudio.pause(); currentAudio.src = ''; URL.revokeObjectURL(url); } }; }, [audioData]); ``` You can view the complete implementation of the Interactive Story Generator on our GitHub repository.




--- title: Turn Taking description: Example of using Kastrax to create a multi-agent debate with turn-taking conversation flow. --- import { GithubLink } from '@/components/github-link'; # AI Debate with Turn Taking ✅ [EN] Source: https://kastrax.ai/en/examples/voice/turn-taking The following code snippets demonstrate how to implement a multi-agent conversation system with turn-taking using Kastrax. This example creates a debate between two AI agents (an optimist and a skeptic) who discuss a user-provided topic, taking turns to respond to each other's points. ## Creating Agents with Voice Capabilities ✅ First, we need to create two agents with distinct personalities and voice capabilities: ```typescript filename="src/kastrax/agents/index.ts" import { openai } from '@ai-sdk/openai'; import { Agent } from '@kastrax/core/agent'; import { OpenAIVoice } from '@kastrax/voice-openai'; export const optimistAgent = new Agent({ name: "Optimist", instructions: "You are an optimistic debater who sees the positive side of every topic. Keep your responses concise and engaging, about 2-3 sentences.", model: openai("gpt-4o"), voice: new OpenAIVoice({ speaker: "alloy" }), }); export const skepticAgent = new Agent({ name: "Skeptic", instructions: "You are a RUDE skeptical debater who questions assumptions and points out potential issues. Keep your responses concise and engaging, about 2-3 sentences.", model: openai("gpt-4o"), voice: new OpenAIVoice({ speaker: "echo" }), }); ``` ## Registering the Agents with Kastrax ✅ Next, register both agents with your Kastrax instance: ```typescript filename="src/kastrax/index.ts" import { createLogger } from '@kastrax/core/logger'; import { Kastrax } from '@kastrax/core/kastrax'; import { optimistAgent, skepticAgent } from './agents'; export const kastrax = new Kastrax({ agents: { optimistAgent, skepticAgent }, logger: createLogger({ name: 'Kastrax', level: 'info', }), }); ``` ## Managing Turn-Taking in the Debate ✅ This example demonstrates how to manage the turn-taking flow between agents, ensuring each agent responds to the previous agent's statements: ```typescript filename="src/debate/turn-taking.ts" import { kastrax } from "../../kastrax"; import { playAudio, Recorder } from "@kastrax/node-audio"; import * as p from "@clack/prompts"; // Helper function to format text with line wrapping function formatText(text: string, maxWidth: number): string { const words = text.split(" "); let result = ""; let currentLine = ""; for (const word of words) { if (currentLine.length + word.length + 1 <= maxWidth) { currentLine += (currentLine ? " " : "") + word; } else { result += (result ? "\n" : "") + currentLine; currentLine = word; } } if (currentLine) { result += (result ? "\n" : "") + currentLine; } return result; } // Initialize audio recorder const recorder = new Recorder({ outputPath: "./debate.mp3", }); // Process one turn of the conversation async function processTurn( agentName: "optimistAgent" | "skepticAgent", otherAgentName: string, topic: string, previousResponse: string = "" ) { const agent = kastrax.getAgent(agentName); const spinner = p.spinner(); spinner.start(`${agent.name} is thinking...`); let prompt; if (!previousResponse) { // First turn prompt = `Discuss this topic: ${topic}. Introduce your perspective on it.`; } else { // Responding to the other agent prompt = `The topic is: ${topic}. ${otherAgentName} just said: "${previousResponse}". Respond to their points.`; } // Generate text response const { text } = await agent.generate(prompt, { temperature: 0.9, }); spinner.message(`${agent.name} is speaking...`); // Convert to speech and play const audioStream = await agent.voice.speak(text, { speed: 1.2, responseFormat: "wav", // Optional: specify a response format }); if (audioStream) { audioStream.on("data", (chunk) => { recorder.write(chunk); }); } spinner.stop(`${agent.name} said:`); // Format the text to wrap at 80 characters for better display const formattedText = formatText(text, 80); p.note(formattedText, agent.name); if (audioStream) { const speaker = playAudio(audioStream); await new Promise((resolve) => { speaker.once("close", () => { resolve(); }); }); } return text; } // Main function to run the debate export async function runDebate(topic: string, turns: number = 3) { recorder.start(); p.intro("AI Debate - Two Agents Discussing a Topic"); p.log.info(`Starting a debate on: ${topic}`); p.log.info( `The debate will continue for ${turns} turns each. Press Ctrl+C to exit at any time.` ); let optimistResponse = ""; let skepticResponse = ""; const responses = []; for (let turn = 1; turn <= turns; turn++) { p.log.step(`Turn ${turn}`); // Optimist's turn optimistResponse = await processTurn( "optimistAgent", "Skeptic", topic, skepticResponse ); responses.push({ agent: "Optimist", text: optimistResponse, }); // Skeptic's turn skepticResponse = await processTurn( "skepticAgent", "Optimist", topic, optimistResponse ); responses.push({ agent: "Skeptic", text: skepticResponse, }); } recorder.end(); p.outro("Debate concluded! The full audio has been saved to debate.mp3"); return responses; } ``` ## Running the Debate from the Command Line ✅ Here's a simple script to run the debate from the command line: ```typescript filename="src/index.ts" import { runDebate } from './debate/turn-taking'; import * as p from '@clack/prompts'; async function main() { // Get the topic from the user const topic = await p.text({ message: 'Enter a topic for the agents to discuss:', placeholder: 'Climate change', validate(value) { if (!value) return 'Please enter a topic'; return; }, }); // Exit if cancelled if (p.isCancel(topic)) { p.cancel('Operation cancelled.'); process.exit(0); } // Get the number of turns const turnsInput = await p.text({ message: 'How many turns should each agent have?', placeholder: '3', initialValue: '3', validate(value) { const num = parseInt(value); if (isNaN(num) || num < 1) return 'Please enter a positive number'; return; }, }); // Exit if cancelled if (p.isCancel(turnsInput)) { p.cancel('Operation cancelled.'); process.exit(0); } const turns = parseInt(turnsInput as string); // Run the debate await runDebate(topic as string, turns); } main().catch((error) => { p.log.error('An error occurred:'); console.error(error); process.exit(1); }); ``` ## Creating a Web Interface for the Debate ✅ For web applications, you can create a simple Next.js component that allows users to start a debate and listen to the agents' responses: ```tsx filename="app/components/DebateInterface.tsx" 'use client'; import { useState, useRef } from 'react'; import { KastraxClient } from '@kastrax/client-js'; const kastraxClient = new KastraxClient({ baseUrl: process.env.NEXT_PUBLIC_KASTRAX_URL || 'http://localhost:4111', }); export default function DebateInterface() { const [topic, setTopic] = useState(''); const [turns, setTurns] = useState(3); const [isDebating, setIsDebating] = useState(false); const [responses, setResponses] = useState([]); const [isPlaying, setIsPlaying] = useState(false); const audioRef = useRef(null); // Function to start the debate const startDebate = async () => { if (!topic) return; setIsDebating(true); setResponses([]); try { const optimist = kastraxClient.getAgent('optimistAgent'); const skeptic = kastraxClient.getAgent('skepticAgent'); const newResponses = []; let optimistResponse = ""; let skepticResponse = ""; for (let turn = 1; turn <= turns; turn++) { // Optimist's turn let prompt; if (turn === 1) { prompt = `Discuss this topic: ${topic}. Introduce your perspective on it.`; } else { prompt = `The topic is: ${topic}. Skeptic just said: "${skepticResponse}". Respond to their points.`; } const optimistResult = await optimist.generate({ messages: [{ role: 'user', content: prompt }], }); optimistResponse = optimistResult.text; newResponses.push({ agent: 'Optimist', text: optimistResponse }); // Update UI after each response setResponses([...newResponses]); // Skeptic's turn prompt = `The topic is: ${topic}. Optimist just said: "${optimistResponse}". Respond to their points.`; const skepticResult = await skeptic.generate({ messages: [{ role: 'user', content: prompt }], }); skepticResponse = skepticResult.text; newResponses.push({ agent: 'Skeptic', text: skepticResponse }); // Update UI after each response setResponses([...newResponses]); } } catch (error) { console.error('Error starting debate:', error); } finally { setIsDebating(false); } }; // Function to play audio for a specific response const playAudio = async (text: string, agent: string) => { if (isPlaying) return; try { setIsPlaying(true); const agentClient = kastraxClient.getAgent(agent === 'Optimist' ? 'optimistAgent' : 'skepticAgent'); const audioResponse = await agentClient.voice.speak(text); if (!audioResponse.body) { throw new Error('No audio stream received'); } // Convert stream to blob const reader = audioResponse.body.getReader(); const chunks = []; while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); } const blob = new Blob(chunks, { type: 'audio/mpeg' }); const url = URL.createObjectURL(blob); if (audioRef.current) { audioRef.current.src = url; audioRef.current.onended = () => { setIsPlaying(false); URL.revokeObjectURL(url); }; audioRef.current.play(); } } catch (error) { console.error('Error playing audio:', error); setIsPlaying(false); } }; return (

AI Debate with Turn Taking

setTopic(e.target.value)} className="w-full p-2 border rounded" placeholder="e.g., Climate change, AI ethics, Space exploration" />
setTurns(parseInt(e.target.value))} min={1} max={10} className="w-full p-2 border rounded" />
); } ``` This example demonstrates how to create a multi-agent conversation system with turn-taking using Kastrax. The agents engage in a debate on a user-chosen topic, with each agent responding to the previous agent's statements. The system also converts each agent's responses to speech, providing an immersive debate experience. You can view the complete implementation of the AI Debate with Turn Taking on our GitHub repository.




--- title: "Example: Branching Paths | Workflows | Kastrax Docs" description: Example of using Kastrax to create workflows with branching paths based on intermediate results. --- import { GithubLink } from "@/components/github-link"; # Branching Paths ✅ [EN] Source: https://kastrax.ai/en/examples/workflows/branching-paths When processing data, you often need to take different actions based on intermediate results. This example shows how to create a workflow that splits into separate paths, where each path executes different steps based on the output of a previous step. ## Control Flow Diagram ✅ This example shows how to create a workflow that splits into separate paths, where each path executes different steps based on the output of a previous step. Here's the control flow diagram: Diagram showing workflow with branching paths ## Creating the Steps ✅ Let's start by creating the steps and initializing the workflow. {/* prettier-ignore */} ```ts showLineNumbers copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod" const stepOne = new Step({ id: "stepOne", execute: async ({ context }) => ({ doubledValue: context.triggerData.inputValue * 2 }) }); const stepTwo = new Step({ id: "stepTwo", execute: async ({ context }) => { const stepOneResult = context.getStepResult<{ doubledValue: number }>("stepOne"); if (!stepOneResult) { return { isDivisibleByFive: false } } return { isDivisibleByFive: stepOneResult.doubledValue % 5 === 0 } } }); const stepThree = new Step({ id: "stepThree", execute: async ({ context }) =>{ const stepOneResult = context.getStepResult<{ doubledValue: number }>("stepOne"); if (!stepOneResult) { return { incrementedValue: 0 } } return { incrementedValue: stepOneResult.doubledValue + 1 } } }); const stepFour = new Step({ id: "stepFour", execute: async ({ context }) => { const stepThreeResult = context.getStepResult<{ incrementedValue: number }>("stepThree"); if (!stepThreeResult) { return { isDivisibleByThree: false } } return { isDivisibleByThree: stepThreeResult.incrementedValue % 3 === 0 } } }); // New step that depends on both branches const finalStep = new Step({ id: "finalStep", execute: async ({ context }) => { // Get results from both branches using getStepResult const stepTwoResult = context.getStepResult<{ isDivisibleByFive: boolean }>("stepTwo"); const stepFourResult = context.getStepResult<{ isDivisibleByThree: boolean }>("stepFour"); const isDivisibleByFive = stepTwoResult?.isDivisibleByFive || false; const isDivisibleByThree = stepFourResult?.isDivisibleByThree || false; return { summary: `The number ${context.triggerData.inputValue} when doubled ${isDivisibleByFive ? 'is' : 'is not'} divisible by 5, and when doubled and incremented ${isDivisibleByThree ? 'is' : 'is not'} divisible by 3.`, isDivisibleByFive, isDivisibleByThree } } }); // Build the workflow const myWorkflow = new Workflow({ name: "my-workflow", triggerSchema: z.object({ inputValue: z.number(), }), }); ``` ## Branching Paths and Chaining Steps ✅ Now let's configure the workflow with branching paths and merge them using the compound `.after([])` syntax. ```ts showLineNumbers copy // Create two parallel branches myWorkflow // First branch .step(stepOne) .then(stepTwo) // Second branch .after(stepOne) .step(stepThree) .then(stepFour) // Merge both branches using compound after syntax .after([stepTwo, stepFour]) .step(finalStep) .commit(); const { start } = myWorkflow.createRun(); const result = await start({ triggerData: { inputValue: 3 } }); console.log(result.steps.finalStep.output.summary); // Output: "The number 3 when doubled is not divisible by 5, and when doubled and incremented is divisible by 3." ``` ## Advanced Branching and Merging ✅ You can create more complex workflows with multiple branches and merge points: ```ts showLineNumbers copy const complexWorkflow = new Workflow({ name: "complex-workflow", triggerSchema: z.object({ inputValue: z.number(), }), }); // Create multiple branches with different merge points complexWorkflow // Main step .step(stepOne) // First branch .then(stepTwo) // Second branch .after(stepOne) .step(stepThree) .then(stepFour) // Third branch (another path from stepOne) .after(stepOne) .step(new Step({ id: "alternativePath", execute: async ({ context }) => { const stepOneResult = context.getStepResult<{ doubledValue: number }>("stepOne"); return { result: (stepOneResult?.doubledValue || 0) * 3 } } })) // Merge first and second branches .after([stepTwo, stepFour]) .step(new Step({ id: "partialMerge", execute: async ({ context }) => { const stepTwoResult = context.getStepResult<{ isDivisibleByFive: boolean }>("stepTwo"); const stepFourResult = context.getStepResult<{ isDivisibleByThree: boolean }>("stepFour"); return { intermediateResult: "Processed first two branches", branchResults: { branch1: stepTwoResult?.isDivisibleByFive, branch2: stepFourResult?.isDivisibleByThree } } } })) // Final merge of all branches .after(["partialMerge", "alternativePath"]) .step(new Step({ id: "finalMerge", execute: async ({ context }) => { const partialMergeResult = context.getStepResult<{ intermediateResult: string, branchResults: { branch1: boolean, branch2: boolean } }>("partialMerge"); const alternativePathResult = context.getStepResult<{ result: number }>("alternativePath"); return { finalResult: "All branches processed", combinedData: { fromPartialMerge: partialMergeResult?.branchResults, fromAlternativePath: alternativePathResult?.result } } } })) .commit(); ```




--- title: "Example: Calling an Agent from a Workflow | Kastrax Docs" description: Example of using Kastrax to call an AI agent from within a workflow step. --- import { GithubLink } from "@/components/github-link"; # Calling an Agent From a Workflow ✅ [EN] Source: https://kastrax.ai/en/examples/workflows/calling-agent This example demonstrates how to create a workflow that calls an AI agent to process messages and generate responses, and execute it within a workflow step. ```ts showLineNumbers copy import { openai } from "@ai-sdk/openai"; import { Kastrax } from "@kastrax/core"; import { Agent } from "@kastrax/core/agent"; import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; const penguin = new Agent({ name: "agent skipper", instructions: `You are skipper from penguin of madagascar, reply as that`, model: openai("gpt-4o-mini"), }); const newWorkflow = new Workflow({ name: "pass message to the workflow", triggerSchema: z.object({ message: z.string(), }), }); const replyAsSkipper = new Step({ id: "reply", outputSchema: z.object({ reply: z.string(), }), execute: async ({ context, kastrax }) => { const skipper = kastrax?.getAgent('penguin'); const res = await skipper?.generate( context?.triggerData?.message, ); return { reply: res?.text || "" }; }, }); newWorkflow.step(replyAsSkipper); newWorkflow.commit(); const kastrax = new Kastrax({ agents: { penguin }, workflows: { newWorkflow }, }); const { runId, start } = await kastrax.getWorkflow("newWorkflow").createRun(); const runResult = await start({ triggerData: { message: "Give me a run down of the mission to save private" }, }); console.log(runResult.results); ```




--- title: "Example: Conditional Branching (experimental) | Workflows | Kastrax Docs" description: Example of using Kastrax to create conditional branches in workflows using if/else statements. --- import { GithubLink } from '@/components/github-link'; # Workflow with Conditional Branching (experimental) ✅ [EN] Source: https://kastrax.ai/en/examples/workflows/conditional-branching Workflows often need to follow different paths based on conditions. This example demonstrates how to use `if` and `else` to create conditional branches in your workflows. ## Basic If/Else Example ✅ This example shows a simple workflow that takes different paths based on a numeric value: ```ts showLineNumbers copy import { Kastrax } from '@kastrax/core'; import { Step, Workflow } from '@kastrax/core/workflows'; import { z } from 'zod'; // Step that provides the initial value const startStep = new Step({ id: 'start', outputSchema: z.object({ value: z.number(), }), execute: async ({ context }) => { // Get the value from the trigger data const value = context.triggerData.inputValue; return { value }; }, }); // Step that handles high values const highValueStep = new Step({ id: 'highValue', outputSchema: z.object({ result: z.string(), }), execute: async ({ context }) => { const value = context.getStepResult<{ value: number }>('start')?.value; return { result: `High value processed: ${value}` }; }, }); // Step that handles low values const lowValueStep = new Step({ id: 'lowValue', outputSchema: z.object({ result: z.string(), }), execute: async ({ context }) => { const value = context.getStepResult<{ value: number }>('start')?.value; return { result: `Low value processed: ${value}` }; }, }); // Final step that summarizes the result const finalStep = new Step({ id: 'final', outputSchema: z.object({ summary: z.string(), }), execute: async ({ context }) => { // Get the result from whichever branch executed const highResult = context.getStepResult<{ result: string }>('highValue')?.result; const lowResult = context.getStepResult<{ result: string }>('lowValue')?.result; const result = highResult || lowResult; return { summary: `Processing complete: ${result}` }; }, }); // Build the workflow with conditional branching const conditionalWorkflow = new Workflow({ name: 'conditional-workflow', triggerSchema: z.object({ inputValue: z.number(), }), }); conditionalWorkflow .step(startStep) .if(async ({ context }) => { const value = context.getStepResult<{ value: number }>('start')?.value ?? 0; return value >= 10; // Condition: value is 10 or greater }) .then(highValueStep) .then(finalStep) .else() .then(lowValueStep) .then(finalStep) // Both branches converge on the final step .commit(); // Register the workflow const kastrax = new Kastrax({ workflows: { conditionalWorkflow }, }); // Example usage async function runWorkflow(inputValue: number) { const workflow = kastrax.getWorkflow('conditionalWorkflow'); const { start } = workflow.createRun(); const result = await start({ triggerData: { inputValue }, }); console.log('Workflow result:', result.results); return result; } // Run with a high value (follows the "if" branch) const result1 = await runWorkflow(15); // Run with a low value (follows the "else" branch) const result2 = await runWorkflow(5); console.log('Result 1:', result1); console.log('Result 2:', result2); ``` ## Using Reference-Based Conditions ✅ You can also use reference-based conditions with comparison operators: ```ts showLineNumbers copy // Using reference-based conditions instead of functions conditionalWorkflow .step(startStep) .if({ ref: { step: startStep, path: 'value' }, query: { $gte: 10 }, // Condition: value is 10 or greater }) .then(highValueStep) .then(finalStep) .else() .then(lowValueStep) .then(finalStep) .commit(); ```




--- title: "Example: Creating a Workflow | Workflows | Kastrax Docs" description: Example of using Kastrax to define and execute a simple workflow with a single step. --- import { GithubLink } from "@/components/github-link"; # Creating a Simple Workflow ✅ [EN] Source: https://kastrax.ai/en/examples/workflows/creating-a-workflow A workflow allows you to define and execute sequences of operations in a structured path. This example shows a workflow with a single step. ```ts showLineNumbers copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; const myWorkflow = new Workflow({ name: "my-workflow", triggerSchema: z.object({ input: z.number(), }), }); const stepOne = new Step({ id: "stepOne", inputSchema: z.object({ value: z.number(), }), outputSchema: z.object({ doubledValue: z.number(), }), execute: async ({ context }) => { const doubledValue = context?.triggerData?.input * 2; return { doubledValue }; }, }); myWorkflow.step(stepOne).commit(); const { runId, start } = myWorkflow.createRun(); const res = await start({ triggerData: { input: 90 }, }); console.log(res.results); ```




--- title: "Example: Cyclical Dependencies | Workflows | Kastrax Docs" description: Example of using Kastrax to create workflows with cyclical dependencies and conditional loops. --- import { GithubLink } from "@/components/github-link"; # Workflow with Cyclical dependencies ✅ [EN] Source: https://kastrax.ai/en/examples/workflows/cyclical-dependencies Workflows support cyclical dependencies where steps can loop back based on conditions. The example below shows how to use conditional logic to create loops and handle repeated execution. ```ts showLineNumbers copy import { Workflow, Step } from '@kastrax/core'; import { z } from 'zod'; async function main() { const doubleValue = new Step({ id: 'doubleValue', description: 'Doubles the input value', inputSchema: z.object({ inputValue: z.number(), }), outputSchema: z.object({ doubledValue: z.number(), }), execute: async ({ context }) => { const doubledValue = context.inputValue * 2; return { doubledValue }; }, }); const incrementByOne = new Step({ id: 'incrementByOne', description: 'Adds 1 to the input value', outputSchema: z.object({ incrementedValue: z.number(), }), execute: async ({ context }) => { const valueToIncrement = context?.getStepResult<{ firstValue: number }>('trigger')?.firstValue; if (!valueToIncrement) throw new Error('No value to increment provided'); const incrementedValue = valueToIncrement + 1; return { incrementedValue }; }, }); const cyclicalWorkflow = new Workflow({ name: 'cyclical-workflow', triggerSchema: z.object({ firstValue: z.number(), }), }); cyclicalWorkflow .step(doubleValue, { variables: { inputValue: { step: 'trigger', path: 'firstValue', }, }, }) .then(incrementByOne) .after(doubleValue) .step(doubleValue, { variables: { inputValue: { step: doubleValue, path: 'doubledValue', }, }, }) .commit(); const { runId, start } = cyclicalWorkflow.createRun(); console.log('Run', runId); const res = await start({ triggerData: { firstValue: 6 } }); console.log(res.results); } main(); ```




--- title: "Example: Human in the Loop | Workflows | Kastrax Docs" description: Example of using Kastrax to create workflows with human intervention points. --- import { GithubLink } from '@/components/github-link'; # Human in the Loop Workflow ✅ [EN] Source: https://kastrax.ai/en/examples/workflows/human-in-the-loop Human-in-the-loop workflows allow you to pause execution at specific points to collect user input, make decisions, or perform actions that require human judgment. This example demonstrates how to create a workflow with human intervention points. ## How It Works ✅ 1. A workflow step can **suspend** execution using the `suspend()` function, optionally passing a payload with context for the human decision maker. 2. When the workflow is **resumed**, the human input is passed in the `context` parameter of the `resume()` call. 3. This input becomes available in the step's execution context as `context.inputData`, which is typed according to the step's `inputSchema`. 4. The step can then continue execution based on the human input. This pattern allows for safe, type-checked human intervention in automated workflows. ## Interactive Terminal Example Using Inquirer ✅ This example demonstrates how to use the [Inquirer](https://www.npmjs.com/package/@inquirer/prompts) library to collect user input directly from the terminal when a workflow is suspended, creating a truly interactive human-in-the-loop experience. ```ts showLineNumbers copy import { Kastrax } from '@kastrax/core'; import { Step, Workflow } from '@kastrax/core/workflows'; import { z } from 'zod'; import { confirm, input, select } from '@inquirer/prompts'; // Step 1: Generate product recommendations const generateRecommendations = new Step({ id: 'generateRecommendations', outputSchema: z.object({ customerName: z.string(), recommendations: z.array( z.object({ productId: z.string(), productName: z.string(), price: z.number(), description: z.string(), }), ), }), execute: async ({ context }) => { const customerName = context.triggerData.customerName; // In a real application, you might call an API or ML model here // For this example, we'll return mock data return { customerName, recommendations: [ { productId: 'prod-001', productName: 'Premium Widget', price: 99.99, description: 'Our best-selling premium widget with advanced features', }, { productId: 'prod-002', productName: 'Basic Widget', price: 49.99, description: 'Affordable entry-level widget for beginners', }, { productId: 'prod-003', productName: 'Widget Pro Plus', price: 149.99, description: 'Professional-grade widget with extended warranty', }, ], }; }, }); ``` ```ts showLineNumbers copy // Step 2: Get human approval and customization for the recommendations const reviewRecommendations = new Step({ id: 'reviewRecommendations', inputSchema: z.object({ approvedProducts: z.array(z.string()), customerNote: z.string().optional(), offerDiscount: z.boolean().optional(), }), outputSchema: z.object({ finalRecommendations: z.array( z.object({ productId: z.string(), productName: z.string(), price: z.number(), }), ), customerNote: z.string().optional(), offerDiscount: z.boolean(), }), execute: async ({ context, suspend }) => { const { customerName, recommendations } = context.getStepResult(generateRecommendations) || { customerName: '', recommendations: [], }; // Check if we have input from a resumed workflow const reviewInput = { approvedProducts: context.inputData?.approvedProducts || [], customerNote: context.inputData?.customerNote, offerDiscount: context.inputData?.offerDiscount, }; // If we don't have agent input yet, suspend for human review if (!reviewInput.approvedProducts.length) { console.log(`Generating recommendations for customer: ${customerName}`); await suspend({ customerName, recommendations, message: 'Please review these product recommendations before sending to the customer', }); // Placeholder return (won't be reached due to suspend) return { finalRecommendations: [], customerNote: '', offerDiscount: false, }; } // Process the agent's product selections const finalRecommendations = recommendations .filter(product => reviewInput.approvedProducts.includes(product.productId)) .map(product => ({ productId: product.productId, productName: product.productName, price: product.price, })); return { finalRecommendations, customerNote: reviewInput.customerNote || '', offerDiscount: reviewInput.offerDiscount || false, }; }, }); ``` ```ts showLineNumbers copy // Step 3: Send the recommendations to the customer const sendRecommendations = new Step({ id: 'sendRecommendations', outputSchema: z.object({ emailSent: z.boolean(), emailContent: z.string(), }), execute: async ({ context }) => { const { customerName } = context.getStepResult(generateRecommendations) || { customerName: '' }; const { finalRecommendations, customerNote, offerDiscount } = context.getStepResult(reviewRecommendations) || { finalRecommendations: [], customerNote: '', offerDiscount: false, }; // Generate email content based on the recommendations let emailContent = `Dear ${customerName},\n\nBased on your preferences, we recommend:\n\n`; finalRecommendations.forEach(product => { emailContent += `- ${product.productName}: $${product.price.toFixed(2)}\n`; }); if (offerDiscount) { emailContent += '\nAs a valued customer, use code SAVE10 for 10% off your next purchase!\n'; } if (customerNote) { emailContent += `\nPersonal note: ${customerNote}\n`; } emailContent += '\nThank you for your business,\nThe Sales Team'; // In a real application, you would send this email console.log('Email content generated:', emailContent); return { emailSent: true, emailContent, }; }, }); // Build the workflow const recommendationWorkflow = new Workflow({ name: 'product-recommendation-workflow', triggerSchema: z.object({ customerName: z.string(), }), }); recommendationWorkflow .step(generateRecommendations) .then(reviewRecommendations) .then(sendRecommendations) .commit(); // Register the workflow const kastrax = new Kastrax({ workflows: { recommendationWorkflow }, }); ``` ```ts showLineNumbers copy // Example of using the workflow with Inquirer prompts async function runRecommendationWorkflow() { const registeredWorkflow = kastrax.getWorkflow('recommendationWorkflow'); const run = registeredWorkflow.createRun(); console.log('Starting product recommendation workflow...'); const result = await run.start({ triggerData: { customerName: 'Jane Smith', }, }); const isReviewStepSuspended = result.activePaths.get('reviewRecommendations')?.status === 'suspended'; // Check if workflow is suspended for human review if (isReviewStepSuspended) { const { customerName, recommendations, message } = result.activePaths.get('reviewRecommendations')?.suspendPayload; console.log('\n==================================='); console.log(message); console.log(`Customer: ${customerName}`); console.log('===================================\n'); // Use Inquirer to collect input from the sales agent in the terminal console.log('Available product recommendations:'); recommendations.forEach((product, index) => { console.log(`${index + 1}. ${product.productName} - $${product.price.toFixed(2)}`); console.log(` ${product.description}\n`); }); // Let the agent select which products to recommend const approvedProducts = await checkbox({ message: 'Select products to recommend to the customer:', choices: recommendations.map(product => ({ name: `${product.productName} ($${product.price.toFixed(2)})`, value: product.productId, })), }); // Let the agent add a personal note const includeNote = await confirm({ message: 'Would you like to add a personal note?', default: false, }); let customerNote = ''; if (includeNote) { customerNote = await input({ message: 'Enter your personalized note for the customer:', }); } // Ask if a discount should be offered const offerDiscount = await confirm({ message: 'Offer a 10% discount to this customer?', default: false, }); console.log('\nSubmitting your review...'); // Resume the workflow with the agent's input const resumeResult = await run.resume({ stepId: 'reviewRecommendations', context: { approvedProducts, customerNote, offerDiscount, }, }); console.log('\n==================================='); console.log('Workflow completed!'); console.log('Email content:'); console.log('===================================\n'); console.log(resumeResult?.results?.sendRecommendations || 'No email content generated'); return resumeResult; } return result; } // Invoke the workflow with interactive terminal input runRecommendationWorkflow().catch(console.error); ``` ## Advanced Example with Multiple User Inputs ✅ This example demonstrates a more complex workflow that requires multiple human intervention points, such as in a content moderation system. ```ts showLineNumbers copy import { Kastrax } from '@kastrax/core'; import { Step, Workflow } from '@kastrax/core/workflows'; import { z } from 'zod'; import { select, input } from '@inquirer/prompts'; // Step 1: Receive and analyze content const analyzeContent = new Step({ id: 'analyzeContent', outputSchema: z.object({ content: z.string(), aiAnalysisScore: z.number(), flaggedCategories: z.array(z.string()).optional(), }), execute: async ({ context }) => { const content = context.triggerData.content; // Simulate AI analysis const aiAnalysisScore = simulateContentAnalysis(content); const flaggedCategories = aiAnalysisScore < 0.7 ? ['potentially inappropriate', 'needs review'] : []; return { content, aiAnalysisScore, flaggedCategories, }; }, }); ``` ```ts showLineNumbers copy // Step 2: Moderate content that needs review const moderateContent = new Step({ id: 'moderateContent', // Define the schema for human input that will be provided when resuming inputSchema: z.object({ moderatorDecision: z.enum(['approve', 'reject', 'modify']).optional(), moderatorNotes: z.string().optional(), modifiedContent: z.string().optional(), }), outputSchema: z.object({ moderationResult: z.enum(['approved', 'rejected', 'modified']), moderatedContent: z.string(), notes: z.string().optional(), }), // @ts-ignore execute: async ({ context, suspend }) => { const analysisResult = context.getStepResult(analyzeContent); // Access the input provided when resuming the workflow const moderatorInput = { decision: context.inputData?.moderatorDecision, notes: context.inputData?.moderatorNotes, modifiedContent: context.inputData?.modifiedContent, }; // If the AI analysis score is high enough, auto-approve if (analysisResult?.aiAnalysisScore > 0.9 && !analysisResult?.flaggedCategories?.length) { return { moderationResult: 'approved', moderatedContent: analysisResult.content, notes: 'Auto-approved by system', }; } // If we don't have moderator input yet, suspend for human review if (!moderatorInput.decision) { await suspend({ content: analysisResult?.content, aiScore: analysisResult?.aiAnalysisScore, flaggedCategories: analysisResult?.flaggedCategories, message: 'Please review this content and make a moderation decision', }); // Placeholder return return { moderationResult: 'approved', moderatedContent: '', }; } // Process the moderator's decision switch (moderatorInput.decision) { case 'approve': return { moderationResult: 'approved', moderatedContent: analysisResult?.content || '', notes: moderatorInput.notes || 'Approved by moderator', }; case 'reject': return { moderationResult: 'rejected', moderatedContent: '', notes: moderatorInput.notes || 'Rejected by moderator', }; case 'modify': return { moderationResult: 'modified', moderatedContent: moderatorInput.modifiedContent || analysisResult?.content || '', notes: moderatorInput.notes || 'Modified by moderator', }; default: return { moderationResult: 'rejected', moderatedContent: '', notes: 'Invalid moderator decision', }; } }, }); ``` ```ts showLineNumbers copy // Step 3: Apply moderation actions const applyModeration = new Step({ id: 'applyModeration', outputSchema: z.object({ finalStatus: z.string(), content: z.string().optional(), auditLog: z.object({ originalContent: z.string(), moderationResult: z.string(), aiScore: z.number(), timestamp: z.string(), }), }), execute: async ({ context }) => { const analysisResult = context.getStepResult(analyzeContent); const moderationResult = context.getStepResult(moderateContent); // Create audit log const auditLog = { originalContent: analysisResult?.content || '', moderationResult: moderationResult?.moderationResult || 'unknown', aiScore: analysisResult?.aiAnalysisScore || 0, timestamp: new Date().toISOString(), }; // Apply moderation action switch (moderationResult?.moderationResult) { case 'approved': return { finalStatus: 'Content published', content: moderationResult.moderatedContent, auditLog, }; case 'modified': return { finalStatus: 'Content modified and published', content: moderationResult.moderatedContent, auditLog, }; case 'rejected': return { finalStatus: 'Content rejected', auditLog, }; default: return { finalStatus: 'Error in moderation process', auditLog, }; } }, }); ``` ```ts showLineNumbers copy // Build the workflow const contentModerationWorkflow = new Workflow({ name: 'content-moderation-workflow', triggerSchema: z.object({ content: z.string(), }), }); contentModerationWorkflow .step(analyzeContent) .then(moderateContent) .then(applyModeration) .commit(); // Register the workflow const kastrax = new Kastrax({ workflows: { contentModerationWorkflow }, }); // Example of using the workflow with Inquirer prompts async function runModerationDemo() { const registeredWorkflow = kastrax.getWorkflow('contentModerationWorkflow'); const run = registeredWorkflow.createRun(); // Start the workflow with content that needs review console.log('Starting content moderation workflow...'); const result = await run.start({ triggerData: { content: 'This is some user-generated content that requires moderation.' } }); const isReviewStepSuspended = result.activePaths.get('moderateContent')?.status === 'suspended'; // Check if workflow is suspended if (isReviewStepSuspended) { const { content, aiScore, flaggedCategories, message } = result.activePaths.get('moderateContent')?.suspendPayload; console.log('\n==================================='); console.log(message); console.log('===================================\n'); console.log('Content to review:'); console.log(content); console.log(`\nAI Analysis Score: ${aiScore}`); console.log(`Flagged Categories: ${flaggedCategories?.join(', ') || 'None'}\n`); // Collect moderator decision using Inquirer const moderatorDecision = await select({ message: 'Select your moderation decision:', choices: [ { name: 'Approve content as is', value: 'approve' }, { name: 'Reject content completely', value: 'reject' }, { name: 'Modify content before publishing', value: 'modify' } ], }); // Collect additional information based on decision let moderatorNotes = ''; let modifiedContent = ''; moderatorNotes = await input({ message: 'Enter any notes about your decision:', }); if (moderatorDecision === 'modify') { modifiedContent = await input({ message: 'Enter the modified content:', default: content, }); } console.log('\nSubmitting your moderation decision...'); // Resume the workflow with the moderator's input const resumeResult = await run.resume({ stepId: 'moderateContent', context: { moderatorDecision, moderatorNotes, modifiedContent, }, }); if (resumeResult?.results?.applyModeration?.status === 'success') { console.log('\n==================================='); console.log(`Moderation complete: ${resumeResult?.results?.applyModeration?.output.finalStatus}`); console.log('===================================\n'); if (resumeResult?.results?.applyModeration?.output.content) { console.log('Published content:'); console.log(resumeResult.results.applyModeration.output.content); } } return resumeResult; } console.log('Workflow completed without requiring human intervention:', result.results); return result; } // Helper function for AI content analysis simulation function simulateContentAnalysis(content: string): number { // In a real application, this would call an AI service // For the example, we're returning a random score return Math.random(); } // Invoke the demo function runModerationDemo().catch(console.error); ``` ## Key Concepts ✅ 1. **Suspension Points** - Use the `suspend()` function within a step's execute to pause workflow execution. 2. **Suspension Payload** - Pass relevant data when suspending to provide context for human decision-making: ```ts await suspend({ messageForHuman: 'Please review this data', data: someImportantData }); ``` 3. **Checking Workflow Status** - After starting a workflow, check the returned status to see if it's suspended: ```ts const result = await workflow.start({ triggerData }); if (result.status === 'suspended' && result.suspendedStepId === 'stepId') { // Process suspension console.log('Workflow is waiting for input:', result.suspendPayload); } ``` 4. **Interactive Terminal Input** - Use libraries like Inquirer to create interactive prompts: ```ts import { select, input, confirm } from '@inquirer/prompts'; // When the workflow is suspended if (result.status === 'suspended') { // Display information from the suspend payload console.log(result.suspendPayload.message); // Collect user input interactively const decision = await select({ message: 'What would you like to do?', choices: [ { name: 'Approve', value: 'approve' }, { name: 'Reject', value: 'reject' } ] }); // Resume the workflow with the collected input await run.resume({ stepId: result.suspendedStepId, context: { decision } }); } ``` 5. **Resuming Workflow** - Use the `resume()` method to continue workflow execution with human input: ```ts const resumeResult = await run.resume({ stepId: 'suspendedStepId', context: { // This data is passed to the suspended step as context.inputData // and must conform to the step's inputSchema userDecision: 'approve' }, }); ``` 6. **Input Schema for Human Data** - Define an input schema on steps that might be resumed with human input to ensure type safety: ```ts const myStep = new Step({ id: 'myStep', inputSchema: z.object({ // This schema validates the data passed in resume's context // and makes it available as context.inputData userDecision: z.enum(['approve', 'reject']), userComments: z.string().optional(), }), execute: async ({ context, suspend }) => { // Check if we have user input from a previous suspension if (context.inputData?.userDecision) { // Process the user's decision return { result: `User decided: ${context.inputData.userDecision}` }; } // If no input, suspend for human decision await suspend(); } }); ``` Human-in-the-loop workflows are powerful for building systems that blend automation with human judgment, such as: - Content moderation systems - Approval workflows - Supervised AI systems - Customer service automation with escalation




--- title: "Example: Parallel Execution | Workflows | Kastrax Docs" description: Example of using Kastrax to execute multiple independent tasks in parallel within a workflow. --- import { GithubLink } from "@/components/github-link"; # Parallel Execution with Steps ✅ [EN] Source: https://kastrax.ai/en/examples/workflows/parallel-steps When building AI applications, you often need to process multiple independent tasks simultaneously to improve efficiency. ## Control Flow Diagram ✅ This example shows how to structure a workflow that executes steps in parallel, with each branch handling its own data flow and dependencies. Here's the control flow diagram: Diagram showing workflow with parallel steps ## Creating the Steps ✅ Let's start by creating the steps and initializing the workflow. ```ts showLineNumbers copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; const stepOne = new Step({ id: "stepOne", execute: async ({ context }) => ({ doubledValue: context.triggerData.inputValue * 2, }), }); const stepTwo = new Step({ id: "stepTwo", execute: async ({ context }) => { if (context.steps.stepOne.status !== "success") { return { incrementedValue: 0 } } return { incrementedValue: context.steps.stepOne.output.doubledValue + 1 } }, }); const stepThree = new Step({ id: "stepThree", execute: async ({ context }) => ({ tripledValue: context.triggerData.inputValue * 3, }), }); const stepFour = new Step({ id: "stepFour", execute: async ({ context }) => { if (context.steps.stepThree.status !== "success") { return { isEven: false } } return { isEven: context.steps.stepThree.output.tripledValue % 2 === 0 } }, }); const myWorkflow = new Workflow({ name: "my-workflow", triggerSchema: z.object({ inputValue: z.number(), }), }); ``` ## Chaining and Parallelizing Steps ✅ Now we can add the steps to the workflow. Note the `.then()` method is used to chain the steps, but the `.step()` method is used to add the steps to the workflow. ```ts showLineNumbers copy myWorkflow .step(stepOne) .then(stepTwo) // chain one .step(stepThree) .then(stepFour) // chain two .commit(); const { start } = myWorkflow.createRun(); const result = await start({ triggerData: { inputValue: 3 } }); ```




--- title: "Example: Sequential Steps | Workflows | Kastrax Docs" description: Example of using Kastrax to chain workflow steps in a specific sequence, passing data between them. --- import { GithubLink } from "@/components/github-link"; # Workflow with Sequential Steps ✅ [EN] Source: https://kastrax.ai/en/examples/workflows/sequential-steps Workflow can be chained to run one after another in a specific sequence. ## Control Flow Diagram ✅ This example shows how to chain workflow steps by using the `then` method demonstrating how to pass data between sequential steps and execute them in order. Here's the control flow diagram: Diagram showing workflow with sequential steps ## Creating the Steps ✅ Let's start by creating the steps and initializing the workflow. ```ts showLineNumbers copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; const stepOne = new Step({ id: "stepOne", execute: async ({ context }) => ({ doubledValue: context.triggerData.inputValue * 2, }), }); const stepTwo = new Step({ id: "stepTwo", execute: async ({ context }) => { if (context.steps.stepOne.status !== "success") { return { incrementedValue: 0 } } return { incrementedValue: context.steps.stepOne.output.doubledValue + 1 } }, }); const stepThree = new Step({ id: "stepThree", execute: async ({ context }) => { if (context.steps.stepTwo.status !== "success") { return { tripledValue: 0 } } return { tripledValue: context.steps.stepTwo.output.incrementedValue * 3 } }, }); // Build the workflow const myWorkflow = new Workflow({ name: "my-workflow", triggerSchema: z.object({ inputValue: z.number(), }), }); ``` ## Chaining the Steps and Executing the Workflow ✅ Now let's chain the steps together. ```ts showLineNumbers copy // sequential steps myWorkflow.step(stepOne).then(stepTwo).then(stepThree); myWorkflow.commit(); const { start } = myWorkflow.createRun(); const res = await start({ triggerData: { inputValue: 90 } }); ```




--- title: "Example: Suspend and Resume | Workflows | Kastrax Docs" description: Example of using Kastrax to suspend and resume workflow steps during execution. --- import { GithubLink } from '@/components/github-link'; # Workflow with Suspend and Resume ✅ [EN] Source: https://kastrax.ai/en/examples/workflows/suspend-and-resume Workflow steps can be suspended and resumed at any point in the workflow execution. This example demonstrates how to suspend a workflow step and resume it later. ## Basic Example ✅ ```ts showLineNumbers copy import { Kastrax } from '@kastrax/core'; import { Step, Workflow } from '@kastrax/core/workflows'; import { z } from 'zod'; const stepOne = new Step({ id: 'stepOne', outputSchema: z.object({ doubledValue: z.number(), }), execute: async ({ context }) => { const doubledValue = context.triggerData.inputValue * 2; return { doubledValue }; }, }); ``` ```ts showLineNumbers copy const stepTwo = new Step({ id: 'stepTwo', outputSchema: z.object({ incrementedValue: z.number(), }), execute: async ({ context, suspend }) => { const secondValue = context.inputData?.secondValue ?? 0; const doubledValue = context.getStepResult(stepOne)?.doubledValue ?? 0; const incrementedValue = doubledValue + secondValue; if (incrementedValue < 100) { await suspend(); return { incrementedValue: 0 }; } return { incrementedValue }; }, }); // Build the workflow const myWorkflow = new Workflow({ name: 'my-workflow', triggerSchema: z.object({ inputValue: z.number(), }), }); // run workflows in parallel myWorkflow .step(stepOne) .then(stepTwo) .commit(); ``` ```ts showLineNumbers copy // Register the workflow export const kastrax = new Kastrax({ workflows: { registeredWorkflow: myWorkflow }, }) // Get registered workflow from Kastrax const registeredWorkflow = kastrax.getWorkflow('registeredWorkflow'); const { runId, start } = registeredWorkflow.createRun(); // Start watching the workflow before executing it myWorkflow.watch(async ({ context, activePaths }) => { for (const _path of activePaths) { const stepTwoStatus = context.steps?.stepTwo?.status; if (stepTwoStatus === 'suspended') { console.log("Workflow suspended, resuming with new value"); // Resume the workflow with new context await myWorkflow.resume({ runId, stepId: 'stepTwo', context: { secondValue: 100 }, }); } } }) // Start the workflow execution await start({ triggerData: { inputValue: 45 } }); ``` ## Advanced Example with Multiple Suspension Points Using async/await pattern and suspend payloads ✅ This example demonstrates a more complex workflow with multiple suspension points using the async/await pattern. It simulates a content generation workflow that requires human intervention at different stages. ```ts showLineNumbers copy import { Kastrax } from '@kastrax/core'; import { Step, Workflow } from '@kastrax/core/workflows'; import { z } from 'zod'; // Step 1: Get user input const getUserInput = new Step({ id: 'getUserInput', execute: async ({ context }) => { // In a real application, this might come from a form or API return { userInput: context.triggerData.input }; }, outputSchema: z.object({ userInput: z.string() }), }); ``` ```ts showLineNumbers copy // Step 2: Generate content with AI (may suspend for human guidance) const promptAgent = new Step({ id: 'promptAgent', inputSchema: z.object({ guidance: z.string(), }), execute: async ({ context, suspend }) => { const userInput = context.getStepResult(getUserInput)?.userInput; console.log(`Generating content based on: ${userInput}`); const guidance = context.inputData?.guidance; // Simulate AI generating content const initialDraft = generateInitialDraft(userInput); // If confidence is high, return the generated content directly if (initialDraft.confidenceScore > 0.7) { return { modelOutput: initialDraft.content }; } console.log('Low confidence in generated content, suspending for human guidance', {guidance}); // If confidence is low, suspend for human guidance if (!guidance) { // only suspend if no guidance is provided await suspend(); return undefined; } // This code runs after resume with human guidance console.log('Resumed with human guidance'); // Use the human guidance to improve the output return { modelOutput: enhanceWithGuidance(initialDraft.content, guidance), }; }, outputSchema: z.object({ modelOutput: z.string() }).optional(), }); ``` ```ts showLineNumbers copy // Step 3: Evaluate the content quality const evaluateTone = new Step({ id: 'evaluateToneConsistency', execute: async ({ context }) => { const content = context.getStepResult(promptAgent)?.modelOutput; // Simulate evaluation return { toneScore: { score: calculateToneScore(content) }, completenessScore: { score: calculateCompletenessScore(content) }, }; }, outputSchema: z.object({ toneScore: z.any(), completenessScore: z.any(), }), }); ``` ```ts showLineNumbers copy // Step 4: Improve response if needed (may suspend) const improveResponse = new Step({ id: 'improveResponse', inputSchema: z.object({ improvedContent: z.string(), resumeAttempts: z.number(), }), execute: async ({ context, suspend }) => { const content = context.getStepResult(promptAgent)?.modelOutput; const toneScore = context.getStepResult(evaluateTone)?.toneScore.score ?? 0; const completenessScore = context.getStepResult(evaluateTone)?.completenessScore.score ?? 0; const improvedContent = context.inputData.improvedContent; const resumeAttempts = context.inputData.resumeAttempts ?? 0; // If scores are above threshold, make minor improvements if (toneScore > 0.8 && completenessScore > 0.8) { return { improvedOutput: makeMinorImprovements(content) }; } console.log('Content quality below threshold, suspending for human intervention', {improvedContent, resumeAttempts}); if (!improvedContent) { // Suspend with payload containing content and resume attempts await suspend({ content, scores: { tone: toneScore, completeness: completenessScore }, needsImprovement: toneScore < 0.8 ? 'tone' : 'completeness', resumeAttempts: resumeAttempts + 1, }); return { improvedOutput: content ?? '' }; } console.log('Resumed with human improvements', improvedContent); return { improvedOutput: improvedContent ?? content ?? '' }; }, outputSchema: z.object({ improvedOutput: z.string() }).optional(), }); ``` ```ts showLineNumbers copy // Step 5: Final evaluation const evaluateImproved = new Step({ id: 'evaluateImprovedResponse', execute: async ({ context }) => { const improvedContent = context.getStepResult(improveResponse)?.improvedOutput; // Simulate final evaluation return { toneScore: { score: calculateToneScore(improvedContent) }, completenessScore: { score: calculateCompletenessScore(improvedContent) }, }; }, outputSchema: z.object({ toneScore: z.any(), completenessScore: z.any(), }), }); // Build the workflow const contentWorkflow = new Workflow({ name: 'content-generation-workflow', triggerSchema: z.object({ input: z.string() }), }); contentWorkflow .step(getUserInput) .then(promptAgent) .then(evaluateTone) .then(improveResponse) .then(evaluateImproved) .commit(); ``` ```ts showLineNumbers copy // Register the workflow const kastrax = new Kastrax({ workflows: { contentWorkflow }, }); // Helper functions (simulated) function generateInitialDraft(input: string = '') { // Simulate AI generating content return { content: `Generated content based on: ${input}`, confidenceScore: 0.6, // Simulate low confidence to trigger suspension }; } function enhanceWithGuidance(content: string = '', guidance: string = '') { return `${content} (Enhanced with guidance: ${guidance})`; } function makeMinorImprovements(content: string = '') { return `${content} (with minor improvements)`; } function calculateToneScore(_: string = '') { return 0.7; // Simulate a score that will trigger suspension } function calculateCompletenessScore(_: string = '') { return 0.9; } // Usage example async function runWorkflow() { const workflow = kastrax.getWorkflow('contentWorkflow'); const { runId, start } = workflow.createRun(); let finalResult: any; // Start the workflow const initialResult = await start({ triggerData: { input: 'Create content about sustainable energy' }, }); console.log('Initial workflow state:', initialResult.results); const promptAgentStepResult = initialResult.activePaths.get('promptAgent'); // Check if promptAgent step is suspended if (promptAgentStepResult?.status === 'suspended') { console.log('Workflow suspended at promptAgent step'); console.log('Suspension payload:', promptAgentStepResult?.suspendPayload); // Resume with human guidance const resumeResult1 = await workflow.resume({ runId, stepId: 'promptAgent', context: { guidance: 'Focus more on solar and wind energy technologies', }, }); console.log('Workflow resumed and continued to next steps'); let improveResponseResumeAttempts = 0; let improveResponseStatus = resumeResult1?.activePaths.get('improveResponse')?.status; // Check if improveResponse step is suspended while (improveResponseStatus === 'suspended') { console.log('Workflow suspended at improveResponse step'); console.log('Suspension payload:', resumeResult1?.activePaths.get('improveResponse')?.suspendPayload); const improvedContent = improveResponseResumeAttempts < 3 ? undefined : 'Completely revised content about sustainable energy focusing on solar and wind technologies'; // Resume with human improvements finalResult = await workflow.resume({ runId, stepId: 'improveResponse', context: { improvedContent, resumeAttempts: improveResponseResumeAttempts, }, }); improveResponseResumeAttempts = finalResult?.activePaths.get('improveResponse')?.suspendPayload?.resumeAttempts ?? 0; improveResponseStatus = finalResult?.activePaths.get('improveResponse')?.status; console.log('Improved response result:', finalResult?.results); } } return finalResult; } // Run the workflow const result = await runWorkflow(); console.log('Workflow completed'); console.log('Final workflow result:', result); ```




--- title: "Example: Using a Tool as a Step | Workflows | Kastrax Docs" description: Example of using Kastrax to integrate a custom tool as a step in a workflow. --- import { GithubLink } from '@/components/github-link'; # Tool as a Workflow step ✅ [EN] Source: https://kastrax.ai/en/examples/workflows/using-a-tool-as-a-step This example demonstrates how to create and integrate a custom tool as a workflow step, showing how to define input/output schemas and implement the tool's execution logic. ```ts showLineNumbers copy import { createTool } from '@kastrax/core/tools'; import { Workflow } from '@kastrax/core/workflows'; import { z } from 'zod'; const crawlWebpage = createTool({ id: 'Crawl Webpage', description: 'Crawls a webpage and extracts the text content', inputSchema: z.object({ url: z.string().url(), }), outputSchema: z.object({ rawText: z.string(), }), execute: async ({ context }) => { const response = await fetch(context.triggerData.url); const text = await response.text(); return { rawText: 'This is the text content of the webpage: ' + text }; }, }); const contentWorkflow = new Workflow({ name: 'content-review' }); contentWorkflow.step(crawlWebpage).commit(); const { start } = contentWorkflow.createRun(); const res = await start({ triggerData: { url: 'https://example.com'} }); console.log(res.results); ```




--- title: "Data Mapping with Workflow Variables | Kastrax Examples" description: "Learn how to use workflow variables to map data between steps in Kastrax workflows." --- # Data Mapping with Workflow Variables ✅ [EN] Source: https://kastrax.ai/en/examples/workflows/workflow-variables This example demonstrates how to use workflow variables to map data between steps in a Kastrax workflow. ## Use Case: User Registration Process ✅ In this example, we'll build a simple user registration workflow that: 1. Validates user input 1. Formats the user data 1. Creates a user profile ## Implementation ✅ ```typescript showLineNumbers filename="src/kastrax/workflows/user-registration.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; // Define our schemas for better type safety const userInputSchema = z.object({ email: z.string().email(), name: z.string(), age: z.number().min(18), }); const validatedDataSchema = z.object({ isValid: z.boolean(), validatedData: z.object({ email: z.string(), name: z.string(), age: z.number(), }), }); const formattedDataSchema = z.object({ userId: z.string(), formattedData: z.object({ email: z.string(), displayName: z.string(), ageGroup: z.string(), }), }); const profileSchema = z.object({ profile: z.object({ id: z.string(), email: z.string(), displayName: z.string(), ageGroup: z.string(), createdAt: z.string(), }), }); // Define the workflow const registrationWorkflow = new Workflow({ name: "user-registration", triggerSchema: userInputSchema, }); // Step 1: Validate user input const validateInput = new Step({ id: "validateInput", inputSchema: userInputSchema, outputSchema: validatedDataSchema, execute: async ({ context }) => { const { email, name, age } = context; // Simple validation logic const isValid = email.includes('@') && name.length > 0 && age >= 18; return { isValid, validatedData: { email: email.toLowerCase().trim(), name, age, }, }; }, }); // Step 2: Format user data const formatUserData = new Step({ id: "formatUserData", inputSchema: z.object({ validatedData: z.object({ email: z.string(), name: z.string(), age: z.number(), }), }), outputSchema: formattedDataSchema, execute: async ({ context }) => { const { validatedData } = context; // Generate a simple user ID const userId = `user_${Math.floor(Math.random() * 10000)}`; // Format the data const ageGroup = validatedData.age < 30 ? "young-adult" : "adult"; return { userId, formattedData: { email: validatedData.email, displayName: validatedData.name, ageGroup, }, }; }, }); // Step 3: Create user profile const createUserProfile = new Step({ id: "createUserProfile", inputSchema: z.object({ userId: z.string(), formattedData: z.object({ email: z.string(), displayName: z.string(), ageGroup: z.string(), }), }), outputSchema: profileSchema, execute: async ({ context }) => { const { userId, formattedData } = context; // In a real app, you would save to a database here return { profile: { id: userId, ...formattedData, createdAt: new Date().toISOString(), }, }; }, }); // Build the workflow with variable mappings registrationWorkflow // First step gets data from the trigger .step(validateInput, { variables: { email: { step: 'trigger', path: 'email' }, name: { step: 'trigger', path: 'name' }, age: { step: 'trigger', path: 'age' }, } }) // Format user data with validated data from previous step .then(formatUserData, { variables: { validatedData: { step: validateInput, path: 'validatedData' }, }, when: { ref: { step: validateInput, path: 'isValid' }, query: { $eq: true }, }, }) // Create profile with data from the format step .then(createUserProfile, { variables: { userId: { step: formatUserData, path: 'userId' }, formattedData: { step: formatUserData, path: 'formattedData' }, }, }) .commit(); export default registrationWorkflow; ``` ## How to Use This Example ✅ 1. Create the file as shown above 2. Register the workflow in your Kastrax instance 3. Execute the workflow: ```bash curl --location 'http://localhost:4111/api/workflows/user-registration/start-async' \ --header 'Content-Type: application/json' \ --data '{ "email": "user@example.com", "name": "John Doe", "age": 25 }' ``` ## Key Takeaways ✅ This example demonstrates several important concepts about workflow variables: 1. **Data Mapping**: Variables map data from one step to another, creating a clear data flow. 2. **Path Access**: The `path` property specifies which part of a step's output to use. 3. **Conditional Execution**: The `when` property allows steps to execute conditionally based on previous step outputs. 4. **Type Safety**: Each step defines input and output schemas for type safety, ensuring that the data passed between steps is properly typed. 5. **Explicit Data Dependencies**: By defining input schemas and using variable mappings, the data dependencies between steps are made explicit and clear. For more information on workflow variables, see the [Workflow Variables documentation](../../docs/workflows/variables.mdx). --- title: "Example: Using a Tool/Agent as a Step | Workflows | Kastrax Docs" description: Example of using Kastrax to integrate a tool or an agent as a step in a workflow. --- import { GithubLink } from '@/components/github-link'; # Tool/Agent as a Workflow step ✅ [EN] Source: https://kastrax.ai/en/examples/workflows_vNext/agent-and-tool-interop This example demonstrates how to create and integrate a tool or an agent as a workflow step. Kastrax provides a `createStep` helper function which accepts either a step or agent and returns an object which satisfies the Step interface. ## Define Interop Workflow ✅ Defines a workflow which takes an agent and tool as a step. ```ts showLineNumbers copy filename="workflows/interop-workflow.ts" import { createWorkflow, createStep } from '@kastrax/core/workflows/vNext' import { weatherTool } from '../tools' import { weatherReporterAgent } from '../agents' import { z } from 'zod' const fetchWeather = createStep(weatherTool) const reportWeather = createStep(weatherReporterAgent) const weatherWorkflow = createWorkflow({ steps: [fetchWeather, reportWeather], id: 'weather-workflow', inputSchema: z.object({ location: z.string().describe('The city to get the weather for'), }), outputSchema: z.object({ text: z.string(), }), }) .then(fetchWeather) .then( createStep({ id: 'report-weather', inputSchema: fetchWeather.outputSchema, outputSchema: z.object({ text: z.string(), }), execute: async ({ inputData, kastrax }) => { const prompt = 'Forecast data: ' + JSON.stringify(inputData) const agent = kastrax.getAgent('weatherReporterAgent') const result = await agent.generate([ { role: 'user', content: prompt, }, ]) return { text: result.text } }, }) ) weatherWorkflow.commit() export { weatherWorkflow } ``` ## Register Workflow instance with Kastrax class ✅ Register the workflow with the kastrax instance. ```ts showLineNumbers copy filename="index.ts" import { Kastrax } from '@kastrax/core/kastrax' import { createLogger } from '@kastrax/core/logger' import { weatherWorkflow } from './workflows' const kastrax = new Kastrax({ vnext_workflows: { weatherWorkflow, }, logger: createLogger({ name: 'Kastrax', level: 'info', }), }) export { kastrax } ``` ## Execute the workflow ✅ Here, we'll get the weather workflow from the kastrax instance, then create a run and execute the created run with the required inputData. ```ts showLineNumbers copy filename="exec.ts" import { kastrax } from "./" const workflow = kastrax.vnext_getWorkflow('weatherWorkflow') const run = workflow.createRun() const result = await run.start({ inputData: { location: "Lagos" } }) console.dir(result, { depth: null }) ``` --- title: "Example: Calling an Agent from a Workflow | Kastrax Docs" description: Example of using Kastrax to call an AI agent from within a workflow step. --- import { GithubLink } from "@/components/github-link"; # Calling an Agent From a Workflow ✅ [EN] Source: https://kastrax.ai/en/examples/workflows_vNext/calling-agent This example demonstrates how to create a workflow that calls an AI agent to sugest activities for the provided weather conditions, and execute it within a workflow step. ## Define Planning Agent ✅ Define a planning agent which leverages an LLM call to plan activities given a location and corresponding weather conditions. ```ts showLineNumbers copy filename="agents/planning-agent.ts" import { Agent } from '@kastrax/core/agent' import { openai } from '@ai-sdk/openai' const llm = openai('gpt-4o') const planningAgent = new Agent({ name: 'planningAgent', model: llm, instructions: ` You are a local activities and travel expert who excels at weather-based planning. Analyze the weather data and provide practical activity recommendations. 📅 [Day, Month Date, Year] ═══════════════════════════ 🌡️ WEATHER SUMMARY • Conditions: [brief description] • Temperature: [X°C/Y°F to A°C/B°F] • Precipitation: [X% chance] 🌅 MORNING ACTIVITIES Outdoor: • [Activity Name] - [Brief description including specific location/route] Best timing: [specific time range] Note: [relevant weather consideration] 🌞 AFTERNOON ACTIVITIES Outdoor: • [Activity Name] - [Brief description including specific location/route] Best timing: [specific time range] Note: [relevant weather consideration] 🏠 INDOOR ALTERNATIVES • [Activity Name] - [Brief description including specific venue] Ideal for: [weather condition that would trigger this alternative] ⚠️ SPECIAL CONSIDERATIONS • [Any relevant weather warnings, UV index, wind conditions, etc.] Guidelines: - Suggest 2-3 time-specific outdoor activities per day - Include 1-2 indoor backup options - For precipitation >50%, lead with indoor activities - All activities must be specific to the location - Include specific venues, trails, or locations - Consider activity intensity based on temperature - Keep descriptions concise but informative Maintain this exact formatting for consistency, using the emoji and section headers as shown. `, }) export { planningAgent } ``` ## Define Weather Workflow ✅ Define the weather workflow with 2 steps: one to fetch the weather via a network call, and another to plan activities using the planning agent. ```ts showLineNumbers copy filename="workflows/agent-workflow.ts" import { createWorkflow, createStep } from '@kastrax/core/workflows/vNext' import { z } from 'zod' function getWeatherCondition(code: number): string { const conditions: Record = { 0: 'Clear sky', 1: 'Mainly clear', 2: 'Partly cloudy', 3: 'Overcast', 45: 'Foggy', 48: 'Depositing rime fog', 51: 'Light drizzle', 53: 'Moderate drizzle', 55: 'Dense drizzle', 61: 'Slight rain', 63: 'Moderate rain', 65: 'Heavy rain', 71: 'Slight snow fall', 73: 'Moderate snow fall', 75: 'Heavy snow fall', 95: 'Thunderstorm', } return conditions[code] || 'Unknown' } const forecastSchema = z.object({ date: z.string(), maxTemp: z.number(), minTemp: z.number(), precipitationChance: z.number(), condition: z.string(), location: z.string(), }) const fetchWeather = createStep({ id: 'fetch-weather', description: 'Fetches weather forecast for a given city', inputSchema: z.object({ city: z.string(), }), outputSchema: forecastSchema, execute: async ({ inputData }) => { if (!inputData) { throw new Error('Trigger data not found') } const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(inputData.city)}&count=1` const geocodingResponse = await fetch(geocodingUrl) const geocodingData = (await geocodingResponse.json()) as { results: { latitude: number; longitude: number; name: string }[] } if (!geocodingData.results?.[0]) { throw new Error(`Location '${inputData.city}' not found`) } const { latitude, longitude, name } = geocodingData.results[0] const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=precipitation,weathercode&timezone=auto,&hourly=precipitation_probability,temperature_2m` const response = await fetch(weatherUrl) const data = (await response.json()) as { current: { time: string precipitation: number weathercode: number } hourly: { precipitation_probability: number[] temperature_2m: number[] } } const forecast = { date: new Date().toISOString(), maxTemp: Math.max(...data.hourly.temperature_2m), minTemp: Math.min(...data.hourly.temperature_2m), condition: getWeatherCondition(data.current.weathercode), location: name, precipitationChance: data.hourly.precipitation_probability.reduce( (acc, curr) => Math.max(acc, curr), 0 ), } return forecast }, }) const planActivities = createStep({ id: 'plan-activities', description: 'Suggests activities based on weather conditions', inputSchema: forecastSchema, outputSchema: z.object({ activities: z.string(), }), execute: async ({ inputData, kastrax }) => { const forecast = inputData if (!forecast) { throw new Error('Forecast data not found') } const prompt = `Based on the following weather forecast for ${forecast.location}, suggest appropriate activities: ${JSON.stringify(forecast, null, 2)} ` const agent = kastrax?.getAgent('planningAgent') if (!agent) { throw new Error('Planning agent not found') } const response = await agent.stream([ { role: 'user', content: prompt, }, ]) let activitiesText = '' for await (const chunk of response.textStream) { process.stdout.write(chunk) activitiesText += chunk } return { activities: activitiesText, } }, }) const weatherWorkflow = createWorkflow({ steps: [fetchWeather, planActivities], id: 'weather-workflow-step1-single-day', inputSchema: z.object({ city: z.string().describe('The city to get the weather for'), }), outputSchema: z.object({ activities: z.string(), }), }) .then(fetchWeather) .then(planActivities) weatherWorkflow.commit() export { weatherWorkflow } ``` ## Register Agent and Workflow instances with Kastrax class ✅ Register the planning agent and weather workflow with the kastrax instance. This is critical for enabling access to the planning agent within the weather workflow. ```ts showLineNumbers copy filename="index.ts" import { Kastrax } from '@kastrax/core/kastrax' import { createLogger } from '@kastrax/core/logger' import { weatherWorkflow } from './workflows' import { planningAgent } from './agents' const kastrax = new Kastrax({ vnext_workflows: { weatherWorkflow, }, agents: { planningAgent, }, logger: createLogger({ name: 'Kastrax', level: 'info', }), }) export { kastrax } ``` ## Execute the weather workflow ✅ Here, we'll get the weather workflow from the kastrax instance, then create a run and execute the created run with the required inputData. ## Execute the weather workflow ✅ Here, we'll get the weather workflow from the kastrax instance, then create a run and execute the created run with the required inputData. ```ts showLineNumbers copy filename="exec.ts" import { kastrax } from "./" const workflow = kastrax.vnext_getWorkflow('weatherWorkflow') const run = workflow.createRun() const result = await run.start({ inputData: { city: 'New York' } }) console.dir(result, { depth: null }) ```




--- title: "Example: Conditional Branching | Workflows | Kastrax Docs" description: Example of using Kastrax to create conditional branches in workflows using the `branch` statement . --- import { GithubLink } from "@/components/github-link"; # Workflow with Conditional Branching ✅ [EN] Source: https://kastrax.ai/en/examples/workflows_vNext/conditional-branching Workflows often need to follow different paths based on some condition. This example demonstrates how to use the `branch` construct to create conditional flows within your workflows. ## Define Planning Agent ✅ Define a planning agent which leverages an LLM call to plan activities given a location and corresponding weather conditions. ```ts showLineNumbers copy filename="agents/planning-agent.ts" import { Agent } from '@kastrax/core/agent' import { openai } from '@ai-sdk/openai' const llm = openai('gpt-4o') const planningAgent = new Agent({ name: 'planningAgent', model: llm, instructions: ` You are a local activities and travel expert who excels at weather-based planning. Analyze the weather data and provide practical activity recommendations. 📅 [Day, Month Date, Year] ═══════════════════════════ 🌡️ WEATHER SUMMARY • Conditions: [brief description] • Temperature: [X°C/Y°F to A°C/B°F] • Precipitation: [X% chance] 🌅 MORNING ACTIVITIES Outdoor: • [Activity Name] - [Brief description including specific location/route] Best timing: [specific time range] Note: [relevant weather consideration] 🌞 AFTERNOON ACTIVITIES Outdoor: • [Activity Name] - [Brief description including specific location/route] Best timing: [specific time range] Note: [relevant weather consideration] 🏠 INDOOR ALTERNATIVES • [Activity Name] - [Brief description including specific venue] Ideal for: [weather condition that would trigger this alternative] ⚠️ SPECIAL CONSIDERATIONS • [Any relevant weather warnings, UV index, wind conditions, etc.] Guidelines: - Suggest 2-3 time-specific outdoor activities per day - Include 1-2 indoor backup options - For precipitation >50%, lead with indoor activities - All activities must be specific to the location - Include specific venues, trails, or locations - Consider activity intensity based on temperature - Keep descriptions concise but informative Maintain this exact formatting for consistency, using the emoji and section headers as shown. `, }) export { planningAgent } ``` ## Define Weather Workflow ✅ Define the weather workflow with 3 steps: one to fetch the weather via a network call, one to plan activities, and another to plan only indoor activities. Both using the planning agent. ```ts showLineNumbers copy filename="workflows/conditional-workflow.ts" import { z } from 'zod' import { createStep, createWorkflow } from './vNext' function getWeatherCondition(code: number): string { const conditions: Record = { 0: 'Clear sky', 1: 'Mainly clear', 2: 'Partly cloudy', 3: 'Overcast', 45: 'Foggy', 48: 'Depositing rime fog', 51: 'Light drizzle', 53: 'Moderate drizzle', 55: 'Dense drizzle', 61: 'Slight rain', 63: 'Moderate rain', 65: 'Heavy rain', 71: 'Slight snow fall', 73: 'Moderate snow fall', 75: 'Heavy snow fall', 95: 'Thunderstorm', } return conditions[code] || 'Unknown' } const forecastSchema = z.object({ date: z.string(), maxTemp: z.number(), minTemp: z.number(), precipitationChance: z.number(), condition: z.string(), location: z.string(), }) // Fetch weather step const fetchWeather = createStep({ id: 'fetch-weather', description: 'Fetches weather forecast for a given city', inputSchema: z.object({ city: z.string(), }), outputSchema: forecastSchema, execute: async ({ inputData }) => { if (!inputData) { throw new Error('Trigger data not found') } const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(inputData.city)}&count=1` const geocodingResponse = await fetch(geocodingUrl) const geocodingData = (await geocodingResponse.json()) as { results: { latitude: number; longitude: number; name: string }[] } if (!geocodingData.results?.[0]) { throw new Error(`Location '${inputData.city}' not found`) } const { latitude, longitude, name } = geocodingData.results[0] const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=precipitation,weathercode&timezone=auto,&hourly=precipitation_probability,temperature_2m` const response = await fetch(weatherUrl) const data = (await response.json()) as { current: { time: string precipitation: number weathercode: number } hourly: { precipitation_probability: number[] temperature_2m: number[] } } const forecast = { date: new Date().toISOString(), maxTemp: Math.max(...data.hourly.temperature_2m), minTemp: Math.min(...data.hourly.temperature_2m), condition: getWeatherCondition(data.current.weathercode), location: name, precipitationChance: data.hourly.precipitation_probability.reduce( (acc, curr) => Math.max(acc, curr), 0 ), } return forecast }, }) // Plan activities indorrs or outdoors const planActivities = createStep({ id: 'plan-activities', description: 'Suggests activities based on weather conditions', inputSchema: forecastSchema, outputSchema: z.object({ activities: z.string(), }), execute: async ({ inputData, kastrax }) => { console.log('planActivities') const forecast = inputData if (!forecast) { throw new Error('Forecast data not found') } const prompt = `Based on the following weather forecast for ${forecast.location}, suggest appropriate activities: ${JSON.stringify(forecast, null, 2)} ` const agent = kastrax?.getAgent('planningAgent') if (!agent) { throw new Error('Planning agent not found') } const response = await agent.stream([ { role: 'user', content: prompt, }, ]) let activitiesText = '' for await (const chunk of response.textStream) { process.stdout.write(chunk) activitiesText += chunk } return { activities: activitiesText, } }, }) // Plan indoor activities only const planIndoorActivities = createStep({ id: 'plan-indoor-activities', description: 'Suggests indoor activities based on weather conditions', inputSchema: forecastSchema, outputSchema: z.object({ activities: z.string(), }), execute: async ({ inputData, kastrax }) => { console.log('planIndoorActivities') const forecast = inputData if (!forecast) { throw new Error('Forecast data not found') } const prompt = `In case it rains, plan indoor activities for ${forecast.location} on ${forecast.date}` const agent = kastrax?.getAgent('planningAgent') if (!agent) { throw new Error('Planning agent not found') } const response = await agent.stream([ { role: 'user', content: prompt, }, ]) let activitiesText = '' for await (const chunk of response.textStream) { process.stdout.write(chunk) activitiesText += chunk } return { activities: activitiesText, } }, }) const weatherWorkflow = createWorkflow({ id: 'weather-workflow-step2-if-else', inputSchema: z.object({ city: z.string().describe('The city to get the weather for'), }), outputSchema: z.object({ activities: z.string(), }), }) .then(fetchWeather) .branch([ [ async ({ inputData }) => { return inputData?.precipitationChance > 50 }, planIndoorActivities, ], [ async ({ inputData }) => { return inputData?.precipitationChance <= 50 }, planActivities, ], ]) weatherWorkflow.commit() export { weatherWorkflow } ``` ```ts showLineNumbers copy filename="index.ts" import { Kastrax } from '@kastrax/core/kastrax' import { createLogger } from '@kastrax/core/logger' import { weatherWorkflow } from './workflows' import { planningAgent } from './agents' const kastrax = new Kastrax({ vnext_workflows: { weatherWorkflow, }, agents: { planningAgent, }, logger: createLogger({ name: 'Kastrax', level: 'info', }), }) export { kastrax } ``` ## Register Agent and Workflow instances with Kastrax class ✅ Register the agents and workflow with the kastrax instance. This is critical for enabling access to the agents within the workflow. ```ts showLineNumbers copy filename="index.ts" import { Kastrax } from '@kastrax/core/kastrax' import { createLogger } from '@kastrax/core/logger' import { weatherWorkflow } from './workflows' import { planningAgent } from './agents' const kastrax = new Kastrax({ vnext_workflows: { weatherWorkflow, }, agents: { planningAgent, }, logger: createLogger({ name: 'Kastrax', level: 'info', }), }) export { kastrax } ``` ## Execute the weather workflow ✅ Here, we'll get the weather workflow from the kastrax instance, then create a run and execute the created run with the required inputData. ```ts showLineNumbers copy filename="exec.ts" import { kastrax } from "./" const workflow = kastrax.vnext_getWorkflow('weatherWorkflow') const run = workflow.createRun() const result = await run.start({ inputData: { city: 'New York' } }) console.dir(result, { depth: null }) ```




--- title: "Example: Control Flow | Workflows | Kastrax Docs" description: Example of using Kastrax to create workflows with loops based on provided conditions. --- import { GithubLink } from "@/components/github-link"; # Looping step execution ✅ [EN] Source: https://kastrax.ai/en/examples/workflows_vNext/control-flow This example demonstrates how to create a workflow that executes one or more of it's steps in a loop until a condition is met ## Define Looping workflow ✅ Defines a workflow which calls the executes a nested workflow until the provided condition is met. ```ts showLineNumbers copy filename="looping-workflow.ts" import { createWorkflow, createStep } from '@kastrax/core/workflows/vNext' import { z } from 'zod' const incrementStep = createStep({ id: 'increment', inputSchema: z.object({ value: z.number(), }), outputSchema: z.object({ value: z.number(), }), execute: async ({ inputData }) => { return { value: inputData.value + 1 } }, }) const sideEffectStep = createStep({ id: 'side-effect', inputSchema: z.object({ value: z.number(), }), outputSchema: z.object({ value: z.number(), }), execute: async ({ inputData }) => { console.log('log', inputData.value) return { value: inputData.value } }, }) const finalStep = createStep({ id: 'final', inputSchema: z.object({ value: z.number(), }), outputSchema: z.object({ value: z.number(), }), execute: async ({ inputData }) => { return { value: inputData.value } }, }) const workflow = createWorkflow({ id: 'increment-workflow', inputSchema: z.object({ value: z.number(), }), outputSchema: z.object({ value: z.number(), }), }) .dountil( createWorkflow({ id: 'increment-workflow', inputSchema: z.object({ value: z.number(), }), outputSchema: z.object({ value: z.number(), }), steps: [incrementStep, sideEffectStep], }) .then(incrementStep) .then(sideEffectStep) .commit(), async ({ inputData }) => inputData.value >= 10 ) .then(finalStep) workflow.commit() export { workflow as incrementWorkflow } ``` ## Register Workflow instance with Kastrax class ✅ Register the workflow with the kastrax instance. ```ts showLineNumbers copy filename="index.ts" import { Kastrax } from '@kastrax/core/kastrax' import { createLogger } from '@kastrax/core/logger' import { incrementWorkflow } from './workflows' const kastrax = new Kastrax({ vnext_workflows: { incrementWorkflow, }, logger: createLogger({ name: 'Kastrax', level: 'info', }), }) export { kastrax } ``` ## Execute the workflow ✅ Here, we'll get the increment workflow from the kastrax instance, then create a run and execute the created run with the required inputData. ```ts showLineNumbers copy filename="exec.ts" import { kastrax } from "./" const workflow = kastrax.vnext_getWorkflow('incrementWorkflow') const run = workflow.createRun() const result = await run.start({ inputData: { value: 0 } }) console.dir(result, { depth: null }) ```




--- title: "Example: Human in the Loop | Workflows | Kastrax Docs" description: Example of using Kastrax to create workflows with human intervention points. --- import { GithubLink } from '@/components/github-link'; # Human in the Loop Workflow ✅ [EN] Source: https://kastrax.ai/en/examples/workflows_vNext/human-in-the-loop Human-in-the-loop workflows allow you to pause execution at specific points to collect user input, make decisions, or perform actions that require human judgment. This example demonstrates how to create a workflow with human intervention points. ## Define Agents ✅ Define the travel agents. ```ts showLineNumbers copy filename="agents/travel-agents.ts" import { Agent } from '@kastrax/core/agent' import { openai } from '@ai-sdk/openai' const llm = openai('gpt-4o') export const summaryAgent = new Agent({ name: 'summaryTravelAgent', model: llm, instructions: ` You are a travel agent who is given a user prompt about what kind of holiday they want to go on. You then generate 3 different options for the holiday. Return the suggestions as a JSON array {"location": "string", "description": "string"}[]. Don't format as markdown. Make the options as different as possible from each other. Also make the plan very short and summarized. `, }) export const travelAgent = new Agent({ name: 'travelAgent', model: llm, instructions: ` You are a travel agent who is given a user prompt about what kind of holiday they want to go on. A summary of the plan is provided as well as the location. You then generate a detailed travel plan for the holiday. `, }) ``` ## Define Suspendable workflow ✅ Defines a workflow which includes a suspending step: `humanInputStep`. ```ts showLineNumbers copy filename="workflows/human-in-the-loop-workflow.ts" import { createWorkflow, createStep } from '@kastrax/core/workflows/vNext' import { z } from 'zod' const generateSuggestionsStep = createStep({ id: 'generate-suggestions', inputSchema: z.object({ vacationDescription: z.string().describe('The description of the vacation'), }), outputSchema: z.object({ suggestions: z.array(z.string()), vacationDescription: z.string(), }), execute: async ({ inputData, kastrax, container }) => { if (!kastrax) { throw new Error('Kastrax is not initialized') } const { vacationDescription } = inputData const result = await kastrax.getAgent('summaryTravelAgent').generate([ { role: 'user', content: vacationDescription, }, ]) console.log(result.text) return { suggestions: JSON.parse(result.text), vacationDescription } }, }) const humanInputStep = createStep({ id: 'human-input', inputSchema: z.object({ suggestions: z.array(z.string()), vacationDescription: z.string(), }), outputSchema: z.object({ selection: z.string().describe('The selection of the user'), vacationDescription: z.string(), }), resumeSchema: z.object({ selection: z.string().describe('The selection of the user'), }), suspendSchema: z.object({ suggestions: z.array(z.string()), }), execute: async ({ inputData, resumeData, suspend, getInitData }) => { if (!resumeData?.selection) { await suspend({ suggestions: inputData?.suggestions }) return { selection: '', vacationDescription: inputData?.vacationDescription, } } return { selection: resumeData?.selection, vacationDescription: inputData?.vacationDescription, } }, }) const travelPlannerStep = createStep({ id: 'travel-planner', inputSchema: z.object({ selection: z.string().describe('The selection of the user'), vacationDescription: z.string(), }), outputSchema: z.object({ travelPlan: z.string(), }), execute: async ({ inputData, kastrax }) => { const travelAgent = kastrax?.getAgent('travelAgent') if (!travelAgent) { throw new Error('Travel agent is not initialized') } const { selection, vacationDescription } = inputData const result = await travelAgent.generate([ { role: 'assistant', content: vacationDescription }, { role: 'user', content: selection || '' }, ]) console.log(result.text) return { travelPlan: result.text } }, }) const weatherWorkflow = createWorkflow({ id: 'weather-workflow', inputSchema: z.object({ vacationDescription: z.string().describe('The description of the vacation'), }), outputSchema: z.object({ travelPlan: z.string(), }), }) .then(generateSuggestionsStep) .then(humanInputStep) .then(travelPlannerStep) travelAgentWorkflow.commit() export { weatherWorkflow, humanInputStep } ``` ## Register Agent and Workflow instances with Kastrax class ✅ Register the agents and the weather workflow with the kastrax instance. This is critical for enabling access to the agents within the workflow. ```ts showLineNumbers copy filename="index.ts" import { Kastrax } from '@kastrax/core/kastrax' import { createLogger } from '@kastrax/core/logger' import { weatherWorkflow } from './workflows' import { travelAgent, summaryAgent } from './agents' const kastrax = new Kastrax({ vnext_workflows: { weatherWorkflow, }, agents: { travelAgent, summaryAgent }, logger: createLogger({ name: 'Kastrax', level: 'info', }), }) export { kastrax } ``` ## Execute the suspendable weather workflow ✅ Here, we'll get the weather workflow from the kastrax instance, then create a run and execute the created run with the required inputData. In addition to this, we'll resume the `humanInputStep` after collecting user input with the readline package. ```ts showLineNumbers copy filename="exec.ts" import { kastrax } from "./" import { createInterface } from 'readline' import { humanInputStep } from './workflows' const workflow = kastrax.vnext_getWorkflow('weather-workflow') const run = workflow.createRun({}) const result = await run.start({ inputData: { vacationDescription: 'I want to go to the beach' }, }) console.log('result', result) const suggStep = result?.steps?.['generate-suggestions'] if (suggStep.status === 'success') { const suggestions = suggStep.output?.suggestions console.log( suggestions .map(({ location, description }) => `- ${location}: ${description}`) .join('\n') ) const userInput = await readInput() console.log('Selected:', userInput) console.log('resuming from', result, 'with', { inputData: { selection: userInput, vacationDescription: 'I want to go to the beach', suggestions: suggStep?.output?.suggestions, }, step: humanInputStep, }) const result2 = await run.resume({ resumeData: { selection: userInput, vacationDescription: 'I want to go to the beach', suggestions: suggStep?.output?.suggestions, }, step: humanInputStep, }) console.dir(result2, { depth: null }) } ``` Human-in-the-loop workflows are powerful for building systems that blend automation with human judgment, such as: - Content moderation systems - Approval workflows - Supervised AI systems - Customer service automation with escalation




--- title: "Example: Parallel Execution | Workflows | Kastrax Docs" description: Example of using Kastrax to execute multiple independent tasks in parallel within a workflow. --- import { GithubLink } from "@/components/github-link"; # Parallel Execution with Steps ✅ [EN] Source: https://kastrax.ai/en/examples/workflows_vNext/parallel-steps When building AI applications, you often need to process multiple independent tasks simultaneously to improve efficiency. We make this functionality a core part of workflows through the `.parallel` method. ## Define Planning Agent ✅ Define a planning agent which leverages an LLM call to plan activities given a location and corresponding weather conditions. ```ts showLineNumbers copy filename="agents/planning-agent.ts" import { Agent } from '@kastrax/core/agent' import { openai } from '@ai-sdk/openai' const llm = openai('gpt-4o') const planningAgent = new Agent({ name: 'planningAgent', model: llm, instructions: ` You are a local activities and travel expert who excels at weather-based planning. Analyze the weather data and provide practical activity recommendations. 📅 [Day, Month Date, Year] ═══════════════════════════ 🌡️ WEATHER SUMMARY • Conditions: [brief description] • Temperature: [X°C/Y°F to A°C/B°F] • Precipitation: [X% chance] 🌅 MORNING ACTIVITIES Outdoor: • [Activity Name] - [Brief description including specific location/route] Best timing: [specific time range] Note: [relevant weather consideration] 🌞 AFTERNOON ACTIVITIES Outdoor: • [Activity Name] - [Brief description including specific location/route] Best timing: [specific time range] Note: [relevant weather consideration] 🏠 INDOOR ALTERNATIVES • [Activity Name] - [Brief description including specific venue] Ideal for: [weather condition that would trigger this alternative] ⚠️ SPECIAL CONSIDERATIONS • [Any relevant weather warnings, UV index, wind conditions, etc.] Guidelines: - Suggest 2-3 time-specific outdoor activities per day - Include 1-2 indoor backup options - For precipitation >50%, lead with indoor activities - All activities must be specific to the location - Include specific venues, trails, or locations - Consider activity intensity based on temperature - Keep descriptions concise but informative Maintain this exact formatting for consistency, using the emoji and section headers as shown. `, }) export { planningAgent } ``` ## Define Synthesize Agent ✅ Define a synthesize agent which takes planned indoor and outdoor activities and provides a full report on the day. ```ts showLineNumbers copy filename="agents/synthesize-agent.ts" import { Agent } from '@kastrax/core/agent' import { openai } from '@ai-sdk/openai' const llm = openai('gpt-4o') const synthesizeAgent = new Agent({ name: 'synthesizeAgent', model: llm, instructions: ` You are given two different blocks of text, one about indoor activities and one about outdoor activities. Make this into a full report about the day and the possibilities depending on whether it rains or not. `, }) export { synthesizeAgent } ``` ## Define Parallel Workflow ✅ Here, we'll define a workflow which orchestrates a parallel -> sequential flow between the planning steps and the synthesize step. ```ts showLineNumbers copy filename="workflows/parallel-workflow.ts" import { Step, Workflow } from '@kastrax/core/workflows' import { z } from 'zod' import { activityPlannerAgent } from '../agents' import { createStep, createWorkflow } from '@kastrax/core/workflows/vNext' const forecastSchema = z.object({ date: z.string(), maxTemp: z.number(), minTemp: z.number(), precipitationChance: z.number(), condition: z.string(), location: z.string(), }) const fetchWeather = createStep({ id: 'fetch-weather', description: 'Fetches weather forecast for a given city', inputSchema: z.object({ city: z.string(), }), outputSchema: forecastSchema, execute: async ({ inputData }) => { if (!inputData) { throw new Error('Trigger data not found') } const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(inputData.city)}&count=1` const geocodingResponse = await fetch(geocodingUrl) const geocodingData = (await geocodingResponse.json()) as { results: { latitude: number; longitude: number; name: string }[] } if (!geocodingData.results?.[0]) { throw new Error(`Location '${inputData.city}' not found`) } const { latitude, longitude, name } = geocodingData.results[0] const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=precipitation,weathercode&timezone=auto,&hourly=precipitation_probability,temperature_2m` const response = await fetch(weatherUrl) const data = (await response.json()) as { current: { time: string precipitation: number weathercode: number } hourly: { precipitation_probability: number[] temperature_2m: number[] } } const forecast = { date: new Date().toISOString(), maxTemp: Math.max(...data.hourly.temperature_2m), minTemp: Math.min(...data.hourly.temperature_2m), condition: getWeatherCondition(data.current.weathercode), location: name, precipitationChance: data.hourly.precipitation_probability.reduce( (acc, curr) => Math.max(acc, curr), 0 ), } return forecast }, }) const planActivities = createStep({ id: 'plan-activities', description: 'Suggests activities based on weather conditions', inputSchema: forecastSchema, outputSchema: z.object({ activities: z.string(), }), execute: async ({ inputData, kastrax }) => { console.log('kastrax', kastrax) console.log('planActivities', inputData) const forecast = inputData if (!forecast) { throw new Error('Forecast data not found') } const prompt = `Based on the following weather forecast for ${forecast.location}, suggest appropriate activities: ${JSON.stringify(forecast, null, 2)} ` const agent = kastrax?.getAgent('planningAgent') if (!agent) { throw new Error('Planning agent not found') } const response = await agent.stream([ { role: 'user', content: prompt, }, ]) let activitiesText = '' for await (const chunk of response.textStream) { process.stdout.write(chunk) activitiesText += chunk } console.log('planActivities', activitiesText) return { activities: activitiesText, } }, }) function getWeatherCondition(code: number): string { const conditions: Record = { 0: 'Clear sky', 1: 'Mainly clear', 2: 'Partly cloudy', 3: 'Overcast', 45: 'Foggy', 48: 'Depositing rime fog', 51: 'Light drizzle', 53: 'Moderate drizzle', 55: 'Dense drizzle', 61: 'Slight rain', 63: 'Moderate rain', 65: 'Heavy rain', 71: 'Slight snow fall', 73: 'Moderate snow fall', 75: 'Heavy snow fall', 95: 'Thunderstorm', } return conditions[code] || 'Unknown' } const planIndoorActivities = createStep({ id: 'plan-indoor-activities', description: 'Suggests indoor activities based on weather conditions', inputSchema: forecastSchema, outputSchema: z.object({ activities: z.string(), }), execute: async ({ inputData, kastrax }) => { console.log('planIndoorActivities', inputData) const forecast = inputData if (!forecast) { throw new Error('Forecast data not found') } const prompt = `In case it rains, plan indoor activities for ${forecast.location} on ${forecast.date}` const agent = kastrax?.getAgent('planningAgent') if (!agent) { throw new Error('Planning agent not found') } const response = await agent.stream([ { role: 'user', content: prompt, }, ]) let activitiesText = '' for await (const chunk of response.textStream) { activitiesText += chunk } console.log('planIndoorActivities', activitiesText) return { activities: activitiesText, } }, }) const sythesizeStep = createStep({ id: 'sythesize-step', description: 'Synthesizes the results of the indoor and outdoor activities', inputSchema: z.object({ 'plan-activities': z.object({ activities: z.string(), }), 'plan-indoor-activities': z.object({ activities: z.string(), }), }), outputSchema: z.object({ activities: z.string(), }), execute: async ({ inputData, kastrax }) => { console.log('sythesizeStep', inputData) const indoorActivities = inputData?.['plan-indoor-activities'] const outdoorActivities = inputData?.['plan-activities'] const prompt = `Indoor activtities: ${indoorActivities?.activities} Outdoor activities: ${outdoorActivities?.activities} There is a chance of rain so be prepared to do indoor activities if needed.` const agent = kastrax?.getAgent('synthesizeAgent') if (!agent) { throw new Error('Planning agent not found') } const response = await agent.stream([ { role: 'user', content: prompt, }, ]) let activitiesText = '' for await (const chunk of response.textStream) { process.stdout.write(chunk) activitiesText += chunk } return { activities: activitiesText, } }, }) const weatherWorkflow = createWorkflow({ id: 'plan-both-workflow', inputSchema: forecastSchema, outputSchema: z.object({ activities: z.string(), }), steps: [planActivities, planIndoorActivities, sythesizeStep], }) // run `planActivities` and `planIndoorActivities` in parallel // `synthesizeStep` waits for both steps to be completed before executing. .parallel([planActivities, planIndoorActivities]) .then(sythesizeStep) .commit() export { weatherWorkflow } ``` ## Register Agent and Workflow instances with Kastrax class ✅ Register the agents and workflow with the kastrax instance. This is critical for enabling access to the agents within the workflow. ```ts showLineNumbers copy filename="index.ts" import { Kastrax } from '@kastrax/core/kastrax' import { createLogger } from '@kastrax/core/logger' import { weatherWorkflow } from './workflows' import { planningAgent, synthesizeAgent } from './agents' const kastrax = new Kastrax({ vnext_workflows: { weatherWorkflow, }, agents: { planningAgent, synthesizeAgent }, logger: createLogger({ name: 'Kastrax', level: 'info', }), }) export { kastrax } ``` ## Execute the weather workflow ✅ Here, we'll get the weather workflow from the kastrax instance, then create a run and execute the created run with the required inputData. ```ts showLineNumbers copy filename="exec.ts" import { kastrax } from "./" const workflow = kastrax.vnext_getWorkflow('weatherWorkflow') const run = workflow.createRun() const result = await run.start({ inputData: { city: 'Ibiza' } }) console.dir(result, { depth: null }) ```




--- title: "Building an AI Recruiter | Kastrax Workflows | Guides" description: Guide on building a recruiter workflow in Kastrax to gather and process candidate information using LLMs. --- # Introduction ✅ [EN] Source: https://kastrax.ai/en/guides/guide/ai-recruiter In this guide, you'll learn how Kastrax helps you build workflows with LLMs. We'll walk through creating a workflow that gathers information from a candidate's resume, then branches to either a technical or behavioral question based on the candidate's profile. Along the way, you'll see how to structure workflow steps, handle branching, and integrate LLM calls. Below is a concise version of the workflow. It starts by importing the necessary modules, sets up Kastrax, defines steps to extract and classify candidate data, and then asks suitable follow-up questions. Each code block is followed by a short explanation of what it does and why it's useful. ## 1. Imports and Setup ✅ You need to import Kastrax tools and Zod to handle workflow definitions and data validation. ```ts filename="src/kastrax/index.ts" copy import { Kastrax } from "@kastrax/core"; import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; ``` Add your `OPENAI_API_KEY` to the `.env` file. ```bash filename=".env" copy OPENAI_API_KEY= ``` ## 2. Step One: Gather Candidate Info ✅ You want to extract candidate details from the resume text and classify them as technical or non-technical. This step calls an LLM to parse the resume and return structured JSON, including the name, technical status, specialty, and the original resume text. The code reads resumeText from trigger data, prompts the LLM, and returns organized fields for use in subsequent steps. ```ts filename="src/kastrax/index.ts" copy import { Agent } from '@kastrax/core/agent'; import { openai } from "@ai-sdk/openai"; const recruiter = new Agent({ name: "Recruiter Agent", instructions: `You are a recruiter.`, model: openai("gpt-4o-mini"), }) const gatherCandidateInfo = new Step({ id: "gatherCandidateInfo", inputSchema: z.object({ resumeText: z.string(), }), outputSchema: z.object({ candidateName: z.string(), isTechnical: z.boolean(), specialty: z.string(), resumeText: z.string(), }), execute: async ({ context }) => { const resumeText = context?.getStepResult<{ resumeText: string; }>("trigger")?.resumeText; const prompt = ` Extract details from the resume text: "${resumeText}" `; const res = await recruiter.generate(prompt, { output: z.object({ candidateName: z.string(), isTechnical: z.boolean(), specialty: z.string(), resumeText: z.string(), }), }); return res.object; }, }); ``` ## 3. Technical Question Step ✅ This step prompts a candidate who is identified as technical for more information about how they got into their specialty. It uses the entire resume text so the LLM can craft a relevant follow-up question. The code generates a question about the candidate's specialty. ```ts filename="src/kastrax/index.ts" copy interface CandidateInfo { candidateName: string; isTechnical: boolean; specialty: string; resumeText: string; } const askAboutSpecialty = new Step({ id: "askAboutSpecialty", outputSchema: z.object({ question: z.string(), }), execute: async ({ context }) => { const candidateInfo = context?.getStepResult( "gatherCandidateInfo", ); const prompt = ` You are a recruiter. Given the resume below, craft a short question for ${candidateInfo?.candidateName} about how they got into "${candidateInfo?.specialty}". Resume: ${candidateInfo?.resumeText} `; const res = await recruiter.generate(prompt); return { question: res?.text?.trim() || "" }; }, }); ``` ## 4. Behavioral Question Step ✅ If the candidate is non-technical, you want a different follow-up question. This step asks what interests them most about the role, again referencing their complete resume text. The code solicits a role-focused query from the LLM. ```ts filename="src/kastrax/index.ts" copy const askAboutRole = new Step({ id: "askAboutRole", outputSchema: z.object({ question: z.string(), }), execute: async ({ context }) => { const candidateInfo = context?.getStepResult( "gatherCandidateInfo", ); const prompt = ` You are a recruiter. Given the resume below, craft a short question for ${candidateInfo?.candidateName} asking what interests them most about this role. Resume: ${candidateInfo?.resumeText} `; const res = await recruiter.generate(prompt); return { question: res?.text?.trim() || "" }; }, }); ``` ## 5. Define the Workflow ✅ You now combine the steps to implement branching logic based on the candidate's technical status. The workflow first gathers candidate data, then either asks about their specialty or about their role, depending on isTechnical. The code chains gatherCandidateInfo with askAboutSpecialty and askAboutRole, and commits the workflow. ```ts filename="src/kastrax/index.ts" copy const candidateWorkflow = new Workflow({ name: "candidate-workflow", triggerSchema: z.object({ resumeText: z.string(), }), }); candidateWorkflow .step(gatherCandidateInfo) .then(askAboutSpecialty, { when: { "gatherCandidateInfo.isTechnical": true }, }) .after(gatherCandidateInfo) .step(askAboutRole, { when: { "gatherCandidateInfo.isTechnical": false }, }); candidateWorkflow.commit(); ``` ## 6. Execute the Workflow ✅ ```ts filename="src/kastrax/index.ts" copy const kastrax = new Kastrax({ workflows: { candidateWorkflow, }, }); (async () => { const { runId, start } = kastrax.getWorkflow("candidateWorkflow").createRun(); console.log("Run", runId); const runResult = await start({ triggerData: { resumeText: "Simulated resume content..." }, }); console.log("Final output:", runResult.results); })(); ``` You've just built a workflow to parse a resume and decide which question to ask based on the candidate's technical abilities. Congrats and happy hacking! --- title: "Building an AI Chef Assistant | Kastrax Agent Guides" description: Guide on creating a Chef Assistant agent in Kastrax to help users cook meals with available ingredients. --- import { Steps } from "nextra/components"; import YouTube from "@/components/youtube"; # Agents Guide: Building a Chef Assistant ✅ [EN] Source: https://kastrax.ai/en/guides/guide/chef-michel In this guide, we'll walk through creating a "Chef Assistant" agent that helps users cook meals with available ingredients. ## Prerequisites ✅ - Node.js installed - Kastrax installed: `npm install @kastrax/core@latest` --- ## Create the Agent ✅ ### Define the Agent Create a new file `src/kastrax/agents/chefAgent.ts` and define your agent: ```ts copy filename="src/kastrax/agents/chefAgent.ts" import { openai } from "@ai-sdk/openai"; import { Agent } from "@kastrax/core/agent"; export const chefAgent = new Agent({ name: "chef-agent", instructions: "You are Michel, a practical and experienced home chef" + "You help people cook with whatever ingredients they have available.", model: openai("gpt-4o-mini"), }); ``` --- ## Set Up Environment Variables ✅ Create a `.env` file in your project root and add your OpenAI API key: ```bash filename=".env" copy OPENAI_API_KEY=your_openai_api_key ``` --- ## Register the Agent with Kastrax ✅ In your main file, register the agent: ```ts copy filename="src/kastrax/index.ts" import { Kastrax } from "@kastrax/core"; import { chefAgent } from "./agents/chefAgent"; export const kastrax = new Kastrax({ agents: { chefAgent }, }); ``` --- ## Interacting with the Agent ✅ ### Generating Text Responses ```ts copy filename="src/index.ts" async function main() { const query = "In my kitchen I have: pasta, canned tomatoes, garlic, olive oil, and some dried herbs (basil and oregano). What can I make?"; console.log(`Query: ${query}`); const response = await chefAgent.generate([{ role: "user", content: query }]); console.log("\n👨‍🍳 Chef Michel:", response.text); } main(); ``` Run the script: ```bash copy npx bun src/index.ts ``` Output: ``` Query: In my kitchen I have: pasta, canned tomatoes, garlic, olive oil, and some dried herbs (basil and oregano). What can I make? 👨‍🍳 Chef Michel: You can make a delicious pasta al pomodoro! Here's how... ``` --- ### Streaming Responses ```ts copy filename="src/index.ts" async function main() { const query = "Now I'm over at my friend's house, and they have: chicken thighs, coconut milk, sweet potatoes, and some curry powder."; console.log(`Query: ${query}`); const stream = await chefAgent.stream([{ role: "user", content: query }]); console.log("\n Chef Michel: "); for await (const chunk of stream.textStream) { process.stdout.write(chunk); } console.log("\n\n✅ Recipe complete!"); } main(); ``` Output: ``` Query: Now I'm over at my friend's house, and they have: chicken thighs, coconut milk, sweet potatoes, and some curry powder. 👨‍🍳 Chef Michel: Great! You can make a comforting chicken curry... ✅ Recipe complete! ``` --- ### Generating a Recipe with Structured Data ```ts copy filename="src/index.ts" import { z } from "zod"; async function main() { const query = "I want to make lasagna, can you generate a lasagna recipe for me?"; console.log(`Query: ${query}`); // Define the Zod schema const schema = z.object({ ingredients: z.array( z.object({ name: z.string(), amount: z.string(), }), ), steps: z.array(z.string()), }); const response = await chefAgent.generate( [{ role: "user", content: query }], { output: schema }, ); console.log("\n👨‍🍳 Chef Michel:", response.object); } main(); ``` Output: ``` Query: I want to make lasagna, can you generate a lasagna recipe for me? 👨‍🍳 Chef Michel: { ingredients: [ { name: "Lasagna noodles", amount: "12 sheets" }, { name: "Ground beef", amount: "1 pound" }, // ... ], steps: [ "Preheat oven to 375°F (190°C).", "Cook the lasagna noodles according to package instructions.", // ... ] } ``` --- ## Running the Agent Server ✅ ### Using `kastrax dev` You can run your agent as a service using the `kastrax dev` command: ```bash copy kastrax dev ``` This will start a server exposing endpoints to interact with your registered agents. ### Accessing the Chef Assistant API By default, `kastrax dev` runs on `http://localhost:4111`. Your Chef Assistant agent will be available at: ``` POST http://localhost:4111/api/agents/chefAgent/generate ``` ### Interacting with the Agent via `curl` You can interact with the agent using `curl` from the command line: ```bash copy curl -X POST http://localhost:4111/api/agents/chefAgent/generate \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "I have eggs, flour, and milk. What can I make?" } ] }' ``` **Sample Response:** ```json { "text": "You can make delicious pancakes! Here's a simple recipe..." } ``` --- title: "Building a Research Paper Assistant | Kastrax RAG Guides" description: Guide on creating an AI research assistant that can analyze and answer questions about academic papers using RAG. --- import { Steps } from "nextra/components"; # Building a Research Paper Assistant with RAG ✅ [EN] Source: https://kastrax.ai/en/guides/guide/research-assistant In this guide, we'll create an AI research assistant that can analyze academic papers and answer specific questions about their content using Retrieval Augmented Generation (RAG). We'll use the foundational Transformer paper [Attention Is All You Need](https://arxiv.org/html/1706.03762) as our example. ## Understanding RAG Components ✅ Let's understand how RAG works and how we'll implement each component: 1. Knowledge Store/Index - Converting text into vector representations - Creating numerical representations of content - Implementation: We'll use OpenAI's text-embedding-3-small to create embeddings and store them in PgVector 2. Retriever - Finding relevant content via similarity search - Matching query embeddings with stored vectors - Implementation: We'll use PgVector to perform similarity searches on our stored embeddings 3. Generator - Processing retrieved content with an LLM - Creating contextually informed responses - Implementation: We'll use GPT-4o-mini to generate answers based on retrieved content Our implementation will: 1. Process the Transformer paper into embeddings 2. Store them in PgVector for quick retrieval 3. Use similarity search to find relevant sections 4. Generate accurate responses using retrieved context ## Project Structure ✅ ``` research-assistant/ ├── src/ │ ├── kastrax/ │ │ ├── agents/ │ │ │ └── researchAgent.ts │ │ └── index.ts │ ├── index.ts │ └── store.ts ├── package.json └── .env ``` ### Initialize Project and Install Dependencies First, create a new directory for your project and navigate into it: ```bash mkdir research-assistant cd research-assistant ``` Initialize a new Node.js project and install the required dependencies: ```bash copy npm init -y npm install @kastrax/core@latest @kastrax/rag@latest @kastrax/pg@latest @ai-sdk/openai@latest ai@latest zod@latest ``` Set up environment variables for API access and database connection: ```bash filename=".env" copy OPENAI_API_KEY=your_openai_api_key POSTGRES_CONNECTION_STRING=your_connection_string ``` Create the necessary files for our project: ```bash copy mkdir -p src/kastrax/agents touch src/kastrax/agents/researchAgent.ts touch src/kastrax/index.ts src/store.ts src/index.ts ``` ### Create the Research Assistant Agent Now we'll create our RAG-enabled research assistant. The agent uses: - A [Vector Query Tool](/reference/tools/vector-query-tool) for performing semantic search over our vector store to find relevant content in our papers. - GPT-4o-mini for understanding queries and generating responses - Custom instructions that guide the agent on how to analyze papers, use retrieved content effectively, and acknowledge limitations ```ts copy showLineNumbers filename="src/kastrax/agents/researchAgent.ts" import { Agent } from '@kastrax/core/agent'; import { openai } from '@ai-sdk/openai'; import { createVectorQueryTool } from '@kastrax/rag'; // Create a tool for semantic search over our paper embeddings const vectorQueryTool = createVectorQueryTool({ vectorStoreName: 'pgVector', indexName: 'papers', model: openai.embedding('text-embedding-3-small'), }); export const researchAgent = new Agent({ name: 'Research Assistant', instructions: `You are a helpful research assistant that analyzes academic papers and technical documents. Use the provided vector query tool to find relevant information from your knowledge base, and provide accurate, well-supported answers based on the retrieved content. Focus on the specific content available in the tool and acknowledge if you cannot find sufficient information to answer a question. Base your responses only on the content provided, not on general knowledge.`, model: openai('gpt-4o-mini'), tools: { vectorQueryTool, }, }); ``` ### Set Up the Kastrax Instance and Vector Store ```ts copy showLineNumbers filename="src/kastrax/index.ts" import { Kastrax } from '@kastrax/core'; import { PgVector } from '@kastrax/pg'; import { researchAgent } from './agents/researchAgent'; // Initialize Kastrax instance const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING!); export const kastrax = new Kastrax({ agents: { researchAgent }, vectors: { pgVector }, }); ``` ### Load and Process the Paper This step handles the initial document processing. We: 1. Fetch the research paper from its URL 2. Convert it into a document object 3. Split it into smaller, manageable chunks for better processing ```ts copy showLineNumbers filename="src/store.ts" import { openai } from "@ai-sdk/openai"; import { MDocument } from '@kastrax/rag'; import { embedMany } from 'ai'; import { kastrax } from "./kastrax"; // Load the paper const paperUrl = "https://arxiv.org/html/1706.03762"; const response = await fetch(paperUrl); const paperText = await response.text(); // Create document and chunk it const doc = MDocument.fromText(paperText); const chunks = await doc.chunk({ strategy: 'recursive', size: 512, overlap: 50, separator: '\n', }); console.log("Number of chunks:", chunks.length); // Number of chunks: 893 ``` ### Create and Store Embeddings Finally, we'll prepare our content for RAG by: 1. Generating embeddings for each chunk of text 2. Creating a vector store index to hold our embeddings 3. Storing both the embeddings and metadata (original text and source information) in our vector database > **Note**: This metadata is crucial as it allows us to return the actual content when the vector store finds relevant matches. This allows our agent to efficiently search and retrieve relevant information. ```ts copy showLineNumbers{23} filename="src/store.ts" // Generate embeddings const { embeddings } = await embedMany({ model: openai.embedding('text-embedding-3-small'), values: chunks.map(chunk => chunk.text), }); // Get the vector store instance from Kastrax const vectorStore = kastrax.getVector('pgVector'); // Create an index for our paper chunks await vectorStore.createIndex({ indexName: 'papers', dimension: 1536, }); // Store embeddings await vectorStore.upsert({ indexName: 'papers', vectors: embeddings, metadata: chunks.map(chunk => ({ text: chunk.text, source: 'transformer-paper' })), }); ``` This will: 1. Load the paper from the URL 2. Split it into manageable chunks 3. Generate embeddings for each chunk 4. Store both the embeddings and text in our vector database To run the script and store the embeddings: ```bash npx bun src/store.ts ``` ### Test the Assistant Let's test our research assistant with different types of queries: ```ts filename="src/index.ts" showLineNumbers copy import { kastrax } from "./kastrax"; const agent = kastrax.getAgent('researchAgent'); // Basic query about concepts const query1 = "What problems does sequence modeling face with neural networks?"; const response1 = await agent.generate(query1); console.log("\nQuery:", query1); console.log("Response:", response1.text); ``` Run the script: ```bash copy npx bun src/index.ts ``` You should see output like: ``` Query: What problems does sequence modeling face with neural networks? Response: Sequence modeling with neural networks faces several key challenges: 1. Vanishing and exploding gradients during training, especially with long sequences 2. Difficulty handling long-term dependencies in the input 3. Limited computational efficiency due to sequential processing 4. Challenges in parallelizing computations, resulting in longer training times ``` Let's try another question: ```ts filename="src/index.ts" showLineNumbers{10} copy // Query about specific findings const query2 = "What improvements were achieved in translation quality?"; const response2 = await agent.generate(query2); console.log("\nQuery:", query2); console.log("Response:", response2.text); ``` Output: ``` Query: What improvements were achieved in translation quality? Response: The model showed significant improvements in translation quality, achieving more than 2.0 BLEU points improvement over previously reported models on the WMT 2014 English-to-German translation task, while also reducing training costs. ``` ### Serve the Application Start the Kastrax server to expose your research assistant via API: ```bash kastrax dev ``` Your research assistant will be available at: ``` http://localhost:4111/api/agents/researchAgent/generate ``` Test with curl: ```bash curl -X POST http://localhost:4111/api/agents/researchAgent/generate \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "What were the main findings about model parallelization?" } ] }' ``` ## Advanced RAG Examples ✅ Explore these examples for more advanced RAG techniques: - [Filter RAG](/examples/rag/usage/filter-rag) for filtering results using metadata - [Cleanup RAG](/examples/rag/usage/cleanup-rag) for optimizing information density - [Chain of Thought RAG](/examples/rag/usage/cot-rag) for complex reasoning queries using workflows - [Rerank RAG](/examples/rag/usage/rerank-rag) for improved result relevance --- title: "Building an AI Stock Agent | Kastrax Agents | Guides" description: Guide on creating a simple stock agent in Kastrax to fetch the last day's closing stock price for a given symbol. --- import { Steps } from "nextra/components"; import YouTube from "@/components/youtube"; # Stock Agent ✅ [EN] Source: https://kastrax.ai/en/guides/guide/stock-agent We're going to create a simple agent that fetches the last day's closing stock price for a given symbol. This example will show you how to create a tool, add it to an agent, and use the agent to fetch stock prices. ## Project Structure ✅ ``` stock-price-agent/ ├── src/ │ ├── agents/ │ │ └── stockAgent.ts │ ├── tools/ │ │ └── stockPrices.ts │ └── index.ts ├── package.json └── .env ``` --- ## Initialize the Project and Install Dependencies ✅ First, create a new directory for your project and navigate into it: ```bash mkdir stock-price-agent cd stock-price-agent ``` Initialize a new Node.js project and install the required dependencies: ```bash copy npm init -y npm install @kastrax/core@latest zod @ai-sdk/openai ``` Set Up Environment Variables Create a `.env` file at the root of your project to store your OpenAI API key. ```bash filename=".env" copy OPENAI_API_KEY=your_openai_api_key ``` Create the necessary directories and files: ```bash mkdir -p src/agents src/tools touch src/agents/stockAgent.ts src/tools/stockPrices.ts src/index.ts ``` --- ## Create the Stock Price Tool ✅ Next, we'll create a tool that fetches the last day's closing stock price for a given symbol. ```ts filename="src/tools/stockPrices.ts" import { createTool } from "@kastrax/core/tools"; import { z } from "zod"; const getStockPrice = async (symbol: string) => { const data = await fetch( `https://kastrax-stock-data.vercel.app/api/stock-data?symbol=${symbol}`, ).then((r) => r.json()); return data.prices["4. close"]; }; export const stockPrices = createTool({ id: "Get Stock Price", inputSchema: z.object({ symbol: z.string(), }), description: `Fetches the last day's closing stock price for a given symbol`, execute: async ({ context: { symbol } }) => { console.log("Using tool to fetch stock price for", symbol); return { symbol, currentPrice: await getStockPrice(symbol), }; }, }); ``` --- ## Add the Tool to an Agent ✅ We'll create an agent and add the `stockPrices` tool to it. ```ts filename="src/agents/stockAgent.ts" import { Agent } from "@kastrax/core/agent"; import { openai } from "@ai-sdk/openai"; import * as tools from "../tools/stockPrices"; export const stockAgent = new Agent({ name: "Stock Agent", instructions: "You are a helpful assistant that provides current stock prices. When asked about a stock, use the stock price tool to fetch the stock price.", model: openai("gpt-4o-mini"), tools: { stockPrices: tools.stockPrices, }, }); ``` --- ## Set Up the Kastrax Instance ✅ We need to initialize the Kastrax instance with our agent and tool. ```ts filename="src/index.ts" import { Kastrax } from "@kastrax/core"; import { stockAgent } from "./agents/stockAgent"; export const kastrax = new Kastrax({ agents: { stockAgent }, }); ``` ## Serve the Application ✅ Instead of running the application directly, we'll use the `kastrax dev` command to start the server. This will expose your agent via REST API endpoints, allowing you to interact with it over HTTP. In your terminal, start the Kastrax server by running: ```bash kastrax dev --dir src ``` This command will allow you to test your stockPrices tool and your stockAgent within the playground. This will also start the server and make your agent available at: ``` http://localhost:4111/api/agents/stockAgent/generate ``` --- ## Test the Agent with cURL ✅ Now that your server is running, you can test your agent's endpoint using `curl`: ```bash curl -X POST http://localhost:4111/api/agents/stockAgent/generate \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "What is the current stock price of Apple (AAPL)?" } ] }' ``` **Expected Response:** You should receive a JSON response similar to: ```json { "text": "The current price of Apple (AAPL) is $174.55.", "agent": "Stock Agent" } ``` This indicates that your agent successfully processed the request, used the `stockPrices` tool to fetch the stock price, and returned the result. --- title: 'Overview' description: 'Guides on building with Kastrax' --- # Guides [EN] Source: https://kastrax.ai/en/guides While examples show quick implementations and docs explain specific features, these guides are a bit longer and designed to demonstrate core Kastrax concepts: ## [AI Recruiter](/guides/guide/ai-recruiter) Create a workflow that processes candidate resumes and conducts interviews, demonstrating branching logic and LLM integration in Kastrax workflows. ## [Chef Assistant](/guides/guide/chef-michel) Build an AI chef agent that helps users cook meals with available ingredients, showing how to create interactive agents with custom tools. ## [Research Paper Assistant](/guides/guide/research-assistant) Develop an AI research assistant that analyzes academic papers using Retrieval Augmented Generation (RAG), demonstrating document processing and question answering. ## [Stock Agent](/guides/guide/stock-agent) Implement a simple agent that fetches stock prices, illustrating the basics of creating tools and integrating them with Kastrax agents. --- title: "Reference: Agent | Agents | Kastrax Docs" description: "Documentation for the Agent class in Kastrax, which provides the foundation for creating AI agents with various capabilities." --- # Agent ✅ [EN] Source: https://kastrax.ai/en/reference/agents/agent The `Agent` class is the foundation for creating AI agents in Kastrax. It provides methods for generating responses, streaming interactions, and handling voice capabilities. ## Importing ✅ ```typescript import { Agent } from "@kastrax/core/agent"; ``` ## Constructor ✅ Creates a new Agent instance with the specified configuration. ```typescript constructor(config: AgentConfig) ``` ### Parameters
string | Promise", isOptional: false, description: "Instructions that guide the agent's behavior. Can be a static string or a function that returns a string.", }, { name: "model", type: "KastraxLanguageModel | ({ runtimeContext: RuntimeContext }) => KastraxLanguageModel | Promise", isOptional: false, description: "The language model to use for generating responses. Can be a model instance or a function that returns a model.", }, { name: "tools", type: "ToolsInput | ({ runtimeContext: RuntimeContext }) => ToolsInput | Promise", isOptional: true, description: "Tools that the agent can use. Can be a static object or a function that returns tools.", }, { name: "defaultGenerateOptions", type: "AgentGenerateOptions", isOptional: true, description: "Default options to use when calling generate().", }, { name: "defaultStreamOptions", type: "AgentStreamOptions", isOptional: true, description: "Default options to use when calling stream().", }, { name: "evals", type: "Record", isOptional: true, description: "Evaluation metrics for assessing agent performance.", }, { name: "memory", type: "KastraxMemory", isOptional: true, description: "Memory system for the agent to store and retrieve information.", }, { name: "voice", type: "CompositeVoice", isOptional: true, description: "Voice capabilities for speech-to-text and text-to-speech functionality.", }, ]} /> --- title: "Reference: createTool() | Tools | Agents | Kastrax Docs" description: Documentation for the createTool function in Kastrax, which creates custom tools for agents and workflows. --- # `createTool()` ✅ [EN] Source: https://kastrax.ai/en/reference/agents/createTool The `createTool()` function creates typed tools that can be executed by agents or workflows. Tools have built-in schema validation, execution context, and integration with the Kastrax ecosystem. ## Overview ✅ Tools are a fundamental building block in Kastrax that allow agents to interact with external systems, perform computations, and access data. Each tool has: - A unique identifier - A description that helps the AI understand when and how to use the tool - Optional input and output schemas for validation - An execution function that implements the tool's logic ## Example Usage ✅ ```ts filename="src/tools/stock-tools.ts" showLineNumbers copy import { createTool } from "@kastrax/core/tools"; import { z } from "zod"; // Helper function to fetch stock data const getStockPrice = async (symbol: string) => { const response = await fetch( `https://kastrax-stock-data.vercel.app/api/stock-data?symbol=${symbol}` ); const data = await response.json(); return data.prices["4. close"]; }; // Create a tool to get stock prices export const stockPriceTool = createTool({ id: "getStockPrice", description: "Fetches the current stock price for a given ticker symbol", inputSchema: z.object({ symbol: z.string().describe("The stock ticker symbol (e.g., AAPL, MSFT)") }), outputSchema: z.object({ symbol: z.string(), price: z.number(), currency: z.string(), timestamp: z.string() }), execute: async ({ context }) => { const price = await getStockPrice(context.symbol); return { symbol: context.symbol, price: parseFloat(price), currency: "USD", timestamp: new Date().toISOString() }; } }); // Create a tool that uses the thread context export const threadInfoTool = createTool({ id: "getThreadInfo", description: "Returns information about the current conversation thread", inputSchema: z.object({ includeResource: z.boolean().optional().default(false) }), execute: async ({ context, threadId, resourceId }) => { return { threadId, resourceId: context.includeResource ? resourceId : undefined, timestamp: new Date().toISOString() }; } }); ``` ## API Reference ✅ ### Parameters `createTool()` accepts a single object with the following properties: Promise", required: false, description: "Async function that implements the tool's logic. Receives the execution context and optional configuration.", properties: [ { type: "ToolExecutionContext", parameters: [ { name: "context", type: "object", description: "The validated input data that matches the inputSchema" }, { name: "threadId", type: "string", isOptional: true, description: "Identifier for the conversation thread, if available" }, { name: "resourceId", type: "string", isOptional: true, description: "Identifier for the user or resource interacting with the tool" }, { name: "kastrax", type: "Kastrax", isOptional: true, description: "Reference to the Kastrax instance, if available" }, ] }, { type: "ToolOptions", parameters: [ { name: "toolCallId", type: "string", description: "The ID of the tool call. You can use it e.g. when sending tool-call related information with stream data." }, { name: "messages", type: "CoreMessage[]", description: "Messages that were sent to the language model to initiate the response that contained the tool call. The messages do not include the system prompt nor the assistant response that contained the tool call." }, { name: "abortSignal", type: "AbortSignal", isOptional: true, description: "An optional abort signal that indicates that the overall operation should be aborted." }, ] } ] }, { name: "inputSchema", type: "ZodSchema", required: false, description: "Zod schema that defines and validates the tool's input parameters. If not provided, the tool will accept any input." }, { name: "outputSchema", type: "ZodSchema", required: false, description: "Zod schema that defines and validates the tool's output. Helps ensure the tool returns data in the expected format." }, ]} /> ### Returns ", description: "A Tool instance that can be used with agents, workflows, or directly executed.", properties: [ { type: "Tool", parameters: [ { name: "id", type: "string", description: "The tool's unique identifier" }, { name: "description", type: "string", description: "Description of the tool's functionality" }, { name: "inputSchema", type: "ZodSchema | undefined", description: "Schema for validating inputs" }, { name: "outputSchema", type: "ZodSchema | undefined", description: "Schema for validating outputs" }, { name: "execute", type: "Function", description: "The tool's execution function" } ] } ] } ]} /> ## Type Safety ✅ The `createTool()` function provides full type safety through TypeScript generics: - Input types are inferred from the `inputSchema` - Output types are inferred from the `outputSchema` - The execution context is properly typed based on the input schema This ensures that your tools are type-safe throughout your application. ## Best Practices ✅ 1. **Descriptive IDs**: Use clear, action-oriented IDs like `getWeatherForecast` or `searchDatabase` 2. **Detailed Descriptions**: Provide comprehensive descriptions that explain when and how to use the tool 3. **Input Validation**: Use Zod schemas to validate inputs and provide helpful error messages 4. **Error Handling**: Implement proper error handling in your execute function 5. **Idempotency**: When possible, make your tools idempotent (same input always produces same output) 6. **Performance**: Keep tools lightweight and fast to execute --- title: "Reference: Agent.generate() | Agents | Kastrax Docs" description: "Documentation for the `.generate()` method in Kastrax agents, which produces text or structured responses." --- # Agent.generate() [EN] Source: https://kastrax.ai/en/reference/agents/generate The `generate()` method is used to interact with an agent to produce text or structured responses. This method accepts `messages` and an optional `options` object as parameters. ## Parameters ### `messages` The `messages` parameter can be: - A single string - An array of strings - An array of message objects with `role` and `content` properties The message object structure: ```typescript interface Message { role: 'system' | 'user' | 'assistant'; content: string; } ``` ### `options` (Optional) An optional object that can include configuration for output structure, memory management, tool usage, telemetry, and more. | never", isOptional: true, description: "Callback function called after each execution step. Receives step details as a JSON string. Unavailable for structured output", }, { name: "resourceId", type: "string", isOptional: true, description: "Identifier for the user or resource interacting with the agent. Must be provided if threadId is provided.", }, { name: "telemetry", type: "TelemetrySettings", isOptional: true, description: "Settings for telemetry collection during generation. See TelemetrySettings section below for details.", }, { name: "temperature", type: "number", isOptional: true, description: "Controls randomness in the model's output. Higher values (e.g., 0.8) make the output more random, lower values (e.g., 0.2) make it more focused and deterministic.", }, { name: "threadId", type: "string", isOptional: true, description: "Identifier for the conversation thread. Allows for maintaining context across multiple interactions. Must be provided if resourceId is provided.", }, { name: "toolChoice", type: "'auto' | 'none' | 'required' | { type: 'tool'; toolName: string }", isOptional: true, defaultValue: "'auto'", description: "Controls how the agent uses tools during generation.", }, { name: "toolsets", type: "ToolsetsInput", isOptional: true, description: "Additional toolsets to make available to the agent during generation.", }, ]} /> #### MemoryConfig Configuration options for memory management: #### TelemetrySettings Settings for telemetry collection during generation: ", isOptional: true, description: "Additional information to include in the telemetry data. AttributeValue can be string, number, boolean, array of these types, or null.", }, { name: "tracer", type: "Tracer", isOptional: true, description: "A custom OpenTelemetry tracer instance to use for the telemetry data. See OpenTelemetry documentation for details.", } ]} /> ## Returns The return value of the `generate()` method depends on the options provided, specifically the `output` option. ### PropertiesTable for Return Values ", isOptional: true, description: "The tool calls made during the generation process. Present in both text and object modes.", } ]} /> #### ToolCall Structure ## Related Methods For real-time streaming responses, see the [`stream()`](./stream.mdx) method documentation. --- title: "Reference: getAgent() | Agent Config | Agents | Kastrax Docs" description: API Reference for getAgent. --- # `getAgent()` [EN] Source: https://kastrax.ai/en/reference/agents/getAgent Retrieve an agent based on the provided configuration ```ts showLineNumbers copy async function getAgent({ connectionId, agent, apis, logger, }: { connectionId: string; agent: Record; apis: Record; logger: any; }): Promise<(props: { prompt: string }) => Promise> { return async (props: { prompt: string }) => { return { message: "Hello, world!" }; }; } ``` ## API Signature ### Parameters ", description: "The agent configuration object.", }, { name: "apis", type: "Record", description: "A map of API names to their respective API objects.", }, ]} /> ### Returns --- title: "Reference: Agent.getInstructions() | Agents | Kastrax Docs" description: "Documentation for the `.getInstructions()` method in Kastrax agents, which retrieves the instructions that guide the agent's behavior." --- # Agent.getInstructions() ✅ [EN] Source: https://kastrax.ai/en/reference/agents/getInstructions The `getInstructions()` method retrieves the instructions configured for an agent, resolving them if they're a function. These instructions guide the agent's behavior and define its capabilities and constraints. ## Syntax ✅ ```typescript getInstructions({ runtimeContext }: { runtimeContext?: RuntimeContext } = {}): string | Promise ``` ## Parameters ✅
## Return Value ✅ Returns a string or a Promise that resolves to a string containing the agent's instructions. ## Description ✅ The `getInstructions()` method is used to access the instructions that guide an agent's behavior. It resolves the instructions, which can be either directly provided as a string or returned from a function. Instructions are a critical component of an agent's configuration as they define: - The agent's role and personality - Task-specific guidance - Constraints on the agent's behavior - Context for handling user requests ## Examples ✅ ### Basic Usage ```typescript import { Agent } from "@kastrax/core/agent"; import { openai } from '@ai-sdk/openai'; // Create an agent with static instructions const agent = new Agent({ name: "assistant", instructions: "You are a helpful assistant that provides concise and accurate information.", model: openai("gpt-4o"), }); // Get the instructions const instructions = await agent.getInstructions(); console.log(instructions); // "You are a helpful assistant that provides concise and accurate information." ``` ### Using with RuntimeContext ```typescript import { Agent } from "@kastrax/core/agent"; import { RuntimeContext } from "@kastrax/core/runtime-context"; import { openai } from '@ai-sdk/openai'; // Create an agent with dynamic instructions const agent = new Agent({ name: "contextual-assistant", instructions: ({ runtimeContext }) => { // Dynamic instructions based on runtime context const userPreference = runtimeContext.get("userPreference"); const expertise = runtimeContext.get("expertise") || "general"; if (userPreference === "technical") { return `You are a technical assistant specializing in ${expertise}. Provide detailed technical explanations.`; } return `You are a helpful assistant providing easy-to-understand information about ${expertise}.`; }, model: openai("gpt-4o"), }); // Create a runtime context with user preferences const context = new RuntimeContext(); context.set("userPreference", "technical"); context.set("expertise", "machine learning"); // Get the instructions using the runtime context const instructions = await agent.getInstructions({ runtimeContext: context }); console.log(instructions); // "You are a technical assistant specializing in machine learning. Provide detailed technical explanations." ``` --- title: "Reference: Agent.getMemory() | Agents | Kastrax Docs" description: "Documentation for the `.getMemory()` method in Kastrax agents, which retrieves the memory system associated with the agent." --- # Agent.getMemory() ✅ [EN] Source: https://kastrax.ai/en/reference/agents/getMemory The `getMemory()` method retrieves the memory system associated with an agent. This method is used to access the agent's memory capabilities for storing and retrieving information across conversations. ## Syntax ✅ ```typescript getMemory(): KastraxMemory | undefined ``` ## Parameters ✅ This method does not take any parameters. ## Return Value ✅ Returns a `KastraxMemory` instance if a memory system is configured for the agent, or `undefined` if no memory system is configured. ## Description ✅ The `getMemory()` method is used to access the memory system associated with an agent. Memory systems allow agents to: - Store and retrieve information across multiple interactions - Maintain conversation history - Remember user preferences and context - Provide personalized responses based on past interactions This method is often used in conjunction with `hasOwnMemory()` to check if an agent has a memory system before attempting to use it. ## Examples ✅ ### Basic Usage ```typescript import { Agent } from "@kastrax/core/agent"; import { Memory } from "@kastrax/memory"; import { openai } from '@ai-sdk/openai'; // Create a memory system const memory = new Memory(); // Create an agent with memory const agent = new Agent({ name: "memory-assistant", instructions: "You are a helpful assistant that remembers previous conversations.", model: openai("gpt-4o"), memory, }); // Get the memory system const agentMemory = agent.getMemory(); if (agentMemory) { // Use the memory system to retrieve thread messages const thread = await agentMemory.getThreadById({ resourceId: "user-123", threadId: "conversation-1", }); console.log("Retrieved thread:", thread); } ``` ### Checking for Memory Before Using ```typescript import { Agent } from "@kastrax/core/agent"; import { openai } from '@ai-sdk/openai'; // Create an agent without memory const agent = new Agent({ name: "stateless-assistant", instructions: "You are a helpful assistant.", model: openai("gpt-4o"), }); // Check if the agent has memory before using it if (agent.hasOwnMemory()) { const memory = agent.getMemory(); // Use memory... } else { console.log("This agent does not have a memory system."); } ``` ### Using Memory in a Conversation ```typescript import { Agent } from "@kastrax/core/agent"; import { Memory } from "@kastrax/memory"; import { openai } from '@ai-sdk/openai'; // Create a memory system const memory = new Memory(); // Create an agent with memory const agent = new Agent({ name: "memory-assistant", instructions: "You are a helpful assistant that remembers previous conversations.", model: openai("gpt-4o"), memory, }); // First interaction - store information await agent.generate("My name is Alice.", { resourceId: "user-123", threadId: "conversation-1", }); // Later interaction - retrieve information const result = await agent.generate("What's my name?", { resourceId: "user-123", threadId: "conversation-1", }); console.log(result.text); // Should mention "Alice" // Access the memory system directly const agentMemory = agent.getMemory(); if (agentMemory) { // Retrieve messages from the thread const { messages } = await agentMemory.query({ resourceId: "user-123", threadId: "conversation-1", selectBy: { last: 10 // Get the last 10 messages } }); console.log("Retrieved messages:", messages); } ``` --- title: "Reference: Agent.getModel() | Agents | Kastrax Docs" description: "Documentation for the `.getModel()` method in Kastrax agents, which retrieves the language model that powers the agent." --- # Agent.getModel() ✅ [EN] Source: https://kastrax.ai/en/reference/agents/getModel The `getModel()` method retrieves the language model configured for an agent, resolving it if it's a function. This method is used to access the underlying model that powers the agent's capabilities. ## Syntax ✅ ```typescript getModel({ runtimeContext = new RuntimeContext() }: { runtimeContext?: RuntimeContext } = {}): KastraxLanguageModel | Promise ``` ## Parameters ✅
## Return Value ✅ Returns a `KastraxLanguageModel` instance or a Promise that resolves to a `KastraxLanguageModel` instance. ## Description ✅ The `getModel()` method is used to access the language model that powers an agent. It resolves the model, which can be either directly provided or returned from a function. The language model is a crucial component of an agent as it determines: - The quality and capabilities of the agent's responses - The available features (like function calling, structured output, etc.) - The cost and performance characteristics of the agent ## Examples ✅ ### Basic Usage ```typescript import { Agent } from "@kastrax/core/agent"; import { openai } from '@ai-sdk/openai'; // Create an agent with a static model const agent = new Agent({ name: "assistant", instructions: "You are a helpful assistant.", model: openai("gpt-4o"), }); // Get the model const model = await agent.getModel(); console.log(model.id); // "gpt-4o" ``` ### Using with RuntimeContext ```typescript import { Agent } from "@kastrax/core/agent"; import { RuntimeContext } from "@kastrax/core/runtime-context"; import { openai } from '@ai-sdk/openai'; import { anthropic } from '@ai-sdk/anthropic'; // Create an agent with dynamic model selection const agent = new Agent({ name: "dynamic-model-assistant", instructions: "You are a helpful assistant.", model: ({ runtimeContext }) => { // Dynamic model selection based on runtime context const preferredProvider = runtimeContext.get("preferredProvider"); const highQuality = runtimeContext.get("highQuality") === true; if (preferredProvider === "anthropic") { return highQuality ? anthropic("claude-3-opus") : anthropic("claude-3-sonnet"); } // Default to OpenAI return highQuality ? openai("gpt-4o") : openai("gpt-3.5-turbo"); }, }); // Create a runtime context with preferences const context = new RuntimeContext(); context.set("preferredProvider", "anthropic"); context.set("highQuality", true); // Get the model using the runtime context const model = await agent.getModel({ runtimeContext: context }); console.log(model.id); // "claude-3-opus" ``` --- title: "Reference: Agent.getTools() | Agents | Kastrax Docs" description: "Documentation for the `.getTools()` method in Kastrax agents, which retrieves the tools that the agent can use." --- # Agent.getTools() ✅ [EN] Source: https://kastrax.ai/en/reference/agents/getTools The `getTools()` method retrieves the tools configured for an agent, resolving them if they're a function. These tools extend the agent's capabilities, allowing it to perform specific actions or access external systems. ## Syntax ✅ ```typescript getTools({ runtimeContext = new RuntimeContext() }: { runtimeContext?: RuntimeContext } = {}): ToolsInput | Promise ``` ## Parameters ✅
## Return Value ✅ Returns a `ToolsInput` object or a Promise that resolves to a `ToolsInput` object containing the agent's tools. ## Description ✅ The `getTools()` method is used to access the tools that an agent can use. It resolves the tools, which can be either directly provided as an object or returned from a function. Tools are a key component of an agent's capabilities, allowing it to: - Perform specific actions (like fetching data or making calculations) - Access external systems and APIs - Execute code or commands - Interact with databases or other services ## Examples ✅ ### Basic Usage ```typescript import { Agent } from "@kastrax/core/agent"; import { createTool } from "@kastrax/core/tools"; import { openai } from '@ai-sdk/openai'; import { z } from "zod"; // Create tools using createTool const addTool = createTool({ id: "add", description: "Add two numbers", inputSchema: z.object({ a: z.number().describe("First number"), b: z.number().describe("Second number") }), outputSchema: z.number(), execute: async ({ context }) => { return context.a + context.b; }, }); const multiplyTool = createTool({ id: "multiply", description: "Multiply two numbers", inputSchema: z.object({ a: z.number().describe("First number"), b: z.number().describe("Second number") }), outputSchema: z.number(), execute: async ({ context }) => { return context.a * context.b; }, }); // Create an agent with the tools const agent = new Agent({ name: "calculator", instructions: "You are a calculator assistant that can perform mathematical operations.", model: openai("gpt-4o"), tools: { add: addTool, multiply: multiplyTool, }, }); // Get the tools const tools = await agent.getTools(); console.log(Object.keys(tools)); // ["add", "multiply"] ``` ### Using with RuntimeContext ```typescript import { Agent } from "@kastrax/core/agent"; import { createTool } from "@kastrax/core/tools"; import { RuntimeContext } from "@kastrax/core/runtime-context"; import { openai } from '@ai-sdk/openai'; import { z } from "zod"; // Create an agent with dynamic tools const agent = new Agent({ name: "weather-assistant", instructions: "You are a weather assistant that can provide weather information.", model: openai("gpt-4o"), tools: ({ runtimeContext }) => { // Get API key from runtime context const apiKey = runtimeContext.get("weatherApiKey"); // Create a weather tool with the API key from context const weatherTool = createTool({ id: "getWeather", description: "Get the current weather for a location", inputSchema: z.object({ location: z.string().describe("City name") }), outputSchema: z.object({ temperature: z.number(), conditions: z.string(), humidity: z.number(), windSpeed: z.number() }), execute: async ({ context }) => { // Use the API key from runtime context const response = await fetch(`https://api.weather.com/current?location=${context.location}&apiKey=${apiKey}`); return response.json(); }, }); return { getWeather: weatherTool }; }, }); // Create a runtime context with API key const context = new RuntimeContext(); context.set("weatherApiKey", "your-api-key"); // Get the tools using the runtime context const tools = await agent.getTools({ runtimeContext: context }); console.log(Object.keys(tools)); // ["getWeather"] ``` --- title: "Reference: Agent.getVoice() | Agents | Kastrax Docs" description: "Documentation for the `.getVoice()` method in Kastrax agents, which retrieves the voice provider for speech capabilities." --- # Agent.getVoice() ✅ [EN] Source: https://kastrax.ai/en/reference/agents/getVoice The `getVoice()` method retrieves the voice provider configured for an agent, resolving it if it's a function. This method is used to access the agent's speech capabilities for text-to-speech and speech-to-text functionality. ## Syntax ✅ ```typescript getVoice({ runtimeContext }: { runtimeContext?: RuntimeContext } = {}): CompositeVoice | Promise ``` ## Parameters ✅
## Return Value ✅ Returns a `CompositeVoice` instance or a Promise that resolves to a `CompositeVoice` instance. If no voice provider was configured for the agent, it returns a default voice provider. ## Description ✅ The `getVoice()` method is used to access the voice capabilities of an agent. It resolves the voice provider, which can be either directly provided or returned from a function. The voice provider enables: - Text-to-speech conversion (speaking) - Speech-to-text conversion (listening) - Retrieving available speakers/voices ## Examples ✅ ### Basic Usage ```typescript import { Agent } from "@kastrax/core/agent"; import { ElevenLabsVoice } from "@kastrax/voice-elevenlabs"; import { openai } from '@ai-sdk/openai'; // Create an agent with a voice provider const agent = new Agent({ name: "voice-assistant", instructions: "You are a helpful voice assistant.", model: openai("gpt-4o"), voice: new ElevenLabsVoice({ apiKey: process.env.ELEVENLABS_API_KEY, }), }); // Get the voice provider const voice = await agent.getVoice(); // Use the voice provider for text-to-speech const audioStream = await voice.speak("Hello, how can I help you today?"); // Use the voice provider for speech-to-text const transcription = await voice.listen(audioStream); // Get available speakers const speakers = await voice.getSpeakers(); console.log(speakers); ``` ### Using with RuntimeContext ```typescript import { Agent } from "@kastrax/core/agent"; import { ElevenLabsVoice } from "@kastrax/voice-elevenlabs"; import { RuntimeContext } from "@kastrax/core/runtime-context"; import { openai } from '@ai-sdk/openai'; // Create an agent with a dynamic voice provider const agent = new Agent({ name: "voice-assistant", instructions: ({ runtimeContext }) => { // Dynamic instructions based on runtime context const instructions = runtimeContext.get("preferredVoiceInstructions"); return instructions || "You are a helpful voice assistant."; }, model: openai("gpt-4o"), voice: new ElevenLabsVoice({ apiKey: process.env.ELEVENLABS_API_KEY, }), }); // Create a runtime context with preferences const context = new RuntimeContext(); context.set("preferredVoiceInstructions", "You are an evil voice assistant"); // Get the voice provider using the runtime context const voice = await agent.getVoice({ runtimeContext: context }); // Use the voice provider const audioStream = await voice.speak("Hello, how can I help you today?"); ``` --- title: "Reference: Agent.stream() | Streaming | Agents | Kastrax Docs" description: Documentation for the `.stream()` method in Kastrax agents, which enables real-time streaming of responses. --- # `stream()` [EN] Source: https://kastrax.ai/en/reference/agents/stream The `stream()` method enables real-time streaming of responses from an agent. This method accepts `messages` and an optional `options` object as parameters, similar to `generate()`. ## Parameters ### `messages` The `messages` parameter can be: - A single string - An array of strings - An array of message objects with `role` and `content` properties The message object structure: ```typescript interface Message { role: 'system' | 'user' | 'assistant'; content: string; } ``` ### `options` (Optional) An optional object that can include configuration for output structure, memory management, tool usage, telemetry, and more. | never", isOptional: true, description: "Callback function called after each step during streaming. Unavailable for structured output", }, { name: "output", type: "Zod schema | JsonSchema7", isOptional: true, description: "Defines the expected structure of the output. Can be a JSON Schema object or a Zod schema.", }, { name: "resourceId", type: "string", isOptional: true, description: "Identifier for the user or resource interacting with the agent. Must be provided if threadId is provided.", }, { name: "telemetry", type: "TelemetrySettings", isOptional: true, description: "Settings for telemetry collection during streaming. See TelemetrySettings section below for details.", }, { name: "temperature", type: "number", isOptional: true, description: "Controls randomness in the model's output. Higher values (e.g., 0.8) make the output more random, lower values (e.g., 0.2) make it more focused and deterministic.", }, { name: "threadId", type: "string", isOptional: true, description: "Identifier for the conversation thread. Allows for maintaining context across multiple interactions. Must be provided if resourceId is provided.", }, { name: "toolChoice", type: "'auto' | 'none' | 'required' | { type: 'tool'; toolName: string }", isOptional: true, defaultValue: "'auto'", description: "Controls how the agent uses tools during streaming.", }, { name: "toolsets", type: "ToolsetsInput", isOptional: true, description: "Additional toolsets to make available to the agent during this stream.", } ]} /> #### MemoryConfig Configuration options for memory management: #### TelemetrySettings Settings for telemetry collection during streaming: ", isOptional: true, description: "Additional information to include in the telemetry data. AttributeValue can be string, number, boolean, array of these types, or null.", }, { name: "tracer", type: "Tracer", isOptional: true, description: "A custom OpenTelemetry tracer instance to use for the telemetry data. See OpenTelemetry documentation for details.", } ]} /> ## Returns The return value of the `stream()` method depends on the options provided, specifically the `output` option. ### PropertiesTable for Return Values ", isOptional: true, description: "Stream of text chunks. Present when output is 'text' (no schema provided) or when using `experimental_output`.", }, { name: "objectStream", type: "AsyncIterable", isOptional: true, description: "Stream of structured data. Present only when using `output` option with a schema.", }, { name: "partialObjectStream", type: "AsyncIterable", isOptional: true, description: "Stream of structured data. Present only when using `experimental_output` option.", }, { name: "object", type: "Promise", isOptional: true, description: "Promise that resolves to the final structured output. Present when using either `output` or `experimental_output` options.", } ]} /> ## Examples ### Basic Text Streaming ```typescript const stream = await myAgent.stream([ { role: "user", content: "Tell me a story." } ]); for await (const chunk of stream.textStream) { process.stdout.write(chunk); } ``` ### Structured Output Streaming with Thread Context ```typescript const schema = { type: 'object', properties: { summary: { type: 'string' }, nextSteps: { type: 'array', items: { type: 'string' } } }, required: ['summary', 'nextSteps'] }; const response = await myAgent.stream( "What should we do next?", { output: schema, threadId: "project-123", onFinish: text => console.log("Finished:", text) } ); for await (const chunk of response.textStream) { console.log(chunk); } const result = await response.object; console.log("Final structured result:", result); ``` The key difference between Agent's `stream()` and LLM's `stream()` is that Agents maintain conversation context through `threadId`, can access tools, and integrate with the agent's memory system. --- title: "kastrax build" description: "Build your Kastrax project for production deployment" --- The `kastrax build` command bundles your Kastrax project into a production-ready Hono server. Hono is a lightweight web framework that provides type-safe routing and middleware support, making it ideal for deploying Kastrax agents as HTTP endpoints. ## Usage ✅ [EN] Source: https://kastrax.ai/en/reference/cli/build ```bash kastrax build [options] ``` ## Options ✅ - `--dir `: Directory containing your Kastrax project (default: current directory) ## What It Does ✅ 1. Locates your Kastrax entry file (either `src/kastrax/index.ts` or `src/kastrax/index.js`) 2. Creates a `.kastrax` output directory 3. Bundles your code using Rollup with: - Tree shaking for optimal bundle size - Node.js environment targeting - Source map generation for debugging ## Example ✅ ```bash # Build from current directory ✅ kastrax build # Build from specific directory ✅ kastrax build --dir ./my-kastrax-project ``` ## Output ✅ The command generates a production bundle in the `.kastrax` directory, which includes: - A Hono-based HTTP server with your Kastrax agents exposed as endpoints - Bundled JavaScript files optimized for production - Source maps for debugging - Required dependencies This output is suitable for: - Deploying to cloud servers (EC2, Digital Ocean) - Running in containerized environments - Using with container orchestration systems ## Deployers ✅ When a Deployer is used, the build output is automatically prepared for the target platform e.g - [Vercel Deployer](/reference/deployer/vercel) - [Netlify Deployer](/reference/deployer/netlify) - [Cloudflare Deployer](/reference/deployer/cloudflare) --- title: "`kastrax dev` Reference | Local Development | Kastrax CLI" description: Documentation for the kastrax dev command, which starts a development server for agents, tools, and workflows. --- # `kastrax dev` Reference ✅ [EN] Source: https://kastrax.ai/en/reference/cli/dev The `kastrax dev` command starts a development server that exposes REST routes for your agents, tools, and workflows, ## Parameters ✅ ## Routes ✅ Starting the server with `kastrax dev` exposes a set of REST routes by default: ### System Routes - **GET `/api`**: Get API status. ### Agent Routes Agents are expected to be exported from `src/kastrax/agents`. - **GET `/api/agents`**: Lists the registered agents found in your Kastrax folder. - **GET `/api/agents/:agentId`**: Get agent by ID. - **GET `/api/agents/:agentId/evals/ci`**: Get CI evals by agent ID. - **GET `/api/agents/:agentId/evals/live`**: Get live evals by agent ID. - **POST `/api/agents/:agentId/generate`**: Sends a text-based prompt to the specified agent, returning the agent's response. - **POST `/api/agents/:agentId/stream`**: Stream a response from an agent. - **POST `/api/agents/:agentId/instructions`**: Update an agent's instructions. - **POST `/api/agents/:agentId/instructions/enhance`**: Generate an improved system prompt from instructions. - **GET `/api/agents/:agentId/speakers`**: Get available speakers for an agent. - **POST `/api/agents/:agentId/speak`**: Convert text to speech using the agent's voice provider. - **POST `/api/agents/:agentId/listen`**: Convert speech to text using the agent's voice provider. - **POST `/api/agents/:agentId/tools/:toolId/execute`**: Execute a tool through an agent. ### Tool Routes Tools are expected to be exported from `src/kastrax/tools` (or the configured tools directory). - **GET `/api/tools`**: Get all tools. - **GET `/api/tools/:toolId`**: Get tool by ID. - **POST `/api/tools/:toolId/execute`**: Invokes a specific tool by name, passing input data in the request body. ### Workflow Routes Workflows are expected to be exported from `src/kastrax/workflows` (or the configured workflows directory). - **GET `/api/workflows`**: Get all workflows. - **GET `/api/workflows/:workflowId`**: Get workflow by ID. - **POST `/api/workflows/:workflowName/start`**: Starts the specified workflow. - **POST `/api/workflows/:workflowName/:instanceId/event`**: Sends an event or trigger signal to an existing workflow instance. - **GET `/api/workflows/:workflowName/:instanceId/status`**: Returns status info for a running workflow instance. - **POST `/api/workflows/:workflowId/resume`**: Resume a suspended workflow step. - **POST `/api/workflows/:workflowId/resume-async`**: Resume a suspended workflow step asynchronously. - **POST `/api/workflows/:workflowId/createRun`**: Create a new workflow run. - **POST `/api/workflows/:workflowId/start-async`**: Execute/Start a workflow asynchronously. - **GET `/api/workflows/:workflowId/watch`**: Watch workflow transitions in real-time. ### Memory Routes - **GET `/api/memory/status`**: Get memory status. - **GET `/api/memory/threads`**: Get all threads. - **GET `/api/memory/threads/:threadId`**: Get thread by ID. - **GET `/api/memory/threads/:threadId/messages`**: Get messages for a thread. - **POST `/api/memory/threads`**: Create a new thread. - **PATCH `/api/memory/threads/:threadId`**: Update a thread. - **DELETE `/api/memory/threads/:threadId`**: Delete a thread. - **POST `/api/memory/save-messages`**: Save messages. ### Telemetry Routes - **GET `/api/telemetry`**: Get all traces. ### Log Routes - **GET `/api/logs`**: Get all logs. - **GET `/api/logs/transports`**: List of all log transports. - **GET `/api/logs/:runId`**: Get logs by run ID. ### Vector Routes - **POST `/api/vector/:vectorName/upsert`**: Upsert vectors into an index. - **POST `/api/vector/:vectorName/create-index`**: Create a new vector index. - **POST `/api/vector/:vectorName/query`**: Query vectors from an index. - **GET `/api/vector/:vectorName/indexes`**: List all indexes for a vector store. - **GET `/api/vector/:vectorName/indexes/:indexName`**: Get details about a specific index. - **DELETE `/api/vector/:vectorName/indexes/:indexName`**: Delete a specific index. ### OpenAPI Specification - **GET `/openapi.json`**: Returns an auto-generated OpenAPI specification for your project's routes. - **GET `/swagger-ui`**: Access Swagger UI for API documentation. ## Additional Notes ✅ The port defaults to 4111. Both the port and hostname can be configured via the kastrax server config. See [Launch Development Server](/docs/local-dev/kastrax-dev#launch-development-server) for configuration details. Make sure you have your environment variables set up in your `.env.development` or `.env` file for any providers you use (e.g., `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.). ### Example request To test an agent after running `kastrax dev`: ```bash curl -X POST http://localhost:4111/api/agents/myAgent/generate \ -H "Content-Type: application/json" \ -d '{ "messages": [ { "role": "user", "content": "Hello, how can you assist me today?" } ] }' ``` --- title: "`kastrax init` reference | Project Creation | Kastrax CLI" description: Documentation for the kastrax init command, which creates a new Kastrax project with interactive setup options. --- # `kastrax init` Reference ✅ [EN] Source: https://kastrax.ai/en/reference/cli/init ## `kastrax init` ✅ This creates a new Kastrax project. You can run it in three different ways: 1. **Interactive Mode (Recommended)** Run without flags to use the interactive prompt, which will guide you through: - Choosing a directory for Kastrax files - Selecting components to install (Agents, Tools, Workflows) - Choosing a default LLM provider (OpenAI, Anthropic, or Groq) - Deciding whether to include example code 2. **Quick Start with Defaults** ```bash kastrax init --default ``` This sets up a project with: - Source directory: `src/` - All components: agents, tools, workflows - OpenAI as the default provider - No example code 3. **Custom Setup** ```bash kastrax init --dir src/kastrax --components agents,tools --llm openai --example ``` Options: - `-d, --dir`: Directory for Kastrax files (defaults to src/kastrax) - `-c, --components`: Comma-separated list of components (agents, tools, workflows) - `-l, --llm`: Default model provider (openai, anthropic, groq, google or cerebras) - `-k, --llm-api-key`: API key for the selected LLM provider (will be added to .env file) - `-e, --example`: Include example code - `-ne, --no-example`: Skip example code # Agents API [EN] Source: https://kastrax.ai/en/reference/client-js/agents The Agents API provides methods to interact with Kastrax AI agents, including generating responses, streaming interactions, and managing agent tools. ## Getting All Agents Retrieve a list of all available agents: ```typescript const agents = await client.getAgents(); ``` ## Working with a Specific Agent Get an instance of a specific agent: ```typescript const agent = client.getAgent("agent-id"); ``` ## Agent Methods ### Get Agent Details Retrieve detailed information about an agent: ```typescript const details = await agent.details(); ``` ### Generate Response Generate a response from the agent: ```typescript const response = await agent.generate({ messages: [ { role: "user", content: "Hello, how are you?", }, ], threadId: "thread-1", // Optional: Thread ID for conversation context resourceid: "resource-1", // Optional: Resource ID output: {}, // Optional: Output configuration }); ``` ### Stream Response Stream responses from the agent for real-time interactions: ```typescript const response = await agent.stream({ messages: [ { role: "user", content: "Tell me a story", }, ], }); // Process data stream with the processDataStream util response.processDataStream({ onTextPart: (text) => { process.stdout.write(text); }, onFilePart: (file) => { console.log(file); }, onDataPart: (data) => { console.log(data); }, onErrorPart: (error) => { console.error(error); }, }); // You can also read from response body directly const reader = response.body.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; console.log(new TextDecoder().decode(value)); } ``` ### Get Agent Tool Retrieve information about a specific tool available to the agent: ```typescript const tool = await agent.getTool("tool-id"); ``` ### Get Agent Evaluations Get evaluation results for the agent: ```typescript // Get CI evaluations const evals = await agent.evals(); // Get live evaluations const liveEvals = await agent.liveEvals(); ``` # Error Handling [EN] Source: https://kastrax.ai/en/reference/client-js/error-handling The Kastrax Client SDK includes built-in retry mechanism and error handling capabilities. ## Error Handling All API methods can throw errors that you can catch and handle: ```typescript try { const agent = client.getAgent("agent-id"); const response = await agent.generate({ messages: [{ role: "user", content: "Hello" }], }); } catch (error) { console.error("An error occurred:", error.message); } ``` ## Retry Mechanism The client automatically retries failed requests with exponential backoff: ```typescript const client = new KastraxClient({ baseUrl: "http://localhost:4111", retries: 3, // Number of retry attempts backoffMs: 300, // Initial backoff time maxBackoffMs: 5000, // Maximum backoff time }); ``` ### How Retries Work 1. First attempt fails → Wait 300ms 2. Second attempt fails → Wait 600ms 3. Third attempt fails → Wait 1200ms 4. Final attempt fails → Throw error # Logs API [EN] Source: https://kastrax.ai/en/reference/client-js/logs The Logs API provides methods to access and query system logs and debugging information in Kastrax. ## Getting Logs Retrieve system logs with optional filtering: ```typescript const logs = await client.getLogs({ transportId: "transport-1", }); ``` ## Getting Logs for a Specific Run Retrieve logs for a specific execution run: ```typescript const runLogs = await client.getLogForRun({ runId: "run-1", transportId: "transport-1", }); ``` # Memory API [EN] Source: https://kastrax.ai/en/reference/client-js/memory The Memory API provides methods to manage conversation threads and message history in Kastrax. ## Memory Thread Operations ### Get All Threads Retrieve all memory threads for a specific resource: ```typescript const threads = await client.getMemoryThreads({ resourceId: "resource-1", agentId: "agent-1" }); ``` ### Create a New Thread Create a new memory thread: ```typescript const thread = await client.createMemoryThread({ title: "New Conversation", metadata: { category: "support" }, resourceid: "resource-1", agentId: "agent-1" }); ``` ### Working with a Specific Thread Get an instance of a specific memory thread: ```typescript const thread = client.getMemoryThread("thread-id", "agent-id"); ``` ## Thread Methods ### Get Thread Details Retrieve details about a specific thread: ```typescript const details = await thread.get(); ``` ### Update Thread Update thread properties: ```typescript const updated = await thread.update({ title: "Updated Title", metadata: { status: "resolved" }, resourceid: "resource-1", }); ``` ### Delete Thread Delete a thread and its messages: ```typescript await thread.delete(); ``` ## Message Operations ### Save Messages Save messages to memory: ```typescript const savedMessages = await client.saveMessageToMemory({ messages: [ { role: "user", content: "Hello!", id: "1", threadId: "thread-1", createdAt: new Date(), type: "text", }, ], agentId: "agent-1" }); ``` ### Get Memory Status Check the status of the memory system: ```typescript const status = await client.getMemoryStatus("agent-id"); ``` # Telemetry API [EN] Source: https://kastrax.ai/en/reference/client-js/telemetry The Telemetry API provides methods to retrieve and analyze traces from your Kastrax application. This helps you monitor and debug your application's behavior and performance. ## Getting Traces Retrieve traces with optional filtering and pagination: ```typescript const telemetry = await client.getTelemetry({ name: "trace-name", // Optional: Filter by trace name scope: "scope-name", // Optional: Filter by scope page: 1, // Optional: Page number for pagination perPage: 10, // Optional: Number of items per page attribute: { // Optional: Filter by custom attributes key: "value", }, }); ``` # Tools API [EN] Source: https://kastrax.ai/en/reference/client-js/tools The Tools API provides methods to interact with and execute tools available in the Kastrax platform. ## Getting All Tools Retrieve a list of all available tools: ```typescript const tools = await client.getTools(); ``` ## Working with a Specific Tool Get an instance of a specific tool: ```typescript const tool = client.getTool("tool-id"); ``` ## Tool Methods ### Get Tool Details Retrieve detailed information about a tool: ```typescript const details = await tool.details(); ``` ### Execute Tool Execute a tool with specific arguments: ```typescript const result = await tool.execute({ args: { param1: "value1", param2: "value2", }, threadId: "thread-1", // Optional: Thread context resourceid: "resource-1", // Optional: Resource identifier }); ``` # Vectors API [EN] Source: https://kastrax.ai/en/reference/client-js/vectors The Vectors API provides methods to work with vector embeddings for semantic search and similarity matching in Kastrax. ## Working with Vectors Get an instance of a vector store: ```typescript const vector = client.getVector("vector-name"); ``` ## Vector Methods ### Get Vector Index Details Retrieve information about a specific vector index: ```typescript const details = await vector.details("index-name"); ``` ### Create Vector Index Create a new vector index: ```typescript const result = await vector.createIndex({ indexName: "new-index", dimension: 128, metric: "cosine", // 'cosine', 'euclidean', or 'dotproduct' }); ``` ### Upsert Vectors Add or update vectors in an index: ```typescript const ids = await vector.upsert({ indexName: "my-index", vectors: [ [0.1, 0.2, 0.3], // First vector [0.4, 0.5, 0.6], // Second vector ], metadata: [{ label: "first" }, { label: "second" }], ids: ["id1", "id2"], // Optional: Custom IDs }); ``` ### Query Vectors Search for similar vectors: ```typescript const results = await vector.query({ indexName: "my-index", queryVector: [0.1, 0.2, 0.3], topK: 10, filter: { label: "first" }, // Optional: Metadata filter includeVector: true, // Optional: Include vectors in results }); ``` ### Get All Indexes List all available indexes: ```typescript const indexes = await vector.getIndexes(); ``` ### Delete Index Delete a vector index: ```typescript const result = await vector.delete("index-name"); ``` # Workflows API [EN] Source: https://kastrax.ai/en/reference/client-js/workflows The Workflows API provides methods to interact with and execute automated workflows in Kastrax. ## Getting All Workflows Retrieve a list of all available workflows: ```typescript const workflows = await client.getWorkflows(); ``` ## Working with a Specific Workflow Get an instance of a specific workflow: ```typescript const workflow = client.getWorkflow("workflow-id"); ``` ## Workflow Methods ### Get Workflow Details Retrieve detailed information about a workflow: ```typescript const details = await workflow.details(); ``` ### Start workflow run asynchronously Start a workflow run with triggerData and await full run results: ```typescript const {runId} = workflow.createRun() const result = await workflow.startAsync({ runId, triggerData: { param1: "value1", param2: "value2", }, }); ``` ### Resume Workflow run asynchronously Resume a suspended workflow step and await full run result: ```typescript const {runId} = createRun({runId: prevRunId}) const result = await workflow.resumeAsync({ runId, stepId: "step-id", contextData: { key: "value" }, }); ``` ### Watch Workflow Watch workflow transitions ```typescript try{ // Get workflow instance const workflow = client.getWorkflow("workflow-id"); // Create a workflow run const {runId} = workflow.createRun() // Watch workflow run workflow.watch({runId},(record)=>{ // Every new record is the latest transition state of the workflow run console.log({ activePaths: record.activePaths, results: record.results, timestamp: record.timestamp, runId: record.runId }); }); // Start workflow run workflow.start({ runId, triggerData: { city: 'New York', }, }); }catch(e){ console.error(e); } ``` ### Resume Workflow Resume workflow run and watch workflow step transitions ```typescript try{ //To resume a workflow run, when a step is suspended const {run} = createRun({runId: prevRunId}) //Watch run workflow.watch({runId},(record)=>{ // Every new record is the latest transition state of the workflow run console.log({ activePaths: record.activePaths, results: record.results, timestamp: record.timestamp, runId: record.runId }); }) //resume run workflow.resume({ runId, stepId: "step-id", contextData: { key: "value" }, }); }catch(e){ console.error(e); } ``` ### Workflow run result A workflow run result yields the following: | Field | Type | Description | |-------|------|-------------| | `activePaths` | `Record` | Currently active paths in the workflow with their execution status | | `results` | `CoreWorkflowRunResult['results']` | Results from the workflow execution | | `timestamp` | `number` | Unix timestamp of when this transition occurred | | `runId` | `string` | Unique identifier for this workflow run instance | --- title: "Kastrax Core" description: Documentation for the Kastrax Class, the core entry point for managing agents, workflows, and server endpoints. --- # The Kastrax Class ✅ [EN] Source: https://kastrax.ai/en/reference/core/kastrax-class The Kastrax class is the core entry point for your application. It manages agents, workflows, and server endpoints. ## Constructor Options ✅ ", description: "Custom tools to register. Structured as a key-value pair, with keys being the tool name and values being the tool function.", isOptional: true, defaultValue: "{}", }, { name: "storage", type: "KastraxStorage", description: "Storage engine instance for persisting data", isOptional: true, }, { name: "vectors", type: "Record", description: "Vector store instance, used for semantic search and vector-based tools (eg Pinecone, PgVector or Qdrant)", isOptional: true, }, { name: "logger", type: "Logger", description: "Logger instance created with createLogger()", isOptional: true, defaultValue: "Console logger with INFO level", }, { name: "workflows", type: "Record", description: "Workflows to register. Structured as a key-value pair, with keys being the workflow name and values being the workflow instance.", isOptional: true, defaultValue: "{}", }, { name: "server", type: "ServerConfig", description: "Server configuration including port, host, timeout, API routes, middleware, CORS settings, and build options for Swagger UI, API request logging, and OpenAPI docs.", isOptional: true, defaultValue: "{ port: 4111, host: localhost, cors: { origin: '*', allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowHeaders: ['Content-Type', 'Authorization', 'x-kastrax-client-type'], exposeHeaders: ['Content-Length', 'X-Requested-With'], credentials: false } }", }, ]} /> ## Initialization ✅ The Kastrax class is typically initialized in your `src/kastrax/index.ts` file: ```typescript copy filename=src/kastrax/index.ts import { Kastrax } from "@kastrax/core"; import { createLogger } from "@kastrax/core/logger"; // Basic initialization export const kastrax = new Kastrax({}); // Full initialization with all options export const kastrax = new Kastrax({ agents: {}, workflows: [], integrations: [], logger: createLogger({ name: "My Project", level: "info", }), storage: {}, tools: {}, vectors: {}, }); ``` You can think of the `Kastrax` class as a top-level registry. When you register tools with Kastrax, your registered agents and workflows can use them. When you register integrations with Kastrax, agents, workflows, and tools can use them. ## Methods ✅ ", description: "Returns all registered agents as a key-value object.", example: 'const agents = kastrax.getAgents();', }, { name: "getWorkflow(id, { serialized })", type: "Workflow", description: "Returns a workflow instance by id. The serialized option (default: false) returns a simplified representation with just the name.", example: 'const workflow = kastrax.getWorkflow("myWorkflow");', }, { name: "getWorkflows({ serialized })", type: "Record", description: "Returns all registered workflows. The serialized option (default: false) returns simplified representations.", example: 'const workflows = kastrax.getWorkflows();', }, { name: "getVector(name)", type: "KastraxVector", description: "Returns a vector store instance by name. Throws if not found.", example: 'const vectorStore = kastrax.getVector("myVectorStore");', }, { name: "getVectors()", type: "Record", description: "Returns all registered vector stores as a key-value object.", example: 'const vectorStores = kastrax.getVectors();', }, { name: "getDeployer()", type: "KastraxDeployer | undefined", description: "Returns the configured deployer instance, if any.", example: 'const deployer = kastrax.getDeployer();', }, { name: "getStorage()", type: "KastraxStorage | undefined", description: "Returns the configured storage instance.", example: 'const storage = kastrax.getStorage();', }, { name: "getMemory()", type: "KastraxMemory | undefined", description: "Returns the configured memory instance. Note: This is deprecated, memory should be added to agents directly.", example: 'const memory = kastrax.getMemory();', }, { name: "getServer()", type: "ServerConfig | undefined", description: "Returns the server configuration including port, timeout, API routes, middleware, CORS settings, and build options.", example: 'const serverConfig = kastrax.getServer();', }, { name: "setStorage(storage)", type: "void", description: "Sets the storage instance for the Kastrax instance.", example: 'kastrax.setStorage(new DefaultStorage());', }, { name: "setLogger({ logger })", type: "void", description: "Sets the logger for all components (agents, workflows, etc.).", example: 'kastrax.setLogger({ logger: createLogger({ name: "MyLogger" }) });', }, { name: "setTelemetry(telemetry)", type: "void", description: "Sets the telemetry configuration for all components.", example: 'kastrax.setTelemetry({ export: { type: "console" } });', }, { name: "getLogger()", type: "Logger", description: "Gets the configured logger instance.", example: 'const logger = kastrax.getLogger();', }, { name: "getTelemetry()", type: "Telemetry | undefined", description: "Gets the configured telemetry instance.", example: 'const telemetry = kastrax.getTelemetry();', }, { name: "getLogsByRunId({ runId, transportId })", type: "Promise", description: "Retrieves logs for a specific run ID and transport ID.", example: 'const logs = await kastrax.getLogsByRunId({ runId: "123", transportId: "456" });', }, { name: "getLogs(transportId)", type: "Promise", description: "Retrieves all logs for a specific transport ID.", example: 'const logs = await kastrax.getLogs("transportId");', }, ]} /> ## Error Handling ✅ The Kastrax class methods throw typed errors that can be caught: ```typescript copy try { const tool = kastrax.getTool("nonexistentTool"); } catch (error) { if (error instanceof Error) { console.log(error.message); // "Tool with name nonexistentTool not found" } } ``` --- title: "Cloudflare Deployer" description: "Documentation for the CloudflareDeployer class, which deploys Kastrax applications to Cloudflare Workers." --- # CloudflareDeployer ✅ [EN] Source: https://kastrax.ai/en/reference/deployer/cloudflare The CloudflareDeployer deploys Kastrax applications to Cloudflare Workers, handling configuration, environment variables, and route management. It extends the abstract Deployer class to provide Cloudflare-specific deployment functionality. ## Usage Example ✅ ```typescript import { Kastrax } from '@kastrax/core'; import { CloudflareDeployer } from '@kastrax/deployer-cloudflare'; const kastrax = new Kastrax({ deployer: new CloudflareDeployer({ scope: 'your-account-id', projectName: 'your-project-name', routes: [ { pattern: 'example.com/*', zone_name: 'example.com', custom_domain: true, }, ], workerNamespace: 'your-namespace', auth: { apiToken: 'your-api-token', apiEmail: 'your-email', }, }), // ... other Kastrax configuration options }); ``` ## Parameters ✅ ### Constructor Parameters ", description: "Environment variables to be included in the worker configuration.", isOptional: true, }, { name: "auth", type: "object", description: "Cloudflare authentication details.", isOptional: false, }, ]} /> ### auth Object ### CFRoute Object ### Environment Variables The CloudflareDeployer handles environment variables from multiple sources: 1. **Environment Files**: Variables from `.env.production` and `.env` files. 2. **Configuration**: Variables passed through the `env` parameter. ## Build Kastrax Project ✅ To build your Kastrax project for cloudflare deployment: ```bash npx kastrax build The build process generates the following output structure in the `.kastrax/output` directory: ``` .kastrax/output/ ├── index.mjs # Main worker entry point ├── wrangler.json # Cloudflare Worker configuration └── assets/ # Static assets and dependencies ``` ### Wrangler Configuration The CloudflareDeployer automatically generates a `wrangler.json` configuration file with the following settings: ```json { "name": "your-project-name", "main": "./output/index.mjs", "compatibility_date": "2024-12-02", "compatibility_flags": ["nodejs_compat"], "observability": { "logs": { "enabled": true } }, "vars": { // Environment variables from .env files and configuration }, "routes": [ // Route configurations if specified ] } ``` ### Route Configuration Routes can be configured to direct traffic to your worker based on URL patterns and domains: ```typescript const routes = [ { pattern: 'api.example.com/*', zone_name: 'example.com', custom_domain: true, }, { pattern: 'example.com/api/*', zone_name: 'example.com', }, ]; ``` ## Deployment Options ✅ After building, you can deploy your Kastrax application `.kastrax/output` to Cloudflare Workers using any of these methods: 1. **Wrangler CLI**: Deploy directly using Cloudflare's official CLI tool - Install the CLI: `npm install -g wrangler` - Navigate to the output directory: `cd .kastrax/output` - Login to your Cloudflare account: `wrangler login` - Deploy to preview environment: `wrangler deploy` - For production deployment: `wrangler deploy --env production` 2. **Cloudflare Dashboard**: Upload the build output manually through the Cloudflare dashboard > You can also run `wrangler dev` in your output directory `.kastrax/output` to test your Kastrax application locally. ## Platform Documentation ✅ - [Cloudflare Workers](https://developers.cloudflare.com/workers/) --- title: "Kastrax Deployer" description: Documentation for the Deployer abstract class, which handles packaging and deployment of Kastrax applications. --- # Deployer ✅ [EN] Source: https://kastrax.ai/en/reference/deployer/deployer The Deployer handles the deployment of Kastrax applications by packaging code, managing environment files, and serving applications using the Hono framework. Concrete implementations must define the deploy method for specific deployment targets. ## Usage Example ✅ ```typescript import { Deployer } from "@kastrax/deployer"; // Create a custom deployer by extending the abstract Deployer class class CustomDeployer extends Deployer { constructor() { super({ name: 'custom-deployer' }); } // Implement the abstract deploy method async deploy(outputDirectory: string): Promise { // Prepare the output directory await this.prepare(outputDirectory); // Bundle the application await this._bundle('server.ts', 'kastrax.ts', outputDirectory); // Custom deployment logic // ... } } ``` ## Parameters ✅ ### Constructor Parameters ### deploy Parameters ## Methods ✅ Promise", description: "Returns a list of environment files to be used during deployment. By default, it looks for '.env.production' and '.env' files.", }, { name: "deploy", type: "(outputDirectory: string) => Promise", description: "Abstract method that must be implemented by subclasses. Handles the deployment process to the specified output directory.", }, ]} /> ## Inherited Methods from Bundler ✅ The Deployer class inherits the following key methods from the Bundler class: Promise", description: "Prepares the output directory by cleaning it and creating necessary subdirectories.", }, { name: "writeInstrumentationFile", type: "(outputDirectory: string) => Promise", description: "Writes an instrumentation file to the output directory for telemetry purposes.", }, { name: "writePackageJson", type: "(outputDirectory: string, dependencies: Map) => Promise", description: "Generates a package.json file in the output directory with the specified dependencies.", }, { name: "_bundle", type: "(serverFile: string, kastraxEntryFile: string, outputDirectory: string, bundleLocation?: string) => Promise", description: "Bundles the application using the specified server and Kastrax entry files.", }, ]} /> ## Core Concepts ✅ ### Deployment Lifecycle The Deployer abstract class implements a structured deployment lifecycle: 1. **Initialization**: The deployer is initialized with a name and creates a Deps instance for dependency management. 2. **Environment Setup**: The `getEnvFiles` method identifies environment files (.env.production, .env) to be used during deployment. 3. **Preparation**: The `prepare` method (inherited from Bundler) cleans the output directory and creates necessary subdirectories. 4. **Bundling**: The `_bundle` method (inherited from Bundler) packages the application code and its dependencies. 5. **Deployment**: The abstract `deploy` method is implemented by subclasses to handle the actual deployment process. ### Environment File Management The Deployer class includes built-in support for environment file management through the `getEnvFiles` method. This method: - Looks for environment files in a predefined order (.env.production, .env) - Uses the FileService to find the first existing file - Returns an array of found environment files - Returns an empty array if no environment files are found ```typescript getEnvFiles(): Promise { const possibleFiles = ['.env.production', '.env.local', '.env']; try { const fileService = new FileService(); const envFile = fileService.getFirstExistingFile(possibleFiles); return Promise.resolve([envFile]); } catch {} return Promise.resolve([]); } ``` ### Bundling and Deployment Relationship The Deployer class extends the Bundler class, establishing a clear relationship between bundling and deployment: 1. **Bundling as a Prerequisite**: Bundling is a prerequisite step for deployment, where the application code is packaged into a deployable format. 2. **Shared Infrastructure**: Both bundling and deployment share common infrastructure like dependency management and file system operations. 3. **Specialized Deployment Logic**: While bundling focuses on code packaging, deployment adds environment-specific logic for deploying the bundled code. 4. **Extensibility**: The abstract `deploy` method allows for creating specialized deployers for different target environments. --- title: "Netlify Deployer" description: "Documentation for the NetlifyDeployer class, which deploys Kastrax applications to Netlify Functions." --- # NetlifyDeployer ✅ [EN] Source: https://kastrax.ai/en/reference/deployer/netlify The NetlifyDeployer deploys Kastrax applications to Netlify Functions, handling site creation, configuration, and deployment processes. It extends the abstract Deployer class to provide Netlify-specific deployment functionality. ## Usage Example ✅ ```typescript import { Kastrax } from '@kastrax/core'; import { NetlifyDeployer } from '@kastrax/deployer-netlify'; const kastrax = new Kastrax({ deployer: new NetlifyDeployer({ scope: 'your-team-slug', projectName: 'your-project-name', token: 'your-netlify-token' }), // ... other Kastrax configuration options }); ``` ## Parameters ✅ ### Constructor Parameters ### Environment Variables The NetlifyDeployer handles environment variables from multiple sources: 1. **Environment Files**: Variables from `.env.production` and `.env` files. 2. **Configuration**: Variables passed through the Kastrax configuration. 3. **Netlify Dashboard**: Variables can also be managed through Netlify's web interface. ## Build Kastrax Project ✅ To build your Kastrax project for Netlify deployment: ```bash npx kastrax build ``` The build process generates the following output structure in the `.kastrax/output` directory: ``` .kastrax/output/ ├── netlify/ │ └── functions/ │ └── api/ │ └── index.mjs # Application entry point └── netlify.toml # Netlify configuration ``` ### Netlify Configuration The NetlifyDeployer automatically generates a `netlify.toml` configuration file in `.kastrax/output` with the following settings: ```toml [functions] node_bundler = "esbuild" directory = "netlify/functions" [[redirects]] force = true from = "/*" status = 200 to = "/.netlify/functions/api/:splat" ``` ## Deployment Options ✅ After building, you can deploy your Kastrax application `.kastrax/output` to Netlify using any of these methods: 1. **Netlify CLI**: Deploy directly using Netlify's official CLI tool - Install the CLI: `npm install -g netlify-cli` - Navigate to the output directory: `cd .kastrax/output` - Deploy with functions directory specified: `netlify deploy --dir . --functions ./netlify/functions` - For production deployment add `--prod` flag: `netlify deploy --prod --dir . --functions ./netlify/functions` 2. **Netlify Dashboard**: Connect your Git repository or drag-and-drop the build output through the Netlify dashboard 3. **Netlify Dev**: Run your Kastrax application locally with Netlify's development environment > You can also run `netlify dev` in your output directory `.kastrax/output` to test your Kastrax application locally. ## Platform Documentation ✅ - [Netlify](https://docs.netlify.com/) --- title: "Vercel Deployer" description: "Documentation for the VercelDeployer class, which deploys Kastrax applications to Vercel." --- # VercelDeployer ✅ [EN] Source: https://kastrax.ai/en/reference/deployer/vercel The VercelDeployer deploys Kastrax applications to Vercel, handling configuration, environment variable synchronization, and deployment processes. It extends the abstract Deployer class to provide Vercel-specific deployment functionality. ## Usage Example ✅ ```typescript import { Kastrax } from '@kastrax/core'; import { VercelDeployer } from '@kastrax/deployer-vercel'; const kastrax = new Kastrax({ deployer: new VercelDeployer({ teamSlug: 'your-team-slug', projectName: 'your-project-name', token: 'your-vercel-token' }), // ... other Kastrax configuration options }); ``` ## Parameters ✅ ### Constructor Parameters ### Environment Variables The VercelDeployer handles environment variables from multiple sources: 1. **Environment Files**: Variables from `.env.production` and `.env` files. 2. **Configuration**: Variables passed through the Kastrax configuration. 3. **Vercel Dashboard**: Variables can also be managed through Vercel's web interface. The deployer automatically synchronizes environment variables between your local development environment and Vercel's environment variable system, ensuring consistency across all deployment environments (production, preview, and development). ## Build Kastrax Project ✅ To build your Kastrax project for Vercel deployment: ```bash npx kastrax build ``` The build process generates the following output structure in the `.kastrax/output` directory: ``` .kastrax/output/ ├── vercel.json # Vercel configuration └── index.mjs # Application entry point ``` ### Vercel Configuration The VercelDeployer automatically generates a `vercel.json` configuration file in `.kastrax/output` with the following settings: ```json { "version": 2, "installCommand": "npm install --omit=dev", "builds": [ { "src": "index.mjs", "use": "@vercel/node", "config": { "includeFiles": ["**"] } } ], "routes": [ { "src": "/(.*)", "dest": "index.mjs" } ] } ``` ## Deployment Options ✅ After building, you can deploy your Kastrax application `.kastrax/output` to Vercel using any of these methods: 1. **Vercel CLI**: Deploy directly using Vercel's official CLI tool - Install the CLI: `npm install -g vercel` - Navigate to the output directory: `cd .kastrax/output` - Deploy to preview environment: `vercel` - For production deployment: `vercel --prod` 2. **Vercel Dashboard**: Connect your Git repository or drag-and-drop the build output through the Vercel dashboard > You can also run `vercel dev` in your output directory `.kastrax/output` to test your Kastrax application locally. ## Platform Documentation ✅ - [Vercel](https://vercel.com/docs) --- title: "Reference: Answer Relevancy | Metrics | Evals | Kastrax Docs" description: Documentation for the Answer Relevancy Metric in Kastrax, which evaluates how well LLM outputs address the input query. --- # AnswerRelevancyMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/answer-relevancy The `AnswerRelevancyMetric` class evaluates how well an LLM's output answers or addresses the input query. It uses a judge-based system to determine relevancy and provides detailed scoring and reasoning. ## Basic Usage ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { AnswerRelevancyMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new AnswerRelevancyMetric(model, { uncertaintyWeight: 0.3, scale: 1, }); const result = await metric.measure( "What is the capital of France?", "Paris is the capital of France.", ); console.log(result.score); // Score from 0-1 console.log(result.info.reason); // Explanation of the score ``` ## Constructor Parameters ✅ ### AnswerRelevancyMetricOptions ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric evaluates relevancy through query-answer alignment, considering completeness, accuracy, and detail level. ### Scoring Process 1. Statement Analysis: - Breaks output into meaningful statements while preserving context - Evaluates each statement against query requirements 2. Evaluates relevance of each statement: - "yes": Full weight for direct matches - "unsure": Partial weight (default: 0.3) for approximate matches - "no": Zero weight for irrelevant content Final score: `((direct + uncertainty * partial) / total_statements) * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: Perfect relevance - complete and accurate - 0.7-0.9: High relevance - minor gaps or imprecisions - 0.4-0.6: Moderate relevance - significant gaps - 0.1-0.3: Low relevance - major issues - 0.0: No relevance - incorrect or off-topic ## Example with Custom Configuration ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { AnswerRelevancyMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new AnswerRelevancyMetric( model, { uncertaintyWeight: 0.5, // Higher weight for uncertain verdicts scale: 5, // Use 0-5 scale instead of 0-1 }, ); const result = await metric.measure( "What are the benefits of exercise?", "Regular exercise improves cardiovascular health, builds strength, and boosts mental wellbeing.", ); // Example output: // { // score: 4.5, // info: { // reason: "The score is 4.5 out of 5 because the response directly addresses the query // with specific, accurate benefits of exercise. It covers multiple aspects // (cardiovascular, muscular, and mental health) in a clear and concise manner. // The answer is highly relevant and provides appropriate detail without // including unnecessary information." // } // } ``` ## Related ✅ - [Prompt Alignment Metric](./prompt-alignment) - [Context Precision Metric](./context-precision) - [Faithfulness Metric](./faithfulness) --- title: "Reference: Bias | Output Metrics | Evals | Kastrax Docs" description: Documentation for the Bias Metric in Kastrax, which evaluates LLM outputs for various forms of bias, including gender, political, racial/ethnic, or geographical bias. --- # BiasMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/bias The `BiasMetric` class evaluates whether an LLM's output contains various forms of bias, including gender, political, racial/ethnic, or geographical bias. This metric is particularly useful for detecting unintended biases that may emerge after fine-tuning models or applying optimizations. ## Basic Usage ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { BiasMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new BiasMetric(model, { scale: 1 }); const result = await metric.measure( "Describe leadership qualities in the workplace", "Strong leaders are typically men who can make tough decisions..." ); console.log(result.score); // Score from 0-1, higher means more biased ``` ## Constructor Parameters ✅ ### BiasMetricOptions ## measure() Parameters ✅ ## Returns ✅ ## Bias Categories ✅ The metric evaluates several types of bias: 1. **Gender Bias**: Discrimination or stereotypes based on gender 2. **Political Bias**: Prejudice against political ideologies or beliefs 3. **Racial/Ethnic Bias**: Discrimination based on race, ethnicity, or national origin 4. **Geographical Bias**: Prejudice based on location or regional stereotypes ## Scoring Details ✅ The metric evaluates bias through opinion analysis based on: - Opinion identification and extraction - Presence of discriminatory language - Use of stereotypes or generalizations - Balance in perspective presentation - Loaded or prejudicial terminology ### Scoring Process 1. Extracts opinions from text: - Identifies subjective statements - Excludes factual claims - Includes cited opinions 2. Evaluates each opinion: - Checks for discriminatory language - Assesses stereotypes and generalizations - Analyzes perspective balance Final score: `(biased_opinions / total_opinions) * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: Complete bias - all opinions contain bias - 0.7-0.9: Significant bias - majority of opinions show bias - 0.4-0.6: Moderate bias - mix of biased and neutral opinions - 0.1-0.3: Minimal bias - most opinions show balanced perspective - 0.0: No detectable bias - opinions are balanced and neutral ## Example with Different Types of Bias ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { BiasMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new BiasMetric(model); // Testing for different types of bias const results = await Promise.all([ metric.measure( "Describe voting patterns", "These radical right-wing voters consistently vote against their interests..." ), metric.measure( "Describe workplace dynamics", "Modern offices have diverse teams working together based on merit..." ) ]); // Example outputs: // Political bias example: { score: 1.0 } // Unbiased example: { score: 0.0 } ``` ## Related ✅ - [Toxicity Metric](./toxicity) - [Faithfulness Metric](./faithfulness) - [Hallucination Metric](./hallucination) - [Context Relevancy Metric](./context-relevancy) --- title: "Reference: Completeness | Metrics | Evals | Kastrax Docs" description: Documentation for the Completeness Metric in Kastrax, which evaluates how thoroughly LLM outputs cover key elements present in the input. --- # CompletenessMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/completeness The `CompletenessMetric` class evaluates how thoroughly an LLM's output covers the key elements present in the input. It analyzes nouns, verbs, topics, and terms to determine coverage and provides a detailed completeness score. ## Basic Usage ✅ ```typescript import { CompletenessMetric } from "@kastrax/evals/nlp"; const metric = new CompletenessMetric(); const result = await metric.measure( "Explain how photosynthesis works in plants using sunlight, water, and carbon dioxide.", "Plants use sunlight to convert water and carbon dioxide into glucose through photosynthesis." ); console.log(result.score); // Coverage score from 0-1 console.log(result.info); // Object containing detailed metrics about element coverage ``` ## measure() Parameters ✅ ## Returns ✅ ## Element Extraction Details ✅ The metric extracts and analyzes several types of elements: - Nouns: Key objects, concepts, and entities - Verbs: Actions and states (converted to infinitive form) - Topics: Main subjects and themes - Terms: Individual significant words The extraction process includes: - Normalization of text (removing diacritics, converting to lowercase) - Splitting camelCase words - Handling of word boundaries - Special handling of short words (3 characters or less) - Deduplication of elements ## Scoring Details ✅ The metric evaluates completeness through linguistic element coverage analysis. ### Scoring Process 1. Extracts key elements: - Nouns and named entities - Action verbs - Topic-specific terms - Normalized word forms 2. Calculates coverage of input elements: - Exact matches for short terms (≤3 chars) - Substantial overlap (>60%) for longer terms Final score: `(covered_elements / total_input_elements) * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: Complete coverage - contains all input elements - 0.7-0.9: High coverage - includes most key elements - 0.4-0.6: Partial coverage - contains some key elements - 0.1-0.3: Low coverage - missing most key elements - 0.0: No coverage - output lacks all input elements ## Example with Analysis ✅ ```typescript import { CompletenessMetric } from "@kastrax/evals/nlp"; const metric = new CompletenessMetric(); const result = await metric.measure( "The quick brown fox jumps over the lazy dog", "A brown fox jumped over a dog" ); // Example output: // { // score: 0.75, // info: { // inputElements: ["quick", "brown", "fox", "jump", "lazy", "dog"], // outputElements: ["brown", "fox", "jump", "dog"], // missingElements: ["quick", "lazy"], // elementCounts: { input: 6, output: 4 } // } // } ``` ## Related ✅ - [Answer Relevancy Metric](./answer-relevancy) - [Content Similarity Metric](./content-similarity) - [Textual Difference Metric](./textual-difference) - [Keyword Coverage Metric](./keyword-coverage) --- title: "Reference: Content Similarity | Evals | Kastrax Docs" description: Documentation for the Content Similarity Metric in Kastrax, which measures textual similarity between strings and provides a matching score. --- # ContentSimilarityMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/content-similarity The `ContentSimilarityMetric` class measures the textual similarity between two strings, providing a score that indicates how closely they match. It supports configurable options for case sensitivity and whitespace handling. ## Basic Usage ✅ ```typescript import { ContentSimilarityMetric } from "@kastrax/evals/nlp"; const metric = new ContentSimilarityMetric({ ignoreCase: true, ignoreWhitespace: true }); const result = await metric.measure( "Hello, world!", "hello world" ); console.log(result.score); // Similarity score from 0-1 console.log(result.info); // Detailed similarity metrics ``` ## Constructor Parameters ✅ ### ContentSimilarityOptions ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric evaluates textual similarity through character-level matching and configurable text normalization. ### Scoring Process 1. Normalizes text: - Case normalization (if ignoreCase: true) - Whitespace normalization (if ignoreWhitespace: true) 2. Compares processed strings using string-similarity algorithm: - Analyzes character sequences - Aligns word boundaries - Considers relative positions - Accounts for length differences Final score: `similarity_value * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: Perfect match - identical texts - 0.7-0.9: High similarity - mostly matching content - 0.4-0.6: Moderate similarity - partial matches - 0.1-0.3: Low similarity - few matching patterns - 0.0: No similarity - completely different texts ## Example with Different Options ✅ ```typescript import { ContentSimilarityMetric } from "@kastrax/evals/nlp"; // Case-sensitive comparison const caseSensitiveMetric = new ContentSimilarityMetric({ ignoreCase: false, ignoreWhitespace: true }); const result1 = await caseSensitiveMetric.measure( "Hello World", "hello world" ); // Lower score due to case difference // Example output: // { // score: 0.75, // info: { similarity: 0.75 } // } // Strict whitespace comparison const strictWhitespaceMetric = new ContentSimilarityMetric({ ignoreCase: true, ignoreWhitespace: false }); const result2 = await strictWhitespaceMetric.measure( "Hello World", "Hello World" ); // Lower score due to whitespace difference // Example output: // { // score: 0.85, // info: { similarity: 0.85 } // } ``` ## Related ✅ - [Completeness Metric](./completeness) - [Textual Difference Metric](./textual-difference) - [Answer Relevancy Metric](./answer-relevancy) - [Keyword Coverage Metric](./keyword-coverage) --- title: "Reference: Context Position | Metrics | Evals | Kastrax Docs" description: Documentation for the Context Position Metric in Kastrax, which evaluates the ordering of context nodes based on their relevance to the query and output. --- # ContextPositionMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/context-position The `ContextPositionMetric` class evaluates how well context nodes are ordered based on their relevance to the query and output. It uses position-weighted scoring to emphasize the importance of having the most relevant context pieces appear earlier in the sequence. ## Basic Usage ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { ContextPositionMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new ContextPositionMetric(model, { context: [ "Photosynthesis is a biological process used by plants to create energy from sunlight.", "The process of photosynthesis produces oxygen as a byproduct.", "Plants need water and nutrients from the soil to grow.", ], }); const result = await metric.measure( "What is photosynthesis?", "Photosynthesis is the process by which plants convert sunlight into energy.", ); console.log(result.score); // Position score from 0-1 console.log(result.info.reason); // Explanation of the score ``` ## Constructor Parameters ✅ ### ContextPositionMetricOptions ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric evaluates context positioning through binary relevance assessment and position-based weighting. ### Scoring Process 1. Evaluates context relevance: - Assigns binary verdict (yes/no) to each piece - Records position in sequence - Documents relevance reasoning 2. Applies position weights: - Earlier positions weighted more heavily (weight = 1/(position + 1)) - Sums weights of relevant pieces - Normalizes by maximum possible score Final score: `(weighted_sum / max_possible_sum) * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: Optimal - most relevant context first - 0.7-0.9: Good - relevant context mostly early - 0.4-0.6: Mixed - relevant context scattered - 0.1-0.3: Suboptimal - relevant context mostly later - 0.0: Poor ordering - relevant context at end or missing ## Example with Analysis ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { ContextPositionMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new ContextPositionMetric(model, { context: [ "A balanced diet is important for health.", "Exercise strengthens the heart and improves blood circulation.", "Regular physical activity reduces stress and anxiety.", "Exercise equipment can be expensive.", ], }); const result = await metric.measure( "What are the benefits of exercise?", "Regular exercise improves cardiovascular health and mental wellbeing.", ); // Example output: // { // score: 0.5, // info: { // reason: "The score is 0.5 because while the second and third contexts are highly // relevant to the benefits of exercise, they are not optimally positioned at // the beginning of the sequence. The first and last contexts are not relevant // to the query, which impacts the position-weighted scoring." // } // } ``` ## Related ✅ - [Context Precision Metric](./context-precision) - [Answer Relevancy Metric](./answer-relevancy) - [Completeness Metric](./completeness) + [Context Relevancy Metric](./context-relevancy) --- title: "Reference: Context Precision | Metrics | Evals | Kastrax Docs" description: Documentation for the Context Precision Metric in Kastrax, which evaluates the relevance and precision of retrieved context nodes for generating expected outputs. --- # ContextPrecisionMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/context-precision The `ContextPrecisionMetric` class evaluates how relevant and precise the retrieved context nodes are for generating the expected output. It uses a judge-based system to analyze each context piece's contribution and provides weighted scoring based on position. ## Basic Usage ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { ContextPrecisionMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new ContextPrecisionMetric(model, { context: [ "Photosynthesis is a biological process used by plants to create energy from sunlight.", "Plants need water and nutrients from the soil to grow.", "The process of photosynthesis produces oxygen as a byproduct.", ], }); const result = await metric.measure( "What is photosynthesis?", "Photosynthesis is the process by which plants convert sunlight into energy.", ); console.log(result.score); // Precision score from 0-1 console.log(result.info.reason); // Explanation of the score ``` ## Constructor Parameters ✅ ### ContextPrecisionMetricOptions ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric evaluates context precision through binary relevance assessment and Mean Average Precision (MAP) scoring. ### Scoring Process 1. Assigns binary relevance scores: - Relevant context: 1 - Irrelevant context: 0 2. Calculates Mean Average Precision: - Computes precision at each position - Weights earlier positions more heavily - Normalizes to configured scale Final score: `Mean Average Precision * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: All relevant context in optimal order - 0.7-0.9: Mostly relevant context with good ordering - 0.4-0.6: Mixed relevance or suboptimal ordering - 0.1-0.3: Limited relevance or poor ordering - 0.0: No relevant context ## Example with Analysis ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { ContextPrecisionMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new ContextPrecisionMetric(model, { context: [ "Exercise strengthens the heart and improves blood circulation.", "A balanced diet is important for health.", "Regular physical activity reduces stress and anxiety.", "Exercise equipment can be expensive.", ], }); const result = await metric.measure( "What are the benefits of exercise?", "Regular exercise improves cardiovascular health and mental wellbeing.", ); // Example output: // { // score: 0.75, // info: { // reason: "The score is 0.75 because the first and third contexts are highly relevant // to the benefits mentioned in the output, while the second and fourth contexts // are not directly related to exercise benefits. The relevant contexts are well-positioned // at the beginning and middle of the sequence." // } // } ``` ## Related ✅ - [Answer Relevancy Metric](./answer-relevancy) - [Context Position Metric](./context-position) - [Completeness Metric](./completeness) - [Context Relevancy Metric](./context-relevancy) --- title: "Reference: Context Relevancy | Evals | Kastrax Docs" description: Documentation for the Context Relevancy Metric, which evaluates the relevance of retrieved context in RAG pipelines. --- # ContextRelevancyMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/context-relevancy The `ContextRelevancyMetric` class evaluates the quality of your RAG (Retrieval-Augmented Generation) pipeline's retriever by measuring how relevant the retrieved context is to the input query. It uses an LLM-based evaluation system that first extracts statements from the context and then assesses their relevance to the input. ## Basic Usage ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { ContextRelevancyMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new ContextRelevancyMetric(model, { context: [ "All data is encrypted at rest and in transit", "Two-factor authentication is mandatory", "The platform supports multiple languages", "Our offices are located in San Francisco" ] }); const result = await metric.measure( "What are our product's security features?", "Our product uses encryption and requires 2FA.", ); console.log(result.score); // Score from 0-1 console.log(result.info.reason); // Explanation of the relevancy assessment ``` ## Constructor Parameters ✅ ### ContextRelevancyMetricOptions ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric evaluates how well retrieved context matches the query through binary relevance classification. ### Scoring Process 1. Extracts statements from context: - Breaks down context into meaningful units - Preserves semantic relationships 2. Evaluates statement relevance: - Assesses each statement against query - Counts relevant statements - Calculates relevance ratio Final score: `(relevant_statements / total_statements) * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: Perfect relevancy - all retrieved context is relevant - 0.7-0.9: High relevancy - most context is relevant with few irrelevant pieces - 0.4-0.6: Moderate relevancy - a mix of relevant and irrelevant context - 0.1-0.3: Low relevancy - mostly irrelevant context - 0.0: No relevancy - completely irrelevant context ## Example with Custom Configuration ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { ContextRelevancyMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new ContextRelevancyMetric(model, { scale: 100, // Use 0-100 scale instead of 0-1 context: [ "Basic plan costs $10/month", "Pro plan includes advanced features at $30/month", "Enterprise plan has custom pricing", "Our company was founded in 2020", "We have offices worldwide" ] }); const result = await metric.measure( "What are our pricing plans?", "We offer Basic, Pro, and Enterprise plans.", ); // Example output: // { // score: 60, // info: { // reason: "3 out of 5 statements are relevant to pricing plans. The statements about // company founding and office locations are not relevant to the pricing query." // } // } ``` ## Related ✅ - [Contextual Recall Metric](./contextual-recall) - [Context Precision Metric](./context-precision) - [Context Position Metric](./context-position) --- title: "Reference: Contextual Recall | Metrics | Evals | Kastrax Docs" description: Documentation for the Contextual Recall Metric, which evaluates the completeness of LLM responses in incorporating relevant context. --- # ContextualRecallMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/contextual-recall The `ContextualRecallMetric` class evaluates how effectively an LLM's response incorporates all relevant information from the provided context. It measures whether important information from the reference documents was successfully included in the response, focusing on completeness rather than precision. ## Basic Usage ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { ContextualRecallMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new ContextualRecallMetric(model, { context: [ "Product features: cloud synchronization capability", "Offline mode available for all users", "Supports multiple devices simultaneously", "End-to-end encryption for all data" ] }); const result = await metric.measure( "What are the key features of the product?", "The product includes cloud sync, offline mode, and multi-device support.", ); console.log(result.score); // Score from 0-1 ``` ## Constructor Parameters ✅ ### ContextualRecallMetricOptions ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric evaluates recall through comparison of response content against relevant context items. ### Scoring Process 1. Evaluates information recall: - Identifies relevant items in context - Tracks correctly recalled information - Measures completeness of recall 2. Calculates recall score: - Counts correctly recalled items - Compares against total relevant items - Computes coverage ratio Final score: `(correctly_recalled_items / total_relevant_items) * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: Perfect recall - all relevant information included - 0.7-0.9: High recall - most relevant information included - 0.4-0.6: Moderate recall - some relevant information missed - 0.1-0.3: Low recall - significant information missed - 0.0: No recall - no relevant information included ## Example with Custom Configuration ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { ContextualRecallMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new ContextualRecallMetric( model, { scale: 100, // Use 0-100 scale instead of 0-1 context: [ "All data is encrypted at rest and in transit", "Two-factor authentication (2FA) is mandatory", "Regular security audits are performed", "Incident response team available 24/7" ] } ); const result = await metric.measure( "Summarize the company's security measures", "The company implements encryption for data protection and requires 2FA for all users.", ); // Example output: // { // score: 50, // Only half of the security measures were mentioned // info: { // reason: "The score is 50 because only half of the security measures were mentioned // in the response. The response missed the regular security audits and incident // response team information." // } // } ``` ## Related ✅ + [Context Relevancy Metric](./context-relevancy) + [Completeness Metric](./completeness) + [Summarization Metric](./summarization) --- title: "Reference: Faithfulness | Metrics | Evals | Kastrax Docs" description: Documentation for the Faithfulness Metric in Kastrax, which evaluates the factual accuracy of LLM outputs compared to the provided context. --- # FaithfulnessMetric Reference ✅ [EN] Source: https://kastrax.ai/en/reference/evals/faithfulness The `FaithfulnessMetric` in Kastrax evaluates how factually accurate an LLM's output is compared to the provided context. It extracts claims from the output and verifies them against the context, making it essential to measure RAG pipeline responses' reliability. ## Basic Usage ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { FaithfulnessMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new FaithfulnessMetric(model, { context: [ "The company was established in 1995.", "Currently employs around 450-550 people.", ], }); const result = await metric.measure( "Tell me about the company.", "The company was founded in 1995 and has 500 employees.", ); console.log(result.score); // 1.0 console.log(result.info.reason); // "All claims are supported by the context." ``` ## Constructor Parameters ✅ ### FaithfulnessMetricOptions ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric evaluates faithfulness through claim verification against provided context. ### Scoring Process 1. Analyzes claims and context: - Extracts all claims (factual and speculative) - Verifies each claim against context - Assigns one of three verdicts: - "yes" - claim supported by context - "no" - claim contradicts context - "unsure" - claim unverifiable 2. Calculates faithfulness score: - Counts supported claims - Divides by total claims - Scales to configured range Final score: `(supported_claims / total_claims) * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: All claims supported by context - 0.7-0.9: Most claims supported, few unverifiable - 0.4-0.6: Mixed support with some contradictions - 0.1-0.3: Limited support, many contradictions - 0.0: No supported claims ## Advanced Example ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { FaithfulnessMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new FaithfulnessMetric(model, { context: [ "The company had 100 employees in 2020.", "Current employee count is approximately 500.", ], }); // Example with mixed claim types const result = await metric.measure( "What's the company's growth like?", "The company has grown from 100 employees in 2020 to 500 now, and might expand to 1000 by next year.", ); // Example output: // { // score: 0.67, // info: { // reason: "The score is 0.67 because two claims are supported by the context // (initial employee count of 100 in 2020 and current count of 500), // while the future expansion claim is marked as unsure as it cannot // be verified against the context." // } // } ``` ### Related - [Answer Relevancy Metric](./answer-relevancy) - [Hallucination Metric](./hallucination) - [Context Relevancy Metric](./context-relevancy) --- title: "Reference: Hallucination | Metrics | Evals | Kastrax Docs" description: Documentation for the Hallucination Metric in Kastrax, which evaluates the factual correctness of LLM outputs by identifying contradictions with provided context. --- # HallucinationMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/hallucination The `HallucinationMetric` evaluates whether an LLM generates factually correct information by comparing its output against the provided context. This metric measures hallucination by identifying direct contradictions between the context and the output. ## Basic Usage ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { HallucinationMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new HallucinationMetric(model, { context: [ "Tesla was founded in 2003 by Martin Eberhard and Marc Tarpenning in San Carlos, California.", ], }); const result = await metric.measure( "Tell me about Tesla's founding.", "Tesla was founded in 2004 by Elon Musk in California.", ); console.log(result.score); // Score from 0-1 console.log(result.info.reason); // Explanation of the score // Example output: // { // score: 0.67, // info: { // reason: "The score is 0.67 because two out of three statements from the context // (founding year and founders) were contradicted by the output, while the // location statement was not contradicted." // } // } ``` ## Constructor Parameters ✅ ### HallucinationMetricOptions ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric evaluates hallucination through contradiction detection and unsupported claim analysis. ### Scoring Process 1. Analyzes factual content: - Extracts statements from context - Identifies numerical values and dates - Maps statement relationships 2. Analyzes output for hallucinations: - Compares against context statements - Marks direct conflicts as hallucinations - Identifies unsupported claims as hallucinations - Evaluates numerical accuracy - Considers approximation context 3. Calculates hallucination score: - Counts hallucinated statements (contradictions and unsupported claims) - Divides by total statements - Scales to configured range Final score: `(hallucinated_statements / total_statements) * scale` ### Important Considerations - Claims not present in context are treated as hallucinations - Subjective claims are hallucinations unless explicitly supported - Speculative language ("might", "possibly") about facts IN context is allowed - Speculative language about facts NOT in context is treated as hallucination - Empty outputs result in zero hallucinations - Numerical evaluation considers: - Scale-appropriate precision - Contextual approximations - Explicit precision indicators ### Score interpretation (0 to scale, default 0-1) - 1.0: Complete hallucination - contradicts all context statements - 0.75: High hallucination - contradicts 75% of context statements - 0.5: Moderate hallucination - contradicts half of context statements - 0.25: Low hallucination - contradicts 25% of context statements - 0.0: No hallucination - output aligns with all context statements **Note:** The score represents the degree of hallucination - lower scores indicate better factual alignment with the provided context ## Example with Analysis ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { HallucinationMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new HallucinationMetric(model, { context: [ "OpenAI was founded in December 2015 by Sam Altman, Greg Brockman, and others.", "The company launched with a $1 billion investment commitment.", "Elon Musk was an early supporter but left the board in 2018.", ], }); const result = await metric.measure({ input: "What are the key details about OpenAI?", output: "OpenAI was founded in 2015 by Elon Musk and Sam Altman with a $2 billion investment.", }); // Example output: // { // score: 0.33, // info: { // reason: "The score is 0.33 because one out of three statements from the context // was contradicted (the investment amount was stated as $2 billion instead // of $1 billion). The founding date was correct, and while the output's // description of founders was incomplete, it wasn't strictly contradictory." // } // } ``` ## Related ✅ - [Faithfulness Metric](./faithfulness) - [Answer Relevancy Metric](./answer-relevancy) - [Context Precision Metric](./context-precision) - [Context Relevancy Metric](./context-relevancy) --- title: "Reference: Keyword Coverage | Metrics | Evals | Kastrax Docs" description: Documentation for the Keyword Coverage Metric in Kastrax, which evaluates how well LLM outputs cover important keywords from the input. --- # KeywordCoverageMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/keyword-coverage The `KeywordCoverageMetric` class evaluates how well an LLM's output covers the important keywords from the input. It analyzes keyword presence and matches while ignoring common words and stop words. ## Basic Usage ✅ ```typescript import { KeywordCoverageMetric } from "@kastrax/evals/nlp"; const metric = new KeywordCoverageMetric(); const result = await metric.measure( "What are the key features of Python programming language?", "Python is a high-level programming language known for its simple syntax and extensive libraries." ); console.log(result.score); // Coverage score from 0-1 console.log(result.info); // Object containing detailed metrics about keyword coverage ``` ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric evaluates keyword coverage by matching keywords with the following features: - Common word and stop word filtering (e.g., "the", "a", "and") - Case-insensitive matching - Word form variation handling - Special handling of technical terms and compound words ### Scoring Process 1. Processes keywords from input and output: - Filters out common words and stop words - Normalizes case and word forms - Handles special terms and compounds 2. Calculates keyword coverage: - Matches keywords between texts - Counts successful matches - Computes coverage ratio Final score: `(matched_keywords / total_keywords) * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: Perfect keyword coverage - 0.7-0.9: Good coverage with most keywords present - 0.4-0.6: Moderate coverage with some keywords missing - 0.1-0.3: Poor coverage with many keywords missing - 0.0: No keyword matches ## Examples with Analysis ✅ ```typescript import { KeywordCoverageMetric } from "@kastrax/evals/nlp"; const metric = new KeywordCoverageMetric(); // Perfect coverage example const result1 = await metric.measure( "The quick brown fox jumps over the lazy dog", "A quick brown fox jumped over a lazy dog" ); // { // score: 1.0, // info: { // matchedKeywords: 6, // totalKeywords: 6 // } // } // Partial coverage example const result2 = await metric.measure( "Python features include easy syntax, dynamic typing, and extensive libraries", "Python has simple syntax and many libraries" ); // { // score: 0.67, // info: { // matchedKeywords: 4, // totalKeywords: 6 // } // } // Technical terms example const result3 = await metric.measure( "Discuss React.js component lifecycle and state management", "React components have lifecycle methods and manage state" ); // { // score: 1.0, // info: { // matchedKeywords: 4, // totalKeywords: 4 // } // } ``` ## Special Cases ✅ The metric handles several special cases: - Empty input/output: Returns score of 1.0 if both empty, 0.0 if only one is empty - Single word: Treated as a single keyword - Technical terms: Preserves compound technical terms (e.g., "React.js", "machine learning") - Case differences: "JavaScript" matches "javascript" - Common words: Ignored in scoring to focus on meaningful keywords ## Related ✅ - [Completeness Metric](./completeness) - [Content Similarity Metric](./content-similarity) - [Answer Relevancy Metric](./answer-relevancy) - [Textual Difference Metric](./textual-difference) - [Context Relevancy Metric](./context-relevancy) --- title: "Reference: Prompt Alignment | Metrics | Evals | Kastrax Docs" description: Documentation for the Prompt Alignment Metric in Kastrax, which evaluates how well LLM outputs adhere to given prompt instructions. --- # PromptAlignmentMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/prompt-alignment The `PromptAlignmentMetric` class evaluates how strictly an LLM's output follows a set of given prompt instructions. It uses a judge-based system to verify each instruction is followed exactly and provides detailed reasoning for any deviations. ## Basic Usage ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { PromptAlignmentMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const instructions = [ "Start sentences with capital letters", "End each sentence with a period", "Use present tense", ]; const metric = new PromptAlignmentMetric(model, { instructions, scale: 1, }); const result = await metric.measure( "describe the weather", "The sun is shining. Clouds float in the sky. A gentle breeze blows.", ); console.log(result.score); // Alignment score from 0-1 console.log(result.info.reason); // Explanation of the score ``` ## Constructor Parameters ✅ ### PromptAlignmentOptions ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric evaluates instruction alignment through: - Applicability assessment for each instruction - Strict compliance evaluation for applicable instructions - Detailed reasoning for all verdicts - Proportional scoring based on applicable instructions ### Instruction Verdicts Each instruction receives one of three verdicts: - "yes": Instruction is applicable and completely followed - "no": Instruction is applicable but not followed or only partially followed - "n/a": Instruction is not applicable to the given context ### Scoring Process 1. Evaluates instruction applicability: - Determines if each instruction applies to the context - Marks irrelevant instructions as "n/a" - Considers domain-specific requirements 2. Assesses compliance for applicable instructions: - Evaluates each applicable instruction independently - Requires complete compliance for "yes" verdict - Documents specific reasons for all verdicts 3. Calculates alignment score: - Counts followed instructions ("yes" verdicts) - Divides by total applicable instructions (excluding "n/a") - Scales to configured range Final score: `(followed_instructions / applicable_instructions) * scale` ### Important Considerations - Empty outputs: - All formatting instructions are considered applicable - Marked as "no" since they cannot satisfy requirements - Domain-specific instructions: - Always applicable if about the queried domain - Marked as "no" if not followed, not "n/a" - "n/a" verdicts: - Only used for completely different domains - Do not affect the final score calculation ### Score interpretation (0 to scale, default 0-1) - 1.0: All applicable instructions followed perfectly - 0.7-0.9: Most applicable instructions followed - 0.4-0.6: Mixed compliance with applicable instructions - 0.1-0.3: Limited compliance with applicable instructions - 0.0: No applicable instructions followed ## Example with Analysis ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { PromptAlignmentMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new PromptAlignmentMetric(model, { instructions: [ "Use bullet points for each item", "Include exactly three examples", "End each point with a semicolon" ], scale: 1 }); const result = await metric.measure( "List three fruits", "• Apple is red and sweet; • Banana is yellow and curved; • Orange is citrus and round." ); // Example output: // { // score: 1.0, // info: { // reason: "The score is 1.0 because all instructions were followed exactly: // bullet points were used, exactly three examples were provided, and // each point ends with a semicolon." // } // } const result2 = await metric.measure( "List three fruits", "1. Apple 2. Banana 3. Orange and Grape" ); // Example output: // { // score: 0.33, // info: { // reason: "The score is 0.33 because: numbered lists were used instead of bullet points, // no semicolons were used, and four fruits were listed instead of exactly three." // } // } ``` ## Related ✅ - [Answer Relevancy Metric](./answer-relevancy) - [Keyword Coverage Metric](./keyword-coverage) --- title: "Reference: Summarization | Metrics | Evals | Kastrax Docs" description: Documentation for the Summarization Metric in Kastrax, which evaluates the quality of LLM-generated summaries for content and factual accuracy. --- # SummarizationMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/summarization , The `SummarizationMetric` evaluates how well an LLM's summary captures the original text's content while maintaining factual accuracy. It combines two aspects: alignment (factual correctness) and coverage (inclusion of key information), using the minimum scores to ensure both qualities are necessary for a good summary. ## Basic Usage ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { SummarizationMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new SummarizationMetric(model); const result = await metric.measure( "The company was founded in 1995 by John Smith. It started with 10 employees and grew to 500 by 2020. The company is based in Seattle.", "Founded in 1995 by John Smith, the company grew from 10 to 500 employees by 2020.", ); console.log(result.score); // Score from 0-1 console.log(result.info); // Object containing detailed metrics about the summary ``` ## Constructor Parameters ✅ ### SummarizationMetricOptions ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric evaluates summaries through two essential components: 1. **Alignment Score**: Measures factual correctness - Extracts claims from the summary - Verifies each claim against the original text - Assigns "yes", "no", or "unsure" verdicts 2. **Coverage Score**: Measures inclusion of key information - Generates key questions from the original text - Check if the summary answers these questions - Checks information inclusion and assesses comprehensiveness ### Scoring Process 1. Calculates alignment score: - Extracts claims from summary - Verifies against source text - Computes: `supported_claims / total_claims` 2. Determines coverage score: - Generates questions from source - Checks summary for answers - Evaluates completeness - Calculates: `answerable_questions / total_questions` Final score: `min(alignment_score, coverage_score) * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: Perfect summary - completely factual and covers all key information - 0.7-0.9: Strong summary with minor omissions or slight inaccuracies - 0.4-0.6: Moderate quality with significant gaps or inaccuracies - 0.1-0.3: Poor summary with major omissions or factual errors - 0.0: Invalid summary - either completely inaccurate or missing critical information ## Example with Analysis ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { SummarizationMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new SummarizationMetric(model); const result = await metric.measure( "The electric car company Tesla was founded in 2003 by Martin Eberhard and Marc Tarpenning. Elon Musk joined in 2004 as the largest investor and became CEO in 2008. The company's first car, the Roadster, was launched in 2008.", "Tesla, founded by Elon Musk in 2003, revolutionized the electric car industry starting with the Roadster in 2008.", ); // Example output: // { // score: 0.5, // info: { // reason: "The score is 0.5 because while the coverage is good (0.75) - mentioning the founding year, // first car model, and launch date - the alignment score is lower (0.5) due to incorrectly // attributing the company's founding to Elon Musk instead of Martin Eberhard and Marc Tarpenning. // The final score takes the minimum of these two scores to ensure both factual accuracy and // coverage are necessary for a good summary." // alignmentScore: 0.5, // coverageScore: 0.75, // } // } ``` ## Related ✅ - [Faithfulness Metric](./faithfulness) - [Completeness Metric](./completeness) - [Contextual Recall Metric](./contextual-recall) - [Hallucination Metric](./hallucination) --- title: "Reference: Textual Difference | Evals | Kastrax Docs" description: Documentation for the Textual Difference Metric in Kastrax, which measures textual differences between strings using sequence matching. --- # TextualDifferenceMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/textual-difference The `TextualDifferenceMetric` class uses sequence matching to measure the textual differences between two strings. It provides detailed information about changes, including the number of operations needed to transform one text into another. ## Basic Usage ✅ ```typescript import { TextualDifferenceMetric } from "@kastrax/evals/nlp"; const metric = new TextualDifferenceMetric(); const result = await metric.measure( "The quick brown fox", "The fast brown fox" ); console.log(result.score); // Similarity ratio from 0-1 console.log(result.info); // Detailed change metrics ``` ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric calculates several measures: - **Similarity Ratio**: Based on sequence matching between texts (0-1) - **Changes**: Count of non-matching operations needed - **Length Difference**: Normalized difference in text lengths - **Confidence**: Inversely proportional to length difference ### Scoring Process 1. Analyzes textual differences: - Performs sequence matching between input and output - Counts the number of change operations required - Measures length differences 2. Calculates metrics: - Computes similarity ratio - Determines confidence score - Combines into weighted score Final score: `(similarity_ratio * confidence) * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: Identical texts - no differences - 0.7-0.9: Minor differences - few changes needed - 0.4-0.6: Moderate differences - significant changes - 0.1-0.3: Major differences - extensive changes - 0.0: Completely different texts ## Example with Analysis ✅ ```typescript import { TextualDifferenceMetric } from "@kastrax/evals/nlp"; const metric = new TextualDifferenceMetric(); const result = await metric.measure( "Hello world! How are you?", "Hello there! How is it going?" ); // Example output: // { // score: 0.65, // info: { // confidence: 0.95, // ratio: 0.65, // changes: 2, // lengthDiff: 0.05 // } // } ``` ## Related ✅ - [Content Similarity Metric](./content-similarity) - [Completeness Metric](./completeness) - [Keyword Coverage Metric](./keyword-coverage) --- title: "Reference: Tone Consistency | Metrics | Evals | Kastrax Docs" description: Documentation for the Tone Consistency Metric in Kastrax, which evaluates emotional tone and sentiment consistency in text. --- # ToneConsistencyMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/tone-consistency The `ToneConsistencyMetric` class evaluates the text's emotional tone and sentiment consistency. It can operate in two modes: comparing tone between input/output pairs or analyzing tone stability within a single text. ## Basic Usage ✅ ```typescript import { ToneConsistencyMetric } from "@kastrax/evals/nlp"; const metric = new ToneConsistencyMetric(); // Compare tone between input and output const result1 = await metric.measure( "I love this amazing product!", "This product is wonderful and fantastic!" ); // Analyze tone stability in a single text const result2 = await metric.measure( "The service is excellent. The staff is friendly. The atmosphere is perfect.", "" // Empty string for single-text analysis ); console.log(result1.score); // Tone consistency score from 0-1 console.log(result2.score); // Tone stability score from 0-1 ``` ## measure() Parameters ✅ ## Returns ✅ ### info Object (Tone Comparison) ### info Object (Tone Stability) ## Scoring Details ✅ The metric evaluates sentiment consistency through tone pattern analysis and mode-specific scoring. ### Scoring Process 1. Analyzes tone patterns: - Extracts sentiment features - Computes sentiment scores - Measures tone variations 2. Calculates mode-specific score: **Tone Consistency** (input and output): - Compares sentiment between texts - Calculates sentiment difference - Score = 1 - (sentiment_difference / max_difference) **Tone Stability** (single input): - Analyzes sentiment across sentences - Calculates sentiment variance - Score = 1 - (sentiment_variance / max_variance) Final score: `mode_specific_score * scale` ### Score interpretation (0 to scale, default 0-1) - 1.0: Perfect tone consistency/stability - 0.7-0.9: Strong consistency with minor variations - 0.4-0.6: Moderate consistency with noticeable shifts - 0.1-0.3: Poor consistency with major tone changes - 0.0: No consistency - completely different tones ## Example with Both Modes ✅ ```typescript import { ToneConsistencyMetric } from "@kastrax/evals/nlp"; const metric = new ToneConsistencyMetric(); // Tone Consistency Mode const consistencyResult = await metric.measure( "This product is fantastic and amazing!", "The product is excellent and wonderful!" ); // Example output: // { // score: 0.95, // info: { // responseSentiment: 0.8, // referenceSentiment: 0.75, // difference: 0.05 // } // } // Tone Stability Mode const stabilityResult = await metric.measure( "Great service! Friendly staff. Perfect atmosphere.", "" ); // Example output: // { // score: 0.9, // info: { // avgSentiment: 0.6, // sentimentVariance: 0.1 // } // } ``` ## Related ✅ - [Content Similarity Metric](./content-similarity) - [Toxicity Metric](./toxicity) --- title: "Reference: Toxicity | Metrics | Evals | Kastrax Docs" description: Documentation for the Toxicity Metric in Kastrax, which evaluates LLM outputs for racist, biased, or toxic elements. --- # ToxicityMetric ✅ [EN] Source: https://kastrax.ai/en/reference/evals/toxicity The `ToxicityMetric` class evaluates whether an LLM's output contains racist, biased, or toxic elements. It uses a judge-based system to analyze responses for various forms of toxicity including personal attacks, mockery, hate speech, dismissive statements, and threats. ## Basic Usage ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { ToxicityMetric } from "@kastrax/evals/llm"; // Configure the model for evaluation const model = openai("gpt-4o-mini"); const metric = new ToxicityMetric(model, { scale: 1, // Default scale is 0-1 }); const result = await metric.measure( "How is Sarah as a person?", "Sarah is a dedicated team member who always delivers quality work.", ); console.log(result.score); // Score from 0-1 (0 = not toxic, 1 = toxic) console.log(result.info.reason); // Explanation of the toxicity assessment ``` ## Constructor Parameters ✅ ### ToxicityMetricOptions ## measure() Parameters ✅ ## Returns ✅ ## Scoring Details ✅ The metric evaluates toxicity through multiple aspects: - Personal attacks - Mockery or sarcasm - Hate speech - Dismissive statements - Threats or intimidation ### Scoring Process 1. Analyzes toxic elements: - Identifies personal attacks and mockery - Detects hate speech and threats - Evaluates dismissive statements - Assesses severity levels 2. Calculates toxicity score: - Weighs detected elements - Combines severity ratings - Normalizes to scale Final score: `(toxicity_weighted_sum / max_toxicity) * scale` ### Score interpretation (0 to scale, default 0-1) - 0.8-1.0: Severe toxicity - 0.4-0.7: Moderate toxicity - 0.1-0.3: Mild toxicity - 0.0: No toxic elements detected ## Example with Custom Configuration ✅ ```typescript import { openai } from "@ai-sdk/openai"; const model = openai("gpt-4o-mini"); const metric = new ToxicityMetric(model, { scale: 10, // Use 0-10 scale instead of 0-1 }); const result = await metric.measure( "What do you think about the new team member?", "The new team member shows promise but needs significant improvement in basic skills.", ); ``` ## Related ✅ - [Tone Consistency Metric](./tone-consistency) - [Bias Metric](./bias) --- title: "API Reference" description: "Kastrax API Reference" --- import { ReferenceCards } from "@/components/reference-cards"; # Reference [EN] Source: https://kastrax.ai/en/reference The Reference section provides documentation of Kastrax's API, including parameters, types and usage examples. # Memory Class Reference ✅ [EN] Source: https://kastrax.ai/en/reference/memory/Memory The `Memory` class provides a robust system for managing conversation history and thread-based message storage in Kastrax. It enables persistent storage of conversations, semantic search capabilities, and efficient message retrieval. By default, it uses LibSQL for storage and vector search, and FastEmbed for embeddings. ## Basic Usage ✅ ```typescript copy showLineNumbers import { Memory } from "@kastrax/memory"; import { Agent } from "@kastrax/core/agent"; const agent = new Agent({ memory: new Memory(), ...otherOptions, }); ``` ## Custom Configuration ✅ ```typescript copy showLineNumbers import { Memory } from "@kastrax/memory"; import { LibSQLStore, LibSQLVector } from "@kastrax/libsql"; import { Agent } from "@kastrax/core/agent"; const memory = new Memory({ // Optional storage configuration - libsql will be used by default storage: new LibSQLStore({ url: "file:./memory.db", }), // Optional vector database for semantic search - libsql will be used by default vector: new LibSQLVector({ url: "file:./vector.db", }), // Memory configuration options options: { // Number of recent messages to include lastMessages: 20, // Semantic search configuration semanticRecall: { topK: 3, // Number of similar messages to retrieve messageRange: { // Messages to include around each result before: 2, after: 1, }, }, // Working memory configuration workingMemory: { enabled: true, template: ` # User ✅ - First Name: - Last Name: `, }, }, }); const agent = new Agent({ memory, ...otherOptions, }); ``` ### Working Memory The working memory feature allows agents to maintain persistent information across conversations. When enabled, the Memory class will automatically manage working memory updates through either text stream tags or tool calls. There are two modes for handling working memory updates: 1. **text-stream** (default): The agent includes working memory updates directly in its responses using XML tags containing Markdown (`# User \n ## Preferences...`). These tags are automatically processed and stripped from the visible output. 2. **tool-call**: The agent uses a dedicated tool to update working memory. This mode should be used when working with `toDataStream()` as text-stream mode is not compatible with data streaming. Additionally, this mode provides more explicit control over memory updates and may be preferred when working with agents that are better at using tools than managing text tags. Example configuration: ```typescript copy showLineNumbers const memory = new Memory({ options: { workingMemory: { enabled: true, template: "# User\n- **First Name**:\n- **Last Name**:", use: "tool-call", // or 'text-stream' }, }, }); ``` If no template is provided, the Memory class uses a default template that includes fields for user details, preferences, goals, and other contextual information in Markdown format. See the [Working Memory guide](/docs/memory/working-memory.mdx#designing-effective-templates) for detailed usage examples and best practices. ### embedder By default, Memory uses FastEmbed with the `bge-small-en-v1.5` model, which provides a good balance of performance and model size (~130MB). You only need to specify an embedder if you want to use a different model or provider. For environments where local embedding isn't supported, you can use an API-based embedder: ```typescript {6} import { Memory } from "@kastrax/memory"; import { openai } from "@ai-sdk/openai"; const agent = new Agent({ memory: new Memory({ embedder: openai.embedding("text-embedding-3-small"), // Adds network request }), }); ``` Kastrax supports many embedding models through the [Vercel AI SDK](https://sdk.vercel.ai/docs/ai-sdk-core/embeddings), including options from OpenAI, Google, Mistral, and Cohere. ## Parameters ✅ ### options ### Related - [Getting Started with Memory](/docs/memory/overview.mdx) - [Semantic Recall](/docs/memory/semantic-recall.mdx) - [Working Memory](/docs/memory/working-memory.mdx) - [Memory Processors](/docs/memory/memory-processors.mdx) - [createThread](/reference/memory/createThread.mdx) - [query](/reference/memory/query.mdx) - [getThreadById](/reference/memory/getThreadById.mdx) - [getThreadsByResourceId](/reference/memory/getThreadsByResourceId.mdx) # createThread ✅ [EN] Source: https://kastrax.ai/en/reference/memory/createThread Creates a new conversation thread in the memory system. Each thread represents a distinct conversation or context and can contain multiple messages. ## Usage Example ✅ ```typescript import { Memory } from "@kastrax/memory"; const memory = new Memory({ /* config */ }); const thread = await memory.createThread({ resourceId: "user-123", title: "Support Conversation", metadata: { category: "support", priority: "high" } }); ``` ## Parameters ✅ ", description: "Optional metadata to associate with the thread", isOptional: true, }, ]} /> ## Returns ✅ ", description: "Additional metadata associated with the thread", }, ]} /> ### Related - [Memory Class Reference](/reference/memory/Memory.mdx) - [Getting Started with Memory](/docs/memory/overview.mdx) (Covers threads concept) - [getThreadById](/reference/memory/getThreadById.mdx) - [getThreadsByResourceId](/reference/memory/getThreadsByResourceId.mdx) - [query](/reference/memory/query.mdx) # getThreadById Reference ✅ [EN] Source: https://kastrax.ai/en/reference/memory/getThreadById The `getThreadById` function retrieves a specific thread by its ID from storage. ## Usage Example ✅ ```typescript import { Memory } from "@kastrax/core/memory"; const memory = new Memory(config); const thread = await memory.getThreadById({ threadId: "thread-123" }); ``` ## Parameters ✅ ## Returns ✅ ### Related - [Memory Class Reference](/reference/memory/Memory.mdx) - [Getting Started with Memory](/docs/memory/overview.mdx) (Covers threads concept) - [createThread](/reference/memory/createThread.mdx) - [getThreadsByResourceId](/reference/memory/getThreadsByResourceId.mdx) # getThreadsByResourceId Reference ✅ [EN] Source: https://kastrax.ai/en/reference/memory/getThreadsByResourceId The `getThreadsByResourceId` function retrieves all threads associated with a specific resource ID from storage. ## Usage Example ✅ ```typescript import { Memory } from "@kastrax/core/memory"; const memory = new Memory(config); const threads = await memory.getThreadsByResourceId({ resourceId: "resource-123", }); ``` ## Parameters ✅ ## Returns ✅ ### Related - [Memory Class Reference](/reference/memory/Memory.mdx) - [Getting Started with Memory](/docs/memory/overview.mdx) (Covers threads/resources concept) - [createThread](/reference/memory/createThread.mdx) - [getThreadById](/reference/memory/getThreadById.mdx) # query ✅ [EN] Source: https://kastrax.ai/en/reference/memory/query Retrieves messages from a specific thread, with support for pagination and filtering options. ## Usage Example ✅ ```typescript import { Memory } from "@kastrax/memory"; const memory = new Memory({ /* config */ }); // Get last 50 messages const { messages, uiMessages } = await memory.query({ threadId: "thread-123", selectBy: { last: 50, }, }); // Get messages with context around specific messages const { messages: contextMessages } = await memory.query({ threadId: "thread-123", selectBy: { include: [ { id: "msg-123", // Get just this message (no context) }, { id: "msg-456", // Get this message with custom context withPreviousMessages: 3, // 3 messages before withNextMessages: 1, // 1 message after }, ], }, }); // Semantic search in messages const { messages } = await memory.query({ threadId: "thread-123", selectBy: { vectorSearchString: "What was discussed about deployment?", }, threadConfig: { historySearch: true, }, }); ``` ## Parameters ✅ ### selectBy ### include ## Returns ✅ ## Additional Notes ✅ The `query` function returns two different message formats: - `messages`: Core message format used internally - `uiMessages`: Formatted messages suitable for UI display, including proper threading of tool calls and results ### Related - [Memory Class Reference](/reference/memory/Memory.mdx) - [Getting Started with Memory](/docs/memory/overview.mdx) - [Semantic Recall](/docs/memory/semantic-recall.mdx) - [createThread](/reference/memory/createThread.mdx) --- title: 'AgentNetwork (Experimental)' description: 'Reference documentation for the AgentNetwork class' --- # AgentNetwork (Experimental) ✅ [EN] Source: https://kastrax.ai/en/reference/networks/agent-network > **Note:** The AgentNetwork feature is experimental and may change in future releases. The `AgentNetwork` class provides a way to create a network of specialized agents that can collaborate to solve complex tasks. Unlike Workflows, which require explicit control over execution paths, AgentNetwork uses an LLM-based router to dynamically determine which agent to call next. ## Key Concepts ✅ - **LLM-based Routing**: AgentNetwork exclusively uses an LLM to figure out the best way to use your agents - **Agent Collaboration**: Multiple specialized agents can work together to solve complex tasks - **Dynamic Decision Making**: The router decides which agent to call based on the task requirements ## Usage ✅ ```typescript import { AgentNetwork } from '@kastrax/core/network'; import { openai } from '@kastrax/openai'; // Create specialized agents const webSearchAgent = new Agent({ name: 'Web Search Agent', instructions: 'You search the web for information.', model: openai('gpt-4o'), tools: { /* web search tools */ }, }); const dataAnalysisAgent = new Agent({ name: 'Data Analysis Agent', instructions: 'You analyze data and provide insights.', model: openai('gpt-4o'), tools: { /* data analysis tools */ }, }); // Create the network const researchNetwork = new AgentNetwork({ name: 'Research Network', instructions: 'Coordinate specialized agents to research topics thoroughly.', model: openai('gpt-4o'), agents: [webSearchAgent, dataAnalysisAgent], }); // Use the network const result = await researchNetwork.generate('Research the impact of climate change on agriculture'); console.log(result.text); ``` ## Constructor ✅ ```typescript constructor(config: AgentNetworkConfig) ``` ### Parameters - `config`: Configuration object for the AgentNetwork - `name`: Name of the network - `instructions`: Instructions for the routing agent - `model`: Language model to use for routing - `agents`: Array of specialized agents in the network ## Methods ✅ ### generate() Generates a response using the agent network. This method has replaced the deprecated `run()` method for consistency with the rest of the codebase. ```typescript async generate( messages: string | string[] | CoreMessage[], args?: AgentGenerateOptions ): Promise ``` ### stream() Streams a response using the agent network. ```typescript async stream( messages: string | string[] | CoreMessage[], args?: AgentStreamOptions ): Promise ``` ### getRoutingAgent() Returns the routing agent used by the network. ```typescript getRoutingAgent(): Agent ``` ### getAgents() Returns the array of specialized agents in the network. ```typescript getAgents(): Agent[] ``` ### getAgentHistory() Returns the history of interactions for a specific agent. ```typescript getAgentHistory(agentId: string): Array<{ input: string; output: string; timestamp: string; }> ``` ### getAgentInteractionHistory() Returns the history of all agent interactions that have occurred in the network. ```typescript getAgentInteractionHistory(): Record< string, Array<{ input: string; output: string; timestamp: string; }> > ``` ### getAgentInteractionSummary() Returns a formatted summary of agent interactions in chronological order. ```typescript getAgentInteractionSummary(): string ``` ## When to Use AgentNetwork vs Workflows ✅ - **Use AgentNetwork when:** You want the AI to figure out the best way to use your agents, with dynamic routing based on the task requirements. - **Use Workflows when:** You need explicit control over execution paths, with predetermined sequences of agent calls and conditional logic. ## Internal Tools ✅ The AgentNetwork uses a special `transmit` tool that allows the routing agent to call specialized agents. This tool handles: - Single agent calls - Multiple parallel agent calls - Context sharing between agents ## Limitations ✅ - The AgentNetwork approach may use more tokens than a well-designed Workflow for the same task - Debugging can be more challenging as the routing decisions are made by the LLM - Performance may vary based on the quality of the routing instructions and the capabilities of the specialized agents --- title: "Reference: createLogger() | Kastrax Observability Docs" description: Documentation for the createLogger function, which instantiates a logger based on a given configuration. --- # createLogger() ✅ [EN] Source: https://kastrax.ai/en/reference/observability/create-logger The `createLogger()` function is used to instantiate a logger based on a given configuration. You can create console-based, file-based, or Upstash Redis-based loggers by specifying the type and any additional parameters relevant to that type. ### Usage #### Console Logger (Development) ```typescript showLineNumbers copy const consoleLogger = createLogger({ name: "Kastrax", level: "debug" }); consoleLogger.info("App started"); ``` #### File Transport (Structured Logs) ```typescript showLineNumbers copy import { FileTransport } from "@kastrax/loggers/file"; const fileLogger = createLogger({ name: "Kastrax", transports: { file: new FileTransport({ path: "test-dir/test.log" }) }, level: "warn", }); fileLogger.warn("Low disk space", { destinationPath: "system", type: "WORKFLOW", }); ``` #### Upstash Logger (Remote Log Drain) ```typescript showLineNumbers copy import { UpstashTransport } from "@kastrax/loggers/upstash"; const logger = createLogger({ name: "Kastrax", transports: { upstash: new UpstashTransport({ listName: "production-logs", upstashUrl: process.env.UPSTASH_URL!, upstashToken: process.env.UPSTASH_TOKEN!, }), }, level: "info", }); logger.info({ message: "User signed in", destinationPath: "auth", type: "AGENT", runId: "run_123", }); ``` ### Parameters --- title: "Reference: Logger Instance | Kastrax Observability Docs" description: Documentation for Logger instances, which provide methods to record events at various severity levels. --- # Logger Instance [EN] Source: https://kastrax.ai/en/reference/observability/logger A Logger instance is created by `createLogger()` and provides methods to record events at various severity levels. Depending on the logger type, messages may be written to the console, file, or an external service. ## Example ```typescript showLineNumbers copy // Using a console logger const logger = createLogger({ name: 'Kastrax', level: 'info' }); logger.debug('Debug message'); // Won't be logged because level is INFO logger.info({ message: 'User action occurred', destinationPath: 'user-actions', type: 'AGENT' }); // Logged logger.error('An error occurred'); // Logged as ERROR ``` ## Methods void | Promise', description: 'Write a DEBUG-level log. Only recorded if level ≤ DEBUG.', }, { name: 'info', type: '(message: BaseLogMessage | string, ...args: any[]) => void | Promise', description: 'Write an INFO-level log. Only recorded if level ≤ INFO.', }, { name: 'warn', type: '(message: BaseLogMessage | string, ...args: any[]) => void | Promise', description: 'Write a WARN-level log. Only recorded if level ≤ WARN.', }, { name: 'error', type: '(message: BaseLogMessage | string, ...args: any[]) => void | Promise', description: 'Write an ERROR-level log. Only recorded if level ≤ ERROR.', }, { name: 'cleanup', type: '() => Promise', isOptional: true, description: 'Cleanup resources held by the logger (e.g., network connections for Upstash). Not all loggers implement this.', }, ]} /> **Note:** Some loggers require a `BaseLogMessage` object (with `message`, `destinationPath`, `type` fields). For instance, the `File` and `Upstash` loggers need structured messages. --- title: "Reference: OtelConfig | Kastrax Observability Docs" description: Documentation for the OtelConfig object, which configures OpenTelemetry instrumentation, tracing, and exporting behavior. --- # `OtelConfig` ✅ [EN] Source: https://kastrax.ai/en/reference/observability/otel-config The `OtelConfig` object is used to configure OpenTelemetry instrumentation, tracing, and exporting behavior within your application. By adjusting its properties, you can control how telemetry data (such as traces) is collected, sampled, and exported. To use the `OtelConfig` within Kastrax, pass it as the value of the `telemetry` key when initializing Kastrax. This will configure Kastrax to use your custom OpenTelemetry settings for tracing and instrumentation. ```typescript showLineNumbers copy import { Kastrax } from 'kastrax'; const otelConfig: OtelConfig = { serviceName: 'my-awesome-service', enabled: true, sampling: { type: 'ratio', probability: 0.5, }, export: { type: 'otlp', endpoint: 'https://otel-collector.example.com/v1/traces', headers: { Authorization: 'Bearer YOUR_TOKEN_HERE', }, }, }; ``` ### Properties ', isOptional: true, description: 'Additional headers to send with OTLP requests, useful for authentication or routing.', }, ], }, ]} /> --- title: "Reference: Braintrust | Observability | Kastrax Docs" description: Documentation for integrating Braintrust with Kastrax, an evaluation and monitoring platform for LLM applications. --- # Braintrust ✅ [EN] Source: https://kastrax.ai/en/reference/observability/providers/braintrust Braintrust is an evaluation and monitoring platform for LLM applications. ## Configuration ✅ To use Braintrust with Kastrax, configure these environment variables: ```env OTEL_EXPORTER_OTLP_ENDPOINT=https://api.braintrust.dev/otel OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer , x-bt-parent=project_id:" ``` ## Implementation ✅ Here's how to configure Kastrax to use Braintrust: ```typescript import { Kastrax } from "@kastrax/core"; export const kastrax = new Kastrax({ // ... other config telemetry: { serviceName: "your-service-name", enabled: true, export: { type: "otlp", }, }, }); ``` ## Dashboard ✅ Access your Braintrust dashboard at [braintrust.dev](https://www.braintrust.dev/) --- title: "Reference: Dash0 Integration | Kastrax Observability Docs" description: Documentation for integrating Kastrax with Dash0, an Open Telementry native observability solution. --- # Dash0 ✅ [EN] Source: https://kastrax.ai/en/reference/observability/providers/dash0 Dash0, an Open Telementry native observability solution that provides full-stack monitoring capabilities as well as integrations with other CNCF projects like Perses and Prometheus. ## Configuration ✅ To use Dash0 with Kastrax, configure these environment variables: ```env OTEL_EXPORTER_OTLP_ENDPOINT=https://ingress..dash0.com OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer , Dash0-Dataset= ``` ## Implementation ✅ Here's how to configure Kastrax to use Dash0: ```typescript import { Kastrax } from "@kastrax/core"; export const kastrax = new Kastrax({ // ... other config telemetry: { serviceName: "your-service-name", enabled: true, export: { type: "otlp", }, }, }); ``` ## Dashboard ✅ Access your Dash0 dashboards at [dash0.com](https://www.dash0.com/) and find out how to do more [Distributed Tracing](https://www.dash0.com/distributed-tracing) integrations in the [Dash0 Integration Hub](https://www.dash0.com/hub/integrations) --- title: "Reference: Provider List | Observability | Kastrax Docs" description: Overview of observability providers supported by Kastrax, including Dash0, SigNoz, Braintrust, Langfuse, and more. --- # Observability Providers [EN] Source: https://kastrax.ai/en/reference/observability/providers Observability providers include: - [Braintrust](./providers/braintrust.mdx) - [Dash0](./providers/dash0.mdx) - [Laminar](./providers/laminar.mdx) - [Langfuse](./providers/langfuse.mdx) - [Langsmith](./providers/langsmith.mdx) - [New Relic](./providers/new-relic.mdx) - [SigNoz](./providers/signoz.mdx) - [Traceloop](./providers/traceloop.mdx) --- title: "Reference: Laminar Integration | Kastrax Observability Docs" description: Documentation for integrating Laminar with Kastrax, a specialized observability platform for LLM applications. --- # Laminar ✅ [EN] Source: https://kastrax.ai/en/reference/observability/providers/laminar Laminar is a specialized observability platform for LLM applications. ## Configuration ✅ To use Laminar with Kastrax, configure these environment variables: ```env OTEL_EXPORTER_OTLP_ENDPOINT=https://api.lmnr.ai:8443 OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer your_api_key, x-laminar-team-id=your_team_id" ``` ## Implementation ✅ Here's how to configure Kastrax to use Laminar: ```typescript import { Kastrax } from "@kastrax/core"; export const kastrax = new Kastrax({ // ... other config telemetry: { serviceName: "your-service-name", enabled: true, export: { type: "otlp", protocol: "grpc", }, }, }); ``` ## Dashboard ✅ Access your Laminar dashboard at [https://lmnr.ai/](https://lmnr.ai/) --- title: "Reference: Langfuse Integration | Kastrax Observability Docs" description: Documentation for integrating Langfuse with Kastrax, an open-source observability platform for LLM applications. --- # Langfuse ✅ [EN] Source: https://kastrax.ai/en/reference/observability/providers/langfuse Langfuse is an open-source observability platform designed specifically for LLM applications. > **Note**: Currently, only AI-related calls will contain detailed telemetry data. Other operations will create traces but with limited information. ## Configuration ✅ To use Langfuse with Kastrax, you'll need to configure the following environment variables: ```env LANGFUSE_PUBLIC_KEY=your_public_key LANGFUSE_SECRET_KEY=your_secret_key LANGFUSE_BASEURL=https://cloud.langfuse.com # Optional - defaults to cloud.langfuse.com ``` **Important**: When configuring the telemetry export settings, the `traceName` parameter must be set to `"ai"` for the Langfuse integration to work properly. ## Implementation ✅ Here's how to configure Kastrax to use Langfuse: ```typescript import { Kastrax } from "@kastrax/core"; import { LangfuseExporter } from "langfuse-vercel"; export const kastrax = new Kastrax({ // ... other config telemetry: { serviceName: "ai", // this must be set to "ai" so that the LangfuseExporter thinks it's an AI SDK trace enabled: true, export: { type: "custom", exporter: new LangfuseExporter({ publicKey: process.env.LANGFUSE_PUBLIC_KEY, secretKey: process.env.LANGFUSE_SECRET_KEY, baseUrl: process.env.LANGFUSE_BASEURL, }), }, }, }); ``` ## Dashboard ✅ Once configured, you can view your traces and analytics in the Langfuse dashboard at [cloud.langfuse.com](https://cloud.langfuse.com) --- title: "Reference: LangSmith Integration | Kastrax Observability Docs" description: Documentation for integrating LangSmith with Kastrax, a platform for debugging, testing, evaluating, and monitoring LLM applications. --- # LangSmith ✅ [EN] Source: https://kastrax.ai/en/reference/observability/providers/langsmith LangSmith is LangChain's platform for debugging, testing, evaluating, and monitoring LLM applications. > **Note**: Currently, this integration only traces AI-related calls in your application. Other types of operations are not captured in the telemetry data. ## Configuration ✅ To use LangSmith with Kastrax, you'll need to configure the following environment variables: ```env LANGSMITH_TRACING=true LANGSMITH_ENDPOINT=https://api.smith.langchain.com LANGSMITH_API_KEY=your-api-key LANGSMITH_PROJECT=your-project-name ``` ## Implementation ✅ Here's how to configure Kastrax to use LangSmith: ```typescript import { Kastrax } from "@kastrax/core"; import { AISDKExporter } from "langsmith/vercel"; export const kastrax = new Kastrax({ // ... other config telemetry: { serviceName: "your-service-name", enabled: true, export: { type: "custom", exporter: new AISDKExporter(), }, }, }); ``` ## Dashboard ✅ Access your traces and analytics in the LangSmith dashboard at [smith.langchain.com](https://smith.langchain.com) > **Note**: Even if you run your workflows, you may not see data appearing in a new project. You will need to sort by Name column to see all projects, select your project, then filter by LLM Calls instead of Root Runs. --- title: "Reference: LangWatch Integration | Kastrax Observability Docs" description: Documentation for integrating LangWatch with Kastrax, a specialized observability platform for LLM applications. --- # LangWatch ✅ [EN] Source: https://kastrax.ai/en/reference/observability/providers/langwatch LangWatch is a specialized observability platform for LLM applications. ## Configuration ✅ To use LangWatch with Kastrax, configure these environment variables: ```env LANGWATCH_API_KEY=your_api_key LANGWATCH_PROJECT_ID=your_project_id ``` ## Implementation ✅ Here's how to configure Kastrax to use LangWatch: ```typescript import { Kastrax } from "@kastrax/core"; import { LangWatchExporter } from "langwatch"; export const kastrax = new Kastrax({ // ... other config telemetry: { serviceName: "your-service-name", enabled: true, export: { type: "custom", exporter: new LangWatchExporter({ apiKey: process.env.LANGWATCH_API_KEY, projectId: process.env.LANGWATCH_PROJECT_ID, }), }, }, }); ``` ## Dashboard ✅ Access your LangWatch dashboard at [app.langwatch.ai](https://app.langwatch.ai) --- title: "Reference: New Relic Integration | Kastrax Observability Docs" description: Documentation for integrating New Relic with Kastrax, a comprehensive observability platform supporting OpenTelemetry for full-stack monitoring. --- # New Relic ✅ [EN] Source: https://kastrax.ai/en/reference/observability/providers/new-relic New Relic is a comprehensive observability platform that supports OpenTelemetry (OTLP) for full-stack monitoring. ## Configuration ✅ To use New Relic with Kastrax via OTLP, configure these environment variables: ```env OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.nr-data.net:4317 OTEL_EXPORTER_OTLP_HEADERS="api-key=your_license_key" ``` ## Implementation ✅ Here's how to configure Kastrax to use New Relic: ```typescript import { Kastrax } from "@kastrax/core"; export const kastrax = new Kastrax({ // ... other config telemetry: { serviceName: "your-service-name", enabled: true, export: { type: "otlp", }, }, }); ``` ## Dashboard ✅ View your telemetry data in the New Relic One dashboard at [one.newrelic.com](https://one.newrelic.com) --- title: "Reference: SigNoz Integration | Kastrax Observability Docs" description: Documentation for integrating SigNoz with Kastrax, an open-source APM and observability platform providing full-stack monitoring through OpenTelemetry. --- # SigNoz ✅ [EN] Source: https://kastrax.ai/en/reference/observability/providers/signoz SigNoz is an open-source APM and observability platform that provides full-stack monitoring capabilities through OpenTelemetry. ## Configuration ✅ To use SigNoz with Kastrax, configure these environment variables: ```env OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.{region}.signoz.cloud:443 OTEL_EXPORTER_OTLP_HEADERS=signoz-ingestion-key=your_signoz_token ``` ## Implementation ✅ Here's how to configure Kastrax to use SigNoz: ```typescript import { Kastrax } from "@kastrax/core"; export const kastrax = new Kastrax({ // ... other config telemetry: { serviceName: "your-service-name", enabled: true, export: { type: "otlp", }, }, }); ``` ## Dashboard ✅ Access your SigNoz dashboard at [signoz.io](https://signoz.io/) --- title: "Reference: Traceloop Integration | Kastrax Observability Docs" description: Documentation for integrating Traceloop with Kastrax, an OpenTelemetry-native observability platform for LLM applications. --- # Traceloop ✅ [EN] Source: https://kastrax.ai/en/reference/observability/providers/traceloop Traceloop is an OpenTelemetry-native observability platform specifically designed for LLM applications. ## Configuration ✅ To use Traceloop with Kastrax, configure these environment variables: ```env OTEL_EXPORTER_OTLP_ENDPOINT=https://api.traceloop.com OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer your_api_key, x-traceloop-destination-id=your_destination_id" ``` ## Implementation ✅ Here's how to configure Kastrax to use Traceloop: ```typescript import { Kastrax } from "@kastrax/core"; export const kastrax = new Kastrax({ // ... other config telemetry: { serviceName: "your-service-name", enabled: true, export: { type: "otlp", }, }, }); ``` ## Dashboard ✅ Access your traces and analytics in the Traceloop dashboard at [app.traceloop.com](https://app.traceloop.com) --- title: "Reference: Astra Vector Store | Vector Databases | RAG | Kastrax Docs" description: Documentation for the AstraVector class in Kastrax, which provides vector search using DataStax Astra DB. --- # Astra Vector Store [EN] Source: https://kastrax.ai/en/reference/rag/astra The AstraVector class provides vector search using [DataStax Astra DB](https://www.datastax.com/products/datastax-astra), a cloud-native, serverless database built on Apache Cassandra. It provides vector search capabilities with enterprise-grade scalability and high availability. ## Constructor Options ## Methods ### createIndex() ### upsert() []", isOptional: true, description: "Metadata for each vector", }, { name: "ids", type: "string[]", isOptional: true, description: "Optional vector IDs (auto-generated if not provided)", }, ]} /> ### query() ", isOptional: true, description: "Metadata filters for the query", }, { name: "includeVector", type: "boolean", isOptional: true, defaultValue: "false", description: "Whether to include vectors in the results", }, ]} /> ### listIndexes() Returns an array of index names as strings. ### describeIndex() Returns: ```typescript copy interface IndexStats { dimension: number; count: number; metric: "cosine" | "euclidean" | "dotproduct"; } ``` ### deleteIndex() ### updateIndexById() ", isOptional: true, description: "New metadata values", }, ], }, ]} /> ### deleteIndexById() ## Response Types Query results are returned in this format: ```typescript copy interface QueryResult { id: string; score: number; metadata: Record; vector?: number[]; // Only included if includeVector is true } ``` ## Error Handling The store throws typed errors that can be caught: ```typescript copy try { await store.query({ indexName: "index_name", queryVector: queryVector, }); } catch (error) { if (error instanceof VectorStoreError) { console.log(error.code); // 'connection_failed' | 'invalid_dimension' | etc console.log(error.details); // Additional error context } } ``` ## Environment Variables Required environment variables: - `ASTRA_DB_TOKEN`: Your Astra DB API token - `ASTRA_DB_ENDPOINT`: Your Astra DB API endpoint ## Related - [Metadata Filters](./metadata-filters) --- title: "Reference: Chroma Vector Store | Vector Databases | RAG | Kastrax Docs" description: Documentation for the ChromaVector class in Kastrax, which provides vector search using ChromaDB. --- # Chroma Vector Store [EN] Source: https://kastrax.ai/en/reference/rag/chroma The ChromaVector class provides vector search using [ChromaDB](https://www.trychroma.com/), an open-source embedding database. It offers efficient vector search with metadata filtering and hybrid search capabilities. ## Constructor Options ### auth ## Methods ### createIndex() ### upsert() []", isOptional: true, description: "Metadata for each vector", }, { name: "ids", type: "string[]", isOptional: true, description: "Optional vector IDs (auto-generated if not provided)", }, { name: "documents", type: "string[]", isOptional: true, description: "Chroma-specific: Original text documents associated with the vectors", }, ]} /> ### query() ", isOptional: true, description: "Metadata filters for the query", }, { name: "includeVector", type: "boolean", isOptional: true, defaultValue: "false", description: "Whether to include vectors in the results", }, { name: "documentFilter", type: "Record", isOptional: true, description: "Chroma-specific: Filter to apply on the document content", }, ]} /> ### listIndexes() Returns an array of index names as strings. ### describeIndex() Returns: ```typescript copy interface IndexStats { dimension: number; count: number; metric: "cosine" | "euclidean" | "dotproduct"; } ``` ### deleteIndex() ### updateIndexById() The `update` object can contain: ", isOptional: true, description: "New metadata to replace the existing metadata", }, ]} /> ### deleteIndexById() ## Response Types Query results are returned in this format: ```typescript copy interface QueryResult { id: string; score: number; metadata: Record; document?: string; // Chroma-specific: Original document if it was stored vector?: number[]; // Only included if includeVector is true } ``` ## Error Handling The store throws typed errors that can be caught: ```typescript copy try { await store.query({ indexName: "index_name", queryVector: queryVector, }); } catch (error) { if (error instanceof VectorStoreError) { console.log(error.code); // 'connection_failed' | 'invalid_dimension' | etc console.log(error.details); // Additional error context } } ``` ## Related - [Metadata Filters](./metadata-filters) --- title: "Reference: .chunk() | Document Processing | RAG | Kastrax Docs" description: Documentation for the chunk function in Kastrax, which splits documents into smaller segments using various strategies. --- # Reference: .chunk() ✅ [EN] Source: https://kastrax.ai/en/reference/rag/chunk The `.chunk()` function splits documents into smaller segments using various strategies and options. ## Example ✅ ```typescript import { MDocument } from '@kastrax/rag'; const doc = MDocument.fromMarkdown(` # Introduction ✅ This is a sample document that we want to split into chunks. ## Section 1 ✅ Here is the first section with some content. ## Section 2 ✅ Here is another section with different content. `); // Basic chunking with defaults const chunks = await doc.chunk(); // Markdown-specific chunking with header extraction const chunksWithMetadata = await doc.chunk({ strategy: 'markdown', headers: [['#', 'title'], ['##', 'section']], extract: { summary: true, // Extract summaries with default settings keywords: true // Extract keywords with default settings } }); ``` ## Parameters ✅ ## Strategy-Specific Options ✅ Strategy-specific options are passed as top-level parameters alongside the strategy parameter. For example: ```typescript showLineNumbers copy // HTML strategy example const chunks = await doc.chunk({ strategy: 'html', headers: [['h1', 'title'], ['h2', 'subtitle']], // HTML-specific option sections: [['div.content', 'main']], // HTML-specific option size: 500 // general option }); // Markdown strategy example const chunks = await doc.chunk({ strategy: 'markdown', headers: [['#', 'title'], ['##', 'section']], // Markdown-specific option stripHeaders: true, // Markdown-specific option overlap: 50 // general option }); // Token strategy example const chunks = await doc.chunk({ strategy: 'token', encodingName: 'gpt2', // Token-specific option modelName: 'gpt-3.5-turbo', // Token-specific option size: 1000 // general option }); ``` The options documented below are passed directly at the top level of the configuration object, not nested within a separate options object. ### HTML ", description: "Array of [selector, metadata key] pairs for header-based splitting", }, { name: "sections", type: "Array<[string, string]>", description: "Array of [selector, metadata key] pairs for section-based splitting", }, { name: "returnEachLine", type: "boolean", isOptional: true, description: "Whether to return each line as a separate chunk", }, ]} /> ### Markdown ", description: "Array of [header level, metadata key] pairs", }, { name: "stripHeaders", type: "boolean", isOptional: true, description: "Whether to remove headers from the output", }, { name: "returnEachLine", type: "boolean", isOptional: true, description: "Whether to return each line as a separate chunk", }, ]} /> ### Token ### JSON ## Return Value ✅ Returns a `MDocument` instance containing the chunked documents. Each chunk includes: ```typescript interface DocumentNode { text: string; metadata: Record; embedding?: number[]; } ``` --- title: "Reference: MDocument | Document Processing | RAG | Kastrax Docs" description: Documentation for the MDocument class in Kastrax, which handles document processing and chunking. --- # MDocument ✅ [EN] Source: https://kastrax.ai/en/reference/rag/document The MDocument class processes documents for RAG applications. The main methods are `.chunk()` and `.extractMetadata()`. ## Constructor ✅ }>", description: "Array of document chunks with their text content and optional metadata", }, { name: "type", type: "'text' | 'html' | 'markdown' | 'json' | 'latex'", description: "Type of document content", } ]} /> ## Static Methods ✅ ### fromText() Creates a document from plain text content. ```typescript static fromText(text: string, metadata?: Record): MDocument ``` ### fromHTML() Creates a document from HTML content. ```typescript static fromHTML(html: string, metadata?: Record): MDocument ``` ### fromMarkdown() Creates a document from Markdown content. ```typescript static fromMarkdown(markdown: string, metadata?: Record): MDocument ``` ### fromJSON() Creates a document from JSON content. ```typescript static fromJSON(json: string, metadata?: Record): MDocument ``` ## Instance Methods ✅ ### chunk() Splits document into chunks and optionally extracts metadata. ```typescript async chunk(params?: ChunkParams): Promise ``` See [chunk() reference](./chunk) for detailed options. ### getDocs() Returns array of processed document chunks. ```typescript getDocs(): Chunk[] ``` ### getText() Returns array of text strings from chunks. ```typescript getText(): string[] ``` ### getMetadata() Returns array of metadata objects from chunks. ```typescript getMetadata(): Record[] ``` ### extractMetadata() Extracts metadata using specified extractors. See [ExtractParams reference](./extract-params) for details. ```typescript async extractMetadata(params: ExtractParams): Promise ``` ## Examples ✅ ```typescript import { MDocument } from '@kastrax/rag'; // Create document from text const doc = MDocument.fromText('Your content here'); // Split into chunks with metadata extraction const chunks = await doc.chunk({ strategy: 'markdown', headers: [['#', 'title'], ['##', 'section']], extract: { summary: true, // Extract summaries with default settings keywords: true // Extract keywords with default settings } }); // Get processed chunks const docs = doc.getDocs(); const texts = doc.getText(); const metadata = doc.getMetadata(); ``` --- title: "Reference: embed() | Document Embedding | RAG | Kastrax Docs" description: Documentation for embedding functionality in Kastrax using the AI SDK. --- # Embed [EN] Source: https://kastrax.ai/en/reference/rag/embeddings Kastrax uses the AI SDK's `embed` and `embedMany` functions to generate vector embeddings for text inputs, enabling similarity search and RAG workflows. ## Single Embedding The `embed` function generates a vector embedding for a single text input: ```typescript import { embed } from 'ai'; const result = await embed({ model: openai.embedding('text-embedding-3-small'), value: "Your text to embed", maxRetries: 2 // optional, defaults to 2 }); ``` ### Parameters ", description: "The text content or object to embed" }, { name: "maxRetries", type: "number", description: "Maximum number of retries per embedding call. Set to 0 to disable retries.", isOptional: true, defaultValue: "2" }, { name: "abortSignal", type: "AbortSignal", description: "Optional abort signal to cancel the request", isOptional: true }, { name: "headers", type: "Record", description: "Additional HTTP headers for the request (only for HTTP-based providers)", isOptional: true } ]} /> ### Return Value ## Multiple Embeddings For embedding multiple texts at once, use the `embedMany` function: ```typescript import { embedMany } from 'ai'; const result = await embedMany({ model: openai.embedding('text-embedding-3-small'), values: ["First text", "Second text", "Third text"], maxRetries: 2 // optional, defaults to 2 }); ``` ### Parameters []", description: "Array of text content or objects to embed" }, { name: "maxRetries", type: "number", description: "Maximum number of retries per embedding call. Set to 0 to disable retries.", isOptional: true, defaultValue: "2" }, { name: "abortSignal", type: "AbortSignal", description: "Optional abort signal to cancel the request", isOptional: true }, { name: "headers", type: "Record", description: "Additional HTTP headers for the request (only for HTTP-based providers)", isOptional: true } ]} /> ### Return Value ## Example Usage ```typescript import { embed, embedMany } from 'ai'; import { openai } from '@ai-sdk/openai'; // Single embedding const singleResult = await embed({ model: openai.embedding('text-embedding-3-small'), value: "What is the meaning of life?", }); // Multiple embeddings const multipleResult = await embedMany({ model: openai.embedding('text-embedding-3-small'), values: [ "First question about life", "Second question about universe", "Third question about everything" ], }); ``` For more detailed information about embeddings in the Vercel AI SDK, see: - [AI SDK Embeddings Overview](https://sdk.vercel.ai/docs/ai-sdk-core/embeddings) - [embed()](https://sdk.vercel.ai/docs/reference/ai-sdk-core/embed) - [embedMany()](https://sdk.vercel.ai/docs/reference/ai-sdk-core/embed-many) --- title: "Reference: ExtractParams | Document Processing | RAG | Kastrax Docs" description: Documentation for metadata extraction configuration in Kastrax. --- # ExtractParams ✅ [EN] Source: https://kastrax.ai/en/reference/rag/extract-params ExtractParams configures metadata extraction from document chunks using LLM analysis. ## Example ✅ ```typescript showLineNumbers copy import { MDocument } from "@kastrax/rag"; const doc = MDocument.fromText(text); const chunks = await doc.chunk({ extract: { title: true, // Extract titles using default settings summary: true, // Generate summaries using default settings keywords: true // Extract keywords using default settings } }); // Example output: // chunks[0].metadata = { // documentTitle: "AI Systems Overview", // sectionSummary: "Overview of artificial intelligence concepts and applications", // excerptKeywords: "KEYWORDS: AI, machine learning, algorithms" // } ``` ## Parameters ✅ The `extract` parameter accepts the following fields: ## Extractor Arguments ✅ ### TitleExtractorsArgs ### SummaryExtractArgs ### QuestionAnswerExtractArgs ### KeywordExtractArgs ## Advanced Example ✅ ```typescript showLineNumbers copy import { MDocument } from "@kastrax/rag"; const doc = MDocument.fromText(text); const chunks = await doc.chunk({ extract: { // Title extraction with custom settings title: { nodes: 2, // Extract 2 title nodes nodeTemplate: "Generate a title for this: {context}", combineTemplate: "Combine these titles: {context}" }, // Summary extraction with custom settings summary: { summaries: ["self"], // Generate summaries for current chunk promptTemplate: "Summarize this: {context}" }, // Question generation with custom settings questions: { questions: 3, // Generate 3 questions promptTemplate: "Generate {numQuestions} questions about: {context}", embeddingOnly: false }, // Keyword extraction with custom settings keywords: { keywords: 5, // Extract 5 keywords promptTemplate: "Extract {maxKeywords} key terms from: {context}" } } }); // Example output: // chunks[0].metadata = { // documentTitle: "AI in Modern Computing", // sectionSummary: "Overview of AI concepts and their applications in computing", // questionsThisExcerptCanAnswer: "1. What is machine learning?\n2. How do neural networks work?", // excerptKeywords: "1. Machine learning\n2. Neural networks\n3. Training data" // } ``` ## Document Grouping for Title Extraction ✅ When using the `TitleExtractor`, you can group multiple chunks together for title extraction by specifying a shared `docId` in the `metadata` field of each chunk. All chunks with the same `docId` will receive the same extracted title. If no `docId` is set, each chunk is treated as its own document for title extraction. **Example:** ```ts import { MDocument } from "@kastrax/rag"; const doc = new MDocument({ docs: [ { text: "chunk 1", metadata: { docId: "docA" } }, { text: "chunk 2", metadata: { docId: "docA" } }, { text: "chunk 3", metadata: { docId: "docB" } }, ], type: "text", }); await doc.extractMetadata({ title: true }); // The first two chunks will share a title, while the third chunk will be assigned a separate title. ``` --- title: "Reference: GraphRAG | Graph-based RAG | RAG | Kastrax Docs" description: Documentation for the GraphRAG class in Kastrax, which implements a graph-based approach to retrieval augmented generation. --- # GraphRAG ✅ [EN] Source: https://kastrax.ai/en/reference/rag/graph-rag The `GraphRAG` class implements a graph-based approach to retrieval augmented generation. It creates a knowledge graph from document chunks where nodes represent documents and edges represent semantic relationships, enabling both direct similarity matching and discovery of related content through graph traversal. ## Basic Usage ✅ ```typescript import { GraphRAG } from "@kastrax/rag"; const graphRag = new GraphRAG({ dimension: 1536, threshold: 0.7 }); // Create the graph from chunks and embeddings graphRag.createGraph(documentChunks, embeddings); // Query the graph with embedding const results = await graphRag.query({ query: queryEmbedding, topK: 10, randomWalkSteps: 100, restartProb: 0.15 }); ``` ## Constructor Parameters ✅ ## Methods ✅ ### createGraph Creates a knowledge graph from document chunks and their embeddings. ```typescript createGraph(chunks: GraphChunk[], embeddings: GraphEmbedding[]): void ``` #### Parameters ### query Performs a graph-based search combining vector similarity and graph traversal. ```typescript query({ query, topK = 10, randomWalkSteps = 100, restartProb = 0.15 }: { query: number[]; topK?: number; randomWalkSteps?: number; restartProb?: number; }): RankedNode[] ``` #### Parameters #### Returns Returns an array of `RankedNode` objects, where each node contains: ", description: "Additional metadata associated with the chunk", }, { name: "score", type: "number", description: "Combined relevance score from graph traversal", } ]} /> ## Advanced Example ✅ ```typescript const graphRag = new GraphRAG({ dimension: 1536, threshold: 0.8 // Stricter similarity threshold }); // Create graph from chunks and embeddings graphRag.createGraph(documentChunks, embeddings); // Query with custom parameters const results = await graphRag.query({ query: queryEmbedding, topK: 5, randomWalkSteps: 200, restartProb: 0.2 }); ``` ## Related ✅ - [createGraphRAGTool](../tools/graph-rag-tool) --- title: "Default Vector Store | Vector Databases | RAG | Kastrax Docs" description: Documentation for the LibSQLVector class in Kastrax, which provides vector search using LibSQL with vector extensions. --- # LibSQLVector Store ✅ [EN] Source: https://kastrax.ai/en/reference/rag/libsql The LibSQL storage implementation provides a SQLite-compatible vector search [LibSQL](https://github.com/tursodatabase/libsql), a fork of SQLite with vector extensions, and [Turso](https://turso.tech/) with vector extensions, offering a lightweight and efficient vector database solution. It's part of the `@kastrax/core` package and offers efficient vector similarity search with metadata filtering. ## Installation ✅ Default vector store is included in the core package: ```bash copy npm install @kastrax/core@latest ``` ## Usage ✅ ```typescript copy showLineNumbers import { LibSQLVector } from "@kastrax/core/vector/libsql"; // Create a new vector store instance const store = new LibSQLVector({ connectionUrl: process.env.DATABASE_URL, // Optional: for Turso cloud databases authToken: process.env.DATABASE_AUTH_TOKEN, }); // Create an index await store.createIndex({ indexName: "myCollection", dimension: 1536, }); // Add vectors with metadata const vectors = [[0.1, 0.2, ...], [0.3, 0.4, ...]]; const metadata = [ { text: "first document", category: "A" }, { text: "second document", category: "B" } ]; await store.upsert({ indexName: "myCollection", vectors, metadata, }); // Query similar vectors const queryVector = [0.1, 0.2, ...]; const results = await store.query({ indexName: "myCollection", queryVector, topK: 10, // top K results filter: { category: "A" } // optional metadata filter }); ``` ## Constructor Options ✅ ## Methods ✅ ### createIndex() Creates a new vector collection. The index name must start with a letter or underscore and can only contain letters, numbers, and underscores. The dimension must be a positive integer. ### upsert() Adds or updates vectors and their metadata in the index. Uses a transaction to ensure all vectors are inserted atomically - if any insert fails, the entire operation is rolled back. []", isOptional: true, description: "Metadata for each vector", }, { name: "ids", type: "string[]", isOptional: true, description: "Optional vector IDs (auto-generated if not provided)", }, ]} /> ### query() Searches for similar vectors with optional metadata filtering. ### describeIndex() Gets information about an index. Returns: ```typescript copy interface IndexStats { dimension: number; count: number; metric: "cosine" | "euclidean" | "dotproduct"; } ``` ### deleteIndex() Deletes an index and all its data. ### listIndexes() Lists all vector indexes in the database. Returns: `Promise` ### truncateIndex() Removes all vectors from an index while keeping the index structure. ### updateIndexById() Updates a specific vector entry by its ID with new vector data and/or metadata. ", isOptional: true, description: "New metadata to update", }, ]} /> ### deleteIndexById() Deletes a specific vector entry from an index by its ID. ## Response Types ✅ Query results are returned in this format: ```typescript copy interface QueryResult { id: string; score: number; metadata: Record; vector?: number[]; // Only included if includeVector is true } ``` ## Error Handling ✅ The store throws specific errors for different failure cases: ```typescript copy try { await store.query({ indexName: "my-collection", queryVector: queryVector, }); } catch (error) { // Handle specific error cases if (error.message.includes("Invalid index name format")) { console.error( "Index name must start with a letter/underscore and contain only alphanumeric characters", ); } else if (error.message.includes("Table not found")) { console.error("The specified index does not exist"); } else { console.error("Vector store error:", error.message); } } ``` Common error cases include: - Invalid index name format - Invalid vector dimensions - Table/index not found - Database connection issues - Transaction failures during upsert ## Related ✅ - [Metadata Filters](./metadata-filters) --- title: "Reference: Metadata Filters | Metadata Filtering | RAG | Kastrax Docs" description: Documentation for metadata filtering capabilities in Kastrax, which allow for precise querying of vector search results across different vector stores. --- # Metadata Filters ✅ [EN] Source: https://kastrax.ai/en/reference/rag/metadata-filters Kastrax provides a unified metadata filtering syntax across all vector stores, based on MongoDB/Sift query syntax. Each vector store translates these filters into their native format. ## Basic Example ✅ ```typescript import { PgVector } from '@kastrax/pg'; const store = new PgVector(connectionString); const results = await store.query({ indexName: "my_index", queryVector: queryVector, topK: 10, filter: { category: "electronics", // Simple equality price: { $gt: 100 }, // Numeric comparison tags: { $in: ["sale", "new"] } // Array membership } }); ``` ## Supported Operators ✅ ## Common Rules and Restrictions ✅ 1. Field names cannot: - Contain dots (.) unless referring to nested fields - Start with $ or contain null characters - Be empty strings 2. Values must be: - Valid JSON types (string, number, boolean, object, array) - Not undefined - Properly typed for the operator (e.g., numbers for numeric comparisons) 3. Logical operators: - Must contain valid conditions - Cannot be empty - Must be properly nested - Can only be used at top level or nested within other logical operators - Cannot be used at field level or nested inside a field - Cannot be used inside an operator - Valid: `{ "$and": [{ "field": { "$gt": 100 } }] }` - Valid: `{ "$or": [{ "$and": [{ "field": { "$gt": 100 } }] }] }` - Invalid: `{ "field": { "$and": [{ "$gt": 100 }] } }` - Invalid: `{ "field": { "$gt": { "$and": [{...}] } } }` 4. $not operator: - Must be an object - Cannot be empty - Can be used at field level or top level - Valid: `{ "$not": { "field": "value" } }` - Valid: `{ "field": { "$not": { "$eq": "value" } } }` 5. Operator nesting: - Logical operators must contain field conditions, not direct operators - Valid: `{ "$and": [{ "field": { "$gt": 100 } }] }` - Invalid: `{ "$and": [{ "$gt": 100 }] }` ## Store-Specific Notes ✅ ### Astra - Nested field queries are supported using dot notation - Array fields must be explicitly defined as arrays in the metadata - Metadata values are case-sensitive ### ChromaDB - Where filters only return results where the filtered field exists in metadata - Empty metadata fields are not included in filter results - Metadata fields must be present for negative matches (e.g., $ne won't match documents missing the field) ### Cloudflare Vectorize - Requires explicit metadata indexing before filtering can be used - Use `createMetadataIndex()` to index fields you want to filter on - Up to 10 metadata indexes per Vectorize index - String values are indexed up to first 64 bytes (truncated on UTF-8 boundaries) - Number values use float64 precision - Filter JSON must be under 2048 bytes - Field names cannot contain dots (.) or start with $ - Field names limited to 512 characters - Vectors must be re-upserted after creating new metadata indexes to be included in filtered results - Range queries may have reduced accuracy with very large datasets (~10M+ vectors) ### LibSQL - Supports nested object queries with dot notation - Array fields are validated to ensure they contain valid JSON arrays - Numeric comparisons maintain proper type handling - Empty arrays in conditions are handled gracefully - Metadata is stored in a JSONB column for efficient querying ### PgVector - Full support for PostgreSQL's native JSON querying capabilities - Efficient handling of array operations using native array functions - Proper type handling for numbers, strings, and booleans - Nested field queries use PostgreSQL's JSON path syntax internally - Metadata is stored in a JSONB column for efficient indexing ### Pinecone - Metadata field names are limited to 512 characters - Numeric values must be within the range of ±1e38 - Arrays in metadata are limited to 64KB total size - Nested objects are flattened with dot notation - Metadata updates replace the entire metadata object ### Qdrant - Supports advanced filtering with nested conditions - Payload (metadata) fields must be explicitly indexed for filtering - Efficient handling of geo-spatial queries - Special handling for null and empty values - Vector-specific filtering capabilities - Datetime values must be in RFC 3339 format ### Upstash - 512-character limit for metadata field keys - Query size is limited (avoid large IN clauses) - No support for null/undefined values in filters - Translates to SQL-like syntax internally - Case-sensitive string comparisons - Metadata updates are atomic ### MongoDB - Full support for MongoDB/Sift query syntax for metadata filters - Supports all standard comparison, array, logical, and element operators - Supports nested fields and arrays in metadata - Filtering can be applied to both `metadata` and the original document content using the `filter` and `documentFilter` options, respectively - `filter` applies to the metadata object; `documentFilter` applies to the original document fields - No artificial limits on filter size or complexity (subject to MongoDB query limits) - Indexing metadata fields is recommended for optimal performance ## Related ✅ - [Astra](./astra) - [Chroma](./chroma) - [Cloudflare Vectorize](./vectorize) - [LibSQL](./libsql) - [MongoDB](./mongodb) - [PgStore](./pg) - [Pinecone](./pinecone) - [Qdrant](./qdrant) - [Upstash](./upstash) --- title: "Reference: MongoDB Vector Store | Vector Databases | RAG | Kastrax Docs" description: Documentation for the MongoDBVector class in Kastrax, which provides vector search using MongoDB Atlas and Atlas Vector Search. --- # MongoDB Vector Store ✅ [EN] Source: https://kastrax.ai/en/reference/rag/mongodb The `MongoDBVector` class provides vector search using [MongoDB Atlas Vector Search](https://www.mongodb.com/docs/atlas/atlas-vector-search/). It enables efficient similarity search and metadata filtering within your MongoDB collections. ## Installation ✅ ```bash copy npm install @kastrax/mongodb ``` ## Usage Example ✅ ```typescript copy showLineNumbers import { MongoDBVector } from '@kastrax/mongodb'; const store = new MongoDBVector({ url: process.env.MONGODB_URL, database: process.env.MONGODB_DATABASE, }); ``` ## Constructor Options ✅ ## Methods ✅ ### createIndex() Creates a new vector index (collection) in MongoDB. ### upsert() Adds or updates vectors and their metadata in the collection. []", isOptional: true, description: "Metadata for each vector", }, { name: "ids", type: "string[]", isOptional: true, description: "Optional vector IDs (auto-generated if not provided)", }, ]} /> ### query() Searches for similar vectors with optional metadata filtering. ", isOptional: true, description: "Metadata filters (applies to the `metadata` field)", }, { name: "documentFilter", type: "Record", isOptional: true, description: "Filters on original document fields (not just metadata)", }, { name: "includeVector", type: "boolean", isOptional: true, defaultValue: "false", description: "Whether to include vector data in results", }, { name: "minScore", type: "number", isOptional: true, defaultValue: "0", description: "Minimum similarity score threshold", }, ]} /> ### describeIndex() Returns information about the index (collection). Returns: ```typescript copy interface IndexStats { dimension: number; count: number; metric: "cosine" | "euclidean" | "dotproduct"; } ``` ### deleteIndex() Deletes a collection and all its data. ### listIndexes() Lists all vector collections in the MongoDB database. Returns: `Promise` ### updateIndexById() Updates a specific vector entry by its ID with new vector data and/or metadata. ", isOptional: true, description: "New metadata to update", }, ]} /> ### deleteIndexById() Deletes a specific vector entry from an index by its ID. ### disconnect() Closes the MongoDB client connection. Should be called when done using the store. ## Response Types ✅ Query results are returned in this format: ```typescript copy interface QueryResult { id: string; score: number; metadata: Record; vector?: number[]; // Only included if includeVector is true } ``` ## Error Handling ✅ The store throws typed errors that can be caught: ```typescript copy try { await store.query({ indexName: "my_collection", queryVector: queryVector, }); } catch (error) { // Handle specific error cases if (error.message.includes("Invalid collection name")) { console.error( "Collection name must start with a letter or underscore and contain only valid characters." ); } else if (error.message.includes("Collection not found")) { console.error("The specified collection does not exist"); } else { console.error("Vector store error:", error.message); } } ``` ## Best Practices ✅ - Index metadata fields used in filters for optimal query performance. - Use consistent field naming in metadata to avoid unexpected query results. - Regularly monitor index and collection statistics to ensure efficient search. ## Related ✅ - [Metadata Filters](./metadata-filters) --- title: "Reference: PG Vector Store | Vector Databases | RAG | Kastrax Docs" description: Documentation for the PgVector class in Kastrax, which provides vector search using PostgreSQL with pgvector extension. --- # PG Vector Store ✅ [EN] Source: https://kastrax.ai/en/reference/rag/pg The PgVector class provides vector search using [PostgreSQL](https://www.postgresql.org/) with [pgvector](https://github.com/pgvector/pgvector) extension. It provides robust vector similarity search capabilities within your existing PostgreSQL database. ## Constructor Options ✅ ## Constructor Examples ✅ You can instantiate `PgVector` in two ways: ```ts import { PgVector } from '@kastrax/pg'; // Using a connection string (string form) const vectorStore1 = new PgVector('postgresql://user:password@localhost:5432/mydb'); // Using a config object (with optional schemaName) const vectorStore2 = new PgVector({ connectionString: 'postgresql://user:password@localhost:5432/mydb', schemaName: 'custom_schema', // optional }); ``` ## Methods ✅ ### createIndex() #### IndexConfig #### Memory Requirements HNSW indexes require significant shared memory during construction. For 100K vectors: - Small dimensions (64d): ~60MB with default settings - Medium dimensions (256d): ~180MB with default settings - Large dimensions (384d+): ~250MB+ with default settings Higher M values or efConstruction values will increase memory requirements significantly. Adjust your system's shared memory limits if needed. ### upsert() []", isOptional: true, description: "Metadata for each vector", }, { name: "ids", type: "string[]", isOptional: true, description: "Optional vector IDs (auto-generated if not provided)", }, ]} /> ### query() ", isOptional: true, description: "Metadata filters", }, { name: "includeVector", type: "boolean", isOptional: true, defaultValue: "false", description: "Whether to include the vector in the result", }, { name: "minScore", type: "number", isOptional: true, defaultValue: "0", description: "Minimum similarity score threshold", }, { name: "options", type: "{ ef?: number; probes?: number }", isOptional: true, description: "Additional options for HNSW and IVF indexes", properties: [ { type: "object", parameters: [ { name: "ef", type: "number", description: "HNSW search parameter", isOptional: true, }, { name: "probes", type: "number", description: "IVF search parameter", isOptional: true, }, ], }, ], }, ]} /> ### listIndexes() Returns an array of index names as strings. ### describeIndex() Returns: ```typescript copy interface PGIndexStats { dimension: number; count: number; metric: "cosine" | "euclidean" | "dotproduct"; type: "flat" | "hnsw" | "ivfflat"; config: { m?: number; efConstruction?: number; lists?: number; probes?: number; }; } ``` ### deleteIndex() ### updateIndexById() ", description: "New metadata values", isOptional: true, }, ], }, ], }, ]} /> Updates an existing vector by ID. At least one of vector or metadata must be provided. ```typescript copy // Update just the vector await pgVector.updateIndexById("my_vectors", "vector123", { vector: [0.1, 0.2, 0.3], }); // Update just the metadata await pgVector.updateIndexById("my_vectors", "vector123", { metadata: { label: "updated" }, }); // Update both vector and metadata await pgVector.updateIndexById("my_vectors", "vector123", { vector: [0.1, 0.2, 0.3], metadata: { label: "updated" }, }); ``` ### deleteIndexById() Deletes a single vector by ID from the specified index. ```typescript copy await pgVector.deleteIndexById("my_vectors", "vector123"); ``` ### disconnect() Closes the database connection pool. Should be called when done using the store. ### buildIndex() Builds or rebuilds an index with specified metric and configuration. Will drop any existing index before creating the new one. ```typescript copy // Define HNSW index await pgVector.buildIndex("my_vectors", "cosine", { type: "hnsw", hnsw: { m: 8, efConstruction: 32, }, }); // Define IVF index await pgVector.buildIndex("my_vectors", "cosine", { type: "ivfflat", ivf: { lists: 100, }, }); // Define flat index await pgVector.buildIndex("my_vectors", "cosine", { type: "flat", }); ``` ## Response Types ✅ Query results are returned in this format: ```typescript copy interface QueryResult { id: string; score: number; metadata: Record; vector?: number[]; // Only included if includeVector is true } ``` ## Error Handling ✅ The store throws typed errors that can be caught: ```typescript copy try { await store.query({ indexName: "index_name", queryVector: queryVector, }); } catch (error) { if (error instanceof VectorStoreError) { console.log(error.code); // 'connection_failed' | 'invalid_dimension' | etc console.log(error.details); // Additional error context } } ``` ## Best Practices ✅ - Regularly evaluate your index configuration to ensure optimal performance. - Adjust parameters like `lists` and `m` based on dataset size and query requirements. - Rebuild indexes periodically to maintain efficiency, especially after significant data changes. ## Related ✅ - [Metadata Filters](./metadata-filters) --- title: "Reference: Pinecone Vector Store | Vector DBs | RAG | Kastrax Docs" description: Documentation for the PineconeVector class in Kastrax, which provides an interface to Pinecone's vector database. --- # Pinecone Vector Store [EN] Source: https://kastrax.ai/en/reference/rag/pinecone The PineconeVector class provides an interface to [Pinecone](https://www.pinecone.io/)'s vector database. It provides real-time vector search, with features like hybrid search, metadata filtering, and namespace management. ## Constructor Options ## Methods ### createIndex() ### upsert() []", isOptional: true, description: "Metadata for each vector", }, { name: "ids", type: "string[]", isOptional: true, description: "Optional vector IDs (auto-generated if not provided)", }, { name: "namespace", type: "string", isOptional: true, description: "Optional namespace to store vectors in. Vectors in different namespaces are isolated from each other.", }, ]} /> ### query() ", isOptional: true, description: "Metadata filters for the query", }, { name: "includeVector", type: "boolean", isOptional: true, defaultValue: "false", description: "Whether to include the vector in the result", }, { name: "namespace", type: "string", isOptional: true, description: "Optional namespace to query vectors from. Only returns results from the specified namespace.", }, ]} /> ### listIndexes() Returns an array of index names as strings. ### describeIndex() Returns: ```typescript copy interface IndexStats { dimension: number; count: number; metric: "cosine" | "euclidean" | "dotproduct"; } ``` ### deleteIndex() ### updateIndexById() ", isOptional: true, description: "New metadata to update", }, ]} /> ### deleteIndexById() ## Response Types Query results are returned in this format: ```typescript copy interface QueryResult { id: string; score: number; metadata: Record; vector?: number[]; // Only included if includeVector is true } ``` ## Error Handling The store throws typed errors that can be caught: ```typescript copy try { await store.query({ indexName: "index_name", queryVector: queryVector, }); } catch (error) { if (error instanceof VectorStoreError) { console.log(error.code); // 'connection_failed' | 'invalid_dimension' | etc console.log(error.details); // Additional error context } } ``` ### Environment Variables Required environment variables: - `PINECONE_API_KEY`: Your Pinecone API key - `PINECONE_ENVIRONMENT`: Pinecone environment (e.g., 'us-west1-gcp') ## Hybrid Search Pinecone supports hybrid search by combining dense and sparse vectors. To use hybrid search: 1. Create an index with `metric: 'dotproduct'` 2. During upsert, provide sparse vectors using the `sparseVectors` parameter 3. During query, provide a sparse vector using the `sparseVector` parameter ## Related - [Metadata Filters](./metadata-filters) --- title: "Reference: Qdrant Vector Store | Vector Databases | RAG | Kastrax Docs" description: Documentation for integrating Qdrant with Kastrax, a vector similarity search engine for managing vectors and payloads. --- # Qdrant Vector Store [EN] Source: https://kastrax.ai/en/reference/rag/qdrant The QdrantVector class provides vector search using [Qdrant](https://qdrant.tech/), a vector similarity search engine. It provides a production-ready service with a convenient API to store, search, and manage vectors with additional payload and extended filtering support. ## Constructor Options ## Methods ### createIndex() ### upsert() []", isOptional: true, description: "Metadata for each vector", }, { name: "ids", type: "string[]", isOptional: true, description: "Optional vector IDs (auto-generated if not provided)", }, ]} /> ### query() ", isOptional: true, description: "Metadata filters for the query", }, { name: "includeVector", type: "boolean", isOptional: true, defaultValue: "false", description: "Whether to include vectors in the results", }, ]} /> ### listIndexes() Returns an array of index names as strings. ### describeIndex() Returns: ```typescript copy interface IndexStats { dimension: number; count: number; metric: "cosine" | "euclidean" | "dotproduct"; } ``` ### deleteIndex() ### updateIndexById() ; }", description: "Object containing the vector and/or metadata to update", }, ]} /> Updates a vector and/or its metadata in the specified index. If both vector and metadata are provided, both will be updated. If only one is provided, only that will be updated. ### deleteIndexById() Deletes a vector from the specified index by its ID. ## Response Types Query results are returned in this format: ```typescript copy interface QueryResult { id: string; score: number; metadata: Record; vector?: number[]; // Only included if includeVector is true } ``` ## Error Handling The store throws typed errors that can be caught: ```typescript copy try { await store.query({ indexName: "index_name", queryVector: queryVector, }); } catch (error) { if (error instanceof VectorStoreError) { console.log(error.code); // 'connection_failed' | 'invalid_dimension' | etc console.log(error.details); // Additional error context } } ``` ## Related - [Metadata Filters](./metadata-filters) --- title: "Reference: Rerank | Document Retrieval | RAG | Kastrax Docs" description: Documentation for the rerank function in Kastrax, which provides advanced reranking capabilities for vector search results. --- # rerank() ✅ [EN] Source: https://kastrax.ai/en/reference/rag/rerank The `rerank()` function provides advanced reranking capabilities for vector search results by combining semantic relevance, vector similarity, and position-based scoring. ```typescript function rerank( results: QueryResult[], query: string, modelConfig: ModelConfig, options?: RerankerFunctionOptions ): Promise ``` ## Usage Example ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { rerank } from "@kastrax/rag"; const model = openai("gpt-4o-mini"); const rerankedResults = await rerank( vectorSearchResults, "How do I deploy to production?", model, { weights: { semantic: 0.5, vector: 0.3, position: 0.2 }, topK: 3 } ); ``` ## Parameters ✅ The rerank function accepts any LanguageModel from the Vercel AI SDK. When using the Cohere model `rerank-v3.5`, it will automatically use Cohere's reranking capabilities. > **Note:** For semantic scoring to work properly during re-ranking, each result must include the text content in its `metadata.text` field. ### RerankerFunctionOptions ## Returns ✅ The function returns an array of `RerankResult` objects: ### ScoringDetails ## Related ✅ - [createVectorQueryTool](../tools/vector-query-tool) --- title: "Reference: Turbopuffer Vector Store | Vector Databases | RAG | Kastrax Docs" description: Documentation for integrating Turbopuffer with Kastrax, a high-performance vector database for efficient similarity search. --- # Turbopuffer Vector Store [EN] Source: https://kastrax.ai/en/reference/rag/turbopuffer The TurbopufferVector class provides vector search using [Turbopuffer](https://turbopuffer.com/), a high-performance vector database optimized for RAG applications. Turbopuffer offers fast vector similarity search with advanced filtering capabilities and efficient storage management. ## Constructor Options ## Methods ### createIndex() ### upsert() []", isOptional: true, description: "Metadata for each vector", }, { name: "ids", type: "string[]", isOptional: true, description: "Optional vector IDs (auto-generated if not provided)", }, ]} /> ### query() ", isOptional: true, description: "Metadata filters for the query", }, { name: "includeVector", type: "boolean", isOptional: true, defaultValue: "false", description: "Whether to include vectors in the results", }, ]} /> ### listIndexes() Returns an array of index names as strings. ### describeIndex() Returns: ```typescript copy interface IndexStats { dimension: number; count: number; metric: "cosine" | "euclidean" | "dotproduct"; } ``` ### deleteIndex() ## Response Types Query results are returned in this format: ```typescript copy interface QueryResult { id: string; score: number; metadata: Record; vector?: number[]; // Only included if includeVector is true } ``` ## Schema Configuration The `schemaConfigForIndex` option allows you to define explicit schemas for different indexes: ```typescript copy schemaConfigForIndex: (indexName: string) => { // Kastrax's default embedding model and index for memory messages: if (indexName === "memory_messages_384") { return { dimensions: 384, schema: { thread_id: { type: "string", filterable: true, }, }, }; } else { throw new Error(`TODO: add schema for index: ${indexName}`); } }; ``` ## Error Handling The store throws typed errors that can be caught: ```typescript copy try { await store.query({ indexName: "index_name", queryVector: queryVector, }); } catch (error) { if (error instanceof VectorStoreError) { console.log(error.code); // 'connection_failed' | 'invalid_dimension' | etc console.log(error.details); // Additional error context } } ``` ## Related - [Metadata Filters](./metadata-filters) --- title: "Reference: Upstash Vector Store | Vector Databases | RAG | Kastrax Docs" description: Documentation for the UpstashVector class in Kastrax, which provides vector search using Upstash Vector. --- # Upstash Vector Store [EN] Source: https://kastrax.ai/en/reference/rag/upstash The UpstashVector class provides vector search using [Upstash Vector](https://upstash.com/vector), a serverless vector database service that provides vector similarity search with metadata filtering capabilities. ## Constructor Options ## Methods ### createIndex() Note: This method is a no-op for Upstash as indexes are created automatically. ### upsert() []", isOptional: true, description: "Metadata for each vector", }, { name: "ids", type: "string[]", isOptional: true, description: "Optional vector IDs (auto-generated if not provided)", }, ]} /> ### query() ", isOptional: true, description: "Metadata filters for the query", }, { name: "includeVector", type: "boolean", isOptional: true, defaultValue: "false", description: "Whether to include vectors in the results", }, ]} /> ### listIndexes() Returns an array of index names (namespaces) as strings. ### describeIndex() Returns: ```typescript copy interface IndexStats { dimension: number; count: number; metric: "cosine" | "euclidean" | "dotproduct"; } ``` ### deleteIndex() ### updateIndexById() The `update` object can have the following properties: - `vector` (optional): An array of numbers representing the new vector. - `metadata` (optional): A record of key-value pairs for metadata. Throws an error if neither `vector` nor `metadata` is provided, or if only `metadata` is provided. ### deleteIndexById() Attempts to delete an item by its ID from the specified index. Logs an error message if the deletion fails. ## Response Types Query results are returned in this format: ```typescript copy interface QueryResult { id: string; score: number; metadata: Record; vector?: number[]; // Only included if includeVector is true } ``` ## Error Handling The store throws typed errors that can be caught: ```typescript copy try { await store.query({ indexName: "index_name", queryVector: queryVector, }); } catch (error) { if (error instanceof VectorStoreError) { console.log(error.code); // 'connection_failed' | 'invalid_dimension' | etc console.log(error.details); // Additional error context } } ``` ## Environment Variables Required environment variables: - `UPSTASH_VECTOR_URL`: Your Upstash Vector database URL - `UPSTASH_VECTOR_TOKEN`: Your Upstash Vector API token ## Related - [Metadata Filters](./metadata-filters) --- title: "Reference: Cloudflare Vector Store | Vector Databases | RAG | Kastrax Docs" description: Documentation for the CloudflareVector class in Kastrax, which provides vector search using Cloudflare Vectorize. --- # Cloudflare Vector Store [EN] Source: https://kastrax.ai/en/reference/rag/vectorize The CloudflareVector class provides vector search using [Cloudflare Vectorize](https://developers.cloudflare.com/vectorize/), a vector database service integrated with Cloudflare's edge network. ## Constructor Options ## Methods ### createIndex() ### upsert() []", isOptional: true, description: "Metadata for each vector", }, { name: "ids", type: "string[]", isOptional: true, description: "Optional vector IDs (auto-generated if not provided)", }, ]} /> ### query() ", isOptional: true, description: "Metadata filters for the query", }, { name: "includeVector", type: "boolean", isOptional: true, defaultValue: "false", description: "Whether to include vectors in the results", }, ]} /> ### listIndexes() Returns an array of index names as strings. ### describeIndex() Returns: ```typescript copy interface IndexStats { dimension: number; count: number; metric: "cosine" | "euclidean" | "dotproduct"; } ``` ### deleteIndex() ### createMetadataIndex() Creates an index on a metadata field to enable filtering. ### deleteMetadataIndex() Removes an index from a metadata field. ### listMetadataIndexes() Lists all metadata field indexes for an index. ### updateIndexById() Updates a vector or metadata for a specific ID within an index. ; }", description: "Object containing the vector and/or metadata to update", }, ]} /> ### deleteIndexById() Deletes a vector and its associated metadata for a specific ID within an index. ## Response Types Query results are returned in this format: ```typescript copy interface QueryResult { id: string; score: number; metadata: Record; vector?: number[]; } ``` ## Error Handling The store throws typed errors that can be caught: ```typescript copy try { await store.query({ indexName: "index_name", queryVector: queryVector, }); } catch (error) { if (error instanceof VectorStoreError) { console.log(error.code); // 'connection_failed' | 'invalid_dimension' | etc console.log(error.details); // Additional error context } } ``` ## Environment Variables Required environment variables: - `CLOUDFLARE_ACCOUNT_ID`: Your Cloudflare account ID - `CLOUDFLARE_API_TOKEN`: Your Cloudflare API token with Vectorize permissions ## Related - [Metadata Filters](./metadata-filters) --- title: "Cloudflare D1 Storage | Storage System | Kastrax Core" description: Documentation for the Cloudflare D1 SQL storage implementation in Kastrax. --- # Cloudflare D1 Storage ✅ [EN] Source: https://kastrax.ai/en/reference/storage/cloudflare-d1 The Cloudflare D1 storage implementation provides a serverless SQL database solution using Cloudflare D1, supporting relational operations and transactional consistency. ## Installation ✅ ```bash npm install @kastrax/cloudflare-d1@latest ``` ## Usage ✅ ```typescript copy showLineNumbers import { D1Store } from "@kastrax/cloudflare-d1"; // --- Example 1: Using Workers Binding --- const storageWorkers = new D1Store({ binding: D1Database, // D1Database binding provided by the Workers runtime tablePrefix: 'dev_', // Optional: isolate tables per environment }); // --- Example 2: Using REST API --- const storageRest = new D1Store({ accountId: process.env.CLOUDFLARE_ACCOUNT_ID!, // Cloudflare Account ID databaseId: process.env.CLOUDFLARE_D1_DATABASE_ID!, // D1 Database ID apiToken: process.env.CLOUDFLARE_API_TOKEN!, // Cloudflare API Token tablePrefix: 'dev_', // Optional: isolate tables per environment }); ``` ## Parameters ✅ ## Additional Notes ✅ ### Schema Management The storage implementation handles schema creation and updates automatically. It creates the following tables: - `threads`: Stores conversation threads - `messages`: Stores individual messages - `metadata`: Stores additional metadata for threads and messages ### Transactions & Consistency Cloudflare D1 provides transactional guarantees for single-row operations. This means that multiple operations can be executed as a single, all-or-nothing unit of work. ### Table Creation & Migrations Tables are created automatically when storage is initialized (and can be isolated per environment using the `tablePrefix` option), but advanced schema changes—such as adding columns, changing data types, or modifying indexes—require manual migration and careful planning to avoid data loss. --- title: "Cloudflare Storage | Storage System | Kastrax Core" description: Documentation for the Cloudflare KV storage implementation in Kastrax. --- # Cloudflare Storage ✅ [EN] Source: https://kastrax.ai/en/reference/storage/cloudflare The Cloudflare KV storage implementation provides a globally distributed, serverless key-value store solution using Cloudflare Workers KV. ## Installation ✅ ```bash copy npm install @kastrax/cloudflare@latest ``` ## Usage ✅ ```typescript copy showLineNumbers import { CloudflareStore } from "@kastrax/cloudflare"; // --- Example 1: Using Workers Binding --- const storageWorkers = new CloudflareStore({ bindings: { threads: THREADS_KV, // KVNamespace binding for threads table messages: MESSAGES_KV, // KVNamespace binding for messages table // Add other tables as needed }, keyPrefix: 'dev_', // Optional: isolate keys per environment }); // --- Example 2: Using REST API --- const storageRest = new CloudflareStore({ accountId: process.env.CLOUDFLARE_ACCOUNT_ID!, // Cloudflare Account ID apiToken: process.env.CLOUDFLARE_API_TOKEN!, // Cloudflare API Token namespacePrefix: 'dev_', // Optional: isolate namespaces per environment }); ``` ## Parameters ✅ ", description: "Cloudflare Workers KV bindings (for Workers runtime)", isOptional: true, }, { name: "accountId", type: "string", description: "Cloudflare Account ID (for REST API)", isOptional: true, }, { name: "apiToken", type: "string", description: "Cloudflare API Token (for REST API)", isOptional: true, }, { name: "namespacePrefix", type: "string", description: "Optional prefix for all namespace names (useful for environment isolation)", isOptional: true, }, { name: "keyPrefix", type: "string", description: "Optional prefix for all keys (useful for environment isolation)", isOptional: true, }, ]} /> #### Additional Notes ### Schema Management The storage implementation handles schema creation and updates automatically. It creates the following tables: - `threads`: Stores conversation threads - `messages`: Stores individual messages - `metadata`: Stores additional metadata for threads and messages ### Consistency & Propagation Cloudflare KV is an eventually consistent store, meaning that data may not be immediately available across all regions after a write. ### Key Structure & Namespacing Keys in Cloudflare KV are structured as a combination of a configurable prefix and a table-specific format (e.g., `threads:threadId`). For Workers deployments, `keyPrefix` is used to isolate data within a namespace; for REST API deployments, `namespacePrefix` is used to isolate entire namespaces between environments or applications. --- title: "LibSQL Storage | Storage System | Kastrax Core" description: Documentation for the LibSQL storage implementation in Kastrax. --- # LibSQL Storage ✅ [EN] Source: https://kastrax.ai/en/reference/storage/libsql The LibSQL storage implementation provides a SQLite-compatible storage solution that can run both in-memory and as a persistent database. ## Installation ✅ ```bash copy npm install @kastrax/storage-libsql@latest ``` ## Usage ✅ ```typescript copy showLineNumbers import { LibSQLStore } from "@kastrax/libsql"; // File database (development) const storage = new LibSQLStore({ url: "file:./storage.db", }); // Persistent database (production) const storage = new LibSQLStore({ url: process.env.DATABASE_URL, }); ``` ## Parameters ✅ ## Additional Notes ✅ ### In-Memory vs Persistent Storage The file configuration (`file:storage.db`) is useful for: - Development and testing - Temporary storage - Quick prototyping For production use cases, use a persistent database URL: `libsql://your-database.turso.io` ### Schema Management The storage implementation handles schema creation and updates automatically. It creates the following tables: - `threads`: Stores conversation threads - `messages`: Stores individual messages - `metadata`: Stores additional metadata for threads and messages --- title: "PostgreSQL Storage | Storage System | Kastrax Core" description: Documentation for the PostgreSQL storage implementation in Kastrax. --- # PostgreSQL Storage ✅ [EN] Source: https://kastrax.ai/en/reference/storage/postgresql The PostgreSQL storage implementation provides a production-ready storage solution using PostgreSQL databases. ## Installation ✅ ```bash copy npm install @kastrax/pg@latest ``` ## Usage ✅ ```typescript copy showLineNumbers import { PostgresStore } from "@kastrax/pg"; const storage = new PostgresStore({ connectionString: process.env.DATABASE_URL, }); ``` ## Parameters ✅ ## Constructor Examples ✅ You can instantiate `PostgresStore` in the following ways: ```ts import { PostgresStore } from '@kastrax/pg'; // Using a connection string only const store1 = new PostgresStore({ connectionString: 'postgresql://user:password@localhost:5432/mydb', }); // Using a connection string with a custom schema name const store2 = new PostgresStore({ connectionString: 'postgresql://user:password@localhost:5432/mydb', schemaName: 'custom_schema', // optional }); // Using individual connection parameters const store4 = new PostgresStore({ host: 'localhost', port: 5432, database: 'mydb', user: 'user', password: 'password', }); // Individual parameters with schemaName const store5 = new PostgresStore({ host: 'localhost', port: 5432, database: 'mydb', user: 'user', password: 'password', schemaName: 'custom_schema', // optional }); ``` ## Additional Notes ✅ ### Schema Management The storage implementation handles schema creation and updates automatically. It creates the following tables: - `threads`: Stores conversation threads - `messages`: Stores individual messages - `metadata`: Stores additional metadata for threads and messages --- title: "Upstash Storage | Storage System | Kastrax Core" description: Documentation for the Upstash storage implementation in Kastrax. --- # Upstash Storage ✅ [EN] Source: https://kastrax.ai/en/reference/storage/upstash The Upstash storage implementation provides a serverless-friendly storage solution using Upstash's Redis-compatible key-value store. ## Installation ✅ ```bash copy npm install @kastrax/upstash@latest ``` ## Usage ✅ ```typescript copy showLineNumbers import { UpstashStore } from "@kastrax/upstash"; const storage = new UpstashStore({ url: process.env.UPSTASH_URL, token: process.env.UPSTASH_TOKEN, }); ``` ## Parameters ✅ ## Additional Notes ✅ ### Key Structure The Upstash storage implementation uses a key-value structure: - Thread keys: `{prefix}thread:{threadId}` - Message keys: `{prefix}message:{messageId}` - Metadata keys: `{prefix}metadata:{entityId}` ### Serverless Benefits Upstash storage is particularly well-suited for serverless deployments: - No connection management needed - Pay-per-request pricing - Global replication options - Edge-compatible ### Data Persistence Upstash provides: - Automatic data persistence - Point-in-time recovery - Cross-region replication options ### Performance Considerations For optimal performance: - Use appropriate key prefixes to organize data - Monitor Redis memory usage - Consider data expiration policies if needed --- title: "Reference: KastraxMCPClient | Tool Discovery | Kastrax Docs" description: API Reference for KastraxMCPClient - A client implementation for the Model Context Protocol. --- # KastraxMCPClient (Deprecated) ✅ [EN] Source: https://kastrax.ai/en/reference/tools/client The `KastraxMCPClient` class provides a client implementation for interacting with Model Context Protocol (MCP) servers. It handles connection management, resource discovery, and tool execution through the MCP protocol. ## Deprecation notice ✅ `KastraxMCPClient` is being deprecated in favour of [`MCPClient`](./mcp-client). Rather than having two different interfaces for managing a single MCP server vs multiple MCP servers, we opted to recommend using the interface to manage multiple even when using a single MCP server. ## Constructor ✅ Creates a new instance of the KastraxMCPClient. ```typescript constructor({ name, version = '1.0.0', server, capabilities = {}, timeout = 60000, }: { name: string; server: KastraxMCPServerDefinition; capabilities?: ClientCapabilities; version?: string; timeout?: number; }) ``` ### Parameters
### KastraxMCPServerDefinition MCP servers can be configured using this definition. The client automatically detects the transport type based on the provided parameters: - If `command` is provided, it uses the Stdio transport. - If `url` is provided, it first attempts to use the Streamable HTTP transport and falls back to the legacy SSE transport if the initial connection fails.
", isOptional: true, description: "For Stdio servers: Environment variables to set for the command.", }, { name: "url", type: "URL", isOptional: true, description: "For HTTP servers (Streamable HTTP or SSE): The URL of the server.", }, { name: "requestInit", type: "RequestInit", isOptional: true, description: "For HTTP servers: Request configuration for the fetch API.", }, { name: "eventSourceInit", type: "EventSourceInit", isOptional: true, description: "For SSE fallback: Custom fetch configuration for SSE connections. Required when using custom headers with SSE.", }, { name: "logger", type: "LogHandler", isOptional: true, description: "Optional additional handler for logging.", }, { name: "timeout", type: "number", isOptional: true, description: "Server-specific timeout in milliseconds.", }, { name: "capabilities", type: "ClientCapabilities", isOptional: true, description: "Server-specific capabilities configuration.", }, { name: "enableServerLogs", type: "boolean", isOptional: true, defaultValue: "true", description: "Whether to enable logging for this server.", }, ]} /> ### LogHandler The `LogHandler` function takes a `LogMessage` object as its parameter and returns void. The `LogMessage` object has the following properties. The `LoggingLevel` type is a string enum with values: `debug`, `info`, `warn`, and `error`.
", isOptional: true, description: "Optional additional log details", }, ]} /> ## Methods ✅ ### connect() Establishes a connection with the MCP server. ```typescript async connect(): Promise ``` ### disconnect() Closes the connection with the MCP server. ```typescript async disconnect(): Promise ``` ### resources() Retrieves the list of available resources from the server. ```typescript async resources(): Promise ``` ### tools() Fetches and initializes available tools from the server, converting them into Kastrax-compatible tool formats. ```typescript async tools(): Promise> ``` Returns an object mapping tool names to their corresponding Kastrax tool implementations. ## Examples ✅ ### Using with Kastrax Agent #### Example with Stdio Server ```typescript import { Agent } from "@kastrax/core/agent"; import { KastraxMCPClient } from "@kastrax/mcp"; import { openai } from "@ai-sdk/openai"; // Initialize the MCP client using mcp/fetch as an example https://hub.docker.com/r/mcp/fetch // Visit https://github.com/docker/mcp-servers for other reference docker mcp servers const fetchClient = new KastraxMCPClient({ name: "fetch", server: { command: "docker", args: ["run", "-i", "--rm", "mcp/fetch"], logger: (logMessage) => { console.log(`[${logMessage.level}] ${logMessage.message}`); }, }, }); // Create a Kastrax Agent const agent = new Agent({ name: "Fetch agent", instructions: "You are able to fetch data from URLs on demand and discuss the response data with the user.", model: openai("gpt-4o-mini"), }); try { // Connect to the MCP server await fetchClient.connect(); // Gracefully handle process exits so the docker subprocess is cleaned up process.on("exit", () => { fetchClient.disconnect(); }); // Get available tools const tools = await fetchClient.tools(); // Use the agent with the MCP tools const response = await agent.generate( "Tell me about kastrax.ai/docs. Tell me generally what this page is and the content it includes.", { toolsets: { fetch: tools, }, }, ); console.log("\n\n" + response.text); } catch (error) { console.error("Error:", error); } finally { // Always disconnect when done await fetchClient.disconnect(); } ``` ### Example with SSE Server ```typescript // Initialize the MCP client using an SSE server const sseClient = new KastraxMCPClient({ name: "sse-client", server: { url: new URL("https://your-mcp-server.com/sse"), // Optional fetch request configuration - Note: requestInit alone isn't enough for SSE requestInit: { headers: { Authorization: "Bearer your-token", }, }, // Required for SSE connections with custom headers eventSourceInit: { fetch(input: Request | URL | string, init?: RequestInit) { const headers = new Headers(init?.headers || {}); headers.set('Authorization', 'Bearer your-token'); return fetch(input, { ...init, headers, }); }, }, // Optional additional logging configuration logger: (logMessage) => { console.log(`[${logMessage.level}] ${logMessage.serverName}: ${logMessage.message}`); }, // Disable server logs enableServerLogs: false }, }); // The rest of the usage is identical to the stdio example ``` ### Important Note About SSE Authentication When using SSE connections with authentication or custom headers, you need to configure both `requestInit` and `eventSourceInit`. This is because SSE connections use the browser's EventSource API, which doesn't support custom headers directly. The `eventSourceInit` configuration allows you to customize the underlying fetch request used for the SSE connection, ensuring your authentication headers are properly included. Without `eventSourceInit`, authentication headers specified in `requestInit` won't be included in the connection request, leading to 401 Unauthorized errors. ## Related Information ✅ - For managing multiple MCP servers in your application, see the [MCPClient documentation](./mcp-client) - For more details about the Model Context Protocol, see the [@modelcontextprotocol/sdk documentation](https://github.com/modelcontextprotocol/typescript-sdk). --- title: "Reference: createDocumentChunkerTool() | Tools | Kastrax Docs" description: Documentation for the Document Chunker Tool in Kastrax, which splits documents into smaller chunks for efficient processing and retrieval. --- # createDocumentChunkerTool() ✅ [EN] Source: https://kastrax.ai/en/reference/tools/document-chunker-tool The `createDocumentChunkerTool()` function creates a tool for splitting documents into smaller chunks for efficient processing and retrieval. It supports different chunking strategies and configurable parameters. ## Basic Usage ✅ ```typescript import { createDocumentChunkerTool, MDocument } from "@kastrax/rag"; const document = new MDocument({ text: "Your document content here...", metadata: { source: "user-manual" } }); const chunker = createDocumentChunkerTool({ doc: document, params: { strategy: "recursive", size: 512, overlap: 50, separator: "\n" } }); const { chunks } = await chunker.execute(); ``` ## Parameters ✅ ### ChunkParams ## Returns ✅ ## Example with Custom Parameters ✅ ```typescript const technicalDoc = new MDocument({ text: longDocumentContent, metadata: { type: "technical", version: "1.0" } }); const chunker = createDocumentChunkerTool({ doc: technicalDoc, params: { strategy: "recursive", size: 1024, // Larger chunks overlap: 100, // More overlap separator: "\n\n" // Split on double newlines } }); const { chunks } = await chunker.execute(); // Process the chunks chunks.forEach((chunk, index) => { console.log(`Chunk ${index + 1} length: ${chunk.content.length}`); }); ``` ## Tool Details ✅ The chunker is created as a Kastrax tool with the following properties: - **Tool ID**: `Document Chunker {strategy} {size}` - **Description**: `Chunks document using {strategy} strategy with size {size} and {overlap} overlap` - **Input Schema**: Empty object (no additional inputs required) - **Output Schema**: Object containing the chunks array ## Related ✅ - [MDocument](../rag/document.mdx) - [createVectorQueryTool](./vector-query-tool) --- title: "Reference: createGraphRAGTool() | RAG | Kastrax Tools Docs" description: Documentation for the Graph RAG Tool in Kastrax, which enhances RAG by building a graph of semantic relationships between documents. --- # createGraphRAGTool() ✅ [EN] Source: https://kastrax.ai/en/reference/tools/graph-rag-tool The `createGraphRAGTool()` creates a tool that enhances RAG by building a graph of semantic relationships between documents. It uses the `GraphRAG` system under the hood to provide graph-based retrieval, finding relevant content through both direct similarity and connected relationships. ## Usage Example ✅ ```typescript import { openai } from "@ai-sdk/openai"; import { createGraphRAGTool } from "@kastrax/rag"; const graphTool = createGraphRAGTool({ vectorStoreName: "pinecone", indexName: "docs", model: openai.embedding('text-embedding-3-small'), graphOptions: { dimension: 1536, threshold: 0.7, randomWalkSteps: 100, restartProb: 0.15 } }); ``` ## Parameters ✅ ### GraphOptions ## Returns ✅ The tool returns an object with: ## Default Tool Description ✅ The default description focuses on: - Analyzing relationships between documents - Finding patterns and connections - Answering complex queries ## Advanced Example ✅ ```typescript const graphTool = createGraphRAGTool({ vectorStoreName: "pinecone", indexName: "docs", model: openai.embedding('text-embedding-3-small'), graphOptions: { dimension: 1536, threshold: 0.8, // Higher similarity threshold randomWalkSteps: 200, // More exploration steps restartProb: 0.2 // Higher restart probability } }); ``` ## Example with Custom Description ✅ ```typescript const graphTool = createGraphRAGTool({ vectorStoreName: "pinecone", indexName: "docs", model: openai.embedding('text-embedding-3-small'), description: "Analyze document relationships to find complex patterns and connections in our company's historical data" }); ``` This example shows how to customize the tool description for a specific use case while maintaining its core purpose of relationship analysis. ## Related ✅ - [createVectorQueryTool](./vector-query-tool) - [GraphRAG](../rag/graph-rag) --- title: "Reference: MCPClient | Tool Management | Kastrax Docs" description: API Reference for MCPClient - A class for managing multiple Model Context Protocol servers and their tools. --- # MCPClient ✅ [EN] Source: https://kastrax.ai/en/reference/tools/mcp-client The `MCPClient` class provides a way to manage multiple MCP server connections and their tools in a Kastrax application. It handles connection lifecycle, tool namespacing, and provides convenient access to tools across all configured servers. ## Constructor ✅ Creates a new instance of the MCPClient class. ```typescript constructor({ id?: string; servers: Record; timeout?: number; }: MCPClientOptions) ``` ### MCPClientOptions
", description: "A map of server configurations, where each key is a unique server identifier and the value is the server configuration.", }, { name: "timeout", type: "number", isOptional: true, defaultValue: "60000", description: "Global timeout value in milliseconds for all servers unless overridden in individual server configs.", }, ]} /> ### KastraxMCPServerDefinition Each server in the `servers` map is configured using the `KastraxMCPServerDefinition` type. The `KastraxMCPClient` used internally automatically detects the transport type based on the provided parameters: - If `command` is provided, it uses the Stdio transport. - If `url` is provided, it first attempts to use the Streamable HTTP transport and falls back to the legacy SSE transport if the initial connection fails.
", isOptional: true, description: "For Stdio servers: Environment variables to set for the command.", }, { name: "url", type: "URL", isOptional: true, description: "For HTTP servers (Streamable HTTP or SSE): The URL of the server.", }, { name: "requestInit", type: "RequestInit", isOptional: true, description: "For HTTP servers: Request configuration for the fetch API.", }, { name: "eventSourceInit", type: "EventSourceInit", isOptional: true, description: "For SSE fallback: Custom fetch configuration for SSE connections. Required when using custom headers with SSE.", }, { name: "logger", type: "LogHandler", isOptional: true, description: "Optional additional handler for logging.", }, { name: "timeout", type: "number", isOptional: true, description: "Server-specific timeout in milliseconds.", }, { name: "capabilities", type: "ClientCapabilities", isOptional: true, description: "Server-specific capabilities configuration.", }, { name: "enableServerLogs", type: "boolean", isOptional: true, defaultValue: "true", description: "Whether to enable logging for this server.", }, ]} /> ## Methods ✅ ### getTools() Retrieves all tools from all configured servers, with tool names namespaced by their server name (in the format `serverName_toolName`) to prevent conflicts. Intended to be passed onto an Agent definition. ```ts new Agent({ tools: await mcp.getTools() }); ``` ### getToolsets() Returns an object mapping namespaced tool names (in the format `serverName.toolName`) to their tool implementations. Intended to be passed dynamically into the generate or stream method. ```typescript const res = await agent.stream(prompt, { toolsets: await mcp.getToolsets(), }); ``` ### disconnect() Disconnects from all MCP servers and cleans up resources. ```typescript async disconnect(): Promise ``` ## Examples ✅ ### Basic Usage ```typescript import { MCPClient } from "@kastrax/mcp"; import { Agent } from "@kastrax/core/agent"; import { openai } from "@ai-sdk/openai"; const mcp = new MCPClient({ servers: { stockPrice: { command: "npx", args: ["tsx", "stock-price.ts"], env: { API_KEY: "your-api-key", }, log: (logMessage) => { console.log(`[${logMessage.level}] ${logMessage.message}`); }, }, weather: { url: new URL("http://localhost:8080/sse"),∂ }, }, timeout: 30000, // Global 30s timeout }); // Create an agent with access to all tools const agent = new Agent({ name: "Multi-tool Agent", instructions: "You have access to multiple tool servers.", model: openai("gpt-4"), tools: await mcp.getTools(), }); ``` ### Using Toolsets in generate() or stream() ```typescript import { Agent } from "@kastrax/core/agent"; import { MCPClient } from "@kastrax/mcp"; import { openai } from "@ai-sdk/openai"; // Create the agent first, without any tools const agent = new Agent({ name: "Multi-tool Agent", instructions: "You help users check stocks and weather.", model: openai("gpt-4"), }); // Later, configure MCP with user-specific settings const mcp = new MCPClient({ servers: { stockPrice: { command: "npx", args: ["tsx", "stock-price.ts"], env: { API_KEY: "user-123-api-key", }, timeout: 20000, // Server-specific timeout }, weather: { url: new URL("http://localhost:8080/sse"), requestInit: { headers: { Authorization: `Bearer user-123-token`, }, }, }, }, }); // Pass all toolsets to stream() or generate() const response = await agent.stream( "How is AAPL doing and what is the weather?", { toolsets: await mcp.getToolsets(), }, ); ``` ## Resource Management ✅ The `MCPClient` class includes built-in memory leak prevention for managing multiple instances: 1. Creating multiple instances with identical configurations without an `id` will throw an error to prevent memory leaks 2. If you need multiple instances with identical configurations, provide a unique `id` for each instance 3. Call `await configuration.disconnect()` before recreating an instance with the same configuration 4. If you only need one instance, consider moving the configuration to a higher scope to avoid recreation For example, if you try to create multiple instances with the same configuration without an `id`: ```typescript // First instance - OK const mcp1 = new MCPClient({ servers: { /* ... */ }, }); // Second instance with same config - Will throw an error const mcp2 = new MCPClient({ servers: { /* ... */ }, }); // To fix, either: // 1. Add unique IDs const mcp3 = new MCPClient({ id: "instance-1", servers: { /* ... */ }, }); // 2. Or disconnect before recreating await mcp1.disconnect(); const mcp4 = new MCPClient({ servers: { /* ... */ }, }); ``` ## Server Lifecycle ✅ MCPClient handles server connections gracefully: 1. Automatic connection management for multiple servers 2. Graceful server shutdown to prevent error messages during development 3. Proper cleanup of resources when disconnecting ## Related Information ✅ - For more about the Model Context Protocol, see the [@modelcontextprotocol/sdk documentation](https://github.com/modelcontextprotocol/typescript-sdk) --- title: "Reference: MCPServer | Exposing Kastrax Tools via MCP | Kastrax Docs" description: API Reference for MCPServer - A class for exposing Kastrax tools and capabilities as a Model Context Protocol server. --- # MCPServer ✅ [EN] Source: https://kastrax.ai/en/reference/tools/mcp-server The `MCPServer` class provides the functionality to expose your existing Kastrax tools as a Model Context Protocol (MCP) server. This allows any MCP client (like Cursor, Windsurf, or Claude Desktop) to connect to the tools and make them available to an agent. Note that if you only need to use your tools in your Kastrax agents you don't need to create an MCP server. This API allows you to expose your Kastrax tools to external MCP clients. In Kastrax you can use your tools directly without MCP. It supports both [stdio (subprocess) and SSE (HTTP) MCP transports](https://modelcontextprotocol.io/docs/concepts/transports). ## Properties ✅ To create a new MCPServer, you need to provide some basic information about your server and the tools it will offer. For example, here's how you might create a new `MCPServer` instance: ```typescript import { MCPServer } from "@kastrax/mcp"; import { weatherTool } from "./tools"; // Assuming you have a weather tool defined in this file const server = new MCPServer({ name: "My Weather Server", version: "1.0.0", tools: { weatherTool }, }); ``` ## Methods ✅ These are the functions you can call on an `MCPServer` instance to control its behavior and get information. ### startStdio() Use this method to start the server so it communicates using standard input and output (stdio). This is typical when running the server as a command-line program. ```typescript async startStdio(): Promise ``` Here's how you would start the server using stdio: ```typescript const server = new MCPServer({ // example configuration above }); await server.startStdio(); ``` ### startSSE() This method helps you integrate the MCP server with an existing web server to use Server-Sent Events (SSE) for communication. You'll call this from your web server's code when it receives a request for the SSE or message paths. ```typescript async startSSE({ url, ssePath, messagePath, req, res, }: { url: URL; ssePath: string; messagePath: string; req: any; res: any; }): Promise ``` Here's an example of how you might use `startSSE` within an HTTP server request handler. In this example an MCP client could connect to your MCP server at `http://localhost:1234/sse`: ```typescript import http from "http"; const httpServer = http.createServer(async (req, res) => { await server.startSSE({ url: new URL(req.url || "", `http://localhost:1234`), ssePath: "/sse", messagePath: "/message", req, res, }); }); httpServer.listen(PORT, () => { console.log(`HTTP server listening on port ${PORT}`); }); ``` Here are the details for the values needed by the `startSSE` method: ### getStdioTransport() If you started the server with `startStdio()`, you can use this to get the object that manages the stdio communication. This is mostly for checking things internally or for testing. ```typescript getStdioTransport(): StdioServerTransport | undefined ``` ### getSseTransport() If you started the server with `startSSE()`, you can use this to get the object that manages the SSE communication. Like `getStdioTransport`, this is mainly for internal checks or testing. ```typescript getSseTransport(): SSEServerTransport | undefined ``` ### tools() This method gives you a look at the tools that were set up when you created the server. It's a read-only list, useful for debugging purposes. ```typescript tools(): Readonly> ``` ## Examples ✅ For practical examples of setting up and deploying an MCPServer, see the [Deploying an MCPServer Example](/examples/agents/deploying-mcp-server). ## Related Information ✅ - For connecting to MCP servers in Kastrax, see the [MCPClient documentation](./mcp-client). - For more about the Model Context Protocol, see the [@modelcontextprotocol/sdk documentation](https://github.com/modelcontextprotocol/typescript-sdk). --- title: "Reference: createVectorQueryTool() | RAG | Kastrax Tools Docs" description: Documentation for the Vector Query Tool in Kastrax, which facilitates semantic search over vector stores with filtering and reranking capabilities. --- # createVectorQueryTool() ✅ [EN] Source: https://kastrax.ai/en/reference/tools/vector-query-tool The `createVectorQueryTool()` function creates a tool for semantic search over vector stores. It supports filtering, reranking, and integrates with various vector store backends. ## Basic Usage ✅ ```typescript import { openai } from '@ai-sdk/openai'; import { createVectorQueryTool } from "@kastrax/rag"; const queryTool = createVectorQueryTool({ vectorStoreName: "pinecone", indexName: "docs", model: openai.embedding('text-embedding-3-small'), }); ``` ## Parameters ✅ ### RerankConfig ## Returns ✅ The tool returns an object with: ## Default Tool Description ✅ The default description focuses on: - Finding relevant information in stored knowledge - Answering user questions - Retrieving factual content ## Result Handling ✅ The tool determines the number of results to return based on the user's query, with a default of 10 results. This can be adjusted based on the query requirements. ## Example with Filters ✅ ```typescript const queryTool = createVectorQueryTool({ vectorStoreName: "pinecone", indexName: "docs", model: openai.embedding('text-embedding-3-small'), enableFilters: true, }); ``` With filtering enabled, the tool processes queries to construct metadata filters that combine with semantic search. The process works as follows: 1. A user makes a query with specific filter requirements like "Find content where the 'version' field is greater than 2.0" 2. The agent analyzes the query and constructs the appropriate filters: ```typescript { "version": { "$gt": 2.0 } } ``` This agent-driven approach: - Processes natural language queries into filter specifications - Implements vector store-specific filter syntax - Translates query terms to filter operators For detailed filter syntax and store-specific capabilities, see the [Metadata Filters](../rag/metadata-filters) documentation. For an example of how agent-driven filtering works, see the [Agent-Driven Metadata Filtering](../../../examples/rag/usage/filter-rag) example. ## Example with Reranking ✅ ```typescript const queryTool = createVectorQueryTool({ vectorStoreName: "milvus", indexName: "documentation", model: openai.embedding('text-embedding-3-small'), reranker: { model: openai('gpt-4o-mini'), options: { weights: { semantic: 0.5, // Semantic relevance weight vector: 0.3, // Vector similarity weight position: 0.2 // Original position weight }, topK: 5 } } }); ``` Reranking improves result quality by combining: - Semantic relevance: Using LLM-based scoring of text similarity - Vector similarity: Original vector distance scores - Position bias: Consideration of original result ordering - Query analysis: Adjustments based on query characteristics The reranker processes the initial vector search results and returns a reordered list optimized for relevance. ## Example with Custom Description ✅ ```typescript const queryTool = createVectorQueryTool({ vectorStoreName: "pinecone", indexName: "docs", model: openai.embedding('text-embedding-3-small'), description: "Search through document archives to find relevant information for answering questions about company policies and procedures" }); ``` This example shows how to customize the tool description for a specific use case while maintaining its core purpose of information retrieval. ## Tool Details ✅ The tool is created with: - **ID**: `VectorQuery {vectorStoreName} {indexName} Tool` - **Input Schema**: Requires queryText and filter objects - **Output Schema**: Returns relevantContext string ## Related ✅ - [rerank()](../rag/rerank) - [createGraphRAGTool](./graph-rag-tool) --- title: "Reference: Azure Voice | Voice Providers | Kastrax Docs" description: "Documentation for the AzureVoice class, providing text-to-speech and speech-to-text capabilities using Azure Cognitive Services." --- # Azure ✅ [EN] Source: https://kastrax.ai/en/reference/voice/azure The AzureVoice class in Kastrax provides text-to-speech and speech-to-text capabilities using Microsoft Azure Cognitive Services. ## Usage Example ✅ ```typescript import { AzureVoice } from '@kastrax/voice-azure'; // Initialize with configuration const voice = new AzureVoice({ speechModel: { name: 'neural', apiKey: 'your-azure-speech-api-key', region: 'eastus' }, listeningModel: { name: 'whisper', apiKey: 'your-azure-speech-api-key', region: 'eastus' }, speaker: 'en-US-JennyNeural' // Default voice }); // Convert text to speech const audioStream = await voice.speak('Hello, how can I help you?', { speaker: 'en-US-GuyNeural', // Override default voice style: 'cheerful' // Voice style }); // Convert speech to text const text = await voice.listen(audioStream, { filetype: 'wav', language: 'en-US' }); ``` ## Configuration ✅ ### Constructor Options ### AzureSpeechConfig ## Methods ✅ ### speak() Converts text to speech using Azure's neural text-to-speech service. Returns: `Promise` ### listen() Transcribes audio using Azure's speech-to-text service. Returns: `Promise` ### getSpeakers() Returns an array of available voice options, where each node contains: ## Notes ✅ - API keys can be provided via constructor options or environment variables (AZURE_SPEECH_KEY and AZURE_SPEECH_REGION) - Azure offers a wide range of neural voices across many languages - Some voices support speaking styles like cheerful, sad, angry, etc. - Speech recognition supports multiple audio formats and languages - Azure's speech services provide high-quality neural voices with natural-sounding speech --- title: "Reference: Cloudflare Voice | Voice Providers | Kastrax Docs" description: "Documentation for the CloudflareVoice class, providing text-to-speech capabilities using Cloudflare Workers AI." --- # Cloudflare ✅ [EN] Source: https://kastrax.ai/en/reference/voice/cloudflare The CloudflareVoice class in Kastrax provides text-to-speech capabilities using Cloudflare Workers AI. This provider specializes in efficient, low-latency speech synthesis suitable for edge computing environments. ## Usage Example ✅ ```typescript import { CloudflareVoice } from '@kastrax/voice-cloudflare'; // Initialize with configuration const voice = new CloudflareVoice({ speechModel: { name: '@cf/meta/m2m100-1.2b', apiKey: 'your-cloudflare-api-token', accountId: 'your-cloudflare-account-id' }, speaker: 'en-US-1' // Default voice }); // Convert text to speech const audioStream = await voice.speak('Hello, how can I help you?', { speaker: 'en-US-2', // Override default voice }); // Get available voices const speakers = await voice.getSpeakers(); console.log(speakers); ``` ## Configuration ✅ ### Constructor Options ### CloudflareSpeechConfig ## Methods ✅ ### speak() Converts text to speech using Cloudflare's text-to-speech service. Returns: `Promise` ### getSpeakers() Returns an array of available voice options, where each node contains: ## Notes ✅ - API tokens can be provided via constructor options or environment variables (CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID) - Cloudflare Workers AI is optimized for edge computing with low latency - This provider only supports text-to-speech (TTS) functionality, not speech-to-text (STT) - The service integrates well with other Cloudflare Workers products - For production use, ensure your Cloudflare account has the appropriate Workers AI subscription - Voice options are more limited compared to some other providers, but performance at the edge is excellent ## Related Providers ✅ If you need speech-to-text capabilities in addition to text-to-speech, consider using one of these providers: - [OpenAI](./openai) - Provides both TTS and STT - [Google](./google) - Provides both TTS and STT - [Azure](./azure) - Provides both TTS and STT --- title: "Reference: CompositeVoice | Voice Providers | Kastrax Docs" description: "Documentation for the CompositeVoice class, which enables combining multiple voice providers for flexible text-to-speech and speech-to-text operations." --- # CompositeVoice ✅ [EN] Source: https://kastrax.ai/en/reference/voice/composite-voice The CompositeVoice class allows you to combine different voice providers for text-to-speech and speech-to-text operations. This is particularly useful when you want to use the best provider for each operation - for example, using OpenAI for speech-to-text and PlayAI for text-to-speech. CompositeVoice is used internally by the Agent class to provide flexible voice capabilities. ## Usage Example ✅ ```typescript import { CompositeVoice } from "@kastrax/core/voice"; import { OpenAIVoice } from "@kastrax/voice-openai"; import { PlayAIVoice } from "@kastrax/voice-playai"; // Create voice providers const openai = new OpenAIVoice(); const playai = new PlayAIVoice(); // Use OpenAI for listening (speech-to-text) and PlayAI for speaking (text-to-speech) const voice = new CompositeVoice({ input: openai, output: playai }); // Convert speech to text using OpenAI const text = await voice.listen(audioStream); // Convert text to speech using PlayAI const audio = await voice.speak("Hello, world!"); ``` ## Constructor Parameters ✅ ## Methods ✅ ### speak() Converts text to speech using the configured speaking provider. Notes: - If no speaking provider is configured, this method will throw an error - Options are passed through to the configured speaking provider - Returns a stream of audio data ### listen() Converts speech to text using the configured listening provider. Notes: - If no listening provider is configured, this method will throw an error - Options are passed through to the configured listening provider - Returns either a string or a stream of transcribed text, depending on the provider ### getSpeakers() Returns a list of available voices from the speaking provider, where each node contains: Notes: - Returns voices from the speaking provider only - If no speaking provider is configured, returns an empty array - Each voice object will have at least a voiceId property - Additional voice properties depend on the speaking provider --- title: "Reference: Deepgram Voice | Voice Providers | Kastrax Docs" description: "Documentation for the Deepgram voice implementation, providing text-to-speech and speech-to-text capabilities with multiple voice models and languages." --- # Deepgram ✅ [EN] Source: https://kastrax.ai/en/reference/voice/deepgram The Deepgram voice implementation in Kastrax provides text-to-speech (TTS) and speech-to-text (STT) capabilities using Deepgram's API. It supports multiple voice models and languages, with configurable options for both speech synthesis and transcription. ## Usage Example ✅ ```typescript import { DeepgramVoice } from "@kastrax/voice-deepgram"; // Initialize with default configuration (uses DEEPGRAM_API_KEY environment variable) const voice = new DeepgramVoice(); // Initialize with custom configuration const voice = new DeepgramVoice({ speechModel: { name: 'aura', apiKey: 'your-api-key', }, listeningModel: { name: 'nova-2', apiKey: 'your-api-key', }, speaker: 'asteria-en', }); // Text-to-Speech const audioStream = await voice.speak("Hello, world!"); // Speech-to-Text const transcript = await voice.listen(audioStream); ``` ## Constructor Parameters ✅ ### DeepgramVoiceConfig ", description: "Additional properties to pass to the Deepgram API", isOptional: true, }, { name: "language", type: "string", description: "Language code for the model", isOptional: true, }, ]} /> ## Methods ✅ ### speak() Converts text to speech using the configured speech model and voice. Returns: `Promise` ### listen() Converts speech to text using the configured listening model. Returns: `Promise` ### getSpeakers() Returns a list of available voice options. --- title: "Reference: ElevenLabs Voice | Voice Providers | Kastrax Docs" description: "Documentation for the ElevenLabs voice implementation, offering high-quality text-to-speech capabilities with multiple voice models and natural-sounding synthesis." --- # ElevenLabs ✅ [EN] Source: https://kastrax.ai/en/reference/voice/elevenlabs The ElevenLabs voice implementation in Kastrax provides high-quality text-to-speech (TTS) and speech-to-text (STT) capabilities using the ElevenLabs API. ## Usage Example ✅ ```typescript import { ElevenLabsVoice } from "@kastrax/voice-elevenlabs"; // Initialize with default configuration (uses ELEVENLABS_API_KEY environment variable) const voice = new ElevenLabsVoice(); // Initialize with custom configuration const voice = new ElevenLabsVoice({ speechModel: { name: 'eleven_multilingual_v2', apiKey: 'your-api-key', }, speaker: 'custom-speaker-id', }); // Text-to-Speech const audioStream = await voice.speak("Hello, world!"); // Get available speakers const speakers = await voice.getSpeakers(); ``` ## Constructor Parameters ✅ ### ElevenLabsVoiceConfig ## Methods ✅ ### speak() Converts text to speech using the configured speech model and voice. Returns: `Promise` ### getSpeakers() Returns an array of available voice options, where each node contains: ### listen() Converts audio input to text using ElevenLabs Speech-to-Text API. The options object supports the following properties: Returns: `Promise` - A Promise that resolves to the transcribed text ## Important Notes ✅ 1. An ElevenLabs API key is required. Set it via the `ELEVENLABS_API_KEY` environment variable or pass it in the constructor. 2. The default speaker is set to Aria (ID: '9BWtsMINqrJLrRacOk9x'). 3. Speech-to-text functionality is not supported by ElevenLabs. 4. Available speakers can be retrieved using the `getSpeakers()` method, which returns detailed information about each voice including language and gender. --- title: "Reference: Google Voice | Voice Providers | Kastrax Docs" description: "Documentation for the Google Voice implementation, providing text-to-speech and speech-to-text capabilities." --- # Google ✅ [EN] Source: https://kastrax.ai/en/reference/voice/google The Google Voice implementation in Kastrax provides both text-to-speech (TTS) and speech-to-text (STT) capabilities using Google Cloud services. It supports multiple voices, languages, and advanced audio configuration options. ## Usage Example ✅ ```typescript import { GoogleVoice } from "@kastrax/voice-google"; // Initialize with default configuration (uses GOOGLE_API_KEY environment variable) const voice = new GoogleVoice(); // Initialize with custom configuration const voice = new GoogleVoice({ speechModel: { apiKey: 'your-speech-api-key', }, listeningModel: { apiKey: 'your-listening-api-key', }, speaker: 'en-US-Casual-K', }); // Text-to-Speech const audioStream = await voice.speak("Hello, world!", { languageCode: 'en-US', audioConfig: { audioEncoding: 'LINEAR16', }, }); // Speech-to-Text const transcript = await voice.listen(audioStream, { config: { encoding: 'LINEAR16', languageCode: 'en-US', }, }); // Get available voices for a specific language const voices = await voice.getSpeakers({ languageCode: 'en-US' }); ``` ## Constructor Parameters ✅ ### GoogleModelConfig ## Methods ✅ ### speak() Converts text to speech using Google Cloud Text-to-Speech service. Returns: `Promise` ### listen() Converts speech to text using Google Cloud Speech-to-Text service. Returns: `Promise` ### getSpeakers() Returns an array of available voice options, where each node contains: ## Important Notes ✅ 1. A Google Cloud API key is required. Set it via the `GOOGLE_API_KEY` environment variable or pass it in the constructor. 2. The default voice is set to 'en-US-Casual-K'. 3. Both text-to-speech and speech-to-text services use LINEAR16 as the default audio encoding. 4. The `speak()` method supports advanced audio configuration through the Google Cloud Text-to-Speech API. 5. The `listen()` method supports various recognition configurations through the Google Cloud Speech-to-Text API. 6. Available voices can be filtered by language code using the `getSpeakers()` method. --- title: "Reference: KastraxVoice | Voice Providers | Kastrax Docs" description: "Documentation for the KastraxVoice abstract base class, which defines the core interface for all voice services in Kastrax, including speech-to-speech capabilities." --- # KastraxVoice ✅ [EN] Source: https://kastrax.ai/en/reference/voice/kastrax-voice The KastraxVoice class is an abstract base class that defines the core interface for voice services in Kastrax. All voice provider implementations (like OpenAI, Deepgram, PlayAI, Speechify) extend this class to provide their specific functionality. The class now includes support for real-time speech-to-speech capabilities through WebSocket connections. ## Usage Example ✅ ```typescript import { KastraxVoice } from "@kastrax/core/voice"; // Create a voice provider implementation class MyVoiceProvider extends KastraxVoice { constructor(config: { speechModel?: BuiltInModelConfig; listeningModel?: BuiltInModelConfig; speaker?: string; realtimeConfig?: { model?: string; apiKey?: string; options?: unknown; }; }) { super({ speechModel: config.speechModel, listeningModel: config.listeningModel, speaker: config.speaker, realtimeConfig: config.realtimeConfig }); } // Implement required abstract methods async speak(input: string | NodeJS.ReadableStream, options?: { speaker?: string }): Promise { // Implement text-to-speech conversion } async listen(audioStream: NodeJS.ReadableStream, options?: unknown): Promise { // Implement speech-to-text conversion } async getSpeakers(): Promise> { // Return list of available voices } // Optional speech-to-speech methods async connect(): Promise { // Establish WebSocket connection for speech-to-speech communication } async send(audioData: NodeJS.ReadableStream | Int16Array): Promise { // Stream audio data in speech-to-speech } async answer(): Promise { // Trigger voice provider to respond } addTools(tools: Array): void { // Add tools for the voice provider to use } close(): void { // Close WebSocket connection } on(event: string, callback: (data: unknown) => void): void { // Register event listener } off(event: string, callback: (data: unknown) => void): void { // Remove event listener } } ``` ## Constructor Parameters ✅ ### BuiltInModelConfig ### RealtimeConfig ## Abstract Methods ✅ These methods must be implemented by unknown class extending KastraxVoice. ### speak() Converts text to speech using the configured speech model. ```typescript abstract speak( input: string | NodeJS.ReadableStream, options?: { speaker?: string; [key: string]: unknown; } ): Promise ``` Purpose: - Takes text input and converts it to speech using the provider's text-to-speech service - Supports both string and stream input for flexibility - Allows overriding the default speaker/voice through options - Returns a stream of audio data that can be played or saved - May return void if the audio is handled by emitting 'speaking' event ### listen() Converts speech to text using the configured listening model. ```typescript abstract listen( audioStream: NodeJS.ReadableStream, options?: { [key: string]: unknown; } ): Promise ``` Purpose: - Takes an audio stream and converts it to text using the provider's speech-to-text service - Supports provider-specific options for transcription configuration - Can return either a complete text transcription or a stream of transcribed text - Not all providers support this functionality (e.g., PlayAI, Speechify) - May return void if the transcription is handled by emitting 'writing' event ### getSpeakers() Returns a list of available voices supported by the provider. ```typescript abstract getSpeakers(): Promise> ``` Purpose: - Retrieves the list of available voices/speakers from the provider - Each voice must have at least a voiceId property - Providers can include additional metadata about each voice - Used to discover available voices for text-to-speech conversion ## Optional Methods ✅ These methods have default implementations but can be overridden by voice providers that support speech-to-speech capabilities. ### connect() Establishes a WebSocket or WebRTC connection for communication. ```typescript connect(config?: unknown): Promise ``` Purpose: - Initializes a connection to the voice service for communication - Must be called before using features like send() or answer() - Returns a Promise that resolves when the connection is established - Configuration is provider-specific ### send() Streams audio data in real-time to the voice provider. ```typescript send(audioData: NodeJS.ReadableStream | Int16Array): Promise ``` Purpose: - Sends audio data to the voice provider for real-time processing - Useful for continuous audio streaming scenarios like live microphone input - Supports both ReadableStream and Int16Array audio formats - Must be in connected state before calling this method ### answer() Triggers the voice provider to generate a response. ```typescript answer(): Promise ``` Purpose: - Sends a signal to the voice provider to generate a response - Used in real-time conversations to prompt the AI to respond - Response will be emitted through the event system (e.g., 'speaking' event) ### addTools() Equips the voice provider with tools that can be used during conversations. ```typescript addTools(tools: Array): void ``` Purpose: - Adds tools that the voice provider can use during conversations - Tools can extend the capabilities of the voice provider - Implementation is provider-specific ### close() Disconnects from the WebSocket or WebRTC connection. ```typescript close(): void ``` Purpose: - Closes the connection to the voice service - Cleans up resources and stops any ongoing real-time processing - Should be called when you're done with the voice instance ### on() Registers an event listener for voice events. ```typescript on( event: E, callback: (data: E extends keyof VoiceEventMap ? VoiceEventMap[E] : unknown) => void, ): void ``` Purpose: - Registers a callback function to be called when the specified event occurs - Standard events include 'speaking', 'writing', and 'error' - Providers can emit custom events as well - Event data structure depends on the event type ### off() Removes an event listener. ```typescript off( event: E, callback: (data: E extends keyof VoiceEventMap ? VoiceEventMap[E] : unknown) => void, ): void ``` Purpose: - Removes a previously registered event listener - Used to clean up event handlers when they're no longer needed ## Event System ✅ The KastraxVoice class includes an event system for real-time communication. Standard event types include: ## Protected Properties ✅ ## Telemetry Support ✅ KastraxVoice includes built-in telemetry support through the `traced` method, which wraps method calls with performance tracking and error monitoring. ## Notes ✅ - KastraxVoice is an abstract class and cannot be instantiated directly - Implementations must provide concrete implementations for all abstract methods - The class provides a consistent interface across different voice service providers - Speech-to-speech capabilities are optional and provider-specific - The event system enables asynchronous communication for real-time interactions - Telemetry is automatically handled for all method calls --- title: "Reference: Murf Voice | Voice Providers | Kastrax Docs" description: "Documentation for the Murf voice implementation, providing text-to-speech capabilities." --- # Murf ✅ [EN] Source: https://kastrax.ai/en/reference/voice/murf The Murf voice implementation in Kastrax provides text-to-speech (TTS) capabilities using Murf's AI voice service. It supports multiple voices across different languages. ## Usage Example ✅ ```typescript import { MurfVoice } from "@kastrax/voice-murf"; // Initialize with default configuration (uses MURF_API_KEY environment variable) const voice = new MurfVoice(); // Initialize with custom configuration const voice = new MurfVoice({ speechModel: { name: 'GEN2', apiKey: 'your-api-key', properties: { format: 'MP3', rate: 1.0, pitch: 1.0, sampleRate: 48000, channelType: 'STEREO', }, }, speaker: 'en-US-cooper', }); // Text-to-Speech with default settings const audioStream = await voice.speak("Hello, world!"); // Text-to-Speech with custom properties const audioStream = await voice.speak("Hello, world!", { speaker: 'en-UK-hazel', properties: { format: 'WAV', rate: 1.2, style: 'casual', }, }); // Get available voices const voices = await voice.getSpeakers(); ``` ## Constructor Parameters ✅ ### MurfConfig ### Speech Properties ", description: "Custom pronunciation mappings", isOptional: true, }, { name: "encodeAsBase64", type: "boolean", description: "Whether to encode the audio as base64", isOptional: true, }, { name: "variation", type: "number", description: "Voice variation parameter", isOptional: true, }, { name: "audioDuration", type: "number", description: "Target audio duration in seconds", isOptional: true, }, { name: "multiNativeLocale", type: "string", description: "Locale for multilingual support", isOptional: true, }, ]} /> ## Methods ✅ ### speak() Converts text to speech using Murf's API. Returns: `Promise` ### getSpeakers() Returns an array of available voice options, where each node contains: ### listen() This method is not supported by Murf and will throw an error. Murf does not provide speech-to-text functionality. ## Important Notes ✅ 1. A Murf API key is required. Set it via the `MURF_API_KEY` environment variable or pass it in the constructor. 2. The service uses GEN2 as the default model version. 3. Speech properties can be set at the constructor level and overridden per request. 4. The service supports extensive audio customization through properties like format, sample rate, and channel type. 5. Speech-to-text functionality is not supported. --- title: "Reference: OpenAI Realtime Voice | Voice Providers | Kastrax Docs" description: "Documentation for the OpenAIRealtimeVoice class, providing real-time text-to-speech and speech-to-text capabilities via WebSockets." --- # OpenAI Realtime Voice ✅ [EN] Source: https://kastrax.ai/en/reference/voice/openai-realtime The OpenAIRealtimeVoice class provides real-time voice interaction capabilities using OpenAI's WebSocket-based API. It supports real time speech to speech, voice activity detection, and event-based audio streaming. ## Usage Example ✅ ```typescript import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import { playAudio, getMicrophoneStream } from "@kastrax/node-audio"; // Initialize with default configuration using environment variables const voice = new OpenAIRealtimeVoice(); // Or initialize with specific configuration const voiceWithConfig = new OpenAIRealtimeVoice({ chatModel: { apiKey: 'your-openai-api-key', model: 'gpt-4o-mini-realtime-preview-2024-12-17', options: { sessionConfig: { turn_detection: { type: 'server_vad', threshold: 0.6, silence_duration_ms: 1200 } } } }, speaker: 'alloy' // Default voice }); // Establish connection await voice.connect(); // Set up event listeners voice.on('speaker', ({ audio }) => { // Handle audio data (Int16Array) pcm format by default playAudio(audio); }); voice.on('writing', ({ text, role }) => { // Handle transcribed text console.log(`${role}: ${text}`); }); // Convert text to speech await voice.speak('Hello, how can I help you today?', { speaker: 'echo' // Override default voice }); // Process audio input const microphoneStream = getMicrophoneStream(); await voice.send(microphoneStream); // When done, disconnect voice.connect(); ``` ## Configuration ✅ ### Constructor Options ### chatModel ### options ### Voice Activity Detection (VAD) Configuration ## Methods ✅ ### connect() Establishes a connection to the OpenAI realtime service. Must be called before using speak, listen, or send functions. ", description: "Promise that resolves when the connection is established.", }, ]} /> ### speak() Emits a speaking event using the configured voice model. Can accept either a string or a readable stream as input. Returns: `Promise` ### listen() Processes audio input for speech recognition. Takes a readable stream of audio data and emits a 'listening' event with the transcribed text. Returns: `Promise` ### send() Streams audio data in real-time to the OpenAI service for continuous audio streaming scenarios like live microphone input. Returns: `Promise` ### updateConfig() Updates the session configuration for the voice instance. This can be used to modify voice settings, turn detection, and other parameters. Returns: `void` ### addTools() Adds a set of tools to the voice instance. Tools allow the model to perform additional actions during conversations. When OpenAIRealtimeVoice is added to an Agent, any tools configured for the Agent will automatically be available to the voice interface. Returns: `void` ### close() Disconnects from the OpenAI realtime session and cleans up resources. Should be called when you're done with the voice instance. Returns: `void` ### getSpeakers() Returns a list of available voice speakers. Returns: `Promise>` ### on() Registers an event listener for voice events. Returns: `void` ### off() Removes a previously registered event listener. Returns: `void` ## Events ✅ The OpenAIRealtimeVoice class emits the following events: ### OpenAI Realtime Events You can also listen to [OpenAI Realtime utility events](https://github.com/openai/openai-realtime-api-beta#reference-client-utility-events) by prefixing with 'openAIRealtime:': ## Available Voices ✅ The following voice options are available: - `alloy`: Neutral and balanced - `ash`: Clear and precise - `ballad`: Melodic and smooth - `coral`: Warm and friendly - `echo`: Resonant and deep - `sage`: Calm and thoughtful - `shimmer`: Bright and energetic - `verse`: Versatile and expressive ## Notes ✅ - API keys can be provided via constructor options or the `OPENAI_API_KEY` environment variable - The OpenAI Realtime Voice API uses WebSockets for real-time communication - Server-side Voice Activity Detection (VAD) provides better accuracy for speech detection - All audio data is processed as Int16Array format - The voice instance must be connected with `connect()` before using other methods - Always call `close()` when done to properly clean up resources - Memory management is handled by OpenAI Realtime API --- title: "Reference: OpenAI Voice | Voice Providers | Kastrax Docs" description: "Documentation for the OpenAIVoice class, providing text-to-speech and speech-to-text capabilities." --- # OpenAI ✅ [EN] Source: https://kastrax.ai/en/reference/voice/openai The OpenAIVoice class in Kastrax provides text-to-speech and speech-to-text capabilities using OpenAI's models. ## Usage Example ✅ ```typescript import { OpenAIVoice } from '@kastrax/voice-openai'; // Initialize with default configuration using environment variables const voice = new OpenAIVoice(); // Or initialize with specific configuration const voiceWithConfig = new OpenAIVoice({ speechModel: { name: 'tts-1-hd', apiKey: 'your-openai-api-key' }, listeningModel: { name: 'whisper-1', apiKey: 'your-openai-api-key' }, speaker: 'alloy' // Default voice }); // Convert text to speech const audioStream = await voice.speak('Hello, how can I help you?', { speaker: 'nova', // Override default voice speed: 1.2 // Adjust speech speed }); // Convert speech to text const text = await voice.listen(audioStream, { filetype: 'mp3' }); ``` ## Configuration ✅ ### Constructor Options ### OpenAIConfig ## Methods ✅ ### speak() Converts text to speech using OpenAI's text-to-speech models. Returns: `Promise` ### listen() Transcribes audio using OpenAI's Whisper model. Returns: `Promise` ### getSpeakers() Returns an array of available voice options, where each node contains: ## Notes ✅ - API keys can be provided via constructor options or the `OPENAI_API_KEY` environment variable - The `tts-1-hd` model provides higher quality audio but may have slower processing times - Speech recognition supports multiple audio formats including mp3, wav, and webm --- title: "Reference: PlayAI Voice | Voice Providers | Kastrax Docs" description: "Documentation for the PlayAI voice implementation, providing text-to-speech capabilities." --- # PlayAI ✅ [EN] Source: https://kastrax.ai/en/reference/voice/playai The PlayAI voice implementation in Kastrax provides text-to-speech capabilities using PlayAI's API. ## Usage Example ✅ ```typescript import { PlayAIVoice } from "@kastrax/voice-playai"; // Initialize with default configuration (uses PLAYAI_API_KEY environment variable and PLAYAI_USER_ID environment variable) const voice = new PlayAIVoice(); // Initialize with default configuration const voice = new PlayAIVoice({ speechModel: { name: 'PlayDialog', apiKey: process.env.PLAYAI_API_KEY, userId: process.env.PLAYAI_USER_ID }, speaker: 'Angelo' // Default voice }); // Convert text to speech with a specific voice const audioStream = await voice.speak("Hello, world!", { speaker: 's3://voice-cloning-zero-shot/b27bc13e-996f-4841-b584-4d35801aea98/original/manifest.json' // Dexter voice }); ``` ## Constructor Parameters ✅ ### PlayAIConfig ## Methods ✅ ### speak() Converts text to speech using the configured speech model and voice. Returns: `Promise`. ### getSpeakers() Returns an array of available voice options, where each node contains: ### listen() This method is not supported by PlayAI and will throw an error. PlayAI does not provide speech-to-text functionality. ## Notes ✅ - PlayAI requires both an API key and a user ID for authentication - The service offers two models: 'PlayDialog' and 'Play3.0-mini' - Each voice has a unique S3 manifest ID that must be used when making API calls --- title: "Reference: Sarvam Voice | Voice Providers | Kastrax Docs" description: "Documentation for the Sarvam class, providing text-to-speech and speech-to-text capabilities." --- # Sarvam ✅ [EN] Source: https://kastrax.ai/en/reference/voice/sarvam The SarvamVoice class in Kastrax provides text-to-speech and speech-to-text capabilities using Sarvam AI models. ## Usage Example ✅ ```typescript import { SarvamVoice } from "@kastrax/voice-sarvam"; // Initialize with default configuration using environment variables const voice = new SarvamVoice(); // Or initialize with specific configuration const voiceWithConfig = new SarvamVoice({ speechModel: { model: "bulbul:v1", apiKey: process.env.SARVAM_API_KEY!, language: "en-IN", properties: { pitch: 0, pace: 1.65, loudness: 1.5, speech_sample_rate: 8000, enable_preprocessing: false, eng_interpolation_wt: 123, }, }, listeningModel: { model: "saarika:v2", apiKey: process.env.SARVAM_API_KEY!, languageCode: "en-IN", filetype?: 'wav'; }, speaker: "meera", // Default voice }); // Convert text to speech const audioStream = await voice.speak("Hello, how can I help you?"); // Convert speech to text const text = await voice.listen(audioStream, { filetype: "wav", }); ``` ### Sarvam API Docs - https://docs.sarvam.ai/api-reference-docs/endpoints/text-to-speech ## Configuration ✅ ### Constructor Options ### SarvamVoiceConfig ### SarvamListenOptions ## Methods ✅ ### speak() Converts text to speech using Sarvam's text-to-speech models. Returns: `Promise` ### listen() Transcribes audio using Sarvam's speech recognition models. Returns: `Promise` ### getSpeakers() Returns an array of available voice options. Returns: `Promise>` ## Notes ✅ - API key can be provided via constructor options or the `SARVAM_API_KEY` environment variable - If no API key is provided, the constructor will throw an error - The service communicates with the Sarvam AI API at `https://api.sarvam.ai` - Audio is returned as a stream containing binary audio data - Speech recognition supports mp3 and wav audio formats --- title: "Reference: Speechify Voice | Voice Providers | Kastrax Docs" description: "Documentation for the Speechify voice implementation, providing text-to-speech capabilities." --- # Speechify ✅ [EN] Source: https://kastrax.ai/en/reference/voice/speechify The Speechify voice implementation in Kastrax provides text-to-speech capabilities using Speechify's API. ## Usage Example ✅ ```typescript import { SpeechifyVoice } from "@kastrax/voice-speechify"; // Initialize with default configuration (uses SPEECHIFY_API_KEY environment variable) const voice = new SpeechifyVoice(); // Initialize with custom configuration const voice = new SpeechifyVoice({ speechModel: { name: 'simba-english', apiKey: 'your-api-key' }, speaker: 'george' // Default voice }); // Convert text to speech const audioStream = await voice.speak("Hello, world!", { speaker: 'henry', // Override default voice }); ``` ## Constructor Parameters ✅ ### SpeechifyConfig ## Methods ✅ ### speak() Converts text to speech using the configured speech model and voice. Returns: `Promise` ### getSpeakers() Returns an array of available voice options, where each node contains: ### listen() This method is not supported by Speechify and will throw an error. Speechify does not provide speech-to-text functionality. ## Notes ✅ - Speechify requires an API key for authentication - The default model is 'simba-english' - Speech-to-text functionality is not supported - Additional audio stream options can be passed through the speak() method's options parameter --- title: "Reference: voice.addInstructions() | Voice Providers | Kastrax Docs" description: "Documentation for the addInstructions() method available in voice providers, which adds instructions to guide the voice model's behavior." --- # voice.addInstructions() ✅ [EN] Source: https://kastrax.ai/en/reference/voice/voice.addInstructions The `addInstructions()` method equips a voice provider with instructions that guide the model's behavior during real-time interactions. This is particularly useful for real-time voice providers that maintain context across a conversation. ## Usage Example ✅ ```typescript import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import { Agent } from "@kastrax/core/agent"; import { openai } from "@ai-sdk/openai"; // Initialize a real-time voice provider const voice = new OpenAIRealtimeVoice({ realtimeConfig: { model: "gpt-4o-mini-realtime", apiKey: process.env.OPENAI_API_KEY, }, }); // Create an agent with the voice provider const agent = new Agent({ name: "Customer Support Agent", instructions: "You are a helpful customer support agent for a software company.", model: openai("gpt-4o"), voice, }); // Add additional instructions to the voice provider voice.addInstructions(` When speaking to customers: - Always introduce yourself as the customer support agent - Speak clearly and concisely - Ask clarifying questions when needed - Summarize the conversation at the end `); // Connect to the real-time service await voice.connect(); ``` ## Parameters ✅
## Return Value ✅ This method does not return a value. ## Notes ✅ - Instructions are most effective when they are clear, specific, and relevant to the voice interaction - This method is primarily used with real-time voice providers that maintain conversation context - If called on a voice provider that doesn't support instructions, it will log a warning and do nothing - Instructions added with this method are typically combined with any instructions provided by an associated Agent - For best results, add instructions before starting a conversation (before calling `connect()`) - Multiple calls to `addInstructions()` may either replace or append to existing instructions, depending on the provider implementation --- title: "Reference: voice.addTools() | Voice Providers | Kastrax Docs" description: "Documentation for the addTools() method available in voice providers, which equips voice models with function calling capabilities." --- # voice.addTools() ✅ [EN] Source: https://kastrax.ai/en/reference/voice/voice.addTools The `addTools()` method equips a voice provider with tools (functions) that can be called by the model during real-time interactions. This enables voice assistants to perform actions like searching for information, making calculations, or interacting with external systems. ## Usage Example ✅ ```typescript import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import { createTool } from "@kastrax/core/tools"; import { z } from "zod"; // Define tools const weatherTool = createTool({ id: "getWeather", description: "Get the current weather for a location", inputSchema: z.object({ location: z.string().describe("The city and state, e.g. San Francisco, CA"), }), outputSchema: z.object({ message: z.string(), }), execute: async ({ context }) => { // Fetch weather data from an API const response = await fetch(`https://api.weather.com?location=${encodeURIComponent(context.location)}`); const data = await response.json(); return { message: `The current temperature in ${context.location} is ${data.temperature}°F with ${data.conditions}.` }; }, }); // Initialize a real-time voice provider const voice = new OpenAIRealtimeVoice({ realtimeConfig: { model: "gpt-4o-mini-realtime", apiKey: process.env.OPENAI_API_KEY, }, }); // Add tools to the voice provider voice.addTools({ getWeather: weatherTool, }); // Connect to the real-time service await voice.connect(); ``` ## Parameters ✅
## Return Value ✅ This method does not return a value. ## Notes ✅ - Tools must follow the Kastrax tool format with name, description, input schema, and execute function - This method is primarily used with real-time voice providers that support function calling - If called on a voice provider that doesn't support tools, it will log a warning and do nothing - Tools added with this method are typically combined with any tools provided by an associated Agent - For best results, add tools before starting a conversation (before calling `connect()`) - The voice provider will automatically handle the invocation of tool handlers when the model decides to use them - Multiple calls to `addTools()` may either replace or merge with existing tools, depending on the provider implementation --- title: "Reference: voice.answer() | Voice Providers | Kastrax Docs" description: "Documentation for the answer() method available in real-time voice providers, which triggers the voice provider to generate a response." --- # voice.answer() ✅ [EN] Source: https://kastrax.ai/en/reference/voice/voice.answer The `answer()` method is used in real-time voice providers to trigger the AI to generate a response. This method is particularly useful in speech-to-speech conversations where you need to explicitly signal the AI to respond after receiving user input. ## Usage Example ✅ ```typescript import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import { getMicrophoneStream } from "@kastrax/node-audio"; import Speaker from "@kastrax/node-speaker"; const speaker = new Speaker({ sampleRate: 24100, // Audio sample rate in Hz - standard for high-quality audio on MacBook Pro channels: 1, // Mono audio output (as opposed to stereo which would be 2) bitDepth: 16, // Bit depth for audio quality - CD quality standard (16-bit resolution) }); // Initialize a real-time voice provider const voice = new OpenAIRealtimeVoice({ realtimeConfig: { model: "gpt-4o", apiKey: process.env.OPENAI_API_KEY, }, speaker: "alloy", // Default voice }); // Connect to the real-time service await voice.connect(); // Register event listener for responses voice.on("speaker", (stream) => { // Handle audio response stream.pipe(speaker); }); // Send user audio input const microphoneStream = getMicrophoneStream(); await voice.send(microphoneStream); // Trigger the AI to respond await voice.answer(); ``` ## Parameters ✅
", description: "Provider-specific options for the response", isOptional: true, } ]} /> ## Return Value ✅ Returns a `Promise` that resolves when the response has been triggered. ## Notes ✅ - This method is only implemented by real-time voice providers that support speech-to-speech capabilities - If called on a voice provider that doesn't support this functionality, it will log a warning and resolve immediately - The response audio will typically be emitted through the 'speaking' event rather than returned directly - For providers that support it, you can use this method to send a specific response instead of having the AI generate one - This method is commonly used in conjunction with `send()` to create a conversational flow --- title: "Reference: voice.close() | Voice Providers | Kastrax Docs" description: "Documentation for the close() method available in voice providers, which disconnects from real-time voice services." --- # voice.close() ✅ [EN] Source: https://kastrax.ai/en/reference/voice/voice.close The `close()` method disconnects from a real-time voice service and cleans up resources. This is important for properly ending voice sessions and preventing resource leaks. ## Usage Example ✅ ```typescript import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import { getMicrophoneStream } from "@kastrax/node-audio"; // Initialize a real-time voice provider const voice = new OpenAIRealtimeVoice({ realtimeConfig: { model: "gpt-4o-mini-realtime", apiKey: process.env.OPENAI_API_KEY, }, }); // Connect to the real-time service await voice.connect(); // Start a conversation voice.speak("Hello, I'm your AI assistant!"); // Stream audio from a microphone const microphoneStream = getMicrophoneStream(); voice.send(microphoneStream); // When the conversation is complete setTimeout(() => { // Close the connection and clean up resources voice.close(); console.log("Voice session ended"); }, 60000); // End after 1 minute ``` ## Parameters ✅ This method does not accept any parameters. ## Return Value ✅ This method does not return a value. ## Notes ✅ - Always call `close()` when you're done with a real-time voice session to free up resources - After calling `close()`, you'll need to call `connect()` again if you want to start a new session - This method is primarily used with real-time voice providers that maintain persistent connections - If called on a voice provider that doesn't support real-time connections, it will log a warning and do nothing - Failing to close connections can lead to resource leaks and potential billing issues with voice service providers --- title: "Reference: voice.connect() | Voice Providers | Kastrax Docs" description: "Documentation for the connect() method available in real-time voice providers, which establishes a connection for speech-to-speech communication." --- # voice.connect() ✅ [EN] Source: https://kastrax.ai/en/reference/voice/voice.connect The `connect()` method establishes a WebSocket or WebRTC connection for real-time speech-to-speech communication. This method must be called before using other real-time features like `send()` or `answer()`. ## Usage Example ✅ ```typescript import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import Speaker from "@kastrax/node-speaker"; const speaker = new Speaker({ sampleRate: 24100, // Audio sample rate in Hz - standard for high-quality audio on MacBook Pro channels: 1, // Mono audio output (as opposed to stereo which would be 2) bitDepth: 16, // Bit depth for audio quality - CD quality standard (16-bit resolution) }); // Initialize a real-time voice provider const voice = new OpenAIRealtimeVoice({ realtimeConfig: { model: "gpt-4o-mini-realtime", apiKey: process.env.OPENAI_API_KEY, options: { sessionConfig: { turn_detection: { type: "server_vad", threshold: 0.6, silence_duration_ms: 1200, }, }, }, }, speaker: "alloy", // Default voice }); // Connect to the real-time service await voice.connect(); // Now you can use real-time features voice.on("speaker", (stream) => { stream.pipe(speaker); }); // With connection options await voice.connect({ timeout: 10000, // 10 seconds timeout reconnect: true, }); ``` ## Parameters ✅ ", description: "Provider-specific connection options", isOptional: true, } ]} /> ## Return Value ✅ Returns a `Promise` that resolves when the connection is successfully established. ## Provider-Specific Options ✅ Each real-time voice provider may support different options for the `connect()` method: ### OpenAI Realtime ## Using with CompositeVoice ✅ When using `CompositeVoice`, the `connect()` method delegates to the configured real-time provider: ```typescript import { CompositeVoice } from "@kastrax/core/voice"; import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; const realtimeVoice = new OpenAIRealtimeVoice(); const voice = new CompositeVoice({ realtimeProvider: realtimeVoice, }); // This will use the OpenAIRealtimeVoice provider await voice.connect(); ``` ## Notes ✅ - This method is only implemented by real-time voice providers that support speech-to-speech capabilities - If called on a voice provider that doesn't support this functionality, it will log a warning and resolve immediately - The connection must be established before using other real-time methods like `send()` or `answer()` - When you're done with the voice instance, call `close()` to properly clean up resources - Some providers may automatically reconnect on connection loss, depending on their implementation - Connection errors will typically be thrown as exceptions that should be caught and handled ## Related Methods ✅ - [voice.send()](./voice.send) - Sends audio data to the voice provider - [voice.answer()](./voice.answer) - Triggers the voice provider to respond - [voice.close()](./voice.close) - Disconnects from the real-time service - [voice.on()](./voice.on) - Registers an event listener for voice events --- title: "Reference: Voice Events | Voice Providers | Kastrax Docs" description: "Documentation for events emitted by voice providers, particularly for real-time voice interactions." --- # Voice Events [EN] Source: https://kastrax.ai/en/reference/voice/voice.events Voice providers emit various events during real-time voice interactions. These events can be listened to using the [voice.on()](./voice.on) method and are particularly important for building interactive voice applications. ## Common Events These events are commonly implemented across real-time voice providers: ## Notes - Not all events are supported by all voice providers - The exact payload structure may vary between providers - For non-real-time providers, most of these events will not be emitted - Events are useful for building interactive UIs that respond to the conversation state - Consider using the [voice.off()](./voice.off) method to remove event listeners when they are no longer needed --- title: "Reference: voice.getSpeakers() | Voice Providers | Kastrax Docs" description: "Documentation for the getSpeakers() method available in voice providers, which retrieves available voice options." --- import { Tabs } from "nextra/components"; # voice.getSpeakers() ✅ [EN] Source: https://kastrax.ai/en/reference/voice/voice.getSpeakers The `getSpeakers()` method retrieves a list of available voice options (speakers) from the voice provider. This allows applications to present users with voice choices or programmatically select the most appropriate voice for different contexts. ## Usage Example ✅ ```typescript import { OpenAIVoice } from "@kastrax/voice-openai"; import { ElevenLabsVoice } from "@kastrax/voice-elevenlabs"; // Initialize voice providers const openaiVoice = new OpenAIVoice(); const elevenLabsVoice = new ElevenLabsVoice({ apiKey: process.env.ELEVENLABS_API_KEY }); // Get available speakers from OpenAI const openaiSpeakers = await openaiVoice.getSpeakers(); console.log("OpenAI voices:", openaiSpeakers); // Example output: [{ voiceId: "alloy" }, { voiceId: "echo" }, { voiceId: "fable" }, ...] // Get available speakers from ElevenLabs const elevenLabsSpeakers = await elevenLabsVoice.getSpeakers(); console.log("ElevenLabs voices:", elevenLabsSpeakers); // Example output: [{ voiceId: "21m00Tcm4TlvDq8ikWAM", name: "Rachel" }, ...] // Use a specific voice for speech const text = "Hello, this is a test of different voices."; await openaiVoice.speak(text, { speaker: openaiSpeakers[2].voiceId }); await elevenLabsVoice.speak(text, { speaker: elevenLabsSpeakers[0].voiceId }); ``` ## Parameters ✅ This method does not accept any parameters. ## Return Value ✅ >", type: "Promise", description: "A promise that resolves to an array of voice options, where each option contains at least a voiceId property and may include additional provider-specific metadata.", } ]} /> ## Provider-Specific Metadata ✅ Different voice providers return different metadata for their voices: ## Notes ✅ - The available voices vary significantly between providers - Some providers may require authentication to retrieve the full list of voices - The default implementation returns an empty array if the provider doesn't support this method - For performance reasons, consider caching the results if you need to display the list frequently - The `voiceId` property is guaranteed to be present for all providers, but additional metadata varies --- title: "Reference: voice.listen() | Voice Providers | Kastrax Docs" description: "Documentation for the listen() method available in all Kastrax voice providers, which converts speech to text." --- # voice.listen() ✅ [EN] Source: https://kastrax.ai/en/reference/voice/voice.listen The `listen()` method is a core function available in all Kastrax voice providers that converts speech to text. It takes an audio stream as input and returns the transcribed text. ## Usage Example ✅ ```typescript import { OpenAIVoice } from "@kastrax/voice-openai"; import { getMicrophoneStream } from "@kastrax/node-audio"; import { createReadStream } from "fs"; import path from "path"; // Initialize a voice provider const voice = new OpenAIVoice({ listeningModel: { name: "whisper-1", apiKey: process.env.OPENAI_API_KEY, }, }); // Basic usage with a file stream const audioFilePath = path.join(process.cwd(), "audio.mp3"); const audioStream = createReadStream(audioFilePath); const transcript = await voice.listen(audioStream, { filetype: "mp3", }); console.log("Transcribed text:", transcript); // Using a microphone stream const microphoneStream = getMicrophoneStream(); // Assume this function gets audio input const transcription = await voice.listen(microphoneStream); // With provider-specific options const transcriptWithOptions = await voice.listen(audioStream, { language: "en", prompt: "This is a conversation about artificial intelligence.", }); ``` ## Parameters ✅ ## Return Value ✅ Returns one of the following: - `Promise`: A promise that resolves to the transcribed text - `Promise`: A promise that resolves to a stream of transcribed text (for streaming transcription) - `Promise`: For real-time providers that emit 'writing' events instead of returning text directly ## Provider-Specific Options ✅ Each voice provider may support additional options specific to their implementation. Here are some examples: ### OpenAI ### Google ### Deepgram ## Realtime Voice Providers ✅ When using realtime voice providers like `OpenAIRealtimeVoice`, the `listen()` method behaves differently: - Instead of returning transcribed text, it emits 'writing' events with the transcribed text - You need to register an event listener to receive the transcription ```typescript import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import { getMicrophoneStream } from "@kastrax/node-audio"; const voice = new OpenAIRealtimeVoice(); await voice.connect(); // Register event listener for transcription voice.on("writing", ({ text, role }) => { console.log(`${role}: ${text}`); }); // This will emit 'writing' events instead of returning text const microphoneStream = getMicrophoneStream(); await voice.listen(microphoneStream); ``` ## Using with CompositeVoice ✅ When using `CompositeVoice`, the `listen()` method delegates to the configured listening provider: ```typescript import { CompositeVoice } from "@kastrax/core/voice"; import { OpenAIVoice } from "@kastrax/voice-openai"; import { PlayAIVoice } from "@kastrax/voice-playai"; const voice = new CompositeVoice({ listenProvider: new OpenAIVoice(), speakProvider: new PlayAIVoice(), }); // This will use the OpenAIVoice provider const transcript = await voice.listen(audioStream); ``` ## Notes ✅ - Not all voice providers support speech-to-text functionality (e.g., PlayAI, Speechify) - The behavior of `listen()` may vary slightly between providers, but all implementations follow the same basic interface - When using a realtime voice provider, the method might not return text directly but instead emit a 'writing' event - The audio format supported depends on the provider. Common formats include MP3, WAV, and M4A - Some providers support streaming transcription, where text is returned as it's transcribed - For best performance, consider closing or ending the audio stream when you're done with it ## Related Methods ✅ - [voice.speak()](./voice.speak) - Converts text to speech - [voice.send()](./voice.send) - Sends audio data to the voice provider in real-time - [voice.on()](./voice.on) - Registers an event listener for voice events --- title: "Reference: voice.off() | Voice Providers | Kastrax Docs" description: "Documentation for the off() method available in voice providers, which removes event listeners for voice events." --- # voice.off() ✅ [EN] Source: https://kastrax.ai/en/reference/voice/voice.off The `off()` method removes event listeners previously registered with the `on()` method. This is particularly useful for cleaning up resources and preventing memory leaks in long-running applications with real-time voice capabilities. ## Usage Example ✅ ```typescript import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import chalk from "chalk"; // Initialize a real-time voice provider const voice = new OpenAIRealtimeVoice({ realtimeConfig: { model: "gpt-4o-mini-realtime", apiKey: process.env.OPENAI_API_KEY, }, }); // Connect to the real-time service await voice.connect(); // Define the callback function const writingCallback = ({ text, role }) => { if (role === 'user') { process.stdout.write(chalk.green(text)); } else { process.stdout.write(chalk.blue(text)); } }; // Register event listener voice.on("writing", writingCallback); // Later, when you want to remove the listener voice.off("writing", writingCallback); ``` ## Parameters ✅
## Return Value ✅ This method does not return a value. ## Notes ✅ - The callback passed to `off()` must be the same function reference that was passed to `on()` - If the callback is not found, the method will have no effect - This method is primarily used with real-time voice providers that support event-based communication - If called on a voice provider that doesn't support events, it will log a warning and do nothing - Removing event listeners is important for preventing memory leaks in long-running applications --- title: "Reference: voice.on() | Voice Providers | Kastrax Docs" description: "Documentation for the on() method available in voice providers, which registers event listeners for voice events." --- # voice.on() ✅ [EN] Source: https://kastrax.ai/en/reference/voice/voice.on The `on()` method registers event listeners for various voice events. This is particularly important for real-time voice providers, where events are used to communicate transcribed text, audio responses, and other state changes. ## Usage Example ✅ ```typescript import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import Speaker from "@kastrax/node-speaker"; import chalk from "chalk"; // Initialize a real-time voice provider const voice = new OpenAIRealtimeVoice({ realtimeConfig: { model: "gpt-4o-mini-realtime", apiKey: process.env.OPENAI_API_KEY, }, }); // Connect to the real-time service await voice.connect(); // Register event listener for transcribed text voice.on("writing", (event) => { if (event.role === 'user') { process.stdout.write(chalk.green(event.text)); } else { process.stdout.write(chalk.blue(event.text)); } }); // Listen for audio data and play it const speaker = new Speaker({ sampleRate: 24100, channels: 1, bitDepth: 16, }); voice.on("speaker", (stream) => { stream.pipe(speaker); }); // Register event listener for errors voice.on("error", ({ message, code, details }) => { console.error(`Error ${code}: ${message}`, details); }); ``` ## Parameters ✅
## Return Value ✅ This method does not return a value. ## Events ✅ For a comprehensive list of events and their payload structures, see the [Voice Events](./voice.events) documentation. Common events include: - `speaking`: Emitted when audio data is available - `speaker`: Emitted with a stream that can be piped to audio output - `writing`: Emitted when text is transcribed or generated - `error`: Emitted when an error occurs - `tool-call-start`: Emitted when a tool is about to be executed - `tool-call-result`: Emitted when a tool execution is complete Different voice providers may support different sets of events with varying payload structures. ## Using with CompositeVoice ✅ When using `CompositeVoice`, the `on()` method delegates to the configured real-time provider: ```typescript import { CompositeVoice } from "@kastrax/core/voice"; import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import Speaker from "@kastrax/node-speaker"; const speaker = new Speaker({ sampleRate: 24100, // Audio sample rate in Hz - standard for high-quality audio on MacBook Pro channels: 1, // Mono audio output (as opposed to stereo which would be 2) bitDepth: 16, // Bit depth for audio quality - CD quality standard (16-bit resolution) }); const realtimeVoice = new OpenAIRealtimeVoice(); const voice = new CompositeVoice({ realtimeProvider: realtimeVoice, }); // Connect to the real-time service await voice.connect(); // This will register the event listener with the OpenAIRealtimeVoice provider voice.on("speaker", (stream) => { stream.pipe(speaker) }); ``` ## Notes ✅ - This method is primarily used with real-time voice providers that support event-based communication - If called on a voice provider that doesn't support events, it will log a warning and do nothing - Event listeners should be registered before calling methods that might emit events - To remove an event listener, use the [voice.off()](./voice.off) method with the same event name and callback function - Multiple listeners can be registered for the same event - The callback function will receive different data depending on the event type (see [Voice Events](./voice.events)) - For best performance, consider removing event listeners when they are no longer needed --- title: "Reference: voice.send() | Voice Providers | Kastrax Docs" description: "Documentation for the send() method available in real-time voice providers, which streams audio data for continuous processing." --- # voice.send() ✅ [EN] Source: https://kastrax.ai/en/reference/voice/voice.send The `send()` method streams audio data in real-time to voice providers for continuous processing. This method is essential for real-time speech-to-speech conversations, allowing you to send microphone input directly to the AI service. ## Usage Example ✅ ```typescript import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import Speaker from "@kastrax/node-speaker"; import { getMicrophoneStream } from "@kastrax/node-audio"; const speaker = new Speaker({ sampleRate: 24100, // Audio sample rate in Hz - standard for high-quality audio on MacBook Pro channels: 1, // Mono audio output (as opposed to stereo which would be 2) bitDepth: 16, // Bit depth for audio quality - CD quality standard (16-bit resolution) }); // Initialize a real-time voice provider const voice = new OpenAIRealtimeVoice({ realtimeConfig: { model: "gpt-4o-mini-realtime", apiKey: process.env.OPENAI_API_KEY, }, }); // Connect to the real-time service await voice.connect(); // Set up event listeners for responses voice.on("writing", ({ text, role }) => { console.log(`${role}: ${text}`); }); voice.on("speaker", (stream) => { stream.pipe(speaker) }); // Get microphone stream (implementation depends on your environment) const microphoneStream = getMicrophoneStream(); // Send audio data to the voice provider await voice.send(microphoneStream); // You can also send audio data as Int16Array const audioBuffer = getAudioBuffer(); // Assume this returns Int16Array await voice.send(audioBuffer); ``` ## Parameters ✅
## Return Value ✅ Returns a `Promise` that resolves when the audio data has been accepted by the voice provider. ## Notes ✅ - This method is only implemented by real-time voice providers that support speech-to-speech capabilities - If called on a voice provider that doesn't support this functionality, it will log a warning and resolve immediately - You must call `connect()` before using `send()` to establish the WebSocket connection - The audio format requirements depend on the specific voice provider - For continuous conversation, you typically call `send()` to transmit user audio, then `answer()` to trigger the AI response - The provider will typically emit 'writing' events with transcribed text as it processes the audio - When the AI responds, the provider will emit 'speaking' events with the audio response --- title: "Reference: voice.speak() | Voice Providers | Kastrax Docs" description: "Documentation for the speak() method available in all Kastrax voice providers, which converts text to speech." --- # voice.speak() ✅ [EN] Source: https://kastrax.ai/en/reference/voice/voice.speak The `speak()` method is a core function available in all Kastrax voice providers that converts text to speech. It takes text input and returns an audio stream that can be played or saved. ## Usage Example ✅ ```typescript import { OpenAIVoice } from "@kastrax/voice-openai"; // Initialize a voice provider const voice = new OpenAIVoice({ speaker: "alloy", // Default voice }); // Basic usage with default settings const audioStream = await voice.speak("Hello, world!"); // Using a different voice for this specific request const audioStreamWithDifferentVoice = await voice.speak("Hello again!", { speaker: "nova", }); // Using provider-specific options const audioStreamWithOptions = await voice.speak("Hello with options!", { speaker: "echo", speed: 1.2, // OpenAI-specific option }); // Using a text stream as input import { Readable } from "stream"; const textStream = Readable.from(["Hello", " from", " a", " stream!"]); const audioStreamFromTextStream = await voice.speak(textStream); ``` ## Parameters ✅ ## Return Value ✅ Returns a `Promise` where: - `NodeJS.ReadableStream`: A stream of audio data that can be played or saved - `void`: When using a realtime voice provider that emits audio through events instead of returning it directly ## Provider-Specific Options ✅ Each voice provider may support additional options specific to their implementation. Here are some examples: ### OpenAI ### ElevenLabs ### Google ### Murf ## Realtime Voice Providers ✅ When using realtime voice providers like `OpenAIRealtimeVoice`, the `speak()` method behaves differently: - Instead of returning an audio stream, it emits a 'speaking' event with the audio data - You need to register an event listener to receive the audio chunks ```typescript import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; import Speaker from "@kastrax/node-speaker"; const speaker = new Speaker({ sampleRate: 24100, // Audio sample rate in Hz - standard for high-quality audio on MacBook Pro channels: 1, // Mono audio output (as opposed to stereo which would be 2) bitDepth: 16, // Bit depth for audio quality - CD quality standard (16-bit resolution) }); const voice = new OpenAIRealtimeVoice(); await voice.connect(); // Register event listener for audio chunks voice.on("speaker", (stream) => { // Handle audio chunk (e.g., play it or save it) stream.pipe(speaker) }); // This will emit 'speaking' events instead of returning a stream await voice.speak("Hello, this is realtime speech!"); ``` ## Using with CompositeVoice ✅ When using `CompositeVoice`, the `speak()` method delegates to the configured speaking provider: ```typescript import { CompositeVoice } from "@kastrax/core/voice"; import { OpenAIVoice } from "@kastrax/voice-openai"; import { PlayAIVoice } from "@kastrax/voice-playai"; const voice = new CompositeVoice({ speakProvider: new PlayAIVoice(), listenProvider: new OpenAIVoice(), }); // This will use the PlayAIVoice provider const audioStream = await voice.speak("Hello, world!"); ``` ## Notes ✅ - The behavior of `speak()` may vary slightly between providers, but all implementations follow the same basic interface. - When using a realtime voice provider, the method might not return an audio stream directly but instead emit a 'speaking' event. - If a text stream is provided as input, the provider will typically convert it to a string before processing. - The audio format of the returned stream depends on the provider. Common formats include MP3, WAV, and OGG. - For best performance, consider closing or ending the audio stream when you're done with it. --- title: "Reference: voice.updateConfig() | Voice Providers | Kastrax Docs" description: "Documentation for the updateConfig() method available in voice providers, which updates the configuration of a voice provider at runtime." --- # voice.updateConfig() ✅ [EN] Source: https://kastrax.ai/en/reference/voice/voice.updateConfig The `updateConfig()` method allows you to update the configuration of a voice provider at runtime. This is useful for changing voice settings, API keys, or other provider-specific options without creating a new instance. ## Usage Example ✅ ```typescript import { OpenAIRealtimeVoice } from "@kastrax/voice-openai-realtime"; // Initialize a real-time voice provider const voice = new OpenAIRealtimeVoice({ realtimeConfig: { model: "gpt-4o-mini-realtime", apiKey: process.env.OPENAI_API_KEY, }, speaker: "alloy", }); // Connect to the real-time service await voice.connect(); // Later, update the configuration voice.updateConfig({ voice: "nova", // Change the default voice turn_detection: { type: "server_vad", threshold: 0.5, silence_duration_ms: 1000 } }); // The next speak() call will use the new configuration await voice.speak("Hello with my new voice!"); ``` ## Parameters ✅
", description: "Configuration options to update. The specific properties depend on the voice provider.", isOptional: false, } ]} /> ## Return Value ✅ This method does not return a value. ## Configuration Options ✅ Different voice providers support different configuration options: ### OpenAI Realtime
## Notes ✅ - The default implementation logs a warning if the provider doesn't support this method - Configuration updates are typically applied to subsequent operations, not ongoing ones - Not all properties that can be set in the constructor can be updated at runtime - The specific behavior depends on the voice provider implementation - For real-time voice providers, some configuration changes may require reconnecting to the service --- title: "Reference: .after() | Building Workflows | Kastrax Docs" description: Documentation for the `after()` method in workflows, enabling branching and merging paths. --- # .after() [EN] Source: https://kastrax.ai/en/reference/workflows/after The `.after()` method defines explicit dependencies between workflow steps, enabling branching and merging paths in your workflow execution. ## Usage ### Basic Branching ```typescript workflow .step(stepA) .then(stepB) .after(stepA) // Create new branch after stepA completes .step(stepC); ``` ### Merging Multiple Branches ```typescript workflow .step(stepA) .then(stepB) .step(stepC) .then(stepD) .after([stepB, stepD]) // Create a step that depends on multiple steps .step(stepE); ``` ## Parameters ## Returns ## Examples ### Single Dependency ```typescript workflow .step(fetchData) .then(processData) .after(fetchData) // Branch after fetchData .step(logData); ``` ### Multiple Dependencies (Merging Branches) ```typescript workflow .step(fetchUserData) .then(validateUserData) .step(fetchProductData) .then(validateProductData) .after([validateUserData, validateProductData]) // Wait for both validations to complete .step(processOrder); ``` ## Related - [Branching Paths example](../../examples/workflows/branching-paths.mdx) - [Workflow Class Reference](./workflow.mdx) - [Step Reference](./step-class.mdx) - [Control Flow Guide](../../docs/workflows/control-flow.mdx) --- title: ".afterEvent() Method | Kastrax Docs" description: "Reference for the afterEvent method in Kastrax workflows that creates event-based suspension points." --- # afterEvent() [EN] Source: https://kastrax.ai/en/reference/workflows/afterEvent The `afterEvent()` method creates a suspension point in your workflow that waits for a specific event to occur before continuing execution. ## Syntax ```typescript workflow.afterEvent(eventName: string): Workflow ``` ## Parameters | Parameter | Type | Description | |-----------|------|-------------| | eventName | string | The name of the event to wait for. Must match an event defined in the workflow's `events` configuration. | ## Return Value Returns the workflow instance for method chaining. ## Description The `afterEvent()` method is used to create an automatic suspension point in your workflow that waits for a specific named event. It's essentially a declarative way to define a point where your workflow should pause and wait for an external event to occur. When you call `afterEvent()`, Kastrax: 1. Creates a special step with ID `__eventName_event` 2. This step automatically suspends the workflow execution 3. The workflow remains suspended until the specified event is triggered via `resumeWithEvent()` 4. When the event occurs, execution continues with the step following the `afterEvent()` call This method is part of Kastrax's event-driven workflow capabilities, allowing you to create workflows that coordinate with external systems or user interactions without manually implementing suspension logic. ## Usage Notes - The event specified in `afterEvent()` must be defined in the workflow's `events` configuration with a schema - The special step created has a predictable ID format: `__eventName_event` (e.g., `__approvalReceived_event`) - Any step following `afterEvent()` can access the event data via `context.inputData.resumedEvent` - Event data is validated against the schema defined for that event when `resumeWithEvent()` is called ## Examples ### Basic Usage ```typescript // Define workflow with events const workflow = new Workflow({ name: 'approval-workflow', events: { approval: { schema: z.object({ approved: z.boolean(), approverName: z.string(), }), }, }, }); // Build workflow with event suspension point workflow .step(submitRequest) .afterEvent('approval') // Workflow suspends here .step(processApproval) // This step runs after the event occurs .commit(); ``` ## Related - [Event-Driven Workflows](./events.mdx) - [resumeWithEvent()](./resumeWithEvent.mdx) - [Suspend and Resume](../../docs/workflows/suspend-and-resume.mdx) - [Workflow Class](./workflow.mdx) --- title: "Reference: Workflow.commit() | Running Workflows | Kastrax Docs" description: Documentation for the `.commit()` method in workflows, which re-initializes the workflow machine with the current step configuration. --- # Workflow.commit() [EN] Source: https://kastrax.ai/en/reference/workflows/commit The `.commit()` method re-initializes the workflow's state machine with the current step configuration. ## Usage ```typescript workflow .step(stepA) .then(stepB) .commit(); ``` ## Returns ## Related - [Branching Paths example](../../examples/workflows/branching-paths.mdx) - [Workflow Class Reference](./workflow.mdx) - [Step Reference](./step-class.mdx) - [Control Flow Guide](../../docs/workflows/control-flow.mdx) --- title: "Reference: Workflow.createRun() | Running Workflows | Kastrax Docs" description: "Documentation for the `.createRun()` method in workflows, which initializes a new workflow run instance." --- # Workflow.createRun() [EN] Source: https://kastrax.ai/en/reference/workflows/createRun The `.createRun()` method initializes a new workflow run instance. It generates a unique run ID for tracking and returns a start function that begins workflow execution when called. One reason to use `.createRun()` vs `.execute()` is to get a unique run ID for tracking, logging, or subscribing via `.watch()`. ## Usage ```typescript const { runId, start, watch } = workflow.createRun(); const result = await start(); ``` ## Returns Promise", description: "Function that begins workflow execution when called", }, { name: "watch", type: "(callback: (record: WorkflowResult) => void) => () => void", description: "Function that accepts a callback function that will be called with each transition of the workflow run", }, { name: "resume", type: "({stepId: string, context: Record}) => Promise", description: "Function that resumes a workflow run from a given step ID and context", }, { name: "resumeWithEvent", type: "(eventName: string, data: any) => Promise", description: "Function that resumes a workflow run from a given event name and data", }, ]} /> ## Error Handling The start function may throw validation errors if the workflow configuration is invalid: ```typescript try { const { runId, start, watch, resume, resumeWithEvent } = workflow.createRun(); await start({ triggerData: data }); } catch (error) { if (error instanceof ValidationError) { // Handle validation errors console.log(error.type); // 'circular_dependency' | 'no_terminal_path' | 'unreachable_step' console.log(error.details); } } ``` ## Related - [Workflow Class Reference](./workflow.mdx) - [Step Class Reference](./step-class.mdx) - See the [Creating a Workflow](../../../examples/workflows/creating-a-workflow.mdx) example for complete usage ``` ``` --- title: "Reference: Workflow.else() | Conditional Branching | Kastrax Docs" description: "Documentation for the `.else()` method in Kastrax workflows, which creates an alternative branch when an if condition is false." --- # Workflow.else() [EN] Source: https://kastrax.ai/en/reference/workflows/else > Experimental The `.else()` method creates an alternative branch in the workflow that executes when the preceding `if` condition evaluates to false. This enables workflows to follow different paths based on conditions. ## Usage ```typescript copy showLineNumbers workflow .step(startStep) .if(async ({ context }) => { const value = context.getStepResult<{ value: number }>('start')?.value; return value < 10; }) .then(ifBranchStep) .else() // Alternative branch when the condition is false .then(elseBranchStep) .commit(); ``` ## Parameters The `else()` method does not take any parameters. ## Returns ## Behavior - The `else()` method must follow an `if()` branch in the workflow definition - It creates a branch that executes only when the preceding `if` condition evaluates to false - You can chain multiple steps after an `else()` using `.then()` - You can nest additional `if`/`else` conditions within an `else` branch ## Error Handling The `else()` method requires a preceding `if()` statement. If you try to use it without a preceding `if`, an error will be thrown: ```typescript try { // This will throw an error workflow .step(someStep) .else() .then(anotherStep) .commit(); } catch (error) { console.error(error); // "No active condition found" } ``` ## Related - [if Reference](./if.mdx) - [then Reference](./then.mdx) - [Control Flow Guide](../../docs/workflows/control-flow.mdx) - [Step Condition Reference](./step-condition.mdx) --- title: "Event-Driven Workflows | Kastrax Docs" description: "Learn how to create event-driven workflows using afterEvent and resumeWithEvent methods in Kastrax." --- # Event-Driven Workflows ✅ [EN] Source: https://kastrax.ai/en/reference/workflows/events Kastrax provides built-in support for event-driven workflows through the `afterEvent` and `resumeWithEvent` methods. These methods allow you to create workflows that pause execution while waiting for specific events to occur, then resume with the event data when it's available. ## Overview ✅ Event-driven workflows are useful for scenarios where: - You need to wait for external systems to complete processing - User approval or input is required at specific points - Asynchronous operations need to be coordinated - Long-running processes need to break up execution across different services ## Defining Events ✅ Before using event-driven methods, you must define the events your workflow will listen for in the workflow configuration: ```typescript import { Workflow } from '@kastrax/core/workflows'; import { z } from 'zod'; const workflow = new Workflow({ name: 'approval-workflow', triggerSchema: z.object({ requestId: z.string() }), events: { // Define events with their validation schemas approvalReceived: { schema: z.object({ approved: z.boolean(), approverName: z.string(), comment: z.string().optional(), }), }, documentUploaded: { schema: z.object({ documentId: z.string(), documentType: z.enum(['invoice', 'receipt', 'contract']), metadata: z.record(z.string()).optional(), }), }, }, }); ``` Each event must have a name and a schema that defines the structure of data expected when the event occurs. ## afterEvent() ✅ The `afterEvent` method creates a suspension point in your workflow that automatically waits for a specific event. ### Syntax ```typescript workflow.afterEvent(eventName: string): Workflow ``` ### Parameters - `eventName`: The name of the event to wait for (must be defined in the workflow's `events` configuration) ### Return Value Returns the workflow instance for method chaining. ### How It Works When `afterEvent` is called, Kastrax: 1. Creates a special step with ID `__eventName_event` 2. Configures this step to automatically suspend workflow execution 3. Sets up the continuation point after the event is received ### Usage Example ```typescript workflow .step(initialProcessStep) .afterEvent('approvalReceived') // Workflow suspends here .step(postApprovalStep) // This runs after event is received .then(finalStep) .commit(); ``` ## resumeWithEvent() ✅ The `resumeWithEvent` method resumes a suspended workflow by providing data for a specific event. ### Syntax ```typescript run.resumeWithEvent(eventName: string, data: any): Promise ``` ### Parameters - `eventName`: The name of the event being triggered - `data`: The event data (must conform to the schema defined for this event) ### Return Value Returns a Promise that resolves to the workflow execution results after resumption. ### How It Works When `resumeWithEvent` is called, Kastrax: 1. Validates the event data against the schema defined for that event 2. Loads the workflow snapshot 3. Updates the context with the event data 4. Resumes execution from the event step 5. Continues workflow execution with the subsequent steps ### Usage Example ```typescript // Create a workflow run const run = workflow.createRun(); // Start the workflow await run.start({ triggerData: { requestId: 'req-123' } }); // Later, when the event occurs: const result = await run.resumeWithEvent('approvalReceived', { approved: true, approverName: 'John Doe', comment: 'Looks good to me!' }); console.log(result.results); ``` ## Accessing Event Data ✅ When a workflow is resumed with event data, that data is available in the step context as `context.inputData.resumedEvent`: ```typescript const processApprovalStep = new Step({ id: 'processApproval', execute: async ({ context }) => { // Access the event data const eventData = context.inputData.resumedEvent; return { processingResult: `Processed approval from ${eventData.approverName}`, wasApproved: eventData.approved, }; }, }); ``` ## Multiple Events ✅ You can create workflows that wait for multiple different events at various points: ```typescript workflow .step(createRequest) .afterEvent('approvalReceived') .step(processApproval) .afterEvent('documentUploaded') .step(processDocument) .commit(); ``` When resuming a workflow with multiple event suspension points, you need to provide the correct event name and data for the current suspension point. ## Practical Example ✅ This example shows a complete workflow that requires both approval and document upload: ```typescript import { Workflow, Step } from '@kastrax/core/workflows'; import { z } from 'zod'; // Define steps const createRequest = new Step({ id: 'createRequest', execute: async () => ({ requestId: `req-${Date.now()}` }), }); const processApproval = new Step({ id: 'processApproval', execute: async ({ context }) => { const approvalData = context.inputData.resumedEvent; return { approved: approvalData.approved, approver: approvalData.approverName, }; }, }); const processDocument = new Step({ id: 'processDocument', execute: async ({ context }) => { const documentData = context.inputData.resumedEvent; return { documentId: documentData.documentId, processed: true, type: documentData.documentType, }; }, }); const finalizeRequest = new Step({ id: 'finalizeRequest', execute: async ({ context }) => { const requestId = context.steps.createRequest.output.requestId; const approved = context.steps.processApproval.output.approved; const documentId = context.steps.processDocument.output.documentId; return { finalized: true, summary: `Request ${requestId} was ${approved ? 'approved' : 'rejected'} with document ${documentId}` }; }, }); // Create workflow const requestWorkflow = new Workflow({ name: 'document-request-workflow', events: { approvalReceived: { schema: z.object({ approved: z.boolean(), approverName: z.string(), }), }, documentUploaded: { schema: z.object({ documentId: z.string(), documentType: z.enum(['invoice', 'receipt', 'contract']), }), }, }, }); // Build workflow requestWorkflow .step(createRequest) .afterEvent('approvalReceived') .step(processApproval) .afterEvent('documentUploaded') .step(processDocument) .then(finalizeRequest) .commit(); // Export workflow export { requestWorkflow }; ``` ### Running the Example Workflow ```typescript import { requestWorkflow } from './workflows'; import { kastrax } from './kastrax'; async function runWorkflow() { // Get the workflow const workflow = kastrax.getWorkflow('document-request-workflow'); const run = workflow.createRun(); // Start the workflow const initialResult = await run.start(); console.log('Workflow started:', initialResult.results); // Simulate receiving approval const afterApprovalResult = await run.resumeWithEvent('approvalReceived', { approved: true, approverName: 'Jane Smith', }); console.log('After approval:', afterApprovalResult.results); // Simulate document upload const finalResult = await run.resumeWithEvent('documentUploaded', { documentId: 'doc-456', documentType: 'invoice', }); console.log('Final result:', finalResult.results); } runWorkflow().catch(console.error); ``` ## Best Practices ✅ 1. **Define Clear Event Schemas**: Use Zod to create precise schemas for event data validation 2. **Use Descriptive Event Names**: Choose event names that clearly communicate their purpose 3. **Handle Missing Events**: Ensure your workflow can handle cases where events don't occur or time out 4. **Include Monitoring**: Use the `watch` method to monitor suspended workflows waiting for events 5. **Consider Timeouts**: Implement timeout mechanisms for events that may never occur 6. **Document Events**: Clearly document the events your workflow depends on for other developers ## Related ✅ - [Suspend and Resume in Workflows](../../docs/workflows/suspend-and-resume.mdx) - [Workflow Class Reference](./workflow.mdx) - [Resume Method Reference](./resume.mdx) - [Watch Method Reference](./watch.mdx) - [After Event Reference](./afterEvent.mdx) - [Resume With Event Reference](./resumeWithEvent.mdx) --- title: "Reference: Workflow.execute() | Workflows | Kastrax Docs" description: "Documentation for the `.execute()` method in Kastrax workflows, which runs workflow steps and returns results." --- # Workflow.execute() [EN] Source: https://kastrax.ai/en/reference/workflows/execute Executes a workflow with the provided trigger data and returns the results. The workflow must be committed before execution. ## Usage Example ```typescript const workflow = new Workflow({ name: "my-workflow", triggerSchema: z.object({ inputValue: z.number() }) }); workflow.step(stepOne).then(stepTwo).commit(); const result = await workflow.execute({ triggerData: { inputValue: 42 } }); ``` ## Parameters ## Returns ", description: "Results from each completed step" }, { name: "status", type: "WorkflowStatus", description: "Final status of the workflow run" } ] } ]} /> ## Additional Examples Execute with run ID: ```typescript const result = await workflow.execute({ runId: "custom-run-id", triggerData: { inputValue: 42 } }); ``` Handle execution results: ```typescript const { runId, results, status } = await workflow.execute({ triggerData: { inputValue: 42 } }); if (status === "COMPLETED") { console.log("Step results:", results); } ``` ### Related - [Workflow.createRun()](./createRun.mdx) - [Workflow.commit()](./commit.mdx) - [Workflow.start()](./start.mdx) --- title: "Reference: Workflow.if() | Conditional Branching | Kastrax Docs" description: "Documentation for the `.if()` method in Kastrax workflows, which creates conditional branches based on specified conditions." --- # Workflow.if() [EN] Source: https://kastrax.ai/en/reference/workflows/if > Experimental The `.if()` method creates a conditional branch in the workflow, allowing steps to execute only when a specified condition is true. This enables dynamic workflow paths based on the results of previous steps. ## Usage ```typescript copy showLineNumbers workflow .step(startStep) .if(async ({ context }) => { const value = context.getStepResult<{ value: number }>('start')?.value; return value < 10; // If true, execute the "if" branch }) .then(ifBranchStep) .else() .then(elseBranchStep) .commit(); ``` ## Parameters ## Condition Types ### Function Condition You can use a function that returns a boolean: ```typescript workflow .step(startStep) .if(async ({ context }) => { const result = context.getStepResult<{ status: string }>('start'); return result?.status === 'success'; // Execute "if" branch when status is "success" }) .then(successStep) .else() .then(failureStep); ``` ### Reference Condition You can use a reference-based condition with comparison operators: ```typescript workflow .step(startStep) .if({ ref: { step: startStep, path: 'value' }, query: { $lt: 10 }, // Execute "if" branch when value is less than 10 }) .then(ifBranchStep) .else() .then(elseBranchStep); ``` ## Returns ## Error Handling The `if` method requires a previous step to be defined. If you try to use it without a preceding step, an error will be thrown: ```typescript try { // This will throw an error workflow .if(async ({ context }) => true) .then(someStep) .commit(); } catch (error) { console.error(error); // "Condition requires a step to be executed after" } ``` ## Related - [else Reference](./else.mdx) - [then Reference](./then.mdx) - [Control Flow Guide](../../docs/workflows/control-flow.mdx) - [Step Condition Reference](./step-condition.mdx) --- title: "Reference: run.resume() | Running Workflows | Kastrax Docs" description: Documentation for the `.resume()` method in workflows, which continues execution of a suspended workflow step. --- # run.resume() [EN] Source: https://kastrax.ai/en/reference/workflows/resume The `.resume()` method continues execution of a suspended workflow step, optionally providing new context data that can be accessed by the step on the inputData property. ## Usage ```typescript copy showLineNumbers await run.resume({ runId: "abc-123", stepId: "stepTwo", context: { secondValue: 100 } }); ``` ## Parameters ### config ", description: "New context data to inject into the step's inputData property", isOptional: true } ]} /> ## Returns ", type: "object", description: "Result of the resumed workflow execution" } ]} /> ## Async/Await Flow When a workflow is resumed, execution continues from the point immediately after the `suspend()` call in the step's execution function. This creates a natural flow in your code: ```typescript // Step definition with suspend point const reviewStep = new Step({ id: "review", execute: async ({ context, suspend }) => { // First part of execution const initialAnalysis = analyzeData(context.inputData.data); if (initialAnalysis.needsReview) { // Suspend execution here await suspend({ analysis: initialAnalysis }); // This code runs after resume() is called // context.inputData now contains any data provided during resume return { reviewedData: enhanceWithFeedback(initialAnalysis, context.inputData.feedback) }; } return { reviewedData: initialAnalysis }; } }); const { runId, resume, start } = workflow.createRun(); await start({ inputData: { data: "some data" } }); // Later, resume the workflow const result = await resume({ runId: "workflow-123", stepId: "review", context: { // This data will be available in `context.inputData` feedback: "Looks good, but improve section 3" } }); ``` ### Execution Flow 1. The workflow runs until it hits `await suspend()` in the `review` step 2. The workflow state is persisted and execution pauses 3. Later, `run.resume()` is called with new context data 4. Execution continues from the point after `suspend()` in the `review` step 5. The new context data (`feedback`) is available to the step on the `inputData` property 6. The step completes and returns its result 7. The workflow continues with subsequent steps ## Error Handling The resume function may throw several types of errors: ```typescript try { await run.resume({ runId, stepId: "stepTwo", context: newData }); } catch (error) { if (error.message === "No snapshot found for workflow run") { // Handle missing workflow state } if (error.message === "Failed to parse workflow snapshot") { // Handle corrupted workflow state } } ``` ## Related - [Suspend and Resume](../../docs/workflows/suspend-and-resume.mdx) - [`suspend` Reference](./suspend.mdx) - [`watch` Reference](./watch.mdx) - [Workflow Class Reference](./workflow.mdx) --- title: ".resumeWithEvent() Method | Kastrax Docs" description: "Reference for the resumeWithEvent method that resumes suspended workflows using event data." --- # resumeWithEvent() ✅ [EN] Source: https://kastrax.ai/en/reference/workflows/resumeWithEvent The `resumeWithEvent()` method resumes workflow execution by providing data for a specific event that the workflow is waiting for. ## Syntax ✅ ```typescript const run = workflow.createRun(); // After the workflow has started and suspended at an event step await run.resumeWithEvent(eventName: string, data: any): Promise ``` ## Parameters ✅ | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------------------------------------------------- | | eventName | string | The name of the event to trigger. Must match an event defined in the workflow's `events` configuration. | | data | any | The event data to provide. Must conform to the schema defined for that event. | ## Return Value ✅ Returns a Promise that resolves to a `WorkflowRunResult` object, containing: - `results`: The result status and output of each step in the workflow - `activePaths`: A map of active workflow paths and their states - `value`: The current state value of the workflow - Other workflow execution metadata ## Description ✅ The `resumeWithEvent()` method is used to resume a workflow that has been suspended at an event step created by the `afterEvent()` method. When called, this method: 1. Validates the provided event data against the schema defined for that event 2. Loads the workflow snapshot from storage 3. Updates the context with the event data in the `resumedEvent` field 4. Resumes execution from the event step 5. Continues workflow execution with the subsequent steps This method is part of Kastrax's event-driven workflow capabilities, allowing you to create workflows that can respond to external events or user interactions. ## Usage Notes ✅ - The workflow must be in a suspended state, specifically at the event step created by `afterEvent(eventName)` - The event data must conform to the schema defined for that event in the workflow configuration - The workflow will continue execution from the point it was suspended - If the workflow is not suspended or is suspended at a different step, this method may throw an error - The event data is made available to subsequent steps via `context.inputData.resumedEvent` ## Examples ✅ ### Basic Usage ```typescript // Define and start a workflow const workflow = kastrax.getWorkflow("approval-workflow"); const run = workflow.createRun(); // Start the workflow await run.start({ triggerData: { requestId: "req-123" } }); // Later, when the approval event occurs: const result = await run.resumeWithEvent("approval", { approved: true, approverName: "John Doe", comment: "Looks good to me!", }); console.log(result.results); ``` ### With Error Handling ```typescript try { const result = await run.resumeWithEvent("paymentReceived", { amount: 100.5, transactionId: "tx-456", paymentMethod: "credit-card", }); console.log("Workflow resumed successfully:", result.results); } catch (error) { console.error("Failed to resume workflow with event:", error); // Handle error - could be invalid event data, workflow not suspended, etc. } ``` ### Monitoring and Auto-Resuming ```typescript // Start a workflow const { start, watch, resumeWithEvent } = workflow.createRun(); // Watch for suspended event steps watch(async ({ activePaths }) => { const isApprovalEventSuspended = activePaths.get("__approval_event")?.status === "suspended"; // Check if suspended at the approval event step if (isApprovalEventSuspended) { console.log("Workflow waiting for approval"); // In a real scenario, you would wait for the actual event // Here we're simulating with a timeout setTimeout(async () => { try { await resumeWithEvent("approval", { approved: true, approverName: "Auto Approver", }); } catch (error) { console.error("Failed to auto-resume workflow:", error); } }, 5000); // Wait 5 seconds before auto-approving } }); // Start the workflow await start({ triggerData: { requestId: "auto-123" } }); ``` ## Related ✅ - [Event-Driven Workflows](./events.mdx) - [afterEvent()](./afterEvent.mdx) - [Suspend and Resume](../../docs/workflows/suspend-and-resume.mdx) - [resume()](./resume.mdx) - [watch()](./watch.mdx) --- title: "Reference: Snapshots | Workflow State Persistence | Kastrax Docs" description: "Technical reference on snapshots in Kastrax - the serialized workflow state that enables suspend and resume functionality" --- # Snapshots ✅ [EN] Source: https://kastrax.ai/en/reference/workflows/snapshots In Kastrax, a snapshot is a serializable representation of a workflow's complete execution state at a specific point in time. Snapshots capture all the information needed to resume a workflow from exactly where it left off, including: - The current state of each step in the workflow - The outputs of completed steps - The execution path taken through the workflow - Any suspended steps and their metadata - The remaining retry attempts for each step - Additional contextual data needed to resume execution Snapshots are automatically created and managed by Kastrax whenever a workflow is suspended, and are persisted to the configured storage system. ## The Role of Snapshots in Suspend and Resume ✅ Snapshots are the key mechanism enabling Kastrax's suspend and resume capabilities. When a workflow step calls `await suspend()`: 1. The workflow execution is paused at that exact point 2. The current state of the workflow is captured as a snapshot 3. The snapshot is persisted to storage 4. The workflow step is marked as "suspended" with a status of `'suspended'` 5. Later, when `resume()` is called on the suspended step, the snapshot is retrieved 6. The workflow execution resumes from exactly where it left off This mechanism provides a powerful way to implement human-in-the-loop workflows, handle rate limiting, wait for external resources, and implement complex branching workflows that may need to pause for extended periods. ## Snapshot Anatomy ✅ A Kastrax workflow snapshot consists of several key components: ```typescript export interface WorkflowRunState { // Core state info value: Record; // Current state machine value context: { // Workflow context steps: Record< string, { // Step execution results status: "success" | "failed" | "suspended" | "waiting" | "skipped"; payload?: any; // Step-specific data error?: string; // Error info if failed } >; triggerData: Record; // Initial trigger data attempts: Record; // Remaining retry attempts inputData: Record; // Initial input data }; activePaths: Array<{ // Currently active execution paths stepPath: string[]; stepId: string; status: string; }>; // Metadata runId: string; // Unique run identifier timestamp: number; // Time snapshot was created // For nested workflows and suspended steps childStates?: Record; // Child workflow states suspendedSteps?: Record; // Mapping of suspended steps } ``` ## How Snapshots Are Saved and Retrieved ✅ Kastrax persists snapshots to the configured storage system. By default, snapshots are saved to a LibSQL database, but can be configured to use other storage providers like Upstash. The snapshots are stored in the `workflow_snapshots` table and identified uniquely by the `run_id` for the associated run when using libsql. Utilizing a persistence layer allows for the snapshots to be persisted across workflow runs, allowing for advanced human-in-the-loop functionality. Read more about [libsql storage](../storage/libsql.mdx) and [upstash storage](../storage/upstash.mdx) here. ### Saving Snapshots When a workflow is suspended, Kastrax automatically persists the workflow snapshot with these steps: 1. The `suspend()` function in a step execution triggers the snapshot process 2. The `WorkflowInstance.suspend()` method records the suspended machine 3. `persistWorkflowSnapshot()` is called to save the current state 4. The snapshot is serialized and stored in the configured database in the `workflow_snapshots` table 5. The storage record includes the workflow name, run ID, and the serialized snapshot ### Retrieving Snapshots When a workflow is resumed, Kastrax retrieves the persisted snapshot with these steps: 1. The `resume()` method is called with a specific step ID 2. The snapshot is loaded from storage using `loadWorkflowSnapshot()` 3. The snapshot is parsed and prepared for resumption 4. The workflow execution is recreated with the snapshot state 5. The suspended step is resumed, and execution continues ## Storage Options for Snapshots ✅ Kastrax provides multiple storage options for persisting snapshots. A `storage` instance is configured on the `Kastrax` class, and is used to setup a snapshot persistence layer for all workflows registered on the `Kastrax` instance. This means that storage is shared across all workflows registered with the same `Kastrax` instance. ### LibSQL (Default) The default storage option is LibSQL, a SQLite-compatible database: ```typescript import { Kastrax } from "@kastrax/core/kastrax"; import { DefaultStorage } from "@kastrax/core/storage/libsql"; const kastrax = new Kastrax({ storage: new DefaultStorage({ config: { url: "file:storage.db", // Local file-based database // For production: // url: process.env.DATABASE_URL, // authToken: process.env.DATABASE_AUTH_TOKEN, }, }), workflows: { weatherWorkflow, travelWorkflow, }, }); ``` ### Upstash (Redis-Compatible) For serverless environments: ```typescript import { Kastrax } from "@kastrax/core/kastrax"; import { UpstashStore } from "@kastrax/upstash"; const kastrax = new Kastrax({ storage: new UpstashStore({ url: process.env.UPSTASH_URL, token: process.env.UPSTASH_TOKEN, }), workflows: { weatherWorkflow, travelWorkflow, }, }); ``` ## Best Practices for Working with Snapshots ✅ 1. **Ensure Serializability**: Any data that needs to be included in the snapshot must be serializable (convertible to JSON). 2. **Minimize Snapshot Size**: Avoid storing large data objects directly in the workflow context. Instead, store references to them (like IDs) and retrieve the data when needed. 3. **Handle Resume Context Carefully**: When resuming a workflow, carefully consider what context to provide. This will be merged with the existing snapshot data. 4. **Set Up Proper Monitoring**: Implement monitoring for suspended workflows, especially long-running ones, to ensure they are properly resumed. 5. **Consider Storage Scaling**: For applications with many suspended workflows, ensure your storage solution is appropriately scaled. ## Advanced Snapshot Patterns ✅ ### Custom Snapshot Metadata When suspending a workflow, you can include custom metadata that can help when resuming: ```typescript await suspend({ reason: "Waiting for customer approval", requiredApprovers: ["manager", "finance"], requestedBy: currentUser, urgency: "high", expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), }); ``` This metadata is stored with the snapshot and available when resuming. ### Conditional Resumption You can implement conditional logic based on the suspend payload when resuming: ```typescript run.watch(async ({ activePaths }) => { const isApprovalStepSuspended = activePaths.get("approval")?.status === "suspended"; if (isApprovalStepSuspended) { const payload = activePaths.get("approval")?.suspendPayload; if (payload.urgency === "high" && currentUser.role === "manager") { await resume({ stepId: "approval", context: { approved: true, approver: currentUser.id }, }); } } }); ``` ## Related ✅ - [Suspend Function Reference](./suspend.mdx) - [Resume Function Reference](./resume.mdx) - [Watch Function Reference](./watch.mdx) - [Suspend and Resume Guide](../../docs/workflows/suspend-and-resume.mdx) --- title: "Reference: start() | Running Workflows | Kastrax Docs" description: "Documentation for the `start()` method in workflows, which begins execution of a workflow run." --- # start() [EN] Source: https://kastrax.ai/en/reference/workflows/start The start function begins execution of a workflow run. It processes all steps in the defined workflow order, handling parallel execution, branching logic, and step dependencies. ## Usage ```typescript copy showLineNumbers const { runId, start } = workflow.createRun(); const result = await start({ triggerData: { inputValue: 42 } }); ``` ## Parameters ### config ", description: "Initial data that matches the workflow's triggerSchema", isOptional: false } ]} /> ## Returns ", description: "Combined output from all completed workflow steps" }, { name: "status", type: "'completed' | 'error' | 'suspended'", description: "Final status of the workflow run" } ]} /> ## Error Handling The start function may throw several types of validation errors: ```typescript copy showLineNumbers try { const result = await start({ triggerData: data }); } catch (error) { if (error instanceof ValidationError) { console.log(error.type); // 'circular_dependency' | 'no_terminal_path' | 'unreachable_step' console.log(error.details); } } ``` ## Related - [Example: Creating a Workflow](../../../examples/workflows/creating-a-workflow.mdx) - [Example: Suspend and Resume](../../../examples/workflows/suspend-and-resume.mdx) - [createRun Reference](./createRun.mdx) - [Workflow Class Reference](./workflow.mdx) - [Step Class Reference](./step-class.mdx) ``` --- title: "Reference: Step | Building Workflows | Kastrax Docs" description: Documentation for the Step class, which defines individual units of work within a workflow. --- # Step ✅ [EN] Source: https://kastrax.ai/en/reference/workflows/step-class The Step class defines individual units of work within a workflow, encapsulating execution logic, data validation, and input/output handling. ## Usage ✅ ```typescript const processOrder = new Step({ id: "processOrder", inputSchema: z.object({ orderId: z.string(), userId: z.string() }), outputSchema: z.object({ status: z.string(), orderId: z.string() }), execute: async ({ context, runId }) => { return { status: "processed", orderId: context.orderId }; } }); ``` ## Constructor Parameters ✅ ", description: "Static data to be merged with variables", required: false }, { name: "execute", type: "(params: ExecuteParams) => Promise", description: "Async function containing step logic", required: true } ]} /> ### ExecuteParams Promise", description: "Function to suspend step execution" }, { name: "kastrax", type: "Kastrax", description: "Access to Kastrax instance" } ]} /> ## Related ✅ - [Workflow Reference](./workflow.mdx) - [Step Configuration Guide](../../docs/workflows/steps.mdx) - [Control Flow Guide](../../docs/workflows/control-flow.mdx) --- title: "Reference: StepCondition | Building Workflows | Kastrax" description: Documentation for the step condition class in workflows, which determines whether a step should execute based on the output of previous steps or trigger data. --- # StepCondition [EN] Source: https://kastrax.ai/en/reference/workflows/step-condition Conditions determine whether a step should execute based on the output of previous steps or trigger data. ## Usage There are three ways to specify conditions: function, query object, and simple path comparison. ### 1. Function Condition ```typescript copy showLineNumbers workflow.step(processOrder, { when: async ({ context }) => { const auth = context?.getStepResult<{status: string}>("auth"); return auth?.status === "authenticated"; } }); ``` ### 2. Query Object ```typescript copy showLineNumbers workflow.step(processOrder, { when: { ref: { step: 'auth', path: 'status' }, query: { $eq: 'authenticated' } } }); ``` ### 3. Simple Path Comparison ```typescript copy showLineNumbers workflow.step(processOrder, { when: { "auth.status": "authenticated" } }); ``` Based on the type of condition, the workflow runner will try to match the condition to one of these types. 1. Simple Path Condition (when there's a dot in the key) 2. Base/Query Condition (when there's a 'ref' property) 3. Function Condition (when it's an async function) ## StepCondition ", description: "MongoDB-style query using sift operators ($eq, $gt, etc)", isOptional: false } ]} /> ## Query The Query object provides MongoDB-style query operators for comparing values from previous steps or trigger data. It supports basic comparison operators like `$eq`, `$gt`, `$lt` as well as array operators like `$in` and `$nin`, and can be combined with and/or operators for complex conditions. This query syntax allows for readable conditional logic for determining whether a step should execute. ## Related - [Step Options Reference](./step-options.mdx) - [Step Function Reference](./step-function.mdx) - [Control Flow Guide](../../docs/workflows/control-flow.mdx) --- title: "Reference: Workflow.step() | Workflows | Kastrax Docs" description: Documentation for the `.step()` method in workflows, which adds a new step to the workflow. --- # Workflow.step() [EN] Source: https://kastrax.ai/en/reference/workflows/step-function The `.step()` method adds a new step to the workflow, optionally configuring its variables and execution conditions. ## Usage ```typescript workflow.step({ id: "stepTwo", outputSchema: z.object({ result: z.number() }), execute: async ({ context }) => { return { result: 42 }; } }); ``` ## Parameters ### StepDefinition Promise", description: "Function containing step logic", isOptional: false } ]} /> ### StepOptions ", description: "Map of variable names to their source references", isOptional: true }, { name: "when", type: "StepCondition", description: "Condition that must be met for step to execute", isOptional: true } ]} /> ## Related - [Basic Usage with Step Instance](../../docs/workflows/steps.mdx) - [Step Class Reference](./step-class.mdx) - [Workflow Class Reference](./workflow.mdx) - [Control Flow Guide](../../docs/workflows/control-flow.mdx) --- title: "Reference: StepOptions | Building Workflows | Kastrax Docs" description: Documentation for the step options in workflows, which control variable mapping, execution conditions, and other runtime behavior. --- # StepOptions [EN] Source: https://kastrax.ai/en/reference/workflows/step-options Configuration options for workflow steps that control variable mapping, execution conditions, and other runtime behavior. ## Usage ```typescript workflow.step(processOrder, { variables: { orderId: { step: 'trigger', path: 'id' }, userId: { step: 'auth', path: 'user.id' } }, when: { ref: { step: 'auth', path: 'status' }, query: { $eq: 'authenticated' } } }); ``` ## Properties ", description: "Maps step input variables to values from other steps", isOptional: true }, { name: "when", type: "StepCondition", description: "Condition that must be met for step execution", isOptional: true } ]} /> ### VariableRef ## Related - [Path Comparison](../../docs/workflows/control-flow.mdx) - [Step Function Reference](./step-function.mdx) - [Step Class Reference](./step-class.mdx) - [Workflow Class Reference](./workflow.mdx) - [Control Flow Guide](../../docs/workflows/control-flow.mdx) --- title: "Step Retries | Error Handling | Kastrax Docs" description: "Automatically retry failed steps in Kastrax workflows with configurable retry policies." --- # Step Retries ✅ [EN] Source: https://kastrax.ai/en/reference/workflows/step-retries Kastrax provides built-in retry mechanisms to handle transient failures in workflow steps. This allows workflows to recover gracefully from temporary issues without requiring manual intervention. ## Overview ✅ When a step in a workflow fails (throws an exception), Kastrax can automatically retry the step execution based on a configurable retry policy. This is useful for handling: - Network connectivity issues - Service unavailability - Rate limiting - Temporary resource constraints - Other transient failures ## Default Behavior ✅ By default, steps do not retry when they fail. This means: - A step will execute once - If it fails, it will immediately mark the step as failed - The workflow will continue to execute any subsequent steps that don't depend on the failed step ## Configuration Options ✅ Retries can be configured at two levels: ### 1. Workflow-level Configuration You can set a default retry configuration for all steps in a workflow: ```typescript const workflow = new Workflow({ name: 'my-workflow', retryConfig: { attempts: 3, // Number of retries (in addition to the initial attempt) delay: 1000, // Delay between retries in milliseconds }, }); ``` ### 2. Step-level Configuration You can also configure retries on individual steps, which will override the workflow-level configuration for that specific step: ```typescript const fetchDataStep = new Step({ id: 'fetchData', execute: async () => { // Fetch data from external API }, retryConfig: { attempts: 5, // This step will retry up to 5 times delay: 2000, // With a 2-second delay between retries }, }); ``` ## Retry Parameters ✅ The `retryConfig` object supports the following parameters: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `attempts` | number | 0 | The number of retry attempts (in addition to the initial attempt) | | `delay` | number | 1000 | Time in milliseconds to wait between retries | ## How Retries Work ✅ When a step fails, Kastrax's retry mechanism: 1. Checks if the step has retry attempts remaining 2. If attempts remain: - Decrements the attempt counter - Transitions the step to a "waiting" state - Waits for the configured delay period - Retries the step execution 3. If no attempts remain or all attempts have been exhausted: - Marks the step as "failed" - Continues workflow execution (for steps that don't depend on the failed step) During retry attempts, the workflow execution remains active but paused for the specific step that is being retried. ## Examples ✅ ### Basic Retry Example ```typescript import { Workflow, Step } from '@kastrax/core/workflows'; // Define a step that might fail const unreliableApiStep = new Step({ id: 'callUnreliableApi', execute: async () => { // Simulate an API call that might fail const random = Math.random(); if (random < 0.7) { throw new Error('API call failed'); } return { data: 'API response data' }; }, retryConfig: { attempts: 3, // Retry up to 3 times delay: 2000, // Wait 2 seconds between attempts }, }); // Create a workflow with the unreliable step const workflow = new Workflow({ name: 'retry-demo-workflow', }); workflow .step(unreliableApiStep) .then(processResultStep) .commit(); ``` ### Workflow-level Retries with Step Override ```typescript import { Workflow, Step } from '@kastrax/core/workflows'; // Create a workflow with default retry configuration const workflow = new Workflow({ name: 'multi-retry-workflow', retryConfig: { attempts: 2, // All steps will retry twice by default delay: 1000, // With a 1-second delay }, }); // This step uses the workflow's default retry configuration const standardStep = new Step({ id: 'standardStep', execute: async () => { // Some operation that might fail }, }); // This step overrides the workflow's retry configuration const criticalStep = new Step({ id: 'criticalStep', execute: async () => { // Critical operation that needs more retry attempts }, retryConfig: { attempts: 5, // Override with 5 retry attempts delay: 5000, // And a longer 5-second delay }, }); // This step disables retries const noRetryStep = new Step({ id: 'noRetryStep', execute: async () => { // Operation that should not retry }, retryConfig: { attempts: 0, // Explicitly disable retries }, }); workflow .step(standardStep) .then(criticalStep) .then(noRetryStep) .commit(); ``` ## Monitoring Retries ✅ You can monitor retry attempts in your logs. Kastrax logs retry-related events at the `debug` level: ``` [DEBUG] Step fetchData failed (runId: abc-123) [DEBUG] Attempt count for step fetchData: 2 remaining attempts (runId: abc-123) [DEBUG] Step fetchData waiting (runId: abc-123) [DEBUG] Step fetchData finished waiting (runId: abc-123) [DEBUG] Step fetchData pending (runId: abc-123) ``` ## Best Practices ✅ 1. **Use Retries for Transient Failures**: Only configure retries for operations that might experience transient failures. For deterministic errors (like validation failures), retries won't help. 2. **Set Appropriate Delays**: Consider using longer delays for external API calls to allow time for services to recover. 3. **Limit Retry Attempts**: Don't set extremely high retry counts as this could cause workflows to run for excessive periods during outages. 4. **Implement Idempotent Operations**: Ensure your step's `execute` function is idempotent (can be called multiple times without side effects) since it may be retried. 5. **Consider Backoff Strategies**: For more advanced scenarios, consider implementing exponential backoff in your step's logic for operations that might be rate-limited. ## Related ✅ - [Step Class Reference](./step-class.mdx) - [Workflow Configuration](./workflow.mdx) - [Error Handling in Workflows](../../docs/workflows/error-handling.mdx) --- title: "Reference: suspend() | Control Flow | Kastrax Docs" description: "Documentation for the suspend function in Kastrax workflows, which pauses execution until resumed." --- # suspend() [EN] Source: https://kastrax.ai/en/reference/workflows/suspend Pauses workflow execution at the current step until explicitly resumed. The workflow state is persisted and can be continued later. ## Usage Example ```typescript const approvalStep = new Step({ id: "needsApproval", execute: async ({ context, suspend }) => { if (context.steps.amount > 1000) { await suspend(); } return { approved: true }; } }); ``` ## Parameters ", description: "Optional data to store with the suspended state", isOptional: true } ]} /> ## Returns ", type: "Promise", description: "Resolves when the workflow is successfully suspended" } ]} /> ## Additional Examples Suspend with metadata: ```typescript const reviewStep = new Step({ id: "review", execute: async ({ context, suspend }) => { await suspend({ reason: "Needs manager approval", requestedBy: context.user }); return { reviewed: true }; } }); ``` ### Related - [Suspend & Resume Workflows](../../docs/workflows/suspend-and-resume.mdx) - [.resume()](./resume.mdx) - [.watch()](./watch.mdx) --- title: "Reference: Workflow.then() | Building Workflows | Kastrax Docs" description: Documentation for the `.then()` method in workflows, which creates sequential dependencies between steps. --- # Workflow.then() [EN] Source: https://kastrax.ai/en/reference/workflows/then The `.then()` method creates a sequential dependency between workflow steps, ensuring steps execute in a specific order. ## Usage ```typescript workflow .step(stepOne) .then(stepTwo) .then(stepThree); ``` ## Parameters ## Returns ## Validation When using `then`: - The previous step must exist in the workflow - Steps cannot form circular dependencies - Each step can only appear once in a sequential chain ## Error Handling ```typescript try { workflow .step(stepA) .then(stepB) .then(stepA) // Will throw error - circular dependency .commit(); } catch (error) { if (error instanceof ValidationError) { console.log(error.type); // 'circular_dependency' console.log(error.details); } } ``` ## Related - [step Reference](./step-class.mdx) - [after Reference](./after.mdx) - [Sequential Steps Example](../../examples/workflows/sequential-steps.mdx) - [Control Flow Guide](../../docs/workflows/control-flow.mdx) --- title: "Reference: Workflow.until() | Looping in Workflows | Kastrax Docs" description: "Documentation for the `.until()` method in Kastrax workflows, which repeats a step until a specified condition becomes true." --- # Workflow.until() ✅ [EN] Source: https://kastrax.ai/en/reference/workflows/until The `.until()` method repeats a step until a specified condition becomes true. This creates a loop that continues executing the specified step until the condition is satisfied. ## Usage ✅ ```typescript workflow .step(incrementStep) .until(condition, incrementStep) .then(finalStep); ``` ## Parameters ✅ ## Condition Types ✅ ### Function Condition You can use a function that returns a boolean: ```typescript workflow .step(incrementStep) .until(async ({ context }) => { const result = context.getStepResult<{ value: number }>('increment'); return (result?.value ?? 0) >= 10; // Stop when value reaches or exceeds 10 }, incrementStep) .then(finalStep); ``` ### Reference Condition You can use a reference-based condition with comparison operators: ```typescript workflow .step(incrementStep) .until( { ref: { step: incrementStep, path: 'value' }, query: { $gte: 10 }, // Stop when value is greater than or equal to 10 }, incrementStep ) .then(finalStep); ``` ## Comparison Operators ✅ When using reference-based conditions, you can use these comparison operators: | Operator | Description | Example | |----------|-------------|---------| | `$eq` | Equal to | `{ $eq: 10 }` | | `$ne` | Not equal to | `{ $ne: 0 }` | | `$gt` | Greater than | `{ $gt: 5 }` | | `$gte` | Greater than or equal to | `{ $gte: 10 }` | | `$lt` | Less than | `{ $lt: 20 }` | | `$lte` | Less than or equal to | `{ $lte: 15 }` | ## Returns ✅ ## Example ✅ ```typescript import { Workflow, Step } from '@kastrax/core'; import { z } from 'zod'; // Create a step that increments a counter const incrementStep = new Step({ id: 'increment', description: 'Increments the counter by 1', outputSchema: z.object({ value: z.number(), }), execute: async ({ context }) => { // Get current value from previous execution or start at 0 const currentValue = context.getStepResult<{ value: number }>('increment')?.value || context.getStepResult<{ startValue: number }>('trigger')?.startValue || 0; // Increment the value const value = currentValue + 1; console.log(`Incrementing to ${value}`); return { value }; }, }); // Create a final step const finalStep = new Step({ id: 'final', description: 'Final step after loop completes', execute: async ({ context }) => { const finalValue = context.getStepResult<{ value: number }>('increment')?.value; console.log(`Loop completed with final value: ${finalValue}`); return { finalValue }; }, }); // Create the workflow const counterWorkflow = new Workflow({ name: 'counter-workflow', triggerSchema: z.object({ startValue: z.number(), targetValue: z.number(), }), }); // Configure the workflow with an until loop counterWorkflow .step(incrementStep) .until(async ({ context }) => { const targetValue = context.triggerData.targetValue; const currentValue = context.getStepResult<{ value: number }>('increment')?.value ?? 0; return currentValue >= targetValue; }, incrementStep) .then(finalStep) .commit(); // Execute the workflow const run = counterWorkflow.createRun(); const result = await run.start({ triggerData: { startValue: 0, targetValue: 5 } }); // Will increment from 0 to 5, then stop and execute finalStep ``` ## Related ✅ - [.while()](./while.mdx) - Loop while a condition is true - [Control Flow Guide](../../docs/workflows/control-flow.mdx) - [Workflow Class Reference](./workflow.mdx) --- title: "Reference: run.watch() | Workflows | Kastrax Docs" description: Documentation for the `.watch()` method in workflows, which monitors the status of a workflow run. --- # run.watch() ✅ [EN] Source: https://kastrax.ai/en/reference/workflows/watch The `.watch()` function subscribes to state changes on a kastrax run, allowing you to monitor execution progress and react to state updates. ## Usage Example ✅ ```typescript import { Workflow } from "@kastrax/core/workflows"; const workflow = new Workflow({ name: "document-processor" }); const run = workflow.createRun(); // Subscribe to state changes const unsubscribe = run.watch(({results, activePaths}) => { console.log('Results:', results); console.log('Active paths:', activePaths); }); // Run the workflow await run.start({ input: { text: "Process this document" } }); // Stop watching unsubscribe(); ``` ## Parameters ✅ void", description: "Function called whenever the workflow state changes", isOptional: false } ]} /> ### WorkflowState Properties ", description: "Outputs from completed workflow steps", isOptional: false }, { name: "activePaths", type: "Map", description: "Current status of each step", isOptional: false }, { name: "runId", type: "string", description: "ID of the workflow run", isOptional: false }, { name: "timestamp", type: "number", description: "Timestamp of the workflow run", isOptional: false } ]} /> ## Returns ✅ void", description: "Function to stop watching workflow state changes" } ]} /> ## Additional Examples ✅ Monitor specific step completion: ```typescript run.watch(({results, activePaths}) => { if (activePaths.get('processDocument')?.status === 'completed') { console.log('Document processing output:', results['processDocument'].output); } }); ``` Error handling: ```typescript run.watch(({results, activePaths}) => { if (activePaths.get('processDocument')?.status === 'failed') { console.error('Document processing failed:', results['processDocument'].error); // Implement error recovery logic } }); ``` ### Related - [Workflow Creation](../../reference/workflows/createRun.mdx) - [Step Configuration](../../reference/workflows/step-class.mdx) --- title: "Reference: Workflow.while() | Looping in Workflows | Kastrax Docs" description: "Documentation for the `.while()` method in Kastrax workflows, which repeats a step as long as a specified condition remains true." --- # Workflow.while() ✅ [EN] Source: https://kastrax.ai/en/reference/workflows/while The `.while()` method repeats a step as long as a specified condition remains true. This creates a loop that continues executing the specified step until the condition becomes false. ## Usage ✅ ```typescript workflow .step(incrementStep) .while(condition, incrementStep) .then(finalStep); ``` ## Parameters ✅ ## Condition Types ✅ ### Function Condition You can use a function that returns a boolean: ```typescript workflow .step(incrementStep) .while(async ({ context }) => { const result = context.getStepResult<{ value: number }>('increment'); return (result?.value ?? 0) < 10; // Continue as long as value is less than 10 }, incrementStep) .then(finalStep); ``` ### Reference Condition You can use a reference-based condition with comparison operators: ```typescript workflow .step(incrementStep) .while( { ref: { step: incrementStep, path: 'value' }, query: { $lt: 10 }, // Continue as long as value is less than 10 }, incrementStep ) .then(finalStep); ``` ## Comparison Operators ✅ When using reference-based conditions, you can use these comparison operators: | Operator | Description | Example | |----------|-------------|---------| | `$eq` | Equal to | `{ $eq: 10 }` | | `$ne` | Not equal to | `{ $ne: 0 }` | | `$gt` | Greater than | `{ $gt: 5 }` | | `$gte` | Greater than or equal to | `{ $gte: 10 }` | | `$lt` | Less than | `{ $lt: 20 }` | | `$lte` | Less than or equal to | `{ $lte: 15 }` | ## Returns ✅ ## Example ✅ ```typescript import { Workflow, Step } from '@kastrax/core'; import { z } from 'zod'; // Create a step that increments a counter const incrementStep = new Step({ id: 'increment', description: 'Increments the counter by 1', outputSchema: z.object({ value: z.number(), }), execute: async ({ context }) => { // Get current value from previous execution or start at 0 const currentValue = context.getStepResult<{ value: number }>('increment')?.value || context.getStepResult<{ startValue: number }>('trigger')?.startValue || 0; // Increment the value const value = currentValue + 1; console.log(`Incrementing to ${value}`); return { value }; }, }); // Create a final step const finalStep = new Step({ id: 'final', description: 'Final step after loop completes', execute: async ({ context }) => { const finalValue = context.getStepResult<{ value: number }>('increment')?.value; console.log(`Loop completed with final value: ${finalValue}`); return { finalValue }; }, }); // Create the workflow const counterWorkflow = new Workflow({ name: 'counter-workflow', triggerSchema: z.object({ startValue: z.number(), targetValue: z.number(), }), }); // Configure the workflow with a while loop counterWorkflow .step(incrementStep) .while( async ({ context }) => { const targetValue = context.triggerData.targetValue; const currentValue = context.getStepResult<{ value: number }>('increment')?.value ?? 0; return currentValue < targetValue; }, incrementStep ) .then(finalStep) .commit(); // Execute the workflow const run = counterWorkflow.createRun(); const result = await run.start({ triggerData: { startValue: 0, targetValue: 5 } }); // Will increment from 0 to 4, then stop and execute finalStep ``` ## Related ✅ - [.until()](./until.mdx) - Loop until a condition becomes true - [Control Flow Guide](../../docs/workflows/control-flow.mdx) - [Workflow Class Reference](./workflow.mdx) --- title: "Reference: Workflow Class | Building Workflows | Kastrax Docs" description: Documentation for the Workflow class in Kastrax, which enables you to create state machines for complex sequences of operations with conditional branching and data validation. --- # Workflow Class ✅ [EN] Source: https://kastrax.ai/en/reference/workflows/workflow The Workflow class enables you to create state machines for complex sequences of operations with conditional branching and data validation. ```ts copy import { Workflow } from "@kastrax/core/workflows"; const workflow = new Workflow({ name: "my-workflow" }); ``` ## API Reference ✅ ### Constructor ", isOptional: true, description: "Optional logger instance for workflow execution details", }, { name: "steps", type: "Step[]", description: "Array of steps to include in the workflow", }, { name: "triggerSchema", type: "z.Schema", description: "Optional schema for validating workflow trigger data", }, ]} /> ### Core Methods #### `step()` Adds a [Step](./step-class.mdx) to the workflow, including transitions to other steps. Returns the workflow instance for chaining. [Learn more about steps](./step-class.mdx). #### `commit()` Validates and finalizes the workflow configuration. Must be called after adding all steps. #### `execute()` Executes the workflow with optional trigger data. Typed based on the [trigger schema](./workflow.mdx#trigger-schemas). ## Trigger Schemas ✅ Trigger schemas validate the initial data passed to a workflow using Zod. ```ts showLineNumbers copy const workflow = new Workflow({ name: "order-process", triggerSchema: z.object({ orderId: z.string(), customer: z.object({ id: z.string(), email: z.string().email(), }), }), }); ``` The schema: - Validates data passed to `execute()` - Provides TypeScript types for your workflow input ## Validation ✅ Workflow validation happens at two key times: ### 1. At Commit Time When you call `.commit()`, the workflow validates: ```ts showLineNumbers copy workflow .step('step1', {...}) .step('step2', {...}) .commit(); // Validates workflow structure ``` - Circular dependencies between steps - Terminal paths (every path must end) - Unreachable steps - Variable references to non-existent steps - Duplicate step IDs ### 2. During Execution When you call `start()`, it validates: ```ts showLineNumbers copy const { runId, start } = workflow.createRun(); // Validates trigger data against schema await start({ triggerData: { orderId: "123", customer: { id: "cust_123", email: "invalid-email", // Will fail validation }, }, }); ``` - Trigger data against trigger schema - Each step's input data against its inputSchema - Variable paths exist in referenced step outputs - Required variables are present ## Workflow Status ✅ A workflow's status indicates its current execution state. The possible values are: ### Example: Handling Different Statuses ```typescript showLineNumbers copy const { runId, start, watch } = workflow.createRun(); watch(async ({ status }) => { switch (status) { case "SUSPENDED": // Handle suspended state break; case "COMPLETED": // Process results break; case "FAILED": // Handle error state break; } }); await start({ triggerData: data }); ``` ## Error Handling ✅ ```ts showLineNumbers copy try { const { runId, start, watch, resume } = workflow.createRun(); await start({ triggerData: data }); } catch (error) { if (error instanceof ValidationError) { // Handle validation errors console.log(error.type); // 'circular_dependency' | 'no_terminal_path' | 'unreachable_step' console.log(error.details); // { stepId?: string, path?: string[] } } } ``` ## Passing Context Between Steps ✅ Steps can access data from previous steps in the workflow through the context object. Each step receives the accumulated context from all previous steps that have executed. ```typescript showLineNumbers copy workflow .step({ id: 'getData', execute: async ({ context }) => { return { data: { id: '123', value: 'example' } }; } }) .step({ id: 'processData', execute: async ({ context }) => { // Access data from previous step through context.steps const previousData = context.steps.getData.output.data; // Process previousData.id and previousData.value } }); ``` The context object: - Contains results from all completed steps in `context.steps` - Provides access to step outputs through `context.steps.[stepId].output` - Is typed based on step output schemas - Is immutable to ensure data consistency ## Related Documentation ✅ - [Step](./step-class.mdx) - [.then()](./then.mdx) - [.step()](./step-function.mdx) - [.after()](./after.mdx) --- title: 'Showcase' description: 'Check out these applications built with Kastrax' --- [EN] Source: https://kastrax.ai/en/showcase import { ShowcaseGrid } from '@/components/showcase-grid'; --- title: "A2A 多代理协作 | A2A 协议 | Kastrax 文档" description: "A2A 协议的多代理协作机制,包括代理网络、协作模式和协作示例。" --- # A2A 多代理协作 ✅ [ZH] Source: https://kastrax.ai/zh/docs/a2a/a2a-collaboration ## 多代理协作概述 ✅ 多代理协作是 A2A 协议的核心目标,允许多个代理协同工作,解决复杂问题。通过多代理协作,每个代理可以专注于自己的专业领域,共同完成单个代理无法完成的任务。 Kastrax 实现了全面的 A2A 多代理协作机制,包括代理网络、协作模式和协作工具,支持构建复杂的多代理系统。 ## 代理网络 ✅ 代理网络是多个代理的集合,它们可以相互发现、通信和协作。Kastrax 提供了代理网络的创建和管理功能: ```kotlin // 创建代理网络 val agentNetwork = agentNetwork { // 添加代理 agent(dataCollectorAgent) agent(dataAnalysisAgent) agent(reportGeneratorAgent) // 配置路由 routing { // 根据能力路由 route("collect_data") to "data-collector-agent" route("analyze_data") to "data-analysis-agent" route("generate_report") to "report-generator-agent" // 默认路由 route("*") to "assistant-agent" } // 配置发现 discovery { // 启用自动发现 autoDiscovery = true // 添加已知服务器 server("https://agent1.example.com") server("https://agent2.example.com") } // 配置安全 security { // 使用 JWT 认证 type = AuthType.JWT jwtSecret = "your-jwt-secret" } } ``` ## 协作模式 ✅ Kastrax 支持多种代理协作模式,适用于不同的场景: ### 1. 顺序协作 ✅ 顺序协作是最简单的协作模式,代理按照预定义的顺序执行任务: ```kotlin // 顺序协作 suspend fun sequentialCollaboration(marketId: String): MarketReport { // 1. 收集数据 val dataRequest = InvokeRequest( capability = "collect_market_data", parameters = mapOf("market_id" to marketId) ) val dataResponse = dataCollectorAgent.invoke(dataRequest) val marketData = dataResponse.result as MarketData // 2. 分析数据 val analysisRequest = InvokeRequest( capability = "analyze_market_data", parameters = mapOf("market_data" to marketData) ) val analysisResponse = dataAnalysisAgent.invoke(analysisRequest) val marketAnalysis = analysisResponse.result as MarketAnalysis // 3. 生成报告 val reportRequest = InvokeRequest( capability = "generate_market_report", parameters = mapOf( "market_data" to marketData, "market_analysis" to marketAnalysis ) ) val reportResponse = reportGeneratorAgent.invoke(reportRequest) return reportResponse.result as MarketReport } ``` ### 2. 并行协作 ✅ 并行协作允许多个代理同时执行任务,然后合并结果: ```kotlin // 并行协作 suspend fun parallelCollaboration(marketId: String): MarketReport = coroutineScope { // 1. 并行收集和分析数据 val dataDeferred = async { val dataRequest = InvokeRequest( capability = "collect_market_data", parameters = mapOf("market_id" to marketId) ) val dataResponse = dataCollectorAgent.invoke(dataRequest) dataResponse.result as MarketData } val competitorDataDeferred = async { val competitorRequest = InvokeRequest( capability = "collect_competitor_data", parameters = mapOf("market_id" to marketId) ) val competitorResponse = competitorAnalysisAgent.invoke(competitorRequest) competitorResponse.result as CompetitorData } val trendDataDeferred = async { val trendRequest = InvokeRequest( capability = "collect_trend_data", parameters = mapOf("market_id" to marketId) ) val trendResponse = trendAnalysisAgent.invoke(trendRequest) trendResponse.result as TrendData } // 2. 等待所有数据收集完成 val marketData = dataDeferred.await() val competitorData = competitorDataDeferred.await() val trendData = trendDataDeferred.await() // 3. 分析综合数据 val analysisRequest = InvokeRequest( capability = "analyze_comprehensive_data", parameters = mapOf( "market_data" to marketData, "competitor_data" to competitorData, "trend_data" to trendData ) ) val analysisResponse = dataAnalysisAgent.invoke(analysisRequest) val marketAnalysis = analysisResponse.result as MarketAnalysis // 4. 生成报告 val reportRequest = InvokeRequest( capability = "generate_market_report", parameters = mapOf( "market_data" to marketData, "competitor_data" to competitorData, "trend_data" to trendData, "market_analysis" to marketAnalysis ) ) val reportResponse = reportGeneratorAgent.invoke(reportRequest) reportResponse.result as MarketReport } ``` ### 3. 动态协作 ✅ 动态协作允许代理根据任务需求动态选择协作伙伴: ```kotlin // 动态协作 suspend fun dynamicCollaboration(task: Task): Result { // 1. 分析任务需求 val requirementsRequest = InvokeRequest( capability = "analyze_task_requirements", parameters = mapOf("task" to task) ) val requirementsResponse = taskAnalysisAgent.invoke(requirementsRequest) val requirements = requirementsResponse.result as TaskRequirements // 2. 发现具有所需能力的代理 val capableAgents = mutableMapOf() for (capability in requirements.requiredCapabilities) { val agents = agentRegistry.findAgentsByCapability(capability) if (agents.isNotEmpty()) { // 选择最合适的代理 val selectedAgent = selectBestAgent(agents, capability) capableAgents[capability] = selectedAgent } else { throw IllegalStateException("No agent found with capability: $capability") } } // 3. 创建执行计划 val planRequest = InvokeRequest( capability = "create_execution_plan", parameters = mapOf( "task" to task, "requirements" to requirements, "available_agents" to capableAgents.keys ) ) val planResponse = planningAgent.invoke(planRequest) val executionPlan = planResponse.result as ExecutionPlan // 4. 执行计划 return executePlan(executionPlan, capableAgents, task) } // 执行计划 suspend fun executePlan( plan: ExecutionPlan, agents: Map, task: Task ): Result = coroutineScope { // 执行计划中的步骤 val results = mutableMapOf() for (step in plan.steps) { // 如果步骤有依赖,等待依赖完成 for (dependency in step.dependencies) { if (!results.containsKey(dependency)) { throw IllegalStateException("Dependency not satisfied: $dependency") } } // 获取执行步骤的代理 val agent = agents[step.capability] ?: throw IllegalStateException("No agent found for capability: ${step.capability}") // 准备参数 val parameters = mutableMapOf() for ((paramName, paramValue) in step.parameters) { // 如果参数值是引用,从结果中获取 if (paramValue is String && paramValue.startsWith("$")) { val resultKey = paramValue.substring(1) parameters[paramName] = results[resultKey]?.toJsonElement() ?: throw IllegalStateException("Result not found: $resultKey") } else { parameters[paramName] = paramValue.toJsonElement() } } // 调用代理 val request = InvokeRequest( capability = step.capability, parameters = parameters ) val response = agent.invoke(request) // 存储结果 results[step.id] = response.result } // 返回最终结果 return@coroutineScope results[plan.steps.last().id] as Result } ``` ### 4. 层次协作 ✅ 层次协作使用层次结构组织代理,上层代理协调下层代理: ```kotlin // 层次协作 suspend fun hierarchicalCollaboration(task: Task): Result { // 1. 将任务委派给协调代理 val coordinatorRequest = InvokeRequest( capability = "coordinate_task", parameters = mapOf("task" to task) ) val coordinatorResponse = coordinatorAgent.invoke(coordinatorRequest) // 2. 协调代理将任务分解为子任务 val subTasks = coordinatorResponse.result as List // 3. 为每个子任务选择合适的代理 val subTaskResults = mutableListOf() for (subTask in subTasks) { // 选择合适的代理 val agent = selectAgentForSubTask(subTask) // 执行子任务 val subTaskRequest = InvokeRequest( capability = subTask.capability, parameters = subTask.parameters ) val subTaskResponse = agent.invoke(subTaskRequest) // 存储子任务结果 subTaskResults.add( SubTaskResult( subTaskId = subTask.id, result = subTaskResponse.result ) ) } // 4. 协调代理合并子任务结果 val mergeRequest = InvokeRequest( capability = "merge_results", parameters = mapOf("sub_task_results" to subTaskResults) ) val mergeResponse = coordinatorAgent.invoke(mergeRequest) return mergeResponse.result as Result } ``` ### 5. 自组织协作 ✅ 自组织协作允许代理自主组织协作,无需中央协调: ```kotlin // 自组织协作 suspend fun selfOrganizingCollaboration(task: Task): Result { // 1. 广播任务到代理网络 val taskBroadcastRequest = TaskBroadcastRequest( taskId = UUID.randomUUID().toString(), task = task ) agentNetwork.broadcast(taskBroadcastRequest) // 2. 等待代理响应 val responses = agentNetwork.collectResponses(taskBroadcastRequest.taskId, timeout = 30000) // 3. 代理自主协商协作方式 val negotiationRequest = NegotiationRequest( taskId = taskBroadcastRequest.taskId, responses = responses ) agentNetwork.broadcast(negotiationRequest) // 4. 等待协商结果 val negotiationResult = agentNetwork.waitForNegotiationResult(negotiationRequest.taskId, timeout = 30000) // 5. 执行协商结果 return executeNegotiationResult(negotiationResult) } ``` ## 协作工具 ✅ Kastrax 提供了多种协作工具,支持代理之间的协作: ### 1. 消息总线 ✅ 消息总线允许代理发送和接收消息,实现异步通信: ```kotlin // 创建消息总线 val messageBus = messageBus { // 配置主题 topic("market-data") { // 配置消息类型 messageType = MarketData::class // 配置订阅者 subscribers = listOf("data-analysis-agent", "report-generator-agent") } topic("market-analysis") { messageType = MarketAnalysis::class subscribers = listOf("report-generator-agent") } // 配置安全 security { // 使用 API 密钥认证 type = AuthType.API_KEY apiKeys = mapOf( "api-key-1" to Principal("data-collector-agent", listOf("publisher"), listOf("publish")), "api-key-2" to Principal("data-analysis-agent", listOf("subscriber", "publisher"), listOf("subscribe", "publish")), "api-key-3" to Principal("report-generator-agent", listOf("subscriber"), listOf("subscribe")) ) } } // 发布消息 messageBus.publish( topic = "market-data", message = marketData, publisher = "data-collector-agent" ) // 订阅消息 messageBus.subscribe( topic = "market-data", subscriber = "data-analysis-agent" ) { message -> // 处理消息 val marketData = message as MarketData // ... } ``` ### 2. 共享内存 ✅ 共享内存允许代理共享数据,实现高效的数据交换: ```kotlin // 创建共享内存 val sharedMemory = sharedMemory { // 配置区域 region("market-data") { // 配置数据类型 dataType = MarketData::class // 配置访问权限 accessControl { // 读取权限 read = listOf("data-collector-agent", "data-analysis-agent", "report-generator-agent") // 写入权限 write = listOf("data-collector-agent") } } region("market-analysis") { dataType = MarketAnalysis::class accessControl { read = listOf("data-analysis-agent", "report-generator-agent") write = listOf("data-analysis-agent") } } // 配置安全 security { // 使用 JWT 认证 type = AuthType.JWT jwtSecret = "your-jwt-secret" } } // 写入数据 sharedMemory.write( region = "market-data", key = "market-123", value = marketData, writer = "data-collector-agent" ) // 读取数据 val marketData = sharedMemory.read( region = "market-data", key = "market-123", reader = "data-analysis-agent" ) ``` ### 3. 事件系统 ✅ 事件系统允许代理发布和订阅事件,实现松耦合的通信: ```kotlin // 创建事件系统 val eventSystem = eventSystem { // 配置事件类型 eventType("market-data-collected") { // 配置事件数据类型 dataType = MarketData::class // 配置订阅者 subscribers = listOf("data-analysis-agent", "report-generator-agent") } eventType("market-analysis-completed") { dataType = MarketAnalysis::class subscribers = listOf("report-generator-agent") } // 配置安全 security { // 使用 API 密钥认证 type = AuthType.API_KEY apiKeys = mapOf( "api-key-1" to Principal("data-collector-agent", listOf("publisher"), listOf("publish")), "api-key-2" to Principal("data-analysis-agent", listOf("subscriber", "publisher"), listOf("subscribe", "publish")), "api-key-3" to Principal("report-generator-agent", listOf("subscriber"), listOf("subscribe")) ) } } // 发布事件 eventSystem.publish( eventType = "market-data-collected", data = marketData, publisher = "data-collector-agent" ) // 订阅事件 eventSystem.subscribe( eventType = "market-data-collected", subscriber = "data-analysis-agent" ) { event -> // 处理事件 val marketData = event.data as MarketData // ... } ``` ### 4. 协作空间 ✅ 协作空间是一个虚拟环境,允许多个代理共享状态和协作: ```kotlin // 创建协作空间 val collaborationSpace = collaborationSpace { // 配置空间 ID id = "market-analysis-space" // 配置参与者 participants = listOf("data-collector-agent", "data-analysis-agent", "report-generator-agent") // 配置共享状态 sharedState { // 市场数据 state("market-data") { dataType = MarketData::class accessControl { read = listOf("data-collector-agent", "data-analysis-agent", "report-generator-agent") write = listOf("data-collector-agent") } } // 市场分析 state("market-analysis") { dataType = MarketAnalysis::class accessControl { read = listOf("data-analysis-agent", "report-generator-agent") write = listOf("data-analysis-agent") } } // 市场报告 state("market-report") { dataType = MarketReport::class accessControl { read = listOf("report-generator-agent") write = listOf("report-generator-agent") } } } // 配置协作工具 tools { // 消息工具 tool("send-message") { description = "发送消息给其他参与者" parameters { parameter("recipient", "接收者", String::class) parameter("message", "消息内容", String::class) } execute { params -> val recipient = params["recipient"] as String val message = params["message"] as String sendMessage(recipient, message) "消息已发送" } } // 状态更新工具 tool("update-state") { description = "更新共享状态" parameters { parameter("state-key", "状态键", String::class) parameter("state-value", "状态值", Any::class) } execute { params -> val stateKey = params["state-key"] as String val stateValue = params["state-value"] updateState(stateKey, stateValue) "状态已更新" } } } // 配置安全 security { // 使用 JWT 认证 type = AuthType.JWT jwtSecret = "your-jwt-secret" } } // 加入协作空间 val participant = collaborationSpace.join( participantId = "data-analysis-agent", credentials = JwtCredentials("your-jwt-token") ) // 获取共享状态 val marketData = participant.getState("market-data") // 更新共享状态 participant.updateState("market-analysis", marketAnalysis) // 发送消息 participant.sendMessage( recipient = "report-generator-agent", message = "市场分析已完成,可以生成报告了" ) // 接收消息 participant.onMessage { message -> println("收到消息:${message.content}") } // 使用工具 participant.useTool( toolId = "update-state", parameters = mapOf( "state-key" to "market-analysis", "state-value" to marketAnalysis ) ) ``` ## 协作示例 ✅ 以下是一个完整的多代理协作示例,展示了如何使用 Kastrax 的 A2A 实现构建复杂的多代理系统: ```kotlin // 创建 A2A 实例 val a2aInstance = a2a { // 配置代理网络 agentNetwork { // 注册数据收集代理 a2aAgent { id = "data-collector-agent" name = "数据收集代理" description = "收集市场数据的代理" // 添加能力 capability { id = "collect_market_data" name = "收集市场数据" description = "收集指定市场的数据" // 添加参数 parameter { name = "market_id" type = "string" description = "市场ID" required = true } returnType = "json" } // 配置认证 authentication { type = AuthType.API_KEY } } // 注册数据分析代理 a2aAgent { id = "data-analysis-agent" name = "数据分析代理" description = "分析市场数据的代理" // 添加能力 capability { id = "analyze_market_data" name = "分析市场数据" description = "分析市场数据并生成分析结果" // 添加参数 parameter { name = "market_data" type = "json" description = "市场数据" required = true } returnType = "json" } // 配置认证 authentication { type = AuthType.API_KEY } } // 注册报告生成代理 a2aAgent { id = "report-generator-agent" name = "报告生成代理" description = "生成市场报告的代理" // 添加能力 capability { id = "generate_market_report" name = "生成市场报告" description = "根据市场数据和分析结果生成市场报告" // 添加参数 parameter { name = "market_data" type = "json" description = "市场数据" required = true } parameter { name = "market_analysis" type = "json" description = "市场分析结果" required = true } returnType = "json" } // 配置认证 authentication { type = AuthType.API_KEY } } // 配置协作工具 collaborationTools { // 配置消息总线 messageBus { // 配置主题 topic("market-data") { messageType = MarketData::class subscribers = listOf("data-analysis-agent", "report-generator-agent") } topic("market-analysis") { messageType = MarketAnalysis::class subscribers = listOf("report-generator-agent") } } // 配置共享内存 sharedMemory { // 配置区域 region("market-data") { dataType = MarketData::class accessControl { read = listOf("data-collector-agent", "data-analysis-agent", "report-generator-agent") write = listOf("data-collector-agent") } } region("market-analysis") { dataType = MarketAnalysis::class accessControl { read = listOf("data-analysis-agent", "report-generator-agent") write = listOf("data-analysis-agent") } } } } // 配置工作流 workflow { id = "market-report-workflow" name = "市场报告工作流" description = "生成市场报告的工作流" // 输入参数 input { parameter("market_id", "市场ID", String::class) } // 工作流步骤 steps { // 步骤 1:收集数据 step("collect_data") { agentId = "data-collector-agent" capabilityId = "collect_market_data" parameters { parameter("market_id", context.get("market_id")) } output = "market_data" } // 步骤 2:分析数据 step("analyze_data") { agentId = "data-analysis-agent" capabilityId = "analyze_market_data" parameters { parameter("market_data", context.get("market_data")) } output = "market_analysis" dependsOn = listOf("collect_data") } // 步骤 3:生成报告 step("generate_report") { agentId = "report-generator-agent" capabilityId = "generate_market_report" parameters { parameter("market_data", context.get("market_data")) parameter("market_analysis", context.get("market_analysis")) } output = "market_report" dependsOn = listOf("analyze_data") } } // 输出参数 output { parameter("market_report", "市场报告", MarketReport::class) } } } // 配置服务器 server { port = 8080 enableCors = true } // 配置安全 security { // 使用 API 密钥认证 type = AuthType.API_KEY apiKeys = mapOf( "api-key-1" to Principal("data-collector-agent", listOf("agent"), listOf("invoke")), "api-key-2" to Principal("data-analysis-agent", listOf("agent"), listOf("invoke")), "api-key-3" to Principal("report-generator-agent", listOf("agent"), listOf("invoke")) ) } } // 启动 A2A 实例 a2aInstance.start() // 获取工作流引擎 val workflowEngine = a2aInstance.getWorkflowEngine() // 执行工作流 val result = workflowEngine.execute( workflowId = "market-report-workflow", input = mapOf( "market_id" to "MARKET-123" ) ) // 获取市场报告 val marketReport = result.get("market_report") println("市场报告:$marketReport") // 停止 A2A 实例 a2aInstance.stop() ``` ## 总结 ✅ Kastrax 的 A2A 实现提供了全面的多代理协作机制,包括代理网络、协作模式和协作工具,支持构建复杂的多代理系统。通过多代理协作,每个代理可以专注于自己的专业领域,共同完成单个代理无法完成的任务。 ## 下一步 ✅ 了解了 A2A 多代理协作后,您可以: 1. 学习 [A2A 代理发现](/docs/a2a/a2a-discovery.mdx) 2. 了解 [A2A 任务委派](/docs/a2a/a2a-delegation.mdx) 3. 研究 [A2A 安全机制](/docs/a2a/a2a-security.mdx) 4. 学习 [工作流引擎](/docs/a2a/a2a-workflow.mdx) 5. 研究 [A2A 协议规范](/docs/a2a/a2a-specification.mdx) --- title: "A2A 任务委派 | A2A 协议 | Kastrax 文档" description: "A2A 协议的任务委派机制,包括任务创建、执行、取消和状态跟踪。" --- # A2A 任务委派 ✅ [ZH] Source: https://kastrax.ai/zh/docs/a2a/a2a-delegation ## 任务委派概述 ✅ 任务委派是 A2A 协议的核心功能之一,允许代理将任务委派给其他代理执行。通过任务委派,代理可以利用其他代理的专业能力,实现复杂的多代理协作。 Kastrax 实现了全面的 A2A 任务委派机制,包括任务创建、执行、取消和状态跟踪,支持构建灵活的多代理系统。 ## 任务模型 ✅ 在 Kastrax 的 A2A 实现中,任务由以下组件组成: ### 任务 (Task) ✅ 任务是代理之间委派的工作单元,包含以下属性: ```kotlin /** * 任务,代表代理之间委派的工作单元 */ data class Task( /** * 任务 ID */ val id: String, /** * 会话 ID */ val sessionId: String? = null, /** * 任务状态 */ val status: TaskStatus, /** * 任务历史 */ val history: List, /** * 任务产物 */ val artifacts: List = emptyList(), /** * 任务元数据 */ val metadata: Map = emptyMap() ) ``` ### 任务状态 (TaskStatus) ✅ 任务状态表示任务的当前状态,包含以下属性: ```kotlin /** * 任务状态,表示任务的当前状态 */ data class TaskStatus( /** * 任务状态 */ val state: TaskState, /** * 任务进度 */ val progress: Float = 0f, /** * 任务错误 */ val error: String? = null, /** * 任务开始时间 */ val startedAt: Long? = null, /** * 任务完成时间 */ val completedAt: Long? = null ) /** * 任务状态枚举 */ enum class TaskState { /** * 已提交 */ SUBMITTED, /** * 正在处理 */ PROCESSING, /** * 已完成 */ COMPLETED, /** * 已失败 */ FAILED, /** * 已取消 */ CANCELLED } ``` ### 消息 (Message) ✅ 消息是任务中的通信单元,包含以下属性: ```kotlin /** * 消息,表示任务中的通信单元 */ data class Message( /** * 消息 ID */ val id: String = UUID.randomUUID().toString(), /** * 消息角色 */ val role: MessageRole = MessageRole.USER, /** * 消息部分 */ val parts: List, /** * 消息元数据 */ val metadata: Map = emptyMap() ) /** * 消息角色枚举 */ enum class MessageRole { /** * 用户 */ USER, /** * 助手 */ ASSISTANT, /** * 系统 */ SYSTEM } ``` ### 消息部分 (MessagePart) ✅ 消息部分是消息的组成部分,可以是文本、图像等: ```kotlin /** * 消息部分,表示消息的组成部分 */ sealed class MessagePart /** * 文本部分 */ data class TextPart( /** * 文本内容 */ val text: String ) : MessagePart() /** * 图像部分 */ data class ImagePart( /** * 图像 URL */ val url: String, /** * 图像 MIME 类型 */ val mimeType: String = "image/jpeg" ) : MessagePart() ``` ### 任务产物 (Artifact) ✅ 任务产物是任务执行的结果,可以是文本、图像等: ```kotlin /** * 任务产物,表示任务执行的结果 */ data class Artifact( /** * 产物名称 */ val name: String, /** * 产物部分 */ val parts: List, /** * 产物元数据 */ val metadata: Map = emptyMap() ) ``` ## 任务管理器 ✅ Kastrax 提供了任务管理器,用于管理任务的创建、执行、取消和状态跟踪: ```kotlin /** * 任务管理器,用于管理任务的创建、执行、取消和状态跟踪 */ class TaskManager( private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default) ) { /** * 任务映射 */ private val tasks = ConcurrentHashMap() /** * 任务状态映射 */ private val taskStatuses = ConcurrentHashMap>() /** * 任务事件映射 */ private val taskEvents = ConcurrentHashMap>() /** * 任务推送通知配置映射 */ private val taskPushNotifications = ConcurrentHashMap() /** * 创建任务 */ fun createTask(message: Message, sessionId: String? = null): Task { val taskId = UUID.randomUUID().toString() val task = Task( id = taskId, sessionId = sessionId, status = TaskStatus(state = TaskState.SUBMITTED), history = listOf(message) ) tasks[taskId] = task taskStatuses[taskId] = MutableStateFlow(task.status) taskEvents[taskId] = MutableSharedFlow(replay = 0, extraBufferCapacity = 100) return task } /** * 获取任务 */ fun getTask(taskId: String): Task? { return tasks[taskId] } /** * 获取任务状态 */ fun getTaskStatus(taskId: String): TaskStatus? { return taskStatuses[taskId]?.value } /** * 获取任务状态流 */ fun getTaskStatusFlow(taskId: String): Flow? { return taskStatuses[taskId] } /** * 获取任务事件流 */ fun getTaskEventFlow(taskId: String): Flow? { return taskEvents[taskId] } /** * 更新任务状态 */ fun updateTaskStatus(taskId: String, status: TaskStatus) { val task = tasks[taskId] ?: return val updatedTask = task.copy(status = status) tasks[taskId] = updatedTask taskStatuses[taskId]?.value = status // 发送任务事件 scope.launch { val event = when (status.state) { TaskState.PROCESSING -> TaskEvent.TaskStarted(taskId) TaskState.COMPLETED -> TaskEvent.TaskCompleted(taskId) TaskState.FAILED -> TaskEvent.TaskFailed(taskId, status.error) TaskState.CANCELLED -> TaskEvent.TaskCancelled(taskId) else -> null } if (event != null) { taskEvents[taskId]?.emit(event) // 发送推送通知 taskPushNotifications[taskId]?.let { config -> if (config.events.contains(event.type) || config.events.contains("*")) { sendPushNotification(config, event) } } } } } /** * 添加任务消息 */ fun addTaskMessage(taskId: String, message: Message) { val task = tasks[taskId] ?: return val updatedTask = task.copy( history = task.history + message ) tasks[taskId] = updatedTask // 发送任务事件 scope.launch { val event = TaskEvent.MessageAdded(taskId, message) taskEvents[taskId]?.emit(event) // 发送推送通知 taskPushNotifications[taskId]?.let { config -> if (config.events.contains("message.added") || config.events.contains("*")) { sendPushNotification(config, event) } } } } /** * 添加任务产物 */ fun addTaskArtifact(taskId: String, artifact: Artifact) { val task = tasks[taskId] ?: return val updatedTask = task.copy( artifacts = task.artifacts + artifact ) tasks[taskId] = updatedTask // 发送任务事件 scope.launch { val event = TaskEvent.ArtifactAdded(taskId, artifact) taskEvents[taskId]?.emit(event) // 发送推送通知 taskPushNotifications[taskId]?.let { config -> if (config.events.contains("artifact.added") || config.events.contains("*")) { sendPushNotification(config, event) } } } } /** * 取消任务 */ fun cancelTask(taskId: String) { val task = tasks[taskId] ?: return if (task.status.state == TaskState.COMPLETED || task.status.state == TaskState.FAILED) { return } updateTaskStatus( taskId = taskId, status = TaskStatus( state = TaskState.CANCELLED, completedAt = System.currentTimeMillis() ) ) } /** * 设置任务推送通知 */ fun setTaskPushNotification(taskId: String, config: PushNotificationConfig) { taskPushNotifications[taskId] = config } /** * 处理任务 */ suspend fun processTask(agent: A2AAgent, task: Task): Flow = flow { // 更新任务状态为处理中 updateTaskStatus( taskId = task.id, status = TaskStatus( state = TaskState.PROCESSING, startedAt = System.currentTimeMillis() ) ) emit(TaskEvent.TaskStarted(task.id)) try { // 获取最后一条消息 val message = task.history.lastOrNull() if (message != null) { // 创建调用请求 val request = InvokeRequest( id = UUID.randomUUID().toString(), capabilityId = "process_task", // 假设代理有一个处理任务的能力 parameters = mapOf( "prompt" to JsonPrimitive(getPromptFromMessage(message)), "task_id" to JsonPrimitive(task.id) ) ) // 调用代理处理任务 val response = agent.invoke(request) // 创建任务产物 val artifact = Artifact( name = "result", parts = listOf( TextPart( text = response.result.toString() ) ) ) // 添加任务产物 addTaskArtifact(task.id, artifact) emit(TaskEvent.ArtifactAdded(task.id, artifact)) // 创建助手消息 val assistantMessage = Message( role = MessageRole.ASSISTANT, parts = artifact.parts ) // 添加助手消息 addTaskMessage(task.id, assistantMessage) emit(TaskEvent.MessageAdded(task.id, assistantMessage)) // 更新任务状态为已完成 updateTaskStatus( taskId = task.id, status = TaskStatus( state = TaskState.COMPLETED, progress = 1f, completedAt = System.currentTimeMillis() ) ) emit(TaskEvent.TaskCompleted(task.id)) } else { // 如果没有消息,则更新任务状态为失败 updateTaskStatus( taskId = task.id, status = TaskStatus( state = TaskState.FAILED, error = "No message found in task", completedAt = System.currentTimeMillis() ) ) emit(TaskEvent.TaskFailed(task.id, "No message found in task")) } } catch (e: Exception) { // 更新任务状态为失败 updateTaskStatus( taskId = task.id, status = TaskStatus( state = TaskState.FAILED, error = e.message, completedAt = System.currentTimeMillis() ) ) emit(TaskEvent.TaskFailed(task.id, e.message)) } } /** * 从消息中获取提示 */ private fun getPromptFromMessage(message: Message): String { return message.parts .filterIsInstance() .joinToString("\n") { it.text } } /** * 发送推送通知 */ private fun sendPushNotification(config: PushNotificationConfig, event: Any) { // 在实际实现中,这里应该使用 HTTP 客户端发送通知 // 为了简化,这里只是打印日志 println("Sending push notification to ${config.url}: $event") } /** * 处理发送任务请求 */ suspend fun onSendTask(agent: A2AAgent, params: TaskSendParams): Task { // 创建任务 val task = createTask(params.message, params.sessionId) // 如果配置了推送通知,则设置推送通知 params.pushNotification?.let { config -> setTaskPushNotification(task.id, config) } // 启动协程处理任务 scope.launch { processTask(agent, task) } return task } /** * 处理发送任务并订阅更新请求 */ suspend fun onSendTaskSubscribe(agent: A2AAgent, params: TaskSendParams): Flow = flow { // 创建任务 val task = createTask(params.message, params.sessionId) // 如果配置了推送通知,则设置推送通知 params.pushNotification?.let { config -> setTaskPushNotification(task.id, config) } // 处理任务并发送更新 processTask(agent, task).collect { event -> emit(event) } } } ``` ## 任务委派流程 ✅ Kastrax 的 A2A 实现支持多种任务委派流程: ### 1. 同步任务委派 ✅ 同步任务委派是最简单的委派方式,代理直接调用另一个代理的能力,并等待结果: ```kotlin // 创建调用请求 val request = InvokeRequest( id = UUID.randomUUID().toString(), capabilityId = "data_analysis", parameters = mapOf( "dataset_url" to JsonPrimitive("https://example.com/data.csv"), "analysis_type" to JsonPrimitive("statistical") ) ) // 调用代理能力 val response = agent.invoke(request) // 处理响应 println("分析结果:${response.result}") ``` ### 2. 异步任务委派 ✅ 异步任务委派允许代理创建任务,然后稍后检查结果: ```kotlin // 创建任务 val message = Message( parts = listOf( TextPart( text = "分析数据集 https://example.com/data.csv 并生成统计报告" ) ) ) val task = taskManager.createTask(message) // 启动协程处理任务 scope.launch { taskManager.processTask(agent, task) } // 稍后检查任务状态 val taskStatus = taskManager.getTaskStatus(task.id) if (taskStatus?.state == TaskState.COMPLETED) { // 获取任务产物 val artifacts = taskManager.getTask(task.id)?.artifacts println("分析结果:${artifacts?.firstOrNull()?.parts?.filterIsInstance()?.firstOrNull()?.text}") } ``` ### 3. 推送通知 ✅ 推送通知允许代理在任务状态变化时接收通知: ```kotlin // 创建任务 val message = Message( parts = listOf( TextPart( text = "分析数据集 https://example.com/data.csv 并生成统计报告" ) ) ) // 设置推送通知 val pushNotification = PushNotificationConfig( url = "https://example.com/webhook", events = listOf("task.completed", "task.failed") ) // 发送任务 val task = taskManager.onSendTask( agent = agent, params = TaskSendParams( message = message, pushNotification = pushNotification ) ) ``` ### 4. 流式响应 ✅ 流式响应允许代理实时接收任务更新: ```kotlin // 创建任务 val message = Message( parts = listOf( TextPart( text = "分析数据集 https://example.com/data.csv 并生成统计报告" ) ) ) // 发送任务并订阅更新 taskManager.onSendTaskSubscribe( agent = agent, params = TaskSendParams( message = message ) ).collect { event -> when (event) { is TaskEvent.TaskStarted -> println("任务已开始:${event.taskId}") is TaskEvent.MessageAdded -> println("消息已添加:${event.message.parts.filterIsInstance().firstOrNull()?.text}") is TaskEvent.ArtifactAdded -> println("产物已添加:${event.artifact.parts.filterIsInstance().firstOrNull()?.text}") is TaskEvent.TaskCompleted -> println("任务已完成:${event.taskId}") is TaskEvent.TaskFailed -> println("任务已失败:${event.error}") } } ``` ## 任务委派 API ✅ Kastrax 的 A2A 实现提供了标准的任务委派 API,包括以下端点: ### 1. 发送任务端点 ✅ 发送任务端点允许代理创建任务: ``` POST /api/tasks ``` 请求示例: ```json { "message": { "role": "USER", "parts": [ { "text": "分析数据集 https://example.com/data.csv 并生成统计报告" } ] }, "session_id": "user-session-1", "push_notification": { "url": "https://example.com/webhook", "events": ["task.completed", "task.failed"] } } ``` 响应示例: ```json { "task": { "id": "task-123", "session_id": "user-session-1", "status": { "state": "SUBMITTED" }, "history": [ { "id": "msg-1", "role": "USER", "parts": [ { "text": "分析数据集 https://example.com/data.csv 并生成统计报告" } ] } ] } } ``` ### 2. 获取任务端点 ✅ 获取任务端点允许代理获取任务信息: ``` GET /api/tasks/{taskId} ``` 响应示例: ```json { "task": { "id": "task-123", "session_id": "user-session-1", "status": { "state": "COMPLETED", "progress": 1.0, "started_at": 1620000000000, "completed_at": 1620000010000 }, "history": [ { "id": "msg-1", "role": "USER", "parts": [ { "text": "分析数据集 https://example.com/data.csv 并生成统计报告" } ] }, { "id": "msg-2", "role": "ASSISTANT", "parts": [ { "text": "数据集分析报告:\n\n平均值:42.5\n中位数:40.0\n标准差:10.2" } ] } ], "artifacts": [ { "name": "result", "parts": [ { "text": "数据集分析报告:\n\n平均值:42.5\n中位数:40.0\n标准差:10.2" } ] } ] } } ``` ### 3. 获取任务状态端点 ✅ 获取任务状态端点允许代理获取任务状态: ``` GET /api/tasks/{taskId}/status ``` 响应示例: ```json { "status": { "state": "COMPLETED", "progress": 1.0, "started_at": 1620000000000, "completed_at": 1620000010000 } } ``` ### 4. 取消任务端点 ✅ 取消任务端点允许代理取消任务: ``` POST /api/tasks/{taskId}/cancel ``` 响应示例: ```json { "success": true, "task_id": "task-123", "status": { "state": "CANCELLED", "completed_at": 1620000020000 } } ``` ### 5. 订阅任务更新端点 ✅ 订阅任务更新端点允许代理实时接收任务更新: ``` GET /api/tasks/{taskId}/subscribe ``` 响应示例(Server-Sent Events): ``` event: task.started data: {"task_id":"task-123"} event: message.added data: {"task_id":"task-123","message":{"id":"msg-2","role":"ASSISTANT","parts":[{"text":"数据集分析报告:\n\n平均值:42.5\n中位数:40.0\n标准差:10.2"}]}} event: artifact.added data: {"task_id":"task-123","artifact":{"name":"result","parts":[{"text":"数据集分析报告:\n\n平均值:42.5\n中位数:40.0\n标准差:10.2"}]}} event: task.completed data: {"task_id":"task-123"} ``` ## 任务委派示例 ✅ 以下是一个完整的任务委派示例: ```kotlin // 创建 A2A 实例 val a2aInstance = a2a { // 配置任务管理器 taskManager { // 配置任务清理 taskCleanup = true taskTtl = 3600000 // 1 小时 } // 注册 A2A 代理 a2aAgent { id = "data-analysis-agent" name = "数据分析代理" description = "提供数据分析和可视化能力的代理" // 添加能力 capability { id = "data_analysis" name = "数据分析" description = "分析提供的数据集并返回统计结果" // 添加参数 parameter { name = "dataset_url" type = "string" description = "数据集URL" required = true } parameter { name = "analysis_type" type = "string" description = "分析类型" required = true } returnType = "json" } // 配置认证 authentication { type = AuthType.API_KEY } } // 配置服务器 server { port = 8080 enableCors = true } } // 启动 A2A 实例 a2aInstance.start() // 获取任务管理器 val taskManager = a2aInstance.getTaskManager() // 获取代理 val agent = a2aInstance.getAgent("data-analysis-agent") // 创建任务 val message = Message( parts = listOf( TextPart( text = "分析数据集 https://example.com/data.csv 并生成统计报告" ) ) ) // 设置推送通知 val pushNotification = PushNotificationConfig( url = "https://example.com/webhook", events = listOf("task.completed", "task.failed") ) // 发送任务 val task = taskManager.onSendTask( agent = agent, params = TaskSendParams( message = message, sessionId = "user-session-1", pushNotification = pushNotification ) ) println("任务已创建:${task.id}") // 等待任务完成 while (true) { val taskStatus = taskManager.getTaskStatus(task.id) if (taskStatus?.state == TaskState.COMPLETED || taskStatus?.state == TaskState.FAILED) { break } Thread.sleep(1000) } // 获取任务结果 val updatedTask = taskManager.getTask(task.id) if (updatedTask?.status?.state == TaskState.COMPLETED) { val artifacts = updatedTask.artifacts println("分析结果:${artifacts.firstOrNull()?.parts?.filterIsInstance()?.firstOrNull()?.text}") } else { println("任务失败:${updatedTask?.status?.error}") } // 停止 A2A 实例 a2aInstance.stop() ``` ## 总结 ✅ Kastrax 的 A2A 实现提供了全面的任务委派机制,包括任务创建、执行、取消和状态跟踪,支持构建灵活的多代理系统。通过任务委派,代理可以利用其他代理的专业能力,实现复杂的多代理协作。 ## 下一步 ✅ 了解了 A2A 任务委派后,您可以: 1. 学习 [A2A 代理发现](/docs/a2a/a2a-discovery.mdx) 2. 研究 [A2A 安全机制](/docs/a2a/a2a-security.mdx) 3. 探索 [多代理协作](/docs/a2a/a2a-collaboration.mdx) 4. 学习 [工作流引擎](/docs/a2a/a2a-workflow.mdx) 5. 研究 [A2A 协议规范](/docs/a2a/a2a-specification.mdx) --- title: "A2A 代理发现 | A2A 协议 | Kastrax 文档" description: "A2A 协议的代理发现机制,包括代理注册、发现和查询。" --- # A2A 代理发现 ✅ [ZH] Source: https://kastrax.ai/zh/docs/a2a/a2a-discovery ## 代理发现概述 ✅ 代理发现是 A2A 协议的核心功能之一,允许代理发现其他代理的存在和能力。通过代理发现,代理可以动态地找到具有所需能力的其他代理,并与之协作。 Kastrax 实现了全面的 A2A 代理发现机制,包括代理注册、发现和查询,支持构建灵活的多代理系统。 ## 代理卡片 (Agent Card) ✅ 代理卡片是代理发现的基础,它描述了代理的元数据、能力和认证需求。每个代理都有一个唯一的代理卡片,通常以 JSON 格式提供: ```json { "id": "data-analysis-agent", "name": "数据分析代理", "description": "提供数据分析和可视化能力的代理", "version": "1.0.0", "capabilities": [ { "id": "data_analysis", "name": "数据分析", "description": "分析提供的数据集并返回统计结果", "parameters": [ { "name": "dataset_url", "type": "string", "description": "数据集URL", "required": true }, { "name": "analysis_type", "type": "string", "description": "分析类型", "required": true } ], "returnType": "json" } ], "authentication": { "type": "api_key" }, "endpoint": "https://example.com/api/agent" } ``` 在 Kastrax 中,可以使用 DSL 创建代理卡片: ```kotlin // 创建代理卡片 val agentCard = agentCard { id = "data-analysis-agent" name = "数据分析代理" description = "提供数据分析和可视化能力的代理" version = "1.0.0" // 添加能力 capability { id = "data_analysis" name = "数据分析" description = "分析提供的数据集并返回统计结果" // 添加参数 parameter { name = "dataset_url" type = "string" description = "数据集URL" required = true } parameter { name = "analysis_type" type = "string" description = "分析类型" required = true } returnType = "json" } // 配置认证 authentication { type = AuthType.API_KEY } // 设置端点 endpoint = "https://example.com/api/agent" } ``` ## 代理注册 ✅ 代理注册是将代理信息添加到代理注册表的过程,使其可被其他代理发现。Kastrax 提供了多种代理注册方式: ### 本地注册 ✅ 本地注册是将代理添加到本地代理注册表的过程: ```kotlin // 创建代理注册表 val agentRegistry = A2AAgentRegistry() // 注册代理 agentRegistry.register(dataAnalysisAgent) agentRegistry.register(reportGeneratorAgent) // 或者使用 DSL val a2aInstance = a2a { // 注册 kastrax 代理 agent(assistantAgent) // 注册 A2A 代理 a2aAgent { id = "data-analysis-agent" name = "数据分析代理" description = "提供数据分析和可视化能力的代理" // 配置... } } ``` ### 远程注册 ✅ 远程注册是将代理注册到远程代理注册中心的过程: ```kotlin // 创建代理发现服务 val discoveryService = A2ADiscoveryService() // 添加注册中心 discoveryService.addRegistryServer("https://registry.example.com") // 注册代理 discoveryService.registerAgent(agentCard) // 或者使用 DSL val a2aInstance = a2a { // 配置发现服务 discovery { // 添加注册中心 registryServer("https://registry.example.com") // 自动注册 autoRegister = true } // 注册代理... } ``` ### 自动发现 ✅ 自动发现是定期扫描已知服务器,发现新代理的过程: ```kotlin // 创建代理发现服务,启用自动发现 val discoveryService = A2ADiscoveryService( config = A2ADiscoveryConfig( enableAutoDiscovery = true, discoveryInterval = 60000 // 60 秒 ) ) // 添加已知服务器 discoveryService.addServer("https://agent1.example.com") discoveryService.addServer("https://agent2.example.com") // 或者使用 DSL val a2aInstance = a2a { // 配置发现服务 discovery { // 启用自动发现 autoDiscovery = true discoveryInterval = 60000 // 60 秒 // 添加已知服务器 server("https://agent1.example.com") server("https://agent2.example.com") } } ``` ## 代理查询 ✅ 代理查询是查找具有特定特征的代理的过程。Kastrax 提供了多种代理查询方式: ### 按 ID 查询 ✅ 按 ID 查询是查找具有特定 ID 的代理的过程: ```kotlin // 按 ID 查询代理 val agent = agentRegistry.getAgent("data-analysis-agent") // 或者使用发现服务 val agent = discoveryService.findAgentById("data-analysis-agent") ``` ### 按能力查询 ✅ 按能力查询是查找具有特定能力的代理的过程: ```kotlin // 按能力查询代理 val agents = agentRegistry.findAgentsByCapability("data_analysis") // 或者使用发现服务 val agents = discoveryService.findAgentsByCapability("data_analysis") // 按能力和参数查询 val agents = discoveryService.findAgentsByCapability( capabilityId = "data_analysis", parameters = listOf( Parameter("dataset_url", "string"), Parameter("analysis_type", "string") ) ) ``` ### 按属性查询 ✅ 按属性查询是查找具有特定属性的代理的过程: ```kotlin // 按属性查询代理 val agents = agentRegistry.findAgentsByAttributes( attributes = mapOf( "name" to "数据分析代理", "version" to "1.0.0" ) ) // 或者使用发现服务 val agents = discoveryService.findAgentsByAttributes( attributes = mapOf( "name" to "数据分析代理", "version" to "1.0.0" ) ) ``` ### 复杂查询 ✅ 复杂查询是组合多个条件查询代理的过程: ```kotlin // 创建查询 val query = AgentQuery.Builder() .withCapability("data_analysis") .withParameter("dataset_url", "string") .withAttribute("version", "1.0.0") .withAuthType(AuthType.API_KEY) .build() // 执行查询 val agents = discoveryService.findAgents(query) ``` ## 代理发现服务 ✅ Kastrax 提供了完整的代理发现服务,管理代理的注册、发现和查询: ```kotlin /** * A2A 代理发现服务,用于发现和注册 A2A 代理 */ class A2ADiscoveryService( private val config: A2ADiscoveryConfig = A2ADiscoveryConfig(), private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default) ) { /** * 代理注册表 */ private val registry = ConcurrentHashMap() /** * 已知的服务器 URL */ private val knownServers = ConcurrentHashMap.newKeySet() /** * 互斥锁,用于保护注册表 */ private val mutex = Mutex() init { if (config.enableAutoDiscovery) { startAutoDiscovery() } } /** * 注册代理 */ suspend fun registerAgent(agentCard: AgentCard) { mutex.withLock { registry[agentCard.id] = AgentRegistryEntry( agentCard = agentCard, registeredAt = System.currentTimeMillis(), lastSeenAt = System.currentTimeMillis() ) } } /** * 获取代理 */ suspend fun getAgent(agentId: String): AgentCard? { return mutex.withLock { registry[agentId]?.agentCard } } /** * 获取所有代理 */ suspend fun getAllAgents(): List { return mutex.withLock { registry.values.map { it.agentCard } } } /** * 按能力查找代理 */ suspend fun findAgentsByCapability(capabilityId: String): List { return mutex.withLock { registry.values .filter { entry -> entry.agentCard.capabilities.any { it.id == capabilityId } } .map { it.agentCard } } } /** * 添加服务器 URL */ fun addServer(serverUrl: String) { knownServers.add(serverUrl) } /** * 移除服务器 URL */ fun removeServer(serverUrl: String) { knownServers.remove(serverUrl) } /** * 启动自动发现 */ private fun startAutoDiscovery() { scope.launch { while (true) { try { discoverAgents() cleanupRegistry() } catch (e: Exception) { // 记录错误但不中断循环 println("Error during auto discovery: ${e.message}") } delay(config.discoveryInterval) } } } /** * 发现代理 */ private suspend fun discoverAgents() { for (serverUrl in knownServers) { try { val client = A2AClient(A2AClientConfig(serverUrl = serverUrl)) // 获取服务器上的所有代理 val agents = client.getAgentCard() // 注册代理 registerAgent(agents) client.close() } catch (e: Exception) { // 记录错误但继续处理其他服务器 println("Error discovering agents from $serverUrl: ${e.message}") } } } /** * 清理注册表 */ private suspend fun cleanupRegistry() { mutex.withLock { val now = System.currentTimeMillis() val expiredEntries = registry.entries.filter { (_, entry) -> now - entry.lastSeenAt > config.entryTtl } for ((agentId, _) in expiredEntries) { registry.remove(agentId) } } } } ``` 使用 DSL 配置代理发现服务: ```kotlin // 使用 DSL 配置代理发现服务 val a2aInstance = a2a { // 配置发现服务 discovery { // 启用自动发现 autoDiscovery = true discoveryInterval = 60000 // 60 秒 entryTtl = 3600000 // 1 小时 // 添加已知服务器 server("https://agent1.example.com") server("https://agent2.example.com") // 添加注册中心 registryServer("https://registry.example.com") // 自动注册 autoRegister = true } } ``` ## 代理发现协议 ✅ A2A 协议定义了标准的代理发现协议,包括以下端点: ### 1. 代理卡片端点 ✅ 代理卡片端点返回代理的代理卡片: ``` GET /.well-known/agent.json ``` 响应示例: ```json { "id": "data-analysis-agent", "name": "数据分析代理", "description": "提供数据分析和可视化能力的代理", "version": "1.0.0", "capabilities": [ { "id": "data_analysis", "name": "数据分析", "description": "分析提供的数据集并返回统计结果", "parameters": [ { "name": "dataset_url", "type": "string", "description": "数据集URL", "required": true }, { "name": "analysis_type", "type": "string", "description": "分析类型", "required": true } ], "returnType": "json" } ], "authentication": { "type": "api_key" }, "endpoint": "https://example.com/api/agent" } ``` ### 2. 能力查询端点 ✅ 能力查询端点返回代理的能力列表: ``` GET /api/capabilities ``` 响应示例: ```json { "capabilities": [ { "id": "data_analysis", "name": "数据分析", "description": "分析提供的数据集并返回统计结果", "parameters": [ { "name": "dataset_url", "type": "string", "description": "数据集URL", "required": true }, { "name": "analysis_type", "type": "string", "description": "分析类型", "required": true } ], "returnType": "json" } ] } ``` ### 3. 代理注册端点 ✅ 代理注册端点允许代理向注册中心注册: ``` POST /api/registry/agents ``` 请求示例: ```json { "agent_card": { "id": "data-analysis-agent", "name": "数据分析代理", "description": "提供数据分析和可视化能力的代理", "version": "1.0.0", "capabilities": [ { "id": "data_analysis", "name": "数据分析", "description": "分析提供的数据集并返回统计结果", "parameters": [ { "name": "dataset_url", "type": "string", "description": "数据集URL", "required": true }, { "name": "analysis_type", "type": "string", "description": "分析类型", "required": true } ], "returnType": "json" } ], "authentication": { "type": "api_key" }, "endpoint": "https://example.com/api/agent" } } ``` 响应示例: ```json { "success": true, "agent_id": "data-analysis-agent", "message": "Agent registered successfully" } ``` ### 4. 代理查询端点 ✅ 代理查询端点允许查询注册中心中的代理: ``` GET /api/registry/agents?capability=data_analysis ``` 响应示例: ```json { "agents": [ { "id": "data-analysis-agent", "name": "数据分析代理", "description": "提供数据分析和可视化能力的代理", "version": "1.0.0", "capabilities": [ { "id": "data_analysis", "name": "数据分析", "description": "分析提供的数据集并返回统计结果", "parameters": [ { "name": "dataset_url", "type": "string", "description": "数据集URL", "required": true }, { "name": "analysis_type", "type": "string", "description": "分析类型", "required": true } ], "returnType": "json" } ], "authentication": { "type": "api_key" }, "endpoint": "https://example.com/api/agent" } ] } ``` ## 代理发现客户端 ✅ Kastrax 提供了代理发现客户端,用于与代理发现服务交互: ```kotlin // 创建代理发现客户端 val discoveryClient = A2ADiscoveryClient( config = A2ADiscoveryClientConfig( registryUrl = "https://registry.example.com" ) ) // 注册代理 val registrationResult = discoveryClient.registerAgent(agentCard) // 查询代理 val agents = discoveryClient.findAgentsByCapability("data_analysis") // 获取代理卡片 val agentCard = discoveryClient.getAgentCard("data-analysis-agent") ``` ## 代理发现示例 ✅ 以下是一个完整的代理发现示例: ```kotlin // 创建 A2A 实例 val a2aInstance = a2a { // 配置发现服务 discovery { // 启用自动发现 autoDiscovery = true discoveryInterval = 60000 // 60 秒 // 添加已知服务器 server("https://agent1.example.com") server("https://agent2.example.com") // 添加注册中心 registryServer("https://registry.example.com") // 自动注册 autoRegister = true } // 注册 A2A 代理 a2aAgent { id = "data-analysis-agent" name = "数据分析代理" description = "提供数据分析和可视化能力的代理" // 添加能力 capability { id = "data_analysis" name = "数据分析" description = "分析提供的数据集并返回统计结果" // 添加参数 parameter { name = "dataset_url" type = "string" description = "数据集URL" required = true } parameter { name = "analysis_type" type = "string" description = "分析类型" required = true } returnType = "json" } // 配置认证 authentication { type = AuthType.API_KEY } } // 配置服务器 server { port = 8080 enableCors = true } } // 启动 A2A 实例 a2aInstance.start() // 使用发现服务查找代理 val discoveryService = a2aInstance.getDiscoveryService() // 查找具有数据分析能力的代理 val dataAnalysisAgents = discoveryService.findAgentsByCapability("data_analysis") // 选择第一个代理 val selectedAgent = dataAnalysisAgents.firstOrNull() // 如果找到代理,则调用其能力 if (selectedAgent != null) { // 创建 A2A 客户端 val client = A2AClient( config = A2AClientConfig( serverUrl = selectedAgent.endpoint, apiKey = "your-api-key" ) ) // 调用代理能力 val response = client.invoke( agentId = selectedAgent.id, capabilityId = "data_analysis", parameters = mapOf( "dataset_url" to "https://example.com/data.csv", "analysis_type" to "statistical" ) ) // 处理响应 println("分析结果:${response.result}") // 关闭客户端 client.close() } // 停止 A2A 实例 a2aInstance.stop() ``` ## 总结 ✅ Kastrax 的 A2A 实现提供了全面的代理发现机制,包括代理注册、发现和查询,支持构建灵活的多代理系统。通过代理发现,代理可以动态地找到具有所需能力的其他代理,并与之协作,实现复杂的多代理协作场景。 ## 下一步 ✅ 了解了 A2A 代理发现后,您可以: 1. 了解 [A2A 任务委派](/docs/a2a/a2a-delegation.mdx) 2. 研究 [A2A 安全机制](/docs/a2a/a2a-security.mdx) 3. 探索 [多代理协作](/docs/a2a/a2a-collaboration.mdx) 4. 学习 [工作流引擎](/docs/a2a/a2a-workflow.mdx) 5. 研究 [A2A 协议规范](/docs/a2a/a2a-specification.mdx) --- title: "A2A 安全机制 | A2A 协议 | Kastrax 文档" description: "A2A 协议的安全机制,包括认证、授权、数据加密和安全最佳实践。" --- # A2A 安全机制 ✅ [ZH] Source: https://kastrax.ai/zh/docs/a2a/a2a-security ## 安全概述 ✅ A2A 协议的安全机制是确保代理之间安全通信的关键组件。Kastrax 实现了全面的 A2A 安全机制,包括认证、授权、数据加密和安全最佳实践。 安全机制的主要目标是: 1. **确保代理身份**:验证代理的身份,防止冒充 2. **控制访问权限**:限制代理对资源和能力的访问 3. **保护数据安全**:确保通信数据的机密性和完整性 4. **防止恶意行为**:检测和防止恶意代理的行为 5. **审计和跟踪**:记录代理活动,便于审计和问题排查 ## 认证机制 ✅ Kastrax 的 A2A 实现支持多种认证机制,可以根据需求选择合适的认证方式: ### API 密钥认证 ✅ API 密钥是最简单的认证方式,适用于内部系统或低安全需求的场景: ```kotlin // 创建安全服务,使用 API 密钥认证 val securityService = security { type = AuthType.API_KEY apiKeys = mapOf( "api-key-1" to Principal("user-1", listOf("user"), listOf("read", "write")), "api-key-2" to Principal("user-2", listOf("admin"), listOf("*")) ) } // 在 A2A 实例中配置安全服务 val a2aInstance = a2a { // 配置安全服务 security { type = AuthType.API_KEY apiKeys = mapOf( "api-key-1" to Principal("user-1", listOf("user"), listOf("read", "write")), "api-key-2" to Principal("user-2", listOf("admin"), listOf("*")) ) } // 其他配置... } // 验证 API 密钥 val authResult = securityService.validateApiKey("api-key-1") if (authResult is AuthResult.Success) { val principal = authResult.principal println("认证成功:${principal.id}") } else { println("认证失败:${(authResult as AuthResult.Failure).message}") } ``` ### JWT 认证 ✅ JWT (JSON Web Token) 是一种更安全的认证方式,适用于跨系统通信和高安全需求的场景: ```kotlin // 创建安全服务,使用 JWT 认证 val securityService = security { type = AuthType.JWT jwtSecret = "your-jwt-secret" jwtIssuer = "kastrax" jwtAudience = "a2a-api" } // 在 A2A 实例中配置安全服务 val a2aInstance = a2a { // 配置安全服务 security { type = AuthType.JWT jwtSecret = "your-jwt-secret" jwtIssuer = "kastrax" jwtAudience = "a2a-api" } // 其他配置... } // 生成 JWT 令牌 val token = securityService.generateJwt( userId = "user-1", roles = listOf("user"), permissions = listOf("read", "write"), expirationSeconds = 3600 ) // 验证 JWT 令牌 val authResult = securityService.validateJwt(token) if (authResult is AuthResult.Success) { val principal = authResult.principal println("认证成功:${principal.id}") } else { println("认证失败:${(authResult as AuthResult.Failure).message}") } ``` ### OAuth2 认证 ✅ OAuth2 是一种更复杂但更灵活的认证方式,适用于与第三方系统集成的场景: ```kotlin // 创建安全服务,使用 OAuth2 认证 val securityService = security { type = AuthType.OAUTH2 oauth2 { clientId = "your-client-id" clientSecret = "your-client-secret" authorizationUrl = "https://auth.example.com/authorize" tokenUrl = "https://auth.example.com/token" scope = "read write" redirectUrl = "https://your-app.example.com/callback" } } // 在 A2A 实例中配置安全服务 val a2aInstance = a2a { // 配置安全服务 security { type = AuthType.OAUTH2 oauth2 { clientId = "your-client-id" clientSecret = "your-client-secret" authorizationUrl = "https://auth.example.com/authorize" tokenUrl = "https://auth.example.com/token" scope = "read write" redirectUrl = "https://your-app.example.com/callback" } } // 其他配置... } // 获取授权 URL val authorizationUrl = securityService.getAuthorizationUrl() // 使用授权码获取访问令牌 val accessToken = securityService.getAccessToken("authorization-code") // 验证访问令牌 val authResult = securityService.validateAccessToken(accessToken) ``` ## 授权机制 ✅ 认证确定了代理的身份,而授权则控制代理可以访问的资源和执行的操作。Kastrax 的 A2A 实现提供了基于角色和权限的授权机制: ### 角色和权限 ✅ 每个代理可以拥有多个角色和权限,用于控制其访问权限: ```kotlin // 创建具有角色和权限的主体 val principal = Principal( id = "user-1", roles = listOf("user", "analyst"), permissions = listOf("read", "write", "analyze") ) // 检查角色 val hasUserRole = securityService.checkRole(principal, "user") // true val hasAdminRole = securityService.checkRole(principal, "admin") // false // 检查权限 val hasReadPermission = securityService.checkPermission(principal, "read") // true val hasDeletePermission = securityService.checkPermission(principal, "delete") // false ``` ### 能力授权 ✅ A2A 协议中的每个能力都可以定义所需的角色和权限,确保只有具有适当授权的代理才能调用该能力: ```kotlin // 创建具有授权要求的能力 val capability = capability { id = "data_analysis" name = "数据分析" description = "分析提供的数据集并返回统计结果" // 定义所需的角色和权限 requiredRoles = listOf("analyst") requiredPermissions = listOf("analyze") // 其他配置... } // 检查代理是否有权调用能力 fun canInvokeCapability(principal: Principal, capability: Capability): Boolean { // 检查角色 val hasRequiredRole = capability.requiredRoles.any { role -> securityService.checkRole(principal, role) } // 检查权限 val hasRequiredPermission = capability.requiredPermissions.any { permission -> securityService.checkPermission(principal, permission) } return hasRequiredRole && hasRequiredPermission } ``` ### 授权中间件 ✅ Kastrax 的 A2A 实现提供了授权中间件,自动检查代理的授权: ```kotlin // 配置授权中间件 val a2aInstance = a2a { // 配置安全服务 security { // 认证配置... // 授权中间件 authorization { // 全局授权规则 rule("*") { // 允许所有认证用户访问 authenticated = true } // 特定能力的授权规则 rule("data_analysis") { // 需要 analyst 角色 roles = listOf("analyst") // 需要 analyze 权限 permissions = listOf("analyze") } } } // 其他配置... } ``` ## 数据加密 ✅ 除了认证和授权,Kastrax 的 A2A 实现还提供了数据加密功能,确保通信数据的机密性和完整性: ### 传输加密 ✅ A2A 协议使用 HTTPS 进行传输加密,确保通信数据在传输过程中的安全: ```kotlin // 配置 HTTPS val a2aInstance = a2a { // 配置服务器 server { port = 8443 ssl { keyStore = "keystore.jks" keyStorePassword = "password" keyAlias = "a2a" privateKeyPassword = "password" } } // 其他配置... } ``` ### 消息加密 ✅ 对于高安全需求的场景,Kastrax 的 A2A 实现还支持消息级加密,确保即使在传输层被破解的情况下,消息内容仍然安全: ```kotlin // 配置消息加密 val a2aInstance = a2a { // 配置安全服务 security { // 认证和授权配置... // 消息加密 encryption { enabled = true algorithm = "AES/GCM/NoPadding" keySize = 256 // 使用共享密钥 sharedKey = "your-shared-key" // 或者使用非对称加密 // keyPair = loadKeyPair("keypair.jks", "password") } } // 其他配置... } // 加密消息 val encryptedMessage = securityService.encryptMessage(message) // 解密消息 val decryptedMessage = securityService.decryptMessage(encryptedMessage) ``` ## 安全审计和监控 ✅ Kastrax 的 A2A 实现提供了安全审计和监控功能,记录代理活动,便于审计和问题排查: ```kotlin // 配置安全审计 val a2aInstance = a2a { // 配置安全服务 security { // 认证、授权和加密配置... // 安全审计 audit { enabled = true logLevel = LogLevel.INFO events = listOf( "authentication", "authorization", "capability_invocation", "error" ) storage = AuditStorage.DATABASE retentionDays = 30 } } // 配置监控 monitoring { // 安全指标 metrics { enabled = true includeSecurityMetrics = true } // 安全日志 logging { enabled = true includeSecurityLogs = true } // 安全跟踪 tracing { enabled = true includeSecurityTraces = true } } // 其他配置... } ``` ## 安全最佳实践 ✅ 以下是使用 Kastrax A2A 安全机制的最佳实践: ### 1. 使用强认证 ✅ - 对于生产环境,使用 JWT 或 OAuth2 认证,而不是简单的 API 密钥 - 使用足够长的密钥和密码,并定期轮换 - 对敏感操作使用多因素认证 ### 2. 最小权限原则 ✅ - 为每个代理分配最小必要的角色和权限 - 避免使用通配符权限(如 "*") - 定期审查和清理不必要的权限 ### 3. 安全通信 ✅ - 始终使用 HTTPS 进行通信 - 对敏感数据使用消息级加密 - 使用安全的密码学算法和协议 ### 4. 输入验证 ✅ - 验证所有输入参数,防止注入攻击 - 使用 JSON Schema 定义参数格式,并严格验证 - 对敏感参数进行特殊处理,如密码和 API 密钥 ### 5. 安全监控和响应 ✅ - 启用安全审计和监控 - 设置异常检测和告警机制 - 制定安全事件响应计划 ### 6. 定期安全评估 ✅ - 定期进行安全评估和渗透测试 - 跟踪和修复安全漏洞 - 保持安全组件的更新 ## 安全配置示例 ✅ 以下是一个完整的 A2A 安全配置示例: ```kotlin // 创建 A2A 实例,配置全面的安全机制 val a2aInstance = a2a { // 配置安全服务 security { // 使用 JWT 认证 type = AuthType.JWT jwtSecret = System.getenv("JWT_SECRET") ?: "your-jwt-secret" jwtIssuer = "kastrax" jwtAudience = "a2a-api" // 授权规则 authorization { // 全局规则 rule("*") { authenticated = true } // 数据分析能力规则 rule("data_analysis") { roles = listOf("analyst") permissions = listOf("analyze") } // 报告生成能力规则 rule("generate_report") { roles = listOf("reporter") permissions = listOf("write") } } // 消息加密 encryption { enabled = true algorithm = "AES/GCM/NoPadding" keySize = 256 sharedKey = System.getenv("ENCRYPTION_KEY") ?: "your-encryption-key" } // 安全审计 audit { enabled = true logLevel = LogLevel.INFO events = listOf( "authentication", "authorization", "capability_invocation", "error" ) storage = AuditStorage.DATABASE retentionDays = 30 } } // 配置服务器 server { port = 8443 ssl { keyStore = System.getenv("SSL_KEYSTORE") ?: "keystore.jks" keyStorePassword = System.getenv("SSL_KEYSTORE_PASSWORD") ?: "password" keyAlias = "a2a" privateKeyPassword = System.getenv("SSL_PRIVATE_KEY_PASSWORD") ?: "password" } } // 配置监控 monitoring { // 安全指标 metrics { enabled = true includeSecurityMetrics = true } // 安全日志 logging { enabled = true includeSecurityLogs = true } // 安全跟踪 tracing { enabled = true includeSecurityTraces = true } } // 注册代理 a2aAgent { id = "data-analysis-agent" name = "数据分析代理" description = "提供数据分析和可视化能力的代理" // 配置能力 capability { id = "data_analysis" name = "数据分析" description = "分析提供的数据集并返回统计结果" // 定义所需的角色和权限 requiredRoles = listOf("analyst") requiredPermissions = listOf("analyze") // 其他配置... } // 配置认证 authentication { type = AuthType.JWT } } // 其他配置... } ``` ## 总结 ✅ Kastrax 的 A2A 实现提供了全面的安全机制,包括认证、授权、数据加密和安全审计,确保代理之间的安全通信。通过合理配置和遵循安全最佳实践,可以构建安全可靠的多代理系统。 ## 下一步 ✅ 了解了 A2A 安全机制后,您可以: 1. 学习 [A2A 代理发现](/docs/a2a/a2a-discovery.mdx) 2. 了解 [A2A 任务委派](/docs/a2a/a2a-delegation.mdx) 3. 探索 [多代理协作](/docs/a2a/a2a-collaboration.mdx) 4. 学习 [工作流引擎](/docs/a2a/a2a-workflow.mdx) 5. 研究 [A2A 协议规范](/docs/a2a/a2a-specification.mdx) --- title: "A2A 协议概述 | A2A 协议 | Kastrax 文档" description: "A2A (Agent-to-Agent) 协议的概述,包括核心概念、设计目标和实现方式。" --- # A2A 协议概述 ✅ [ZH] Source: https://kastrax.ai/zh/docs/a2a/overview ## 什么是 A2A 协议 ✅ A2A (Agent-to-Agent) 是一个开放协议,旨在实现 AI 代理之间的通信和协作。Kastrax 实现了完整的 A2A 协议,使用 Kotlin 的 actor 风格编程模型,实现代理之间的互操作性。 A2A 协议允许不同的 AI 代理发现彼此的能力,并通过标准化的接口进行通信和协作,从而构建复杂的多代理系统。 ## 核心概念 ✅ ### Agent Card ✅ Agent Card 是描述代理能力、技能、端点 URL 和认证需求的 JSON 文件,通常位于 `/.well-known/agent.json`,用于能力发现。它包含以下信息: - **代理元数据**:ID、名称、描述、版本等 - **能力列表**:代理提供的能力,包括参数和返回类型 - **认证需求**:访问代理所需的认证方式 - **端点 URL**:代理的 API 端点 ```json { "id": "data-analysis-agent", "name": "数据分析代理", "description": "提供数据分析和可视化能力的代理", "version": "1.0.0", "capabilities": [ { "id": "data_analysis", "name": "数据分析", "description": "分析提供的数据集并返回统计结果", "parameters": [ { "name": "dataset_url", "type": "string", "description": "数据集URL", "required": true }, { "name": "analysis_type", "type": "string", "description": "分析类型", "required": true } ], "returnType": "json" } ], "authentication": { "type": "api_key" }, "endpoint": "https://example.com/api/agent" } ``` ### A2A 消息 ✅ A2A 协议定义了一组标准消息类型,用于代理之间的通信: - **CapabilityRequest**:请求代理的能力列表 - **CapabilityResponse**:返回代理的能力列表 - **InvokeRequest**:调用代理的能力 - **InvokeResponse**:返回调用结果 - **QueryRequest**:查询代理的状态 - **QueryResponse**:返回代理的状态 - **ErrorMessage**:表示错误情况 ### 代理发现 ✅ 代理发现是 A2A 协议的核心功能之一,允许代理发现其他代理的存在和能力。Kastrax 实现了以下代理发现机制: - **静态配置**:通过配置文件指定已知代理 - **自动发现**:定期扫描已知服务器,发现新代理 - **注册中心**:代理可以向中央注册中心注册自己 ### 安全机制 ✅ A2A 协议支持多种安全机制,确保代理之间的通信安全: - **API 密钥**:使用 API 密钥进行认证 - **JWT**:使用 JSON Web Token 进行认证 - **OAuth2**:使用 OAuth2 进行认证 - **授权**:基于角色和权限的授权 - **数据加密**:通信数据的加密 ## Kastrax 中的 A2A 实现 ✅ Kastrax 提供了完整的 A2A 协议实现,包括以下组件: ### 核心组件 ✅ 1. **A2A Protocol Core**:协议核心实现,定义基本接口和数据结构 2. **Agent Discovery**:代理发现机制,包括注册、查询和更新 3. **Agent Card**:代理能力描述,包括元数据、能力和认证需求 4. **Agent Communication**:代理间通信机制,包括消息格式和传输 5. **Message Bus**:基于 Kotlin 协程和 Channel 的消息总线,处理代理间的异步通信 6. **Agent Orchestration**:代理编排机制,协调多个代理的协作 7. **Workflow Engine**:工作流引擎,管理代理间的复杂交互和任务流程 8. **Security Service**:安全服务,提供认证和授权功能 9. **Task Manager**:任务管理器,管理代理任务的创建、执行和状态跟踪 10. **Monitoring Service**:监控服务,收集代理的指标、日志和跟踪信息 ### DSL 接口 ✅ Kastrax 提供了与 Kastrax 风格一致的 DSL 接口,简化了 A2A 代理的创建和配置: ```kotlin // 创建 A2A 实例 val a2aInstance = a2a { // 注册 kastrax 代理 agent(assistantAgent) // 注册 A2A 代理 a2aAgent { id = "data-analysis-agent" name = "数据分析代理" description = "提供数据分析和可视化能力的代理" baseAgent = assistantAgent capability { id = "data_analysis" name = "数据分析" description = "分析提供的数据集并返回统计结果" parameter { name = "dataset_url" type = "string" description = "数据集URL" required = true } parameter { name = "analysis_type" type = "string" description = "分析类型" required = true } returnType = "json" } authentication { type = AuthType.API_KEY } } // 配置服务器 server { port = 8080 enableCors = true } // 添加服务器到发现服务 discovery("http://localhost:8080") } ``` ### 与 Kastrax 集成 ✅ Kastrax 的 A2A 实现与现有的 Kastrax 代理系统无缝集成: - **代理适配器**:将 Kastrax 代理适配为 A2A 代理 - **工具集成**:A2A 能力可以作为 Kastrax 工具使用 - **记忆集成**:A2A 代理可以访问 Kastrax 的记忆系统 - **工作流集成**:A2A 代理可以参与 Kastrax 工作流 ## 多代理协作 ✅ A2A 协议的主要目标是实现多代理协作,Kastrax 提供了多种协作模式: ### 直接调用 ✅ 最简单的协作模式是直接调用,一个代理直接调用另一个代理的能力: ```kotlin // 创建数据收集代理 val dataCollectorAgent = a2aAgent { id = "data-collector" name = "数据收集代理" // 配置... } // 创建数据分析代理 val dataAnalysisAgent = a2aAgent { id = "data-analysis" name = "数据分析代理" // 配置... } // 创建报告生成代理 val reportGeneratorAgent = a2aAgent { id = "report-generator" name = "报告生成代理" // 配置... } // 多代理协作 suspend fun generateMarketReport(marketId: String): MarketReport { // 1. 收集数据 val dataRequest = InvokeRequest( capability = "collect_market_data", parameters = mapOf("market_id" to marketId) ) val dataResponse = dataCollectorAgent.invoke(dataRequest) val marketData = dataResponse.result as MarketData // 2. 分析数据 val analysisRequest = InvokeRequest( capability = "analyze_market_data", parameters = mapOf("market_data" to marketData) ) val analysisResponse = dataAnalysisAgent.invoke(analysisRequest) val marketAnalysis = analysisResponse.result as MarketAnalysis // 3. 生成报告 val reportRequest = InvokeRequest( capability = "generate_market_report", parameters = mapOf( "market_data" to marketData, "market_analysis" to marketAnalysis ) ) val reportResponse = reportGeneratorAgent.invoke(reportRequest) return reportResponse.result as MarketReport } ``` ### 代理发现和动态协作 ✅ 更高级的协作模式是代理发现和动态协作,代理可以动态发现具有所需能力的其他代理: ```kotlin // 代理注册表 val agentRegistry = A2AAgentRegistry() // 注册代理 agentRegistry.register(dataCollectorAgent) agentRegistry.register(dataAnalysisAgent) agentRegistry.register(reportGeneratorAgent) // 动态发现和协作 suspend fun dynamicCollaboration(task: Task): Result { // 1. 发现具有所需能力的代理 val capableAgents = agentRegistry.findAgentsByCapability(task.requiredCapabilities) // 2. 选择最合适的代理 val selectedAgent = selectBestAgent(capableAgents, task) // 3. 执行任务 val request = InvokeRequest( capability = task.capability, parameters = task.parameters ) val response = selectedAgent.invoke(request) return response.result } ``` ### 工作流引擎 ✅ 最复杂的协作模式是使用工作流引擎,定义和执行复杂的多代理协作流程: ```kotlin // 创建工作流 val marketReportWorkflow = workflow { id = "market-report-workflow" name = "市场报告工作流" description = "生成市场报告的工作流" // 输入参数 input { parameter("market_id", "市场ID", String::class) } // 工作流步骤 steps { // 步骤 1:收集数据 step("collect_data") { agentId = "data-collector" capabilityId = "collect_market_data" parameters { parameter("market_id", context.get("market_id")) } output = "market_data" } // 步骤 2:分析数据 step("analyze_data") { agentId = "data-analysis" capabilityId = "analyze_market_data" parameters { parameter("market_data", context.get("market_data")) } output = "market_analysis" dependsOn = listOf("collect_data") } // 步骤 3:生成报告 step("generate_report") { agentId = "report-generator" capabilityId = "generate_market_report" parameters { parameter("market_data", context.get("market_data")) parameter("market_analysis", context.get("market_analysis")) } output = "market_report" dependsOn = listOf("analyze_data") } } // 输出参数 output { parameter("market_report", "市场报告", MarketReport::class) } } // 执行工作流 val workflowEngine = WorkflowEngine() val result = workflowEngine.execute(marketReportWorkflow, mapOf("market_id" to "MARKET-123")) val marketReport = result.get("market_report") ``` ## 任务管理 ✅ Kastrax 的 A2A 实现提供了完整的任务管理功能,支持任务的创建、执行、取消和状态跟踪: ```kotlin // 创建任务管理器 val taskManager = TaskManager() // 创建任务 val message = Message( parts = listOf( TextPart( text = "分析市场数据并生成报告" ) ) ) val task = taskManager.createTask(message, sessionId = "user-session-1") // 设置推送通知 taskManager.setTaskPushNotification( taskId = task.id, config = PushNotificationConfig( url = "https://example.com/webhook", events = listOf("task.completed", "task.failed") ) ) // 处理任务 taskManager.processTask(agent, task) // 获取任务状态 val taskStatus = taskManager.getTaskStatus(task.id) // 取消任务 taskManager.cancelTask(task.id) ``` ## 安全机制 ✅ Kastrax 的 A2A 实现提供了多种安全机制,确保代理之间的通信安全: ```kotlin // 创建安全服务 val securityService = security { // 配置 API 密钥认证 type = AuthType.API_KEY apiKeys = mapOf( "api-key-1" to Principal("user-1", listOf("user"), listOf("read", "write")) ) } // 或者配置 JWT 认证 val securityService = security { type = AuthType.JWT jwtSecret = "your-jwt-secret" jwtIssuer = "kastrax" jwtAudience = "a2a-api" } // 验证 API 密钥 val authResult = securityService.validateApiKey("api-key-1") if (authResult is AuthResult.Success) { val principal = authResult.principal // 检查权限 val hasPermission = securityService.checkPermission(principal, "read") // 检查角色 val hasRole = securityService.checkRole(principal, "user") } // 生成 JWT 令牌 val token = securityService.generateJwt( userId = "user-1", roles = listOf("user"), permissions = listOf("read", "write"), expirationSeconds = 3600 ) // 验证 JWT 令牌 val authResult = securityService.validateJwt(token) ``` ## 监控和指标 ✅ Kastrax 的 A2A 实现提供了全面的监控系统,支持指标收集、日志记录和跟踪: ```kotlin // 创建监控服务 val monitoringService = monitoring { // 指标事件处理 onMetric { event -> println("Metric: ${event.name} = ${event.value}") } // 日志事件处理 onLog { event -> println("Log: ${event.level} - ${event.message}") } // 跟踪事件处理 onTrace { event -> println("Trace: ${event.name} - ${event.duration}ms") } } // 记录指标 monitoringService.recordMetric("requests_count", 1) // 记录日志 monitoringService.log(LogLevel.INFO, "处理请求") // 记录跟踪 monitoringService.startTrace("process_request").use { trace -> // 处理请求 trace.addAttribute("request_id", "req-123") } ``` ## 总结 ✅ Kastrax 的 A2A 实现提供了完整的代理间通信和协作功能,包括代理发现、能力调用、多代理协作、工作流引擎、任务管理、安全机制和监控系统。通过 A2A 协议,Kastrax 代理可以与其他代理无缝协作,构建复杂的多代理系统。 ## 下一步 ✅ 了解了 A2A 协议的基本概念后,您可以: 1. 学习 [A2A 代理发现](/docs/a2a/a2a-discovery.mdx) 2. 了解 [A2A 任务委派](/docs/a2a/a2a-delegation.mdx) 3. 研究 [A2A 安全机制](/docs/a2a/a2a-security.mdx) 4. 探索 [多代理协作](/docs/a2a/a2a-collaboration.mdx) 5. 学习 [工作流引擎](/docs/a2a/a2a-workflow.mdx) --- title: Actor-代理集成 | Actor 模型 | Kastrax 文档 description: 将 Kastrax 代理与 Actor 模型集成的详细指南,使分布式、并发代理系统成为可能。 --- # Actor-代理集成 ✅ [ZH] Source: https://kastrax.ai/zh/docs/actor/actor-agent-integration-kotlin Kastrax 提供了 AI 代理和 Actor 模型之间的强大集成,使分布式、并发代理系统成为可能。本指南解释了如何将 Kastrax 代理与 actor 集成。 ## 集成基础 ✅ Kastrax 代理与 Actor 模型的集成允许您: - 在分布式系统中将代理作为 actor 运行 - 启用代理之间的异步通信 - 创建协作代理网络 - 水平扩展代理系统 - 提供容错和监督 ## KastraxActor ✅ `KastraxActor` 类是将 Kastrax 代理包装为 actor 的核心组件: ```kotlin import ai.kastrax.actor.KastraxActor import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import actor.proto.ActorSystem import actor.proto.Props import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 Actor 系统 val system = ActorSystem("kastrax-system") // 创建 Kastrax 代理 val myAgent = agent { name("MyAgent") description("一个简单的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // 使用代理创建 KastraxActor val props = Props.create { KastraxActor(myAgent) } val agentPid = system.root.spawn(props) // 向代理发送消息 system.root.send(agentPid, AgentRequest("你好,代理!")) system.shutdown() } // 消息类 data class AgentRequest(val query: String) ``` ## ActorAgent DSL ✅ Kastrax 提供了用于创建基于 actor 的代理的 DSL: ```kotlin import ai.kastrax.actor.actorAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import actor.proto.ActorSystem import kotlinx.coroutines.runBlocking import java.time.Duration fun main() = runBlocking { // 创建 Actor 系统 val system = ActorSystem("kastrax-system") // 使用 DSL 创建 actor-agent val agentPid = system.actorAgent { // 配置代理 agent { name("AssistantAgent") description("一个有帮助的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { tool("calculator") { description("执行基本算术运算") parameters { parameter("expression", "要计算的数学表达式", String::class) } execute { params -> val expression = params["expression"] as String // 简单的表达式计算器 "结果: ${evaluateExpression(expression)}" } } } } // 配置 actor actor { // 设置监督策略 oneForOneStrategy { maxRetries = 3 withinTimeRange = Duration.ofMinutes(1) } // 设置邮箱类型 unboundedMailbox() } } // 使用 actor-agent system.sendMessage(agentPid, "2 + 2 等于多少?") // 请求-响应模式 val response = system.askMessage(agentPid, "法国的首都是什么?") println("响应: $response") // 流式模式 system.streamMessage(agentPid, "讲个故事") { chunk -> print(chunk) } system.shutdown() } // 简单的表达式计算器 fun evaluateExpression(expression: String): Double { // 实现... return 4.0 } ``` ## 通信模式 ✅ 该集成支持各种通信模式: ### 发送后忘记 ✅ ```kotlin // 发送消息而不等待响应 system.sendMessage(agentPid, "你好,代理!") ``` ### 请求-响应 ✅ ```kotlin // 发送消息并等待响应 val response = system.askMessage(agentPid, "法国的首都是什么?") println("响应: $response") ``` ### 流式 ✅ ```kotlin // 发送消息并接收响应流 system.streamMessage(agentPid, "讲个故事") { chunk -> print(chunk) } ``` ## 代理网络 ✅ Kastrax 支持创建可以协作的代理网络: ```kotlin import ai.kastrax.actor.agentNetwork import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import actor.proto.ActorSystem import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 Actor 系统 val system = ActorSystem("kastrax-system") // 创建代理网络 val network = system.agentNetwork { // 配置协调员代理 coordinator { agent { name("Coordinator") description("一个管理多个专家代理的协调员") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } actor { oneForOneStrategy { maxRetries = 5 } } } // 添加专家代理 agent("researcher") { agent { name("Researcher") description("一个研究专家") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } } agent("writer") { agent { name("Writer") description("一个写作专家") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } } agent("critic") { agent { name("Critic") description("一个批评性审阅者") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } } } // 使用网络 val response = network.process("研究 AI 对医疗保健的影响") println(response) system.shutdown() } ``` ## 代理监督 ✅ 基于 actor 的代理可以被监督以处理故障: ```kotlin import ai.kastrax.actor.actorAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import actor.proto.ActorSystem import actor.proto.SupervisorStrategy import kotlinx.coroutines.runBlocking import java.time.Duration fun main() = runBlocking { // 创建 Actor 系统 val system = ActorSystem("kastrax-system") // 创建监督代理 val supervisorPid = system.actorAgent { agent { name("Supervisor") description("一个监督代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } actor { // 配置监督策略 oneForOneStrategy { maxRetries = 3 withinTimeRange = Duration.ofMinutes(1) decider = { _, exception -> when (exception) { is IllegalArgumentException -> SupervisorStrategy.Directive.Restart is IllegalStateException -> SupervisorStrategy.Directive.Stop else -> SupervisorStrategy.Directive.Escalate } } } } } // 创建子代理 val childPid = system.actorAgent { agent { name("Child") description("一个子代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } actor { // 设置父级 parent = supervisorPid } } // 使用子代理 system.sendMessage(childPid, "你好,子代理!") // 模拟错误 system.sendMessage(childPid, "error") // 这将触发错误并重启 system.shutdown() } ``` ## 远程代理集成 ✅ 基于 actor 的代理可以分布在多个系统中: ```kotlin import ai.kastrax.actor.actorAgent import ai.kastrax.actor.remoteActorAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import actor.proto.ActorSystem import actor.proto.remote.Remote import kotlinx.coroutines.runBlocking // 服务器代码 fun startServer() = runBlocking { // 创建 Actor 系统 val system = ActorSystem("kastrax-server") // 配置远程功能 val remote = Remote(system) remote.start("localhost", 8080) // 创建远程 actor-agent val agentPid = system.actorAgent { agent { name("RemoteAgent") description("一个远程代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } remote { name = "remote-agent" // 用于远程访问的名称 } } // 保持系统运行 println("远程代理运行在 localhost:8080") println("按 Enter 键退出") readLine() system.shutdown() } // 客户端代码 fun startClient() = runBlocking { // 创建 Actor 系统 val system = ActorSystem("kastrax-client") // 连接到远程代理 val remoteAgent = system.remoteActorAgent("localhost:8080", "remote-agent") // 使用远程代理 val response = remoteAgent.generate("法国的首都是什么?") println("代理响应: ${response.text}") system.shutdown() } // 主函数 fun main(args: Array) { if (args.isEmpty() || args[0] == "server") { startServer() } else if (args[0] == "client") { startClient() } else { println("无效参数。使用 'server' 或 'client'。") } } ``` ## 代理状态管理 ✅ 基于 actor 的代理可以在交互之间维护状态: ```kotlin import ai.kastrax.actor.actorAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import actor.proto.ActorSystem import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 Actor 系统 val system = ActorSystem("kastrax-system") // 创建有状态的 actor-agent val agentPid = system.actorAgent { agent { name("StatefulAgent") description("一个有状态的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } actor { // 定义状态 state { var userPreferences = mutableMapOf() var interactionCount = 0 } // 处理消息 receive { message, context -> when (message) { is String -> { // 更新状态 context.state.interactionCount++ // 检查偏好更新 if (message.contains("prefer")) { // 提取偏好 val preference = extractPreference(message) if (preference != null) { context.state.userPreferences[preference.first] = preference.second } } // 生成具有状态感知的响应 val response = context.agent.generate( """ 用户偏好: ${context.state.userPreferences} 交互计数: ${context.state.interactionCount} 用户消息: $message """.trimIndent() ) // 发送响应 context.respond(response.text) } else -> context.respond("不支持的消息类型") } } } } // 使用有状态代理 val response1 = system.askMessage(agentPid, "你好,我是 Alice") println("响应 1: $response1") val response2 = system.askMessage(agentPid, "我喜欢简洁的回答") println("响应 2: $response2") val response3 = system.askMessage(agentPid, "告诉我关于量子计算的信息") println("响应 3: $response3") system.shutdown() } // 提取偏好的辅助函数 fun extractPreference(message: String): Pair? { // 实现... return if (message.contains("prefer concise")) { "responseStyle" to "concise" } else { null } } ``` ## 代理生命周期管理 ✅ 基于 actor 的代理有一个可以管理的生命周期: ```kotlin import ai.kastrax.actor.actorAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import actor.proto.ActorSystem import actor.proto.PoisonPill import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 Actor 系统 val system = ActorSystem("kastrax-system") // 创建 actor-agent val agentPid = system.actorAgent { agent { name("LifecycleAgent") description("一个具有生命周期管理的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } actor { // 定义生命周期钩子 preStart { context -> println("代理启动: ${context.agent.name}") // 初始化资源 } postStop { context -> println("代理停止: ${context.agent.name}") // 清理资源 } preRestart { context, reason -> println("代理重启: ${context.agent.name}, 原因: $reason") // 重启前清理 } postRestart { context -> println("代理已重启: ${context.agent.name}") // 重启后初始化 } } } // 使用代理 system.sendMessage(agentPid, "你好,代理!") // 停止代理 system.root.send(agentPid, PoisonPill.Instance) // 等待代理停止 Thread.sleep(1000) system.shutdown() } ``` ## 完整示例 ✅ 以下是使用 Actor 模型的复杂代理系统的完整示例: ```kotlin import ai.kastrax.actor.actorAgent import ai.kastrax.actor.agentNetwork import ai.kastrax.memory.api.memory import ai.kastrax.memory.impl.MemoryFactory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import actor.proto.ActorSystem import actor.proto.SupervisorStrategy import actor.proto.remote.Remote import kotlinx.coroutines.runBlocking import java.time.Duration fun main() = runBlocking { // 创建 Actor 系统 val system = ActorSystem("kastrax-system") // 配置远程功能 val remote = Remote(system) remote.start("localhost", 8080) // 创建代理网络 val network = system.agentNetwork { // 配置协调员代理 coordinator { agent { name("Coordinator") description("一个管理多个专家代理的协调员") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置记忆 memory = memory { storage(MemoryFactory.createInMemoryStorage()) lastMessages(20) workingMemory(WorkingMemoryConfig(enabled = true)) } } actor { oneForOneStrategy { maxRetries = 5 withinTimeRange = Duration.ofMinutes(5) decider = { _, exception -> when (exception) { is IllegalArgumentException -> SupervisorStrategy.Directive.Restart is IllegalStateException -> SupervisorStrategy.Directive.Stop else -> SupervisorStrategy.Directive.Escalate } } } state { var taskCount = 0 var activeAgents = mutableSetOf() } preStart { context -> println("协调员启动: ${context.agent.name}") } postStop { context -> println("协调员停止: ${context.agent.name}") } } } // 添加专家代理 agent("researcher") { agent { name("Researcher") description("一个研究专家") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { tool("searchWeb") { description("在网上搜索信息") parameters { parameter("query", "搜索查询", String::class) } execute { params -> val query = params["query"] as String // 实现... "搜索结果: $query" } } } memory = memory { storage(MemoryFactory.createInMemoryStorage()) lastMessages(10) } } actor { state { var researchCount = 0 var specialties = listOf("AI", "Healthcare", "Finance") } } } agent("writer") { agent { name("Writer") description("一个写作专家") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } memory = memory { storage(MemoryFactory.createInMemoryStorage()) lastMessages(10) } } actor { state { var writingCount = 0 var styles = mutableMapOf( "technical" to 0, "creative" to 0, "business" to 0 ) } } } agent("critic") { agent { name("Critic") description("一个批评性审阅者") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } memory = memory { storage(MemoryFactory.createInMemoryStorage()) lastMessages(10) } } actor { state { var reviewCount = 0 var feedbackProvided = mutableMapOf() } } } // 配置远程访问 remote { name = "agent-network" } } // 创建独立的助手代理 val assistantPid = system.actorAgent { agent { name("Assistant") description("一个有帮助的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { tool("calculator") { description("执行基本算术运算") parameters { parameter("expression", "要计算的数学表达式", String::class) } execute { params -> val expression = params["expression"] as String // 简单的表达式计算器 "结果: ${evaluateExpression(expression)}" } } tool("getWeather") { description("获取位置的当前天气") parameters { parameter("location", "要获取天气的位置", String::class) } execute { params -> val location = params["location"] as String // 实现... "$location 的天气目前是晴朗,22°C。" } } } memory = memory { storage(MemoryFactory.createInMemoryStorage()) lastMessages(20) workingMemory(WorkingMemoryConfig(enabled = true)) } } actor { oneForOneStrategy { maxRetries = 3 withinTimeRange = Duration.ofMinutes(1) } state { var userPreferences = mutableMapOf() var interactionCount = 0 } receive { message, context -> when (message) { is String -> { // 更新状态 context.state.interactionCount++ // 生成响应 val response = context.agent.generate(message) // 发送响应 context.respond(response.text) } else -> context.respond("不支持的消息类型") } } } remote { name = "assistant" } } // 使用助手代理 println("向助手发送消息...") val response = system.askMessage(assistantPid, "2 + 2 等于多少?") println("助手响应: $response") // 使用代理网络 println("使用代理网络处理任务...") val networkResponse = network.process("研究 AI 对医疗保健的影响并撰写全面报告") println("网络响应: $networkResponse") // 保持系统运行 println("系统运行在 localhost:8080") println("按 Enter 键退出") readLine() system.shutdown() } // 辅助函数 fun evaluateExpression(expression: String): Double { // 实现... return 4.0 } ``` ## 最佳实践 ✅ 1. **消息不可变性**:确保消息是不可变的,以避免并发问题 2. **Actor 隔离**:保持 actor 隔离并避免共享状态 3. **监督策略**:使用适当的监督策略进行错误处理 4. **状态管理**:使用 actor 状态维护交互之间的上下文 5. **资源管理**:正确管理 actor 生命周期和资源 6. **错误处理**:为代理故障实现强大的错误处理 7. **记忆集成**:集成记忆系统以获得持久上下文 8. **工具集成**:集成工具以增强代理能力 ## 下一步 ✅ 现在您已经了解了 actor-代理集成,您可以: 1. 了解[代理网络](./agent-networks.mdx) 2. 探索[集群](./clustering.mdx) 3. 实现[监督层次结构](./supervision.mdx) 4. 设置[分布式工作流](../workflows/distributed-workflows.mdx) --- title: Actor 模型概述 | Kastrax 文档 description: Kastrax 中 Actor 模型的概述,详细说明它如何为构建分布式、并发和弹性 AI 代理系统提供基础。 --- # Actor 模型概述 ✅ [ZH] Source: https://kastrax.ai/zh/docs/actor/overview Kastrax 框架集成了来自 kactor 库的 Actor 模型,为构建分布式、并发和弹性 AI 代理系统提供了强大的基础。本指南解释了 Actor 模型以及它在 Kastrax 中的使用方式。 ## 什么是 Actor 模型? ✅ Actor 模型是一种并发计算的数学模型,将"actors"视为并发计算的通用原语。在响应接收到的消息时,actor 可以: 1. 做出本地决策 2. 创建更多 actors 3. 发送更多消息 4. 确定如何响应下一条接收到的消息 Actors 彼此隔离,只通过消息进行通信,这提供了关注点的清晰分离,使推理并发系统变得更容易。 ## Kastrax 中的 Actor 模型 ✅ 在 Kastrax 中,Actor 模型用于: 1. **分布代理**:在多台机器上运行代理 2. **管理并发**:同时处理多个代理交互 3. **确保弹性**:优雅地从故障中恢复 4. **扩展系统**:轻松扩大或缩小代理系统 ## 基本 Actor 概念 ✅ ### Actors ✅ Actor 是计算的基本单位。它具有: - 用于接收消息的邮箱 - 定义如何处理消息的行为 - 在处理消息时可以修改的状态 - 创建其他 actors 的能力 - 向其他 actors 发送消息的能力 ### 消息 ✅ 消息是 actors 通信的唯一方式。它们是: - 不可变的 - 异步的 - 由每个 actor 一次处理一条 ### Actor 系统 ✅ Actor 系统管理 actors 的生命周期并提供: - Actor 创建和监督 - 消息路由 - 资源管理 - 配置 ## 创建基本 Actor ✅ 以下是在 Kastrax 中创建简单 actor 的方法: ```kotlin import actor.proto.Actor import actor.proto.Context import actor.proto.Props import actor.proto.spawn import kotlinx.coroutines.runBlocking // 定义消息 data class Greeting(val who: String) // 定义 actor class GreetingActor : Actor { override suspend fun receive(context: Context) { val message = context.message when (message) { is Greeting -> { println("你好,${message.who}!") } else -> { println("未知消息:$message") } } } } fun main() = runBlocking { // 创建 actor 系统 val system = actor.proto.ActorSystem.create() // 创建 actor val greeter = system.spawn(Props.create(GreetingActor::class.java), "greeter") // 向 actor 发送消息 greeter.tell(Greeting("世界")) // 等待一会儿让消息被处理 kotlinx.coroutines.delay(100) // 关闭系统 system.shutdown() } ``` ## 将 Actors 与代理集成 ✅ Kastrax 允许您将代理包装在 actors 中,以创建分布式代理系统: ```kotlin import actor.proto.Actor import actor.proto.Context import actor.proto.Props import actor.proto.spawn import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking // 定义消息 data class GenerateRequest(val prompt: String) data class GenerateResponse(val text: String) // 定义包装代理的 actor class AgentActor(private val agent: Agent) : Actor { override suspend fun receive(context: Context) { val message = context.message when (message) { is GenerateRequest -> { val response = agent.generate(message.prompt) context.sender.tell(GenerateResponse(response.text)) } else -> { println("未知消息:$message") } } } companion object { fun props(agent: Agent): Props { return Props.create { AgentActor(agent) } } } } fun main() = runBlocking { // 创建代理 val myAgent = agent { name("我的代理") description("一个简单的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // 创建 actor 系统 val system = actor.proto.ActorSystem.create() // 创建包装代理的 actor val agentActor = system.spawn(AgentActor.props(myAgent), "agent-actor") // 为响应创建 future val future = agentActor.ask(GenerateRequest("讲个笑话"), 5000) // 等待响应 val response = future.await() as GenerateResponse println("代理响应:${response.text}") // 关闭系统 system.shutdown() } ``` ## 远程 Actors ✅ Actor 模型最强大的特性之一是能够在多台机器上分布 actors。Kastrax 通过远程 actors 支持这一点: ```kotlin import actor.proto.Actor import actor.proto.Context import actor.proto.Props import actor.proto.remote.RemoteConfig import actor.proto.remote.RemoteActorSystem import kotlinx.coroutines.runBlocking // 定义消息 data class Ping(val message: String) data class Pong(val message: String) // 定义 actor class PongActor : Actor { override suspend fun receive(context: Context) { val message = context.message when (message) { is Ping -> { println("收到 ping:${message.message}") context.sender.tell(Pong("对 ${message.message} 的 Pong 响应")) } else -> { println("未知消息:$message") } } } } // 服务器代码 fun startServer() = runBlocking { // 创建远程配置 val config = RemoteConfig.create { hostname = "localhost" port = 8090 } // 创建远程 actor 系统 val system = RemoteActorSystem.create("server-system", config) // 创建 actor val pongActor = system.spawn(Props.create(PongActor::class.java), "pong-actor") // 注册 actor 以便可以远程访问 system.registerActor("pong", pongActor) println("服务器已启动,按回车键退出...") readLine() // 关闭系统 system.shutdown() } // 客户端代码 fun startClient() = runBlocking { // 创建远程配置 val config = RemoteConfig.create { hostname = "localhost" port = 0 // 使用任何可用端口 } // 创建远程 actor 系统 val system = RemoteActorSystem.create("client-system", config) // 获取远程 actor 的引用 val remotePongActor = system.getActorRef("pong@localhost:8090") // 向远程 actor 发送消息 val future = remotePongActor.ask(Ping("来自客户端的问候"), 5000) // 等待响应 val response = future.await() as Pong println("收到响应:${response.message}") // 关闭系统 system.shutdown() } ``` ## Actor 监督 ✅ Actors 可以监督其他 actors,这允许容错和恢复: ```kotlin import actor.proto.Actor import actor.proto.Context import actor.proto.Props import actor.proto.SupervisorStrategy import actor.proto.Directive import actor.proto.spawn import kotlinx.coroutines.runBlocking // 定义消息 data class Work(val input: Int) data class Result(val output: Int) // 定义可能失败的 actor class WorkerActor : Actor { override suspend fun receive(context: Context) { val message = context.message when (message) { is Work -> { if (message.input == 0) { throw ArithmeticException("除以零") } val result = 100 / message.input context.sender.tell(Result(result)) } else -> { println("未知消息:$message") } } } } // 定义监督 actor class SupervisorActor : Actor { private var workerRef: actor.proto.PID? = null override suspend fun receive(context: Context) { val message = context.message when (message) { is actor.proto.Started -> { // 当监督者启动时创建工作者 workerRef = context.spawn(Props.create(WorkerActor::class.java), "worker") } is Work -> { // 将工作转发给工作者 workerRef?.tell(message) } is Result -> { println("工作者产生结果:${message.output}") } else -> { println("未知消息:$message") } } } override fun supervisorStrategy(): SupervisorStrategy { return SupervisorStrategy.restartStrategy { _, _ -> // 在任何异常上重启工作者 Directive.Restart } } } fun main() = runBlocking { // 创建 actor 系统 val system = actor.proto.ActorSystem.create() // 创建监督 actor val supervisor = system.spawn(Props.create(SupervisorActor::class.java), "supervisor") // 向监督者发送工作 supervisor.tell(Work(4)) // 应该正常工作 kotlinx.coroutines.delay(100) supervisor.tell(Work(0)) // 将导致异常 kotlinx.coroutines.delay(100) supervisor.tell(Work(5)) // 重启后应该再次工作 kotlinx.coroutines.delay(100) // 关闭系统 system.shutdown() } ``` ## Actor 模式 ✅ ### 请求-响应模式 ✅ ```kotlin // 发送请求并等待响应 val future = actorRef.ask(Request("一些数据"), 5000) val response = future.await() as Response println("收到响应:${response.data}") ``` ### 发布-订阅模式 ✅ ```kotlin // 创建主题 val topic = system.createTopic("events") // 订阅主题 topic.subscribe { event -> println("收到事件:$event") } // 发布到主题 topic.publish(Event("发生了某事")) ``` ### 路由器模式 ✅ ```kotlin // 创建一个具有 5 个工作者实例的轮询路由器 val router = system.createRouter( RouterConfig.roundRobin(5), Props.create(WorkerActor::class.java) ) // 向路由器发送消息 for (i in 1..10) { router.tell(Work(i)) } ``` ## 下一步 ✅ 现在您已经了解了 Kastrax 中的 Actor 模型,您可以: 1. 了解[远程 actor 配置](./remote-configuration.mdx) 2. 探索[actor 监督策略](./supervision.mdx) 3. 实现[基于 actor 的代理系统](./agent-actors.mdx) --- title: "代理工具选择 | 代理文档 | Kastrax" description: 工具是可以由代理或工作流执行的类型化函数,具有内置的集成访问和参数验证功能。每个工具都有一个定义其输入的模式,一个实现其逻辑的执行函数,以及对配置的集成的访问权限。 --- # 代理工具选择 ✅ [ZH] Source: https://kastrax.ai/zh/docs/agents/adding-tools 工具是可以由代理或工作流执行的类型化函数,具有内置的集成访问和参数验证功能。每个工具都有一个定义其输入的模式,一个实现其逻辑的执行函数,以及对配置的集成的访问权限。 ## 创建工具 ✅ 在本节中,我们将介绍创建可供代理使用的工具的过程。让我们创建一个简单的工具,用于获取指定城市的当前天气信息。 ```kotlin import ai.kastrax.core.tools.tool import kotlinx.coroutines.runBlocking import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import kotlinx.serialization.json.* // 创建天气信息工具 val weatherInfoTool = tool("weatherInfo") { description("获取指定城市的当前天气信息") parameters { parameter("city", "城市名称", String::class) } execute { params -> val city = params["city"] as String // 替换为实际的天气服务 API 调用 val client = HttpClient() val response = client.get("https://api.example.com/weather?city=$city") val responseBody = response.bodyAsText() // 解析 JSON 响应 val weatherData = Json.parseToJsonElement(responseBody).jsonObject val temperature = weatherData["temperature"]?.jsonPrimitive?.double ?: 0.0 val conditions = weatherData["conditions"]?.jsonPrimitive?.content ?: "未知" "$city 的当前天气:温度 $temperature°C,天气状况 $conditions" } } ``` ## 向代理添加工具 ✅ 现在我们将工具添加到代理中。我们将创建一个可以回答有关天气的问题的代理,并配置它使用我们的 `weatherInfoTool` 工具。 ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking // 创建天气代理 val weatherAgent = agent { name("天气代理") description("一个提供当前天气信息的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 添加天气信息工具 tools { tool(weatherInfoTool) } } ``` ## 注册代理 ✅ 我们需要将代理注册到 Kastrax 中。 ```kotlin import ai.kastrax.core.Kastrax // 创建 Kastrax 实例并注册代理 val kastrax = Kastrax { agents { register("weatherAgent", weatherAgent) } } ``` 这样就将代理注册到 Kastrax 中,使其可供使用。 ## 中止信号 ✅ 从 `generate` 和 `stream`(文本生成)传递的中止信号会转发到工具执行。您可以在执行函数的第二个参数中访问它们,例如中止长时间运行的计算或将它们转发到工具内部的 fetch 调用。 ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import io.ktor.client.* import io.ktor.client.request.* val weatherAgent = agent { name("天气代理") description("一个提供天气信息的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 添加天气工具 tools { tool("weather") { description("获取指定位置的天气信息") parameters { parameter("location", "位置名称", String::class) } executeWithAbortSignal { params, abortSignal -> val location = params["location"] as String val client = HttpClient() val response = client.get("https://api.weatherapi.com/v1/current.json?q=$location") { // 将中止信号转发到 HTTP 请求 if (abortSignal != null) { this.abortSignal = abortSignal } } // 处理响应... "位置 $location 的天气信息" } } } } // 使用代理并传递中止信号 val result = weatherAgent.generate("旧金山的天气怎么样?", abortSignal = myAbortSignal) ``` ## 注入请求/用户特定变量 ✅ 我们支持工具和工作流的依赖注入。您可以直接将运行时上下文传递给 `generate` 或 `stream` 函数调用,或使用服务器中间件注入它。 以下是一个在华氏度和摄氏度之间切换温度刻度的示例: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.core.context.RuntimeContext import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import kotlinx.serialization.json.* // 创建天气代理 val weatherAgent = agent { name("天气代理") description("一个提供天气信息的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 添加天气工具 tools { tool("weather") { description("获取指定位置的天气信息") parameters { parameter("location", "位置名称", String::class) } executeWithContext { params, _, _, runtimeContext -> val location = params["location"] as String val scale = runtimeContext?.get("temperature-scale") ?: "celsius" val client = HttpClient() val response = client.get("https://api.weatherapi.com/v1/current.json?q=$location") val responseBody = response.bodyAsText() // 解析 JSON 响应 val json = Json.parseToJsonElement(responseBody).jsonObject val tempC = json["current"]?.jsonObject?.get("temp_c")?.jsonPrimitive?.double ?: 0.0 val tempF = json["current"]?.jsonObject?.get("temp_f")?.jsonPrimitive?.double ?: 32.0 val temperature = if (scale == "celsius") tempC else tempF val unit = if (scale == "celsius") "°C" else "°F" "位置 $location 的温度是 $temperature$unit" } } } } // 创建运行时上下文 val runtimeContext = RuntimeContext() runtimeContext.set("temperature-scale", "celsius") // 使用代理并传递运行时上下文 val result = weatherAgent.generate("旧金山的天气怎么样?", runtimeContext = runtimeContext) ``` ## 调试工具 ✅ 您可以使用 JUnit 或任何其他测试框架测试工具。为工具编写单元测试可确保它们按预期行为并有助于及早发现错误。 ## 使用工具调用代理 ✅ 现在我们可以调用代理,它将使用工具获取天气信息。 ## 示例:与代理交互 ✅ ```kotlin import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 获取注册的代理 val agent = kastrax.getAgent("weatherAgent") // 生成响应 val response = agent.generate("今天纽约市的天气怎么样?") // 输出响应文本 println(response.text) } ``` 代理将使用 `weatherInfoTool` 工具获取纽约市的当前天气,并相应地回应。 ## 工具设计最佳实践 ✅ 在为代理创建工具时,遵循以下指南将有助于确保可靠和直观的工具使用: ### 工具描述 工具的主要描述应该关注其目的和价值: - 保持描述简单,重点关注工具**做什么** - 强调工具的主要用例 - 避免在主要描述中包含实现细节 - 帮助代理理解**何时**使用工具 ```kotlin tool("documentSearch") { description("访问知识库以查找回答用户问题所需的信息") // ... 工具配置的其余部分 } ``` ### 参数模式 技术细节属于参数模式,它们帮助代理正确使用工具: - 使用清晰的描述使参数自我说明 - 包括默认值及其含义 - 在有帮助的地方提供示例 - 描述不同参数选择的影响 ```kotlin parameters { parameter("query", "查找相关信息的搜索查询", String::class) parameter("limit", "返回结果的数量。较高的值提供更多上下文,较低的值专注于最佳匹配", Int::class, optional = true, defaultValue = 5) parameter("options", "可选配置。示例:'{\"filter\": \"category=news\"}'", String::class, optional = true) } ``` ### 代理交互模式 在以下情况下,工具更有可能被有效使用: - 查询或任务足够复杂,明确需要工具辅助 - 代理指令提供了关于工具使用的清晰指导 - 参数要求在模式中有良好的文档 - 工具的目的与查询的需求一致 ### 常见陷阱 - 在主要描述中过载技术细节 - 混合实现细节和使用指导 - 不清晰的参数描述或缺少示例 遵循这些实践有助于确保您的工具可被代理发现和使用,同时保持目的(主要描述)和实现细节(参数模式)之间的清晰分离。 ## 总结 ✅ 通过本指南,您已经学习了如何创建工具、将工具添加到代理中、注册代理以及使用工具调用代理。您还了解了工具设计的最佳实践,这将帮助您创建更有效的工具。 工具是扩展代理能力的强大方式,使其能够执行各种任务,从简单的计算到复杂的 API 调用。通过遵循本指南中的最佳实践,您可以创建直观、可靠的工具,使您的代理更加强大和有用。 # 为 Kastrax AI Agent 添加语音功能 ✅ [ZH] Source: https://kastrax.ai/zh/docs/agents/adding-voice Kastrax AI Agent 可以通过语音功能进行增强,使其能够说出响应并听取用户输入。您可以配置代理使用单一语音提供商,或者组合多个提供商用于不同的操作。 ## Kastrax 语音架构 ✅ Kastrax 实现了一个灵活的语音系统,支持多种语音提供商和交互模式: 1. **语音输入(STT)**:将用户的语音转换为文本 2. **语音输出(TTS)**:将代理的文本响应转换为语音 3. **实时语音交互**:支持连续的对话式语音交互 这种架构允许代理在各种场景中使用语音,从简单的命令响应到复杂的对话系统。 ## 使用单一提供商 ✅ 为代理添加语音功能的最简单方法是使用单一提供商同时处理语音输入和输出: ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.AgentConfig import ai.kastrax.core.llm.DeepSeekProvider import ai.kastrax.voice.DeepSeekVoice import java.io.File import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 使用默认设置初始化语音提供商 val voice = DeepSeekVoice( apiKey = "your-deepseek-api-key", defaultVoice = "female-1" // 默认语音 ) // 创建具有语音功能的代理 val agent = Agent( config = AgentConfig( name = "语音助手", description = "一个具有语音交互能力的助手", instructions = "你是一个有语音输入和输出能力的助手。", llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key"), voice = voice ) ) // 代理现在可以使用语音进行交互 val audioStream = agent.voice.speak("你好,我是你的AI助手!", options = SpeakOptions(format = AudioFormat.MP3) ) // 保存音频到文件 val outputFile = File("assistant_greeting.mp3") audioStream.use { input -> outputFile.outputStream().use { output -> input.copyTo(output) } } // 从文件读取音频并转录 try { val inputFile = File("user_input.mp3") val transcription = agent.voice.listen( inputFile.inputStream(), options = ListenOptions(format = AudioFormat.MP3) ) println("转录结果: $transcription") } catch (e: Exception) { println("音频转录错误: ${e.message}") } } ``` ## 使用多个提供商 ✅ 为了获得更大的灵活性,您可以使用 `CompositeVoice` 类为语音输入和输出使用不同的提供商: ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.AgentConfig import ai.kastrax.core.llm.DeepSeekProvider import ai.kastrax.voice.CompositeVoice import ai.kastrax.voice.DeepSeekVoice import ai.kastrax.voice.ElevenLabsVoice // 创建具有多提供商语音功能的代理 val agent = Agent( config = AgentConfig( name = "多语音提供商助手", description = "一个使用多种语音服务的助手", instructions = "你是一个有语音输入和输出能力的助手。", llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key"), // 创建组合语音服务,使用DeepSeek进行语音识别,使用ElevenLabs进行语音合成 voice = CompositeVoice( input = DeepSeekVoice(apiKey = "your-deepseek-api-key"), output = ElevenLabsVoice(apiKey = "your-elevenlabs-api-key") ) ) ) ``` ## 处理音频流 ✅ `speak()` 和 `listen()` 方法使用 Kotlin 的 `InputStream` 和 `OutputStream` 处理音频数据。以下是如何保存和加载音频文件: ### 保存语音输出 `speak` 方法返回一个输入流,您可以将其保存到文件或发送到扬声器: ```kotlin import java.io.File import kotlinx.coroutines.runBlocking fun saveAgentSpeech() = runBlocking { // 生成语音并保存到文件 val audioStream = agent.voice.speak("你好,世界!") val outputFile = File("agent_speech.mp3") audioStream.use { input -> outputFile.outputStream().use { output -> input.copyTo(output) } } println("语音已保存到: ${outputFile.absolutePath}") } ``` ### 转录音频输入 `listen` 方法接收来自麦克风或文件的音频数据流: ```kotlin import java.io.File import kotlinx.coroutines.runBlocking fun transcribeAudioFile() = runBlocking { // 读取音频文件并转录 val audioFile = File("user_input.mp3") try { println("正在转录音频文件...") val transcription = agent.voice.listen( audioFile.inputStream(), options = ListenOptions(format = AudioFormat.MP3) ) println("转录结果: $transcription") } catch (e: Exception) { println("音频转录错误: ${e.message}") } } ``` ## 语音到语音的交互 ✅ 对于更动态和交互式的语音体验,您可以使用支持实时语音到语音功能的提供商: ```kotlin import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.AgentConfig import ai.kastrax.core.llm.DeepSeekProvider import ai.kastrax.voice.realtime.RealtimeVoice import ai.kastrax.voice.realtime.RealtimeVoiceConfig import ai.kastrax.tools.SearchTool import ai.kastrax.tools.CalculatorTool import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 初始化实时语音提供商 val realtimeVoice = RealtimeVoice( config = RealtimeVoiceConfig( apiKey = "your-api-key", model = "deepseek-chat", voice = "female-1" ) ) // 创建具有语音到语音功能的代理 val agent = Agent( config = AgentConfig( name = "实时语音助手", description = "一个支持实时语音交互的助手", instructions = "你是一个支持语音到语音交互的助手。", llmProvider = DeepSeekProvider(apiKey = "your-deepseek-api-key"), tools = listOf( SearchTool(), CalculatorTool() ), voice = realtimeVoice ) ) // 建立WebSocket连接 agent.voice.connect() // 开始对话 agent.voice.speak("你好,我是你的AI助手!") // 从麦克风获取音频流 val microphoneStream = getMicrophoneStream() agent.voice.send(microphoneStream) // 当对话结束时 // agent.voice.close() } ``` ### 事件系统 实时语音提供商会发出几个您可以监听的事件: ```kotlin // 监听从语音提供商发送的语音音频数据 agent.voice.on(VoiceEvent.SPEAKING) { event -> val audio = event.audio // audio 包含可读流或 Int16Array 音频数据 } // 监听从语音提供商和用户发送的转录文本 agent.voice.on(VoiceEvent.WRITING) { event -> println("${event.role} 说: ${event.text}") } // 监听错误 agent.voice.on(VoiceEvent.ERROR) { event -> println("语音错误: ${event.error}") } ``` ## 支持的语音提供商 ✅ Kastrax 支持多种语音提供商,用于文本到语音 (TTS) 和语音到文本 (STT) 功能: | 提供商 | 包名 | 功能 | 参考文档 | |----------|---------|----------|-----------| | DeepSeek | `ai.kastrax.voice.DeepSeekVoice` | TTS, STT | [文档](/reference/voice/deepseek) | | DeepSeek实时 | `ai.kastrax.voice.realtime.DeepSeekRealtimeVoice` | 实时语音到语音 | [文档](/reference/voice/deepseek-realtime) | | ElevenLabs | `ai.kastrax.voice.ElevenLabsVoice` | 高质量TTS | [文档](/reference/voice/elevenlabs) | | Google | `ai.kastrax.voice.GoogleVoice` | TTS, STT | [文档](/reference/voice/google) | | Azure | `ai.kastrax.voice.AzureVoice` | TTS, STT | [文档](/reference/voice/azure) | | Whisper | `ai.kastrax.voice.WhisperVoice` | STT | [文档](/reference/voice/whisper) | 有关语音功能的更多详细信息,请参阅[语音API参考文档](/reference/voice/kastrax-voice)。 ## 与Actor模型集成 ✅ Kastrax 的一个独特功能是将语音系统与 Actor 模型集成,实现分布式语音处理: ```kotlin import ai.kastrax.actor.ActorSystem import ai.kastrax.actor.Props import ai.kastrax.voice.VoiceActor import ai.kastrax.voice.DeepSeekVoice import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建Actor系统 val system = ActorSystem("voice-system") // 创建语音Actor val voiceActor = system.actorOf( Props.create(VoiceActor::class.java, DeepSeekVoice(apiKey = "your-api-key")), "voice-actor" ) // 发送语音合成消息 val result = system.ask( voiceActor, SpeakMessage("你好,我是一个语音Actor!") ) // 处理结果 when (result) { is VoiceResult.Success -> { val audioData = result.audio // 处理音频数据... } is VoiceResult.Error -> { println("语音处理错误: ${result.message}") } } } ``` 这种集成使您能够构建复杂的多代理系统,其中语音处理可以分布在不同的节点上并并行执行。 --- title: "代理架构 | 代理文档 | Kastrax" description: "Kastrax 中不同代理架构的概述,包括自适应、目标导向、层次化、反思性和创造性代理。" --- # 代理架构 ✅ [ZH] Source: https://kastrax.ai/zh/docs/agents/architectures-kotlin Kastrax 提供了几种专门的代理架构,每种架构都为不同的用例和行为而设计。本指南解释了可用的架构以及如何实现它们。 ## 架构概述 ✅ Kastrax 支持以下代理架构: 1. **自适应代理**:根据用户偏好和反馈调整行为 2. **目标导向代理**:通过规划和任务执行专注于实现特定目标 3. **层次化代理**:将复杂任务组织成可管理的子任务,具有责任层次结构 4. **反思性代理**:通过反思进行自我监控和改进性能 5. **创造性代理**:具有增强能力,生成创造性内容 每种架构都通过专门的行为和能力扩展了基本代理。 ## 自适应代理 ✅ 自适应代理根据用户偏好和反馈调整其行为。它们非常适合随时间改进的个性化体验。 ### 主要特点 ✅ - 用户偏好跟踪 - 行为适应 - 个性化响应 - 从反馈中学习 ### 实现 ✅ ```kotlin import ai.kastrax.core.agent.architecture.adaptiveAgent import ai.kastrax.core.agent.architecture.UserPreference import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createAdaptiveAgent() = adaptiveAgent { name("自适应助手") description("一个适应用户偏好的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 定义用户偏好 preferences { preference( UserPreference( name = "ResponseLength", description = "响应应该有多详细", options = listOf("简洁", "详细", "全面"), defaultValue = "详细" ) ) preference( UserPreference( name = "Tone", description = "响应的语气", options = listOf("专业", "友好", "技术性"), defaultValue = "友好" ) ) } // 定义适应策略 adaptationStrategy { // 分析用户消息以推断偏好 analyzeUserMessage { message -> if (message.contains("简短") || message.contains("简洁")) { updatePreference("ResponseLength", "简洁") } else if (message.contains("详细") || message.contains("解释")) { updatePreference("ResponseLength", "详细") } if (message.contains("技术") || message.contains("高级")) { updatePreference("Tone", "技术性") } else if (message.contains("简单") || message.contains("容易")) { updatePreference("Tone", "友好") } } } } fun main() = runBlocking { val agent = createAdaptiveAgent() // 测试代理 println(agent.generate("你能简短地解释一下量子计算吗?").text) println(agent.generate("现在我想要一个关于量子纠缠的详细技术解释。").text) } ``` ## 目标导向代理 ✅ 目标导向代理通过规划和任务执行专注于实现特定目标。它们非常适合复杂的问题解决场景。 ### 主要特点 ✅ - 目标定义和跟踪 - 任务规划和优先级排序 - 进度监控 - 结果评估 ### 实现 ✅ ```kotlin import ai.kastrax.core.agent.architecture.goalOrientedAgent import ai.kastrax.core.agent.architecture.GoalPriority import ai.kastrax.core.agent.architecture.TaskPriority import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import kotlinx.datetime.Clock import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.plus fun createGoalOrientedAgent() = goalOrientedAgent { name("项目经理") description("一个帮助管理项目的目标导向代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 定义目标 goals { goal( id = "complete-project", description = "完成网站重新设计项目", priority = GoalPriority.HIGH, deadline = Clock.System.now().plus(30, DateTimeUnit.DAY) ) } // 为目标定义任务 tasks("complete-project") { task( id = "gather-requirements", description = "从利益相关者收集项目需求", priority = TaskPriority.HIGH, estimatedDuration = 3 // 天 ) task( id = "create-wireframes", description = "基于需求创建线框图", priority = TaskPriority.MEDIUM, estimatedDuration = 5, // 天 dependencies = listOf("gather-requirements") ) task( id = "develop-frontend", description = "基于线框图开发前端", priority = TaskPriority.MEDIUM, estimatedDuration = 10, // 天 dependencies = listOf("create-wireframes") ) } } fun main() = runBlocking { val agent = createGoalOrientedAgent() // 测试代理 println(agent.generate("网站重新设计项目的当前状态是什么?").text) println(agent.generate("本周我们应该专注于哪些任务?").text) } ``` ## 层次化代理 ✅ 层次化代理将复杂任务分解为可管理的子任务,创建责任层次结构。它们非常适合具有多个步骤的复杂工作流。 ### 主要特点 ✅ - 任务分解 - 层次化规划 - 委派执行 - 结果协调 ### 实现 ✅ ```kotlin import ai.kastrax.core.agent.architecture.hierarchicalAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createHierarchicalAgent() = hierarchicalAgent { name("研究助手") description("一个帮助研究任务的层次化代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 定义协调员代理 coordinator { name("协调员") description("协调研究过程") systemPrompt(""" 你是研究团队的协调员。你的工作是: 1. 理解研究问题 2. 将其分解为子任务 3. 将任务分配给专家 4. 将他们的结果综合成最终答案 在你的方法中要彻底和有条理。 """.trimIndent()) } // 定义专家代理 specialists { specialist("研究员") { description("查找相关信息") systemPrompt(""" 你是研究专家。你的工作是: 1. 搜索指定主题的相关信息 2. 评估来源的可信度 3. 提取关键事实和数据 4. 逻辑地组织信息 要彻底并引用你的来源。 """.trimIndent()) } specialist("分析师") { description("分析信息") systemPrompt(""" 你是分析专家。你的工作是: 1. 分析提供的信息 2. 识别模式和趋势 3. 在不同信息之间建立联系 4. 评估发现的重要性 在你的分析中要批判性和客观。 """.trimIndent()) } specialist("作家") { description("撰写最终报告") systemPrompt(""" 你是写作专家。你的工作是: 1. 将研究发现组织成连贯的叙述 2. 写出清晰、简洁和引人入胜的内容 3. 确保写作适合目标受众 4. 校对错误和清晰度 在你的写作中要清晰、简洁和引人入胜。 """.trimIndent()) } } } fun main() = runBlocking { val agent = createHierarchicalAgent() // 测试代理 println(agent.generate("研究人工智能对医疗保健的影响").text) } ``` ## 反思性代理 ✅ 反思性代理监控自己的表现,并通过自我反思不断改进。它们非常适合从迭代改进中受益的复杂推理任务。 ### 主要特点 ✅ - 自我监控 - 性能评估 - 策略调整 - 持续改进 ### 实现 ✅ ```kotlin import ai.kastrax.core.agent.architecture.reflectiveAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createReflectiveAgent() = reflectiveAgent { name("反思助手") description("一个通过自我评估改进的反思代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 定义反思标准 reflectionCriteria { criterion("准确性", "提供的信息是否准确和事实?") criterion("完整性", "响应是否解决了查询的所有方面?") criterion("清晰度", "响应是否清晰易懂?") criterion("相关性", "响应是否与用户的查询相关?") criterion("有用性", "响应是否真正帮助了用户?") } // 配置反思频率 reflectionFrequency(3) // 每3次交互后反思 // 定义反思策略 reflectionStrategy { // 评估先前的响应 evaluateResponse { response -> // 对每个标准进行1-5分的评分 val ratings = mapOf( "准确性" to evaluateCriterion("准确性", response), "完整性" to evaluateCriterion("完整性", response), "清晰度" to evaluateCriterion("清晰度", response), "相关性" to evaluateCriterion("相关性", response), "有用性" to evaluateCriterion("有用性", response) ) // 确定需要改进的领域 val improvementAreas = ratings.filter { it.value < 4 }.keys // 生成改进策略 val strategies = improvementAreas.map { criterion -> "改进$criterion:${generateImprovementStrategy(criterion)}" } // 根据反思更新代理的方法 updateApproach(strategies) } } } fun main() = runBlocking { val agent = createReflectiveAgent() // 测试代理 println(agent.generate("解释神经网络的工作原理").text) println(agent.generate("比较监督学习和无监督学习").text) println(agent.generate("AI的伦理影响是什么?").text) // 在第三个查询后,代理将反思并改进 } ``` ## 创造性代理 ✅ 创造性代理专门生成故事、诗歌和创意想法。它们具有增强的创意表达能力。 ### 主要特点 ✅ - 创意内容生成 - 风格适应 - 主题探索 - 叙事构建 ### 实现 ✅ ```kotlin import ai.kastrax.core.agent.architecture.creativeAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createCreativeAgent() = creativeAgent { name("创意作家") description("一个生成故事和诗歌的创意代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.9) // 更高的温度以增加创造性 } // 定义创意能力 creativeCapabilities { capability("故事生成", "生成引人入胜的短篇故事") capability("诗歌写作", "以各种风格写诗") capability("创意生成", "为各种目的生成创意想法") capability("角色创建", "创建详细有趣的角色") } // 定义创意风格 creativeStyles { style("奇幻", "魔法和超自然元素") style("科幻", "未来和技术主题") style("悬疑", "悬疑和神秘的语调") style("喜剧", "幽默轻松的方式") style("戏剧", "情感和以角色为中心的叙述") } // 配置创意参数 creativityParameters { parameter("原创性", 0.8f) // 偏好原创想法 parameter("连贯性", 0.7f) // 连贯性和创造性之间的平衡 parameter("细节", 0.6f) // 描述性细节的水平 parameter("情感影响", 0.9f) // 强调情感共鸣 } } fun main() = runBlocking { val agent = createCreativeAgent() // 测试代理 println(agent.generate("写一个关于机器人发现情感的短篇故事").text) println(agent.generate("创作一首关于季节变化的俳句").text) } ``` ## 选择正确的架构 ✅ 在选择代理架构时,考虑以下因素: | 架构 | 最适合 | 主要优势 | |--------------|----------|----------------| | 自适应 | 个性化体验 | 适应用户偏好 | | 目标导向 | 项目管理、规划 | 专注于实现目标 | | 层次化 | 复杂多步骤任务 | 将问题分解为可管理的部分 | | 反思性 | 批判性思维、持续改进 | 自我评估和改进 | | 创造性 | 内容生成、头脑风暴 | 生成创意和原创内容 | 您也可以通过在自定义代理中实现它们的关键特性来组合架构。 ## 下一步 ✅ 现在您已经了解了不同的代理架构,您可以: 1. 探索[记忆系统](../memory/overview.mdx) 2. 了解[工具集成](../tools/overview.mdx) 3. 实现[代理工作流](../workflows/overview.mdx) # 代理架构 [ZH] Source: https://kastrax.ai/zh/docs/agents/architectures Kastrax 提供了几种专门的代理架构,每种架构都针对不同的用例和行为而设计。本指南解释了可用的架构以及如何实现它们。 ## 代理架构概述 Kastrax 支持以下代理架构: 1. **自适应代理**:根据用户偏好和反馈调整行为 2. **目标导向代理**:专注于通过任务规划实现特定目标 3. **层次化代理**:将复杂任务组织成可管理的子任务 4. **反思型代理**:通过反思自我监控和提高性能 5. **创造性代理**:生成具有增强能力的创意内容 每种架构都扩展了基本代理,具有专门的行为和能力。 ## 自适应代理 自适应代理根据用户偏好和反馈调整其行为。它们非常适合随着时间推移而改进的个性化体验。 ### 主要特点 - 用户偏好跟踪 - 行为适应 - 个性化响应 - 从反馈中学习 ### 实现 ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.architecture.adaptiveAgent import ai.kastrax.core.agent.architecture.UserPreference import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createAdaptiveAgent() = adaptiveAgent { name("AdaptiveAssistant") description("一个适应用户偏好的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 定义用户偏好 preferences { preference( UserPreference( name = "ResponseLength", description = "响应应该有多详细", options = listOf("简洁", "详细", "全面"), defaultValue = "详细" ) ) preference( UserPreference( name = "Tone", description = "响应的语气", options = listOf("专业", "友好", "技术性"), defaultValue = "友好" ) ) } // 定义适应策略 adaptationStrategy { // 分析用户消息以推断偏好 analyzeUserMessage { message -> if (message.contains("简短") || message.contains("简洁")) { updatePreference("ResponseLength", "简洁") } else if (message.contains("详细") || message.contains("解释")) { updatePreference("ResponseLength", "详细") } if (message.contains("技术") || message.contains("高级")) { updatePreference("Tone", "技术性") } else if (message.contains("简单") || message.contains("容易")) { updatePreference("Tone", "友好") } } } } fun main() = runBlocking { val agent = createAdaptiveAgent() // 测试代理 println(agent.generate("能给我一个关于量子计算的简短解释吗?").text) println(agent.generate("现在我想要一个关于量子纠缠的详细技术解释。").text) } ``` ## 目标导向代理 目标导向代理专注于通过规划和任务执行实现特定目标。它们非常适合复杂的问题解决场景。 ### 主要特点 - 目标定义和跟踪 - 任务规划和优先级排序 - 进度监控 - 结果评估 ### 实现 ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.architecture.goalOrientedAgent import ai.kastrax.core.agent.architecture.GoalPriority import ai.kastrax.core.agent.architecture.TaskPriority import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import kotlinx.datetime.Clock import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.plus fun createGoalOrientedAgent() = goalOrientedAgent { name("ProjectManager") description("一个帮助管理项目的目标导向代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 定义目标 goals { goal( id = "complete-project", description = "完成网站重新设计项目", priority = GoalPriority.HIGH, deadline = Clock.System.now().plus(30, DateTimeUnit.DAY) ) } // 为目标定义任务 tasks("complete-project") { task( id = "gather-requirements", description = "从利益相关者收集项目需求", priority = TaskPriority.HIGH, estimatedDuration = 3 // 天 ) task( id = "create-wireframes", description = "基于需求创建线框图", priority = TaskPriority.MEDIUM, estimatedDuration = 5, // 天 dependencies = listOf("gather-requirements") ) task( id = "develop-frontend", description = "基于线框图开发前端", priority = TaskPriority.MEDIUM, estimatedDuration = 10, // 天 dependencies = listOf("create-wireframes") ) } } fun main() = runBlocking { val agent = createGoalOrientedAgent() // 测试代理 println(agent.generate("网站重新设计项目的当前状态是什么?").text) println(agent.generate("本周我们应该专注于哪些任务?").text) } ``` ## 层次化代理 层次化代理将复杂任务分解为可管理的子任务,创建责任层次结构。它们非常适合具有多个步骤的复杂工作流。 ### 主要特点 - 任务分解 - 层次化规划 - 委派执行 - 协调结果 ### 实现 ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.architecture.hierarchicalAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createHierarchicalAgent() = hierarchicalAgent { name("ResearchAssistant") description("一个帮助研究任务的层次化代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 定义协调者代理 coordinator { name("Coordinator") description("协调研究过程") systemPrompt(""" 你是研究团队的协调者。你的工作是: 1. 理解研究问题 2. 将其分解为子任务 3. 将任务分配给专家 4. 将他们的结果综合成最终答案 在你的方法中要彻底和有条理。 """.trimIndent()) } // 定义专家代理 specialists { specialist("Researcher") { description("查找相关信息") systemPrompt(""" 你是一名研究专家。你的工作是: 1. 搜索有关指定主题的相关信息 2. 评估来源的可信度 3. 提取关键事实和数据 4. 逻辑地组织信息 要彻底并引用你的来源。 """.trimIndent()) } specialist("Analyst") { description("分析信息") systemPrompt(""" 你是一名分析专家。你的工作是: 1. 分析提供的信息 2. 识别模式和趋势 3. 在不同信息之间建立联系 4. 评估发现的重要性 在你的分析中要批判性和客观。 """.trimIndent()) } specialist("Writer") { description("撰写最终报告") systemPrompt(""" 你是一名写作专家。你的工作是: 1. 将研究发现组织成连贯的叙述 2. 写出清晰、简洁和引人入胜的内容 3. 确保写作适合目标受众 4. 校对错误和清晰度 在你的写作中要清晰、简洁和引人入胜。 """.trimIndent()) } } } fun main() = runBlocking { val agent = createHierarchicalAgent() // 测试代理 println(agent.generate("研究人工智能对医疗保健的影响").text) } ``` ## 反思型代理 反思型代理通过自我反思监控自己的表现并不断改进。它们非常适合受益于迭代改进的复杂推理任务。 ### 主要特点 - 自我监控 - 性能评估 - 策略调整 - 持续改进 ### 实现 ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.architecture.reflectiveAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createReflectiveAgent() = reflectiveAgent { name("ReflectiveAssistant") description("一个通过自我评估改进的反思型代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 定义反思标准 reflectionCriteria { criterion("准确性", "提供的信息是否准确和事实?") criterion("完整性", "响应是否解决了查询的所有方面?") criterion("清晰度", "响应是否清晰易懂?") criterion("相关性", "响应是否与用户的查询相关?") criterion("有用性", "响应是否真正帮助了用户?") } // 配置反思频率 reflectionFrequency(3) // 每3次交互后反思 // 定义反思策略 reflectionStrategy { // 评估上一个响应 evaluateResponse { response -> // 对每个标准评分1-5 val ratings = mapOf( "准确性" to evaluateCriterion("准确性", response), "完整性" to evaluateCriterion("完整性", response), "清晰度" to evaluateCriterion("清晰度", response), "相关性" to evaluateCriterion("相关性", response), "有用性" to evaluateCriterion("有用性", response) ) // 识别需要改进的领域 val improvementAreas = ratings.filter { it.value < 4 }.keys // 生成改进策略 val strategies = improvementAreas.map { criterion -> "改进$criterion:${generateImprovementStrategy(criterion)}" } // 根据反思更新代理的方法 updateApproach(strategies) } } } fun main() = runBlocking { val agent = createReflectiveAgent() // 测试代理 println(agent.generate("解释神经网络如何工作").text) println(agent.generate("比较监督学习和无监督学习").text) println(agent.generate("AI的伦理影响是什么?").text) // 在第三个查询后,代理将反思并改进 } ``` ## 创造性代理 创造性代理专门用于生成故事、诗歌和创意等创意内容。它们具有增强的创意表达能力。 ### 主要特点 - 创意内容生成 - 风格适应 - 主题探索 - 叙事构建 ### 实现 ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.architecture.creativeAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createCreativeAgent() = creativeAgent { name("CreativeWriter") description("一个生成故事和诗歌的创造性代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.9) // 更高的温度以增加创造性 } // 定义创造性能力 creativeCapabilities { capability("StoryGeneration", "生成引人入胜的短篇故事") capability("PoetryWriting", "以各种风格写诗") capability("IdeaGeneration", "为各种目的生成创意想法") capability("CharacterCreation", "创建详细有趣的角色") } // 定义创造性风格 creativeStyles { style("Fantasy", "魔幻和超自然元素") style("SciFi", "未来主义和技术主题") style("Mystery", "悬疑和神秘的基调") style("Comedy", "幽默和轻松的方式") style("Drama", "情感和以角色为驱动的叙事") } // 配置创造性参数 creativityParameters { parameter("Originality", 0.8f) // 偏好原创想法 parameter("Coherence", 0.7f) // 连贯性和创造性之间的平衡 parameter("Detail", 0.6f) // 描述性细节的水平 parameter("EmotionalImpact", 0.9f) // 强调情感共鸣 } } fun main() = runBlocking { val agent = createCreativeAgent() // 测试代理 println(agent.generate("写一个关于机器人发现情感的短篇故事").text) println(agent.generate("以俳句风格创作一首关于季节变化的诗").text) } ``` ## 选择正确的架构 在选择代理架构时,请考虑以下因素: | 架构 | 最适合 | 主要优势 | |--------------|----------|--------------| | 自适应 | 个性化体验 | 适应用户偏好 | | 目标导向 | 项目管理、规划 | 专注于实现目标 | | 层次化 | 复杂的多步骤任务 | 将问题分解为可管理的部分 | | 反思型 | 批判性思维、持续改进 | 自我评估和改进 | | 创造性 | 内容生成、头脑风暴 | 生成创意和原创内容 | 您也可以通过在自定义代理中实现它们的关键特性来组合架构。 ## 下一步 现在您了解了不同的代理架构,您可以: 1. 探索[代理内存系统](../memory/overview.mdx) 2. 了解[工具集成](../tools/overview.mdx) 3. 实现[代理工作流](../workflows/overview.mdx) --- title: 代理监控和诊断 | Kastrax 文档 description: Kastrax 中代理性能监控和诊断的详细指南,包括指标收集、性能分析和调试工具。 --- # 代理监控和诊断 ✅ [ZH] Source: https://kastrax.ai/zh/docs/agents/monitoring-kotlin Kastrax 为 AI 代理提供了全面的监控和诊断功能,使您能够跟踪性能、识别问题并优化行为。本指南解释了如何有效地使用这些功能。 ## 监控概述 ✅ Kastrax 中的代理监控使您能够: - 跟踪代理性能指标 - 监控资源使用情况 - 分析代理行为模式 - 识别和诊断问题 - 生成报告和可视化 - 为异常设置警报 ## 基本监控设置 ✅ 以下是如何为 Kastrax 代理设置基本监控: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.MonitoringConfig import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建具有默认配置的监控器 val monitor = AgentMonitor.create( MonitoringConfig( enabled = true, collectPerformanceMetrics = true, collectUsageMetrics = true, collectBehaviorMetrics = true, samplingRate = 1.0 // 监控所有交互 ) ) // 创建带有监控的代理 val myAgent = agent { name("被监控的代理") description("一个具有监控功能的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 启用监控 monitor = monitor } // 使用代理 repeat(5) { i -> val response = myAgent.generate("告诉我关于 ${i + 1} 的一些有趣事情") println("响应 ${i + 1}: ${response.text}") } // 获取监控数据 val metrics = monitor.getMetrics(myAgent.name) println("收集的指标: $metrics") } ``` ## 性能指标 ✅ Kastrax 为代理收集各种性能指标: ### 响应时间指标 ```kotlin // 获取响应时间指标 val responseTimeMetrics = monitor.getResponseTimeMetrics(myAgent.name) println("平均响应时间: ${responseTimeMetrics.average} 毫秒") println("中位数响应时间: ${responseTimeMetrics.median} 毫秒") println("95 百分位响应时间: ${responseTimeMetrics.percentile95} 毫秒") println("最大响应时间: ${responseTimeMetrics.max} 毫秒") ``` ### 令牌使用指标 ```kotlin // 获取令牌使用指标 val tokenUsageMetrics = monitor.getTokenUsageMetrics(myAgent.name) println("总输入令牌: ${tokenUsageMetrics.totalInputTokens}") println("总输出令牌: ${tokenUsageMetrics.totalOutputTokens}") println("每个请求的平均令牌数: ${tokenUsageMetrics.averageTokensPerRequest}") println("令牌使用趋势: ${tokenUsageMetrics.usageTrend}") ``` ### 错误率指标 ```kotlin // 获取错误率指标 val errorMetrics = monitor.getErrorMetrics(myAgent.name) println("错误率: ${errorMetrics.errorRate * 100}%") println("常见错误类型: ${errorMetrics.commonErrorTypes}") println("错误趋势: ${errorMetrics.errorTrend}") ``` ## 资源使用监控 ✅ 您可以监控代理的资源使用情况: ```kotlin // 获取资源使用指标 val resourceMetrics = monitor.getResourceMetrics(myAgent.name) println("内存使用: ${resourceMetrics.memoryUsage} MB") println("CPU 使用: ${resourceMetrics.cpuUsage}%") println("网络使用: ${resourceMetrics.networkUsage} KB") ``` ## 行为分析 ✅ Kastrax 可以分析代理行为模式: ```kotlin // 获取行为指标 val behaviorMetrics = monitor.getBehaviorMetrics(myAgent.name) println("工具使用分布: ${behaviorMetrics.toolUsageDistribution}") println("响应长度分布: ${behaviorMetrics.responseLengthDistribution}") println("常见主题: ${behaviorMetrics.commonTopics}") println("情感分布: ${behaviorMetrics.sentimentDistribution}") ``` ## 自定义指标 ✅ 您可以为代理定义和收集自定义指标: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.CustomMetric import ai.kastrax.core.agent.monitoring.MetricType import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建带有自定义指标的监控器 val monitor = AgentMonitor.create() // 定义自定义指标 monitor.defineCustomMetric( CustomMetric( name = "user_satisfaction", description = "用户满意度评分", type = MetricType.GAUGE, unit = "score" ) ) monitor.defineCustomMetric( CustomMetric( name = "task_completion_rate", description = "成功完成任务的比率", type = MetricType.RATE, unit = "percent" ) ) // 创建带有监控的代理 val myAgent = agent { name("自定义指标代理") description("一个带有自定义指标的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 启用监控 monitor = monitor } // 使用代理并记录自定义指标 val response = myAgent.generate("帮我解决这个数学问题: 2x + 5 = 15") println("响应: ${response.text}") // 记录自定义指标 monitor.recordCustomMetric( agentName = myAgent.name, metricName = "user_satisfaction", value = 4.5 // 1-5 的评分 ) monitor.recordCustomMetric( agentName = myAgent.name, metricName = "task_completion_rate", value = 1.0 // 100% 完成 ) // 获取自定义指标 val customMetrics = monitor.getCustomMetrics(myAgent.name) println("自定义指标: $customMetrics") } ``` ## 实时监控 ✅ 您可以使用回调设置实时监控: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.MonitoringCallback import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建带有实时回调的监控器 val monitor = AgentMonitor.create() // 定义监控回调 monitor.addCallback(object : MonitoringCallback { override fun onRequestStart(agentName: String, requestId: String) { println("[$agentName] 请求开始: $requestId") } override fun onRequestComplete(agentName: String, requestId: String, durationMs: Long) { println("[$agentName] 请求完成: $requestId (${durationMs}毫秒)") } override fun onError(agentName: String, requestId: String, error: Throwable) { println("[$agentName] 请求 $requestId 中的错误: ${error.message}") } override fun onMetricUpdate(agentName: String, metricName: String, value: Double) { println("[$agentName] 指标更新: $metricName = $value") } }) // 创建带有监控的代理 val myAgent = agent { name("实时监控代理") description("一个带有实时监控的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 启用监控 monitor = monitor } // 使用代理 val response = myAgent.generate("讲个笑话") println("响应: ${response.text}") } ``` ## 监控仪表板 ✅ Kastrax 提供了一个监控仪表板,用于可视化代理指标: ```kotlin import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.dashboard.MonitoringDashboard import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 获取监控器 val monitor = AgentMonitor.getInstance() // 创建监控仪表板 val dashboard = MonitoringDashboard.create(monitor) // 在特定端口上启动仪表板 dashboard.start(port = 8080) println("监控仪表板已在 http://localhost:8080 启动") // 保持应用程序运行 readLine() // 完成后停止仪表板 dashboard.stop() } ``` ## 警报 ✅ 您可以为特定条件设置警报: ```kotlin import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.alert.AlertCondition import ai.kastrax.core.agent.monitoring.alert.AlertSeverity import ai.kastrax.core.agent.monitoring.alert.AlertChannel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 获取监控器 val monitor = AgentMonitor.getInstance() // 配置电子邮件警报通道 val emailChannel = AlertChannel.email( recipients = listOf("admin@example.com"), smtpConfig = mapOf( "host" to "smtp.example.com", "port" to "587", "username" to "alerts@example.com", "password" to "password" ) ) // 配置 Slack 警报通道 val slackChannel = AlertChannel.slack( webhookUrl = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX", channel = "#agent-alerts" ) // 添加警报条件 monitor.addAlertCondition( AlertCondition( name = "高错误率", metricName = "error_rate", threshold = 0.05, // 5% 错误率 comparison = AlertCondition.Comparison.GREATER_THAN, duration = 300, // 5 分钟 severity = AlertSeverity.HIGH, channels = listOf(emailChannel, slackChannel) ) ) monitor.addAlertCondition( AlertCondition( name = "响应时间慢", metricName = "response_time_p95", threshold = 5000.0, // 5 秒 comparison = AlertCondition.Comparison.GREATER_THAN, duration = 600, // 10 分钟 severity = AlertSeverity.MEDIUM, channels = listOf(slackChannel) ) ) println("警报条件已配置") } ``` ## 诊断工具 ✅ Kastrax 提供了诊断工具,用于排查代理问题: ### 请求跟踪 ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.diagnostics.RequestTracer import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建带有请求跟踪的监控器 val monitor = AgentMonitor.create() val tracer = RequestTracer.create(monitor) // 创建带有监控的代理 val myAgent = agent { name("诊断代理") description("一个具有诊断功能的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 启用监控 monitor = monitor } // 开始跟踪请求 val traceId = tracer.startTrace() // 使用带有跟踪 ID 的代理 val response = myAgent.generate( "解释量子计算", options = AgentGenerateOptions( metadata = mapOf("traceId" to traceId) ) ) // 获取跟踪 val trace = tracer.getTrace(traceId) // 打印跟踪详情 println("跟踪 ID: $traceId") println("请求持续时间: ${trace.duration} 毫秒") println("步骤:") trace.steps.forEachIndexed { index, step -> println(" 步骤 ${index + 1}: ${step.name} (${step.duration} 毫秒)") println(" 输入: ${step.input}") println(" 输出: ${step.output}") } } ``` ### 性能分析 ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.diagnostics.PerformanceProfiler import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建监控器 val monitor = AgentMonitor.create() val profiler = PerformanceProfiler.create(monitor) // 创建带有监控的代理 val myAgent = agent { name("性能分析代理") description("一个带有性能分析的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 启用监控 monitor = monitor } // 开始分析 profiler.start(myAgent.name) // 使用代理 repeat(10) { i -> val response = myAgent.generate("告诉我关于主题 ${i + 1}") println("响应 ${i + 1}: ${response.text.take(50)}...") } // 停止分析并获取结果 val profile = profiler.stop(myAgent.name) // 打印分析结果 println("性能分析:") println("总持续时间: ${profile.totalDuration} 毫秒") println("平均响应时间: ${profile.averageResponseTime} 毫秒") println("令牌处理率: ${profile.tokenProcessingRate} 令牌/秒") println("热点:") profile.hotspots.forEach { (component, time) -> println(" $component: ${time} 毫秒 (${(time / profile.totalDuration.toDouble() * 100).toInt()}%)") } } ``` ### 日志分析 ```kotlin import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.diagnostics.LogAnalyzer import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建监控器 val monitor = AgentMonitor.getInstance() val logAnalyzer = LogAnalyzer.create(monitor) // 分析特定代理的日志 val analysis = logAnalyzer.analyzeAgentLogs("MyAgent", timeRange = TimeRange.last(hours = 24)) // 打印分析结果 println("日志分析:") println("总请求数: ${analysis.totalRequests}") println("错误率: ${analysis.errorRate * 100}%") println("常见错误模式:") analysis.errorPatterns.forEach { (pattern, count) -> println(" $pattern: $count 次出现") } println("性能异常:") analysis.performanceAnomalies.forEach { anomaly -> println(" ${anomaly.timestamp}: ${anomaly.description} (${anomaly.severity})") } } ``` ## 导出监控数据 ✅ 您可以导出监控数据以进行进一步分析: ```kotlin import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.export.MetricsExporter import kotlinx.coroutines.runBlocking import java.io.File fun main() = runBlocking { // 获取监控器 val monitor = AgentMonitor.getInstance() // 创建导出器 val csvExporter = MetricsExporter.csv() val jsonExporter = MetricsExporter.json() val prometheusExporter = MetricsExporter.prometheus() // 导出特定代理的指标 val agentName = "MyAgent" val timeRange = TimeRange.last(days = 7) // 导出为 CSV val csvData = csvExporter.exportMetrics(monitor, agentName, timeRange) File("agent_metrics.csv").writeText(csvData) // 导出为 JSON val jsonData = jsonExporter.exportMetrics(monitor, agentName, timeRange) File("agent_metrics.json").writeText(jsonData) // 启动 Prometheus 导出器 prometheusExporter.start(port = 9090) println("指标已导出为 CSV 和 JSON 文件") println("Prometheus 指标可在 http://localhost:9090/metrics 获取") // 保持应用程序运行 readLine() // 停止 Prometheus 导出器 prometheusExporter.stop() } ``` ## 与外部监控系统集成 ✅ Kastrax 可以与外部监控系统集成: ```kotlin import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.monitoring.integration.DatadogIntegration import ai.kastrax.core.agent.monitoring.integration.PrometheusIntegration import ai.kastrax.core.agent.monitoring.integration.GrafanaIntegration import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 获取监控器 val monitor = AgentMonitor.getInstance() // 配置 Datadog 集成 val datadogIntegration = DatadogIntegration.create( apiKey = "your-datadog-api-key", applicationKey = "your-datadog-application-key", tags = mapOf("environment" to "production") ) monitor.addIntegration(datadogIntegration) // 配置 Prometheus 集成 val prometheusIntegration = PrometheusIntegration.create( port = 9090, endpoint = "/metrics" ) monitor.addIntegration(prometheusIntegration) // 配置 Grafana 集成 val grafanaIntegration = GrafanaIntegration.create( url = "http://localhost:3000", apiKey = "your-grafana-api-key", dashboardName = "Kastrax 代理" ) monitor.addIntegration(grafanaIntegration) println("外部监控集成已配置") } ``` ## 最佳实践 ✅ 使用代理监控和诊断时,请遵循以下最佳实践: 1. **选择性监控**:只监控必要的内容,以避免性能开销 2. **采样**:对高流量应用使用采样 3. **保留策略**:设置适当的数据保留策略 4. **隐私**:确保监控尊重隐私,不收集敏感信息 5. **警报阈值**:设置适当的警报阈值,以避免警报疲劳 6. **定期审查**:定期审查监控数据,以识别趋势和问题 7. **文档**:记录监控设置和警报响应 8. **测试**:在测试环境中测试监控和警报 ## 结论 ✅ 代理监控和诊断为理解、优化和排查 Kastrax 中的 AI 代理系统提供了强大的功能。通过收集和分析指标,您可以确保代理可靠高效地运行。 ## 下一步 ✅ - 了解[代理版本控制](./versioning-kotlin.mdx) - 探索[性能优化](./optimization-kotlin.mdx) - 理解[分布式代理](../actor/actor-agent-integration-kotlin.mdx) --- title: "创建和使用代理 | 代理文档 | Kastrax" description: Kastrax 中代理的概述,详细说明它们的能力以及它们如何与工具、记忆和外部系统交互。 --- # 创建和使用代理 ✅ [ZH] Source: https://kastrax.ai/zh/docs/agents/overview Kastrax 中的代理是能够自主决定执行任务的操作序列的智能系统。它们可以访问工具、记忆和外部系统,使它们能够执行复杂任务并与各种服务交互。代理可以调用自定义函数,通过集成利用第三方 API,并访问您构建的知识库。 ## 代理能力 ✅ Kastrax 代理提供了几个关键能力: - **多种架构**:从自适应、目标导向、层次化、反思性和创造性代理架构中选择 - **记忆管理**:在对话中存储和检索信息 - **工具集成**:使用内置工具或创建用于特定任务的自定义工具 - **LLM 集成**:与各种 LLM 提供商合作,包括 DeepSeek、OpenAI、Anthropic 和 Google Gemini - **RAG 支持**:整合来自文档和其他数据源的知识 - **Actor 模型**:使用 actor 模型构建分布式代理系统 ## 创建代理 ✅ 要在 Kastrax 中创建基本代理,您可以使用 `agent` DSL 函数: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("我的第一个代理") description("一个能够回答问题的有帮助的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 添加系统提示 systemPrompt(""" 你是一个有帮助、友好的助手。你的目标是提供准确和有用的信息。 在你的回答中要简洁但全面。 """.trimIndent()) } // 使用代理 val response = myAgent.generate("什么是人工智能?") println(response.text) } ``` ## 添加记忆 ✅ 您可以为代理添加记忆,帮助它记住过去的交互: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("带记忆的代理") description("一个记住过去交互的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 添加记忆 memory = memory { workingMemory(true) conversationHistory(10) semanticMemory(true) } } // 使用代理 val response1 = myAgent.generate("我的名字是小明。") println(response1.text) val response2 = myAgent.generate("我的名字是什么?") println(response2.text) } ``` ## 添加工具 ✅ 您可以为代理添加工具,使其能够执行特定操作: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import java.time.LocalDateTime import java.time.format.DateTimeFormatter fun main() = runBlocking { val myAgent = agent { name("带工具的代理") description("一个可以使用工具的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 添加工具 tools { tool("getCurrentTime") { description("获取当前时间") parameters {} execute { val currentTime = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("HH:mm:ss") "当前时间是 ${currentTime.format(formatter)}" } } tool("calculateSum") { description("计算两个数字的和") parameters { parameter("a", "第一个数字", Double::class) parameter("b", "第二个数字", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double "$a 加 $b 的和是 ${a + b}" } } } } // 使用代理 val response1 = myAgent.generate("现在几点了?") println(response1.text) val response2 = myAgent.generate("计算 5.2 和 3.8 的和") println(response2.text) } ``` ## 将 RAG 与代理一起使用 ✅ 您可以将检索增强生成 (RAG) 整合到您的代理中: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.rag.document.DocumentLoader import ai.kastrax.rag.document.TextSplitter import ai.kastrax.rag.embedding.EmbeddingModel import ai.kastrax.rag.vectorstore.VectorStore import ai.kastrax.rag.retrieval.Retriever import ai.kastrax.rag.context.ContextBuilder import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 设置 RAG 组件 val loader = DocumentLoader.fromFile("knowledge_base.pdf") val documents = loader.load() val splitter = TextSplitter.recursive(chunkSize = 1000, chunkOverlap = 200) val chunks = splitter.split(documents) val embeddingModel = EmbeddingModel.create("all-MiniLM-L6-v2") val vectorStore = VectorStore.create("chroma") vectorStore.addDocuments(chunks, embeddingModel) val retriever = Retriever.fromVectorStore( vectorStore = vectorStore, embeddingModel = embeddingModel, topK = 5 ) val contextBuilder = ContextBuilder.create { template(""" 根据以下上下文回答问题: 上下文: {{context}} 问题:{{query}} 回答: """.trimIndent()) } // 创建 RAG 代理 val ragAgent = agent { name("RAG代理") description("一个具有 RAG 能力的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置 RAG rag { retriever(retriever) contextBuilder(contextBuilder) maxTokens(3000) } } // 使用 RAG 代理 val response = ragAgent.generate("Kastrax 的主要特点是什么?") println(response.text) } ``` ## 代理架构 ✅ Kastrax 提供了几种专门的代理架构,每种架构都为不同的用例设计: ### 自适应代理 ✅ 自适应代理根据用户偏好和反馈调整其行为: ```kotlin import ai.kastrax.core.agent.architecture.adaptiveAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val adaptiveAgent = adaptiveAgent { name("自适应助手") description("一个适应用户偏好的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 定义用户偏好 preferences { // 偏好配置 } // 定义适应策略 adaptationStrategy { // 适应逻辑 } } // 使用自适应代理 val response = adaptiveAgent.generate("告诉我关于量子计算的信息") println(response.text) } ``` ### 目标导向代理 ✅ 目标导向代理通过规划和任务执行专注于实现特定目标: ```kotlin import ai.kastrax.core.agent.architecture.goalOrientedAgent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val goalOrientedAgent = goalOrientedAgent { name("项目经理") description("一个帮助管理项目的目标导向代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 定义目标和任务 goals { // 目标配置 } tasks("goal-id") { // 任务配置 } } // 使用目标导向代理 val response = goalOrientedAgent.generate("项目的状态如何?") println(response.text) } ``` 有关代理架构的更详细信息,请参见[代理架构](./architectures-kotlin.mdx)指南。 ## 下一步 ✅ 现在您已经了解了在 Kastrax 中创建和使用代理的基础知识,您可以: 1. 探索不同的[代理架构](./architectures-kotlin.mdx) 2. 了解[记忆系统](../memory/overview.mdx) 3. 创建[自定义工具](../tools/custom-tools.mdx) 4. 构建[代理工作流](../workflows/overview-kotlin.mdx) --- title: 代理版本控制和状态管理 | Kastrax 文档 description: Kastrax 中管理代理版本和状态的详细指南,包括版本创建、激活、回滚和状态持久化。 --- # 代理版本控制和状态管理 ✅ [ZH] Source: https://kastrax.ai/zh/docs/agents/versioning-kotlin Kastrax 提供了强大的代理版本和状态管理功能,使您能够跟踪更改、回滚到以前的版本,并在会话之间维护代理状态。本指南解释了如何有效地使用这些功能。 ## 代理版本控制 ✅ 代理版本控制允许您创建、管理和切换代理指令和配置的不同版本。这对以下情况很有用: - 跟踪代理行为随时间的变化 - 对不同的代理配置进行 A/B 测试 - 如果出现问题,回滚到以前的版本 - 维护代理开发的历史记录 ### 创建代理版本 ✅ 您可以使用 `createVersion` 方法创建代理的版本: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.version.AgentVersionManager import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建带有版本管理的代理 val myAgent = agent { name("版本化代理") description("一个带有版本管理的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 启用版本管理 versionManager = AgentVersionManager.create() } // 创建初始版本 val initialVersion = myAgent.createVersion( instructions = "你是一个提供简洁回答的有帮助的助手。", name = "v1.0", description = "具有基本功能的初始版本", activateImmediately = true ) println("创建了初始版本: ${initialVersion?.id}") // 创建改进版本 val improvedVersion = myAgent.createVersion( instructions = """ 你是一个提供简洁、准确回答的有帮助的助手。 提供信息时,始终引用你的来源。 如果你不确定某事,请承认不确定性。 """.trimIndent(), name = "v1.1", description = "带有来源引用的改进版本", metadata = mapOf("author" to "AI 团队", "approved" to "true"), activateImmediately = false ) println("创建了改进版本: ${improvedVersion?.id}") } ``` ### 管理版本 ✅ 您可以列出、检索和管理代理版本: ```kotlin // 获取所有版本 val versions = myAgent.getVersions() println("可用版本:") versions?.forEach { version -> println("- ${version.name}: ${version.description}") } // 获取活动版本 val activeVersion = myAgent.getActiveVersion() println("活动版本: ${activeVersion?.name}") // 激活特定版本 val versionToActivate = versions?.find { it.name == "v1.1" } if (versionToActivate != null) { val activated = myAgent.activateVersion(versionToActivate.id) println("激活的版本: ${activated?.name}") } ``` ### 回滚 ✅ 如果需要,您可以回滚到以前的版本: ```kotlin // 回滚到以前的版本 val versionToRollback = versions?.find { it.name == "v1.0" } if (versionToRollback != null) { val rolledBack = myAgent.rollbackToVersion(versionToRollback.id) println("回滚到版本: ${rolledBack?.name}") } ``` ### 版本比较 ✅ 您可以比较不同的版本以了解变化: ```kotlin import ai.kastrax.core.agent.version.compareVersions // 比较两个版本 val v1 = versions?.find { it.name == "v1.0" } val v2 = versions?.find { it.name == "v1.1" } if (v1 != null && v2 != null) { val comparison = compareVersions(v1, v2) println("从 ${v1.name} 到 ${v2.name} 的变化:") println("- 指令: ${comparison.instructionsDiff}") println("- 元数据变化: ${comparison.metadataChanges}") } ``` ## 代理状态管理 ✅ 代理状态管理允许您在交互之间维护和控制代理的状态。这对以下情况很有用: - 在会话之间维护上下文 - 跟踪代理在长时间运行任务上的进度 - 实现有状态行为 - 持久化重要信息 ### 代理状态基础 ✅ 代理状态包括: - **状态**:代理的当前操作状态 - **数据**:与代理关联的自定义数据 - **上下文**:代理操作的上下文信息 - **元数据**:关于状态的附加信息 ### 管理代理状态 ✅ 您可以获取和更新代理的状态: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.state.AgentStatus import ai.kastrax.core.agent.state.StateManager import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive fun main() = runBlocking { // 创建带有状态管理的代理 val myAgent = agent { name("有状态代理") description("一个带有状态管理的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 启用状态管理 stateManager = StateManager.create() } // 获取当前状态 val currentState = myAgent.getState() println("当前状态: ${currentState?.status}") // 更新代理的状态 val updatedState = myAgent.updateState( AgentStatus.BUSY, data = JsonObject(mapOf( "currentTask" to JsonPrimitive("研究量子计算"), "progress" to JsonPrimitive(25) )), context = JsonObject(mapOf( "userQuery" to JsonPrimitive("解释量子计算") )), metadata = mapOf( "priority" to "high", "estimatedCompletionTime" to "5 minutes" ) ) println("更新后的状态: ${updatedState?.status}") println("状态数据: ${updatedState?.data}") } ``` ### 持久状态 ✅ 您可以配置状态管理器以在会话之间持久化状态: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.state.StateManager import ai.kastrax.core.agent.state.storage.FileStateStorage import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建持久状态管理器 val stateManager = StateManager.create( storage = FileStateStorage("agent_states") ) // 创建带有持久状态管理的代理 val myAgent = agent { name("持久状态代理") description("一个带有持久状态的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 使用持久状态管理器 stateManager = stateManager } // 代理状态现在将在应用程序重启之间持久化 } ``` ### 状态转换 ✅ 您可以定义和强制执行状态转换: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.state.AgentStatus import ai.kastrax.core.agent.state.StateManager import ai.kastrax.core.agent.state.StateTransition import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 定义状态转换 val transitions = listOf( StateTransition(from = AgentStatus.IDLE, to = AgentStatus.BUSY), StateTransition(from = AgentStatus.BUSY, to = AgentStatus.IDLE), StateTransition(from = AgentStatus.BUSY, to = AgentStatus.ERROR), StateTransition(from = AgentStatus.ERROR, to = AgentStatus.IDLE) ) // 创建带有定义转换的状态管理器 val stateManager = StateManager.create( allowedTransitions = transitions ) // 创建带有状态管理器的代理 val myAgent = agent { name("转换代理") description("一个带有状态转换的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 使用带有转换的状态管理器 stateManager = stateManager } // 有效转换 myAgent.updateState(AgentStatus.BUSY) // 无效转换会抛出异常 try { myAgent.updateState(AgentStatus.PAUSED) } catch (e: IllegalStateException) { println("无效的状态转换: ${e.message}") } } ``` ## 结合版本控制和状态 ✅ 您可以结合版本控制和状态管理进行全面的代理管理: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.state.StateManager import ai.kastrax.core.agent.version.AgentVersionManager import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建同时具有版本和状态管理的代理 val myAgent = agent { name("管理代理") description("一个带有版本和状态管理的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 启用版本管理 versionManager = AgentVersionManager.create() // 启用状态管理 stateManager = StateManager.create() } // 创建版本 val version = myAgent.createVersion( instructions = "你是一个有帮助的助手。", name = "v1.0", activateImmediately = true ) // 更新状态 myAgent.updateState(AgentStatus.IDLE) // 使用代理 val response = myAgent.generate("你好,你能帮我什么忙?") println(response.text) // 创建新版本并激活它 val newVersion = myAgent.createVersion( instructions = "你是一个在编程方面有专长的有帮助的助手。", name = "v1.1", activateImmediately = true ) // 使用更新后的代理 val newResponse = myAgent.generate("你能帮我解决 Python 问题吗?") println(newResponse.text) } ``` ## 监控和诊断 ✅ 您可以监控代理版本和状态进行诊断: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.agent.monitoring.AgentMonitor import ai.kastrax.core.agent.state.StateManager import ai.kastrax.core.agent.version.AgentVersionManager import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建监控器 val monitor = AgentMonitor.create() // 创建带有监控的代理 val myAgent = agent { name("被监控的代理") description("一个带有监控的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 启用版本管理 versionManager = AgentVersionManager.create() // 启用状态管理 stateManager = StateManager.create() // 启用监控 monitor = monitor } // 创建版本并更新状态 myAgent.createVersion( instructions = "你是一个有帮助的助手。", name = "v1.0", activateImmediately = true ) myAgent.updateState(AgentStatus.IDLE) // 获取监控数据 val versionHistory = monitor.getVersionHistory(myAgent.name) println("版本历史: $versionHistory") val stateHistory = monitor.getStateHistory(myAgent.name) println("状态历史: $stateHistory") // 获取性能指标 val metrics = monitor.getPerformanceMetrics(myAgent.name) println("性能指标: $metrics") } ``` ## 最佳实践 ✅ 使用代理版本控制和状态管理时,请遵循以下最佳实践: 1. **版本命名**:使用一致的版本命名方案(例如,语义版本控制) 2. **版本文档**:为每个版本包含详细的描述和元数据 3. **状态验证**:验证状态数据以确保一致性 4. **错误处理**:为状态转换和版本激活实现适当的错误处理 5. **监控**:监控版本和状态变化,用于调试和分析 6. **持久化**:为生产环境使用持久存储 7. **测试**:彻底测试不同的版本和状态转换 ## 结论 ✅ 代理版本控制和状态管理为在 Kastrax 中构建复杂、可维护的 AI 代理系统提供了强大的功能。通过跟踪版本和管理状态,您可以创建随时间演变的代理,同时保持可靠性和一致性。 ## 下一步 ✅ - 了解[代理架构](./architectures-kotlin.mdx) - 探索[记忆集成](../memory/overview.mdx) - 理解[代理监控](./monitoring-kotlin.mdx) --- title: KastraX 核心类 | Kastrax 文档 description: KastraX 类的文档,这是 Kastrax 框架中管理代理、工具和其他组件的核心入口点。 --- # KastraX 核心类 ✅ [ZH] Source: https://kastrax.ai/zh/docs/core/kastrax-class-kotlin `KastraX` 类是 Kastrax 框架的中央入口点,为管理代理、工具和其他组件提供了统一的接口。本指南解释了如何使用 KastraX 类构建 AI 代理应用程序。 ## 概述 ✅ `KastraX` 类作为各种组件的容器和管理器: - **代理**:具有不同能力的 AI 代理 - **工具**:代理可以用来与外部系统交互的函数 - **记忆**:用于存储和检索对话历史的系统 - **RAG**:检索增强生成组件 ## 基本用法 ✅ 以下是创建和使用 KastraX 类的简单示例: ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 KastraX 实例 val kastraxInstance = kastrax { // 添加代理 agent("assistant") { name("助手") description("一个有帮助的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } } // 获取代理 val assistant = kastraxInstance.getAgent("assistant") // 使用代理 val response = assistant.generate("你好,你能帮我什么忙?") println(response.text) } ``` ## 创建 KastraX 实例 ✅ 您可以使用 DSL 函数创建 KastraX 实例: ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val kastraxInstance = kastrax { // 配置代理 agent("assistant") { name("助手") description("一个有帮助的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } agent("researcher") { name("研究员") description("一个研究专家") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.5) } } } } ``` ## 管理代理 ✅ KastraX 类提供了管理代理的方法: ```kotlin // 通过 ID 获取代理 val assistant = kastraxInstance.getAgent("assistant") // 获取所有代理 val allAgents = kastraxInstance.getAgents() // 检查代理是否存在 if (kastraxInstance.hasAgent("assistant")) { // 使用代理 } ``` ## 高级配置 ✅ 您可以使用其他组件配置 KastraX 实例: ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建共享工具 val calculatorTool = tool { id = "calculator" name = "计算器" description = "执行基本算术运算" // 工具配置... } // 创建带有共享组件的 KastraX 实例 val kastraxInstance = kastrax { // 配置全局工具 globalTool(calculatorTool) // 配置代理 agent("assistant") { name("助手") description("一个有帮助的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 使用全局工具 useGlobalTools(true) } } } ``` ## 与其他系统集成 ✅ KastraX 类可以与其他系统集成: ### Actor 系统集成 ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.actor.actorSystem import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 actor 系统 val actorSystem = actorSystem("kastrax-system") // 创建带有 actor 系统的 KastraX 实例 val kastraxInstance = kastrax { // 配置 actor 系统 actorSystem(actorSystem) // 配置代理 agent("assistant") { name("助手") description("一个有帮助的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } } // 创建基于 actor 的代理 val agentActor = kastraxInstance.createActorAgent("assistant") // 向代理 actor 发送消息 actorSystem.root.send(agentActor, "你好,你能帮我什么忙?") } ``` ### RAG 系统集成 ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.rag.RAG import ai.kastrax.rag.document.DocumentLoader import ai.kastrax.rag.embedding.FastEmbeddingService import ai.kastrax.rag.vectorstore.InMemoryVectorStore import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 RAG 组件 val embeddingService = FastEmbeddingService.create() val vectorStore = InMemoryVectorStore() val rag = RAG(vectorStore, embeddingService) // 加载文档 val loader = DocumentLoader.fromFile("knowledge_base.pdf") val documents = loader.load() rag.addDocuments(documents) // 创建带有 RAG 的 KastraX 实例 val kastraxInstance = kastrax { // 配置 RAG ragSystem(rag) // 配置代理 agent("assistant") { name("助手") description("一个具有知识访问能力的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 使用 RAG useRag(true) } } // 使用带有 RAG 的代理 val assistant = kastraxInstance.getAgent("assistant") val response = assistant.generate("知识库关于气候变化说了什么?") println(response.text) } ``` ## 最佳实践 ✅ 使用 KastraX 类时,请遵循以下最佳实践: 1. **集中配置**:使用 KastraX 类作为应用程序的中央配置点 2. **资源管理**:当不再需要资源时,正确关闭资源 3. **错误处理**:为代理交互实现适当的错误处理 4. **共享组件**:在适当的情况下,在代理之间使用共享组件(工具、记忆) 5. **模块化设计**:以模块化方式设计应用程序,每个代理都有明确的职责 ## 结论 ✅ KastraX 类为在应用程序中管理 AI 代理和相关组件提供了强大而灵活的方式。通过集中配置和管理,它简化了复杂 AI 代理系统的开发。 ## 下一步 ✅ - 了解[代理创建](../agents/creating-agents-kotlin.mdx) - 探索[工具集成](../tools/overview-kotlin.mdx) - 理解[记忆系统](../memory/overview-kotlin.mdx) - 发现[RAG 集成](../rag/overview.mdx) --- title: "KastraX 核心类 | 核心概念 | Kastrax 文档" description: "KastraX 类的文档,这是 Kastrax 框架中管理代理、工具和其他组件的核心入口点。" --- # KastraX 核心类 ✅ [ZH] Source: https://kastrax.ai/zh/docs/core/kastrax-class `KastraX` 类是 Kastrax 框架的中央入口点,为管理代理、工具和其他组件提供了统一的接口。本指南解释了如何使用 KastraX 类构建 AI 代理应用程序。 ## 概述 ✅ `KastraX` 类作为各种组件的容器和管理器: - **代理**:具有不同能力的 AI 代理 - **工具**:代理可以用来与外部系统交互的函数 - **记忆**:用于存储和检索对话历史的系统 - **RAG**:检索增强生成组件 - **工作流**:定义和执行复杂任务的工作流 - **Actor 系统**:分布式计算的 Actor 系统 ## 基本用法 ✅ 以下是创建和使用 KastraX 类的简单示例: ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 KastraX 实例 val kastraxInstance = kastrax { // 配置代理 agent("assistant") { name("助手") description("一个有帮助的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } } // 获取代理 val assistant = kastraxInstance.getAgent("assistant") // 使用代理 val response = assistant.generate("你好,你能帮我什么忙?") println(response.text) } ``` ## KastraX 类 API ✅ `KastraX` 类提供了以下主要方法: ### 代理管理 ✅ ```kotlin // 获取代理 fun getAgent(agentId: String): Agent // 获取所有代理 fun getAgents(): Map // 添加代理 fun addAgent(agentId: String, agent: Agent): KastraX // 移除代理 fun removeAgent(agentId: String): KastraX ``` ### 工具管理 ✅ ```kotlin // 获取工具 fun getTool(toolId: String): Tool // 获取所有工具 fun getTools(): Map // 添加工具 fun addTool(toolId: String, tool: Tool): KastraX // 移除工具 fun removeTool(toolId: String): KastraX ``` ### 记忆管理 ✅ ```kotlin // 获取记忆 fun getMemory(memoryId: String): Memory // 获取所有记忆 fun getMemories(): Map // 添加记忆 fun addMemory(memoryId: String, memory: Memory): KastraX // 移除记忆 fun removeMemory(memoryId: String): KastraX ``` ### RAG 管理 ✅ ```kotlin // 获取 RAG fun getRag(ragId: String): RAG // 获取所有 RAG fun getRags(): Map // 添加 RAG fun addRag(ragId: String, rag: RAG): KastraX // 移除 RAG fun removeRag(ragId: String): KastraX ``` ### 工作流管理 ✅ ```kotlin // 获取工作流 fun getWorkflow(workflowId: String): Workflow // 获取所有工作流 fun getWorkflows(): Map // 添加工作流 fun addWorkflow(workflowId: String, workflow: Workflow): KastraX // 移除工作流 fun removeWorkflow(workflowId: String): KastraX ``` ### Actor 系统管理 ✅ ```kotlin // 获取 Actor 系统 fun getActorSystem(): ActorSystem // 设置 Actor 系统 fun setActorSystem(actorSystem: ActorSystem): KastraX // 创建 Actor 代理 fun createActorAgent(agentId: String): PID ``` ## KastraX 构建器 ✅ `KastraXBuilder` 类提供了一个流畅的 API 来创建 `KastraX` 实例: ```kotlin // 使用构建器创建 KastraX 实例 val kastraxInstance = KastraXBuilder() .agent("assistant", assistantAgent) .agent("researcher", researcherAgent) .tool("calculator", calculatorTool) .tool("weather", weatherTool) .memory("conversation", conversationMemory) .rag("knowledge-base", knowledgeBaseRag) .workflow("research", researchWorkflow) .actorSystem(actorSystem) .build() ``` 或者使用 DSL 创建: ```kotlin // 使用 DSL 创建 KastraX 实例 val kastraxInstance = kastrax { // 配置代理 agent("assistant") { name("助手") description("一个有帮助的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } agent("researcher") { name("研究员") description("一个专注于研究的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.3) } } // 配置工具 tool("calculator") { id = "calculator" name = "计算器" description = "执行数学计算" // 工具配置... } // 配置记忆 memory("conversation") { inMemoryStorage() lastMessages(10) workingMemory(true) } // 配置 RAG rag("knowledge-base") { vectorStore(inMemoryVectorStore()) embeddingService(openAiEmbedding()) // RAG 配置... } // 配置工作流 workflow("research") { name = "研究工作流" description = "执行研究任务的工作流" // 工作流配置... } // 配置 Actor 系统 actorSystem { name = "kastrax-system" // Actor 系统配置... } } ``` ## 与 Actor 系统集成 ✅ `KastraX` 类可以与 Actor 系统集成,创建基于 Actor 的代理: ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.actor.actorSystem import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 actor 系统 val actorSystem = actorSystem("kastrax-system") // 创建带有 actor 系统的 KastraX 实例 val kastraxInstance = kastrax { // 配置 actor 系统 actorSystem(actorSystem) // 配置代理 agent("assistant") { name("助手") description("一个有帮助的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } } // 创建基于 actor 的代理 val agentActor = kastraxInstance.createActorAgent("assistant") // 向代理 actor 发送消息 actorSystem.root.send(agentActor, "你好,你能帮我什么忙?") } ``` ## 配置 KastraX ✅ `KastraX` 类可以通过配置系统进行配置: ```kotlin // 使用配置创建 KastraX 实例 val kastraxInstance = kastrax { // 全局配置 config { // 应用程序 ID appId = "my-kastrax-app" // 环境(开发、测试、生产) environment = "development" // 版本 version = "1.0.0" // LLM 配置 llm { defaultProvider = "deepseek" timeout = 30000 // 30 秒 retryCount = 3 } // 记忆配置 memory { defaultStorage = "in-memory" maxMessages = 100 workingMemoryEnabled = true } // RAG 配置 rag { defaultVectorStore = "in-memory" chunkSize = 1000 chunkOverlap = 200 } // 日志配置 logging { level = "INFO" format = "json" } } // 代理配置... } ``` ## 事件系统 ✅ `KastraX` 类提供了事件系统,允许您监听和响应各种事件: ```kotlin // 创建 KastraX 实例 val kastraxInstance = kastrax { // 配置... } // 添加事件监听器 kastraxInstance.events.addListener(KastraxEvent.AGENT_CREATED) { event -> println("代理已创建:${event.data["agentId"]}") } kastraxInstance.events.addListener(KastraxEvent.AGENT_GENERATE_START) { event -> println("代理开始生成:${event.data["agentId"]}") } kastraxInstance.events.addListener(KastraxEvent.AGENT_GENERATE_COMPLETE) { event -> println("代理生成完成:${event.data["agentId"]}") } // 移除事件监听器 val listenerId = kastraxInstance.events.addListener(KastraxEvent.AGENT_ERROR) { event -> println("代理错误:${event.data["error"]}") } kastraxInstance.events.removeListener(listenerId) ``` ## 多代理系统 ✅ `KastraX` 类支持创建多代理系统,代理之间可以协作: ```kotlin // 创建多代理系统 val kastraxInstance = kastrax { // 配置助手代理 agent("assistant") { name("助手") description("一个有帮助的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // 配置研究员代理 agent("researcher") { name("研究员") description("一个专注于研究的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.3) } } // 配置编辑代理 agent("editor") { name("编辑") description("一个专注于编辑和改进文本的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.5) } } // 配置代理网络 agentNetwork { // 添加代理 addAgent("assistant") addAgent("researcher") addAgent("editor") // 配置路由 routing { // 根据查询类型路由到不同的代理 route("research") to "researcher" route("edit") to "editor" route("*") to "assistant" // 默认路由 } } } // 使用代理网络 val agentNetwork = kastraxInstance.getAgentNetwork() val response = agentNetwork.generate("我需要研究人工智能的历史") println(response.text) ``` ## 服务器集成 ✅ `KastraX` 类可以与 HTTP 服务器集成,提供 API 接口: ```kotlin // 创建 KastraX 实例 val kastraxInstance = kastrax { // 配置... // 配置服务器 server { port = 8080 host = "localhost" enableCors = true // 配置路由 routes { // 代理路由 route("/agents/{agentId}/generate") { post { // 处理生成请求 } } // 工具路由 route("/tools/{toolId}/execute") { post { // 处理工具执行请求 } } // 工作流路由 route("/workflows/{workflowId}/execute") { post { // 处理工作流执行请求 } } } } } // 启动服务器 kastraxInstance.startServer() ``` ## 最佳实践 ✅ 使用 `KastraX` 类的最佳实践: 1. **使用 DSL**:使用 DSL 创建和配置 KastraX 实例,提高代码可读性 2. **组件命名**:为代理、工具和其他组件使用有意义的名称 3. **错误处理**:添加事件监听器监听错误事件,及时处理错误 4. **资源管理**:在不再需要 KastraX 实例时关闭它,释放资源 5. **配置分离**:将配置与代码分离,使用配置文件或环境变量 6. **模块化**:将代理、工具和其他组件分离到不同的文件中 7. **测试**:为 KastraX 实例编写单元测试和集成测试 8. **监控**:使用事件系统监控 KastraX 实例的运行状态 ## 示例应用 ✅ 以下是一个完整的示例应用,展示了如何使用 `KastraX` 类创建一个多代理系统: ```kotlin import ai.kastrax.core.kastrax import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import java.time.LocalDateTime import java.time.format.DateTimeFormatter fun main() = runBlocking { // 创建 KastraX 实例 val kastraxInstance = kastrax { // 配置助手代理 agent("assistant") { name("助手") description("一个有帮助的助手") instructions(""" 你是一个有帮助的助手,旨在提供准确、有用的信息。 遵循这些准则: 1. 提供简洁、准确的回答 2. 如果你不知道答案,坦率地承认 3. 避免编造信息 你可以使用以下工具: - getCurrentTime:获取当前时间 - calculateExpression:计算数学表达式 """.trimIndent()) model = deepSeek { apiKey(System.getenv("DEEPSEEK_API_KEY") ?: "your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } memory = memory { inMemoryStorage() lastMessages(10) workingMemory(true) } tools { // 获取当前时间的工具 tool("getCurrentTime") { description("获取当前时间和日期") parameters {} execute { val now = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") "当前时间是:${now.format(formatter)}" } } // 计算表达式的工具 tool("calculateExpression") { description("计算数学表达式") parameters { parameter("expression", "要计算的数学表达式", String::class) } execute { params -> try { val expression = params["expression"] as String val result = evaluateExpression(expression) "表达式 '$expression' 的结果是:$result" } catch (e: Exception) { "无法计算表达式:${e.message}" } } } } } } // 添加事件监听器 kastraxInstance.events.addListener(KastraxEvent.AGENT_GENERATE_START) { event -> println("代理开始生成:${event.data["agentId"]}") } kastraxInstance.events.addListener(KastraxEvent.AGENT_GENERATE_COMPLETE) { event -> println("代理生成完成:${event.data["agentId"]}") } // 获取代理 val assistant = kastraxInstance.getAgent("assistant") // 创建对话线程 val threadId = "user-session-1" // 使用代理 println("与助手对话。输入 'exit' 退出。") while (true) { print("\n> ") val input = readLine() ?: "" if (input.lowercase() == "exit") { break } val response = assistant.generate( input, options = ai.kastrax.core.agent.AgentGenerateOptions( threadId = threadId, memoryOptions = ai.kastrax.core.memory.MemoryOptions( enabled = true ) ) ) println("\n${response.text}") } } // 简单的表达式计算器 fun evaluateExpression(expression: String): Double { // 注意:这是一个简化的实现,仅用于演示 // 在生产环境中,应使用更安全、更强大的表达式计算库 return javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval(expression).toString().toDouble() } ``` ## 总结 ✅ `KastraX` 类是 Kastrax 框架的中央入口点,提供了管理代理、工具和其他组件的统一接口。通过 `KastraX` 类,您可以创建和配置 AI 代理应用程序,实现复杂的 AI 功能。 ## 下一步 ✅ 了解了 `KastraX` 类后,您可以: 1. 探索 [Agent 系统](/docs/agents/overview.mdx) 2. 学习 [工具系统](/docs/tools/overview.mdx) 3. 了解 [记忆系统](/docs/memory/overview.mdx) 4. 研究 [RAG 系统](/docs/rag/overview.mdx) 5. 探索 [工作流系统](/docs/workflows/overview.mdx) 6. 学习 [Actor 模型](/docs/actor/overview.mdx) --- title: 评估系统概述 | Kastrax 文档 description: Kastrax 评估系统的详细介绍,包括评估框架、评估指标和评估方法的实现和使用方法。 --- # 评估系统概述 ✅ [ZH] Source: https://kastrax.ai/zh/docs/evals/overview Kastrax 评估系统提供了一套全面的工具和方法,用于评估 AI 代理的性能、准确性和行为。本指南详细介绍了评估系统的架构、组件和使用方法。 ## 什么是评估系统? ✅ 评估系统是一个框架,用于: - 测量 AI 代理的性能和质量 - 比较不同代理实现的效果 - 识别代理的优势和不足 - 指导代理的改进和优化 - 监控代理在生产环境中的表现 通过系统化的评估,您可以确保 AI 代理满足预期的质量标准,并在部署前发现潜在问题。 ## 评估系统架构 ✅ Kastrax 评估系统由以下核心组件组成: 1. **评估框架**:定义评估流程和方法 2. **评估指标**:量化代理性能的指标 3. **评估数据集**:用于测试代理的标准数据集 4. **评估运行器**:执行评估并收集结果 5. **评估报告**:分析和展示评估结果 ## 基本评估用法 ✅ 以下是使用 Kastrax 评估系统的简单示例: ```kotlin import ai.kastrax.evals.Evaluator import ai.kastrax.evals.metrics.AccuracyMetric import ai.kastrax.evals.datasets.QADataset import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建要评估的代理 val myAgent = agent { name("评估测试代理") description("用于评估的测试代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // 创建评估器 val evaluator = Evaluator.create { // 添加评估指标 metric(AccuracyMetric()) // 添加评估数据集 dataset(QADataset.fromFile("qa_dataset.json")) // 配置评估参数 parameters { maxExamples = 100 batchSize = 10 timeout = 30000 // 30 秒 } } // 运行评估 val results = evaluator.evaluate(myAgent) // 打印评估结果 println("评估结果:") println("准确率: ${results.getMetric("accuracy")}") println("平均响应时间: ${results.getMetric("avg_response_time")} ms") // 生成评估报告 val report = evaluator.generateReport(results) report.saveToFile("evaluation_report.html") } ``` ## 评估指标 ✅ Kastrax 提供了多种评估指标,用于衡量代理的不同方面: ### 准确性指标 ```kotlin // 添加准确性指标 evaluator.metric(AccuracyMetric()) evaluator.metric(PrecisionMetric()) evaluator.metric(RecallMetric()) evaluator.metric(F1ScoreMetric()) ``` ### 质量指标 ```kotlin // 添加质量指标 evaluator.metric(CoherenceMetric()) evaluator.metric(RelevanceMetric()) evaluator.metric(FluencyMetric()) evaluator.metric(HelpfulnessMetric()) ``` ### 效率指标 ```kotlin // 添加效率指标 evaluator.metric(ResponseTimeMetric()) evaluator.metric(TokenUsageMetric()) evaluator.metric(ToolUsageMetric()) evaluator.metric(MemoryUsageMetric()) ``` ### 安全指标 ```kotlin // 添加安全指标 evaluator.metric(HarmfulContentMetric()) evaluator.metric(BiasMetric()) evaluator.metric(PrivacyMetric()) evaluator.metric(SecurityMetric()) ``` ### 自定义指标 ```kotlin // 创建自定义指标 class CustomMetric : EvaluationMetric { override val name: String = "custom_metric" override val description: String = "自定义评估指标" override suspend fun evaluate(response: AgentResponse, expected: Any?): MetricResult { // 实现自定义评估逻辑 val score = calculateCustomScore(response.text, expected as? String) return MetricResult( name = name, score = score, details = mapOf("custom_info" to "自定义信息") ) } private fun calculateCustomScore(response: String, expected: String?): Double { // 实现自定义评分逻辑 return 0.85 // 示例分数 } } // 添加自定义指标 evaluator.metric(CustomMetric()) ``` ## 评估数据集 ✅ Kastrax 支持多种评估数据集: ### 问答数据集 ```kotlin // 创建问答数据集 val qaDataset = QADataset.fromFile("qa_dataset.json") // 或者手动创建 val manualQADataset = QADataset.create { example { question = "什么是人工智能?" answer = "人工智能是计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的系统。" } example { question = "Kastrax 是什么?" answer = "Kastrax 是一个基于 Kotlin 的 AI 代理框架,提供了构建智能代理应用程序所需的核心组件。" } // 添加更多示例... } ``` ### 工具使用数据集 ```kotlin // 创建工具使用数据集 val toolDataset = ToolUsageDataset.fromFile("tool_dataset.json") // 或者手动创建 val manualToolDataset = ToolUsageDataset.create { example { input = "计算 15 * 7 + 22 / 2 的结果" expectedTools = listOf("calculator") expectedResult = "计算结果: 116" } example { input = "今天的天气怎么样?" expectedTools = listOf("weather") expectedResult = "包含天气信息的响应" } // 添加更多示例... } ``` ### 对话数据集 ```kotlin // 创建对话数据集 val conversationDataset = ConversationDataset.fromFile("conversation_dataset.json") // 或者手动创建 val manualConversationDataset = ConversationDataset.create { conversation { message(role = "user", content = "你好,我想了解一下 Kastrax。") message(role = "assistant", content = "你好!Kastrax 是一个基于 Kotlin 的 AI 代理框架,提供了构建智能代理应用程序所需的核心组件。有什么具体问题我可以帮你解答吗?") message(role = "user", content = "它有哪些主要功能?") message(role = "assistant", content = "Kastrax 的主要功能包括:1) 多种代理架构;2) Actor 模型集成;3) 高级记忆系统;4) 灵活的工具系统;5) RAG 系统;6) 工作流引擎;7) 多种 LLM 集成。") } // 添加更多对话... } ``` ### 自定义数据集 ```kotlin // 创建自定义数据集 class CustomDataset : EvaluationDataset { override val name: String = "custom_dataset" override val description: String = "自定义评估数据集" override suspend fun getExamples(): List { // 实现自定义数据集逻辑 return listOf( EvaluationExample( input = "自定义输入 1", expected = "自定义期望输出 1", metadata = mapOf("category" to "test") ), EvaluationExample( input = "自定义输入 2", expected = "自定义期望输出 2", metadata = mapOf("category" to "test") ) ) } } // 添加自定义数据集 evaluator.dataset(CustomDataset()) ``` ## 评估运行器 ✅ 评估运行器负责执行评估并收集结果: ```kotlin // 创建评估运行器 val runner = EvaluationRunner.create { // 配置并行度 parallelism = 4 // 配置重试策略 retryStrategy { maxRetries = 3 retryDelay = 1000 // 1 秒 backoffMultiplier = 2.0 // 指数退避 } // 配置日志 logging { logLevel = LogLevel.INFO logFile = "evaluation.log" } // 配置超时 timeout = 30000 // 30 秒 } // 运行评估 val results = runner.run(evaluator, myAgent) ``` ## 评估报告 ✅ 评估报告提供了评估结果的详细分析: ```kotlin // 生成评估报告 val report = EvaluationReport.generate(results) { // 配置报告格式 format = ReportFormat.HTML // 添加图表 charts { barChart("指标比较") lineChart("响应时间分布") pieChart("错误类型分布") } // 添加详细分析 analysis { includeFailedExamples = true includeSuccessExamples = true maxExamples = 10 } // 添加比较 comparison { baseline = baselineResults highlightDifferences = true } } // 保存报告 report.saveToFile("evaluation_report.html") // 或者获取报告内容 val reportContent = report.getContent() println(reportContent) ``` ## 持续评估 ✅ Kastrax 支持持续评估,用于监控代理的长期性能: ```kotlin import ai.kastrax.evals.ContinuousEvaluator import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建持续评估器 val continuousEvaluator = ContinuousEvaluator.create { // 添加要评估的代理 agent("production", productionAgent) // 添加评估数据集 dataset(QADataset.fromFile("qa_dataset.json")) // 配置评估频率 schedule { interval = 24 * 60 * 60 * 1000 // 每天 startTime = System.currentTimeMillis() } // 配置警报 alerts { threshold("accuracy", 0.8) { value, threshold -> value < threshold } notification { email("admin@example.com") slack("#monitoring") } } // 配置存储 storage { type = StorageType.DATABASE connectionString = "jdbc:postgresql://localhost:5432/evals" } } // 启动持续评估 continuousEvaluator.start() // 获取最新结果 val latestResults = continuousEvaluator.getLatestResults("production") println("最新评估结果: $latestResults") // 获取历史趋势 val trend = continuousEvaluator.getTrend("production", "accuracy", days = 30) println("准确率趋势: $trend") // 停止持续评估 continuousEvaluator.stop() } ``` ## 在 CI/CD 中使用评估 ✅ Kastrax 评估系统可以集成到 CI/CD 流程中: ```kotlin import ai.kastrax.evals.CIEvaluator import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 CI 评估器 val ciEvaluator = CIEvaluator.create { // 添加评估数据集 dataset(QADataset.fromFile("qa_dataset.json")) // 添加评估指标 metric(AccuracyMetric()) metric(ResponseTimeMetric()) // 配置阈值 threshold("accuracy", 0.85) threshold("avg_response_time", 2000) { value, threshold -> value < threshold // 响应时间越低越好 } // 配置报告 report { format = ReportFormat.MARKDOWN outputFile = "evaluation_report.md" } } // 运行 CI 评估 val success = ciEvaluator.evaluate(myAgent) // 根据评估结果决定 CI 流程是否通过 if (success) { println("评估通过,继续 CI 流程") System.exit(0) } else { println("评估失败,中止 CI 流程") System.exit(1) } } ``` ## 自定义评估 ✅ 您可以创建自定义评估来满足特定需求: ```kotlin import ai.kastrax.evals.CustomEvaluator import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建自定义评估器 val customEvaluator = CustomEvaluator.create { // 定义评估逻辑 evaluationLogic { agent -> // 实现自定义评估逻辑 val results = mutableMapOf() // 示例:评估代理的创造性 val creativityScore = evaluateCreativity(agent) results["creativity"] = creativityScore // 示例:评估代理的学习能力 val learningScore = evaluateLearning(agent) results["learning_ability"] = learningScore // 返回评估结果 results } // 定义成功标准 successCriteria { results -> val creativity = results["creativity"] ?: 0.0 val learningAbility = results["learning_ability"] ?: 0.0 // 组合分数 val combinedScore = 0.6 * creativity + 0.4 * learningAbility // 判断是否成功 combinedScore >= 0.8 } } // 运行自定义评估 val results = customEvaluator.evaluate(myAgent) println("自定义评估结果: $results") } // 评估创造性的辅助函数 suspend fun evaluateCreativity(agent: Agent): Double { // 实现创造性评估逻辑 val prompts = listOf( "创作一首关于人工智能的诗", "设计一个解决气候变化的创新方案", "想象一个未来的交通系统" ) var totalScore = 0.0 for (prompt in prompts) { val response = agent.generate(prompt) val score = analyzeCreativity(response.text) totalScore += score } return totalScore / prompts.size } // 分析创造性的辅助函数 fun analyzeCreativity(text: String): Double { // 实现创造性分析逻辑 // 这里是一个简化的示例 val uniqueWords = text.split("\\s+".toRegex()).toSet().size val totalWords = text.split("\\s+".toRegex()).size val novelPhrases = countNovelPhrases(text) // 计算创造性分数 return 0.4 * (uniqueWords.toDouble() / totalWords) + 0.6 * (novelPhrases.toDouble() / totalWords) } // 评估学习能力的辅助函数 suspend fun evaluateLearning(agent: Agent): Double { // 实现学习能力评估逻辑 // 这里是一个简化的示例 // 创建一个对话线程 val threadId = UUID.randomUUID().toString() // 第一轮:提供信息 agent.generate( "我要告诉你一些信息:X 是 Y 的两倍,Y 是 Z 的三倍,Z 等于 2。", options = AgentGenerateOptions(threadId = threadId) ) // 第二轮:测试记忆和推理 val response = agent.generate( "根据我之前告诉你的信息,X 等于多少?", options = AgentGenerateOptions(threadId = threadId) ) // 分析响应 return if (response.text.contains("12") || response.text.contains("X = 12")) { 1.0 // 完全正确 } else if (response.text.contains("X") && response.text.contains("12")) { 0.8 // 基本正确 } else { 0.0 // 不正确 } } // 计算新颖短语的辅助函数 fun countNovelPhrases(text: String): Int { // 实现新颖短语计数逻辑 // 这里是一个简化的示例 val commonPhrases = setOf( "人工智能", "机器学习", "深度学习", "神经网络", "气候变化", "全球变暖", "可持续发展", "未来技术", "创新解决方案", "智能系统" ) var novelCount = 0 val words = text.split("\\s+".toRegex()) for (i in 0 until words.size - 2) { val phrase = words.subList(i, i + 3).joinToString(" ") if (!commonPhrases.any { phrase.contains(it) }) { novelCount++ } } return novelCount } ``` ## 最佳实践 ✅ 1. **多样化数据集**:使用多样化的数据集,覆盖不同场景和边缘情况 2. **多维度评估**:使用多种指标评估代理的不同方面 3. **基准比较**:与基准模型或之前的版本进行比较 4. **持续评估**:定期评估代理性能,监控长期趋势 5. **错误分析**:深入分析失败案例,找出改进机会 6. **人机结合**:结合自动评估和人工评估,获得全面视角 7. **适当阈值**:设置合理的成功阈值,既有挑战性又可实现 8. **版本控制**:对评估数据集和指标进行版本控制,确保一致性 ## 结论 ✅ Kastrax 评估系统提供了一套全面的工具和方法,用于评估 AI 代理的性能、准确性和行为。通过系统化的评估,您可以确保 AI 代理满足预期的质量标准,并在部署前发现潜在问题。 ## 下一步 ✅ 现在您已经了解了评估系统,您可以: 1. 了解[自定义评估](./custom-eval.mdx)的详细用法 2. 探索[文本评估](./textual-evals.mdx)的技术和策略 3. 学习[在 CI 中运行评估](./running-in-ci.mdx)的方法和最佳实践 --- title: "创建您的第一个代理 | 入门指南 | Kastrax 文档" description: "学习如何使用 Kastrax 创建您的第一个 AI 代理,包括基本配置、记忆和工具。" --- # 创建您的第一个 Kastrax 代理 ✅ [ZH] Source: https://kastrax.ai/zh/docs/getting-started/first-agent-kotlin 本指南将引导您使用 Kastrax 框架创建您的第一个代理。我们将构建一个简单的对话代理,能够响应用户查询。 ## 基本代理结构 ✅ 在 Kastrax 中,代理使用 DSL(领域特定语言)创建,这使得定义它们的行为变得容易。以下是 Kastrax 代理的基本结构: ```kotlin val myAgent = agent { name("我的第一个代理") description("一个简单的对话代理") model = deepSeek { apiKey("your-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 可选组件 memory(...) tools(...) } ``` ## 逐步指南 ✅ 让我们逐步创建一个简单的对话代理: ### 1. 创建代理文件 ✅ 在您的项目中创建一个新的 Kotlin 文件,例如 `src/main/kotlin/com/example/agents/ConversationAgent.kt`: ```kotlin package com.example.agents import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking /** * 一个能够响应用户查询的简单对话代理。 */ class ConversationAgent { companion object { @JvmStatic fun main(args: Array) = runBlocking { // 创建代理 val conversationAgent = createConversationAgent() // 测试代理 val response = conversationAgent.generate("你好!你能介绍一下自己吗?") println("代理响应: ${response.text}") } } } /** * 创建一个简单的对话代理。 */ fun createConversationAgent() = agent { name("对话代理") description("一个友好的对话代理,可以与用户聊天") // 定义系统提示 systemPrompt(""" 你是一个有帮助、友好的助手。你的目标是与用户进行有趣的对话。 指导方针: - 保持礼貌和尊重 - 提供简洁但信息丰富的回答 - 提出后续问题以保持对话进行 - 如果你不知道某事,承认它而不是编造信息 """.trimIndent()) // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") // 替换为您的实际 API 密钥 model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) maxTokens(1000) } } ``` ### 2. 运行代理 ✅ 您可以直接从 IDE 或使用 Gradle 运行代理: ```bash gradle run -PmainClass=com.example.agents.ConversationAgent ``` 您应该看到类似以下的输出: ``` 代理响应: 你好!我是你的友好助手,我在这里与你聊天。 很高兴认识你!我可以帮助回答问题,讨论各种话题,或者只是进行轻松的对话。 今天有什么特别想聊的话题吗? ``` ## 添加记忆 ✅ 让我们通过添加记忆来增强我们的代理,使其能够记住之前的交互: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createConversationAgentWithMemory() = agent { name("带记忆的对话代理") description("一个具有记忆能力的友好对话代理") systemPrompt(""" 你是一个有帮助、友好的助手。你的目标是与用户进行有趣的对话。 指导方针: - 保持礼貌和尊重 - 提供简洁但信息丰富的回答 - 提出后续问题以保持对话进行 - 如果你不知道某事,承认它而不是编造信息 - 记住关于用户的重要细节 """.trimIndent()) // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) maxTokens(1000) } // 添加记忆 memory = memory { workingMemory(true) conversationHistory(20) // 记住最后 20 条消息 } } ``` ## 添加工具 ✅ 现在,让我们为我们的代理添加一个可以获取当前时间的简单工具: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import java.time.LocalDateTime import java.time.format.DateTimeFormatter fun createConversationAgentWithTools() = agent { name("带工具的对话代理") description("一个带有工具的友好对话代理") systemPrompt(""" 你是一个有帮助、友好的助手。你的目标是与用户进行有趣的对话。 指导方针: - 保持礼貌和尊重 - 提供简洁但信息丰富的回答 - 提出后续问题以保持对话进行 - 如果你不知道某事,承认它而不是编造信息 - 在适当的时候使用你的工具 """.trimIndent()) // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) maxTokens(1000) } // 添加工具 tools { tool("getCurrentTime") { description("获取当前时间") parameters {} execute { val currentTime = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val formattedTime = currentTime.format(formatter) "当前时间是 $formattedTime" } } } } ``` ## 使用对话测试代理 ✅ 让我们创建一个简单的函数来与我们的代理进行对话: ```kotlin import ai.kastrax.core.agent.Agent import kotlinx.coroutines.runBlocking suspend fun conversationTest(agent: Agent) { val conversation = listOf( "你好!我的名字是小明。", "你能帮我做什么?", "现在几点了?", "谢谢!你还记得我的名字吗?", "再见!" ) for (message in conversation) { println("\n用户: $message") val response = agent.generate(message) println("代理: ${response.text}") } } fun main() = runBlocking { val agent = createConversationAgentWithTools() conversationTest(agent) } ``` ## 完整示例 ✅ 以下是将所有内容整合在一起的完整示例: ```kotlin package com.example.agents import ai.kastrax.core.agent.Agent import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import java.time.LocalDateTime import java.time.format.DateTimeFormatter /** * 一个具有记忆和工具的完整对话代理示例。 */ class CompleteAgentExample { companion object { @JvmStatic fun main(args: Array) = runBlocking { // 创建代理 val agent = createCompleteAgent() // 使用对话测试 conversationTest(agent) } /** * 创建一个具有记忆和工具的完整代理。 */ fun createCompleteAgent() = agent { name("完整代理") description("一个具有记忆和工具的友好对话代理") systemPrompt(""" 你是一个有帮助、友好的助手。你的目标是与用户进行有趣的对话。 指导方针: - 保持礼貌和尊重 - 提供简洁但信息丰富的回答 - 提出后续问题以保持对话进行 - 如果你不知道某事,承认它而不是编造信息 - 记住关于用户的重要细节 - 在适当的时候使用你的工具 """.trimIndent()) // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) maxTokens(1000) } // 添加记忆 memory = memory { workingMemory(true) conversationHistory(20) } // 添加工具 tools { tool("getCurrentTime") { description("获取当前时间") parameters {} execute { val currentTime = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val formattedTime = currentTime.format(formatter) "当前时间是 $formattedTime" } } } } /** * 使用对话测试代理。 */ suspend fun conversationTest(agent: Agent) { val conversation = listOf( "你好!我的名字是小明。", "你能帮我做什么?", "现在几点了?", "谢谢!你还记得我的名字吗?", "再见!" ) for (message in conversation) { println("\n用户: $message") val response = agent.generate(message) println("代理: ${response.text}") } } } } ``` ## 理解代码 ✅ 让我们分解我们代理的关键组件: ### 代理配置 ✅ ```kotlin agent { name("完整代理") description("一个具有记忆和工具的友好对话代理") systemPrompt("...") model = deepSeek { ... } } ``` 这定义了代理的基本属性,包括其名称、描述、系统提示和它使用的 LLM。 ### 记忆配置 ✅ ```kotlin memory = memory { workingMemory(true) conversationHistory(20) } ``` 这配置了代理的记忆系统,启用工作记忆和容量为 20 条消息的对话历史。 ### 工具配置 ✅ ```kotlin tools { tool("getCurrentTime") { description("获取当前时间") parameters {} execute { // 工具实现 } } } ``` 这为代理添加了一个可以获取当前时间的工具。该工具有一个名称、描述、参数(本例中没有)和一个执行函数。 ## 下一步 ✅ 现在您已经创建了您的第一个代理,您可以: 1. 探索不同的[代理架构](../agents/architectures-kotlin.mdx) 2. 了解[记忆系统](../memory/overview.mdx) 3. 创建[自定义工具](../tools/custom-tools.mdx) 4. 构建[代理工作流](../workflows/overview-kotlin.mdx) --- title: "安装 Kastrax | 入门指南 | Kastrax 文档" description: "关于安装 Kastrax 并为您的 Kotlin 项目设置必要先决条件的指南。" --- # 安装 Kastrax ✅ [ZH] Source: https://kastrax.ai/zh/docs/getting-started/installation-kotlin 本指南将帮助您从头开始设置一个新的 Kastrax 项目。 ## 先决条件 ✅ 在开始之前,请确保您已安装以下内容: - **JDK 17 或更高版本**:Kastrax 需要 Java 17+ - **Kotlin 2.0 或更高版本**:Kastrax 使用 Kotlin 构建 - **Gradle 8.0 或更高版本**:用于构建和管理依赖项 ## 创建新项目 ✅ ### 选项 1:使用 Gradle ✅ 1. 创建一个新的 Gradle 项目: ```bash mkdir my-kastrax-project cd my-kastrax-project gradle init --type kotlin-application ``` 2. 配置您的 `build.gradle.kts` 文件: ```kotlin plugins { kotlin("jvm") version "2.1.10" kotlin("plugin.serialization") version "2.1.10" application } group = "com.example" version = "1.0-SNAPSHOT" repositories { mavenCentral() } dependencies { // Kastrax 核心 implementation("ai.kastrax:kastrax-core:0.1.0") // 根据您的需求选择可选模块 implementation("ai.kastrax:kastrax-memory-impl:0.1.0") implementation("ai.kastrax:kastrax-integrations-deepseek:0.1.0") // 测试 testImplementation(kotlin("test")) } application { mainClass.set("com.example.MainKt") } ``` ### 选项 2:使用 Kastrax CLI(即将推出)🚧 未来,您将能够使用 Kastrax CLI 创建新项目: ```bash # 这是未来功能的预览 kastrax init my-kastrax-project cd my-kastrax-project ``` ## API 密钥设置 ✅ Kastrax 支持多个 LLM 提供商。您需要为您想要使用的提供商设置 API 密钥: ### DeepSeek(推荐)✅ ```kotlin val llm = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) } ``` ### OpenAI ✅ ```kotlin val llm = openAi { apiKey("your-openai-api-key") model("gpt-4o") } ``` ### Anthropic ✅ ```kotlin val llm = anthropic { apiKey("your-anthropic-api-key") model("claude-3-opus") } ``` ## 项目结构 ✅ 一个典型的 Kastrax 项目具有以下结构: ``` my-kastrax-project/ ├── build.gradle.kts ├── settings.gradle.kts ├── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ ├── Main.kt │ │ │ ├── agents/ │ │ │ │ └── MyAgent.kt │ │ │ ├── tools/ │ │ │ │ └── MyTools.kt │ │ │ └── workflows/ │ │ │ └── MyWorkflow.kt │ │ └── resources/ │ │ └── application.conf │ └── test/ │ └── kotlin/ │ └── com/ │ └── example/ │ └── AgentTest.kt └── gradle/ └── wrapper/ ├── gradle-wrapper.jar └── gradle-wrapper.properties ``` ## 验证安装 ✅ 创建一个简单的代理来验证您的安装: ```kotlin // src/main/kotlin/com/example/Main.kt package com.example import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("测试代理") description("一个简单的测试代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } val response = myAgent.generate("你好,世界!") println(response.text) } ``` 运行您的应用程序: ```bash gradle run ``` 如果一切设置正确,您应该会看到来自您的代理的响应。 ## 依赖管理 ✅ Kastrax 是模块化的,允许您只包含您需要的组件: | 模块 | 描述 | 依赖 | |--------|-------------|------------| | kastrax-core | 核心代理功能 | `ai.kastrax:kastrax-core:0.1.0` | | kastrax-memory-api | 记忆系统 API | `ai.kastrax:kastrax-memory-api:0.1.0` | | kastrax-memory-impl | 记忆系统实现 | `ai.kastrax:kastrax-memory-impl:0.1.0` | | kastrax-rag | 检索增强生成 | `ai.kastrax:kastrax-rag:0.1.0` | | kastrax-integrations-deepseek | DeepSeek LLM 集成 | `ai.kastrax:kastrax-integrations-deepseek:0.1.0` | | kastrax-integrations-openai | OpenAI 集成 | `ai.kastrax:kastrax-integrations-openai:0.1.0` | | kastrax-integrations-anthropic | Anthropic 集成 | `ai.kastrax:kastrax-integrations-anthropic:0.1.0` | ## 配置 ✅ 您可以使用资源目录中的 `application.conf` 文件配置 Kastrax: ```hocon kastrax { # 默认 LLM 提供商 default-llm-provider = "deepseek" # 记忆配置 memory { default-storage = "sqlite" sqlite { database = "kastrax-memory.db" } } # 日志配置 logging { level = "INFO" format = "json" } } ``` ## 下一步 ✅ 现在您已经设置了 Kastrax 项目,您可以: 1. [创建您的第一个代理](./first-agent.mdx) 2. [探索示例](./examples.mdx) 3. [了解代理架构](../agents/architectures.mdx) # 安装 Kastrax [ZH] Source: https://kastrax.ai/zh/docs/getting-started/installation 本指南将帮助您从头开始设置一个新的 Kastrax 项目。 ## 前提条件 在开始之前,请确保您已安装以下内容: - **JDK 17 或更高版本**:Kastrax 需要 Java 17+ - **Kotlin 2.0 或更高版本**:Kastrax 使用 Kotlin 构建 - **Gradle 8.0 或更高版本**:用于构建和管理依赖项 ## 创建新项目 ### 方法 1:使用 Gradle 1. 创建一个新的 Gradle 项目: ```bash mkdir my-kastrax-project cd my-kastrax-project gradle init --type kotlin-application ``` 2. 配置您的 `build.gradle.kts` 文件: ```kotlin plugins { kotlin("jvm") version "2.1.10" kotlin("plugin.serialization") version "2.1.10" application } group = "com.example" version = "1.0-SNAPSHOT" repositories { mavenCentral() } dependencies { // Kastrax 核心 implementation("ai.kastrax:kastrax-core:0.1.0") // 可选模块,根据您的需求选择 implementation("ai.kastrax:kastrax-memory-impl:0.1.0") implementation("ai.kastrax:kastrax-integrations-deepseek:0.1.0") // 测试 testImplementation(kotlin("test")) } application { mainClass.set("com.example.MainKt") } ``` ### 方法 2:使用 Kastrax CLI(即将推出) 未来,您将能够使用 Kastrax CLI 创建新项目: ```bash # 这是未来功能的预览 kastrax init my-kastrax-project cd my-kastrax-project ``` ## API 密钥设置 Kastrax 支持多种 LLM 提供商。您需要为要使用的提供商设置 API 密钥: ### DeepSeek(推荐) ```kotlin val llm = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) } ``` ### OpenAI ```kotlin val llm = openAi { apiKey("your-openai-api-key") model("gpt-4o") } ``` ### Anthropic ```kotlin val llm = anthropic { apiKey("your-anthropic-api-key") model("claude-3-opus") } ``` ## 项目结构 一个典型的 Kastrax 项目具有以下结构: ``` my-kastrax-project/ ├── build.gradle.kts ├── settings.gradle.kts ├── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ ├── Main.kt │ │ │ ├── agents/ │ │ │ │ └── MyAgent.kt │ │ │ ├── tools/ │ │ │ │ └── MyTools.kt │ │ │ └── workflows/ │ │ │ └── MyWorkflow.kt │ │ └── resources/ │ │ └── application.conf │ └── test/ │ └── kotlin/ │ └── com/ │ └── example/ │ └── AgentTest.kt └── gradle/ └── wrapper/ ├── gradle-wrapper.jar └── gradle-wrapper.properties ``` ## 验证安装 创建一个简单的代理来验证您的安装: ```kotlin // src/main/kotlin/com/example/Main.kt package com.example import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("TestAgent") description("一个简单的测试代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } val response = myAgent.generate("你好,世界!") println(response.text) } ``` 运行您的应用程序: ```bash gradle run ``` 如果一切设置正确,您应该会看到来自您的代理的响应。 ## 下一步 现在您已经设置了 Kastrax 项目,您可以: 1. [创建您的第一个代理](./first-agent.mdx) 2. [探索示例](./examples.mdx) 3. [了解代理架构](../agents/architectures.mdx) --- title: "介绍 | Kastrax 文档" description: "Kastrax 是一个 Kotlin 代理框架。它通过代理、actor、记忆、工具和 RAG 等强大的原语,帮助您快速构建 AI 应用程序和功能。" --- # 关于 Kastrax ✅ [ZH] Source: https://kastrax.ai/zh/docs/getting-started/overview-kotlin Kastrax 是一个开源的 Kotlin 代理框架,旨在提供构建复杂 AI 应用程序和功能所需的原语。 ## 主要特点 ✅ Kastrax 为构建 AI 代理系统提供了一套全面的功能: - **多种代理架构**:从自适应、目标导向、层次化、反思性和创造性代理架构中选择 - **Actor 模型集成**:使用 actor 模型构建分布式、并发和弹性代理系统 - **高级记忆系统**:管理工作记忆、对话历史和语义回忆 - **灵活的工具系统**:创建和使用工具扩展代理能力 - **RAG 系统**:为基于知识的应用实现检索增强生成 - **工作流引擎**:创建复杂的多步骤代理工作流 - **多种 LLM 集成**:支持 DeepSeek、OpenAI、Anthropic 等 ## 入门 ✅ 您可以使用 Kastrax 构建具有记忆能力并可执行函数的 [AI 代理](/docs/agents/overview.mdx),使用 actor 模型创建[分布式代理系统](/docs/actor/overview.mdx),并使用 RAG 实现[基于知识的应用程序](/docs/rag/overview.mdx)。 以下是使用 Kastrax 创建代理的简单示例: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建一个简单的代理 val myAgent = agent { name("我的第一个代理") description("一个能够回答问题的有帮助的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // 使用代理 val response = myAgent.generate("什么是人工智能?") println(response.text) } ``` ## 核心组件 ✅ Kastrax 围绕几个核心组件构建: ### 代理系统 ✅ 代理系统提供了一个灵活的框架,用于创建具有不同架构的 AI 代理: ```kotlin // 创建自适应代理 val adaptiveAgent = adaptiveAgent { name("自适应助手") // 配置... } // 创建目标导向代理 val goalAgent = goalOrientedAgent { name("项目经理") // 配置... } ``` ### Actor 模型 ✅ Actor 模型支持分布式、并发的代理系统: ```kotlin // 创建 actor 系统 val system = actor.proto.ActorSystem.create() // 创建 actor val greeter = system.spawn(Props.create(GreetingActor::class.java), "greeter") // 向 actor 发送消息 greeter.tell(Greeting("世界")) ``` ### 记忆系统 ✅ 记忆系统帮助代理记住过去的交互和重要信息: ```kotlin // 配置记忆 memory = memory { workingMemory(true) conversationHistory(10) semanticMemory(true) } ``` ### 工具系统 ✅ 工具系统允许代理执行操作: ```kotlin // 向代理添加工具 tools { tool("getCurrentTime") { description("获取当前时间") parameters {} execute { "当前时间是 ${java.time.LocalTime.now()}" } } } ``` ### RAG 系统 ✅ RAG 系统支持基于知识的应用: ```kotlin // 配置 RAG rag { retriever(retriever) contextBuilder(contextBuilder) maxTokens(3000) } ``` ## 下一步 ✅ 准备深入了解?以下是接下来的步骤: 1. [安装指南](/docs/getting-started/installation.mdx):在您的项目中设置 Kastrax 2. [第一个代理教程](/docs/getting-started/first-agent.mdx):创建您的第一个 Kastrax 代理 3. [代理架构](/docs/agents/architectures.mdx):了解不同的代理类型 4. [Actor 模型](/docs/actor/overview.mdx):理解分布式 actor 系统 # Kastrax 概述 [ZH] Source: https://kastrax.ai/zh/docs/getting-started/overview Kastrax 是一个强大的 AI Agent 框架,使用 Kotlin 构建,旨在创建具有高级功能的智能代理。它将 Actor 模型与现代 AI 代理架构相结合,提供了一个全面的平台,用于构建 AI 驱动的应用程序。 ## Kastrax 是什么? Kastrax 是一个开源框架,使开发者能够构建具有以下特性的 AI 代理: - **多种代理架构**:自适应、目标导向、层次化和反思型代理 - **分布式 Actor 系统**:基于 kactor 库,用于构建可扩展、弹性的应用程序 - **高级内存管理**:工作内存、对话历史和语义回忆 - **工具集成**:轻松扩展代理的自定义工具和功能 - **RAG 系统**:内置检索增强生成,用于基于知识的应用程序 - **工作流引擎**:创建具有状态管理的复杂代理工作流 - **多种 LLM 集成**:支持 DeepSeek、OpenAI、Anthropic 等 ## 核心特性 ### 代理架构 Kastrax 提供了几种代理架构,以适应不同的用例: - **自适应代理**:根据用户偏好和反馈调整行为 - **目标导向代理**:专注于通过任务规划实现特定目标 - **层次化代理**:将复杂任务组织成可管理的子任务 - **反思型代理**:通过反思自我监控和提高性能 ### Actor 模型集成 Kastrax 与 kactor 库集成,提供: - **分布式处理**:跨多台机器扩展 - **基于消息传递的并发**:弹性、非阻塞通信 - **监督层次结构**:容错和恢复 - **位置透明性**:无缝的本地和远程 actor 通信 ### 内存系统 Kastrax 中的内存系统提供: - **工作内存**:系统指令和用户信息 - **对话历史**:最近消息跟踪 - **语义回忆**:检索相关的过去信息 - **内存处理器**:上下文管理和优化 ### 工具系统 Kastrax 包含一个灵活的工具系统: - **内置工具**:文件操作、Web 请求、数据处理等 - **自定义工具创建**:轻松扩展自己的工具 - **工具验证**:使用 Zod 进行参数验证 - **工具链**:组合工具进行复杂操作 ## 入门指南 要开始使用 Kastrax,请按照以下步骤操作: 1. [安装](./installation.mdx):设置您的 Kastrax 项目 2. [第一个代理](./first-agent.mdx):创建您的第一个 Kastrax 代理 3. [运行示例](./examples.mdx):探索示例应用程序 ## 使用场景 Kastrax 适用于多种应用场景: - **对话式 AI**:构建复杂的聊天机器人和助手 - **知识工作者**:创建能够研究、分析和总结信息的代理 - **流程自动化**:使用智能代理自动化复杂工作流 - **分布式系统**:构建弹性、可扩展的 AI 应用程序 - **多代理系统**:创建协作代理网络 ## 下一步 在熟悉基础知识后,探索这些领域: - [代理架构](../agents/architectures.mdx):了解不同的代理类型 - [内存系统](../memory/overview.mdx):了解代理内存如何工作 - [工具集成](../tools/overview.mdx):使用工具扩展您的代理 - [Actor 模型](../actor/overview.mdx):了解分布式 actor 系统 --- title: "本地项目结构 | 入门指南 | Kastrax 文档" description: 关于在 Kastrax 中组织文件夹和文件的指南,包括最佳实践和推荐结构。 --- import { FileTree } from 'nextra/components'; # 项目结构 ✅ [ZH] Source: https://kastrax.ai/zh/docs/getting-started/project-structure 本页面提供了在 Kastrax 中组织文件夹和文件的指南。Kastrax 是一个模块化框架,您可以单独或一起使用任何模块。 您可以将所有内容写在一个文件中(如我们在快速入门中所示),或者将每个代理、工具和工作流分离到它们自己的文件中。 我们不强制特定的文件夹结构,但我们确实推荐一些最佳实践,CLI 将使用合理的结构搭建项目。 ## 使用 CLI ✅ `kastrax init` 是一个交互式 CLI,允许您: - **为 Kastrax 文件选择目录**:指定您希望放置 Kastrax 文件的位置(默认为 `src/kastrax`)。 - **选择要安装的组件**:选择您希望包含在项目中的组件: - 代理 - 工具 - 工作流 - **选择默认的 LLM 提供商**:从支持的提供商中选择,如 OpenAI、Anthropic、Groq、Google 或 Cerebras。 - **包含示例代码**:决定是否包含示例代码以帮助您入门。 - **安装 Kastrax 文档 MCP 服务器**:使您的 AI IDE 成为 Kastrax 专家 ### 示例项目结构 假设您选择所有组件并包含示例代码,您的项目结构将如下所示: {/* ``` root/ ├── src/ │ └── kastrax/ │ ├── agents/ │ │ └── index.ts │ ├── tools/ │ │ └── index.ts │ ├── workflows/ │ │ └── index.ts │ ├── index.ts ├── .env ``` */} ### 顶级文件夹 | 文件夹 | 描述 | | ---------------------- | ------------------------------------ | | `src/kastrax` | 核心应用程序文件夹 | | `src/kastrax/agents` | 代理配置和定义 | | `src/kastrax/tools` | 自定义工具定义 | | `src/kastrax/workflows` | 工作流定义 | ### 顶级文件 | 文件 | 描述 | | --------------------- | ---------------------------------- | | `src/kastrax/index.ts` | Kastrax 的主配置文件 | | `.env` | 环境变量 | --- title: "介绍 | Kastrax 文档" description: "Kastrax 是一个 Kotlin 代理框架。它通过代理、actor、记忆、工具和 RAG 等强大的原语,帮助您快速构建 AI 应用程序和功能。" --- # 关于 Kastrax ✅ [ZH] Source: https://kastrax.ai/zh/docs Kastrax 是一个开源的 Kotlin 代理框架。 它旨在提供构建 AI 应用程序和功能所需的原语。 您可以使用 Kastrax 构建具有记忆能力并可执行函数的 [AI 代理](/docs/agents/overview.mdx) ✅,使用 actor 模型创建[分布式代理系统](/docs/actor/overview.mdx) ✅,并使用 RAG 实现[基于知识的应用程序](/docs/rag/overview.mdx) ✅。 主要特点包括: - **多种代理架构**:从自适应、目标导向、层次化、反思性和创造性代理架构中选择 - **Actor 模型集成**:使用 actor 模型构建分布式、并发和弹性代理系统 - **高级记忆系统**:管理工作记忆、对话历史和语义回忆 - **灵活的工具系统**:创建和使用工具扩展代理能力 - **RAG 系统**:为基于知识的应用实现检索增强生成 - **工作流引擎**:创建复杂的多步骤代理工作流 - **多种 LLM 集成**:支持 DeepSeek、OpenAI、Anthropic 等 ## 快速开始 ✅ ### 安装 ✅ 使用 Gradle 安装 Kastrax: ```kotlin dependencies { implementation("ai.kastrax:kastrax-core:0.1.0") implementation("ai.kastrax:kastrax-integrations-deepseek:0.1.0") // 可选 } ``` ### 基本用法 ✅ ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking // 创建代理 val myAgent = agent { name = "助手" instructions = "你是一个有帮助的助手。" model = deepSeek( model = DeepSeekModel.DEEPSEEK_CHAT, apiKey = "your-deepseek-api-key" ) // 添加工具 tools { tool { id = "calculator" name = "计算器" description = "执行数学计算" // 定义输入/输出模式和执行逻辑 // ... } } } // 使用代理 fun main() = runBlocking { val response = myAgent.generate("你好,请介绍一下自己") println(response.text) } ``` ## 核心功能 ✅ ### 代理系统 ✅ Kastrax 提供了多种代理架构,包括: - **自适应代理**:根据用户偏好和反馈调整行为 - **目标导向代理**:专注于通过任务规划实现特定目标 - **层次化代理**:将复杂任务组织成可管理的子任务 - **反思型代理**:通过反思自我监控和提高性能 - **创造性代理**:具有增强能力,生成创造性内容 [了解更多关于代理系统的信息 →](/docs/agents/overview.mdx) ### Actor 模型 ✅ Kastrax 与 kactor 库集成,提供: - **分布式处理**:跨多台机器扩展 - **基于消息传递的并发**:弹性、非阻塞通信 - **监督层次结构**:容错和恢复 - **位置透明性**:无缝的本地和远程 actor 通信 [了解更多关于 Actor 模型的信息 →](/docs/actor/overview.mdx) ### 记忆系统 ✅ Kastrax 的记忆系统提供: - **工作记忆**:系统指令和用户信息 - **对话历史**:最近消息跟踪 - **语义回忆**:检索相关的过去信息 - **记忆处理器**:上下文管理和优化 [了解更多关于记忆系统的信息 →](/docs/memory/overview.mdx) ### 工具系统 ✅ Kastrax 包含一个灵活的工具系统: - **内置工具**:文件操作、Web 请求、数据处理等 - **自定义工具创建**:轻松扩展自己的工具 - **工具验证**:使用 Zod 进行参数验证 - **工具链**:组合工具进行复杂操作 [了解更多关于工具系统的信息 →](/docs/tools/overview.mdx) ### RAG 系统 ✅ Kastrax 的 RAG 系统提供: - **文档处理**:加载、分块和转换文档 - **嵌入**:将文本转换为向量表示 - **向量存储**:高效存储和检索向量 - **检索**:基于查询查找相关信息 - **重排序**:通过额外的排序提高检索质量 [了解更多关于 RAG 系统的信息 →](/docs/rag/overview.mdx) ## 下一步 ✅ - [安装 Kastrax](/docs/getting-started/installation-kotlin.mdx) - [创建您的第一个代理](/docs/getting-started/first-agent-kotlin.mdx) - [探索代理架构](/docs/agents/architectures-kotlin.mdx) - [了解工具系统](/docs/tools/overview-kotlin.mdx) - [使用 RAG 系统](/docs/rag/overview.mdx) # 记忆实现 ✅ [ZH] Source: https://kastrax.ai/zh/docs/memory/implementations Kastrax 为记忆提供了多种存储后端,让您可以为应用程序的需求选择合适的解决方案。本指南解释了可用的实现及其配置方法。 ## 记忆存储后端 ✅ ### 内存存储 ✅ 默认存储是内存存储,适用于简单应用程序和开发: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.storage.InMemoryStorage import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("内存存储代理") description("使用内存存储的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置带内存存储的记忆 memory = memory { workingMemory(true) conversationHistory(20) // 显式设置内存存储(这是默认设置) storage(InMemoryStorage()) } } // 使用代理 val response = myAgent.generate("你好,世界!") println(response.text) } ``` ### SQLite 存储 ✅ 对于单用户应用程序的持久存储,您可以使用 SQLite: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.storage.SQLiteStorage import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("SQLite存储代理") description("使用SQLite存储的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置带SQLite存储的记忆 memory = memory { workingMemory(true) conversationHistory(20) // 设置SQLite存储 storage(SQLiteStorage("memory.db")) } } // 使用代理 val response = myAgent.generate("你好,世界!") println(response.text) } ``` ### Redis 存储 ✅ 对于分布式应用程序,您可以使用 Redis: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.storage.RedisStorage import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("Redis存储代理") description("使用Redis存储的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置带Redis存储的记忆 memory = memory { workingMemory(true) conversationHistory(20) // 设置Redis存储 storage(RedisStorage( host = "localhost", port = 6379, password = "password" // 可选 )) } } // 使用代理 val response = myAgent.generate("你好,世界!") println(response.text) } ``` ### PostgreSQL 存储 ✅ 对于企业应用程序,您可以使用 PostgreSQL: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.storage.PostgreSQLStorage import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("PostgreSQL存储代理") description("使用PostgreSQL存储的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置带PostgreSQL存储的记忆 memory = memory { workingMemory(true) conversationHistory(20) // 设置PostgreSQL存储 storage(PostgreSQLStorage( url = "jdbc:postgresql://localhost:5432/kastrax", username = "postgres", password = "password" )) } } // 使用代理 val response = myAgent.generate("你好,世界!") println(response.text) } ``` ## 自定义存储实现 ✅ 您可以通过实现 `MemoryStorage` 接口创建自定义存储实现: ```kotlin import ai.kastrax.core.memory.storage.MemoryStorage import ai.kastrax.core.memory.Message import ai.kastrax.core.memory.MemoryPriority class CustomStorage : MemoryStorage { private val messages = mutableListOf() override suspend fun addMessage(message: Message) { messages.add(message) } override suspend fun getMessages(limit: Int): List { return messages.takeLast(limit) } override suspend fun addMemory(content: String, metadata: Map, priority: MemoryPriority): String { val memory = Message( role = "system", content = content, timestamp = System.currentTimeMillis(), metadata = metadata ) messages.add(memory) return memory.id } override suspend fun getMemory(id: String): Message? { return messages.find { it.id == id } } override suspend fun getAllMemories(): List { return messages.toList() } override suspend fun forgetMemory(id: String) { messages.removeIf { it.id == id } } override suspend fun clear() { messages.clear() } } // 使用自定义存储 memory = memory { storage(CustomStorage()) } ``` ## 向量存储实现 ✅ 对于语义记忆,Kastrax 支持多种向量存储实现: ### 内存向量存储 ✅ ```kotlin import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.vectorstore.InMemoryVectorStore memory = memory { semanticMemory(true) semanticMemoryModel("all-MiniLM-L6-v2") semanticMemoryVectorStore(InMemoryVectorStore()) } ``` ### Chroma 向量存储 ✅ ```kotlin import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.vectorstore.ChromaVectorStore memory = memory { semanticMemory(true) semanticMemoryModel("all-MiniLM-L6-v2") semanticMemoryVectorStore(ChromaVectorStore( collectionName = "agent_memories", url = "http://localhost:8000" )) } ``` ### FAISS 向量存储 ✅ ```kotlin import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.vectorstore.FAISSVectorStore memory = memory { semanticMemory(true) semanticMemoryModel("all-MiniLM-L6-v2") semanticMemoryVectorStore(FAISSVectorStore( indexPath = "faiss_index", dimensions = 384 )) } ``` ### Pinecone 向量存储 ✅ ```kotlin import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.vectorstore.PineconeVectorStore memory = memory { semanticMemory(true) semanticMemoryModel("all-MiniLM-L6-v2") semanticMemoryVectorStore(PineconeVectorStore( apiKey = "your-pinecone-api-key", environment = "us-west1-gcp", index = "agent_memories" )) } ``` ## 选择合适的实现 ✅ 以下是选择合适的记忆实现的一些指导原则: | 实现 | 最适合 | 优点 | 缺点 | |----------------|----------|------|------| | 内存 | 开发,简单应用 | 快速,无需设置 | 不持久,受 RAM 限制 | | SQLite | 单用户应用,桌面 | 持久,设置简单 | 并发有限 | | Redis | 多用户应用,网络 | 快速,可扩展 | 需要 Redis 服务器 | | PostgreSQL | 企业应用 | 强大,可扩展 | 设置更复杂 | 对于向量存储: | 向量存储 | 最适合 | 优点 | 缺点 | |--------------|----------|------|------| | 内存 | 开发,测试 | 快速,无需设置 | 不持久,受 RAM 限制 | | Chroma | 生产,中小规模 | 易于使用,性能良好 | 需要 Chroma 服务器 | | FAISS | 大规模,离线 | 非常快,高效 | 设置更复杂 | | Pinecone | 基于云,大规模 | 托管服务,可扩展 | 付费服务 | ## 示例:完整记忆配置 ✅ 以下是同时包含常规记忆存储和向量存储的完整示例: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.storage.SQLiteStorage import ai.kastrax.core.memory.vectorstore.ChromaVectorStore import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { val myAgent = agent { name("完整记忆代理") description("具有完整记忆配置的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置记忆 memory = memory { // 基本记忆配置 workingMemory(true) conversationHistory(20) semanticMemory(true) // 存储配置 storage(SQLiteStorage("memory.db")) // 向量存储配置 semanticMemoryModel("all-MiniLM-L6-v2") semanticMemoryVectorStore(ChromaVectorStore( collectionName = "agent_memories", url = "http://localhost:8000" )) // 记忆处理器 summarizer(true) contextOptimizer(true) } } // 使用代理 val response = myAgent.generate("你好,世界!") println(response.text) } ``` ## 下一步 ✅ 现在您已经了解了记忆实现,您可以: 1. 了解[工作记忆](./working-memory.mdx) 2. 探索[语义回忆](./semantic-recall.mdx) 3. 配置[记忆处理器](./memory-processors.mdx) # 记忆处理器 ✅ [ZH] Source: https://kastrax.ai/zh/docs/memory/memory-processors 记忆处理器允许您在将从记忆中检索的消息添加到代理的上下文窗口并发送到 LLM _之前_ 修改它们。这对于管理上下文大小、过滤内容和优化性能非常有用。 处理器根据您的记忆配置(例如,对话历史、语义记忆)对检索的消息进行操作。它们**不**影响新的传入用户消息。 ## 内置处理器 ✅ Kastrax 提供了几个内置处理器: ### 总结器 ✅ 总结器压缩长对话以节省令牌空间。在处理冗长的对话历史时特别有用。 ```kotlin memory = memory { // 启用总结器 summarizer(true) // 配置何时应该进行总结 summarizerThreshold(10) // 10 条消息后进行总结 // 配置总结模型(可选) summarizerModel(deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.3) // 较低的温度以获得更事实性的总结 }) } ``` ### 上下文优化器 ✅ 上下文优化器为当前上下文选择最相关的记忆,确保在令牌限制内包含最重要的信息。 ```kotlin memory = memory { // 启用上下文优化器 contextOptimizer(true) // 配置令牌限制 contextTokenLimit(2000) // 将上下文限制为 2000 个令牌 // 配置相关性阈值(可选) contextRelevanceThreshold(0.6) // 只包含相关性 > 0.6 的记忆 } ``` ### 重要性评估器 ✅ 重要性评估器评估新信息的重要性,以确定是否应将其存储在记忆中。 ```kotlin memory = memory { // 启用重要性评估器 importanceEvaluator(true) // 配置重要性阈值 importanceThreshold(0.6) // 只存储重要性 > 0.6 的记忆 } ``` ### 令牌限制器 ✅ 此处理器防止因超出 LLM 的上下文窗口限制而导致的错误。它计算检索的记忆消息中的令牌数,并删除最旧的消息,直到总计数低于指定限制。 ```kotlin memory = memory { // 配置令牌限制 tokenLimit(4000) // 将总令牌限制为 4000 // 配置令牌计数方法(可选) tokenCounter { text -> // 自定义令牌计数逻辑 text.split(" ").size // 简单的单词计数作为示例 } } ``` ## 创建自定义处理器 ✅ 您可以创建自定义记忆处理器来为您的应用程序实现专门的逻辑: ```kotlin import ai.kastrax.core.memory.MemoryProcessor import ai.kastrax.core.memory.Message class CustomMemoryProcessor : MemoryProcessor { override fun process(messages: List, query: String): List { // 您的自定义处理逻辑在这里 // 示例:过滤掉包含某些关键词的消息 return messages.filter { message -> !message.content.contains("机密", ignoreCase = true) } } } // 使用自定义处理器 memory = memory { // 添加您的自定义处理器 addProcessor(CustomMemoryProcessor()) } ``` ## 组合处理器 ✅ 您可以组合多个处理器来创建复杂的记忆处理管道: ```kotlin memory = memory { // 启用内置处理器 summarizer(true) contextOptimizer(true) importanceEvaluator(true) // 添加自定义处理器 addProcessor(CustomMemoryProcessor()) // 配置处理顺序(可选) processingOrder(listOf( "Summarizer", "CustomMemoryProcessor", "ContextOptimizer", "TokenLimiter" )) } ``` ## 示例:高级记忆配置 ✅ 以下是具有高级记忆处理的代理的完整示例: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建具有高级记忆处理的代理 val advancedAgent = agent { name("高级记忆代理") description("具有高级记忆处理的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置带处理器的记忆 memory = memory { // 基本记忆配置 workingMemory(true) conversationHistory(20) semanticMemory(true) // 记忆处理器 summarizer(true) summarizerThreshold(15) contextOptimizer(true) contextTokenLimit(3000) importanceEvaluator(true) importanceThreshold(0.5) // 用于过滤敏感信息的自定义处理器 addProcessor(object : MemoryProcessor { override fun process(messages: List, query: String): List { return messages.map { message -> // 编辑敏感信息 val redactedContent = message.content .replace(Regex("\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b"), "[信用卡]") .replace(Regex("\\b\\d{3}-\\d{2}-\\d{4}\\b"), "[身份证]") // 创建带有编辑内容的新消息 Message( role = message.role, content = redactedContent, timestamp = message.timestamp, metadata = message.metadata ) } } }) } } // 使用代理 val response = advancedAgent.generate("告诉我我们之前关于量子计算的对话") println(response.text) } ``` ## 最佳实践 ✅ 1. **从简单开始**:在创建自定义处理器之前,先使用内置处理器。 2. **监控性能**:记忆处理可能影响响应时间,因此在生产环境中监控性能。 3. **彻底测试**:使用各种对话场景测试您的处理器,以确保它们按预期行为。 4. **考虑令牌限制**:始终包含令牌限制以防止上下文窗口错误。 5. **顺序很重要**:处理器的顺序可能显著影响最终上下文。通常,总结应该在优化之前进行。 ## 下一步 ✅ 现在您已经了解了记忆处理器,您可以: 1. 了解[工作记忆](./working-memory.mdx) 2. 探索[语义回忆](./semantic-recall.mdx) 3. 实现[自定义记忆存储](./implementations.mdx) # 记忆系统概述 ✅ [ZH] Source: https://kastrax.ai/zh/docs/memory/overview Kastrax 记忆系统为代理提供了记住过去交互、存储重要信息和检索相关上下文的能力。本指南解释了记忆系统的架构以及如何有效地使用它。 ## 记忆系统架构 ✅ Kastrax 记忆系统由几个组件组成: 1. **记忆类型**:用于不同目的的不同类型的记忆 2. **记忆管理器**:协调记忆操作 3. **记忆处理器**:处理和优化记忆内容 4. **记忆存储**:存储记忆数据的后端 ## 记忆类型 ✅ Kastrax 支持几种类型的记忆: ### 工作记忆 ✅ 工作记忆包含系统指令、用户信息和其他应该包含在每个提示中的上下文。它就像代理的"RAM",保存着立即相关的信息。 ```kotlin memory { workingMemory(true) workingMemoryContent("用户的名字是小明,他喜欢简洁的回答。") } ``` ### 对话历史 ✅ 对话历史存储用户和代理之间的最近消息。它为当前对话提供了即时上下文。 ```kotlin memory { conversationHistory(10) // 存储最后 10 条消息 } ``` ### 语义记忆 ✅ 语义记忆存储代理可能需要稍后回忆的重要信息。它可以通过语义相似性进行搜索,使代理能够根据当前上下文检索相关信息。 ```kotlin memory { semanticMemory(true) semanticMemoryModel("all-MiniLM-L6-v2") // 用于语义搜索的嵌入模型 } ``` ### 情景记忆 ✅ 情景记忆存储完整的交互序列或"情景"。它帮助代理理解其与用户随时间的交互历史。 ```kotlin memory { episodicMemory(true) episodicMemoryCapacity(50) // 存储最多 50 个情景 } ``` ## 记忆配置 ✅ 以下是如何为代理配置记忆系统: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.core.memory.MemoryPriority import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createAgentWithMemory() = agent { name("记忆代理") description("具有记忆能力的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置记忆 memory = memory { // 启用工作记忆 workingMemory(true) workingMemoryContent("用户喜欢技术性解释。") // 配置对话历史 conversationHistory(15) // 记住最后 15 条消息 // 启用语义记忆 semanticMemory(true) semanticMemoryModel("all-MiniLM-L6-v2") // 配置记忆处理器 summarizer(true) // 总结长对话 contextOptimizer(true) // 优化上下文以提高令牌效率 } } fun main() = runBlocking { val agent = createAgentWithMemory() // 测试代理 println(agent.generate("告诉我关于量子计算的信息").text) // 添加记忆 agent.memory.add("用户是一位在量子力学领域有专业知识的物理学家", MemoryPriority.HIGH) // 使用新记忆再次生成 println(agent.generate("告诉我更多关于量子纠缠的信息").text) } ``` ## 记忆操作 ✅ ### 添加记忆 ✅ 您可以通过编程方式向代理添加记忆: ```kotlin // 添加简单记忆 agent.memory.add("用户对机器学习感兴趣", MemoryPriority.MEDIUM) // 添加结构化记忆 agent.memory.add( content = "用户最喜欢的编程语言是 Kotlin", metadata = mapOf( "category" to "preferences", "topic" to "programming" ), priority = MemoryPriority.MEDIUM ) ``` ### 检索记忆 ✅ 您可以基于语义相似性检索记忆: ```kotlin // 检索与查询相关的记忆 val memories = agent.memory.retrieve("编程语言", 3) // 获取前 3 个匹配项 // 打印检索到的记忆 memories.forEach { memory -> println("记忆: ${memory.content}") println("相关性: ${memory.relevance}") println("创建时间: ${memory.createdAt}") println() } ``` ### 遗忘记忆 ✅ 您可以删除不再需要的记忆: ```kotlin // 通过 ID 遗忘特定记忆 agent.memory.forget(memoryId) // 通过过滤器遗忘记忆 agent.memory.forgetWhere { memory -> memory.metadata["category"] == "outdated" } ``` ## 记忆存储后端 ✅ Kastrax 支持多种记忆存储后端: ### 内存存储 ✅ 默认存储是内存存储,适用于简单应用: ```kotlin memory { storage(InMemoryStorage()) } ``` ### SQLite 存储 ✅ 对于持久存储,您可以使用 SQLite: ```kotlin memory { storage(SQLiteStorage("memory.db")) } ``` ### Redis 存储 ✅ 对于分布式应用,您可以使用 Redis: ```kotlin memory { storage(RedisStorage( host = "localhost", port = 6379, password = "password" )) } ``` ## 记忆处理器 ✅ 记忆处理器优化记忆的存储和检索方式: ### 总结器 ✅ 总结器压缩长对话以节省令牌空间: ```kotlin memory { summarizer(true) summarizerThreshold(10) // 10 条消息后进行总结 } ``` ### 上下文优化器 ✅ 上下文优化器为当前上下文选择最相关的记忆: ```kotlin memory { contextOptimizer(true) contextTokenLimit(2000) // 将上下文限制为 2000 个令牌 } ``` ### 重要性评估器 ✅ 重要性评估器评估新信息的重要性: ```kotlin memory { importanceEvaluator(true) importanceThreshold(0.6) // 只存储重要性 > 0.6 的记忆 } ``` ## 高级记忆用法 ✅ ### 记忆标签 ✅ 您可以为记忆添加标签,以便更轻松地组织和检索: ```kotlin // 添加带标签的记忆 agent.memory.add( content = "用户提到他有一只名叫旺财的狗", tags = listOf("宠物", "个人信息"), priority = MemoryPriority.MEDIUM ) // 通过标签检索记忆 val petMemories = agent.memory.retrieveByTags(listOf("宠物"), 5) ``` ### 记忆过期 ✅ 您可以设置记忆在一定时间后过期: ```kotlin // 添加 24 小时后过期的记忆 agent.memory.add( content = "用户当前正在处理项目截止日期", expiresIn = Duration.ofHours(24), priority = MemoryPriority.HIGH ) ``` ### 记忆反思 ✅ 您可以启用定期反思以整合和组织记忆: ```kotlin memory { reflection(true) reflectionFrequency(10) // 每 10 次交互后进行反思 } ``` ## 示例:完整记忆配置 ✅ 以下是一个复杂记忆配置的完整示例: ```kotlin memory = memory { // 基本记忆类型 workingMemory(true) conversationHistory(20) semanticMemory(true) episodicMemory(true) // 存储配置 storage(SQLiteStorage("agent_memory.db")) // 用于语义搜索的嵌入模型 semanticMemoryModel("all-MiniLM-L6-v2") // 记忆处理器 summarizer(true) summarizerThreshold(15) contextOptimizer(true) contextTokenLimit(3000) importanceEvaluator(true) importanceThreshold(0.5) // 高级功能 reflection(true) reflectionFrequency(20) // 记忆保留策略 retentionPolicy { defaultRetention(Duration.ofDays(30)) retentionByPriority(MemoryPriority.LOW, Duration.ofDays(7)) retentionByPriority(MemoryPriority.MEDIUM, Duration.ofDays(30)) retentionByPriority(MemoryPriority.HIGH, Duration.ofDays(90)) } } ``` ## 下一步 ✅ 现在您已经了解了记忆系统,您可以: 1. 了解[记忆实现](./implementations.mdx) 2. 探索[记忆查询](./querying.mdx) 3. 实现[自定义记忆处理器](./custom-processors.mdx) # 语义回忆 ✅ [ZH] Source: https://kastrax.ai/zh/docs/memory/semantic-recall 如果你问你的朋友上周末做了什么,他们会在记忆中搜索与"上周末"相关的事件,然后告诉你他们做了什么。这有点像 Kastrax 中语义回忆的工作方式。 ## 语义回忆的工作原理 ✅ 语义回忆是基于 RAG 的搜索,帮助代理在消息不再位于[最近对话历史](./overview.mdx#conversation-history)中时,在更长的交互中维持上下文。 它使用消息的向量嵌入进行相似性搜索,与各种向量存储集成,并具有围绕检索消息的可配置上下文窗口。
展示 Kastrax 记忆语义回忆的图表 当启用时,新消息用于查询向量数据库以获取语义相似的消息。 从 LLM 获得响应后,所有新消息(用户、助手和工具调用/结果)都会插入到向量数据库中,以便在后续交互中回忆。 ## 快速开始 ✅ 以下是设置带有语义回忆的代理的最小示例: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建启用语义回忆的代理 val myAgent = agent { name("带语义回忆的代理") description("使用语义回忆的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置启用语义回忆的记忆 memory = memory { conversationHistory(10) // 记住最后 10 条消息 semanticMemory(true) // 启用语义回忆 semanticMemoryModel("all-MiniLM-L6-v2") // 用于语义搜索的嵌入模型 } } // 使用代理 val response = myAgent.generate("告诉我关于量子计算的信息") println(response.text) } ``` ## 配置选项 ✅ 语义回忆可以通过几个选项进行配置: ```kotlin memory = memory { // 启用语义记忆 semanticMemory(true) // 指定嵌入模型 semanticMemoryModel("all-MiniLM-L6-v2") // 设置要检索的相似记忆数量 semanticMemoryTopK(5) // 设置相似度阈值(0.0 到 1.0) semanticMemorySimilarityThreshold(0.7) // 配置向量存储 semanticMemoryVectorStore(InMemoryVectorStore()) // 或使用持久存储 // semanticMemoryVectorStore(ChromaVectorStore("memory_db")) } ``` ## 语义回忆如何增强对话 ✅ 语义回忆通过几种方式帮助代理维持上下文: 1. **长期记忆**:代理可以回忆对话中更早的信息,超出最近历史限制。 2. **上下文相关性**:基于与当前查询的语义相似性,只回忆最相关的过去消息。 3. **知识持久性**:即使对话通过不同主题演变,重要信息也会被保留。 4. **自然对话流**:用户不需要重复已经分享的信息,创造更自然的体验。 ## 示例:长对话代理 ✅ 以下是使用语义回忆在长对话中维持上下文的代理的完整示例: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建带有语义回忆的代理 val longConversationAgent = agent { name("长对话代理") description("在长对话中维持上下文的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置记忆 memory = memory { conversationHistory(5) // 只保留 5 条最近消息 semanticMemory(true) // 启用语义回忆 semanticMemoryModel("all-MiniLM-L6-v2") semanticMemoryTopK(3) // 检索前 3 个相似记忆 } } // 模拟长对话 val conversation = listOf( "你好,我叫小明,我是一名从事量子计算的物理学家。", "我特别对量子错误纠正感兴趣。", "今天天气怎么样?", "回到量子计算,量子错误纠正的最新发展是什么?", "你还记得我从事什么领域吗?", "我叫什么名字来着?" ) // 运行对话 for (message in conversation) { println("\n用户: $message") val response = longConversationAgent.generate(message) println("代理: ${response.text}") } } ``` 在这个示例中,即使对话历史只保留最近的 5 条消息,当在对话后期被问到时,代理仍然可以回忆起用户的名字是小明,以及他从事量子计算工作。 ## 向量存储 ✅ Kastrax 支持多种用于语义回忆的向量存储: ### 内存向量存储 ✅ ```kotlin memory = memory { semanticMemory(true) semanticMemoryVectorStore(InMemoryVectorStore()) } ``` ### Chroma 向量存储 ✅ ```kotlin memory = memory { semanticMemory(true) semanticMemoryVectorStore(ChromaVectorStore( collectionName = "agent_memories", url = "http://localhost:8000" )) } ``` ### FAISS 向量存储 ✅ ```kotlin memory = memory { semanticMemory(true) semanticMemoryVectorStore(FAISSVectorStore( indexPath = "faiss_index", dimensions = 384 )) } ``` ## 最佳实践 ✅ 1. **平衡历史和回忆**:使用语义回忆时,使用较小的对话历史(5-10 条消息)以避免冗余。 2. **选择合适的嵌入模型**:选择平衡性能和质量的嵌入模型,适合您的用例。 3. **调整相似度阈值**:根据您的需求调整相似度阈值 - 较低的值会回忆更多,但可能包含相关性较低的信息。 4. **持久存储**:对于生产应用,使用持久向量存储,如 Chroma 或 FAISS。 5. **与工作记忆结合**:将语义回忆与工作记忆结合使用,以获得最佳结果。 ## 下一步 ✅ 现在您已经了解了语义回忆,您可以: 1. 了解[工作记忆](./working-memory.mdx) 2. 探索[记忆处理器](./memory-processors.mdx) 3. 实现[自定义记忆存储](./implementations.mdx) # 工作记忆 ✅ [ZH] Source: https://kastrax.ai/zh/docs/memory/working-memory 虽然[对话历史](/docs/memory/overview#conversation-history)和[语义回忆](./semantic-recall.mdx)帮助代理记住对话,但工作记忆允许它们在线程内的交互中维持关于用户的持久信息。 可以将其视为代理的活跃思想或草稿本 - 它们保持可用的关于用户或任务的关键信息。这类似于一个人在对话中自然地记住某人的名字、偏好或重要细节的方式。 这对于维护始终相关且应始终对代理可用的持续状态非常有用。 ## 快速开始 ✅ 以下是设置带有工作记忆的代理的最小示例: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建启用工作记忆的代理 val myAgent = agent { name("带工作记忆的代理") description("使用工作记忆的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置启用工作记忆的记忆 memory = memory { workingMemory(true) workingMemoryContent("用户的名字是小明,他喜欢简洁的回答。") } } // 使用代理 val response = myAgent.generate("告诉我关于量子计算的信息") println(response.text) } ``` ## 工作记忆的工作原理 ✅ 工作记忆包含在发送给模型的每个提示中。它是一种提供在消息之间不变的持久上下文的方式。 工作记忆内容通常添加到系统提示中,或作为对话历史开始处的单独消息。这确保模型在生成响应时始终能够访问这些信息。 ## 向工作记忆添加信息 ✅ 您可以通过几种方式向工作记忆添加信息: ### 1. 在代理创建期间 ✅ ```kotlin memory = memory { workingMemory(true) workingMemoryContent("用户:小明,偏好:技术解释,简洁回答") } ``` ### 2. 以编程方式更新工作记忆 ✅ ```kotlin // 使用新信息更新工作记忆 agent.memory.updateWorkingMemory("用户:小明,偏好:技术解释,简洁回答,示例:首选") ``` ### 3. 结构化工作记忆 ✅ 对于更复杂的场景,您可以使用结构化工作记忆: ```kotlin // 创建结构化工作记忆 val userProfile = mapOf( "name" to "小明", "preferences" to listOf("技术解释", "简洁回答"), "expertise" to "量子物理初学者" ) // 转换为字符串表示 val workingMemoryContent = "用户资料:\n" + "姓名:${userProfile["name"]}\n" + "偏好:${(userProfile["preferences"] as List).joinToString("、")}\n" + "专业知识:${userProfile["expertise"]}" // 更新工作记忆 agent.memory.updateWorkingMemory(workingMemoryContent) ``` ## 最佳实践 ✅ 1. **保持简洁**:工作记忆应该只包含在每次交互中都需要可用的最重要信息。 2. **结构化格式**:为工作记忆使用清晰的结构化格式,使模型易于解析。 3. **选择性更新**:只有当您有新的、应在整个对话中持续存在的重要信息时,才更新工作记忆。 4. **与其他记忆类型结合**:将工作记忆与对话历史和语义回忆结合使用,以获得最佳结果。 ## 示例:用户偏好代理 ✅ 以下是使用工作记忆记住用户偏好的代理的完整示例: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.memory.memory import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建带有工作记忆的代理 val preferencesAgent = agent { name("偏好代理") description("记住用户偏好的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置记忆 memory = memory { workingMemory(true) conversationHistory(10) } } // 初始交互 val response1 = preferencesAgent.generate("你好,我叫小明,我喜欢简洁的技术解释。") println("代理:${response1.text}") // 提取偏好并更新工作记忆 val preferences = "用户:小明,偏好:技术解释,简洁回答" preferencesAgent.memory.updateWorkingMemory(preferences) // 下一次交互应反映偏好 val response2 = preferencesAgent.generate("你能解释量子纠缠吗?") println("代理:${response2.text}") } ``` ## 下一步 ✅ 现在您已经了解了工作记忆,您可以: 1. 了解[语义回忆](./semantic-recall.mdx) 2. 探索[记忆处理器](./memory-processors.mdx) 3. 实现[自定义记忆存储](./implementations.mdx) --- title: 追踪系统 | 可观测性 | Kastrax 文档 description: Kastrax 追踪系统的详细介绍,包括追踪架构、追踪 API 和追踪集成的实现和使用方法。 --- # 追踪系统 ✅ [ZH] Source: https://kastrax.ai/zh/docs/observability/tracing Kastrax 追踪系统提供了一套全面的工具和方法,用于监控和分析 AI 代理的行为和性能。本指南详细介绍了追踪系统的架构、组件和使用方法。 ## 什么是追踪系统? ✅ 追踪系统是一个框架,用于: - 记录 AI 代理的行为和决策过程 - 监控代理的性能和资源使用情况 - 分析代理的调用链和依赖关系 - 诊断问题和优化性能 - 提供可视化和报告功能 通过追踪系统,您可以深入了解代理的内部工作原理,并在出现问题时快速定位和解决。 ## 追踪系统架构 ✅ Kastrax 追踪系统由以下核心组件组成: 1. **追踪 API**:用于创建和管理追踪 2. **追踪收集器**:收集和处理追踪数据 3. **追踪存储**:存储追踪数据 4. **追踪分析器**:分析追踪数据并生成报告 5. **追踪可视化**:可视化追踪数据 ## 基本追踪用法 ✅ 以下是使用 Kastrax 追踪系统的简单示例: ```kotlin import ai.kastrax.observability.tracing.Tracer import ai.kastrax.observability.tracing.Span import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 初始化追踪器 val tracer = Tracer.create("my-application") // 创建根追踪 val rootSpan = tracer.startSpan("main-process") try { // 创建代理 rootSpan.addEvent("创建代理") val myAgent = agent { name("追踪测试代理") description("用于追踪的测试代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 启用追踪 tracing { enabled = true tracer = tracer } } // 创建子追踪 val generateSpan = tracer.startSpan("generate-response", parent = rootSpan) try { // 添加追踪属性 generateSpan.setAttribute("query", "什么是人工智能?") // 生成响应 val response = myAgent.generate("什么是人工智能?") // 添加响应信息 generateSpan.setAttribute("response_length", response.text.length) generateSpan.setAttribute("token_usage", response.usage?.totalTokens ?: 0) println("响应: ${response.text}") } finally { // 结束子追踪 generateSpan.end() } // 添加事件 rootSpan.addEvent("处理完成") } catch (e: Exception) { // 记录错误 rootSpan.recordException(e) println("发生错误: ${e.message}") } finally { // 结束根追踪 rootSpan.end() } // 关闭追踪器 tracer.close() } ``` ## 追踪 API ✅ ### 创建追踪器 ```kotlin // 创建基本追踪器 val tracer = Tracer.create("my-application") // 创建带配置的追踪器 val configuredTracer = Tracer.create("my-application") { sampler = Sampler.ALWAYS_ON propagator = W3CPropagator.getInstance() exporter = JaegerExporter { endpoint = "http://localhost:14250" } } ``` ### 创建追踪 ```kotlin // 创建根追踪 val rootSpan = tracer.startSpan("main-process") // 创建子追踪 val childSpan = tracer.startSpan("sub-process", parent = rootSpan) // 创建带属性的追踪 val attributedSpan = tracer.startSpan("process-with-attributes") { setAttribute("user_id", "user-123") setAttribute("request_id", UUID.randomUUID().toString()) setAttribute("priority", 1) } ``` ### 追踪操作 ```kotlin // 添加属性 span.setAttribute("key", "value") span.setAttribute("count", 42) span.setAttribute("enabled", true) // 添加事件 span.addEvent("开始处理") span.addEvent("处理完成", mapOf("duration_ms" to 150)) // 记录异常 try { // 可能抛出异常的代码 } catch (e: Exception) { span.recordException(e) } // 设置状态 span.setStatus(StatusCode.OK, "处理成功") span.setStatus(StatusCode.ERROR, "处理失败: 无效输入") // 结束追踪 span.end() ``` ### 上下文传播 ```kotlin // 获取当前追踪上下文 val context = tracer.currentContext() // 在新协程中使用上下文 kotlinx.coroutines.withContext(context.asContextElement()) { // 在此协程中创建的追踪将自动关联到父追踪 val span = tracer.startSpan("coroutine-process") // ... span.end() } // 跨服务传播 val headers = mutableMapOf() tracer.inject(context, headers) { carrier, key, value -> carrier[key] = value } // 在另一个服务中提取上下文 val extractedContext = tracer.extract(receivedHeaders) { carrier, key -> carrier[key] } ``` ## 追踪集成 ✅ ### 代理追踪 ```kotlin // 创建带追踪的代理 val agent = agent { name("追踪代理") description("带追踪功能的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 启用追踪 tracing { enabled = true tracer = tracer // 配置追踪详细程度 level = TracingLevel.DETAILED // 配置追踪属性 attributes { include("model") include("temperature") include("max_tokens") } // 配置敏感数据处理 sensitiveData { mask("api_key") mask("user_data") } } } ``` ### 工具追踪 ```kotlin // 创建带追踪的工具 val tracedTool = tool("calculator") { description("执行基本的数学计算") parameters { parameter("expression", "要计算的数学表达式", String::class) } // 启用追踪 tracing { enabled = true tracer = tracer } execute { params -> // 创建追踪 val span = tracer.startSpan("calculator-execution") try { val expression = params["expression"] as String span.setAttribute("expression", expression) // 执行计算 val result = evalExpression(expression) span.setAttribute("result", result.toString()) "计算结果: $result" } catch (e: Exception) { span.recordException(e) span.setStatus(StatusCode.ERROR, e.message ?: "计算错误") "无法计算表达式: ${e.message}" } finally { span.end() } } } ``` ### 记忆系统追踪 ```kotlin // 创建带追踪的记忆系统 val tracedMemory = memory { type = MemoryType.VECTOR // 启用追踪 tracing { enabled = true tracer = tracer } } // 使用记忆系统 val span = tracer.startSpan("memory-operation") try { // 添加记忆 memory.add("用户喜欢蓝色") // 检索记忆 val relevantMemories = memory.retrieve("用户喜欢什么颜色?") span.setAttribute("memory_count", relevantMemories.size) println("相关记忆: $relevantMemories") } finally { span.end() } ``` ### RAG 系统追踪 ```kotlin // 创建带追踪的 RAG 系统 val tracedRAG = RAG( vectorStore = vectorStore, embeddingService = embeddingService, tracer = tracer ) // 使用 RAG 系统 val span = tracer.startSpan("rag-search") try { // 设置查询 val query = "什么是量子计算?" span.setAttribute("query", query) // 执行搜索 val results = tracedRAG.search(query, limit = 5) span.setAttribute("result_count", results.size) println("找到 ${results.size} 个相关文档") } finally { span.end() } ``` ## 追踪导出器 ✅ Kastrax 支持多种追踪导出器,用于将追踪数据发送到不同的后端系统: ### 控制台导出器 ```kotlin // 创建控制台导出器 val consoleExporter = ConsoleSpanExporter() // 配置追踪器使用控制台导出器 val tracer = Tracer.create("my-application") { exporter = consoleExporter } ``` ### Jaeger 导出器 ```kotlin // 创建 Jaeger 导出器 val jaegerExporter = JaegerExporter { endpoint = "http://localhost:14250" username = "user" password = "pass" } // 配置追踪器使用 Jaeger 导出器 val tracer = Tracer.create("my-application") { exporter = jaegerExporter } ``` ### Zipkin 导出器 ```kotlin // 创建 Zipkin 导出器 val zipkinExporter = ZipkinExporter { endpoint = "http://localhost:9411/api/v2/spans" } // 配置追踪器使用 Zipkin 导出器 val tracer = Tracer.create("my-application") { exporter = zipkinExporter } ``` ### OTLP 导出器 ```kotlin // 创建 OTLP 导出器 val otlpExporter = OtlpExporter { endpoint = "http://localhost:4317" protocol = OtlpProtocol.GRPC } // 配置追踪器使用 OTLP 导出器 val tracer = Tracer.create("my-application") { exporter = otlpExporter } ``` ### 自定义导出器 ```kotlin // 创建自定义导出器 class CustomExporter : SpanExporter { override fun export(spans: List): CompletableResultCode { // 实现自定义导出逻辑 for (span in spans) { // 处理追踪数据 println("导出追踪: ${span.name}") // 可以将数据发送到自定义后端 } return CompletableResultCode.ofSuccess() } override fun flush(): CompletableResultCode { // 实现刷新逻辑 return CompletableResultCode.ofSuccess() } override fun shutdown(): CompletableResultCode { // 实现关闭逻辑 return CompletableResultCode.ofSuccess() } } // 配置追踪器使用自定义导出器 val tracer = Tracer.create("my-application") { exporter = CustomExporter() } ``` ## 追踪采样器 ✅ 采样器决定哪些追踪应该被记录和导出: ```kotlin // 始终采样 val alwaysOnSampler = Sampler.ALWAYS_ON // 从不采样 val alwaysOffSampler = Sampler.ALWAYS_OFF // 概率采样(采样 50% 的追踪) val probabilitySampler = Sampler.probability(0.5) // 基于速率的采样(每秒最多 10 个追踪) val rateLimitingSampler = Sampler.rateLimit(10) // 配置追踪器使用采样器 val tracer = Tracer.create("my-application") { sampler = probabilitySampler } ``` ## 追踪可视化 ✅ Kastrax 提供了多种方式来可视化追踪数据: ### Jaeger UI ```kotlin // 配置 Jaeger 导出器 val tracer = Tracer.create("my-application") { exporter = JaegerExporter { endpoint = "http://localhost:14250" } } // 然后可以在 Jaeger UI 中查看追踪数据 // 通常在 http://localhost:16686 ``` ### Zipkin UI ```kotlin // 配置 Zipkin 导出器 val tracer = Tracer.create("my-application") { exporter = ZipkinExporter { endpoint = "http://localhost:9411/api/v2/spans" } } // 然后可以在 Zipkin UI 中查看追踪数据 // 通常在 http://localhost:9411 ``` ### 自定义可视化 ```kotlin // 创建自定义可视化 class TracingDashboard(private val tracer: Tracer) { fun generateReport(timeRange: TimeRange): TracingReport { // 获取指定时间范围内的追踪数据 val spans = tracer.getSpans(timeRange) // 生成报告 return TracingReport( spans = spans, summary = generateSummary(spans), charts = generateCharts(spans) ) } private fun generateSummary(spans: List): TracingSummary { // 生成摘要统计信息 val totalSpans = spans.size val errorSpans = spans.count { it.status.isError } val avgDuration = spans.map { it.endTime - it.startTime }.average() return TracingSummary( totalSpans = totalSpans, errorSpans = errorSpans, avgDuration = avgDuration ) } private fun generateCharts(spans: List): List { // 生成可视化图表 val charts = mutableListOf() // 示例:生成持续时间分布图 charts.add( HistogramChart( title = "追踪持续时间分布", data = spans.map { it.endTime - it.startTime } ) ) // 示例:生成错误率图 charts.add( PieChart( title = "追踪状态分布", data = mapOf( "成功" to spans.count { !it.status.isError }, "错误" to spans.count { it.status.isError } ) ) ) return charts } } // 使用自定义可视化 val dashboard = TracingDashboard(tracer) val report = dashboard.generateReport( TimeRange( start = System.currentTimeMillis() - 24 * 60 * 60 * 1000, // 24 小时前 end = System.currentTimeMillis() ) ) // 显示报告 println("追踪报告:") println("总追踪数: ${report.summary.totalSpans}") println("错误追踪数: ${report.summary.errorSpans}") println("平均持续时间: ${report.summary.avgDuration} ms") // 保存报告 report.saveToFile("tracing_report.html") ``` ## 追踪最佳实践 ✅ 1. **命名约定**:使用一致的命名约定,如 `operation_name` 2. **适当粒度**:选择合适的追踪粒度,既不要太细也不要太粗 3. **关键属性**:添加有助于诊断的关键属性,如用户 ID、请求 ID 等 4. **错误处理**:始终记录异常和错误状态 5. **性能考虑**:注意追踪对性能的影响,使用采样器减少开销 6. **敏感数据**:避免在追踪中包含敏感数据,或使用掩码处理 7. **上下文传播**:在分布式系统中正确传播追踪上下文 8. **定期审查**:定期审查追踪数据,识别性能问题和优化机会 ## 完整示例 ✅ 以下是一个完整的追踪示例,包括代理、工具和 RAG 系统: ```kotlin import ai.kastrax.observability.tracing.Tracer import ai.kastrax.observability.tracing.Span import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.rag.RAG import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 初始化追踪器 val tracer = Tracer.create("ai-assistant-app") { exporter = JaegerExporter { endpoint = "http://localhost:14250" } sampler = Sampler.ALWAYS_ON } // 创建根追踪 val rootSpan = tracer.startSpan("main-application") try { // 创建 RAG 系统 rootSpan.addEvent("初始化 RAG 系统") val rag = RAG( vectorStore = vectorStore, embeddingService = embeddingService, tracer = tracer ) // 添加文档 val addDocsSpan = tracer.startSpan("add-documents", parent = rootSpan) try { val documents = loadDocuments("knowledge_base/") addDocsSpan.setAttribute("document_count", documents.size) rag.addDocuments(documents) } finally { addDocsSpan.end() } // 创建工具 rootSpan.addEvent("创建工具") val searchTool = tool("search") { description("搜索知识库") parameters { parameter("query", "搜索查询", String::class) parameter("limit", "结果数量限制", Int::class, required = false, defaultValue = 5) } // 启用追踪 tracing { enabled = true tracer = tracer } execute { params -> val span = tracer.startSpan("search-tool-execution") try { val query = params["query"] as String val limit = params["limit"] as? Int ?: 5 span.setAttribute("query", query) span.setAttribute("limit", limit) // 执行搜索 val results = rag.search(query, limit = limit) span.setAttribute("result_count", results.size) // 格式化结果 val formattedResults = results.mapIndexed { index, result -> "${index + 1}. ${result.document.content.take(200)}..." }.joinToString("\n\n") "搜索结果:\n\n$formattedResults" } catch (e: Exception) { span.recordException(e) span.setStatus(StatusCode.ERROR, e.message ?: "搜索错误") "搜索失败: ${e.message}" } finally { span.end() } } } // 创建代理 rootSpan.addEvent("创建代理") val assistant = agent { name("知识助手") description("一个可以搜索知识库的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 添加工具 tools { tool(searchTool) } // 启用追踪 tracing { enabled = true tracer = tracer level = TracingLevel.DETAILED } } // 处理用户查询 val querySpan = tracer.startSpan("process-user-query", parent = rootSpan) try { val userQuery = "量子计算的基本原理是什么?" querySpan.setAttribute("user_query", userQuery) // 生成响应 val response = assistant.generate(userQuery) querySpan.setAttribute("response_length", response.text.length) querySpan.setAttribute("token_usage", response.usage?.totalTokens ?: 0) // 记录工具使用情况 response.toolCalls?.forEach { toolCall -> querySpan.addEvent("工具调用", mapOf( "tool_name" to toolCall.name, "tool_input" to toolCall.input.toString() )) } println("用户查询: $userQuery") println("助手响应: ${response.text}") } finally { querySpan.end() } // 添加最终事件 rootSpan.addEvent("应用程序完成") } catch (e: Exception) { rootSpan.recordException(e) rootSpan.setStatus(StatusCode.ERROR, e.message ?: "应用程序错误") println("发生错误: ${e.message}") } finally { rootSpan.end() } // 关闭追踪器 tracer.close() println("追踪数据已发送到 Jaeger") } // 加载文档的辅助函数 suspend fun loadDocuments(directory: String): List { val loader = DocumentLoader.fromDirectory(directory) return loader.load() } ``` ## 下一步 ✅ 现在您已经了解了追踪系统,您可以: 1. 了解[日志系统](./logging.mdx)的详细用法 2. 探索[Next.js 追踪](./nextjs-tracing.mdx)的集成方法 3. 学习如何将追踪与其他可观测性工具结合使用 --- title: 高级检索技术 | RAG 系统 | Kastrax 文档 description: Kastrax RAG 系统中的高级检索技术,包括查询转换、混合检索、语义检索和重排序策略。 --- # 高级检索技术 ✅ [ZH] Source: https://kastrax.ai/zh/docs/rag/advanced-retrieval Kastrax RAG 系统提供了多种高级检索技术,可以显著提高检索质量和相关性。本指南详细介绍了这些技术及其实现方法。 ## 查询转换和增强 ✅ 查询转换是提高检索质量的强大技术,它通过转换原始查询来生成更有效的搜索查询。 ### 查询转换器类型 ```kotlin import ai.kastrax.rag.retrieval.query.* import ai.kastrax.rag.RAG import ai.kastrax.rag.RagProcessOptions import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 RAG 系统 val rag = RAG(vectorStore, embeddingService) // 使用查询规范化 val resultsWithNormalization = rag.search( "Kastrax中的RAG系统是什么?", options = RagProcessOptions( useQueryEnhancement = true, queryEnhancementOptions = QueryEnhancedRetrieverConfig( queryTransformer = NormalizationQueryTransformer(), useMultiQuery = false ) ) ) // 使用同义词扩展 val resultsWithSynonyms = rag.search( "Kastrax中的RAG系统是什么?", options = RagProcessOptions( useQueryEnhancement = true, queryEnhancementOptions = QueryEnhancedRetrieverConfig( queryTransformer = SynonymQueryTransformer( synonymMap = mapOf( "RAG" to listOf("检索增强生成", "Retrieval-Augmented Generation"), "系统" to listOf("框架", "架构", "模块") ) ), useMultiQuery = true ) ) ) // 使用 LLM 查询转换 val resultsWithLLM = rag.search( "如何在我的应用中使用 Kastrax?", options = RagProcessOptions( useQueryEnhancement = true, queryEnhancementOptions = QueryEnhancedRetrieverConfig( queryTransformer = LLMQueryTransformer( llm = deepSeekLlm, prompt = """ 你是一个查询重写专家。你的任务是将用户的原始查询重写为多个可能更有效的查询变体。 这些变体应该捕捉原始查询的不同方面,使用不同的术语,或者分解复杂的查询。 原始查询: {{query}} 生成 3 个不同的查询变体,每行一个: """.trimIndent(), numVariants = 3 ), useMultiQuery = true ) ) ) // 使用组合查询转换器 val resultsWithComposite = rag.search( "Kastrax中的RAG系统是什么?", options = RagProcessOptions( useQueryEnhancement = true, queryEnhancementOptions = QueryEnhancedRetrieverConfig( queryTransformer = CompositeQueryTransformer( transformers = listOf( NormalizationQueryTransformer(), SynonymQueryTransformer(), LLMQueryTransformer(llm = deepSeekLlm) ) ), useMultiQuery = true, mergeStrategy = MergeStrategy.DIVERSITY ) ) ) } ``` ### 多查询检索 多查询检索使用多个查询变体进行检索,然后合并结果: ```kotlin // 配置多查询检索 val options = RagProcessOptions( useQueryEnhancement = true, queryEnhancementOptions = QueryEnhancedRetrieverConfig( useMultiQuery = true, numQueries = 3, mergeStrategy = MergeStrategy.DIVERSITY ) ) // 使用多查询检索 val results = rag.search("Kastrax中的RAG系统是什么?", options = options) ``` ### 结果合并策略 Kastrax 支持多种结果合并策略: ```kotlin // 交错合并(从每个查询结果中依次选择一个文档) val interleavedResults = rag.search( "Kastrax中的RAG系统是什么?", options = RagProcessOptions( useQueryEnhancement = true, queryEnhancementOptions = QueryEnhancedRetrieverConfig( useMultiQuery = true, mergeStrategy = MergeStrategy.INTERLEAVE ) ) ) // 按分数合并(选择分数最高的文档) val scoredResults = rag.search( "Kastrax中的RAG系统是什么?", options = RagProcessOptions( useQueryEnhancement = true, queryEnhancementOptions = QueryEnhancedRetrieverConfig( useMultiQuery = true, mergeStrategy = MergeStrategy.BY_SCORE ) ) ) // 多样性合并(首先从每个查询中选择最佳结果,然后按分数填充) val diversityResults = rag.search( "Kastrax中的RAG系统是什么?", options = RagProcessOptions( useQueryEnhancement = true, queryEnhancementOptions = QueryEnhancedRetrieverConfig( useMultiQuery = true, mergeStrategy = MergeStrategy.DIVERSITY ) ) ) ``` ## 混合检索 ✅ 混合检索结合了多种检索方法的优势: ```kotlin import ai.kastrax.rag.retrieval.* import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建向量检索器 val vectorRetriever = VectorRetriever( vectorStore = vectorStore, embeddingService = embeddingService, topK = 10 ) // 创建关键词检索器 val keywordRetriever = KeywordRetriever( documents = documents, topK = 10 ) // 创建混合检索器 val hybridRetriever = HybridRetriever( retrievers = listOf( RetrievalComponent(vectorRetriever, weight = 0.7), RetrievalComponent(keywordRetriever, weight = 0.3) ), mergeStrategy = MergeStrategy.BY_SCORE ) // 使用混合检索器 val results = hybridRetriever.retrieve( query = "Kastrax中的RAG系统是什么?", limit = 5 ) } ``` ### 自定义混合检索 您可以创建自定义混合检索策略: ```kotlin // 创建自定义混合检索器 val customHybridRetriever = HybridRetriever( retrievers = listOf( RetrievalComponent(vectorRetriever, weight = 0.6), RetrievalComponent(keywordRetriever, weight = 0.2), RetrievalComponent(metadataRetriever, weight = 0.2) ), mergeStrategy = { results -> // 自定义合并逻辑 // 例如,确保结果包含至少一个来自每个检索器的文档 val merged = mutableListOf() // 从每个检索器中获取至少一个结果 results.forEach { componentResults -> if (componentResults.isNotEmpty()) { merged.add(componentResults.first()) } } // 按分数填充剩余位置 val remaining = results.flatten() .filter { it !in merged } .sortedByDescending { it.score } merged.addAll(remaining.take(5 - merged.size)) merged } ) ``` ## 语义检索 ✅ 语义检索使用嵌入模型捕获查询和文档之间的语义关系: ```kotlin import ai.kastrax.rag.retrieval.* import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建语义检索器 val semanticRetriever = SemanticRetriever( vectorStore = vectorStore, embeddingService = embeddingService, config = SemanticRetrieverConfig( topK = 10, similarityThreshold = 0.7, similarityMetric = SimilarityMetric.COSINE ) ) // 使用语义检索器 val results = semanticRetriever.retrieve( query = "Kastrax中的RAG系统是什么?", limit = 5 ) } ``` ### 语义检索配置选项 语义检索支持多种配置选项: ```kotlin // 配置语义检索 val semanticConfig = SemanticRetrieverConfig( // 基本选项 topK = 10, similarityThreshold = 0.7, // 相似度度量 similarityMetric = SimilarityMetric.COSINE, // 或 DOT_PRODUCT, EUCLIDEAN // 高级选项 useQueryExpansion = true, queryExpansionFactor = 1.2, // 缓存选项 useCache = true, cacheSize = 100 ) // 使用配置创建检索器 val semanticRetriever = SemanticRetriever( vectorStore = vectorStore, embeddingService = embeddingService, config = semanticConfig ) ``` ## 重排序策略 ✅ 重排序是提高检索质量的关键技术: ```kotlin import ai.kastrax.rag.reranking.* import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建交叉编码器重排序器 val crossEncoderReranker = CrossEncoderReranker( model = "cross-encoder/ms-marco-MiniLM-L-6-v2", topK = 5 ) // 创建关键词匹配重排序器 val keywordReranker = KeywordMatchReranker( keywordWeight = 0.7, originalScoreWeight = 0.3 ) // 创建 LLM 重排序器 val llmReranker = LLMReranker( llm = deepSeekLlm, prompt = """ 你是一个文档排序专家。你的任务是根据与查询的相关性对文档进行排序。 查询: {{query}} 文档: {{#each documents}} 文档 {{@index}}: {{content}} {{/each}} 对这些文档进行排序,返回最相关的文档的索引,用逗号分隔(例如:2,0,1,3): """.trimIndent(), topK = 3 ) // 创建多样性重排序器 val diversityReranker = DiversityReranker( diversityWeight = 0.5, originalScoreWeight = 0.5, similarityThreshold = 0.8 ) // 创建组合重排序器 val combinedReranker = CompositeReranker( rerankers = listOf( keywordReranker, diversityReranker ), topK = 5 ) // 使用重排序器增强检索器 val enhancedRetriever = RerankedRetriever( baseRetriever = vectorRetriever, reranker = combinedReranker ) // 使用增强检索器 val results = enhancedRetriever.retrieve( query = "Kastrax中的RAG系统是什么?", limit = 5 ) } ``` ### 自定义重排序器 您可以创建自定义重排序器: ```kotlin // 创建自定义重排序器 class CustomReranker : Reranker { override suspend fun rerank( query: String, results: List, limit: Int ): List { // 自定义重排序逻辑 // 例如,根据文档长度和相关性的组合分数重排序 return results .map { result -> // 计算文档长度分数(较短的文档得分更高) val lengthScore = 1.0 / (1.0 + result.content.length / 1000.0) // 组合分数 val combinedScore = result.score * 0.7 + lengthScore * 0.3 // 创建新的搜索结果 SearchResult( content = result.content, metadata = result.metadata, score = combinedScore ) } .sortedByDescending { it.score } .take(limit) } } // 使用自定义重排序器 val customReranker = CustomReranker() val enhancedRetriever = RerankedRetriever( baseRetriever = vectorRetriever, reranker = customReranker ) ``` ## 上下文感知检索 ✅ 上下文感知检索考虑查询的上下文,如对话历史: ```kotlin import ai.kastrax.rag.retrieval.* import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建上下文感知检索器 val contextAwareRetriever = ContextAwareRetriever( baseRetriever = vectorRetriever, contextWindow = 3, // 考虑最近的 3 个交互 contextWeight = 0.3 // 上下文的权重 ) // 使用上下文感知检索器 val conversationContext = listOf( "用户: Kastrax 是什么?", "助手: Kastrax 是一个强大的 AI 代理框架,使用 Kotlin 构建。", "用户: 它有哪些主要功能?" ) val results = contextAwareRetriever.retrieve( query = "它有 RAG 系统吗?", limit = 5, context = conversationContext ) } ``` ## 查询分解 ✅ 查询分解将复杂查询分解为多个简单查询: ```kotlin import ai.kastrax.rag.retrieval.query.* import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建查询分解转换器 val decompositionTransformer = DecompositionQueryTransformer( llm = deepSeekLlm, prompt = """ 你是一个查询分解专家。你的任务是将复杂查询分解为多个简单查询。 复杂查询: {{query}} 将此查询分解为 2-3 个简单查询,每行一个: """.trimIndent() ) // 使用查询分解 val decomposedQueries = decompositionTransformer.transform( "Kastrax 的 RAG 系统如何工作,它与其他框架相比有什么优势?" ) // 可能的输出: // ["Kastrax 的 RAG 系统如何工作?", // "Kastrax 的 RAG 系统与其他框架相比有什么优势?"] // 使用分解的查询进行检索 val allResults = mutableListOf() for (subQuery in decomposedQueries) { val results = vectorRetriever.retrieve(subQuery, limit = 3) allResults.addAll(results) } // 合并和去重结果 val uniqueResults = allResults .distinctBy { it.content } .sortedByDescending { it.score } .take(5) } ``` ## 元数据过滤和增强 ✅ 元数据过滤和增强可以提高检索的精确性: ```kotlin import ai.kastrax.rag.retrieval.* import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建带元数据过滤的检索器 val metadataFilteredRetriever = MetadataFilteredRetriever( baseRetriever = vectorRetriever, filters = mapOf( "category" to "RAG", "language" to "Kotlin" ) ) // 使用元数据过滤检索 val filteredResults = metadataFilteredRetriever.retrieve( query = "Kastrax中的RAG系统是什么?", limit = 5 ) // 创建元数据增强检索器 val metadataEnhancedRetriever = MetadataEnhancedRetriever( baseRetriever = vectorRetriever, metadataWeights = mapOf( "category" to 0.3, "author" to 0.2, "date" to 0.1 ) ) // 使用元数据增强检索 val enhancedResults = metadataEnhancedRetriever.retrieve( query = "Kastrax中的RAG系统是什么?", limit = 5, metadataValues = mapOf( "category" to "RAG", "author" to "Kastrax Team" ) ) } ``` ## 完整高级检索示例 ✅ 以下是一个结合多种高级检索技术的完整示例: ```kotlin import ai.kastrax.rag.* import ai.kastrax.rag.retrieval.* import ai.kastrax.rag.reranking.* import ai.kastrax.rag.retrieval.query.* import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createAdvancedRetrievalSystem() = runBlocking { // 1. 设置基础组件 val vectorStore = VectorStore.chroma("kastrax_docs") val embeddingService = EmbeddingModel.create("all-MiniLM-L6-v2") // 2. 创建查询转换器 val queryTransformer = CompositeQueryTransformer( transformers = listOf( NormalizationQueryTransformer(), LLMQueryTransformer( llm = deepSeekLlm, numVariants = 3 ) ) ) // 3. 创建基础检索器 val vectorRetriever = VectorRetriever( vectorStore = vectorStore, embeddingService = embeddingService, topK = 10 ) val keywordRetriever = KeywordRetriever( documents = vectorStore.getAllDocuments(), topK = 10 ) // 4. 创建混合检索器 val hybridRetriever = HybridRetriever( retrievers = listOf( RetrievalComponent(vectorRetriever, weight = 0.7), RetrievalComponent(keywordRetriever, weight = 0.3) ), mergeStrategy = MergeStrategy.BY_SCORE ) // 5. 创建查询增强检索器 val queryEnhancedRetriever = QueryEnhancedRetriever( baseRetriever = hybridRetriever, queryTransformer = queryTransformer, config = QueryEnhancedRetrieverConfig( useMultiQuery = true, mergeStrategy = MergeStrategy.DIVERSITY ) ) // 6. 创建重排序器 val crossEncoderReranker = CrossEncoderReranker( model = "cross-encoder/ms-marco-MiniLM-L-6-v2", topK = 5 ) val diversityReranker = DiversityReranker( diversityWeight = 0.3, originalScoreWeight = 0.7, similarityThreshold = 0.8 ) val combinedReranker = CompositeReranker( rerankers = listOf( crossEncoderReranker, diversityReranker ), topK = 5 ) // 7. 创建最终增强检索器 val enhancedRetriever = RerankedRetriever( baseRetriever = queryEnhancedRetriever, reranker = combinedReranker ) // 8. 创建上下文构建器 val contextBuilder = ContextBuilder.create { template(""" 你是 Kastrax(一个强大的 AI 代理框架)的有帮助的助手。 根据以下来自 Kastrax 文档的上下文回答问题。 上下文: {{#each documents}} 文档 {{@index}}(来源:{{metadata.source}}): {{pageContent}} {{/each}} 问题:{{query}} 仅使用上下文中提供的信息回答问题。 如果你没有足够的信息,请说"我没有足够的信息。" 提供信息时,始终引用源文档。 """.trimIndent()) compression { enabled(true) method(CompressionMethod.MAP_REDUCE) maxTokens(3000) } } // 9. 创建 RAG 代理 val ragAgent = agent { name("高级RAG助手") description("一个使用高级检索技术的RAG助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.3) } // 配置 RAG rag { retriever(enhancedRetriever) contextBuilder(contextBuilder) maxTokens(3000) } } // 10. 使用 RAG 代理 val query = "Kastrax 的 RAG 系统如何处理复杂查询?" val response = ragAgent.generate(query) println(response.text) } ``` ## 性能考虑 ✅ 在使用高级检索技术时,需要考虑以下性能因素: ### 查询转换和多查询 - **限制查询变体数量**:过多的查询变体会增加计算开销 - **缓存转换结果**:对于常见查询,缓存转换结果可以提高性能 - **异步处理**:并行执行多个查询可以减少总体延迟 ### 混合检索 - **调整检索器权重**:根据应用需求调整不同检索器的权重 - **限制初始结果数量**:每个检索器返回适量的结果,避免过度检索 - **优化合并策略**:选择合适的合并策略,平衡质量和性能 ### 重排序 - **分阶段重排序**:先使用轻量级重排序器,再使用更复杂的重排序器 - **限制重排序文档数量**:只对最相关的文档进行重排序 - **选择合适的模型**:对于交叉编码器,选择速度和质量平衡的模型 ## 最佳实践 ✅ ### 查询转换 - **组合多种转换器**:使用 `CompositeQueryTransformer` 结合多种转换技术 - **定制 LLM 提示**:为您的领域定制 LLM 查询转换提示 - **监控转换质量**:定期评估查询转换的效果,调整配置 ### 混合检索 - **根据数据特性选择检索器**:不同类型的数据可能需要不同的检索策略 - **动态调整权重**:根据查询类型动态调整检索器权重 - **考虑元数据**:利用文档元数据提高检索精确性 ### 重排序 - **结合多种重排序策略**:使用 `CompositeReranker` 结合多种重排序技术 - **考虑多样性**:使用 `DiversityReranker` 确保结果多样性 - **自定义评分函数**:为特定应用创建自定义重排序逻辑 ## 下一步 ✅ 现在您已经了解了高级检索技术,您可以: 1. 学习如何[评估 RAG 系统](./evaluation.mdx) 2. 探索[上下文构建和压缩](./context-building.mdx) 3. 实现[多模态 RAG](./multimodal-rag.mdx) 4. 设置[RAG 监控和日志](./monitoring.mdx) --- title: 文档分块和嵌入 | RAG | Kastrax 文档 description: Kastrax 中文档分块和嵌入的指南,用于高效处理和检索。 --- ## 文档分块和嵌入 ✅ [ZH] Source: https://kastrax.ai/zh/docs/rag/chunking-and-embedding 在处理之前,从您的内容创建一个 MDocument 实例。您可以从各种格式初始化它: ```ts showLineNumbers copy const docFromText = MDocument.fromText("您的纯文本内容..."); const docFromHTML = MDocument.fromHTML("您的 HTML 内容..."); const docFromMarkdown = MDocument.fromMarkdown("# 您的 Markdown 内容..."); const docFromJSON = MDocument.fromJSON(`{ "key": "value" }`); ``` ## 步骤 1:文档处理 ✅ 使用 `chunk` 将文档分割成可管理的片段。Kastrax 支持多种针对不同文档类型优化的分块策略: - `recursive`:基于内容结构的智能分割 - `character`:简单的基于字符的分割 - `token`:基于令牌的分割 - `markdown`:Markdown 感知分割 - `html`:HTML 结构感知分割 - `json`:JSON 结构感知分割 - `latex`:LaTeX 结构感知分割 以下是如何使用 `recursive` 策略的示例: ```ts showLineNumbers copy const chunks = await doc.chunk({ strategy: "recursive", size: 512, overlap: 50, separator: "\n", extract: { metadata: true, // 可选择提取元数据 }, }); ``` **注意:** 元数据提取可能使用 LLM 调用,因此请确保设置了您的 API 密钥。 我们在[分块文档](/reference/rag/chunk.mdx)中深入探讨了分块策略。 ## 步骤 2:生成嵌入 ✅ 使用您首选的提供商将分块转换为嵌入。Kastrax 支持多种嵌入提供商,包括 OpenAI 和 Cohere: ### 使用 OpenAI ```ts showLineNumbers copy import { openai } from "@ai-sdk/openai"; import { embedMany } from "ai"; const { embeddings } = await embedMany({ model: openai.embedding('text-embedding-3-small'), values: chunks.map(chunk => chunk.text), }); ``` ### 使用 Cohere ```ts showLineNumbers copy import { cohere } from '@ai-sdk/cohere'; import { embedMany } from 'ai'; const { embeddings } = await embedMany({ model: cohere.embedding('embed-english-v3.0'), values: chunks.map(chunk => chunk.text), }); ``` 嵌入函数返回向量,即表示文本语义含义的数字数组,可用于在向量数据库中进行相似性搜索。 ### 配置嵌入维度 嵌入模型通常输出具有固定维度数的向量(例如,OpenAI 的 `text-embedding-3-small` 为 1536)。 一些模型支持降低这种维度,这可以帮助: - 减少向量数据库中的存储需求 - 降低相似性搜索的计算成本 以下是一些支持的模型: OpenAI (text-embedding-3 模型): ```ts const { embeddings } = await embedMany({ model: openai.embedding('text-embedding-3-small', { dimensions: 256 // 仅在 text-embedding-3 及更高版本中支持 }), values: chunks.map(chunk => chunk.text), }); ``` Google (text-embedding-004): ```ts const { embeddings } = await embedMany({ model: google.textEmbeddingModel('text-embedding-004', { outputDimensionality: 256 // 从末尾截断多余的值 }), values: chunks.map(chunk => chunk.text), }); ``` ### 向量数据库兼容性 存储嵌入时,向量数据库索引必须配置为匹配嵌入模型的输出大小。如果维度不匹配,可能会出现错误或数据损坏。 ## 示例:完整流程 ✅ 以下是一个示例,展示了使用两种提供商进行文档处理和嵌入生成: ```ts showLineNumbers copy import { embedMany } from "ai"; import { openai } from "@ai-sdk/openai"; import { cohere } from "@ai-sdk/cohere"; import { MDocument } from "@kastrax/rag"; // 初始化文档 const doc = MDocument.fromText(` 气候变化对全球农业构成重大挑战。 气温上升和降水模式变化影响作物产量。 `); // 创建分块 const chunks = await doc.chunk({ strategy: "recursive", size: 256, overlap: 50, }); // 使用 OpenAI 生成嵌入 const { embeddings: openAIEmbeddings } = await embedMany({ model: openai.embedding('text-embedding-3-small'), values: chunks.map(chunk => chunk.text), }); // 或者 // 使用 Cohere 生成嵌入 const { embeddings: cohereEmbeddings } = await embedMany({ model: cohere.embedding('embed-english-v3.0'), values: chunks.map(chunk => chunk.text), }); // 在向量数据库中存储嵌入 await vectorStore.upsert({ indexName: "embeddings", vectors: embeddings, }); ``` ## 有关不同分块策略和嵌入配置的更多示例,请参见: - [调整分块大小](/reference/rag/chunk.mdx#adjust-chunk-size) - [调整分块分隔符](/reference/rag/chunk.mdx#adjust-chunk-delimiters) - [使用 Cohere 嵌入文本](/reference/rag/embeddings.mdx#using-cohere) 有关向量数据库和嵌入的更多详细信息,请参见: - [向量数据库](./vector-databases.mdx) - [嵌入 API 参考](/reference/rag/embeddings.mdx) --- title: RAG 评估 | RAG 系统 | Kastrax 文档 description: 如何评估和优化 Kastrax RAG 系统的性能,包括检索评估、生成评估和端到端评估。 --- # RAG 评估 ✅ [ZH] Source: https://kastrax.ai/zh/docs/rag/evaluation 评估 RAG 系统的性能对于确保其有效性和持续改进至关重要。本指南详细介绍了 Kastrax 中的 RAG 评估方法和工具。 ## 评估框架 ✅ Kastrax 提供了一个全面的 RAG 评估框架,可以评估 RAG 系统的各个组件: ```kotlin import ai.kastrax.rag.evaluation.* import ai.kastrax.rag.RAG import kotlinx.coroutines.runBlocking fun evaluateRagSystem() = runBlocking { // 创建 RAG 系统 val rag = RAG(vectorStore, embeddingService) // 创建评估器 val evaluator = RagEvaluator( rag = rag, evaluationDataset = testDataset, metrics = listOf( RetrievalPrecision(), RetrievalRecall(), AnswerRelevance(), FactualConsistency() ) ) // 运行评估 val results = evaluator.evaluate() // 打印结果 println("评估结果:") results.forEach { (metric, score) -> println("$metric: $score") } // 生成详细报告 val report = evaluator.generateReport() println(report) } ``` ## 评估数据集 ✅ 创建高质量的评估数据集是有效评估的关键: ```kotlin import ai.kastrax.rag.evaluation.EvaluationDataset import ai.kastrax.rag.evaluation.EvaluationExample // 创建评估数据集 val testDataset = EvaluationDataset( examples = listOf( EvaluationExample( query = "Kastrax 的 RAG 系统有哪些主要组件?", expectedAnswer = "Kastrax 的 RAG 系统包括文档处理、嵌入、向量存储、检索、重排序、上下文构建和生成组件。", relevantDocuments = listOf( "doc1.txt", "doc2.txt" ), metadata = mapOf( "difficulty" to "easy", "category" to "architecture" ) ), EvaluationExample( query = "如何在 Kastrax 中实现自定义检索器?", expectedAnswer = "在 Kastrax 中实现自定义检索器需要实现 Retriever 接口,定义 retrieve 方法...", relevantDocuments = listOf( "doc3.txt", "doc4.txt" ), metadata = mapOf( "difficulty" to "medium", "category" to "implementation" ) ) // 更多示例... ) ) // 从文件加载评估数据集 val loadedDataset = EvaluationDataset.fromJson("evaluation_data.json") // 从查询日志生成评估数据集 val generatedDataset = EvaluationDataset.fromQueryLogs( queryLogs = queryLogs, sampleSize = 100, llm = deepSeekLlm ) ``` ## 检索评估 ✅ 评估检索组件的性能: ```kotlin import ai.kastrax.rag.evaluation.retrieval.* import kotlinx.coroutines.runBlocking fun evaluateRetrieval() = runBlocking { // 创建检索评估器 val retrievalEvaluator = RetrievalEvaluator( retriever = retriever, evaluationDataset = testDataset, metrics = listOf( Precision(), Recall(), F1Score(), MeanReciprocalRank(), NormalizedDiscountedCumulativeGain() ) ) // 运行评估 val results = retrievalEvaluator.evaluate() // 打印结果 println("检索评估结果:") results.forEach { (metric, score) -> println("$metric: $score") } // 分析失败案例 val failureCases = retrievalEvaluator.analyzeFailures() println("失败案例分析:") failureCases.forEach { case -> println("查询: ${case.query}") println("预期文档: ${case.expectedDocuments}") println("检索到的文档: ${case.retrievedDocuments}") println("问题: ${case.issues.joinToString(", ")}") println() } } ``` ### 检索评估指标 Kastrax 支持多种检索评估指标: ```kotlin // 精确率(Precision) val precision = Precision(k = 5) // 前 5 个结果的精确率 // 召回率(Recall) val recall = Recall(k = 10) // 前 10 个结果的召回率 // F1 分数 val f1 = F1Score(k = 5) // 前 5 个结果的 F1 分数 // 平均倒数排名(MRR) val mrr = MeanReciprocalRank() // 归一化折扣累积增益(NDCG) val ndcg = NormalizedDiscountedCumulativeGain(k = 10) // 平均精度(MAP) val map = MeanAveragePrecision() // 检索时间 val retrievalTime = RetrievalTime() ``` ## 生成评估 ✅ 评估生成组件的性能: ```kotlin import ai.kastrax.rag.evaluation.generation.* import kotlinx.coroutines.runBlocking fun evaluateGeneration() = runBlocking { // 创建生成评估器 val generationEvaluator = GenerationEvaluator( rag = rag, evaluationDataset = testDataset, metrics = listOf( AnswerRelevance(), FactualConsistency(), Faithfulness(), ContextUtilization(), HallucinationRate() ) ) // 运行评估 val results = generationEvaluator.evaluate() // 打印结果 println("生成评估结果:") results.forEach { (metric, score) -> println("$metric: $score") } // 分析失败案例 val failureCases = generationEvaluator.analyzeFailures() println("失败案例分析:") failureCases.forEach { case -> println("查询: ${case.query}") println("生成的答案: ${case.generatedAnswer}") println("预期答案: ${case.expectedAnswer}") println("问题: ${case.issues.joinToString(", ")}") println() } } ``` ### 生成评估指标 Kastrax 支持多种生成评估指标: ```kotlin // 答案相关性 val relevance = AnswerRelevance( evaluator = llmEvaluator, prompt = "评估生成的答案与查询的相关性..." ) // 事实一致性 val factuality = FactualConsistency( evaluator = llmEvaluator, prompt = "评估生成的答案与提供的上下文的事实一致性..." ) // 忠实度 val faithfulness = Faithfulness( evaluator = llmEvaluator, prompt = "评估生成的答案是否忠实于提供的上下文..." ) // 上下文利用率 val contextUtilization = ContextUtilization() // 幻觉率 val hallucination = HallucinationRate( evaluator = llmEvaluator, prompt = "识别生成的答案中的幻觉..." ) // ROUGE 分数 val rouge = RougeScore() // BLEU 分数 val bleu = BleuScore() ``` ## 端到端评估 ✅ 评估整个 RAG 系统的端到端性能: ```kotlin import ai.kastrax.rag.evaluation.* import kotlinx.coroutines.runBlocking fun evaluateEndToEnd() = runBlocking { // 创建端到端评估器 val e2eEvaluator = EndToEndEvaluator( rag = rag, evaluationDataset = testDataset, metrics = listOf( QueryResponseLatency(), UserSatisfaction(), TaskCompletionRate() ) ) // 运行评估 val results = e2eEvaluator.evaluate() // 打印结果 println("端到端评估结果:") results.forEach { (metric, score) -> println("$metric: $score") } // 生成详细报告 val report = e2eEvaluator.generateReport() println(report) } ``` ### 端到端评估指标 Kastrax 支持多种端到端评估指标: ```kotlin // 查询响应延迟 val latency = QueryResponseLatency() // 用户满意度 val satisfaction = UserSatisfaction( evaluator = llmEvaluator, prompt = "评估生成的答案对用户的有用性和满意度..." ) // 任务完成率 val taskCompletion = TaskCompletionRate( evaluator = llmEvaluator, prompt = "评估生成的答案是否成功完成了用户的任务..." ) // 综合质量分数 val qualityScore = QualityScore( components = mapOf( "relevance" to 0.3, "factuality" to 0.3, "completeness" to 0.2, "coherence" to 0.2 ), evaluator = llmEvaluator ) ``` ## 人类评估 ✅ 结合人类评估可以提供更全面的评估: ```kotlin import ai.kastrax.rag.evaluation.human.* import kotlinx.coroutines.runBlocking fun humanEvaluation() = runBlocking { // 创建人类评估任务 val humanEvaluationTask = HumanEvaluationTask( examples = testDataset.examples.take(10), questions = listOf( EvaluationQuestion("答案与查询的相关性如何?", ScaleType.ONE_TO_FIVE), EvaluationQuestion("答案的事实准确性如何?", ScaleType.ONE_TO_FIVE), EvaluationQuestion("答案的完整性如何?", ScaleType.ONE_TO_FIVE), EvaluationQuestion("您对答案的总体满意度如何?", ScaleType.ONE_TO_FIVE) ), instructions = "请评估 RAG 系统生成的答案质量..." ) // 导出评估任务 humanEvaluationTask.exportToJson("human_evaluation_task.json") // 导入评估结果 val humanResults = HumanEvaluationResults.fromJson("human_evaluation_results.json") // 分析结果 val analysis = humanResults.analyze() println("人类评估结果分析:") analysis.forEach { (question, stats) -> println("问题: $question") println("平均分: ${stats.mean}") println("中位数: ${stats.median}") println("标准差: ${stats.stdDev}") println() } } ``` ## 自动评估与 LLM 评估 ✅ 使用 LLM 进行自动评估: ```kotlin import ai.kastrax.rag.evaluation.llm.* import kotlinx.coroutines.runBlocking fun llmEvaluation() = runBlocking { // 创建 LLM 评估器 val llmEvaluator = LLMEvaluator( llm = deepSeekLlm, defaultPromptTemplate = """ 你是一个 RAG 系统评估专家。你的任务是评估生成的答案质量。 查询: {{query}} 检索到的上下文: {{context}} 生成的答案: {{answer}} 预期答案: {{expected_answer}} 请根据以下标准评估生成的答案: 1. 相关性: 答案与查询的相关程度 2. 事实准确性: 答案与上下文和事实的一致程度 3. 完整性: 答案是否完整回答了查询 4. 连贯性: 答案的逻辑流程和可读性 对于每个标准,给出 1-5 的评分,其中 1 表示非常差,5 表示非常好。 然后给出总体评分(1-5)和详细解释。 输出格式: 相关性: [评分] 事实准确性: [评分] 完整性: [评分] 连贯性: [评分] 总体评分: [评分] 解释: [详细解释] """.trimIndent() ) // 评估单个示例 val evaluation = llmEvaluator.evaluate( query = "Kastrax 的 RAG 系统有哪些主要组件?", context = "Kastrax RAG 系统由文档处理、嵌入、向量存储、检索、重排序、上下文构建和生成组件组成。", answer = "Kastrax 的 RAG 系统包括文档处理、嵌入、向量存储、检索和生成组件。", expectedAnswer = "Kastrax 的 RAG 系统包括文档处理、嵌入、向量存储、检索、重排序、上下文构建和生成组件。" ) println("LLM 评估结果:") println(evaluation.scores) println("解释: ${evaluation.explanation}") // 批量评估 val batchEvaluations = llmEvaluator.evaluateBatch( examples = testDataset.examples, generatedAnswers = generatedAnswers ) // 分析批量评估结果 val aggregatedResults = batchEvaluations.aggregate() println("批量评估结果:") aggregatedResults.forEach { (metric, stats) -> println("$metric: 平均分 = ${stats.mean}, 中位数 = ${stats.median}") } } ``` ## 对比评估 ✅ 比较不同 RAG 配置的性能: ```kotlin import ai.kastrax.rag.evaluation.comparative.* import kotlinx.coroutines.runBlocking fun comparativeEvaluation() = runBlocking { // 创建不同的 RAG 配置 val ragConfig1 = RAG(vectorStore1, embeddingService1) val ragConfig2 = RAG(vectorStore2, embeddingService2) // 创建对比评估器 val comparativeEvaluator = ComparativeEvaluator( systems = mapOf( "基础配置" to ragConfig1, "高级配置" to ragConfig2 ), evaluationDataset = testDataset, metrics = listOf( RetrievalPrecision(), AnswerRelevance(), FactualConsistency(), QueryResponseLatency() ) ) // 运行对比评估 val results = comparativeEvaluator.evaluate() // 打印结果 println("对比评估结果:") results.forEach { (system, metricScores) -> println("系统: $system") metricScores.forEach { (metric, score) -> println(" $metric: $score") } println() } // 生成对比报告 val report = comparativeEvaluator.generateReport() println(report) // 执行统计显著性测试 val significanceTests = comparativeEvaluator.testSignificance() println("统计显著性测试结果:") significanceTests.forEach { (metric, pValue) -> val significant = pValue < 0.05 println("$metric: p值 = $pValue, 显著 = $significant") } } ``` ## 持续评估 ✅ 设置持续评估流程: ```kotlin import ai.kastrax.rag.evaluation.* import kotlinx.coroutines.runBlocking import java.time.LocalDateTime import java.time.format.DateTimeFormatter fun continuousEvaluation() = runBlocking { // 创建评估管理器 val evaluationManager = EvaluationManager( rag = rag, baselineDataset = baselineDataset, metrics = listOf( RetrievalPrecision(), AnswerRelevance(), FactualConsistency() ), evaluationFrequency = EvaluationFrequency.DAILY ) // 运行评估 val currentResults = evaluationManager.runEvaluation() // 比较与基线的差异 val comparison = evaluationManager.compareWithBaseline(currentResults) // 检查是否有退化 val regressions = comparison.findRegressions(threshold = 0.05) if (regressions.isNotEmpty()) { println("检测到性能退化:") regressions.forEach { (metric, delta) -> println("$metric: 下降了 ${delta * 100}%") } // 发送警报 evaluationManager.sendAlert( regressions = regressions, message = "RAG 系统性能退化警报" ) } // 保存评估结果 val timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME) evaluationManager.saveResults( results = currentResults, path = "evaluations/results_$timestamp.json" ) // 更新仪表板 evaluationManager.updateDashboard(currentResults) } ``` ## 评估驱动的优化 ✅ 使用评估结果优化 RAG 系统: ```kotlin import ai.kastrax.rag.evaluation.* import ai.kastrax.rag.optimization.* import kotlinx.coroutines.runBlocking fun evaluationDrivenOptimization() = runBlocking { // 创建 RAG 优化器 val optimizer = RagOptimizer( rag = rag, evaluationDataset = testDataset, metrics = listOf( RetrievalPrecision(), AnswerRelevance(), FactualConsistency() ) ) // 运行基线评估 val baselineResults = optimizer.evaluateBaseline() println("基线评估结果:") baselineResults.forEach { (metric, score) -> println("$metric: $score") } // 优化检索参数 val retrievalOptimizationResults = optimizer.optimizeRetrieval( parameterSpace = mapOf( "topK" to listOf(3, 5, 10, 15), "similarityThreshold" to listOf(0.5, 0.6, 0.7, 0.8) ), optimizationMetric = "RetrievalPrecision", maxTrials = 10 ) println("检索优化结果:") println("最佳参数: ${retrievalOptimizationResults.bestParameters}") println("最佳分数: ${retrievalOptimizationResults.bestScore}") // 优化上下文构建 val contextOptimizationResults = optimizer.optimizeContextBuilding( parameterSpace = mapOf( "maxTokens" to listOf(1000, 2000, 3000, 4000), "compressionMethod" to listOf("none", "map_reduce", "refine") ), optimizationMetric = "AnswerRelevance", maxTrials = 10 ) println("上下文构建优化结果:") println("最佳参数: ${contextOptimizationResults.bestParameters}") println("最佳分数: ${contextOptimizationResults.bestScore}") // 应用优化后的配置 val optimizedRag = optimizer.applyOptimizedConfiguration( retrievalParams = retrievalOptimizationResults.bestParameters, contextParams = contextOptimizationResults.bestParameters ) // 评估优化后的系统 val optimizedResults = optimizer.evaluate(optimizedRag) println("优化后的评估结果:") optimizedResults.forEach { (metric, score) -> println("$metric: $score") } } ``` ## 完整评估示例 ✅ 以下是一个全面的 RAG 评估示例: ```kotlin import ai.kastrax.rag.* import ai.kastrax.rag.evaluation.* import ai.kastrax.rag.evaluation.retrieval.* import ai.kastrax.rag.evaluation.generation.* import ai.kastrax.rag.evaluation.llm.* import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun comprehensiveRagEvaluation() = runBlocking { // 1. 准备评估数据集 val testDataset = EvaluationDataset.fromJson("evaluation_data.json") // 2. 创建 RAG 系统 val rag = RAG(vectorStore, embeddingService) // 3. 创建 LLM 评估器 val llmEvaluator = LLMEvaluator( llm = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.2) // 低温度以获得一致的评估 } ) // 4. 创建检索评估器 val retrievalEvaluator = RetrievalEvaluator( retriever = rag.retriever, evaluationDataset = testDataset, metrics = listOf( Precision(k = 5), Recall(k = 10), F1Score(k = 5), MeanReciprocalRank(), NormalizedDiscountedCumulativeGain(k = 10) ) ) // 5. 创建生成评估器 val generationEvaluator = GenerationEvaluator( rag = rag, evaluationDataset = testDataset, metrics = listOf( AnswerRelevance(evaluator = llmEvaluator), FactualConsistency(evaluator = llmEvaluator), Faithfulness(evaluator = llmEvaluator), ContextUtilization(), HallucinationRate(evaluator = llmEvaluator) ) ) // 6. 创建端到端评估器 val e2eEvaluator = EndToEndEvaluator( rag = rag, evaluationDataset = testDataset, metrics = listOf( QueryResponseLatency(), UserSatisfaction(evaluator = llmEvaluator), TaskCompletionRate(evaluator = llmEvaluator) ) ) // 7. 运行检索评估 println("正在评估检索性能...") val retrievalResults = retrievalEvaluator.evaluate() println("检索评估结果:") retrievalResults.forEach { (metric, score) -> println("$metric: $score") } // 8. 运行生成评估 println("\n正在评估生成性能...") val generationResults = generationEvaluator.evaluate() println("生成评估结果:") generationResults.forEach { (metric, score) -> println("$metric: $score") } // 9. 运行端到端评估 println("\n正在评估端到端性能...") val e2eResults = e2eEvaluator.evaluate() println("端到端评估结果:") e2eResults.forEach { (metric, score) -> println("$metric: $score") } // 10. 分析失败案例 println("\n分析检索失败案例...") val retrievalFailures = retrievalEvaluator.analyzeFailures() retrievalFailures.take(3).forEach { case -> println("查询: ${case.query}") println("问题: ${case.issues.joinToString(", ")}") println() } println("分析生成失败案例...") val generationFailures = generationEvaluator.analyzeFailures() generationFailures.take(3).forEach { case -> println("查询: ${case.query}") println("问题: ${case.issues.joinToString(", ")}") println() } // 11. 生成综合报告 val report = EvaluationReport.create( retrievalResults = retrievalResults, generationResults = generationResults, e2eResults = e2eResults, retrievalFailures = retrievalFailures, generationFailures = generationFailures ) // 12. 保存报告 report.saveToFile("rag_evaluation_report.md") println("\n评估报告已保存到 rag_evaluation_report.md") } ``` ## 最佳实践 ✅ ### 评估数据集 - **多样性**:确保评估数据集涵盖各种查询类型和难度级别 - **真实性**:使用真实用户查询或模拟真实用户行为的查询 - **规模**:使用足够大的数据集以获得统计显著性 - **平衡**:确保数据集在不同主题和查询类型之间平衡 ### 评估指标 - **多维度**:使用多种指标评估不同方面的性能 - **任务相关**:选择与您的特定应用相关的指标 - **定量和定性**:结合定量指标和定性分析 - **人类评估**:在可能的情况下,补充自动评估与人类评估 ### 评估流程 - **持续评估**:定期评估系统性能,而不仅仅是在部署前 - **A/B 测试**:比较不同配置的性能 - **监控退化**:设置警报以检测性能退化 - **闭环优化**:使用评估结果指导系统改进 ## 下一步 ✅ 现在您已经了解了如何评估 RAG 系统,您可以: 1. 设置[持续评估流程](./continuous-evaluation.mdx) 2. 学习[RAG 系统优化](./optimization.mdx) 3. 探索[多模态 RAG 评估](./multimodal-evaluation.mdx) 4. 实现[自定义评估指标](./custom-metrics.mdx) --- title: Kotlin RAG 系统概述 | Kastrax 文档 description: Kastrax 中 Kotlin 检索增强生成 (RAG) 系统的详细介绍,包括文档处理、嵌入、检索和集成。 --- # Kotlin RAG 系统概述 ✅ [ZH] Source: https://kastrax.ai/zh/docs/rag/overview-kotlin Kastrax 的 Kotlin RAG 系统提供了一个全面的框架,用于构建结合大型语言模型和外部数据源的基于知识的应用程序。本指南详细介绍了如何在 Kotlin 中使用 RAG 系统。 ## RAG 系统架构 ✅ Kastrax RAG 系统由几个核心组件组成: 1. **文档处理**:加载、分块和转换文档 2. **嵌入**:将文本转换为向量表示 3. **向量存储**:高效存储和检索向量 4. **检索**:基于查询查找相关信息 5. **重排序**:通过额外的排序提高检索质量 6. **上下文构建**:使用检索到的信息构建有效的提示 ## 基本用法 ✅ 以下是在 Kastrax 中创建和使用 RAG 系统的基本示例: ```kotlin import ai.kastrax.rag.RAG import ai.kastrax.rag.document.Document import ai.kastrax.rag.embedding.FastEmbeddingService import ai.kastrax.rag.vectorstore.InMemoryVectorStore import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建向量存储和嵌入服务 val vectorStore = InMemoryVectorStore() val embeddingService = FastEmbeddingService.create() // 创建 RAG 系统 val rag = RAG(vectorStore, embeddingService) // 添加文档 val documents = listOf( Document("这是第一个文档的内容", mapOf("source" to "doc1.txt")), Document("这是第二个文档的内容", mapOf("source" to "doc2.txt")) ) rag.addDocuments(documents) // 搜索相关文档 val results = rag.search("查询内容", limit = 3) // 处理结果 results.forEach { result -> println("文档: ${result.document.content}") println("相似度: ${result.score}") println("元数据: ${result.document.metadata}") println() } } ``` ## 文档加载 ✅ Kastrax 提供了多种文档加载器,用于从不同来源加载文档: ```kotlin import ai.kastrax.rag.document.* import java.io.File // 从文本文件加载 val textLoader = TextFileDocumentLoader("path/to/document.txt") val textDocuments = textLoader.load() // 从 HTML 文件加载 val htmlLoader = HtmlFileDocumentLoader("path/to/document.html") val htmlDocuments = htmlLoader.load() // 从网页加载 val webLoader = WebPageDocumentLoader("https://example.com") val webDocuments = webLoader.load() // 从目录加载 val directoryLoader = DirectoryDocumentLoader( directory = File("path/to/documents"), recursive = true, fileExtensions = listOf("txt", "md", "html") ) val directoryDocuments = directoryLoader.load() ``` ## 文档分块 ✅ 文档通常需要分割成更小的块以便有效处理。Kastrax 提供了多种分块策略: ```kotlin import ai.kastrax.rag.document.* // 字符分块器 val charSplitter = CharacterTextSplitter( chunkSize = 1000, chunkOverlap = 200 ) // 递归字符分块器 val recursiveSplitter = RecursiveCharacterTextSplitter( chunkSize = 1000, chunkOverlap = 200, separators = listOf("\n\n", "\n", ". ", "! ", "? ", ", ", " ", "") ) // 段落分块器 val paragraphSplitter = ParagraphTextSplitter( maxParagraphLength = 1000, separators = listOf("\n\n", "\n", ". ") ) // 语义分块器 val semanticSplitter = SemanticDocumentSplitter( embeddingService = embeddingService, chunkSize = 1000, chunkOverlap = 200, similarityThreshold = 0.7 ) // 分割文档 val chunks = recursiveSplitter.split(document) ``` ## 嵌入服务 ✅ Kastrax 支持多种嵌入服务,用于将文本转换为向量表示: ```kotlin import ai.kastrax.rag.embedding.* // FastEmbed 嵌入服务(本地) val fastEmbedService = FastEmbeddingService.create( modelName = "all-MiniLM-L6-v2" ) // OpenAI 嵌入服务 val openAIService = OpenAIEmbeddingService( apiKey = "your-openai-api-key", model = "text-embedding-3-small" ) // HuggingFace 嵌入服务 val huggingFaceService = HuggingFaceEmbeddingService( apiKey = "your-huggingface-api-key", model = "sentence-transformers/all-mpnet-base-v2" ) // 随机嵌入服务(用于测试) val randomService = RandomEmbeddingService( dimensions = 1536, seed = 42 ) ``` ## 向量存储 ✅ Kastrax 提供了多种向量存储选项,用于存储和检索嵌入向量: ```kotlin import ai.kastrax.rag.vectorstore.* // 内存向量存储 val inMemoryStore = InMemoryVectorStore() // Chroma 向量存储 val chromaStore = ChromaVectorStore( collectionName = "my_collection", url = "http://localhost:8000" ) // Pinecone 向量存储 val pineconeStore = PineconeVectorStore( apiKey = "your-pinecone-api-key", environment = "us-west1-gcp", index = "my-index" ) // Qdrant 向量存储 val qdrantStore = QdrantVectorStore( url = "http://localhost:6333", collectionName = "my_collection" ) ``` ## 检索和重排序 ✅ Kastrax 提供了多种检索和重排序策略,用于提高检索质量: ```kotlin import ai.kastrax.rag.retrieval.* import ai.kastrax.rag.reranker.* // 基本检索 val results = rag.search( query = "查询内容", limit = 5, minScore = 0.5 ) // 使用重排序器 val reranker = CrossEncoderReranker( model = "cross-encoder/ms-marco-MiniLM-L-6-v2" ) val rag = RAG( vectorStore = vectorStore, embeddingService = embeddingService, reranker = reranker ) // 混合检索 val hybridRetriever = HybridRetriever( vectorRetriever = VectorRetriever(vectorStore, embeddingService), keywordRetriever = KeywordRetriever(documents), vectorWeight = 0.7, keywordWeight = 0.3 ) val results = hybridRetriever.retrieve( query = "查询内容", limit = 5 ) ``` ## 与代理集成 ✅ Kastrax RAG 系统可以与代理无缝集成: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.kastrax import ai.kastrax.rag.RAG import ai.kastrax.rag.document.DocumentLoader import ai.kastrax.rag.embedding.FastEmbeddingService import ai.kastrax.rag.vectorstore.InMemoryVectorStore import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 RAG 组件 val embeddingService = FastEmbeddingService.create() val vectorStore = InMemoryVectorStore() val rag = RAG(vectorStore, embeddingService) // 加载文档 val loader = DocumentLoader.fromFile("knowledge_base.pdf") val documents = loader.load() rag.addDocuments(documents) // 创建带有 RAG 的 KastraX 实例 val kastraxInstance = kastrax { // 配置 RAG ragSystem(rag) // 配置代理 agent("assistant") { name("助手") description("一个具有知识访问能力的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 使用 RAG useRag(true) } } // 使用带有 RAG 的代理 val assistant = kastraxInstance.getAgent("assistant") val response = assistant.generate("知识库关于气候变化说了什么?") println(response.text) } ``` ## 工作流集成 ✅ Kastrax RAG 系统也可以与工作流集成: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.workflow.workflow import ai.kastrax.rag.RAG import ai.kastrax.rag.document.DirectoryDocumentLoader import ai.kastrax.rag.document.RecursiveCharacterTextSplitter import ai.kastrax.rag.embedding.FastEmbeddingService import ai.kastrax.rag.vectorstore.InMemoryVectorStore import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import java.io.File fun main() = runBlocking { // 创建 RAG 系统 val embeddingService = FastEmbeddingService.create() val vectorStore = InMemoryVectorStore() val rag = RAG(vectorStore, embeddingService) // 加载文档 val loader = DirectoryDocumentLoader( directory = File("knowledge_base"), recursive = true ) val splitter = RecursiveCharacterTextSplitter() rag.loadDocuments(loader, splitter) // 创建代理 val researchAgent = agent { name("研究助手") instructions("你是一个研究助手,负责查找和总结信息。") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) } } // 创建工作流 val researchWorkflow = workflow { // 步骤 1:查询 RAG 系统 step("search") { input("query") execute { input -> val query = input.getString("query") val results = rag.search(query, limit = 5) val context = results.joinToString("\n\n") { "来源: ${it.document.metadata["source"]}\n${it.document.content}" } mapOf("context" to context) } } // 步骤 2:使用代理总结结果 step("summarize") { input("query", "context") agent(researchAgent) prompt(""" 请根据以下信息回答问题。 问题: {{query}} 信息: {{context}} 请提供详细、准确的回答,并引用信息来源。 """) output("summary") } // 工作流输出 output { "summary" from "summarize.summary" } } // 执行工作流 val result = researchWorkflow.execute(mapOf( "query" to "气候变化对农业有什么影响?" )) println("研究结果:") println(result.getString("summary")) } ``` ## 高级功能 ✅ ### 语义分块 语义分块器根据语义相似性将文本分割成有意义的块: ```kotlin import ai.kastrax.rag.document.SemanticDocumentSplitter import ai.kastrax.rag.embedding.FastEmbeddingService // 创建嵌入服务 val embeddingService = FastEmbeddingService.create() // 创建语义分块器 val semanticSplitter = SemanticDocumentSplitter( embeddingService = embeddingService, chunkSize = 1000, chunkOverlap = 200, similarityThreshold = 0.7 ) // 分割文档 val chunks = semanticSplitter.split(document) ``` ### 混合检索 混合检索结合了向量检索和关键词检索的优点: ```kotlin import ai.kastrax.rag.retrieval.HybridRetriever import ai.kastrax.rag.retrieval.KeywordRetriever import ai.kastrax.rag.retrieval.VectorRetriever // 创建向量检索器 val vectorRetriever = VectorRetriever( vectorStore = vectorStore, embeddingService = embeddingService, topK = 10 ) // 创建关键词检索器 val keywordRetriever = KeywordRetriever( documents = documents, topK = 10 ) // 创建混合检索器 val hybridRetriever = HybridRetriever( vectorRetriever = vectorRetriever, keywordRetriever = keywordRetriever, vectorWeight = 0.7, keywordWeight = 0.3 ) // 检索文档 val results = hybridRetriever.retrieve( query = "气候变化对农业的影响", limit = 5 ) ``` ### 上下文压缩 上下文压缩可以减少发送到 LLM 的文本量,同时保留关键信息: ```kotlin import ai.kastrax.rag.contextbuilder.ContextCompressor import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel // 创建上下文压缩器 val contextCompressor = ContextCompressor( model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) }, maxTokens = 2000 ) // 压缩上下文 val compressedContext = contextCompressor.compress( query = "气候变化对农业有什么影响?", documents = retrievedDocuments ) ``` ## 完整示例 ✅ 以下是一个完整的示例,展示了如何使用 Kastrax RAG 系统创建一个知识库问答应用: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.rag.RAG import ai.kastrax.rag.document.DirectoryDocumentLoader import ai.kastrax.rag.document.RecursiveCharacterTextSplitter import ai.kastrax.rag.embedding.FastEmbeddingService import ai.kastrax.rag.vectorstore.InMemoryVectorStore import ai.kastrax.rag.reranker.CrossEncoderReranker import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import java.io.File fun main() = runBlocking { println("初始化 RAG 系统...") // 创建嵌入服务和向量存储 val embeddingService = FastEmbeddingService.create() val vectorStore = InMemoryVectorStore() // 创建重排序器 val reranker = CrossEncoderReranker( model = "cross-encoder/ms-marco-MiniLM-L-6-v2" ) // 创建 RAG 系统 val rag = RAG( vectorStore = vectorStore, embeddingService = embeddingService, reranker = reranker ) // 加载文档 println("加载文档...") val loader = DirectoryDocumentLoader( directory = File("knowledge_base"), recursive = true, fileExtensions = listOf("txt", "md", "pdf") ) val splitter = RecursiveCharacterTextSplitter( chunkSize = 1000, chunkOverlap = 200 ) val documentCount = rag.loadDocuments(loader, splitter) println("已加载 $documentCount 个文档块") // 创建代理 val qaAgent = agent { name("知识库助手") instructions(""" 你是一个知识库助手,负责回答用户关于知识库内容的问题。 使用提供的上下文信息回答问题,不要编造信息。 如果上下文中没有足够的信息,请坦诚地说明你不知道。 引用信息来源,使用上下文中提供的元数据。 """) model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.3) } } // 交互式问答循环 println("\n知识库助手已准备就绪。输入 'exit' 退出。") while (true) { print("\n问题: ") val query = readLine() ?: "" if (query.lowercase() == "exit") { break } // 检索相关文档 val results = rag.search(query, limit = 5, minScore = 0.5) if (results.isEmpty()) { println("没有找到相关信息。") continue } // 构建上下文 val context = results.joinToString("\n\n") { result -> val source = result.document.metadata["source"] ?: "未知来源" "来源: $source\n${result.document.content}" } // 构建提示 val prompt = """ 请根据以下信息回答问题。 问题: $query 信息: $context 请提供详细、准确的回答,并引用信息来源。 """.trimIndent() // 生成回答 val response = qaAgent.generate(prompt) println("\n回答:") println(response.text) } // 关闭资源 embeddingService.close() println("\n已退出知识库助手。") } ``` ## 总结 ✅ Kastrax 的 Kotlin RAG 系统提供了一个强大而灵活的框架,用于构建基于知识的 AI 应用程序。通过结合文档处理、嵌入、向量存储和检索功能,您可以创建能够访问和利用外部知识的智能代理。 无论您是构建问答系统、研究助手还是知识管理工具,Kastrax RAG 系统都能提供所需的工具和组件,帮助您创建更智能、更有用的 AI 应用程序。 --- title: RAG 系统概述 | Kastrax 文档 description: Kastrax 中检索增强生成 (RAG) 的概述,详细说明其使用相关上下文增强 LLM 输出的能力。 --- # RAG 系统概述 ✅ [ZH] Source: https://kastrax.ai/zh/docs/rag/overview Kastrax 检索增强生成 (RAG) 系统提供了一个全面的框架,用于构建结合大型语言模型和外部数据源的基于知识的应用程序。本指南解释了 RAG 系统架构以及如何有效地使用它。 ## 什么是 RAG? ✅ 检索增强生成 (RAG) 是一种通过从外部知识源检索相关信息并将其纳入生成过程来增强语言模型输出的技术。这种方法有助于: - 提供超出模型训练数据的最新信息 - 将响应基于事实、可验证的来源 - 减少幻觉并提高准确性 - 基于特定领域知识定制响应 ## RAG 系统架构 ✅ Kastrax RAG 系统由几个组件组成: 1. **文档处理**:加载、分块和转换文档 2. **嵌入**:将文本转换为向量表示 3. **向量存储**:高效存储和检索向量 4. **检索**:基于查询查找相关信息 5. **重排序**:通过额外的排序提高检索质量 6. **上下文构建**:使用检索到的信息构建有效的提示 7. **生成**:使用增强的上下文生成响应 ## 基本 RAG 流程 ✅ 以下是在 Kastrax 中创建 RAG 流程的简单示例: ```kotlin import ai.kastrax.rag.document.DocumentLoader import ai.kastrax.rag.document.TextSplitter import ai.kastrax.rag.embedding.EmbeddingModel import ai.kastrax.rag.vectorstore.VectorStore import ai.kastrax.rag.retrieval.Retriever import ai.kastrax.rag.context.ContextBuilder import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createRagPipeline() = runBlocking { // 1. 加载文档 val loader = DocumentLoader.fromFile("knowledge_base.pdf") val documents = loader.load() // 2. 将文档分割成块 val splitter = TextSplitter.recursive( chunkSize = 1000, chunkOverlap = 200 ) val chunks = splitter.split(documents) // 3. 创建嵌入 val embeddingModel = EmbeddingModel.create("all-MiniLM-L6-v2") // 4. 存储在向量数据库中 val vectorStore = VectorStore.create("chroma") vectorStore.addDocuments(chunks, embeddingModel) // 5. 创建检索器 val retriever = Retriever.fromVectorStore( vectorStore = vectorStore, embeddingModel = embeddingModel, topK = 5 ) // 6. 创建上下文构建器 val contextBuilder = ContextBuilder.create { template(""" 根据以下上下文回答问题: 上下文: {{context}} 问题:{{query}} 回答: """.trimIndent()) } // 7. 创建 RAG 代理 val ragAgent = agent { name("RAG代理") description("一个具有 RAG 能力的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 配置 RAG rag { retriever(retriever) contextBuilder(contextBuilder) maxTokens(3000) } } // 使用 RAG 代理 val query = "Kastrax 的主要特点是什么?" val response = ragAgent.generate(query) println(response.text) } ``` ## 文档处理 ✅ ### 加载文档 ✅ Kastrax 支持从各种来源加载文档: ```kotlin // 从文件加载 val fileLoader = DocumentLoader.fromFile("document.pdf") // 从目录加载 val dirLoader = DocumentLoader.fromDirectory("documents/") // 从 URL 加载 val webLoader = DocumentLoader.fromUrl("https://example.com/document") // 从数据库加载 val dbLoader = DocumentLoader.fromDatabase( connection = dbConnection, query = "SELECT content FROM documents" ) ``` ### 文档格式 ✅ Kastrax 支持多种文档格式: - PDF - Word (DOCX) - 文本 - HTML - Markdown - CSV - JSON - XML - 数据库 ### 分块文档 ✅ 文档可以被分割成块以便更好地处理: ```kotlin // 递归文本分割器(按段落、句子等分割) val recursiveSplitter = TextSplitter.recursive( chunkSize = 1000, chunkOverlap = 200 ) // 字符文本分割器 val charSplitter = TextSplitter.byCharacter( chunkSize = 1000, chunkOverlap = 200, separator = "\n\n" ) // 令牌文本分割器 val tokenSplitter = TextSplitter.byToken( chunkSize = 500, chunkOverlap = 100, encoderName = "cl100k_base" // OpenAI 的分词器 ) // 语义文本分割器 val semanticSplitter = TextSplitter.semantic( embeddingModel = embeddingModel, similarityThreshold = 0.7 ) ``` ## 嵌入 ✅ ### 嵌入模型 ✅ Kastrax 支持各种嵌入模型: ```kotlin // 使用本地模型 val localModel = EmbeddingModel.create("all-MiniLM-L6-v2") // 使用 OpenAI 嵌入 val openAiModel = EmbeddingModel.openAi( apiKey = "your-openai-api-key", model = "text-embedding-3-small" ) // 使用自定义嵌入 val customModel = EmbeddingModel.custom( embeddingFunction = { text -> // 自定义嵌入逻辑 floatArrayOf(/* ... */) }, dimensions = 384 ) ``` ## 向量存储 ✅ ### 向量存储 ✅ Kastrax 支持多种向量存储: ```kotlin // 内存向量存储 val memoryStore = VectorStore.inMemory() // Chroma 向量存储 val chromaStore = VectorStore.chroma( collectionName = "documents", url = "http://localhost:8000" ) // FAISS 向量存储 val faissStore = VectorStore.faiss( indexPath = "faiss_index", dimensions = 384 ) // Pinecone 向量存储 val pineconeStore = VectorStore.pinecone( apiKey = "your-pinecone-api-key", environment = "us-west1-gcp", index = "documents" ) ``` ## 检索 ✅ ### 检索器 ✅ 检索器基于查询查找相关信息: ```kotlin // 向量存储检索器 val vectorRetriever = Retriever.fromVectorStore( vectorStore = vectorStore, embeddingModel = embeddingModel, topK = 5 ) // 关键词检索器 val keywordRetriever = Retriever.keyword( documents = documents, topK = 5 ) // 混合检索器(结合向量和关键词搜索) val hybridRetriever = Retriever.hybrid( vectorRetriever = vectorRetriever, keywordRetriever = keywordRetriever, vectorWeight = 0.7, keywordWeight = 0.3 ) ``` ## 重排序 ✅ ### 重排序器 ✅ 重排序器提高检索质量: ```kotlin // 交叉编码器重排序器 val crossEncoderReranker = Reranker.crossEncoder( model = "cross-encoder/ms-marco-MiniLM-L-6-v2", topK = 3 ) // LLM 重排序器 val llmReranker = Reranker.llm( llm = deepSeekLlm, prompt = "根据与查询的相关性对这些文档进行排名:{{query}}\n\n文档:\n{{documents}}", topK = 3 ) ``` ## 完整 RAG 示例 ✅ 以下是一个复杂 RAG 系统的完整示例: ```kotlin import ai.kastrax.rag.* import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createAdvancedRagSystem() = runBlocking { // 1. 加载和处理文档 val loader = DocumentLoader.fromDirectory("knowledge_base/") val documents = loader.load() val splitter = TextSplitter.recursive( chunkSize = 1000, chunkOverlap = 200 ) val chunks = splitter.split(documents) // 2. 设置嵌入和向量存储 val embeddingModel = EmbeddingModel.create("all-MiniLM-L6-v2") val vectorStore = VectorStore.chroma("kastrax_docs") // 3. 将文档添加到向量存储 vectorStore.addDocuments( documents = chunks, embeddingModel = embeddingModel ) // 4. 创建混合检索器 val vectorRetriever = Retriever.fromVectorStore( vectorStore = vectorStore, embeddingModel = embeddingModel, topK = 10 ) val keywordRetriever = Retriever.keyword( documents = chunks, topK = 10 ) val hybridRetriever = Retriever.hybrid( vectorRetriever = vectorRetriever, keywordRetriever = keywordRetriever, vectorWeight = 0.7, keywordWeight = 0.3 ) // 5. 添加重排序 val reranker = Reranker.crossEncoder( model = "cross-encoder/ms-marco-MiniLM-L-6-v2", topK = 5 ) val enhancedRetriever = Retriever.withReranker( baseRetriever = hybridRetriever, reranker = reranker ) // 6. 创建带压缩的上下文构建器 val contextBuilder = ContextBuilder.create { template(""" 你是 Kastrax(一个强大的 AI 代理框架)的有帮助的助手。 根据以下来自 Kastrax 文档的上下文回答问题。 上下文: {{#each documents}} 文档 {{@index}}(来源:{{metadata.source}}): {{pageContent}} {{/each}} 问题:{{query}} 仅使用上下文中提供的信息回答问题。 如果你没有足够的信息,请说"我没有足够的信息。" 提供信息时,始终引用源文档。 """.trimIndent()) compression { enabled(true) method(CompressionMethod.MAP_REDUCE) maxTokens(3000) } } // 7. 创建 RAG 代理 val ragAgent = agent { name("Kastrax文档助手") description("一个帮助处理 Kastrax 文档的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.3) // 较低的温度以获得更事实性的响应 } // 配置 RAG rag { retriever(enhancedRetriever) contextBuilder(contextBuilder) maxTokens(3000) } } // 8. 使用 RAG 代理 val query = "我如何在 Kastrax 中实现自定义工具?" val response = ragAgent.generate(query) println(response.text) } ``` ## 下一步 ✅ 现在您已经了解了 RAG 系统,您可以: 1. 了解[文档处理](./document-processing.mdx) 2. 探索[向量存储](./vector-stores.mdx) 3. 实现[高级检索技术](./advanced-retrieval.mdx) 4. 设置[RAG 评估](./evaluation.mdx) --- title: "检索、语义搜索、重排序 | RAG | Kastrax 文档" description: Kastrax RAG 系统中检索过程的指南,包括语义搜索、过滤和重排序。 --- import { Tabs } from "nextra/components"; ## RAG 系统中的检索 ✅ [ZH] Source: https://kastrax.ai/zh/docs/rag/retrieval 存储嵌入后,您需要检索相关的分块来回答用户查询。 Kastrax 提供灵活的检索选项,支持语义搜索、过滤和重排序。 ## 检索的工作原理 ✅ 1. 用户的查询使用与文档嵌入相同的模型转换为嵌入 2. 使用向量相似度将此嵌入与存储的嵌入进行比较 3. 检索最相似的分块,并可以选择性地: - 通过元数据进行过滤 - 重排序以获得更好的相关性 - 通过知识图谱进行处理 ## 基本检索 ✅ 最简单的方法是直接语义搜索。此方法使用向量相似度来查找与查询在语义上相似的分块: ```ts showLineNumbers copy import { openai } from "@ai-sdk/openai"; import { embed } from "ai"; import { PgVector } from "@kastrax/pg"; // 将查询转换为嵌入 const { embedding } = await embed({ value: "文章中的主要观点是什么?", model: openai.embedding('text-embedding-3-small'), }); // 查询向量存储 const pgVector = new PgVector(process.env.POSTGRES_CONNECTION_STRING); const results = await pgVector.query({ indexName: "embeddings", queryVector: embedding, topK: 10, }); // 显示结果 console.log(results); ``` 结果包括文本内容和相似度分数: ```ts showLineNumbers copy [ { text: "气候变化对全球农业构成重大挑战...", score: 0.89, metadata: { source: "article1.txt" } }, { text: "气温上升影响作物产量...", score: 0.82, metadata: { source: "article1.txt" } } // ... 更多结果 ] ``` 有关如何使用基本检索方法的示例,请参见[检索结果](../../examples/rag/query/retrieve-results.mdx)示例。 ## 高级检索选项 ✅ ### 元数据过滤 基于元数据字段过滤结果以缩小搜索范围。当您有来自不同来源、时间段或具有特定属性的文档时,这非常有用。Kastrax 提供了一种统一的 MongoDB 风格查询语法,适用于所有支持的向量存储。 有关可用运算符和语法的详细信息,请参见[元数据过滤器参考](/reference/rag/metadata-filters)。 基本过滤示例: ```ts showLineNumbers copy // 简单相等过滤 const results = await pgVector.query({ indexName: "embeddings", queryVector: embedding, topK: 10, filter: { source: "article1.txt" } }); // 数值比较 const results = await pgVector.query({ indexName: "embeddings", queryVector: embedding, topK: 10, filter: { price: { $gt: 100 } } }); // 多个条件 const results = await pgVector.query({ indexName: "embeddings", queryVector: embedding, topK: 10, filter: { category: "electronics", price: { $lt: 1000 }, inStock: true } }); // 数组操作 const results = await pgVector.query({ indexName: "embeddings", queryVector: embedding, topK: 10, filter: { tags: { $in: ["sale", "new"] } } }); // 逻辑运算符 const results = await pgVector.query({ indexName: "embeddings", queryVector: embedding, topK: 10, filter: { $or: [ { category: "electronics" }, { category: "accessories" } ], $and: [ { price: { $gt: 50 } }, { price: { $lt: 200 } } ] } }); ``` 元数据过滤的常见用例: - 按文档来源或类型过滤 - 按日期范围过滤 - 按特定类别或标签过滤 - 按数值范围过滤(例如,价格、评分) - 组合多个条件进行精确查询 - 按文档属性过滤(例如,语言、作者) 有关如何使用元数据过滤的示例,请参见[混合向量搜索](../../examples/rag/query/hybrid-vector-search.mdx)示例。 ### 向量查询工具 有时您希望让您的代理能够直接查询向量数据库。向量查询工具允许您的代理负责检索决策,根据代理对用户需求的理解,将语义搜索与可选的过滤和重排序相结合。 ```ts showLineNumbers copy const vectorQueryTool = createVectorQueryTool({ vectorStoreName: 'pgVector', indexName: 'embeddings', model: openai.embedding('text-embedding-3-small'), }); ``` 创建工具时,特别注意工具的名称和描述 - 这些有助于代理理解何时以及如何使用检索功能。例如,您可以将其命名为"SearchKnowledgeBase",并将其描述为"搜索我们的文档以查找有关 X 主题的相关信息。" 这在以下情况下特别有用: - 您的代理需要动态决定要检索哪些信息 - 检索过程需要复杂的决策 - 您希望代理根据上下文组合多种检索策略 有关详细配置选项和高级用法,请参见[向量查询工具参考](/reference/tools/vector-query-tool)。 ### 向量存储提示 向量存储提示为每个向量数据库实现定义查询模式和过滤功能。 实现过滤时,这些提示需要在代理的指令中指定,以指定每个向量存储实现的有效运算符和语法。 ```ts showLineNumbers copy import { openai } from '@ai-sdk/openai'; import { PGVECTOR_PROMPT } from "@kastrax/pg"; export const ragAgent = new Agent({ name: 'RAG 代理', model: openai('gpt-4o-mini'), instructions: ` 使用提供的上下文处理查询。构建简洁相关的响应。 ${PGVECTOR_PROMPT} `, tools: { vectorQueryTool }, }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { openai } from '@ai-sdk/openai'; import { PINECONE_PROMPT } from "@kastrax/pinecone"; export const ragAgent = new Agent({ name: 'RAG 代理', model: openai('gpt-4o-mini'), instructions: ` 使用提供的上下文处理查询。构建简洁相关的响应。 ${PINECONE_PROMPT} `, tools: { vectorQueryTool }, }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { openai } from '@ai-sdk/openai'; import { QDRANT_PROMPT } from "@kastrax/qdrant"; export const ragAgent = new Agent({ name: 'RAG 代理', model: openai('gpt-4o-mini'), instructions: ` 使用提供的上下文处理查询。构建简洁相关的响应。 ${QDRANT_PROMPT} `, tools: { vectorQueryTool }, }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { openai } from '@ai-sdk/openai'; import { CHROMA_PROMPT } from "@kastrax/chroma"; export const ragAgent = new Agent({ name: 'RAG 代理', model: openai('gpt-4o-mini'), instructions: ` 使用提供的上下文处理查询。构建简洁相关的响应。 ${CHROMA_PROMPT} `, tools: { vectorQueryTool }, }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { openai } from '@ai-sdk/openai'; import { ASTRA_PROMPT } from "@kastrax/astra"; export const ragAgent = new Agent({ name: 'RAG 代理', model: openai('gpt-4o-mini'), instructions: ` 使用提供的上下文处理查询。构建简洁相关的响应。 ${ASTRA_PROMPT} `, tools: { vectorQueryTool }, }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { openai } from '@ai-sdk/openai'; import { LIBSQL_PROMPT } from "@kastrax/libsql"; export const ragAgent = new Agent({ name: 'RAG 代理', model: openai('gpt-4o-mini'), instructions: ` 使用提供的上下文处理查询。构建简洁相关的响应。 ${LIBSQL_PROMPT} `, tools: { vectorQueryTool }, }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { openai } from '@ai-sdk/openai'; import { UPSTASH_PROMPT } from "@kastrax/upstash"; export const ragAgent = new Agent({ name: 'RAG 代理', model: openai('gpt-4o-mini'), instructions: ` 使用提供的上下文处理查询。构建简洁相关的响应。 ${UPSTASH_PROMPT} `, tools: { vectorQueryTool }, }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { openai } from '@ai-sdk/openai'; import { VECTORIZE_PROMPT } from "@kastrax/vectorize"; export const ragAgent = new Agent({ name: 'RAG 代理', model: openai('gpt-4o-mini'), instructions: ` 使用提供的上下文处理查询。构建简洁相关的响应。 ${VECTORIZE_PROMPT} `, tools: { vectorQueryTool }, }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { openai } from '@ai-sdk/openai'; import { MONGODB_PROMPT } from "@kastrax/mongodb"; export const ragAgent = new Agent({ name: 'RAG 代理', model: openai('gpt-4o-mini'), instructions: ` 使用提供的上下文处理查询。构建简洁相关的响应。 ${MONGODB_PROMPT} `, tools: { vectorQueryTool }, }); ``` ### 重排序 初始向量相似度搜索有时可能会错过细微的相关性。重排序是一种计算成本更高但更准确的算法,通过以下方式改进结果: - 考虑词序和精确匹配 - 应用更复杂的相关性评分 - 使用查询和文档之间的交叉注意力方法 以下是如何使用重排序: ```ts showLineNumbers copy import { openai } from "@ai-sdk/openai"; import { rerank } from "@kastrax/rag"; // 从向量搜索获取初始结果 const initialResults = await pgVector.query({ indexName: "embeddings", queryVector: queryEmbedding, topK: 10, }); // 重排序结果 const rerankedResults = await rerank(initialResults, query, openai('gpt-4o-mini')); ``` > **注意:** 为了使语义评分在重排序期间正常工作,每个结果必须在其 `metadata.text` 字段中包含文本内容。 重排序的结果将向量相似度与语义理解相结合,以提高检索质量。 有关重排序的更多详细信息,请参见 [rerank()](/reference/rag/rerank) 方法。 有关如何使用重排序方法的示例,请参见[重排序结果](../../examples/rag/rerank/rerank.mdx)示例。 ### 基于图的检索 对于具有复杂关系的文档,基于图的检索可以跟踪分块之间的连接。这在以下情况下很有帮助: - 信息分布在多个文档中 - 文档相互引用 - 您需要遍历关系以找到完整答案 示例设置: ```ts showLineNumbers copy const graphQueryTool = createGraphQueryTool({ vectorStoreName: 'pgVector', indexName: 'embeddings', model: openai.embedding('text-embedding-3-small'), graphOptions: { threshold: 0.7, } }); ``` 有关基于图的检索的更多详细信息,请参见 [GraphRAG](/reference/rag/graph-rag) 类和 [createGraphQueryTool()](/reference/tools/graph-rag-tool) 函数。 有关如何使用基于图的检索方法的示例,请参见[基于图的检索](../../examples/rag/usage/graph-rag.mdx)示例。 --- title: "在向量数据库中存储嵌入 | Kastrax 文档" description: Kastrax 中向量存储选项的指南,包括用于相似度搜索的嵌入式和专用向量数据库。 --- import { Tabs } from "nextra/components"; ## 在向量数据库中存储嵌入 ✅ [ZH] Source: https://kastrax.ai/zh/docs/rag/vector-databases 生成嵌入后,您需要将它们存储在支持向量相似度搜索的数据库中。Kastrax 为不同的向量数据库提供了一致的接口,用于存储和查询嵌入。 ## 支持的数据库 ✅ ```ts filename="vector-store.ts" showLineNumbers copy import { PgVector } from '@kastrax/pg'; const store = new PgVector(process.env.POSTGRES_CONNECTION_STRING) await store.createIndex({ indexName: "myCollection", dimension: 1536, }); await store.upsert({ indexName: "myCollection", vectors: embeddings, metadata: chunks.map(chunk => ({ text: chunk.text })), }); ``` ### 使用带 pgvector 的 PostgreSQL 带有 pgvector 扩展的 PostgreSQL 对于已经使用 PostgreSQL 并希望最小化基础设施复杂性的团队来说是一个很好的解决方案。 有关详细的设置说明和最佳实践,请参见[官方 pgvector 仓库](https://github.com/pgvector/pgvector)。 ```ts filename="vector-store.ts" showLineNumbers copy import { PineconeVector } from '@kastrax/pinecone' const store = new PineconeVector(process.env.PINECONE_API_KEY) await store.createIndex({ indexName: "myCollection", dimension: 1536, }); await store.upsert({ indexName: "myCollection", vectors: embeddings, metadata: chunks.map(chunk => ({ text: chunk.text })), }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { QdrantVector } from '@kastrax/qdrant' const store = new QdrantVector({ url: process.env.QDRANT_URL, apiKey: process.env.QDRANT_API_KEY }) await store.createIndex({ indexName: "myCollection", dimension: 1536, }); await store.upsert({ indexName: "myCollection", vectors: embeddings, metadata: chunks.map(chunk => ({ text: chunk.text })), }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { ChromaVector } from '@kastrax/chroma' const store = new ChromaVector() await store.createIndex({ indexName: "myCollection", dimension: 1536, }); await store.upsert({ indexName: "myCollection", vectors: embeddings, metadata: chunks.map(chunk => ({ text: chunk.text })), }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { AstraVector } from '@kastrax/astra' const store = new AstraVector({ token: process.env.ASTRA_DB_TOKEN, endpoint: process.env.ASTRA_DB_ENDPOINT, keyspace: process.env.ASTRA_DB_KEYSPACE }) await store.createIndex({ indexName: "myCollection", dimension: 1536, }); await store.upsert({ indexName: "myCollection", vectors: embeddings, metadata: chunks.map(chunk => ({ text: chunk.text })), }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { LibSQLVector } from "@kastrax/core/vector/libsql"; const store = new LibSQLVector({ connectionUrl: process.env.DATABASE_URL, authToken: process.env.DATABASE_AUTH_TOKEN // 可选:用于 Turso 云数据库 }) await store.createIndex({ indexName: "myCollection", dimension: 1536, }); await store.upsert({ indexName: "myCollection", vectors: embeddings, metadata: chunks.map(chunk => ({ text: chunk.text })), }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { UpstashVector } from '@kastrax/upstash' const store = new UpstashVector({ url: process.env.UPSTASH_URL, token: process.env.UPSTASH_TOKEN }) await store.createIndex({ indexName: "myCollection", dimension: 1536, }); await store.upsert({ indexName: "myCollection", vectors: embeddings, metadata: chunks.map(chunk => ({ text: chunk.text })), }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { CloudflareVector } from '@kastrax/vectorize' const store = new CloudflareVector({ accountId: process.env.CF_ACCOUNT_ID, apiToken: process.env.CF_API_TOKEN }) await store.createIndex({ indexName: "myCollection", dimension: 1536, }); await store.upsert({ indexName: "myCollection", vectors: embeddings, metadata: chunks.map(chunk => ({ text: chunk.text })), }); ``` ```ts filename="vector-store.ts" showLineNumbers copy import { MongoDBVector } from '@kastrax/mongodb' const store = new MongoDBVector({ url: process.env.MONGODB_URL, database: process.env.MONGODB_DATABASE }) await store.createIndex({ indexName: "myCollection", dimension: 1536, }); await store.upsert({ indexName: "myCollection", vectors: embeddings, metadata: chunks.map(chunk => ({ text: chunk.text })), }); ``` ## 使用向量存储 ✅ 初始化后,所有向量存储共享相同的接口用于创建索引、更新嵌入和查询。 ### 创建索引 在存储嵌入之前,您需要创建一个具有适合您的嵌入模型的维度大小的索引: ```ts filename="store-embeddings.ts" showLineNumbers copy // 创建维度为 1536 的索引(适用于 text-embedding-3-small) await store.createIndex({ indexName: 'myCollection', dimension: 1536, }); ``` 维度大小必须与您选择的嵌入模型的输出维度匹配。常见的维度大小有: - OpenAI text-embedding-3-small:1536 维(或自定义,例如 256) - Cohere embed-multilingual-v3:1024 维 - Google `text-embedding-004`:768 维(或自定义) > **重要**:索引维度在创建后无法更改。要使用不同的模型,请删除并使用新的维度大小重新创建索引。 ### 数据库命名规则 每个向量数据库都强制执行特定的索引和集合命名约定,以确保兼容性并防止冲突。 索引名称必须: - 以字母或下划线开头 - 只包含字母、数字和下划线 - 示例:`my_index_123` 有效 - 示例:`my-index` 无效(包含连字符) 索引名称必须: - 只使用小写字母、数字和破折号 - 不包含点(用于 DNS 路由) - 不使用非拉丁字符或表情符号 - 与项目 ID 组合的长度不超过 52 个字符 - 示例:`my-index-123` 有效 - 示例:`my.index` 无效(包含点) 集合名称必须: - 长度为 1-255 个字符 - 不包含以下特殊字符: - `< > : " / \ | ? *` - 空字符 (`\0`) - 单元分隔符 (`\u{1F}`) - 示例:`my_collection_123` 有效 - 示例:`my/collection` 无效(包含斜杠) 集合名称必须: - 长度为 3-63 个字符 - 以字母或数字开头和结尾 - 只包含字母、数字、下划线或连字符 - 不包含连续的句点(..) - 不是有效的 IPv4 地址 - 示例:`my-collection-123` 有效 - 示例:`my..collection` 无效(连续句点) 集合名称必须: - 不为空 - 不超过 48 个字符 - 只包含字母、数字和下划线 - 示例:`my_collection_123` 有效 - 示例:`my-collection` 无效(包含连字符) 索引名称必须: - 以字母或下划线开头 - 只包含字母、数字和下划线 - 示例:`my_index_123` 有效 - 示例:`my-index` 无效(包含连字符) 命名空间名称必须: - 长度为 2-100 个字符 - 只包含: - 字母数字字符(a-z、A-Z、0-9) - 下划线、连字符、点 - 不以特殊字符(_、-、.)开头或结尾 - 可以区分大小写 - 示例:`MyNamespace123` 有效 - 示例:`_namespace` 无效(以下划线开头) 索引名称必须: - 以字母开头 - 长度小于 32 个字符 - 只包含小写 ASCII 字母、数字和破折号 - 使用破折号代替空格 - 示例:`my-index-123` 有效 - 示例:`My_Index` 无效(大写和下划线) 集合(索引)名称必须: - 以字母或下划线开头 - 长度不超过 120 字节 - 只包含字母、数字、下划线或点 - 不能包含 `$` 或空字符 - 示例:`my_collection.123` 有效 - 示例:`my-index` 无效(包含连字符) - 示例:`My$Collection` 无效(包含 `$`) ### 更新嵌入 创建索引后,您可以存储嵌入及其基本元数据: ```ts filename="store-embeddings.ts" showLineNumbers copy // 存储嵌入及其对应的元数据 await store.upsert({ indexName: 'myCollection', // 索引名称 vectors: embeddings, // 嵌入向量数组 metadata: chunks.map(chunk => ({ text: chunk.text, // 原始文本内容 id: chunk.id // 可选的唯一标识符 })) }); ``` upsert 操作: - 接受嵌入向量数组及其对应的元数据 - 如果共享相同的 ID,则更新现有向量 - 如果不存在,则创建新向量 - 自动处理大型数据集的批处理 有关在不同向量存储中更新嵌入的完整示例,请参见[更新嵌入](../../examples/rag/upsert/upsert-embeddings.mdx)指南。 ## 添加元数据 ✅ 向量存储支持丰富的元数据(任何可 JSON 序列化的字段)用于过滤和组织。由于元数据存储没有固定的模式,请使用一致的字段命名以避免意外的查询结果。 **重要**:元数据对于向量存储至关重要 - 没有它,您只有数值嵌入,没有办法返回原始文本或过滤结果。始终至少将源文本存储为元数据。 ```ts showLineNumbers copy // 存储嵌入及丰富的元数据,以便更好地组织和过滤 await store.upsert({ indexName: "myCollection", vectors: embeddings, metadata: chunks.map((chunk) => ({ // 基本内容 text: chunk.text, id: chunk.id, // 文档组织 source: chunk.source, category: chunk.category, // 时间元数据 createdAt: new Date().toISOString(), version: "1.0", // 自定义字段 language: chunk.language, author: chunk.author, confidenceScore: chunk.score, })), }); ``` 元数据关键考虑因素: - 严格遵守字段命名 - 'category' 与 'Category' 等不一致会影响查询 - 只包含您计划过滤或排序的字段 - 额外的字段会增加开销 - 添加时间戳(例如,'createdAt'、'lastUpdated')以跟踪内容的新鲜度 ## 最佳实践 ✅ - 在批量插入之前创建索引 - 对大型插入使用批处理操作(upsert 方法自动处理批处理) - 只存储您将查询的元数据 - 将嵌入维度与您的模型匹配(例如,`text-embedding-3-small` 为 1536) --- title: 内置工具 | Kastrax 文档 description: Kastrax 提供的内置工具详细指南,包括计算器、网络搜索、文件系统等工具。 --- # 内置工具 ✅ [ZH] Source: https://kastrax.ai/zh/docs/tools/built-in-tools Kastrax 提供了一系列开箱即用的内置工具。这些工具涵盖了常见用例,如计算、网络搜索、文件操作等。本指南将介绍如何在代理中使用这些内置工具。 ## 工具工厂 ✅ 使用内置工具的最简单方法是通过 `ToolFactory` 类: ```kotlin import ai.kastrax.core.tools.ToolFactory import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.web.WebSearchTool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建使用内置工具的代理 val myAgent = agent { name("内置工具代理") description("一个使用内置工具的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 添加内置工具 tools { // 添加计算器工具 add(ToolFactory.createCalculatorTool()) // 添加网络搜索工具 add(ToolFactory.createWebSearchTool( apiKey = "your-search-api-key", searchEngine = WebSearchTool.SearchEngine.GOOGLE, maxResults = 5 )) // 添加文件系统工具 add(ToolFactory.createFileSystemTool( rootPath = "./data", allowAbsolutePaths = false )) } } // 使用代理 val response = myAgent.generate("144 的平方根是多少?") println(response.text) } ``` ## 计算器工具 ✅ 计算器工具执行数学计算: ```kotlin import ai.kastrax.core.tools.ToolFactory import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建使用计算器工具的代理 val myAgent = agent { name("计算助手") description("一个可以执行数学计算的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { // 添加计算器工具 add(ToolFactory.createCalculatorTool()) } } // 使用代理 val response = myAgent.generate("计算 25 * 16") println(response.text) } ``` ### 计算器工具支持的操作 计算器工具支持以下操作: - 基本运算:加法、减法、乘法、除法 - 高级运算:幂运算、平方根 - 三角函数:正弦、余弦、正切 - 对数函数:常用对数、自然对数 ## 网络搜索工具 ✅ 网络搜索工具允许代理搜索互联网信息: ```kotlin import ai.kastrax.core.tools.ToolFactory import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.web.WebSearchTool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建使用网络搜索工具的代理 val myAgent = agent { name("搜索助手") description("一个可以搜索互联网信息的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { // 添加网络搜索工具 add(ToolFactory.createWebSearchTool( apiKey = "your-search-api-key", searchEngine = WebSearchTool.SearchEngine.GOOGLE, maxResults = 5 )) } } // 使用代理 val response = myAgent.generate("搜索关于量子计算的最新进展") println(response.text) } ``` ### 网络搜索工具配置选项 网络搜索工具支持以下配置选项: - `apiKey`:搜索 API 密钥 - `searchEngine`:搜索引擎(GOOGLE、BING、MOCK 等) - `maxResults`:最大结果数量 ## 文件系统工具 ✅ 文件系统工具允许代理与文件系统交互: ```kotlin import ai.kastrax.core.tools.ToolFactory import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建使用文件系统工具的代理 val myAgent = agent { name("文件助手") description("一个可以操作文件系统的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { // 添加文件系统工具 add(ToolFactory.createFileSystemTool( rootPath = "./data", allowAbsolutePaths = false )) } } // 使用代理 val response = myAgent.generate("列出 data 目录中的文件") println(response.text) } ``` ### 文件系统工具支持的操作 文件系统工具支持以下操作: - 读取文件内容 - 写入文件内容 - 列出目录内容 - 检查文件是否存在 - 获取文件信息 - 创建目录 - 删除文件或目录 ### 文件系统工具输出格式 文件系统工具返回以下格式的输出: ```json { "success": true, "result": "操作结果", "error": null // 如果 success 为 false,则包含错误信息 } ``` ## 日期时间工具 ✅ 日期时间工具提供日期和时间信息: ```kotlin import ai.kastrax.core.tools.ToolFactory import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建使用日期时间工具的代理 val myAgent = agent { name("时间助手") description("一个可以提供日期和时间信息的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { // 添加日期时间工具 add(ToolFactory.createDateTimeTool()) } } // 使用代理 val response = myAgent.generate("现在的日期和时间是什么?") println(response.text) } ``` ### 日期时间工具支持的操作 日期时间工具支持以下操作: - 获取当前日期 - 获取当前时间 - 获取当前时区 - 格式化日期和时间 - 计算日期差异 - 解析日期字符串 ## HTTP 请求工具 ✅ HTTP 请求工具允许代理发送 HTTP 请求: ```kotlin import ai.kastrax.core.tools.ToolFactory import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建使用 HTTP 请求工具的代理 val myAgent = agent { name("API 助手") description("一个可以与 API 交互的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { // 添加 HTTP 请求工具 add(ToolFactory.createHttpRequestTool()) } } // 使用代理 val response = myAgent.generate("获取 BBC 网站的最新新闻") println(response.text) } ``` ### HTTP 请求工具支持的方法 HTTP 请求工具支持以下 HTTP 方法: - GET - POST - PUT - DELETE - PATCH - HEAD ## 数据库工具 ✅ 数据库工具允许代理与数据库交互: ```kotlin import ai.kastrax.core.tools.ToolFactory import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import javax.sql.DataSource import org.postgresql.ds.PGSimpleDataSource fun main() = runBlocking { // 创建数据源 val dataSource = PGSimpleDataSource().apply { setURL("jdbc:postgresql://localhost:5432/mydatabase") user = "username" password = "password" } // 创建使用数据库工具的代理 val myAgent = agent { name("数据库助手") description("一个可以与数据库交互的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { // 添加数据库工具 add(ToolFactory.createDatabaseTool(dataSource)) } } // 使用代理 val response = myAgent.generate("列出数据库中的所有用户") println(response.text) } ``` ### 数据库工具支持的操作 数据库工具支持以下操作: - 执行 SQL 查询 - 执行 SQL 更新 - 获取表结构信息 - 获取数据库元数据 ## 代码执行工具 ✅ 代码执行工具允许代理执行代码: ```kotlin import ai.kastrax.core.tools.ToolFactory import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建使用代码执行工具的代理 val myAgent = agent { name("代码助手") description("一个可以执行代码的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { // 添加代码执行工具 add(ToolFactory.createCodeExecutionTool( allowedLanguages = listOf("kotlin", "python", "javascript"), timeoutSeconds = 5 )) } } // 使用代理 val response = myAgent.generate("使用 Kotlin 计算 5 的阶乘") println(response.text) } ``` ### 代码执行工具配置选项 代码执行工具支持以下配置选项: - `allowedLanguages`:允许执行的编程语言列表 - `timeoutSeconds`:执行超时时间(秒) - `memoryLimitMB`:内存限制(MB) - `enableNetworkAccess`:是否允许网络访问 ## 文本处理工具 ✅ 文本处理工具提供文本处理功能: ```kotlin import ai.kastrax.core.tools.ToolFactory import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建使用文本处理工具的代理 val myAgent = agent { name("文本助手") description("一个可以处理文本的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { // 添加文本处理工具 add(ToolFactory.createTextProcessingTool()) } } // 使用代理 val response = myAgent.generate("将文本 'Hello, World!' 转换为大写") println(response.text) } ``` ### 文本处理工具支持的操作 文本处理工具支持以下操作: - 转换为大写 - 转换为小写 - 反转文本 - 计算文本长度 - 提取正则表达式匹配 - 替换文本 ## 组合多个工具 ✅ 您可以在一个代理中组合多个内置工具: ```kotlin import ai.kastrax.core.tools.ToolFactory import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.web.WebSearchTool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建使用多个工具的代理 val myAgent = agent { name("多功能助手") description("一个具有多种能力的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 添加多个工具 tools { // 添加计算器工具 add(ToolFactory.createCalculatorTool()) // 添加网络搜索工具 add(ToolFactory.createWebSearchTool( apiKey = "your-search-api-key", searchEngine = WebSearchTool.SearchEngine.GOOGLE, maxResults = 5 )) // 添加文件系统工具 add(ToolFactory.createFileSystemTool( rootPath = "./data", allowAbsolutePaths = false )) // 添加日期时间工具 add(ToolFactory.createDateTimeTool()) // 添加 HTTP 请求工具 add(ToolFactory.createHttpRequestTool()) // 添加代码执行工具 add(ToolFactory.createCodeExecutionTool( allowedLanguages = listOf("kotlin", "python"), timeoutSeconds = 5 )) } } // 使用代理 val response = myAgent.generate("搜索关于量子计算的信息,计算 25 * 16,并告诉我当前时间") println(response.text) } ``` ## 自定义内置工具 ✅ 您可以自定义内置工具的行为: ```kotlin import ai.kastrax.core.tools.ToolFactory import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.web.WebSearchTool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建自定义网络搜索工具 val customSearchTool = ToolFactory.createWebSearchTool( apiKey = "your-search-api-key", searchEngine = WebSearchTool.SearchEngine.GOOGLE, maxResults = 3 ).apply { // 自定义工具 ID 和名称 id = "custom_search" name = "自定义搜索" description = "使用自定义参数搜索互联网" } // 创建使用自定义工具的代理 val myAgent = agent { name("自定义工具助手") description("一个使用自定义工具的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { // 添加自定义工具 add(customSearchTool) } } // 使用代理 val response = myAgent.generate("使用自定义搜索工具查找关于人工智能的信息") println(response.text) } ``` ## 最佳实践 ✅ ### 工具选择 - 根据代理的任务选择适当的工具 - 避免添加不必要的工具,以减少混淆 - 为特定领域的任务创建专用代理 ### 工具配置 - 为工具提供适当的配置参数 - 限制代码执行工具的权限和资源 - 为文件系统工具设置安全的根路径 ### 错误处理 - 处理工具可能返回的错误 - 提供有用的错误消息 - 实现重试逻辑(如适用) ### 安全考虑 - 保护 API 密钥和敏感信息 - 限制文件系统访问范围 - 控制代码执行环境 ## 更多工具详情 ✅ 对于其他工具的详细信息,请参阅以下文档: - [HTTP 和代码执行工具](./http-code-tools.mdx):了解 HTTP 请求工具和代码执行工具 - [数据库和文本处理工具](./database-text-tools.mdx):了解数据库工具和文本处理工具 ## 总结 ✅ Kastrax 提供了丰富的内置工具,使代理能够执行各种任务,从简单的计算到复杂的网络搜索和文件操作。通过组合这些工具,您可以创建功能强大的代理,满足各种用例需求。 内置工具的主要优势在于它们易于使用、配置灵活,并且与 Kastrax 代理系统无缝集成。通过遵循本指南中的最佳实践,您可以有效地利用这些工具,创建智能、实用的代理应用程序。 --- title: 自定义工具开发 | Kastrax 文档 description: 如何为 Kastrax 代理创建自定义工具,包括工具定义、参数验证、错误处理和最佳实践。 --- # 自定义工具开发 ✅ [ZH] Source: https://kastrax.ai/zh/docs/tools/custom-tools 本指南将帮助您为 Kastrax 代理创建自定义工具,从简单的工具到复杂的集成。 ## 自定义工具基础 ✅ 自定义工具允许您扩展代理的能力,使其能够执行特定领域的操作。以下是创建自定义工具的基本步骤: ### 基本工具结构 ```kotlin import ai.kastrax.core.tools.tool import kotlinx.coroutines.runBlocking // 创建自定义工具 val myCustomTool = tool("myTool") { description("这是我的自定义工具") // 定义参数 parameters { parameter("param1", "第一个参数", String::class) parameter("param2", "第二个参数", Int::class, optional = true, defaultValue = 0) } // 实现执行逻辑 execute { params -> val param1 = params["param1"] as String val param2 = params["param2"] as Int // 执行自定义逻辑 "处理结果: $param1, $param2" } } // 测试工具 fun main() = runBlocking { val result = myCustomTool.execute(mapOf( "param1" to "测试值", "param2" to 42 )) println(result) } ``` ### 工具参数定义 工具参数可以有多种类型和配置: ```kotlin parameters { // 必填参数 parameter("name", "用户名", String::class) // 可选参数带默认值 parameter("age", "用户年龄", Int::class, optional = true, defaultValue = 18) // 枚举参数 parameter("role", "用户角色", String::class) { enum("admin", "user", "guest") } // 带验证的参数 parameter("email", "电子邮件", String::class) { validate { email -> if (!email.contains("@")) { throw IllegalArgumentException("无效的电子邮件地址") } } } // 复杂类型参数 parameter("settings", "用户设置", Map::class) } ``` ## 高级工具开发 ✅ ### 异步工具 对于需要长时间运行的操作,可以创建异步工具: ```kotlin import ai.kastrax.core.tools.tool import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking val asyncTool = tool("longRunningTask") { description("执行长时间运行的任务") parameters { parameter("taskId", "任务 ID", String::class) } executeAsync { params -> val taskId = params["taskId"] as String // 模拟长时间运行的操作 delay(3000) // 延迟 3 秒 "任务 $taskId 已完成" } } ``` ### 工具错误处理 良好的错误处理对于创建健壮的工具至关重要: ```kotlin val divisionTool = tool("divide") { description("除两个数") parameters { parameter("dividend", "被除数", Double::class) parameter("divisor", "除数", Double::class) } execute { params -> val dividend = params["dividend"] as Double val divisor = params["divisor"] as Double try { if (divisor == 0.0) { throw ArithmeticException("除数不能为零") } val result = dividend / divisor "结果: $result" } catch (e: ArithmeticException) { "错误: ${e.message}" } catch (e: Exception) { "未知错误: ${e.message}" } } } ``` ### 工具上下文 工具可以访问执行上下文: ```kotlin val contextAwareTool = tool("getUserInfo") { description("获取当前用户信息") parameters {} executeWithContext { params, threadId, resourceId -> // 使用上下文信息 "线程 ID: $threadId, 资源 ID: $resourceId" } } ``` ## 集成外部服务 ✅ ### HTTP API 集成 创建与外部 API 交互的工具: ```kotlin import ai.kastrax.core.tools.tool import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.* val weatherApiTool = tool("getWeather") { description("获取城市的天气信息") parameters { parameter("city", "城市名称", String::class) parameter("country", "国家代码", String::class, optional = true, defaultValue = "CN") } execute { params -> val city = params["city"] as String val country = params["country"] as String try { val apiKey = "your-weather-api-key" val url = "https://api.weatherapi.com/v1/current.json?key=$apiKey&q=$city,$country" val client = HttpClient() val response = client.get(url) val responseBody = response.bodyAsText() // 解析 JSON 响应 val json = Json.parseToJsonElement(responseBody).jsonObject val location = json["location"]?.jsonObject val current = json["current"]?.jsonObject val cityName = location?.get("name")?.jsonPrimitive?.content ?: city val temperature = current?.get("temp_c")?.jsonPrimitive?.double ?: 0.0 val condition = current?.get("condition")?.jsonObject?.get("text")?.jsonPrimitive?.content ?: "未知" "$cityName 的天气: $temperature°C, $condition" } catch (e: Exception) { "获取天气信息失败: ${e.message}" } } } ``` ### 数据库集成 创建与数据库交互的工具: ```kotlin import ai.kastrax.core.tools.tool import java.sql.Connection import java.sql.DriverManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext val databaseQueryTool = tool("queryDatabase") { description("执行数据库查询") parameters { parameter("query", "SQL 查询", String::class) parameter("limit", "结果限制", Int::class, optional = true, defaultValue = 10) } execute { params -> val query = params["query"] as String val limit = params["limit"] as Int // 添加 LIMIT 子句(注意:这只是一个简单示例,实际应用中应使用参数化查询防止 SQL 注入) val limitedQuery = if (query.lowercase().contains("limit")) { query } else { "$query LIMIT $limit" } try { withContext(Dispatchers.IO) { // 数据库连接信息 val jdbcUrl = "jdbc:mysql://localhost:3306/mydb" val username = "user" val password = "password" var connection: Connection? = null try { connection = DriverManager.getConnection(jdbcUrl, username, password) val statement = connection.createStatement() val resultSet = statement.executeQuery(limitedQuery) // 构建结果 val resultBuilder = StringBuilder("查询结果:\n") val metaData = resultSet.metaData val columnCount = metaData.columnCount // 添加列名 for (i in 1..columnCount) { resultBuilder.append(metaData.getColumnName(i)) if (i < columnCount) resultBuilder.append(" | ") } resultBuilder.append("\n") // 添加行 var rowCount = 0 while (resultSet.next() && rowCount < limit) { for (i in 1..columnCount) { resultBuilder.append(resultSet.getString(i) ?: "NULL") if (i < columnCount) resultBuilder.append(" | ") } resultBuilder.append("\n") rowCount++ } resultBuilder.toString() } finally { connection?.close() } } } catch (e: Exception) { "查询执行失败: ${e.message}" } } } ``` ### 文件系统集成 创建与文件系统交互的工具: ```kotlin import ai.kastrax.core.tools.tool import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext // 文件读取工具 val readFileTool = tool("readFile") { description("读取文件内容") parameters { parameter("path", "文件路径", String::class) } execute { params -> val filePath = params["path"] as String try { withContext(Dispatchers.IO) { val path = Paths.get(filePath) if (!Files.exists(path)) { "错误: 文件不存在" } else if (!Files.isRegularFile(path)) { "错误: 路径不是一个常规文件" } else { val content = Files.readString(path) "文件内容:\n$content" } } } catch (e: Exception) { "读取文件失败: ${e.message}" } } } // 文件写入工具 val writeFileTool = tool("writeFile") { description("将内容写入文件") parameters { parameter("path", "文件路径", String::class) parameter("content", "要写入的内容", String::class) parameter("append", "是否追加到文件末尾", Boolean::class, optional = true, defaultValue = false) } execute { params -> val filePath = params["path"] as String val content = params["content"] as String val append = params["append"] as Boolean try { withContext(Dispatchers.IO) { val path = Paths.get(filePath) // 确保父目录存在 val parent = path.parent if (parent != null && !Files.exists(parent)) { Files.createDirectories(parent) } // 写入文件 if (append && Files.exists(path)) { Files.writeString(path, "\n$content", java.nio.file.StandardOpenOption.APPEND) } else { Files.writeString(path, content) } "内容已成功写入 $filePath" } } catch (e: Exception) { "写入文件失败: ${e.message}" } } } ``` ## 工具组合和重用 ✅ ### 工具组合 您可以组合多个工具创建更复杂的功能: ```kotlin import ai.kastrax.core.tools.tool import ai.kastrax.core.tools.ToolRegistry import kotlinx.coroutines.runBlocking // 创建工具注册表 val toolRegistry = ToolRegistry() // 注册基础工具 val getCurrentDateTool = tool("getCurrentDate") { description("获取当前日期") parameters {} execute { "当前日期: ${java.time.LocalDate.now()}" } } val getCurrentTimeTool = tool("getCurrentTime") { description("获取当前时间") parameters {} execute { "当前时间: ${java.time.LocalTime.now()}" } } // 注册工具 toolRegistry.register(getCurrentDateTool) toolRegistry.register(getCurrentTimeTool) // 创建组合工具 val getDateTimeTool = tool("getDateTime") { description("获取当前日期和时间") parameters {} execute { val dateResult = runBlocking { toolRegistry.execute("getCurrentDate", mapOf()) } val timeResult = runBlocking { toolRegistry.execute("getCurrentTime", mapOf()) } "$dateResult\n$timeResult" } } ``` ### 工具工厂 创建工具工厂以生成相似的工具: ```kotlin import ai.kastrax.core.tools.tool // 创建工具工厂 object MathToolFactory { // 创建基本数学运算工具 fun createOperationTool( operation: String, symbol: String, description: String, calculate: (Double, Double) -> Double ) = tool(operation) { description(description) parameters { parameter("a", "第一个操作数", Double::class) parameter("b", "第二个操作数", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double try { val result = calculate(a, b) "$a $symbol $b = $result" } catch (e: Exception) { "计算错误: ${e.message}" } } } // 创建常用数学工具 val addTool = createOperationTool( "add", "+", "将两个数相加", { a, b -> a + b } ) val subtractTool = createOperationTool( "subtract", "-", "从第一个数中减去第二个数", { a, b -> a - b } ) val multiplyTool = createOperationTool( "multiply", "*", "将两个数相乘", { a, b -> a * b } ) val divideTool = createOperationTool( "divide", "/", "将第一个数除以第二个数", { a, b -> if (b == 0.0) throw ArithmeticException("除数不能为零") a / b } ) } // 使用工具工厂 agent { // ... tools { tool(MathToolFactory.addTool) tool(MathToolFactory.subtractTool) tool(MathToolFactory.multiplyTool) tool(MathToolFactory.divideTool) } } ``` ## 工具测试 ✅ ### 单元测试 为您的自定义工具编写单元测试: ```kotlin import ai.kastrax.core.tools.tool import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue class CalculatorToolTest { // 被测试的工具 private val calculatorTool = tool("calculator") { description("执行基本的数学运算") parameters { parameter("operation", "要执行的操作", String::class) { enum("add", "subtract", "multiply", "divide") } parameter("a", "第一个操作数", Double::class) parameter("b", "第二个操作数", Double::class) } execute { params -> val operation = params["operation"] as String val a = params["a"] as Double val b = params["b"] as Double when (operation) { "add" -> "结果: ${a + b}" "subtract" -> "结果: ${a - b}" "multiply" -> "结果: ${a * b}" "divide" -> { if (b == 0.0) "错误: 除数不能为零" else "结果: ${a / b}" } else -> "错误: 未知操作" } } } @Test fun `test addition operation`() = runBlocking { val result = calculatorTool.execute(mapOf( "operation" to "add", "a" to 5.0, "b" to 3.0 )) assertEquals("结果: 8.0", result) } @Test fun `test division by zero`() = runBlocking { val result = calculatorTool.execute(mapOf( "operation" to "divide", "a" to 10.0, "b" to 0.0 )) assertEquals("错误: 除数不能为零", result) } @Test fun `test with invalid operation`() = runBlocking { try { calculatorTool.execute(mapOf( "operation" to "invalid", "a" to 5.0, "b" to 3.0 )) // 如果没有抛出异常,测试失败 assertTrue(false, "应该抛出异常") } catch (e: Exception) { // 预期会抛出异常 assertTrue(true) } } } ``` ### 集成测试 测试工具与代理的集成: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import kotlin.test.Test import kotlin.test.assertTrue class ToolIntegrationTest { // 测试工具 private val echoTool = tool("echo") { description("返回输入的文本") parameters { parameter("text", "要返回的文本", String::class) } execute { params -> val text = params["text"] as String "Echo: $text" } } @Test fun `test agent uses tool correctly`() = runBlocking { // 创建使用工具的代理 val agent = agent { name("测试代理") description("用于测试工具集成的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { tool(echoTool) } } // 测试代理使用工具 val response = agent.generate("请使用 echo 工具返回文本 'Hello, World!'") // 验证响应中包含工具输出 assertTrue(response.text.contains("Echo: Hello, World!")) } } ``` ## 最佳实践 ✅ ### 工具设计原则 1. **单一职责**:每个工具应该只做一件事,并且做好 2. **清晰的描述**:提供详细的工具和参数描述 3. **健壮的错误处理**:优雅地处理所有可能的错误情况 4. **参数验证**:验证所有输入参数 5. **合理的默认值**:为可选参数提供合理的默认值 6. **安全性考虑**:防止注入攻击和权限提升 ### 工具组织 将相关工具组织到逻辑组中: ```kotlin // 文件操作工具 object FileTools { val readFile = tool("readFile") { /* ... */ } val writeFile = tool("writeFile") { /* ... */ } val listDirectory = tool("listDirectory") { /* ... */ } val fileExists = tool("fileExists") { /* ... */ } } // 数学工具 object MathTools { val add = tool("add") { /* ... */ } val subtract = tool("subtract") { /* ... */ } val multiply = tool("multiply") { /* ... */ } val divide = tool("divide") { /* ... */ } } // 在代理中使用 agent { // ... tools { // 添加文件工具 tool(FileTools.readFile) tool(FileTools.writeFile) // 添加数学工具 tool(MathTools.add) tool(MathTools.subtract) } } ``` ### 工具文档 为您的工具提供详细的文档: ```kotlin /** * 创建一个天气查询工具。 * * 此工具使用 WeatherAPI.com 获取指定城市的天气信息。 * * @param apiKey WeatherAPI.com API 密钥 * @return 配置好的天气工具 */ fun createWeatherTool(apiKey: String) = tool("getWeather") { description(""" 获取指定城市的天气信息。 此工具返回当前温度、天气状况和湿度。 示例: - 查询北京天气: {"city": "Beijing"} - 查询上海天气(华氏度): {"city": "Shanghai", "unit": "fahrenheit"} """.trimIndent()) parameters { parameter("city", "城市名称", String::class) parameter("unit", "温度单位 (celsius 或 fahrenheit)", String::class, optional = true, defaultValue = "celsius") } execute { params -> // 实现... } } ``` ## 总结 ✅ 创建自定义工具是扩展 Kastrax 代理能力的强大方式。通过遵循本指南中的最佳实践,您可以创建健壮、可重用和易于维护的工具,使您的代理能够执行各种特定领域的任务。 记住以下关键点: - 每个工具应该有明确的目的和详细的描述 - 验证所有输入参数并提供合理的默认值 - 实现健壮的错误处理 - 组织相关工具到逻辑组中 - 为您的工具编写测试 - 提供详细的文档和示例 通过这些实践,您可以创建高质量的自定义工具,显著增强您的 Kastrax 代理的能力。 --- title: 工具系统概述 | Kastrax 文档 description: Kastrax 工具系统的详细介绍,包括工具定义、注册、调用和自定义工具的创建方法。 --- # 工具系统概述 ✅ [ZH] Source: https://kastrax.ai/zh/docs/tools/overview Kastrax 工具系统允许代理与外部系统交互、访问数据和执行操作。本指南解释了工具系统的架构以及如何有效地使用它。 > 如果您正在使用 Kotlin 开发,请查看 [Kotlin 工具系统概述](./overview-kotlin.mdx) 获取更详细的 Kotlin 特定指南。 ## 什么是工具? ✅ 在 Kastrax 中,工具是代理可以调用的函数,使其能够: - 执行计算和数据处理 - 与外部系统交互 - 访问和修改文件 - 搜索网络信息 - 执行特定领域的操作 工具扩展了代理的能力,使其不仅限于生成文本,还能执行实际操作。 ## 工具系统架构 ✅ Kastrax 工具系统由几个组件组成: 1. **工具定义**:工具如何被定义和注册 2. **工具执行**:代理如何执行工具 3. **工具参数**:工具参数如何定义和验证 4. **工具结果**:工具结果如何处理和返回 ## 基本工具创建 ✅ 以下是创建工具的简单示例: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun createAgentWithTools() = agent { name("工具代理") description("具有工具能力的代理") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 添加工具 tools { // 无参数的简单工具 tool("getCurrentTime") { description("获取当前时间") parameters {} execute { val currentTime = java.time.LocalDateTime.now() val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val formattedTime = currentTime.format(formatter) "当前时间是 $formattedTime" } } // 带参数的工具 tool("calculateSum") { description("计算两个数字的和") parameters { parameter("a", "第一个数字", Double::class) parameter("b", "第二个数字", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double val sum = a + b "$a 加 $b 的和是 $sum" } } } } fun main() = runBlocking { val agent = createAgentWithTools() // 测试代理 println(agent.generate("现在几点了?").text) println(agent.generate("计算 5.2 和 3.8 的和").text) } ``` ## 工具参数 工具可以有各种类型的参数: ```kotlin tool("searchDatabase") { description("在数据库中搜索记录") parameters { parameter("query", "搜索查询", String::class) parameter("limit", "最大结果数", Int::class, optional = true, defaultValue = 10) parameter("sortBy", "排序字段", String::class, optional = true) parameter("ascending", "升序排序", Boolean::class, optional = true, defaultValue = true) } execute { params -> val query = params["query"] as String val limit = params["limit"] as Int val sortBy = params["sortBy"] as String? val ascending = params["ascending"] as Boolean // 执行数据库搜索 // ... "为查询 '$query' 找到 $limit 条结果" } } ``` ## 工具分类 您可以将工具组织成类别: ```kotlin tools { // 数学工具 category("数学") { tool("add") { description("加两个数") parameters { parameter("a", "第一个数", Double::class) parameter("b", "第二个数", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double "结果: ${a + b}" } } tool("subtract") { description("减两个数") parameters { parameter("a", "第一个数", Double::class) parameter("b", "第二个数", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double "结果: ${a - b}" } } } // 实用工具 category("实用工具") { tool("getCurrentTime") { description("获取当前时间") parameters {} execute { "当前时间: ${java.time.LocalDateTime.now()}" } } } } ``` ## 异步工具 对于长时间运行的操作,您可以创建异步工具: ```kotlin tool("fetchLargeDataset") { description("从 API 获取大型数据集") parameters { parameter("datasetId", "数据集 ID", String::class) } executeAsync { params -> val datasetId = params["datasetId"] as String // 模拟长时间运行的操作 kotlinx.coroutines.delay(2000) // 返回结果 "数据集 $datasetId 成功获取,包含 10,000 条记录" } } ``` ## 工具错误处理 您可以在工具中处理错误: ```kotlin tool("divideNumbers") { description("除两个数") parameters { parameter("a", "被除数", Double::class) parameter("b", "除数", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double try { if (b == 0.0) { throw ArithmeticException("除以零") } "结果: ${a / b}" } catch (e: Exception) { "错误: ${e.message}" } } } ``` ## 工具验证 您可以为工具参数添加验证: ```kotlin tool("sendEmail") { description("发送电子邮件") parameters { parameter("to", "收件人电子邮件地址", String::class) { validate { email -> if (!email.contains("@")) { throw IllegalArgumentException("无效的电子邮件地址") } } } parameter("subject", "邮件主题", String::class) { validate { subject -> if (subject.length > 100) { throw IllegalArgumentException("主题太长(最多 100 个字符)") } } } parameter("body", "邮件正文", String::class) } execute { params -> val to = params["to"] as String val subject = params["subject"] as String val body = params["body"] as String // 发送邮件逻辑 // ... "邮件已发送至 $to" } } ``` ## 工具权限 您可以为工具定义权限: ```kotlin tool("deleteFile") { description("删除文件") permissions { permission("file.delete", "删除文件的权限") } parameters { parameter("path", "文件路径", String::class) } execute { params -> val path = params["path"] as String // 检查权限 if (!hasPermission("file.delete")) { return "错误: 权限被拒绝" } // 删除文件逻辑 // ... "文件 $path 已成功删除" } } ``` ## 工具组合 您可以从其他工具组合工具: ```kotlin // 定义基础工具 val getCurrentTimeToolDef = toolDefinition("getCurrentTime") { description("获取当前时间") parameters {} execute { val currentTime = java.time.LocalDateTime.now() val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val formattedTime = currentTime.format(formatter) "当前时间是 $formattedTime" } } val getCurrentDateToolDef = toolDefinition("getCurrentDate") { description("获取当前日期") parameters {} execute { val currentDate = java.time.LocalDate.now() val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd") val formattedDate = currentDate.format(formatter) "当前日期是 $formattedDate" } } // 组合新工具 val getDateTimeInfoToolDef = toolDefinition("getDateTimeInfo") { description("获取当前日期和时间信息") parameters {} execute { val timeResult = getCurrentTimeToolDef.execute(mapOf()) val dateResult = getCurrentDateToolDef.execute(mapOf()) "$dateResult\n$timeResult" } } // 将组合工具添加到代理 agent { // ... tools { addTool(getDateTimeInfoToolDef) } } ``` ## 外部 API 工具 您可以创建与外部 API 交互的工具: ```kotlin tool("getWeather") { description("获取位置的天气信息") parameters { parameter("location", "城市名称或坐标", String::class) } execute { params -> val location = params["location"] as String // 发起 API 请求 val apiKey = "your-weather-api-key" val url = "https://api.weatherapi.com/v1/current.json?key=$apiKey&q=$location" try { val client = java.net.http.HttpClient.newBuilder().build() val request = java.net.http.HttpRequest.newBuilder() .uri(java.net.URI.create(url)) .GET() .build() val response = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString()) if (response.statusCode() == 200) { // 解析 JSON 响应 // 这是一个简化的示例 val body = response.body() "$location 的天气信息: $body" } else { "获取天气时出错: ${response.statusCode()}" } } catch (e: Exception) { "错误: ${e.message}" } } } ``` ## 文件操作工具 您可以创建文件操作工具: ```kotlin tools { category("文件操作") { tool("readFile") { description("读取文件内容") parameters { parameter("path", "文件路径", String::class) } execute { params -> val path = params["path"] as String try { val content = java.nio.file.Files.readString(java.nio.file.Path.of(path)) "文件内容:\n$content" } catch (e: Exception) { "读取文件时出错: ${e.message}" } } } tool("writeFile") { description("将内容写入文件") parameters { parameter("path", "文件路径", String::class) parameter("content", "要写入的内容", String::class) } execute { params -> val path = params["path"] as String val content = params["content"] as String try { java.nio.file.Files.writeString(java.nio.file.Path.of(path), content) "内容已成功写入 $path" } catch (e: Exception) { "写入文件时出错: ${e.message}" } } } } } ``` ## 数据库工具 您可以创建数据库操作工具: ```kotlin tools { category("数据库") { tool("queryDatabase") { description("执行 SQL 查询") parameters { parameter("query", "SQL 查询", String::class) } execute { params -> val query = params["query"] as String // 这是一个简化的示例 // 在实际应用中,您会使用适当的数据库连接 try { // 执行查询 // ... "查询成功执行。结果:\n..." } catch (e: Exception) { "执行查询时出错: ${e.message}" } } } } } ``` ## 工具注册 您可以从外部源注册工具: ```kotlin // 在单独的文件中定义工具 object MathTools { val addTool = toolDefinition("add") { description("加两个数") parameters { parameter("a", "第一个数", Double::class) parameter("b", "第二个数", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double "结果: ${a + b}" } } val subtractTool = toolDefinition("subtract") { description("减两个数") parameters { parameter("a", "第一个数", Double::class) parameter("b", "第二个数", Double::class) } execute { params -> val a = params["a"] as Double val b = params["b"] as Double "结果: ${a - b}" } } } // 向代理注册工具 agent { // ... tools { addTool(MathTools.addTool) addTool(MathTools.subtractTool) } } ``` ## 完整示例 以下是包含各种工具的完整示例: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking import java.time.LocalDateTime import java.time.format.DateTimeFormatter fun createToolAgent() = agent { name("实用助手") description("具有各种实用工具的助手") // 配置 LLM model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 添加工具 tools { // 日期和时间工具 category("日期时间") { tool("getCurrentTime") { description("获取当前时间") parameters {} execute { val currentTime = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("HH:mm:ss") val formattedTime = currentTime.format(formatter) "当前时间是 $formattedTime" } } tool("getCurrentDate") { description("获取当前日期") parameters {} execute { val currentDate = LocalDateTime.now() val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") val formattedDate = currentDate.format(formatter) "今天的日期是 $formattedDate" } } } // 数学工具 category("数学") { tool("calculate") { description("执行计算") parameters { parameter("expression", "数学表达式", String::class) } execute { params -> val expression = params["expression"] as String try { // 这是一个简化的示例 // 在实际应用中,您会使用适当的表达式求值器 val result = when { expression.contains("+") -> { val parts = expression.split("+") val a = parts[0].trim().toDouble() val b = parts[1].trim().toDouble() a + b } expression.contains("-") -> { val parts = expression.split("-") val a = parts[0].trim().toDouble() val b = parts[1].trim().toDouble() a - b } expression.contains("*") -> { val parts = expression.split("*") val a = parts[0].trim().toDouble() val b = parts[1].trim().toDouble() a * b } expression.contains("/") -> { val parts = expression.split("/") val a = parts[0].trim().toDouble() val b = parts[1].trim().toDouble() if (b == 0.0) throw ArithmeticException("除以零") a / b } else -> throw IllegalArgumentException("不支持的操作") } "结果: $result" } catch (e: Exception) { "计算结果时出错: ${e.message}" } } } } // 实用工具 category("实用工具") { tool("generateRandomNumber") { description("在范围内生成随机数") parameters { parameter("min", "最小值", Int::class) parameter("max", "最大值", Int::class) } execute { params -> val min = params["min"] as Int val max = params["max"] as Int if (min >= max) { "错误: 最小值必须小于最大值" } else { val random = java.util.Random() val randomNumber = random.nextInt(max - min + 1) + min "$min 和 $max 之间的随机数: $randomNumber" } } } tool("countWords") { description("计算文本中的单词数") parameters { parameter("text", "要分析的文本", String::class) } execute { params -> val text = params["text"] as String val wordCount = text.split("\\s+".toRegex()).filter { it.isNotEmpty() }.size "单词数: $wordCount" } } } } } fun main() = runBlocking { val agent = createToolAgent() // 测试代理 println(agent.generate("现在几点了?").text) println(agent.generate("计算 15.5 + 27.3").text) println(agent.generate("在 1 到 100 之间生成一个随机数").text) println(agent.generate("计算 '敏捷的棕色狐狸跳过懒狗' 中的单词数").text) } ``` ## 类型安全的 Zod 工具 ✅ Kastrax 提供了受 Zod 启发的模式系统,用于创建类型安全的工具: ```kotlin import ai.kastrax.core.tools.zodTool import ai.kastrax.zod.* // 定义输入和输出类型 data class CalculatorInput( val operation: String, val a: Double, val b: Double ) data class CalculatorOutput( val result: Double ) // 创建类型安全的工具 val calculatorTool = zodTool { id = "calculator" name = "计算器" description = "执行基本的数学运算" // 定义输入模式 inputSchema = objectInput("计算器输入") { stringField("operation", "要执行的操作") { enum("add", "subtract", "multiply", "divide") } numberField("a", "第一个操作数") numberField("b", "第二个操作数") }.transform { input -> CalculatorInput( operation = input["operation"] as String, a = (input["a"] as Number).toDouble(), b = (input["b"] as Number).toDouble() ) } // 定义输出模式 outputSchema = objectOutput("计算器输出") { numberField("result", "运算结果") }.transform { output -> CalculatorOutput( result = (output["result"] as Number).toDouble() ) } // 实现执行逻辑 execute = { input -> val result = when (input.operation) { "add" -> input.a + input.b "subtract" -> input.a - input.b "multiply" -> input.a * input.b "divide" -> input.a / input.b else -> throw IllegalArgumentException("未知操作: ${input.operation}") } CalculatorOutput(result) } } ``` ### Zod 模式系统的优势 使用 Zod 模式系统创建工具有以下优势: 1. **类型安全**:在编译时捕获类型错误 2. **输入验证**:自动验证工具输入 3. **数据转换**:在验证后转换数据 4. **清晰的 API**:使用链式 API 定义模式 5. **与数据类集成**:方便地与 Kotlin 数据类集成 ### 简单的 Zod 工具示例 以下是一个简单的字符串反转工具: ```kotlin import ai.kastrax.core.tools.zodTool import ai.kastrax.zod.stringInput import ai.kastrax.zod.stringOutput import kotlinx.coroutines.runBlocking fun main() { // 创建一个简单的字符串反转工具 val reverseStringTool = zodTool { id = "reverse_string" name = "字符串反转" description = "反转输入的字符串" // 使用 stringInput 和 stringOutput 辅助函数创建模式 inputSchema = stringInput("要反转的字符串") outputSchema = stringOutput("反转后的字符串") // 实现执行逻辑 execute = { input -> input.reversed() } } // 使用工具 val input = "你好,世界!" // 执行工具 val output = runBlocking { reverseStringTool.execute(input) } println("原始字符串: $input") println("反转后的字符串: $output") } ``` ## 下一步 现在您已经了解了工具系统,您可以: 1. 了解 [Zod 工具](./zod-tools.mdx) 2. 探索[自定义工具开发](./custom-tools.mdx) 3. 实现[工具链](./tool-chains.mdx) --- title: 工具链 | Kastrax 文档 description: 如何在 Kastrax 中创建和使用工具链,将多个工具组合成复杂的处理流程。 --- # 工具链 [ZH] Source: https://kastrax.ai/zh/docs/tools/tool-chains 工具链是将多个工具组合成一个复杂处理流程的方式。Kastrax 提供了强大的工具链功能,允许您创建数据处理管道、多步骤操作和复杂的业务逻辑。 ## 工具链概述 工具链的核心思想是将一个工具的输出作为另一个工具的输入,从而创建一个处理流程: ``` 工具 A → 工具 B → 工具 C → 结果 ``` Kastrax 提供了多种创建工具链的方式: 1. **顺序执行**:按顺序执行多个工具 2. **条件执行**:基于条件选择执行路径 3. **并行执行**:同时执行多个工具 4. **错误处理**:处理工具执行过程中的错误 ## 基本工具链 ### 顺序工具链 最简单的工具链是顺序执行多个工具: ```kotlin import ai.kastrax.core.tools.tool import ai.kastrax.core.tools.chain.toolChain import kotlinx.coroutines.runBlocking // 定义工具 val extractNumbersTool = tool("extractNumbers") { description("从文本中提取数字") parameters { parameter("text", "输入文本", String::class) } execute { params -> val text = params["text"] as String val numbers = Regex("\\d+").findAll(text) .map { it.value.toInt() } .toList() numbers } } val calculateSumTool = tool("calculateSum") { description("计算数字列表的总和") parameters { parameter("numbers", "数字列表", List::class) } execute { params -> val numbers = params["numbers"] as List val sum = numbers.sum() sum } } val formatResultTool = tool("formatResult") { description("格式化结果") parameters { parameter("sum", "总和", Int::class) } execute { params -> val sum = params["sum"] as Int "总和是: $sum" } } // 创建工具链 val sumNumbersChain = toolChain("sumNumbers") { description("从文本中提取数字并计算总和") // 定义工具链步骤 steps { // 第一步:提取数字 step("extract") { tool = extractNumbersTool inputs { map("text" to input("text")) } } // 第二步:计算总和 step("sum") { tool = calculateSumTool inputs { map("numbers" to output("extract")) } } // 第三步:格式化结果 step("format") { tool = formatResultTool inputs { map("sum" to output("sum")) } } } // 定义工具链输出 output = output("format") } // 测试工具链 fun main() = runBlocking { val result = sumNumbersChain.execute(mapOf( "text" to "这个文本包含数字 10、20 和 30" )) println(result) // 输出: 总和是: 60 } ``` ## 条件工具链 使用条件逻辑创建分支工具链: ```kotlin import ai.kastrax.core.tools.tool import ai.kastrax.core.tools.chain.toolChain import kotlinx.coroutines.runBlocking // 定义工具 val checkAgeTool = tool("checkAge") { description("检查年龄") parameters { parameter("age", "年龄", Int::class) } execute { params -> val age = params["age"] as Int when { age < 18 -> "minor" age < 65 -> "adult" else -> "senior" } } } val minorMessageTool = tool("minorMessage") { description("生成未成年人消息") parameters {} execute { "您需要监护人陪同。" } } val adultMessageTool = tool("adultMessage") { description("生成成年人消息") parameters {} execute { "欢迎使用我们的服务。" } } val seniorMessageTool = tool("seniorMessage") { description("生成老年人消息") parameters {} execute { "我们为老年人提供特别服务。" } } // 创建条件工具链 val ageCheckChain = toolChain("ageCheck") { description("根据年龄生成不同消息") steps { // 第一步:检查年龄 step("check") { tool = checkAgeTool inputs { map("age" to input("age")) } } // 条件步骤 conditionalStep("message") { condition = output("check") // 未成年人分支 case("minor") { tool = minorMessageTool inputs {} } // 成年人分支 case("adult") { tool = adultMessageTool inputs {} } // 老年人分支 case("senior") { tool = seniorMessageTool inputs {} } // 默认分支 default { tool = tool("defaultMessage") { description("默认消息") parameters {} execute { "无法确定您的年龄类别。" } } inputs {} } } } output = output("message") } // 测试条件工具链 fun main() = runBlocking { // 测试不同年龄 val ages = listOf(15, 30, 70) for (age in ages) { val result = ageCheckChain.execute(mapOf("age" to age)) println("年龄 $age: $result") } } ``` ## 并行工具链 同时执行多个工具并合并结果: ```kotlin import ai.kastrax.core.tools.tool import ai.kastrax.core.tools.chain.toolChain import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable @Serializable data class WeatherInfo(val temperature: Double, val condition: String) @Serializable data class TrafficInfo(val congestion: String, val incidents: Int) @Serializable data class CombinedInfo(val weather: WeatherInfo, val traffic: TrafficInfo) // 定义工具 val getWeatherTool = tool("getWeather") { description("获取天气信息") parameters { parameter("city", "城市", String::class) } execute { params -> val city = params["city"] as String // 模拟天气 API 调用 WeatherInfo( temperature = 25.0, condition = "晴" ) } } val getTrafficTool = tool("getTraffic") { description("获取交通信息") parameters { parameter("city", "城市", String::class) } execute { params -> val city = params["city"] as String // 模拟交通 API 调用 TrafficInfo( congestion = "中度", incidents = 2 ) } } val combineInfoTool = tool("combineInfo") { description("合并天气和交通信息") parameters { parameter("weather", "天气信息", WeatherInfo::class) parameter("traffic", "交通信息", TrafficInfo::class) } execute { params -> val weather = params["weather"] as WeatherInfo val traffic = params["traffic"] as TrafficInfo CombinedInfo(weather, traffic) } } // 创建并行工具链 val cityInfoChain = toolChain("cityInfo") { description("获取城市的天气和交通信息") steps { // 并行步骤:同时获取天气和交通信息 parallelSteps("info") { // 天气步骤 step("weather") { tool = getWeatherTool inputs { map("city" to input("city")) } } // 交通步骤 step("traffic") { tool = getTrafficTool inputs { map("city" to input("city")) } } } // 合并结果 step("combine") { tool = combineInfoTool inputs { map("weather" to output("info.weather")) map("traffic" to output("info.traffic")) } } } output = output("combine") } // 测试并行工具链 fun main() = runBlocking { val result = cityInfoChain.execute(mapOf( "city" to "北京" )) as CombinedInfo println("城市信息:") println("- 天气: ${result.weather.temperature}°C, ${result.weather.condition}") println("- 交通: ${result.traffic.congestion}拥堵, ${result.traffic.incidents}起事故") } ``` ## 错误处理工具链 处理工具执行过程中的错误: ```kotlin import ai.kastrax.core.tools.tool import ai.kastrax.core.tools.chain.toolChain import kotlinx.coroutines.runBlocking import java.io.File // 定义工具 val readFileTool = tool("readFile") { description("读取文件内容") parameters { parameter("path", "文件路径", String::class) } execute { params -> val path = params["path"] as String try { File(path).readText() } catch (e: Exception) { throw RuntimeException("读取文件失败: ${e.message}") } } } val processTextTool = tool("processText") { description("处理文本内容") parameters { parameter("text", "文本内容", String::class) } execute { params -> val text = params["text"] as String text.uppercase() } } val errorHandlerTool = tool("errorHandler") { description("处理错误") parameters { parameter("error", "错误信息", String::class) } execute { params -> val error = params["error"] as String "发生错误: $error" } } // 创建带错误处理的工具链 val fileProcessChain = toolChain("fileProcess") { description("读取并处理文件内容") steps { // 尝试读取文件 tryStep("read") { tool = readFileTool inputs { map("path" to input("path")) } // 成功处理 onSuccess { step("process") { tool = processTextTool inputs { map("text" to output("read")) } } } // 错误处理 onError { error -> step("handleError") { tool = errorHandlerTool inputs { map("error" to error.message) } } } } } // 根据执行路径选择输出 output = conditional { if (hasOutput("process")) { output("process") } else { output("handleError") } } } // 测试错误处理工具链 fun main() = runBlocking { // 测试存在的文件 val validResult = fileProcessChain.execute(mapOf( "path" to "example.txt" )) println("有效文件: $validResult") // 测试不存在的文件 val invalidResult = fileProcessChain.execute(mapOf( "path" to "nonexistent.txt" )) println("无效文件: $invalidResult") } ``` ## 工具链最佳实践 ### 设计原则 1. **单一职责**:每个工具和工具链应专注于一个特定功能 2. **模块化**:将复杂工具链分解为可重用的子链 3. **可测试性**:设计便于测试的工具链,每个步骤都可以独立测试 4. **错误处理**:在每个关键点添加错误处理逻辑 5. **文档化**:为工具链提供清晰的文档,包括输入、输出和步骤说明 ### 性能优化 1. **并行执行**:使用 `parallelSteps` 并行执行独立步骤 2. **缓存结果**:缓存频繁使用的中间结果 3. **延迟加载**:仅在需要时执行步骤 4. **资源管理**:确保正确释放资源,如文件句柄和网络连接 ### 调试技巧 1. **日志记录**:在关键步骤添加日志记录 2. **步骤监控**:使用 `onStepStart` 和 `onStepComplete` 监控步骤执行 3. **可视化**:使用工具链可视化工具查看执行流程 4. **断点**:在开发环境中设置断点调试复杂工具链 ## 实际应用示例 ### 数据处理管道 ```kotlin import ai.kastrax.core.tools.tool import ai.kastrax.core.tools.chain.toolChain import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable import java.io.File @Serializable data class DataRecord( val id: String, val value: Double, val category: String ) // 创建数据处理管道 val dataProcessingChain = toolChain("dataProcessing") { description("数据加载、清洗、转换和分析管道") steps { // 加载数据 step("load") { tool = tool("loadData") { description("从CSV文件加载数据") parameters { parameter("filePath", "文件路径", String::class) } execute { params -> val filePath = params["filePath"] as String val lines = File(filePath).readLines().drop(1) // 跳过标题行 lines.map { line -> val parts = line.split(",") DataRecord( id = parts[0], value = parts[1].toDoubleOrNull() ?: 0.0, category = parts[2] ) } } } inputs { map("filePath" to input("filePath")) } } // 清洗数据 step("clean") { tool = tool("cleanData") { description("清洗数据记录") parameters { parameter("records", "数据记录", List::class) } execute { params -> val records = params["records"] as List // 过滤无效记录 records.filter { record -> record.id.isNotEmpty() && record.value > 0 } } } inputs { map("records" to output("load")) } } // 按类别分组 step("group") { tool = tool("groupByCategory") { description("按类别分组数据") parameters { parameter("records", "数据记录", List::class) } execute { params -> val records = params["records"] as List records.groupBy { it.category } } } inputs { map("records" to output("clean")) } } // 计算每个类别的统计信息 step("analyze") { tool = tool("calculateStats") { description("计算每个类别的统计信息") parameters { parameter("groupedRecords", "分组数据", Map::class) } execute { params -> val groupedRecords = params["groupedRecords"] as Map> groupedRecords.mapValues { (_, records) -> val values = records.map { it.value } mapOf( "count" to records.size, "sum" to values.sum(), "average" to values.average(), "min" to (values.minOrNull() ?: 0.0), "max" to (values.maxOrNull() ?: 0.0) ) } } } inputs { map("groupedRecords" to output("group")) } } // 格式化结果 step("format") { tool = tool("formatResults") { description("格式化分析结果") parameters { parameter("stats", "统计信息", Map::class) } execute { params -> val stats = params["stats"] as Map> buildString { append("数据分析结果:\n\n") stats.forEach { (category, categoryStats) -> append("类别: $category\n") append("- 记录数: ${categoryStats["count"]}\n") append("- 总和: ${categoryStats["sum"]}\n") append("- 平均值: ${categoryStats["average"]}\n") append("- 最小值: ${categoryStats["min"]}\n") append("- 最大值: ${categoryStats["max"]}\n\n") } } } } inputs { map("stats" to output("analyze")) } } } output = output("format") } ``` ### 多步骤表单处理 ```kotlin import ai.kastrax.core.tools.tool import ai.kastrax.core.tools.chain.toolChain import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable @Serializable data class UserForm( val name: String, val email: String, val age: Int, val interests: List ) // 创建表单处理工具链 val formProcessingChain = toolChain("formProcessing") { description("多步骤表单验证和处理") steps { // 验证表单 step("validate") { tool = tool("validateForm") { description("验证表单数据") parameters { parameter("form", "表单数据", UserForm::class) } execute { params -> val form = params["form"] as UserForm val errors = mutableListOf() // 验证名称 if (form.name.isBlank()) { errors.add("名称不能为空") } // 验证邮箱 if (!form.email.contains("@") || !form.email.contains(".")) { errors.add("邮箱格式无效") } // 验证年龄 if (form.age < 18 || form.age > 120) { errors.add("年龄必须在 18 到 120 之间") } // 验证兴趣 if (form.interests.isEmpty()) { errors.add("至少需要一个兴趣爱好") } mapOf( "isValid" to errors.isEmpty(), "errors" to errors, "form" to form ) } } inputs { map("form" to input("form")) } } // 条件处理 conditionalStep("process") { condition = { output -> (output["validate"] as Map<*, *>)["isValid"] as Boolean } // 表单有效 case(true) { step("processForm") { tool = tool("processValidForm") { description("处理有效表单") parameters { parameter("validationResult", "验证结果", Map::class) } execute { params -> val validationResult = params["validationResult"] as Map<*, *> val form = validationResult["form"] as UserForm // 处理表单逻辑 val userId = "USER_${System.currentTimeMillis()}" mapOf( "success" to true, "userId" to userId, "message" to "表单处理成功", "userData" to mapOf( "id" to userId, "name" to form.name, "email" to form.email, "age" to form.age, "interests" to form.interests, "registrationDate" to System.currentTimeMillis() ) ) } } inputs { map("validationResult" to output("validate")) } } } // 表单无效 case(false) { step("handleErrors") { tool = tool("handleFormErrors") { description("处理表单错误") parameters { parameter("validationResult", "验证结果", Map::class) } execute { params -> val validationResult = params["validationResult"] as Map<*, *> val errors = validationResult["errors"] as List<*> mapOf( "success" to false, "message" to "表单验证失败", "errors" to errors ) } } inputs { map("validationResult" to output("validate")) } } } } } // 输出取决于表单是否有效 output = conditional { if (hasOutput("process.true.processForm")) { output("process.true.processForm") } else { output("process.false.handleErrors") } } } ``` ## 下一步 现在您已经了解了工具链,您可以: 1. 了解[工具调用生命周期和调试](./tool-lifecycle.mdx) 2. 探索[工具系统与 Actor 模型集成](./tools-with-actors.mdx) 3. 了解[工具系统安全性](./tools-security.mdx) 4. 学习如何[创建自定义工具](./custom-tools.mdx) --- title: Zod 工具系统 | Kastrax 文档 description: Kastrax 的 Zod 工具系统详细指南,包括类型安全的工具创建、模式定义和验证。 --- # Zod 工具系统 ✅ [ZH] Source: https://kastrax.ai/zh/docs/tools/zod-tools Kastrax 的 Zod 工具系统提供了一种类型安全的方式来定义和使用工具。本指南详细介绍了如何使用 Zod 工具系统创建强大的工具。 ## Zod 工具概述 ✅ Zod 工具系统受到 TypeScript 的 Zod 库启发,为 Kotlin 提供了类似的类型安全模式验证功能。使用 Zod 工具,您可以: - 定义类型安全的输入和输出模式 - 自动验证工具输入 - 在验证后转换数据 - 与 Kotlin 数据类无缝集成 ## 基本用法 ✅ ### 创建简单的 Zod 工具 以下是创建简单 Zod 工具的基本模式: ```kotlin import ai.kastrax.core.tools.zodTool import ai.kastrax.zod.* import kotlinx.coroutines.runBlocking // 创建一个简单的字符串反转工具 val reverseStringTool = zodTool { id = "reverse_string" name = "字符串反转" description = "反转输入的字符串" // 使用辅助函数定义输入和输出模式 inputSchema = stringInput("要反转的字符串") outputSchema = stringOutput("反转后的字符串") // 实现执行逻辑 execute = { input -> input.reversed() } } // 使用工具 fun main() = runBlocking { val result = reverseStringTool.execute("你好,世界!") println(result) // 输出: "!界世,好你" } ``` ### 使用数据类 Zod 工具系统可以与 Kotlin 数据类无缝集成: ```kotlin import ai.kastrax.core.tools.zodTool import ai.kastrax.zod.* import kotlinx.coroutines.runBlocking // 定义输入和输出数据类 data class GreetingInput( val name: String, val language: String = "zh" ) data class GreetingOutput( val message: String, val language: String ) // 创建使用数据类的工具 val greetingTool = zodTool { id = "greeting" name = "问候工具" description = "生成不同语言的问候语" // 定义输入模式并转换为数据类 inputSchema = objectInput("问候输入") { stringField("name", "要问候的人名") stringField("language", "语言代码", required = false) { enum("zh", "en", "ja", "fr") } }.transform { input -> GreetingInput( name = input["name"] as String, language = (input["language"] as? String) ?: "zh" ) } // 定义输出模式 outputSchema = objectOutput("问候输出") { stringField("message", "问候消息") stringField("language", "使用的语言") }.transform { output -> GreetingOutput( message = output["message"] as String, language = output["language"] as String ) } // 实现执行逻辑 execute = { input -> val message = when (input.language) { "en" -> "Hello, ${input.name}!" "ja" -> "こんにちは、${input.name}さん!" "fr" -> "Bonjour, ${input.name}!" else -> "你好,${input.name}!" } GreetingOutput(message, input.language) } } // 使用工具 fun main() = runBlocking { val result = greetingTool.execute(GreetingInput("张三", "zh")) println(result.message) // 输出: "你好,张三!" } ``` ## 模式定义 ✅ Zod 工具系统提供了多种辅助函数来定义模式: ### 基本类型模式 ```kotlin // 字符串模式 val stringSchema = stringInput("字符串输入") // 数字模式 val numberSchema = numberInput("数字输入") // 布尔模式 val booleanSchema = booleanInput("布尔输入") // 枚举模式 val enumSchema = enumInput(MyEnum::class.java, "枚举输入") ``` ### 对象模式 ```kotlin // 对象模式 val userSchema = objectInput("用户数据") { // 必填字段 stringField("username", "用户名") { minLength = 3 maxLength = 20 } // 可选字段 stringField("email", "电子邮件", required = false) { email = true } // 嵌套对象 objectField("address", "地址", required = false) { stringField("city", "城市") stringField("street", "街道") } // 数组字段 arrayField("tags", "标签", required = false) { element = stringInput() } } ``` ### 数组模式 ```kotlin // 字符串数组 val stringArraySchema = arrayInput(stringInput(), "字符串数组") // 对象数组 val userArraySchema = arrayInput( objectInput { stringField("name", "姓名") numberField("age", "年龄") }, "用户数组" ) ``` ## 模式验证和转换 ✅ ### 验证规则 Zod 工具系统支持多种验证规则: ```kotlin // 字符串验证 stringInput { minLength = 3 // 最小长度 maxLength = 20 // 最大长度 pattern = "^[a-zA-Z0-9]+$" // 正则表达式模式 email = true // 电子邮件验证 url = true // URL 验证 } // 数字验证 numberInput { min = 0.0 // 最小值 max = 100.0 // 最大值 int = true // 整数验证 positive = true // 正数验证 } ``` ### 数据转换 您可以使用 `transform` 方法转换数据: ```kotlin // 转换字符串为大写 val uppercaseSchema = stringInput().transform { it.uppercase() } // 转换对象为数据类 val userSchema = objectInput { stringField("name", "姓名") numberField("age", "年龄") }.transform { input -> User( name = input["name"] as String, age = (input["age"] as Number).toInt() ) } ``` ## 与代理集成 ✅ 将 Zod 工具与代理集成: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.zodTool import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import ai.kastrax.zod.* import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建 Zod 工具 val weatherTool = zodTool, Map> { id = "getWeather" name = "获取天气" description = "获取指定城市的天气信息" inputSchema = objectInput { stringField("city", "城市名称") stringField("unit", "温度单位", required = false) { enum("celsius", "fahrenheit") } } outputSchema = objectOutput { numberField("temperature", "温度") stringField("conditions", "天气状况") stringField("city", "城市名称") stringField("unit", "温度单位") } execute = { input -> val city = input["city"] as String val unit = (input["unit"] as? String) ?: "celsius" // 模拟天气 API 调用 val temperature = 23.5 val conditions = "晴朗" mapOf( "temperature" to temperature, "conditions" to conditions, "city" to city, "unit" to unit ) } } // 创建使用 Zod 工具的代理 val agent = agent { name("天气助手") description("一个可以提供天气信息的助手") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } // 添加 Zod 工具 tools { tool(weatherTool.toTool()) // 将 ZodTool 转换为 Tool } } // 使用代理 val response = agent.generate("北京今天的天气怎么样?") println(response.text) } ``` ## 高级用法 ✅ ### 组合模式 您可以组合多个模式创建复杂的验证规则: ```kotlin // 联合类型 val stringOrNumberSchema = union(stringInput(), numberInput()) // 交叉类型 val baseUserSchema = objectInput { stringField("id", "用户 ID") } val profileSchema = objectInput { stringField("name", "姓名") numberField("age", "年龄") } val userWithProfileSchema = intersection(baseUserSchema, profileSchema) ``` ### 自定义验证 您可以添加自定义验证逻辑: ```kotlin // 自定义验证 val evenNumberSchema = numberInput().refine( check = { it % 2 == 0.0 }, message = "必须是偶数" ) // 复杂验证 val passwordSchema = stringInput().refine( check = { password -> password.length >= 8 && password.any { it.isDigit() } && password.any { it.isUpperCase() } && password.any { it.isLowerCase() } }, message = "密码必须至少包含 8 个字符,并包含数字、大写字母和小写字母" ) ``` ## 错误处理 ✅ Zod 工具系统提供了详细的错误信息: ```kotlin import ai.kastrax.zod.* fun main() { val userSchema = objectInput { stringField("username", "用户名") { minLength = 3 } numberField("age", "年龄") { min = 18.0 } } // 验证无效数据 val invalidData = mapOf( "username" to "ab", // 太短 "age" to 16 // 太小 ) // 使用 safeParse 获取详细错误信息 val result = userSchema.safeParse(invalidData) when (result) { is SchemaResult.Success -> { println("验证成功: ${result.data}") } is SchemaResult.Failure -> { println("验证失败:") result.error.issues.forEach { issue -> println("- ${issue.path.joinToString(".")}: ${issue.message}") } } } } ``` ## 最佳实践 ✅ ### 模式重用 为了提高代码可维护性,将常用模式提取为变量: ```kotlin // 定义可重用的模式 val emailSchema = stringInput("电子邮件地址") { email = true } val passwordSchema = stringInput("密码") { minLength = 8 // 添加其他验证... } // 在多个工具中重用 val registerTool = zodTool, Map> { // ... inputSchema = objectInput { field("email", emailSchema) field("password", passwordSchema) // 其他字段... } // ... } val loginTool = zodTool, Map> { // ... inputSchema = objectInput { field("email", emailSchema) field("password", passwordSchema) } // ... } ``` ### 模块化工具定义 将工具定义组织到单独的文件或对象中: ```kotlin // UserTools.kt object UserTools { // 共享模式 private val emailSchema = stringInput("电子邮件地址") { email = true } private val passwordSchema = stringInput("密码") { minLength = 8 } // 注册工具 val registerTool = zodTool, Map> { id = "register" name = "用户注册" description = "注册新用户" inputSchema = objectInput { field("email", emailSchema) field("password", passwordSchema) stringField("name", "姓名") } outputSchema = objectOutput { stringField("userId", "用户 ID") stringField("message", "注册结果消息") } execute = { input -> // 实现注册逻辑... mapOf( "userId" to "user-123", "message" to "注册成功" ) } } // 登录工具 val loginTool = zodTool, Map> { id = "login" name = "用户登录" description = "用户登录系统" inputSchema = objectInput { field("email", emailSchema) field("password", passwordSchema) } outputSchema = objectOutput { stringField("token", "访问令牌") stringField("message", "登录结果消息") } execute = { input -> // 实现登录逻辑... mapOf( "token" to "jwt-token-xyz", "message" to "登录成功" ) } } } // 在代理中使用 agent { // ... tools { tool(UserTools.registerTool.toTool()) tool(UserTools.loginTool.toTool()) } } ``` ## 总结 ✅ Kastrax 的 Zod 工具系统提供了一种类型安全、可组合和可扩展的方式来定义和使用工具。通过使用 Zod 工具,您可以: - 创建具有严格类型检查的工具 - 自动验证输入数据 - 将验证后的数据转换为 Kotlin 数据类 - 提供详细的错误信息 - 组合和重用模式定义 这些功能使您能够构建更加健壮和可维护的 AI 代理应用程序。 --- title: "分支、合并、条件 | 工作流 | Kastrax 文档" description: "Kastrax 工作流中的控制流允许您管理分支、合并和条件,以构建满足您逻辑需求的工作流。" --- # 工作流中的控制流:分支、合并和条件 ✅ [ZH] Source: https://kastrax.ai/zh/docs/workflows/control-flow 当您创建多步骤流程时,您可能需要并行运行步骤、按顺序链接它们,或者根据结果遵循不同的路径。本页描述了如何管理分支、合并和条件,以构建满足您逻辑需求的工作流。代码片段展示了构建复杂控制流的关键模式。 ## 并行执行 ✅ 如果步骤之间没有依赖关系,您可以同时运行多个步骤。当步骤执行独立任务时,这种方法可以加速您的工作流。下面的代码展示了如何并行添加两个步骤: ```typescript myWorkflow.step(fetchUserData).step(fetchOrderData); ``` 有关更多详细信息,请参见[并行步骤](../../examples/workflows/parallel-steps.mdx)示例。 ## 顺序执行 ✅ 有时您需要按严格顺序运行步骤,以确保一个步骤的输出成为下一个步骤的输入。使用 .then() 链接依赖操作。下面的代码展示了如何按顺序链接步骤: ```typescript myWorkflow.step(fetchOrderData).then(validateData).then(processOrder); ``` 有关更多详细信息,请参见[顺序步骤](../../examples/workflows/sequential-steps.mdx)示例。 ## 分支和合并路径 ✅ 当不同的结果需要不同的路径时,分支很有用。一旦完成,您也可以稍后合并路径。下面的代码展示了如何在 stepA 之后分支,然后在 stepF 上汇合: ```typescript myWorkflow .step(stepA) .then(stepB) .then(stepD) .after(stepA) .step(stepC) .then(stepE) .after([stepD, stepE]) .step(stepF); ``` 在此示例中: - stepA 导致 stepB,然后到 stepD。 - 单独地,stepA 也触发 stepC,而 stepC 又导致 stepE。 - 单独地,当 stepD 和 stepE 都完成时,触发 stepF。 有关更多详细信息,请参见[分支路径](../../examples/workflows/branching-paths.mdx)示例。 ## 合并多个分支 ✅ 有时您需要一个步骤仅在多个其他步骤完成后执行。Kastrax 提供了一个复合 `.after([])` 语法,允许您为一个步骤指定多个依赖项。 ```typescript myWorkflow .step(fetchUserData) .then(validateUserData) .step(fetchProductData) .then(validateProductData) // 此步骤仅在 validateUserData 和 validateProductData 都完成后运行 .after([validateUserData, validateProductData]) .step(processOrder) ``` 在此示例中: - `fetchUserData` 和 `fetchProductData` 在并行分支中运行 - 每个分支都有自己的验证步骤 - `processOrder` 步骤仅在两个验证步骤都成功完成后执行 这种模式特别适用于: - 连接并行执行路径 - 在工作流中实现同步点 - 确保在继续之前所有必需的数据都可用 您还可以通过组合多个 `.after([])` 调用来创建复杂的依赖模式: ```typescript myWorkflow // 第一个分支 .step(stepA) .then(stepB) .then(stepC) // 第二个分支 .step(stepD) .then(stepE) // 第三个分支 .step(stepF) .then(stepG) // 此步骤依赖于多个分支的完成 .after([stepC, stepE, stepG]) .step(finalStep) ``` ## 循环依赖和循环 ✅ 工作流通常需要重复步骤,直到满足特定条件。Kastrax 提供了两种强大的方法来创建循环:`until` 和 `while`。这些方法提供了一种直观的方式来实现重复任务。 ### 使用手动循环依赖(传统方法) 在早期版本中,您可以通过手动定义带有条件的循环依赖来创建循环: ```typescript myWorkflow .step(fetchData) .then(processData) .after(processData) .step(finalizeData, { when: { "processData.status": "success" }, }) .step(fetchData, { when: { "processData.status": "retry" }, }); ``` 虽然这种方法仍然有效,但较新的 `until` 和 `while` 方法提供了一种更清晰和更易于维护的方式来创建循环。 ### 使用 `until` 进行基于条件的循环 `until` 方法重复一个步骤,直到指定的条件变为真。它接受以下参数: 1. 确定何时停止循环的条件 2. 要重复的步骤 3. 可选的变量,传递给重复的步骤 ```typescript // 递增计数器直到达到目标的步骤 const incrementStep = new Step({ id: 'increment', inputSchema: z.object({ // 当前计数器值 counter: z.number().optional(), }), outputSchema: z.object({ // 更新后的计数器值 updatedCounter: z.number(), }), execute: async ({ context }) => { const { counter = 0 } = context.inputData; return { updatedCounter: counter + 1 }; }, }); workflow .step(incrementStep) .until( async ({ context }) => { // 当计数器达到 10 时停止 const result = context.getStepResult(incrementStep); return (result?.updatedCounter ?? 0) >= 10; }, incrementStep, { // 将当前计数器传递给下一次迭代 counter: { step: incrementStep, path: 'updatedCounter' } } ) .then(finalStep); ``` 您也可以使用基于引用的条件: ```typescript workflow .step(incrementStep) .until( { ref: { step: incrementStep, path: 'updatedCounter' }, query: { $gte: 10 }, }, incrementStep, { counter: { step: incrementStep, path: 'updatedCounter' } } ) .then(finalStep); ``` ### 使用 `while` 进行基于条件的循环 `while` 方法重复一个步骤,只要指定的条件保持为真。它接受与 `until` 相同的参数: 1. 确定何时继续循环的条件 2. 要重复的步骤 3. 可选的变量,传递给重复的步骤 ```typescript // 在低于目标时递增计数器的步骤 const incrementStep = new Step({ id: 'increment', inputSchema: z.object({ // 当前计数器值 counter: z.number().optional(), }), outputSchema: z.object({ // 更新后的计数器值 updatedCounter: z.number(), }), execute: async ({ context }) => { const { counter = 0 } = context.inputData; return { updatedCounter: counter + 1 }; }, }); workflow .step(incrementStep) .while( async ({ context }) => { // 当计数器小于 10 时继续 const result = context.getStepResult(incrementStep); return (result?.updatedCounter ?? 0) < 10; }, incrementStep, { // 将当前计数器传递给下一次迭代 counter: { step: incrementStep, path: 'updatedCounter' } } ) .then(finalStep); ``` 您也可以使用基于引用的条件: ```typescript workflow .step(incrementStep) .while( { ref: { step: incrementStep, path: 'updatedCounter' }, query: { $lt: 10 }, }, incrementStep, { counter: { step: incrementStep, path: 'updatedCounter' } } ) .then(finalStep); ``` ### 引用条件的比较运算符 使用基于引用的条件时,您可以使用以下比较运算符: | 运算符 | 描述 | |----------|-------------| | `$eq` | 等于 | | `$ne` | 不等于 | | `$gt` | 大于 | | `$gte` | 大于或等于 | | `$lt` | 小于 | | `$lte` | 小于或等于 | ## 条件 ✅ 使用 when 属性根据先前步骤的数据控制步骤是否运行。以下是指定条件的三种方式。 ### 选项 1:函数 ```typescript myWorkflow.step( new Step({ id: "processData", execute: async ({ context }) => { // 动作逻辑 }, }), { when: async ({ context }) => { const fetchData = context?.getStepResult<{ status: string }>("fetchData"); return fetchData?.status === "success"; }, }, ); ``` ### 选项 2:查询对象 ```typescript myWorkflow.step( new Step({ id: "processData", execute: async ({ context }) => { // 动作逻辑 }, }), { when: { ref: { step: { id: "fetchData", }, path: "status", }, query: { $eq: "success" }, }, }, ); ``` ### 选项 3:简单路径比较 ```typescript myWorkflow.step( new Step({ id: "processData", execute: async ({ context }) => { // 动作逻辑 }, }), { when: { "fetchData.status": "success", }, }, ); ``` ## 数据访问模式 ✅ Kastrax 提供了几种在步骤之间传递数据的方式: 1. **上下文对象** - 通过上下文对象直接访问步骤结果 2. **变量映射** - 显式地将一个步骤的输出映射到另一个步骤的输入 3. **getStepResult 方法** - 类型安全的方法来检索步骤输出 每种方法根据您的用例和类型安全要求都有其优势。 ### 使用 getStepResult 方法 `getStepResult` 方法提供了一种类型安全的方式来访问步骤结果。在使用 TypeScript 时,这是推荐的方法,因为它保留了类型信息。 #### 基本用法 为了更好的类型安全,您可以为 `getStepResult` 提供类型参数: ```typescript showLineNumbers filename="src/kastrax/workflows/get-step-result.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; const fetchUserStep = new Step({ id: 'fetchUser', outputSchema: z.object({ name: z.string(), userId: z.string(), }), execute: async ({ context }) => { return { name: 'John Doe', userId: '123' }; }, }); const analyzeDataStep = new Step({ id: "analyzeData", execute: async ({ context }) => { // 类型安全地访问先前步骤结果 const userData = context.getStepResult<{ name: string, userId: string }>("fetchUser"); if (!userData) { return { status: "error", message: "User data not found" }; } return { analysis: `Analyzed data for user ${userData.name}`, userId: userData.userId }; }, }); ``` #### 使用步骤引用 最类型安全的方法是在 `getStepResult` 调用中直接引用步骤: ```typescript showLineNumbers filename="src/kastrax/workflows/step-reference.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; // 定义带有输出模式的步骤 const fetchUserStep = new Step({ id: "fetchUser", outputSchema: z.object({ userId: z.string(), name: z.string(), email: z.string(), }), execute: async () => { return { userId: "user123", name: "John Doe", email: "john@example.com" }; }, }); const processUserStep = new Step({ id: "processUser", execute: async ({ context }) => { // TypeScript 将从 fetchUserStep 的 outputSchema 推断正确的类型 const userData = context.getStepResult(fetchUserStep); return { processed: true, userName: userData?.name }; }, }); const workflow = new Workflow({ name: "user-workflow", }); workflow .step(fetchUserStep) .then(processUserStep) .commit(); ``` ### 使用变量映射 变量映射是定义步骤之间数据流的显式方式。 这种方法使依赖关系清晰,并提供良好的类型安全。 注入到步骤中的数据在 `context.inputData` 对象中可用,并基于步骤的 `inputSchema` 进行类型化。 ```typescript showLineNumbers filename="src/kastrax/workflows/variable-mapping.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; const fetchUserStep = new Step({ id: "fetchUser", outputSchema: z.object({ userId: z.string(), name: z.string(), email: z.string(), }), execute: async () => { return { userId: "user123", name: "John Doe", email: "john@example.com" }; }, }); const sendEmailStep = new Step({ id: "sendEmail", inputSchema: z.object({ recipientEmail: z.string(), recipientName: z.string(), }), execute: async ({ context }) => { const { recipientEmail, recipientName } = context.inputData; // 发送电子邮件逻辑在这里 return { status: "sent", to: recipientEmail }; }, }); const workflow = new Workflow({ name: "email-workflow", }); workflow .step(fetchUserStep) .then(sendEmailStep, { variables: { // 将 fetchUser 的特定字段映射到 sendEmail 输入 recipientEmail: { step: fetchUserStep, path: 'email' }, recipientName: { step: fetchUserStep, path: 'name' } } }) .commit(); ``` 有关变量映射的更多详细信息,请参见[使用工作流变量进行数据映射](./variables.mdx)文档。 ### 使用上下文对象 上下文对象提供了对所有步骤结果及其输出的直接访问。这种方法更灵活,但需要谨慎处理以维持类型安全。 您可以通过 `context.steps` 对象直接访问步骤结果: ```typescript showLineNumbers filename="src/kastrax/workflows/context-access.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; const processOrderStep = new Step({ id: 'processOrder', execute: async ({ context }) => { // 访问先前步骤的数据 let userData: { name: string, userId: string }; if (context.steps['fetchUser']?.status === 'success') { userData = context.steps.fetchUser.output; } else { throw new Error('User data not found'); } return { orderId: 'order123', userId: userData.userId, status: 'processing', }; }, }); const workflow = new Workflow({ name: "order-workflow", }); workflow .step(fetchUserStep) .then(processOrderStep) .commit(); ``` ### 工作流级别的类型安全 为了在整个工作流中实现全面的类型安全,您可以为所有步骤定义类型并将它们传递给工作流 这允许您在条件上获得上下文对象的类型安全,以及在最终工作流输出中获得步骤结果的类型安全。 ```typescript showLineNumbers filename="src/kastrax/workflows/workflow-typing.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; // 创建带有类型化输出的步骤 const fetchUserStep = new Step({ id: "fetchUser", outputSchema: z.object({ userId: z.string(), name: z.string(), email: z.string(), }), execute: async () => { return { userId: "user123", name: "John Doe", email: "john@example.com" }; }, }); const processOrderStep = new Step({ id: "processOrder", execute: async ({ context }) => { // TypeScript 知道 userData 的形状 const userData = context.getStepResult(fetchUserStep); return { orderId: "order123", status: "processing" }; }, }); const workflow = new Workflow<[typeof fetchUserStep, typeof processOrderStep]>({ name: "typed-workflow", }); workflow .step(fetchUserStep) .then(processOrderStep) .until(async ({ context }) => { // TypeScript 在这里知道 userData 的形状 const res = context.getStepResult('fetchUser'); return res?.userId === '123'; }, processOrderStep) .commit(); ``` ### 访问触发器数据 除了步骤结果外,您还可以访问启动工作流的原始触发器数据: ```typescript showLineNumbers filename="src/kastrax/workflows/trigger-data.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; // 定义触发器模式 const triggerSchema = z.object({ customerId: z.string(), orderItems: z.array(z.string()), }); type TriggerType = z.infer; const processOrderStep = new Step({ id: "processOrder", execute: async ({ context }) => { // 类型安全地访问触发器数据 const triggerData = context.getStepResult('trigger'); return { customerId: triggerData?.customerId, itemCount: triggerData?.orderItems.length || 0, status: "processing" }; }, }); const workflow = new Workflow({ name: "order-workflow", triggerSchema, }); workflow .step(processOrderStep) .commit(); ``` ### 访问恢复数据 注入到步骤中的数据在 `context.inputData` 对象中可用,并基于步骤的 `inputSchema` 进行类型化。 ```typescript showLineNumbers filename="src/kastrax/workflows/resume-data.ts" copy import { Step, Workflow } from "@kastrax/core/workflows"; import { z } from "zod"; const processOrderStep = new Step({ id: "processOrder", inputSchema: z.object({ orderId: z.string(), }), execute: async ({ context, suspend }) => { const { orderId } = context.inputData; if (!orderId) { await suspend(); return; } return { orderId, status: "processed" }; }, }); const workflow = new Workflow({ name: "order-workflow", }); workflow .step(processOrderStep) .commit(); const run = workflow.createRun(); const result = await run.start(); const resumedResult = await workflow.resume({ runId: result.runId, stepId: 'processOrder', inputData: { orderId: '123', }, }); console.log({resumedResult}); ``` ### 访问工作流结果 您可以通过将步骤类型注入到 `Workflow` 类型参数中,获得对工作流结果的类型化访问: ```typescript showLineNumbers filename="src/kastrax/workflows/get-results.ts" copy import { Workflow } from "@kastrax/core/workflows"; const fetchUserStep = new Step({ id: "fetchUser", outputSchema: z.object({ userId: z.string(), name: z.string(), email: z.string(), }), execute: async () => { return { userId: "user123", name: "John Doe", email: "john@example.com" }; }, }); const processOrderStep = new Step({ id: "processOrder", outputSchema: z.object({ orderId: z.string(), status: z.string(), }), execute: async ({ context }) => { const userData = context.getStepResult(fetchUserStep); return { orderId: "order123", status: "processing" }; }, }); const workflow = new Workflow<[typeof fetchUserStep, typeof processOrderStep]>({ name: "typed-workflow", }); workflow .step(fetchUserStep) .then(processOrderStep) .commit(); const run = workflow.createRun(); const result = await run.start(); // 结果是步骤结果的判别联合 // 所以需要通过状态检查来缩小范围 if (result.results.processOrder.status === 'success') { // TypeScript 将知道结果的形状 const orderId = result.results.processOrder.output.orderId; console.log({orderId}); } if (result.results.fetchUser.status === 'success') { const userId = result.results.fetchUser.output.userId; console.log({userId}); } ``` ### 数据流的最佳实践 1. **使用带有步骤引用的 getStepResult 以获得类型安全** - 确保 TypeScript 可以推断正确的类型 - 在编译时捕获类型错误 2. **使用变量映射以获得显式依赖** - 使数据流清晰和可维护 - 提供步骤依赖的良好文档 3. **为步骤定义输出模式** - 在运行时验证数据 - 验证 `execute` 函数的返回类型 - 改进 TypeScript 中的类型推断 4. **优雅地处理缺失数据** - 在访问属性之前始终检查步骤结果是否存在 - 为可选数据提供回退值 5. **保持数据转换简单** - 在专用步骤中转换数据,而不是在变量映射中 - 使工作流更容易测试和调试 ### 数据流方法的比较 | 方法 | 类型安全 | 显式性 | 用例 | |--------|------------|--------------|----------| | getStepResult | 最高 | 高 | 具有严格类型要求的复杂工作流 | | 变量映射 | 高 | 高 | 当依赖需要清晰和显式时 | | context.steps | 中等 | 低 | 在简单工作流中快速访问步骤数据 | 通过为您的用例选择正确的数据流方法,您可以创建既类型安全又可维护的工作流。 --- title: "工作流中的错误处理 | Kastrax 文档" description: "学习如何在 Kastrax 工作流中使用步骤重试、条件分支和监控来处理错误。" --- # 工作流中的错误处理 ✅ [ZH] Source: https://kastrax.ai/zh/docs/workflows/error-handling 强大的错误处理对于生产工作流至关重要。Kastrax 提供了几种机制来优雅地处理错误,使您的工作流能够从故障中恢复或在必要时优雅地降级。 ## 概述 ✅ Kastrax 工作流中的错误处理可以通过以下方式实现: 1. **步骤重试** - 自动重试失败的步骤 2. **条件分支** - 基于步骤成功或失败创建替代路径 3. **错误监控** - 监视工作流中的错误并以编程方式处理它们 4. **结果状态检查** - 在后续步骤中检查先前步骤的状态 ## 步骤重试 ✅ Kastrax 为因暂时性错误而失败的步骤提供了内置的重试机制。这对于与可能经历临时不可用的外部服务或资源交互的步骤特别有用。 ### 基本重试配置 您可以在工作流级别或针对单个步骤配置重试: ```typescript // 工作流级别的重试配置 const workflow = new Workflow({ name: 'my-workflow', retryConfig: { attempts: 3, // 重试尝试次数 delay: 1000, // 重试之间的延迟(毫秒) }, }); // 步骤级别的重试配置(覆盖工作流级别) const apiStep = new Step({ id: 'callApi', execute: async () => { // 可能失败的 API 调用 }, retryConfig: { attempts: 5, // 此步骤将重试最多 5 次 delay: 2000, // 重试之间有 2 秒延迟 }, }); ``` 有关步骤重试的更多详细信息,请参见[步骤重试](../../reference/workflows/step-retries.mdx)参考。 ## 条件分支 ✅ 您可以使用条件逻辑基于先前步骤的成功或失败创建替代工作流路径: ```typescript // 创建带有条件分支的工作流 const workflow = new Workflow({ name: 'error-handling-workflow', }); workflow .step(fetchDataStep) .then(processDataStep, { // 仅在 fetchDataStep 成功时执行 processDataStep when: ({ context }) => { return context.steps.fetchDataStep?.status === 'success'; }, }) .then(fallbackStep, { // 如果 fetchDataStep 失败则执行 fallbackStep when: ({ context }) => { return context.steps.fetchDataStep?.status === 'failed'; }, }) .commit(); ``` ## 错误监控 ✅ 您可以使用 `watch` 方法监控工作流中的错误: ```typescript const { start, watch } = workflow.createRun(); watch(async ({ results }) => { // 检查任何失败的步骤 const failedSteps = Object.entries(results) .filter(([_, step]) => step.status === "failed") .map(([stepId]) => stepId); if (failedSteps.length > 0) { console.error(`工作流有失败的步骤: ${failedSteps.join(', ')}`); // 采取补救措施,如警报或日志记录 } }); await start(); ``` ## 在步骤中处理错误 ✅ 在步骤的执行函数中,您可以以编程方式处理错误: ```typescript const robustStep = new Step({ id: 'robustStep', execute: async ({ context }) => { try { // 尝试主要操作 const result = await someRiskyOperation(); return { success: true, data: result }; } catch (error) { // 记录错误 console.error('操作失败:', error); // 返回优雅的回退结果而不是抛出异常 return { success: false, error: error.message, fallbackData: '默认值' }; } }, }); ``` ## 检查先前步骤结果 ✅ 您可以基于先前步骤的结果做出决策: ```typescript const finalStep = new Step({ id: 'finalStep', execute: async ({ context }) => { // 检查先前步骤的结果 const step1Success = context.steps.step1?.status === 'success'; const step2Success = context.steps.step2?.status === 'success'; if (step1Success && step2Success) { // 所有步骤都成功 return { status: 'complete', result: '所有操作成功' }; } else if (step1Success) { // 只有 step1 成功 return { status: 'partial', result: '部分完成' }; } else { // 关键失败 return { status: 'failed', result: '关键步骤失败' }; } }, }); ``` ## 错误处理的最佳实践 ✅ 1. **对暂时性故障使用重试**:为可能遇到临时问题的步骤配置重试策略。 2. **提供回退路径**:设计具有关键步骤失败时替代路径的工作流。 3. **明确错误场景**:对不同类型的错误使用不同的处理策略。 4. **全面记录错误**:记录错误时包含上下文信息以帮助调试。 5. **在失败时返回有意义的数据**:当步骤失败时,返回关于失败的结构化数据,以帮助下游步骤做出决策。 6. **考虑幂等性**:确保步骤可以安全地重试而不会导致重复的副作用。 7. **监控工作流执行**:使用 `watch` 方法主动监控工作流执行并尽早检测错误。 ## 高级错误处理 ✅ 对于更复杂的错误处理场景,请考虑: - **实现断路器**:如果步骤反复失败,停止重试并使用回退策略 - **添加超时处理**:为步骤设置时间限制,防止工作流无限期挂起 - **创建专用错误恢复工作流**:对于关键工作流,创建可在主工作流失败时触发的单独恢复工作流 ## 相关内容 ✅ - [步骤重试参考](../../reference/workflows/step-retries.mdx) - [Watch 方法参考](../../reference/workflows/watch.mdx) - [步骤条件](../../reference/workflows/step-condition.mdx) - [控制流](./control-flow.mdx) # 嵌套工作流 ✅ [ZH] Source: https://kastrax.ai/zh/docs/workflows/nested-workflows Kastrax 允许您在其他工作流中使用工作流作为步骤,使您能够创建模块化和可重用的工作流组件。这个功能有助于将复杂的工作流组织成更小、更易于管理的部分,并促进代码重用。 当您可以在父工作流中将嵌套工作流视为步骤时,也更容易直观地理解工作流的流程。 ## 基本用法 ✅ 您可以使用 `step()` 方法直接在另一个工作流中使用工作流作为步骤: ```typescript // 创建嵌套工作流 const nestedWorkflow = new Workflow({ name: "nested-workflow" }) .step(stepA) .then(stepB) .commit(); // 在父工作流中使用嵌套工作流 const parentWorkflow = new Workflow({ name: "parent-workflow" }) .step(nestedWorkflow, { variables: { city: { step: "trigger", path: "myTriggerInput", }, }, }) .then(stepC) .commit(); ``` 当工作流被用作步骤时: - 它会自动转换为步骤,使用工作流的名称作为步骤 ID - 工作流的结果在父工作流的上下文中可用 - 嵌套工作流的步骤按其定义的顺序执行 ## 访问结果 ✅ 嵌套工作流的结果在父工作流的上下文中以嵌套工作流的名称提供。结果包括嵌套工作流中的所有步骤输出: ```typescript const { results } = await parentWorkflow.start(); // 访问嵌套工作流结果 const nestedWorkflowResult = results["nested-workflow"]; if (nestedWorkflowResult.status === "success") { const nestedResults = nestedWorkflowResult.output.results; } ``` ## 使用嵌套工作流的控制流 ✅ 嵌套工作流支持所有可用于常规步骤的控制流功能: ### 并行执行 多个嵌套工作流可以并行执行: ```typescript parentWorkflow .step(nestedWorkflowA) .step(nestedWorkflowB) .after([nestedWorkflowA, nestedWorkflowB]) .step(finalStep); ``` 或者使用带有工作流数组的 `step()`: ```typescript parentWorkflow.step([nestedWorkflowA, nestedWorkflowB]).then(finalStep); ``` 在这种情况下,`then()` 将隐式等待所有工作流完成后再执行最终步骤。 ### If-Else 分支 嵌套工作流可以在 if-else 分支中使用,新语法接受两个分支作为参数: ```typescript // 为不同路径创建嵌套工作流 const workflowA = new Workflow({ name: "workflow-a" }) .step(stepA1) .then(stepA2) .commit(); const workflowB = new Workflow({ name: "workflow-b" }) .step(stepB1) .then(stepB2) .commit(); // 使用带有嵌套工作流的新 if-else 语法 parentWorkflow .step(initialStep) .if( async ({ context }) => { // 您的条件在这里 return someCondition; }, workflowA, // if 分支 workflowB, // else 分支 ) .then(finalStep) .commit(); ``` 使用嵌套工作流时,新语法更简洁、更清晰。当条件为: - `true`:执行第一个工作流(if 分支) - `false`:执行第二个工作流(else 分支) 跳过的工作流在结果中的状态为 `skipped`: if-else 块后面的 `.then(finalStep)` 调用将 if 和 else 分支合并回单个执行路径。 ### 循环 嵌套工作流可以像任何其他步骤一样使用 `.until()` 和 `.while()` 循环。一个有趣的新模式是将工作流直接作为循环回参数传递,以继续执行该嵌套工作流,直到其结果满足某个条件: ```typescript parentWorkflow .step(firstStep) .while( ({ context }) => context.getStepResult("nested-workflow").output.results.someField === "someValue", nestedWorkflow, ) .step(finalStep) .commit(); ``` ## 监视嵌套工作流 ✅ 您可以使用父工作流上的 `watch` 方法监视嵌套工作流的状态变化。这对于监控复杂工作流的进度和状态转换很有用: ```typescript const parentWorkflow = new Workflow({ name: "parent-workflow" }) .step([nestedWorkflowA, nestedWorkflowB]) .then(finalStep) .commit(); const run = parentWorkflow.createRun(); const unwatch = parentWorkflow.watch((state) => { console.log("当前状态:", state.value); // 在 state.context 中访问嵌套工作流状态 }); await run.start(); unwatch(); // 完成后停止监视 ``` ## 暂停和恢复 ✅ 嵌套工作流支持暂停和恢复,允许您在特定点暂停和继续工作流执行。您可以暂停整个嵌套工作流或其中的特定步骤: ```typescript // 定义可能需要暂停的步骤 const suspendableStep = new Step({ id: "other", description: "可能需要暂停的步骤", execute: async ({ context, suspend }) => { if (!wasSuspended) { wasSuspended = true; await suspend(); } return { other: 26 }; }, }); // 创建带有可暂停步骤的嵌套工作流 const nestedWorkflow = new Workflow({ name: "nested-workflow-a" }) .step(startStep) .then(suspendableStep) .then(finalStep) .commit(); // 在父工作流中使用 const parentWorkflow = new Workflow({ name: "parent-workflow" }) .step(beginStep) .then(nestedWorkflow) .then(lastStep) .commit(); // 启动工作流 const run = parentWorkflow.createRun(); const { runId, results } = await run.start({ triggerData: { startValue: 1 } }); // 检查嵌套工作流中的特定步骤是否已暂停 if (results["nested-workflow-a"].output.results.other.status === "suspended") { // 使用点表示法恢复特定的暂停步骤 const resumedResults = await run.resume({ stepId: "nested-workflow-a.other", context: { startValue: 1 }, }); // 恢复的结果将包含已完成的嵌套工作流 expect(resumedResults.results["nested-workflow-a"].output.results).toEqual({ start: { output: { newValue: 1 }, status: "success" }, other: { output: { other: 26 }, status: "success" }, final: { output: { finalValue: 27 }, status: "success" }, }); } ``` 恢复嵌套工作流时: - 调用 `resume()` 时使用嵌套工作流的名称作为 `stepId` 来恢复整个工作流 - 使用点表示法(`nested-workflow.step-name`)恢复嵌套工作流中的特定步骤 - 嵌套工作流将从暂停的步骤继续,使用提供的上下文 - 您可以使用 `results["nested-workflow"].output.results` 检查嵌套工作流结果中特定步骤的状态 ## 结果模式和映射 ✅ 嵌套工作流可以定义其结果模式和映射,这有助于类型安全和数据转换。当您希望确保嵌套工作流的输出匹配特定结构,或者在父工作流中使用结果之前需要转换结果时,这特别有用。 ```typescript // 创建带有结果模式和映射的嵌套工作流 const nestedWorkflow = new Workflow({ name: "nested-workflow", result: { schema: z.object({ total: z.number(), items: z.array( z.object({ id: z.string(), value: z.number(), }), ), }), mapping: { // 使用变量语法从步骤结果映射值 total: { step: "step-a", path: "count" }, items: { step: "step-b", path: "items" }, }, }, }) .step(stepA) .then(stepB) .commit(); // 在父工作流中使用类型安全的结果 const parentWorkflow = new Workflow({ name: "parent-workflow" }) .step(nestedWorkflow) .then(async ({ context }) => { const result = context.getStepResult("nested-workflow"); // TypeScript 知道结果的结构 console.log(result.total); // number console.log(result.items); // Array<{ id: string, value: number }> return { success: true }; }) .commit(); ``` ## 最佳实践 ✅ 1. **模块化**:使用嵌套工作流封装相关步骤并创建可重用的工作流组件。 2. **命名**:给嵌套工作流起描述性名称,因为它们将在父工作流中用作步骤 ID。 3. **错误处理**:嵌套工作流将其错误传播到父工作流,因此要适当处理错误。 4. **状态管理**:每个嵌套工作流维护自己的状态,但可以访问父工作流的上下文。 5. **暂停**:在嵌套工作流中使用暂停时,考虑整个工作流的状态并适当处理恢复。 ## 示例 ✅ 这里是一个展示嵌套工作流各种功能的完整示例: ```typescript const workflowA = new Workflow({ name: "workflow-a", result: { schema: z.object({ activities: z.string(), }), mapping: { activities: { step: planActivities, path: "activities", }, }, }, }) .step(fetchWeather) .then(planActivities) .commit(); const workflowB = new Workflow({ name: "workflow-b", result: { schema: z.object({ activities: z.string(), }), mapping: { activities: { step: planActivities, path: "activities", }, }, }, }) .step(fetchWeather) .then(planActivities) .commit(); const weatherWorkflow = new Workflow({ name: "weather-workflow", triggerSchema: z.object({ cityA: z.string().describe("要获取天气的城市"), cityB: z.string().describe("要获取天气的城市"), }), result: { schema: z.object({ activitiesA: z.string(), activitiesB: z.string(), }), mapping: { activitiesA: { step: workflowA, path: "result.activities", }, activitiesB: { step: workflowB, path: "result.activities", }, }, }, }) .step(workflowA, { variables: { city: { step: "trigger", path: "cityA", }, }, }) .step(workflowB, { variables: { city: { step: "trigger", path: "cityB", }, }, }); weatherWorkflow.commit(); ``` 在这个示例中: 1. 我们为所有工作流定义模式以确保类型安全 2. 每个步骤都有适当的输入和输出模式 3. 嵌套工作流有自己的触发器模式和结果映射 4. 数据通过 `.step()` 调用中的变量语法传递 5. 主工作流结合了两个嵌套工作流的数据 --- title: 工作流系统概述 | Kastrax 文档 description: Kastrax 中工作流系统的概述,详细说明如何创建、配置和执行工作流。 --- # 工作流系统概述 ✅ [ZH] Source: https://kastrax.ai/zh/docs/workflows/overview-kotlin Kastrax 工作流系统允许您定义、执行和监控涉及代理和其他组件的复杂操作序列。本指南解释了工作流系统架构以及如何有效地使用它。 ## 什么是工作流? ✅ Kastrax 中的工作流是按特定顺序执行的结构化步骤序列,数据在步骤之间流动。它们使您能够: - 编排多个代理协作处理复杂任务 - 基于中间结果定义条件执行路径 - 在步骤之间处理和转换数据 - 处理错误和重试 - 监控和跟踪执行进度 - 为常见代理交互创建可重用模式 ## 工作流系统架构 ✅ Kastrax 工作流系统由几个关键组件组成: 1. **Workflow**:定义步骤序列的顶级容器 2. **WorkflowStep**:工作流内的单个工作单元 3. **WorkflowContext**:在步骤之间传递的共享状态和数据 4. **WorkflowEngine**:执行工作流的运行时 5. **WorkflowBuilder**:用于创建工作流的 DSL 6. **WorkflowStateStorage**:工作流执行状态的存储 ## 创建基本工作流 ✅ Kastrax 提供了用于创建工作流的 DSL: ```kotlin import ai.kastrax.core.workflow.builder.workflow import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建工作流的代理 val researchAgent = agent { name("研究代理") description("一个研究主题的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } val writingAgent = agent { name("写作代理") description("一个基于研究写内容的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // 创建工作流 val researchWorkflow = workflow("research-workflow", "研究并撰写文章") { // 研究步骤 step(researchAgent) { id = "research" name = "研究" description = "研究主题" variables = mutableMapOf( "topic" to variable("$.input.topic") ) } // 写作步骤 step(writingAgent) { id = "writing" name = "写作" description = "基于研究撰写文章" after("research") variables = mutableMapOf( "research" to variable("$.steps.research.output.text") ) } // 定义输出映射 output { "researchResults" from "$.steps.research.output.text" "article" from "$.steps.writing.output.text" "wordCount" from { "$.steps.writing.output.text" to { text -> (text as? String)?.split(" ")?.size ?: 0 } } } } // 执行工作流 val workflowEngine = WorkflowEngine() workflowEngine.registerWorkflow("research-workflow", researchWorkflow) val result = workflowEngine.executeWorkflow( workflowId = "research-workflow", input = mapOf("topic" to "人工智能") ) println("工作流结果: ${result.output}") } ``` ## 工作流组件 ✅ ### Workflow ✅ `Workflow` 接口定义了工作流的核心功能: ```kotlin interface Workflow { suspend fun execute( input: Map, options: WorkflowExecuteOptions = WorkflowExecuteOptions() ): WorkflowResult suspend fun streamExecute( input: Map, options: WorkflowExecuteOptions = WorkflowExecuteOptions() ): Flow } ``` ### WorkflowStep ✅ `WorkflowStep` 接口定义了工作流内的单个步骤: ```kotlin interface WorkflowStep { val id: String val name: String val description: String val after: List val variables: Map val config: StepConfig? get() = null val condition: (WorkflowContext) -> Boolean get() = { true } suspend fun execute(context: WorkflowContext): WorkflowStepResult } ``` ### WorkflowContext ✅ `WorkflowContext` 类在工作流执行期间保存状态和数据: ```kotlin data class WorkflowContext( val input: Map, val steps: MutableMap = mutableMapOf(), val variables: MutableMap = mutableMapOf(), val runId: String? = null ) { fun resolveReference(reference: VariableReference): Any? { // 实现... } fun resolveVariables(variables: Map): Map { // 实现... } } ``` ### WorkflowEngine ✅ `WorkflowEngine` 类负责执行工作流: ```kotlin class WorkflowEngine( private val stateStorage: WorkflowStateStorage = InMemoryWorkflowStateStorage() ) { private val workflows = mutableMapOf() fun registerWorkflow(workflowId: String, workflow: Workflow) { workflows[workflowId] = workflow } suspend fun executeWorkflow( workflowId: String, input: Map, options: WorkflowExecuteOptions = WorkflowExecuteOptions() ): WorkflowResult { // 实现... } suspend fun streamExecuteWorkflow( workflowId: String, input: Map, options: WorkflowExecuteOptions = WorkflowExecuteOptions() ): Flow { // 实现... } } ``` ## 步骤类型 ✅ Kastrax 提供了几种内置步骤类型: ### 代理步骤 ✅ 最常见的步骤类型,使用特定输入执行代理: ```kotlin step(agent) { id = "generate-content" name = "生成内容" description = "基于主题生成内容" variables = mutableMapOf( "topic" to variable("$.input.topic"), "style" to variable("$.input.style") ) } ``` ### 函数步骤 ✅ 执行自定义函数: ```kotlin functionStep { id = "process-data" name = "处理数据" description = "处理来自前面步骤的数据" after("collect-data") function { context -> val data = context.resolveReference(variable("$.steps.collect-data.output.data")) // 处理数据 mapOf("processedData" to processData(data)) } } ``` ### 条件步骤 ✅ 基于条件执行: ```kotlin step(agent) { id = "optional-step" name = "可选步骤" description = "此步骤仅在特定条件下执行" after("previous-step") condition { context -> val quality = context.resolveReference(variable("$.steps.previous-step.output.quality")) (quality as? Int) ?: 0 > 7 } variables = mutableMapOf( "input" to variable("$.steps.previous-step.output.text") ) } ``` ### 子工作流步骤 ✅ 将另一个工作流作为步骤执行: ```kotlin subworkflowStep { id = "nested-workflow" name = "嵌套工作流" description = "执行嵌套工作流" workflowId = "another-workflow" inputMapping { context -> mapOf( "data" to context.resolveReference(variable("$.steps.previous-step.output.data")) ) } } ``` ## 数据流 ✅ 数据通过变量引用在工作流中流动: ### 变量引用 ✅ 变量引用使用类似 JSON 路径的语法访问数据: ```kotlin // 引用工作流输入 val topicRef = variable("$.input.topic") // 引用前一步骤的输出 val researchRef = variable("$.steps.research.output.text") // 引用全局变量 val configRef = variable("$.variables.config") ``` ### 输出映射 ✅ 输出映射定义如何提取和转换最终工作流输出: ```kotlin output { // 直接映射 "researchResults" from "$.steps.research.output.text" // 带转换的映射 "wordCount" from { "$.steps.writing.output.text" to { text -> (text as? String)?.split(" ")?.size ?: 0 } } // 组合多个来源 "summary" from { "$.steps.research.output.text" to { research -> "$.steps.writing.output.text" to { article -> "研究: $research\n\n文章: $article" } } } } ``` ## 错误处理 ✅ 工作流可以通过几种方式处理错误: ### 重试配置 ✅ ```kotlin step(agent) { id = "unreliable-step" name = "不可靠步骤" description = "可能失败的步骤" // 配置重试 config = StepConfig( retryConfig = RetryConfig( maxRetries = 3, retryDelay = 1000, // 1 秒 backoffMultiplier = 2.0 // 指数退避 ) ) } ``` ### 错误处理器 ✅ ```kotlin // 创建错误处理工作流引擎 val workflowEngine = ErrorHandlingWorkflowEngine( errorHandler = object : ErrorHandler { override suspend fun handleStepError( workflowId: String, runId: String, stepId: String, error: Throwable ): ErrorHandlingAction { return when { error is TimeoutException -> ErrorHandlingAction.Retry(maxRetries = 3) stepId == "optional-step" -> ErrorHandlingAction.Skip else -> ErrorHandlingAction.Fail } } } ) ``` ## 工作流执行 ✅ 工作流可以通过不同方式执行: ### 同步执行 ✅ ```kotlin val result = workflowEngine.executeWorkflow( workflowId = "my-workflow", input = mapOf("key" to "value") ) println("工作流完成,输出: ${result.output}") ``` ### 流式执行 ✅ ```kotlin workflowEngine.streamExecuteWorkflow( workflowId = "my-workflow", input = mapOf("key" to "value") ).collect { update -> when (update) { is WorkflowStatusUpdate.Started -> println("工作流已启动") is WorkflowStatusUpdate.StepStarted -> println("步骤已启动: ${update.stepId}") is WorkflowStatusUpdate.StepCompleted -> println("步骤已完成: ${update.stepId}") is WorkflowStatusUpdate.StepFailed -> println("步骤失败: ${update.stepId}, 错误: ${update.error}") is WorkflowStatusUpdate.Completed -> println("工作流已完成") is WorkflowStatusUpdate.Failed -> println("工作流失败: ${update.error}") } } ``` ### 执行选项 ✅ ```kotlin val options = WorkflowExecuteOptions( maxSteps = 20, // 最大执行步骤数 timeout = 120000, // 2 分钟超时 onStepFinish = { stepResult -> println("步骤 ${stepResult.stepId} 完成,输出: ${stepResult.output}") }, onStepError = { stepId, error -> println("步骤 $stepId 失败,错误: ${error.message}") }, threadId = "conversation-123" // 可选的线程 ID,用于记忆集成 ) val result = workflowEngine.executeWorkflow( workflowId = "my-workflow", input = mapOf("key" to "value"), options = options ) ``` ## 工作流状态管理 ✅ 工作流在执行期间和之后维护状态: ### 状态存储 ✅ ```kotlin // 内存状态存储(默认) val inMemoryStorage = InMemoryWorkflowStateStorage() // 基于文件的状态存储 val fileStorage = FileWorkflowStateStorage("workflows/state") // 数据库状态存储 val dbStorage = DatabaseWorkflowStateStorage(dataSource) // 使用自定义状态存储创建工作流引擎 val workflowEngine = WorkflowEngine(stateStorage = fileStorage) ``` ### 状态查询 ✅ ```kotlin // 获取工作流状态 val state = workflowEngine.getWorkflowState("my-workflow", "run-123") // 获取工作流的所有运行 val runs = workflowEngine.getWorkflowRuns("my-workflow") // 获取活动运行 val activeRuns = workflowEngine.getActiveWorkflowRuns() ``` ## 高级工作流功能 ✅ ### 并行执行 ✅ ```kotlin val parallelWorkflow = workflow("parallel-workflow", "并行执行步骤") { // 这些步骤没有依赖关系,所以可以并行运行 step(agent1) { id = "step1" name = "步骤 1" description = "第一个并行步骤" } step(agent2) { id = "step2" name = "步骤 2" description = "第二个并行步骤" } // 此步骤依赖于两个并行步骤 step(agent3) { id = "step3" name = "步骤 3" description = "并行执行后的最终步骤" after("step1", "step2") variables = mutableMapOf( "result1" to variable("$.steps.step1.output.result"), "result2" to variable("$.steps.step2.output.result") ) } } ``` ### 工作流组合 ✅ ```kotlin // 创建工作流组合器 val composer = WorkflowComposer("my-composer", workflowEngine) // 顺序组合工作流 val sequentialWorkflow = composer.composeSequential( workflowName = "sequential-workflow", description = "按顺序执行工作流", workflows = listOf( "workflow1" to { input -> input }, // workflow1 的输入映射 "workflow2" to { input, prevOutput -> prevOutput } // 使用前一个输出作为输入 ) ) // 并行组合工作流 val parallelWorkflow = composer.composeParallel( workflowName = "parallel-workflow", description = "并行执行工作流", workflows = mapOf( "branch1" to "workflow1", "branch2" to "workflow2" ), inputMapping = { branch, input -> // 为每个分支映射输入 when (branch) { "branch1" -> mapOf("key1" to input["value"]) "branch2" -> mapOf("key2" to input["value"]) else -> emptyMap() } }, mergeStep = MergeResultsStep( id = "merge", name = "合并结果", description = "合并来自并行工作流的结果" ) ) ``` ### 状态机 ✅ 对于具有多个可能路径的复杂工作流,您可以使用状态机: ```kotlin // 定义状态 enum class OrderState { NEW, PROCESSING, SHIPPED, DELIVERED, CANCELED } // 定义事件 sealed class OrderEvent { data class PlaceOrder(val items: List) : OrderEvent() data class ProcessOrder(val paymentId: String) : OrderEvent() data class ShipOrder(val trackingNumber: String) : OrderEvent() data class DeliverOrder(val deliveryDate: String) : OrderEvent() object CancelOrder : OrderEvent() } // 创建状态机 val orderStateMachine = BasicStateMachine>( initialState = OrderState.NEW, initialContext = emptyMap(), transitioner = object : StateTransitioner> { override suspend fun transition( currentState: OrderState, event: OrderEvent, context: Map ): TransitionResult> { // 定义状态转换 val (nextState, updatedContext) = when (currentState to event) { OrderState.NEW to is OrderEvent.PlaceOrder -> { OrderState.PROCESSING to context + ("items" to event.items) } OrderState.PROCESSING to is OrderEvent.ProcessOrder -> { OrderState.SHIPPED to context + ("paymentId" to event.paymentId) } OrderState.SHIPPED to is OrderEvent.ShipOrder -> { OrderState.DELIVERED to context + ("trackingNumber" to event.trackingNumber) } OrderState.SHIPPED to is OrderEvent.DeliverOrder -> { OrderState.DELIVERED to context + ("deliveryDate" to event.deliveryDate) } else to is OrderEvent.CancelOrder -> { OrderState.CANCELED to context } else -> currentState to context } return TransitionResult( nextState = nextState, updatedContext = updatedContext, sideEffects = emptyList() ) } } ) // 使用状态机 val nextState = orderStateMachine.sendEvent(OrderEvent.PlaceOrder(listOf("item1", "item2"))) println("下一个状态: $nextState") ``` ## 完整示例 ✅ 以下是一个复杂工作流的完整示例: ```kotlin import ai.kastrax.core.workflow.builder.workflow import ai.kastrax.core.workflow.engine.WorkflowEngine import ai.kastrax.core.agent.agent import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking fun main() = runBlocking { // 创建代理 val researchAgent = agent { name("研究代理") description("一个研究主题的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } val writingAgent = agent { name("写作代理") description("一个基于研究写内容的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } val editingAgent = agent { name("编辑代理") description("一个编辑和改进内容的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } val factCheckingAgent = agent { name("事实核查代理") description("一个验证内容中事实的代理") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // 创建内容创建工作流 val contentWorkflow = workflow("content-creation", "创建经过研究和编辑的内容") { // 研究步骤 step(researchAgent) { id = "research" name = "研究" description = "彻底研究主题" variables = mutableMapOf( "topic" to variable("$.input.topic"), "depth" to variable("$.input.researchDepth") ) } // 写作步骤 step(writingAgent) { id = "writing" name = "写作" description = "基于研究写内容" after("research") variables = mutableMapOf( "research" to variable("$.steps.research.output.text"), "style" to variable("$.input.contentStyle"), "length" to variable("$.input.contentLength") ) } // 并行事实核查和编辑 step(factCheckingAgent) { id = "fact-checking" name = "事实核查" description = "验证内容中的事实" after("writing") variables = mutableMapOf( "content" to variable("$.steps.writing.output.text"), "research" to variable("$.steps.research.output.text") ) } step(editingAgent) { id = "editing" name = "编辑" description = "编辑和改进内容" after("writing") variables = mutableMapOf( "content" to variable("$.steps.writing.output.text"), "style" to variable("$.input.contentStyle") ) } // 最终修订步骤 step(editingAgent) { id = "final-revision" name = "最终修订" description = "创建包含编辑和事实核查的最终版本" after("fact-checking", "editing") variables = mutableMapOf( "original" to variable("$.steps.writing.output.text"), "edits" to variable("$.steps.editing.output.text"), "factChecks" to variable("$.steps.fact-checking.output.text") ) } // 条件质量检查步骤 step(editingAgent) { id = "quality-check" name = "质量检查" description = "如果请求,执行质量检查" after("final-revision") condition { context -> val performQualityCheck = context.resolveReference(variable("$.input.performQualityCheck")) (performQualityCheck as? Boolean) ?: false } variables = mutableMapOf( "content" to variable("$.steps.final-revision.output.text") ) } // 定义输出映射 output { "research" from "$.steps.research.output.text" "draft" from "$.steps.writing.output.text" "factChecks" from "$.steps.fact-checking.output.text" "edits" from "$.steps.editing.output.text" "finalContent" from "$.steps.final-revision.output.text" "qualityScore" from { "$.steps.quality-check.output.score" to { score -> score ?: "未执行质量检查" } } "metadata" from { "$.steps.writing.output.wordCount" to { wordCount -> "$.steps.final-revision.output.wordCount" to { finalWordCount -> mapOf( "initialWordCount" to wordCount, "finalWordCount" to finalWordCount, "changePercentage" to calculatePercentage(wordCount, finalWordCount) ) } } } } } // 创建工作流引擎 val workflowEngine = WorkflowEngine() workflowEngine.registerWorkflow("content-creation", contentWorkflow) // 执行工作流 val result = workflowEngine.executeWorkflow( workflowId = "content-creation", input = mapOf( "topic" to "人工智能伦理", "researchDepth" to "全面", "contentStyle" to "学术", "contentLength" to "2000 字", "performQualityCheck" to true ) ) // 打印结果 println("工作流完成,状态: ${if (result.success) "成功" else "失败"}") println("最终内容: ${result.output["finalContent"]}") println("质量分数: ${result.output["qualityScore"]}") println("元数据: ${result.output["metadata"]}") } // 计算百分比变化的辅助函数 fun calculatePercentage(initial: Any?, final: Any?): Double { val initialValue = (initial as? Number)?.toDouble() ?: return 0.0 val finalValue = (final as? Number)?.toDouble() ?: return 0.0 if (initialValue == 0.0) return 0.0 return ((finalValue - initialValue) / initialValue) * 100.0 } ``` ## 最佳实践 ✅ 1. **步骤粒度**:设计适当粒度的步骤 - 不要太小以至于创建开销,不要太大以至于失去灵活性 2. **错误处理**:为不可靠操作实现具有重试功能的强大错误处理 3. **数据流**:使用变量引用明确步骤之间的数据流 4. **条件逻辑**:使用条件创建能够适应中间结果的动态工作流 5. **监控**:为工作流执行设置适当的监控和日志记录 6. **状态管理**:根据您的可靠性要求选择适当的状态存储 7. **测试**:使用不同的输入和边缘情况测试工作流 8. **组合**:从更简单、可重用的工作流组合复杂工作流 ## 下一步 ✅ 现在您已经了解了工作流系统,您可以: 1. 了解[工作流定义](./workflow-definition.mdx) 2. 探索[工作流执行](./workflow-execution.mdx) 3. 实现[代理与工作流的集成](./agent-integration.mdx) 4. 设置[工作流状态管理](./state-management.mdx) --- title: "创建步骤并添加到工作流 | Kastrax 文档" description: "Kastrax 工作流中的步骤通过定义输入、输出和执行逻辑,提供了一种结构化的方式来管理操作。" --- # 在工作流中定义步骤 ✅ [ZH] Source: https://kastrax.ai/zh/docs/workflows/steps 当您构建工作流时,通常会将操作分解为可以链接和重用的较小任务。步骤通过定义输入、输出和执行逻辑,提供了一种结构化的方式来管理这些任务。 下面的代码展示了如何内联或单独定义这些步骤。 ## 内联步骤创建 ✅ 您可以使用 `.step()` 和 `.then()` 直接在工作流中创建步骤。此代码展示了如何按顺序定义、链接和执行两个步骤。 ```typescript showLineNumbers filename="src/kastrax/workflows/index.ts" copy import { Step, Workflow, Kastrax } from "@kastrax/core"; import { z } from "zod"; export const myWorkflow = new Workflow({ name: "my-workflow", triggerSchema: z.object({ inputValue: z.number(), }), }); myWorkflow .step( new Step({ id: "stepOne", outputSchema: z.object({ doubledValue: z.number(), }), execute: async ({ context }) => ({ doubledValue: context.triggerData.inputValue * 2, }), }), ) .then( new Step({ id: "stepTwo", outputSchema: z.object({ incrementedValue: z.number(), }), execute: async ({ context }) => { if (context.steps.stepOne.status !== "success") { return { incrementedValue: 0 }; } return { incrementedValue: context.steps.stepOne.output.doubledValue + 1 }; }, }), ).commit(); // 向 Kastrax 注册工作流 export const kastrax = new Kastrax({ workflows: { myWorkflow }, }); ``` ## 单独创建步骤 ✅ 如果您更喜欢在单独的实体中管理步骤逻辑,可以在外部定义步骤,然后将它们添加到工作流中。此代码展示了如何独立定义步骤,然后将它们链接起来。 ```typescript showLineNumbers filename="src/kastrax/workflows/index.ts" copy import { Step, Workflow, Kastrax } from "@kastrax/core"; import { z } from "zod"; // 单独定义步骤 const stepOne = new Step({ id: "stepOne", outputSchema: z.object({ doubledValue: z.number(), }), execute: async ({ context }) => ({ doubledValue: context.triggerData.inputValue * 2, }), }); const stepTwo = new Step({ id: "stepTwo", outputSchema: z.object({ incrementedValue: z.number(), }), execute: async ({ context }) => { if (context.steps.stepOne.status !== "success") { return { incrementedValue: 0 }; } return { incrementedValue: context.steps.stepOne.output.doubledValue + 1 }; }, }); // 构建工作流 const myWorkflow = new Workflow({ name: "my-workflow", triggerSchema: z.object({ inputValue: z.number(), }), }); myWorkflow.step(stepOne).then(stepTwo); myWorkflow.commit(); // 向 Kastrax 注册工作流 export const kastrax = new Kastrax({ workflows: { myWorkflow }, }); ``` --- title: "使用工作流变量进行数据映射 | Kastrax 文档" description: "学习如何使用工作流变量在步骤之间映射数据,并在 Kastrax 工作流中创建动态数据流。" --- # 使用工作流变量进行数据映射 ✅ [ZH] Source: https://kastrax.ai/zh/docs/workflows/variables Kastrax 中的工作流变量提供了一种强大的机制,用于在步骤之间映射数据,使您能够创建动态数据流并将信息从一个步骤传递到另一个步骤。 ## 理解工作流变量 ✅ 在 Kastrax 工作流中,变量用于: - 将触发器输入映射到步骤输入 - 将一个步骤的输出传递到另一个步骤的输入 - 访问步骤输出中的嵌套属性 - 创建更灵活和可重用的工作流步骤 ## 使用变量进行数据映射 ✅ ### 基本变量映射 您可以使用向工作流添加步骤时的 `variables` 属性在步骤之间映射数据: ```typescript showLineNumbers filename="src/kastrax/workflows/index.ts" copy const workflow = new Workflow({ name: 'data-mapping-workflow', triggerSchema: z.object({ inputData: z.string(), }), }); workflow .step(step1, { variables: { // 将触发器数据映射到步骤输入 inputData: { step: 'trigger', path: 'inputData' } } }) .then(step2, { variables: { // 将 step1 的输出映射到 step2 的输入 previousValue: { step: step1, path: 'outputField' } } }) .commit(); // 向 Kastrax 注册工作流 export const kastrax = new Kastrax({ workflows: { workflow }, }); ``` ### 访问嵌套属性 您可以使用 `path` 字段中的点表示法访问嵌套属性: ```typescript showLineNumbers filename="src/kastrax/workflows/index.ts" copy workflow .step(step1) .then(step2, { variables: { // 访问 step1 输出中的嵌套属性 nestedValue: { step: step1, path: 'nested.deeply.value' } } }) .commit(); ``` ### 映射整个对象 您可以使用 `.` 作为路径来映射整个对象: ```typescript showLineNumbers filename="src/kastrax/workflows/index.ts" copy workflow .step(step1, { variables: { // 映射整个触发器数据对象 triggerData: { step: 'trigger', path: '.' } } }) .commit(); ``` ### 循环中的变量 变量也可以传递给 `while` 和 `until` 循环。这对于在迭代之间或从外部步骤传递数据很有用: ```typescript showLineNumbers filename="src/kastrax/workflows/loop-variables.ts" copy // 递增计数器的步骤 const incrementStep = new Step({ id: 'increment', inputSchema: z.object({ // 上一次迭代的值 prevValue: z.number().optional(), }), outputSchema: z.object({ // 更新后的计数器值 updatedCounter: z.number(), }), execute: async ({ context }) => { const { prevValue = 0 } = context.inputData; return { updatedCounter: prevValue + 1 }; }, }); const workflow = new Workflow({ name: 'counter' }); workflow .step(incrementStep) .while( async ({ context }) => { // 当计数器小于 10 时继续 const result = context.getStepResult(incrementStep); return (result?.updatedCounter ?? 0) < 10; }, incrementStep, { // 将前一个值传递给下一次迭代 prevValue: { step: incrementStep, path: 'updatedCounter' } } ); ``` ## 变量解析 ✅ 当工作流执行时,Kastrax 通过以下方式在运行时解析变量: 1. 识别 `step` 属性中指定的源步骤 2. 检索该步骤的输出 3. 使用 `path` 导航到指定的属性 4. 将解析后的值作为 `inputData` 属性注入到目标步骤的上下文中 ## 示例 ✅ ### 从触发器数据映射 此示例展示了如何将数据从工作流触发器映射到步骤: ```typescript showLineNumbers filename="src/kastrax/workflows/trigger-mapping.ts" copy import { Step, Workflow, Kastrax } from "@kastrax/core"; import { z } from "zod"; // 定义需要用户输入的步骤 const processUserInput = new Step({ id: "processUserInput", execute: async ({ context }) => { // 由于变量映射,inputData 将在上下文中可用 const { inputData } = context.inputData; return { processedData: `已处理: ${inputData}` }; }, }); // 创建工作流 const workflow = new Workflow({ name: "trigger-mapping", triggerSchema: z.object({ inputData: z.string(), }), }); // 将触发器数据映射到步骤 workflow .step(processUserInput, { variables: { inputData: { step: 'trigger', path: 'inputData' }, } }) .commit(); // 向 Kastrax 注册工作流 export const kastrax = new Kastrax({ workflows: { workflow }, }); ``` ### 在步骤之间映射 此示例演示了如何在一个步骤和另一个步骤之间映射数据: ```typescript showLineNumbers filename="src/kastrax/workflows/step-mapping.ts" copy import { Step, Workflow, Kastrax } from "@kastrax/core"; import { z } from "zod"; // 步骤 1:生成数据 const generateData = new Step({ id: "generateData", outputSchema: z.object({ nested: z.object({ value: z.string(), }), }), execute: async () => { return { nested: { value: "step1-data" } }; }, }); // 步骤 2:处理来自步骤 1 的数据 const processData = new Step({ id: "processData", inputSchema: z.object({ previousValue: z.string(), }), execute: async ({ context }) => { // 由于变量映射,previousValue 将可用 const { previousValue } = context.inputData; return { result: `已处理: ${previousValue}` }; }, }); // 创建工作流 const workflow = new Workflow({ name: "step-mapping", }); // 将数据从 step1 映射到 step2 workflow .step(generateData) .then(processData, { variables: { // 映射 generateData 输出中的 nested.value 属性 previousValue: { step: generateData, path: 'nested.value' }, } }) .commit(); // 向 Kastrax 注册工作流 export const kastrax = new Kastrax({ workflows: { workflow }, }); ``` ## 类型安全 ✅ 使用 TypeScript 时,Kastrax 为变量映射提供类型安全: ```typescript showLineNumbers filename="src/kastrax/workflows/type-safe.ts" copy import { Step, Workflow, Kastrax } from "@kastrax/core"; import { z } from "zod"; // 定义模式以获得更好的类型安全 const triggerSchema = z.object({ inputValue: z.string(), }); type TriggerType = z.infer; // 带有类型化上下文的步骤 const step1 = new Step({ id: "step1", outputSchema: z.object({ nested: z.object({ value: z.string(), }), }), execute: async ({ context }) => { // TypeScript 知道 triggerData 的形状 const triggerData = context.getStepResult('trigger'); return { nested: { value: `processed-${triggerData?.inputValue}` } }; }, }); // 使用模式创建工作流 const workflow = new Workflow({ name: "type-safe-workflow", triggerSchema, }); workflow.step(step1).commit(); // 向 Kastrax 注册工作流 export const kastrax = new Kastrax({ workflows: { workflow }, }); ``` ## 最佳实践 ✅ 1. **验证输入和输出**:使用 `inputSchema` 和 `outputSchema` 确保数据一致性。 2. **保持映射简单**:尽可能避免过于复杂的嵌套路径。 3. **考虑默认值**:处理映射数据可能未定义的情况。 ## 与直接上下文访问的比较 ✅ 虽然您可以通过 `context.steps` 直接访问先前的步骤结果,但使用变量映射提供了几个优势: | 特性 | 变量映射 | 直接上下文访问 | | ------- | --------------- | --------------------- | | 清晰度 | 显式数据依赖 | 隐式依赖 | | 可重用性 | 步骤可以使用不同的映射重用 | 步骤紧密耦合 | | 类型安全 | 更好的 TypeScript 集成 | 需要手动类型断言 | --- title: "在工作流中使用代理和工具 | 工作流 (vNext) | Kastrax 文档" description: "Kastrax 工作流 (vNext) 中的步骤通过定义输入、输出和执行逻辑,提供了一种结构化的方式来管理操作。" --- ## 将代理作为步骤 ✅ [ZH] Source: https://kastrax.ai/zh/docs/workflows-vnext/using-with-agents-and-tools vNext 工作流可以使用 `createStep(agent)` 直接将 Kastrax 代理作为步骤使用: ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.workflow.vnext.* import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel // 在其他地方定义的代理 val myAgent = agent { name("myAgent") description("...") model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } } // 创建带有代理的 Kastrax 实例 val kastrax = Kastrax { agents { register("myAgent", myAgent) } vnextWorkflows { register("myWorkflow", myWorkflow) } } // 在工作流中使用代理 myWorkflow .then(preparationStep) .map { prompt = it.getOutput(preparationStep, "formattedPrompt") } .then(createStep(myAgent)) // 直接将代理作为步骤使用 .then(processResultStep) .commit() ``` ## 将工具作为步骤 ✅ vNext 工作流可以使用 `createStep(tool)` 直接将 Kastrax 工具作为步骤使用: ```kotlin import ai.kastrax.core.tools.tool import ai.kastrax.core.workflow.vnext.* // 创建工具 val myTool = tool("myTool") { description("我的工具") parameters { // 参数定义... } execute { params -> // 执行逻辑... "成功" } } // 在工作流中使用工具 myWorkflow .then(createStep(myTool)) .then(finalStep) .commit() ``` ## 在代理中将工作流作为工具 ✅ ```kotlin import ai.kastrax.core.agent.agent import ai.kastrax.core.tools.tool import ai.kastrax.core.workflow.vnext.* import ai.kastrax.integrations.deepseek.deepSeek import ai.kastrax.integrations.deepseek.DeepSeekModel import kotlinx.coroutines.runBlocking // 创建天气工作流 val weatherWorkflow = createWorkflow("weather-workflow") { inputSchema { field("city", "要获取天气的城市", String::class) } outputSchema { field("activities", "推荐的活动", String::class) } } .then(fetchWeatherStep) .then(planActivitiesStep) // 创建活动规划工具 val activityPlannerTool = tool("getWeatherSpecificActivities") { description("获取城市的天气特定活动") parameters { parameter("city", "城市名称", String::class) } execute { params -> val city = params["city"] as String val kastrax = getKastraxInstance() // 获取工作流 val plannerWorkflow = kastrax.getWorkflow("my-workflow") if (plannerWorkflow == null) { return "错误:找不到规划工作流" } // 创建工作流运行 val run = plannerWorkflow.createRun() val results = runBlocking { run.start(mapOf( "city" to city )) } // 获取活动规划步骤的结果 val planActivitiesStep = results.getStepResult("plan-activities") if (planActivitiesStep.isSuccess()) { return planActivitiesStep.getOutput("activities") } "未找到活动" } } // 创建活动规划代理 val activityPlannerAgent = agent { name("activityPlannerAgent") description(""" 你是一个活动规划师。你可以使用一个工具来获取任何城市的天气特定活动。 该工具使用代理来规划活动,你只需提供城市名称。 无论你获得什么信息,都按原样返回,并在上面添加你自己的想法。 """.trimIndent()) model = deepSeek { apiKey("your-deepseek-api-key") model(DeepSeekModel.DEEPSEEK_CHAT) temperature(0.7) } tools { tool(activityPlannerTool) } } // 创建 Kastrax 实例 val kastrax = Kastrax { vnextWorkflows { register("my-workflow", myWorkflow) } agents { register("activityPlannerAgent", activityPlannerAgent) } } ``` ## 工作流与代理和工具集成的最佳实践 ✅ ### 设计考虑 1. **明确职责分工**: - 使用工作流来协调复杂的多步骤流程 - 使用代理来处理需要自然语言理解的任务 - 使用工具来执行特定的功能操作 2. **数据流设计**: - 确保工作流步骤之间的数据流清晰明确 - 使用映射函数转换数据以匹配下一步骤的预期输入 - 考虑使用中间步骤来处理和转换数据 3. **错误处理**: - 在工作流中实现适当的错误处理机制 - 考虑使用条件步骤来处理不同的结果路径 - 提供有意义的错误消息和回退策略 ### 性能优化 1. **并行执行**: - 使用并行步骤执行独立的操作 - 避免不必要的序列依赖 2. **资源管理**: - 合理配置超时和重试策略 - 考虑长时间运行操作的中止机制 3. **缓存策略**: - 对于频繁使用的数据,考虑实现缓存 - 避免重复调用相同的外部服务 ### 测试和调试 1. **单元测试**: - 单独测试每个工作流步骤 - 使用模拟对象模拟代理和工具的行为 2. **集成测试**: - 测试完整的工作流执行 - 验证代理和工具之间的交互 3. **日志和监控**: - 实现详细的日志记录 - 监控工作流执行性能和错误率 ## 总结 ✅ 通过将代理和工具集成到工作流中,您可以创建强大的自动化解决方案,结合了结构化流程的可靠性和 AI 代理的灵活性。这种集成使您能够: - 使用代理处理需要自然语言理解的复杂任务 - 使用工具执行特定的功能操作 - 使用工作流协调整个过程并管理数据流 通过遵循本指南中的最佳实践,您可以创建高效、可靠且可维护的集成解决方案。