Ema: Picking the Right Tool
Should you use entity_extraction or conversation_summarizer to get the caller's name? When does chat_categorizer beat call_llm? This decision guide helps you pick the right Ema workflow tool for every scenario.
The Core Building Blocks
Ema workflows provide several specialized tools. Each is optimized for a specific purpose:
| Tool | Purpose | Output Type |
|---|---|---|
conversation_summarizer |
Convert messy conversation → clean search query | Text |
entity_extraction |
Pull structured fields FROM text | JSON blob |
chat_categorizer |
Classify and route | Single enum value |
knowledge_search |
Find relevant documents | Search results |
call_llm |
General LLM tasks | Text (can be JSON) |
custom_agent |
Complex tasks with role/instructions | Text + optional JSON fields |
JSON Extractor |
Pull individual values from JSON | Individual variables |
Key insight: Using the wrong tool works... poorly. Using the right tool is effortless.
Decision Tree: What Tool Do I Need?
flowchart TB
A{"What are you trying to do?"}:::primary
A -->|Extract values?| B[entity_extraction]:::accent
A -->|Route by intent?| C[chat_categorizer]:::secondary
A -->|Build search query?| D[conversation_summarizer]:::accent
A -->|Find documents?| E[knowledge_search]:::accent
A -->|Complex analysis?| F[custom_agent]:::agent
A -->|Simple transform?| G[call_llm]:::secondary
A -->|JSON field?| H[JSON Extractor]:::secondary
Scenario 1: "Get the Caller's Name"
Question: Should I use conversation_summarizer, entity_extraction, or call_llm?
❌ conversation_summarizer — WRONG TOOL
Input: "Hi, this is Michael Thompson, I want to check my portfolio"
Output: "Portfolio check request from client"
Problem: Summarizer is for search queries, not extraction.
It might paraphrase away the exact name!
✅ entity_extraction — RIGHT TOOL
Input: Same conversation
Output: {
"caller_name": "Michael Thompson",
"request_type": "portfolio_check"
}
Why: Designed to pull specific fields, preserves exact values.
✅ call_llm with JSON — ALSO WORKS
call_llm:
instructions: "Extract caller_name from conversation. Output JSON only."
Output: { "caller_name": "Michael Thompson" }
Why: Flexible, same result, more control over prompt.
Verdict
| Tool | Use for "get name"? | Why |
|---|---|---|
| conversation_summarizer | ❌ No | Summarizes, may lose exact name |
| entity_extraction | ✅ Yes | Designed for this |
| call_llm + JSON | ✅ Yes | Works, more flexible |
Scenario 2: "Build a Search Query"
Question: I have a messy conversation, need to search my knowledge base. What do I use?
✅ conversation_summarizer — RIGHT TOOL
Input conversation:
User: "Hey so like I was wondering about that tech stock"
Bot: "Which one?"
User: "The NVIDIA one, what's happening with it"
Bot: "Let me check"
User: "Also for Michael's account"
Output: "NVIDIA NVDA stock update Michael client portfolio"
Why: Converts verbose conversation into searchable keywords.
❌ entity_extraction — WRONG TOOL
Output: {
"ticker": "NVDA",
"client_name": "Michael"
}
Problem: Gives you fields, but not a search query string.
You'd need another step to combine them.
Better Pattern: entity_extraction → call_llm query builder
1. entity_extraction → {ticker: "NVDA", client: "Michael"}
2. call_llm → "Build query: portfolio"
3. Output → "NVDA Michael portfolio holdings exposure"
Why: More precise than summarizer, uses extracted entities.
Verdict
| Tool | Use for search query? | Why |
|---|---|---|
| conversation_summarizer | ✅ Quick & easy | Good for simple cases |
| entity_extraction alone | ❌ No | Outputs fields, not query |
| entity_extraction + call_llm | ✅ Best | Precise, entity-aware queries |
Scenario 3: "Route Based on Request Type"
Question: User might be asking for portfolio review, compliance check, or market update. How do I route?
✅ chat_categorizer — RIGHT TOOL
chat_categorizer:
categories:
- PORTFOLIO_REVIEW: "Review my portfolio", "How am I doing"
- COMPLIANCE_CHECK: "Am I compliant", "Check TMD"
- MARKET_UPDATE: "What's happening with NVDA", "Market news"
Output: category = "PORTFOLIO_REVIEW"
Why: Designed for classification and routing.
Works directly with runIf conditions.
❌ entity_extraction — WRONG TOOL
Output: {
"request_type": "portfolio_review"
}
Problem: Can't use extraction_columns directly in runIf!
runIf needs a single value, not JSON blob.
Workaround: entity-aware routing
# Step 1: Extract entities
entity_extraction:
output: extraction_columns
{needs_email: true, has_recipient: false}
# Step 2: Categorizer evaluates extraction
chat_categorizer:
custom_data: entity_extraction.extraction_columns
categories:
- NEEDS_EMAIL: "needs_email is true AND recipient_email is null"
- READY: "has all required data"
Why: Categorizer can evaluate the extraction in custom_data.
Verdict
| Tool | Use for routing? | Why |
|---|---|---|
| chat_categorizer | ✅ Yes | Designed for this, works with runIf |
| entity_extraction | ❌ Alone no | Output can't drive runIf directly |
| entity_extraction + categorizer | ✅ Yes | Categorizer uses extraction for decisions |
Scenario 4: "Wire Extracted Email to Send Email Node"
Question: I extracted recipient_email, how do I connect it to the email node's to field?
❌ entity_extraction alone — CAN'T WIRE DIRECTLY
entity_extraction outputs: extraction_columns (blob)
{
"recipient_email": "m.t@corp.com",
"subject": "...",
...
}
Problem: Can't do extraction_columns.recipient_email in wiring!
The platform treats it as an opaque blob.
✅ JSON Extractor — RIGHT TOOL
entity_extraction → JSON Extractor
Variable: recipient_email
Path: $.recipient_email
Output: "m.t@corp.com" (single string)
Now you can wire: JSON_Extractor.recipient_email → Email.to
✅ call_llm as value extractor — ALSO WORKS
call_llm:
named_inputs:
- Entities: entity_extraction.extraction_columns
instructions: |
Output ONLY the recipient email from Entities.
Just the email, nothing else.
Output: "m.t@corp.com"
Wire: call_llm.response → Email.to
Verdict
| Tool | Use for direct wiring? | Why |
|---|---|---|
| entity_extraction alone | ❌ No | Can't index into blob |
| JSON Extractor | ✅ Yes | Extracts individual values |
| call_llm | ✅ Yes | LLM extracts the value |
Scenario 5: "Complex Analysis with Specific Expertise"
Question: I need a "Senior Portfolio Analyst" to review holdings and make recommendations.
✅ custom_agent — RIGHT TOOL
custom_agent:
role_instructions: |
You are a Senior Portfolio Analyst specializing in...
task_instructions: |
Analyze the portfolio data and produce:
1. Performance summary
2. Risk assessment
3. Recommendations
output_fields:
- summary (string)
- risk_score (number)
- recommendations (array)
Why: Role defines expertise, task defines job, output_fields structure result.
⚠️ call_llm — TOO SIMPLE
call_llm:
instructions: "Analyze this portfolio..."
Problem: No role definition, no structured output.
Works but less organized.
Verdict
| Tool | Use for complex analysis? | Why |
|---|---|---|
| custom_agent | ✅ Yes | Role + task + structured output |
| call_llm | ⚠️ Okay | Works, but less structured |
Quick Reference: Tool Selection
| Need to... | Use |
|---|---|
| Extract specific fields (name, email, type) | entity_extraction |
| Convert conversation to search query | conversation_summarizer |
| Route/branch based on intent | chat_categorizer |
| Find documents | knowledge_search |
| Complex analysis with persona | custom_agent |
| Simple text transformation | call_llm |
| Get individual value from JSON | JSON Extractor |
| Combine routing + entity logic | categorizer with custom_data from extraction |
Common Mistakes
| Mistake | Why It's Wrong | Fix |
|---|---|---|
| Using summarizer to extract names | May paraphrase away exact values | Use entity_extraction |
| Using extraction for routing | Can't use blob in runIf | Use categorizer |
| Expecting extraction to wire to fields | Platform treats as blob | Use JSON Extractor |
| Using call_llm for everything | Less structured, harder to manage | Use appropriate specialized tool |
The Golden Pattern
When in doubt, this architecture handles most conversational AI needs:
flowchart TB
A[trigger.chat_conversation]:::primary --> B["entity_extraction<br/>(structured fields)"]:::accent
A --> C["conversation_summarizer<br/>(search query)"]:::accent
C --> D[knowledge_search]:::accent
B --> E["chat_categorizer<br/>(routing)"]:::secondary
B --> F["custom_agent<br/>(analysis)"]:::agent
D --> F
E --> G[runIf branches]:::secondary
F --> H((WORKFLOW_OUTPUT)):::accent
G --> H
This combines the right tools for each job while sharing data effectively:
- Entity extraction captures structured data
- Summarizer optimizes for search
- Categorizer drives routing decisions
- Agent does the heavy analytical lifting
TL;DR
| If you need... | Use this |
|---|---|
| Exact values from conversation | entity_extraction |
| Search-optimized query | conversation_summarizer |
| Intent-based routing | chat_categorizer |
| Complex reasoning | custom_agent |
| Simple transform | call_llm |
| Individual JSON field | JSON Extractor |
Rule of thumb: Each tool does one thing well. Match the tool to the task.
For more on routing patterns, see Ema Workflows: Routing and Branching.