BoxLang AI 3.1 is here, and it’s a release that makes your agents smarter, faster, and more capable than ever. 🎉
While 3.0 rewrote the rules on multi-agent orchestration and skills, 3.1 fills in the gaps your production applications have been waiting for — full audio support, non-blocking async execution, concurrent multi-model pipelines, a secure filesystem tool suite, and the ElevenLabs voice provider. Plus 21 bug fixes that make the entire stack more reliable under production load.
Here’s everything landing in 3.1:
- 🎤 Audio Models —
aiSpeak(),aiTranscribe(), andaiTranslate()for text-to-speech, speech-to-text, and audio translation - 🤖 ElevenLabs Provider — high-quality multilingual neural voice synthesis
- ⚡ Async Runnables —
runAsync()on everyIAiRunnablereturning aBoxFuture - 🔀
aiParallel()BIF — fan-out a single input to multiple models concurrently, collect named results - 🗂️ FileSystem Agent Tools — 19 opt-in, path-guarded tools for full filesystem operations
- 🎤 Audio Agent Tools —
speak@bxai,transcribe@bxai,translate@bxaiauto-registered in the Global Tool Registry - 🧠 New Memory Events —
onHybridMemoryAddandonVectorSearchinterception points - 🔊 Audio Module Configuration — centralized defaults for all audio operations
- 📡 6 New Audio Events — full before/after interception for speech, transcription, and translation
Let’s dig in. 🎉
🎤 Audio BIFs — Your Agents Can Now Speak and Listen
3.1 brings full audio support to BoxLang AI through three new global Built-in Functions that follow the same familiar API you already know:
| BIF | Description |
|---|---|
aiSpeak( text, params, options ) | Convert text to natural speech audio |
aiTranscribe( audio, params, options ) | Transcribe audio file/URL/binary to text |
aiTranslate( audio, params, options ) | Translate any-language audio to English text |
// Text-to-speech — save to file
audio = aiSpeak( "Welcome to BoxLang AI!" )
.saveToFile( "/audio/welcome.mp3" )
// Speech-to-text — get plain text
text = aiTranscribe( "/recordings/meeting.mp3" )
// Audio translation — any language → English
english = aiTranslate( "/recordings/french-meeting.mp3" )
aiSpeak() returns an AiSpeechResponse with everything you need: saveToFile(), getBase64(), toDataURI(), getMimeType(), getAudioFormat(), and getSize(). aiTranscribe() and aiTranslate() both return an AiTranscriptionResponse with getText(), getWordCount(), getFormattedDuration(), getSegments(), and word-level timestamp data via getWords().
Both response types extend AiBaseResponse — giving you model, provider, metadata, timestamp, and chainable setMetadataValue() out of the box. 📦
🤖 ElevenLabs — Neural Voice Synthesis
The new elevenlabs provider brings high-quality multilingual neural voice synthesis to your BoxLang applications. 🎙️
Default model: eleven_multilingual_v2 — plug in any voice ID from your ElevenLabs voice library via params.
audio = aiSpeak(
"Bonjour, bienvenue dans BoxLang AI.",
{ voice_id: "21m00Tcm4TlvDq8ikWAM" },
{ provider: "elevenlabs" }
)
audio.saveToFile( "/audio/bonjour.mp3" )
Configure it once in config/boxlang.json and forget about it:
{
"modules": {
"bxai": {
"settings": {
"providers": {
"elevenlabs": {
"params": {
"model": "eleven_multilingual_v2"
}
}
}
}
}
}
}
Set ELEVENLABS_API_KEY in your .env — that’s all the wiring you need. ✅
⚡ Async Runnables — Non-Blocking AI Execution
Every IAiRunnable object — AiModel, AiAgent, AiRunnableSequence, AiTransformRunnable, and AiRunnableParallel — now exposes runAsync(). It dispatches to the io-tasks virtual thread pool and returns a BoxFuture you can chain, combine, or block on when you’re ready. 🚀
model = aiModel( "openai" )
// Fire and forget until you need it
future = model.runAsync( "Summarize this document..." )
// Block when you need the result
result = future.get()
// Or go fully reactive with a callback
future.then( result -> {
println( "Done: #result#" )
} )
Where runAsync() really shines is running independent tasks simultaneously:
tasks = [
aiModel( "openai" ).runAsync( "Summarize topic A" ),
aiModel( "openai" ).runAsync( "Summarize topic B" ),
aiModel( "openai" ).runAsync( "Summarize topic C" )
]
// All three in-flight at once — collect when ready
summaries = tasks.map( f => f.get() )
Three LLM calls. One wall-clock wait. No threading code. ⏱️
🔀 aiParallel() — Run Multiple Models Concurrently
The new aiParallel() BIF creates an AiRunnableParallel that fans out a single input to multiple named runnables at the same time and collects results into a named struct. Compare models, aggregate perspectives, and build truly parallel AI pipelines. 🧠
parallel = aiParallel({
openai: aiModel( "openai", { params: { model: "gpt-4o-mini" } } ),
claude: aiModel( "claude", { params: { model: "claude-3-haiku-20240307" } } ),
mistral: aiModel( "mistral", { params: { model: "mistral-small-latest" } } )
})
results = parallel.run( "What is the capital of France?" )
println( results.openai ) // "Paris"
println( results.claude ) // "Paris"
println( results.mistral ) // "The capital of France is Paris."
Because AiRunnableParallel implements IAiRunnable, it composes naturally into larger pipelines:
pipeline = aiParallel({ fast: aiModel( "groq" ), smart: aiModel( "openai" ) })
.transform( results => "Fast: #results.fast#\nSmart: #results.smart#" )
combined = pipeline.run( "Explain quantum entanglement in one sentence" )
println( combined )
Fan out, collect, transform. Zero boilerplate. 🎯
🗂️ FileSystem Agent Tools — Power With a Security-First Design
3.1 ships FileSystemTools — a class with 19 @mcpTool-annotated methods covering the full filesystem lifecycle. Unlike audio tools, this one is not auto-registered by design. You opt in explicitly, and you supply the paths you want to allow. 🔒
// Register with explicit path restrictions
aiToolRegistry().scanClass(
new FileSystemTools( allowedPaths: [ "/app/data", "/app/uploads" ] )
)
// Then assign specific tools to your agent
agent = aiAgent(
name: "file-manager",
aiModel: "openai",
tools: [ "readFile@bxai", "writeFile@bxai", "listDirectory@bxai" ]
)
Every path argument is canonicalized and validated against your allowedPaths list at invocation time — directory-traversal attacks are blocked before any file operation runs. The full 19-tool suite covers read, write, append, edit, move, copy, delete, search, zip, unzip, and more.
🎤 Audio Agent Tools — Voice-Enabled Agents in One Line
Three audio tools are auto-registered in the Global Tool Registry at module startup:
| Tool Key | Description |
|---|---|
speak@bxai | Convert text to speech; auto-generates a temp file if no outputFile is supplied |
transcribe@bxai | Transcribe a local audio file or URL to plain text |
translate@bxai | Translate any-language audio to English text |
Opt-in by listing them in your agent definition:
agent = aiAgent(
name: "voice-assistant",
aiModel: "openai",
tools: [ "speak@bxai", "transcribe@bxai", "translate@bxai" ]
)
Your agents can now receive voice input, reason over it, and speak back — all with the same tool system you already know. 🔊
🧠 New Memory Events
Two new interception points give you visibility into what your memory subsystem is doing at runtime:
| Event | When Fired |
|---|---|
onHybridMemoryAdd | When a message is added to a HybridMemory instance |
onVectorSearch | When a semantic search runs against a vector memory |
BoxRegisterInterceptor( "onVectorSearch", event => {
println( "Vector search: query='#event.query#' found #event.results.len()# results" )
} )
Pair these with the existing event system to build observability pipelines, debug RAG relevance, or feed search telemetry into your monitoring stack. 📊
🔊 Audio Module Configuration
A new audio section in module settings gives you global defaults for all audio operations — no more repeating params across every call:
{
"modules": {
"bxai": {
"settings": {
"audio": {
"defaultVoice": "alloy",
"defaultOutputFormat": "mp3",
"defaultSpeechModel": "tts-1",
"defaultTranscriptionModel": "whisper-1"
}
}
}
}
}
Six new audio interception events — beforeAISpeech, afterAISpeech, beforeAITranscription, afterAITranscription, beforeAITranslation, afterAITranslation — round out the observability story for every audio operation. 📡
🐛 21 Bug Fixes Worth Calling Out
3.1 ships 21 bug fixes across the stack. The biggest ones:
- Streaming
onAITokenCountnever fired — all streaming calls were invisible to usage tracking and billing interceptors. Fixed. 💥 AiModel.stream()missing middleware — streaming was skipping middleware injection. Fixed and now consistent withrun().- Closure scoping in tool calls —
ArgumentsScoperesolution failures inside.each()/.map()closures across multiple providers. Fixed by capturing outer variables before closures. onAITokenCountstandardization — event data shape was inconsistent across providers; standardized across Bedrock, Claude, Cohere, and Gemini.MCPServer.scan()/scanClass()— path-resolution edge cases across dot-notation, absolute, relative, and instance inputs. Now reliable.aiAgent()single skill normalization —skillsandavailableSkillsnow accept a single instance or an array without wrapping in[].WindowMemory.count()off-by-one — returnedmaxMessages + 1instead of actual count. Fixed.- Gemini null chunk on final SSE — streaming responses occasionally emitted a
nullchunk on the final event. Now handled gracefully. - AWS Bedrock signature trailing slash — caused
SignatureDoesNotMatcherrors on certain endpoints. Fixed. HybridMemorydeduplication — combining window and vector search results could produce duplicate messages. Fixed.aiTranscribeURL redirect — HTTP 301/302 from CDNs were not followed. Now follows up to 5 redirects.
See the full changelog for the complete list.
No Breaking Changes
3.0 code runs on 3.1 without modification. Upgrade, run your tests, and start exploring. ✅
Get Started
# Install or upgrade via BoxLang
install-bx-module bx-ai
# Install or upgrade via CommandBox
install bx-ai
Professional Support
Remember that BoxLang and BoxLang AI are professional support software. We are there for you when you need it the most: https://www.boxlang.io/plans
📖 Full Documentation
🛠️ Changelog
🐛 Report Issues
💬 Community Slack
💼 BoxLang+ Plans