Guidecoheretiny-ayalanggraph

Building a Multilingual Support Agent with Tiny Aya

We built a multilingual support agent with Cohere Labs' 3.35B Tiny Aya and benchmarked it against models 8-10x its size on technical translation.

14 min readPublished April 7, 2026Updated October 30, 2026

Geoffrey Harrazi

Chief Product Officer · Co-founder, Idun Group

Header: in Idun's test, the small Tiny Aya model translates 2,000 support tickets an hour, versus 137 for a 27B model.

A 3.35B-parameter model and a LangGraph agent walk into a support queue. What follows is a surprisingly competitive multilingual pipeline.

When every ticket is a language problem

Enterprise support does not happen in one language. A French engineer files a bug report with a full stack trace. A Turkish operations team submits a ticket about a failing backup schedule, complete with cron expressions and log excerpts. A Japanese developer describes a memory leak, referencing heap dumps and JVM flags.

Before anyone can triage, classify, or respond to these tickets, someone (or something) needs to understand them. And understanding a multilingual technical support ticket is harder than it sounds. You need to preserve error codes, API endpoints, SQL fragments, and the causal reasoning that makes a bug report actionable. Translate "le serveur renvoie une erreur 504" wrong and you lose the status code. Paraphrase a stack trace and you lose the debugging path entirely.

The standard approach is to route everything through a large model API, typically 30B+ parameters on a cloud endpoint. It works. It is also slow (10 to 30 seconds per request), expensive at scale, and creates a hard dependency on external infrastructure.

We wanted to see if we could build a complete multilingual support agent powered primarily by a 3.35B-parameter model. Language detection, translation, verification, classification, all running fast enough for real-time triage.

Enter Tiny Aya

When Cohere Labs released Tiny Aya in February 2026, it caught our attention. Most multilingual models at this scale treat language coverage as a checkbox: they support many languages, but performance degrades sharply outside English and a few high-resource languages. Tiny Aya does something different.

Rather than spreading thin, Tiny Aya covers 67 languages with instruction-tuned variants that specialize in different linguistic regions:

  • TinyAya-Global: the generalist, balanced across all 67 languages.
  • TinyAya-Earth: Africa and West Asia (Arabic, Turkish, Swahili, Amharic, Hausa, etc.)
  • TinyAya-Fire: South Asia (Hindi, Bengali, Tamil, Urdu, etc.)
  • TinyAya-Water: Asia-Pacific and Europe (French, Japanese, Chinese, Portuguese, Spanish, Korean, etc.)

This structure immediately suggested an architecture. Instead of picking one model and hoping it works everywhere, we could run the global model as a baseline while simultaneously running the specialized variant for the detected language. Two translations, one verification step, best output wins.

Worth noting: Tiny Aya's tokenizer was designed to reduce fragmentation across scripts. Where other multilingual tokenizers produce long token sequences for non-Latin scripts (slower inference, more memory), Tiny Aya gets significantly fewer tokens per sentence. That directly means faster inference and lower memory usage.

The agent architecture

We built the agent as a LangGraph workflow. Each node does one job and passes state forward. Here is the full pipeline:

START ├── fetch_ticket ──────────┐ │ ├── detect_language ──┬── translate_global ──┐ └── fetch_categories ──────┘ └── translate_sub ─────┤ ├── verify_translation ── classify ── update_ticket │ │ │ END

Eight nodes, three parallelization points, two model families.

Fetching context

The pipeline starts with two parallel operations: fetch_ticket retrieves the ticket from Jira, and fetch_categories queries the project for available category labels. Both use MCP tools to interact with Jira.

python
async def fetch_ticket(state: TicketState) -> FetchTicketResult: tools = await load_mcp_tools() tool = get_tool("jira_get_issue", tools) result = await tool.ainvoke({"issue_key": state["ticket_key"]}) # Parse summary + description from the response ...

MCP (Model Context Protocol) is an open standard for connecting agents to external tools. Instead of writing a Jira API client, we declare the tools we need and use them as standard LangChain tools. Agent code stays focused on business logic.

Language detection

detect_language uses TinyAya-Global to identify the source language, then resolves it to the appropriate regional variant:

python
_global_model = create_tiny_aya(TinyAyaVariant.GLOBAL) def detect_language(state: TicketState) -> DetectResult: response = _global_model.invoke( [HumanMessage(content=f"What language is this? Answer in English: {state['ticket_text']}")] ) # Match response against supported languages language = next( (lang for lang in _SUPPORTED_LANGUAGES if lang.lower() in raw.lower()), None, ) variant = get_variant_for_language(language) return DetectResult(detected_language=language, sub_variant=variant)

The routing map:

python
LANGUAGE_TO_VARIANT = { "French": "water", "Arabic": "earth", "Hindi": "fire", "Turkish": "earth", "Japanese": "water", "Chinese": "water", "Portuguese": "water", "Spanish": "water", "Swahili": "earth", "English": "global", }

Dual translation

After language detection, the pipeline forks into two parallel translation paths:

translate_global runs the ticket through TinyAya-Global. Broadest training data, most consistently formatted output across languages.

translate_sub runs the same ticket through the detected regional variant (Earth, Fire, or Water). Deeper linguistic grounding for its target languages, potentially better at capturing nuances.

Both run concurrently with the same system prompt:

python
_SYSTEM_PROMPT = ( "You are a professional translator. " "Translate the following text to English. " "Output ONLY the English translation, nothing else. " "Do not add notes, explanations, or commentary." )

Cohere Labs built the regional variants for "stronger linguistic grounding and cultural nuance" while the global model provides a "consistent multilingual backbone." We wanted to see if combining both would beat either alone.

Verification

verify_translation receives both outputs and uses TinyAya-Global as arbiter. It picks the better translation, with a bias toward the regional variant when they conflict:

python
_USER_PROMPT = ( "Below are two English translations of the same text.\n\n" "Translation A:\n{global_translation}\n\n" "Translation B:\n{sub_translation}\n\n" "Pick the better translation. If they conflict, pick Translation B. " "Write ONLY the final English translation below, no explanation:\n\n" "Final translation:" )

The bias toward Translation B reflects the idea that when two models disagree, the specialist's interpretation is more likely correct. As we will see in the eval section, that turned out to be more nuanced than expected.

Classification and update

Once we have a verified English translation, the pipeline leaves Tiny Aya's domain. classify uses Gemini 3 Flash to assign a category and priority (Critical/High/Medium/Low). We use Gemini here because classification is a reasoning task over English text, not a multilingual problem.

update_ticket writes everything back to Jira: detected language, variant used, verified translation, category, and priority. The original ticket is preserved, with the analysis appended.

The state

The pipeline communicates through a typed state dictionary:

python
class TicketState(TypedDict, total=False): ticket_key: str ticket_text: str detected_language: str global_translation: str sub_translation: str verified_translation: str sub_variant: str category: str priority: str categories: list[str] updated: bool

Each node reads what it needs and writes its results back. LangGraph handles routing, parallelism, and state merging. total=False means nodes only set the fields they are responsible for.

From notebook to production

Building a LangGraph agent that works locally is one thing. Running it as a production service with observability, access control, tool management, and a serving layer is another. Most teams spend more engineering time on this infrastructure than on the agent logic.

MCP tools and access control

Our agent talks to Jira: fetching tickets, looking up categories, writing back results. Without a standard protocol, you write a REST client, handle auth, parse responses, manage connections: a few hundred lines of integration code, minimum. An MCP server (mcp-atlassian, over STDIO transport) gives the agent jira_get_issue, jira_search, and jira_update_issue. Restrict which MCP tools each agent can reach as soon as several agents run with different permission levels.

For a support ticket agent processing customer data, input/output guardrails (PII detection, prompt-injection defenses, topic restrictions, allowlists/blocklists) are not optional, and neither are SSO and RBAC: the internal support team gets full access, external integrations only submit tickets without seeing raw translations.

Observability with Langfuse

Building the agent was the fun part. Figuring out why a ticket took 30 seconds instead of 3, or why a translation came back empty, was the hard part.

We went with Langfuse because it traces actual prompt/completion pairs, token counts, and cost per model call, not just spans and durations. Every node execution, model call, and tool invocation gets traced. You can drill into any span to see exact prompts, raw responses, token counts, and latency.

Some things we caught through traces:

  • First translation call for each variant was much slower than subsequent calls. Turns out the HuggingFace Inference API was loading the model on first request. We started caching model instances.
  • A ticket got a surprisingly low eval score. Tracing back through the spans showed the issue was in verification, not translation. The global model was mangling the sub-variant's output during comparison.
  • The verification step was sometimes the slowest node, not the translation. The model was "deliberating" between two similar translations instead of quickly picking one.

Langfuse also gives aggregate metrics across executions: latency per node, error rates, token usage over time, cost breakdowns by model. When you are processing hundreds of tickets a day, that is how you know things are working.

Evaluating translation quality

Building the agent was the easy part. We needed to know how Tiny Aya actually performs on technical content compared to models many times its size, so we built a systematic evaluation.

The dataset

40 technical support tickets across 10 languages: French, Spanish, Portuguese, Arabic, Turkish, Hindi, Japanese, Chinese, Swahili, and English (4 per language). Each ticket includes the kind of technical detail you would find in a real support queue:

  • Stack traces and error logs: Python tracebacks, Java exceptions, Node.js errors with file paths and line numbers that must be preserved exactly.
  • SQL queries and database details: SELECT with JOIN clauses, index names, VACUUM FULL, specific table/column identifiers.
  • API endpoints and configuration: REST paths like /api/v2/batch/process, env vars, config keys, HTTP status codes.
  • Numbers that must survive: error codes, port numbers, memory sizes (3GB, 12GB), thresholds (100 requests), durations (24-hour window).
  • Causal reasoning: "When X happens, Y fails because Z." Break this chain and the ticket becomes useless.

The tickets were written in each language by native-level prompts, not machine-translated from English. Machine-translated test data would test the model's ability to reverse its own output, which is not what we wanted.

The judge

We used Gemini 3 Flash as an LLM judge through deepeval's GEval metric. Each translation scored on a 0 to 10 rubric (normalized to 0 to 1) with six criteria:

  1. Numbers: are all numbers preserved exactly?
  2. Technical identifiers: API endpoints, file paths, config keys, SQL snippets preserved verbatim?
  3. Completeness: any sentences or details missing?
  4. Factual accuracy: does the translation introduce incorrect information?
  5. Fluency: would a native English support engineer find it confusing?
  6. Coherence: logical structure maintained (numbered steps, cause-effect)?

We paired these with explicit score anchors using deepeval's Rubric class:

  • 0 to 2: Unintelligible, wrong language, or missing most content
  • 3 to 4: General topic captured but major errors
  • 5 to 6: Understandable and mostly complete, but several errors
  • 7 to 8: Accurate and fluent with only minor issues
  • 9: Near-perfect
  • 10: Flawless

This mattered. In early iterations, we only used evaluation steps with penalty-based scoring and every model scored nearly perfectly. The rubric gave the judge calibrated expectations for what each score level actually looks like.

The competitors

We evaluated Tiny Aya against models from 4B to 32B parameters, all through the Hugging Face Inference API:

ModelParameters
TinyAya-Global3.35B
TinyAya-Routed3.35B (regional variants)
Gemma3n-E4B~4B
Qwen3-4B4B
Qwen3-8B8B
Qwen3-32B32B
Qwen3.5-27B27B

"TinyAya-Routed" is our routing pipeline that sends each ticket to the appropriate regional variant based on detected language. This lets us compare global-only against specialized routing directly.

Each model translated all 36 non-English tickets. We parallelized with pytest-xdist across models and ThreadPoolExecutor within each model's ticket set (3 threads, staying within HuggingFace rate limits).

The results

ModelParamsAvg ScoreAvg Latency
Qwen3.5-27B27B0.98126.2s
Gemma3n-E4B~4B0.9724.5s
Qwen3-32B32B0.9532.1s
Qwen3-8B8B0.9504.3s
Qwen3-4B4B0.9442.6s
TinyAya-Global3.35B0.9221.7s
TinyAya-Routed3.35B0.8582.9s

TinyAya-Global scores 0.922, within 3 to 6% of models up to 10x its size, at the fastest inference speed in the comparison. At 1.7 seconds per ticket, that is roughly 2,000 tickets per hour. Qwen3.5-27B scores 0.981 but at 26.2 seconds per ticket, so about 137 per hour.

The efficiency gap is even wider:

ModelScore/SecondScore/Param
TinyAya-Global0.5460.275
Qwen3-32B0.4550.030
Qwen3-4B0.3620.236
Qwen3-8B0.2220.119
Gemma3n-E4B0.2140.243
Qwen3.5-27B0.0370.036

Highest score-per-second and score-per-parameter of any model tested. Nothing in the comparison is both faster and better.

Performance by language

LanguageTinyAya-GlobalQwen3.5-27BGemma3n-E4B
Arabic0.971.000.97
French0.951.000.97
Spanish0.970.971.00
Hindi0.950.971.00
Portuguese0.950.950.93
Turkish0.901.000.95
Japanese0.900.950.97
Swahili0.880.971.00
Chinese0.821.000.95

Strongest on Arabic (0.97), Spanish (0.97), French (0.95), Hindi (0.95), and Portuguese (0.95), all within 1 to 5% of the best model. The gap widens for Turkish (0.90), Japanese (0.90), Swahili (0.88), and Chinese (0.82). Chinese is the clearest weak point, 0.18 behind the best.

The consistency is the interesting part. Score distribution is tightly clustered. No catastrophic failures, no degenerate outputs. Every ticket produced a usable translation. For a production system, that consistency matters more than occasional peak scores.

Global vs routed

One of the more surprising findings. We expected routing to the regional variant would outperform the global model on its home languages. It mostly did not.

LanguageVariantGlobalRoutedDiff
Chinesewater0.820.90+0.08
Hindifire0.950.950.00
Swahiliearth0.880.880.00
Arabicearth0.970.95-0.03
Frenchwater0.950.93-0.02
Spanishwater0.970.90-0.07
Turkishearth0.900.78-0.12
Portuguesewater0.950.75-0.20
Japanesewater0.900.70-0.20

Global scored 0.922 versus 0.858 for routed. The global model was more consistent, the routed approach more variable.

This comes down to task fit. The regional variants are designed for deeper linguistic grounding and cultural nuance, which matters for open-ended generation and culturally sensitive content. Our task is structured technical translation with strict output formatting. The global model's broader instruction-following training gave it an edge here.

The exception was Chinese, where routing to Water improved the score from 0.82 to 0.90. That suggests a middle ground: use global as default, route only the languages where it demonstrably underperforms. That is exactly what our dual-translation architecture enables.

Both configurations use a 3.35B model. The fact that we are debating routing strategies between variants of a model this small, with scores in the 0.85 to 0.92 range, says something about where small multilingual models are at right now.

What we learned

Small models can compete on real tasks. TinyAya-Global at 3.35B is within 3 to 6% of models 8 to 10x its size on technical translation. The gap is real but narrow. And the 15x speed advantage over larger models changes what is possible: 2,000 tickets/hour versus 137.

Consistency beats peak performance. TinyAya-Global never catastrophically failed across 36 translations. Every output was usable. The routed approach occasionally scored higher but also dropped as low as 0.10. In production, the system that consistently scores 0.92 is more useful than the one that alternates between 0.95 and 0.50.

Eval design is harder than it looks. Our first run gave every model near-perfect scores (rubric was too lenient). Our second run penalized harmless model preamble as "hallucination," scoring good translations at 0.05. It took several iterations to get scores that actually differentiated model quality. Spot-checking would never have revealed the routing underperformance, the Chinese exception, or the failure modes of each variant.

Infrastructure is undifferentiated work. Time should go to agent logic and eval design, not to building a serving layer or debugging Jira auth. An MCP server replaces hundreds of lines of API client code.

Model families are a design tool. Tiny Aya's structure (global backbone plus regional specialists) directly shaped our architecture. The dual translation step exists because the model family makes it possible. Routing underperformed global in our eval, but the architecture is sound. With different tasks (open-ended generation, culturally sensitive content), the regional variants' deeper grounding could make the difference.

Try it yourself

The code, eval suite, and results are all open source:

If you run this eval on your own data and get different results, we would be curious to hear about it.

Was this article helpful?

About the author

Geoffrey Harrazi« geoffrey »

Chief Product Officer · Co-founder, Idun Group

Chief Product Officer and co-founder of Idun Group. Product, business and sales. Ex-LVMH. Leads Idun Cockpit, which measures the real usage of the AI companies have already deployed: licences, assistants and agents.

LinkedInSite20 articles published

Get our guides on the value of AI every month

Field notes on real AI usage in companies: adoption, costs, what it returns and what to do about it. No spam, ever.

1 email per month. No more.

Unsubscribe at any time from the link in every email. How we handle your data: privacy policy