Appearance
Data Collection ​
Learn how to configure AI agents to automatically extract structured data (contacts, leads, appointments, etc.) from conversations.
Overview ​
The Entity Extraction System automatically extracts structured data from AI conversations. When users interact with agents, the system identifies and captures relevant entities based on entity types configured on each agent.
Use Cases:
- Collect contact information from chat conversations
- Extract lead data for CRM integration
- Capture appointment details automatically
- Build structured datasets from unstructured conversations
Key Features:
- Automatic extraction after every conversation
- Configurable schemas via entity types
- Confidence scoring to filter low-quality extractions
- Field-level merge into one record per kind: a later conversation turn enriches or corrects an earlier one, silence never clears a value, and a value you edited by hand is never overwritten by extraction
- Manual review with status workflow (NEW → REVIEWED → ACTIONED → ARCHIVED)
Quick Start ​
1. Define an Entity Type ​
Entity types are reusable templates that define what data to extract. Before building one from scratch, check the shared starter catalog — every account can read these system-owned templates (is_template: true) and adapt one instead:
bash
# List your own entity types plus the shared starters
curl "http://localhost:8000/api/v1/entity-types?include_templates=true" \
-H "Authorization: Bearer $TOKEN"
# List only the starter catalog (Sales Lead, Support Ticket, Booking Request,
# Feedback, Patient Intake, Job Application, Event RSVP)
curl "http://localhost:8000/api/v1/entity-types/templates" \
-H "Authorization: Bearer $TOKEN"Adapting a starter (the UI's "Use Template" action) always creates a new, independent entity type owned by your account — the starter itself is never modified. To build a custom one from scratch:
bash
curl -X POST "http://localhost:8000/api/v1/entity-types" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Contact",
"description": "Person contact information",
"icon": "user",
"attributes": [
{
"name": "full_name",
"type": "string",
"required": true,
"ai_hint": "The complete name of the person"
},
{
"name": "email",
"type": "email",
"required": true,
"ai_hint": "Email address"
},
{
"name": "company",
"type": "string",
"required": false,
"ai_hint": "Company or organization name"
},
{
"name": "phone",
"type": "string",
"required": false,
"ai_hint": "Phone number in any format"
},
{
"name": "interests",
"type": "array",
"items_type": "string",
"required": false,
"ai_hint": "List of interests or topics mentioned"
}
]
}'2. Assign Entity Type to Agent ​
bash
curl -X POST "http://localhost:8000/api/v1/agents/{agent_id}/entity-types/{entity_type_id}" \
-H "Authorization: Bearer $TOKEN"3. Extraction Happens Automatically ​
When a conversation session ends (after inactivity timeout), the system:
- Generates a session summary
- Extracts entities based on configured entity types
- Creates
ExtractedEntityrecords with confidence scores
4. Review Extracted Entities ​
bash
# List all extracted entities
curl "http://localhost:8000/api/v1/extracted-entities" \
-H "Authorization: Bearer $TOKEN"
# Filter by agent
curl "http://localhost:8000/api/v1/extracted-entities?agent_id=1" \
-H "Authorization: Bearer $TOKEN"
# Filter by status
curl "http://localhost:8000/api/v1/extracted-entities?status=NEW" \
-H "Authorization: Bearer $TOKEN"Entity Type Attributes ​
Supported Attribute Types ​
| Type | JSON Schema | Description |
|---|---|---|
string | {"type": "string"} | Text field |
number | {"type": "number"} | Decimal number |
integer | {"type": "integer"} | Whole number |
boolean | {"type": "boolean"} | True/false |
array | {"type": "array"} | List of items |
object | {"type": "object"} | Nested object |
date | {"type": "string", "format": "date"} | Date (YYYY-MM-DD) |
datetime | {"type": "string", "format": "date-time"} | ISO 8601 datetime |
email | {"type": "string", "format": "email"} | Email address |
url | {"type": "string", "format": "uri"} | URL |
Attribute Definition Format ​
json
{
"name": "field_name",
"type": "string",
"required": true,
"ai_hint": "Clear description for the LLM"
}Fields:
name(required): Field identifier (use snake_case)type(required): Data type from supported typesrequired(required): Marks the field as essential for extraction. The model prioritizes capturing it, and a required text field namednameor*_namecan also be used to resolve the record's display name. It is NOT a generation constraint: a field the visitor never stated is omitted, never forced to an empty/0placeholderai_hint(recommended): Description to guide the LLM
Array Attributes ​
json
{
"name": "interests",
"type": "array",
"items_type": "string",
"required": false,
"ai_hint": "List of topics the person mentioned interest in"
}Nested Object Attributes ​
json
{
"name": "address",
"type": "object",
"properties": [
{"name": "street", "type": "string", "required": false},
{"name": "city", "type": "string", "required": false},
{"name": "postal_code", "type": "string", "required": false}
],
"required": false,
"ai_hint": "Mailing address details"
}Entity Extraction Flow ​
Visitor-Only Attribution ​
Collected data is what the visitor said — never what the agent said about itself. Before anything is stored, the extraction pipeline applies an attribution gate:
- Every attribute's provenance (cited transcript message + verbatim quote) is verified against the session's actual messages.
- An attribute survives only when its verified provenance anchors to a
user(visitor) message. Assistant, system, and tool turns are context for understanding the conversation — they can never be the source of a persisted value. The assistant's own descriptions, answers, and marketing copy are things the agent said, not things the visitor said. - When the visitor confirms or selects something the assistant proposed ("yes, that one"), the value is extracted with the visitor's confirming message as its source — not the assistant's proposal.
- An entity with zero surviving attributes is not created, and fields the visitor never stated are omitted entirely — never stored as
""or0placeholders. - Values read from a visitor-uploaded document (attachment) anchor to the visitor's upload turn: the quote lives in the document, not the transcript text, so upload turns verify without a text match.
Entity Status Workflow ​
Extracted entities follow a status workflow for review and action:
| Status | Description | Typical Action |
|---|---|---|
NEW | Just extracted, not reviewed | Review in dashboard |
REVIEWED | Reviewed by user | Mark as valid |
ACTIONED | Action taken (e.g., added to CRM) | Integration completed |
ARCHIVED | Archived/dismissed | Not relevant |
Update Entity Status ​
bash
curl -X PATCH "http://localhost:8000/api/v1/extracted-entities/1" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "REVIEWED",
"notes": "Verified and added to CRM"
}'Bulk Status Update ​
bash
curl -X POST "http://localhost:8000/api/v1/extracted-entities/bulk-status" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"entity_ids": [1, 2, 3],
"status": "ACTIONED"
}'Confidence Scoring ​
The LLM assigns confidence scores based on extraction quality:
| Score | Meaning | Action |
|---|---|---|
| 1.0 | Information is explicitly stated | High confidence, likely accurate |
| 0.8-0.9 | Information is clearly implied | Good confidence |
| 0.6-0.7 | Information requires some inference | Review recommended |
| < 0.5 | Low confidence | Filtered out (below threshold) |
Configuration:
bash
# Minimum confidence threshold (default: 0.5)
ENTITY_EXTRACTION_MIN_CONFIDENCE=0.5Entities below the threshold are not created.
Filtering and Search ​
Filter by Entity Type ​
bash
curl "http://localhost:8000/api/v1/extracted-entities?entity_type_id=1" \
-H "Authorization: Bearer $TOKEN"Filter by Agent ​
bash
curl "http://localhost:8000/api/v1/extracted-entities?agent_id=1" \
-H "Authorization: Bearer $TOKEN"Filter by Confidence ​
bash
curl "http://localhost:8000/api/v1/extracted-entities?min_confidence=0.8" \
-H "Authorization: Bearer $TOKEN"Filter by Date Range ​
bash
curl "http://localhost:8000/api/v1/extracted-entities?date_from=2025-01-01&date_to=2025-01-31" \
-H "Authorization: Bearer $TOKEN"Search (Attributes, Agent Name, Data Type Name) ​
bash
curl "http://localhost:8000/api/v1/extracted-entities?search=john@example.com" \
-H "Authorization: Bearer $TOKEN"Also matches against the entity's agent name and entity-type (data type) name — not just attribute values.
Include Archived ​
bash
curl "http://localhost:8000/api/v1/extracted-entities?include_archived=true" \
-H "Authorization: Bearer $TOKEN"Entity Statistics ​
Get entity extraction statistics for dashboard:
bash
curl "http://localhost:8000/api/v1/extracted-entities/stats?days=30" \
-H "Authorization: Bearer $TOKEN"Response:
json
{
"total": 145,
"period_days": 30,
"status_counts": {
"NEW": 45,
"REVIEWED": 70,
"ACTIONED": 25,
"ARCHIVED": 5
},
"type_counts": {
"Contact": 100,
"Lead": 30,
"Appointment": 15
}
}Example: Lead Collection Agent ​
1. Create Lead Entity Type ​
bash
curl -X POST "http://localhost:8000/api/v1/entity-types" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "Lead",
"description": "Sales lead information",
"icon": "briefcase",
"attributes": [
{
"name": "company_name",
"type": "string",
"required": true,
"ai_hint": "Company or organization name"
},
{
"name": "contact_name",
"type": "string",
"required": true,
"ai_hint": "Full name of the contact person"
},
{
"name": "email",
"type": "email",
"required": false,
"ai_hint": "Business email address"
},
{
"name": "industry",
"type": "string",
"required": false,
"ai_hint": "Industry or sector"
},
{
"name": "estimated_value",
"type": "number",
"required": false,
"ai_hint": "Estimated deal value in dollars"
},
{
"name": "pain_points",
"type": "array",
"items_type": "string",
"required": false,
"ai_hint": "List of challenges or problems mentioned"
},
{
"name": "timeline",
"type": "string",
"required": false,
"ai_hint": "When they need a solution (e.g., Q1 2025, urgent, 3 months)"
}
]
}'2. Create Sales Agent with Entity Type ​
bash
# Create agent (or use existing)
AGENT_ID=5
# Assign Lead entity type
curl -X POST "http://localhost:8000/api/v1/agents/$AGENT_ID/entity-types/1" \
-H "Authorization: Bearer $TOKEN"3. Example Conversation ​
User: "Hi, I'm Sarah from Acme Corp. We're a software company looking for a better task management solution."
Agent: "Hello Sarah! Great to meet you. Tell me more about your current challenges with task management."
User: "Our team of 50 struggles with visibility across projects. We need something by end of Q1 and budget around $10k."
4. Automatic Extraction ​
After session ends, system extracts:
json
{
"entity_type": "Lead",
"confidence": 0.92,
"attributes": {
"company_name": "Acme Corp",
"contact_name": "Sarah",
"industry": "software",
"estimated_value": 10000,
"pain_points": ["visibility across projects", "team coordination"],
"timeline": "end of Q1"
}
}5. Review and Export ​
bash
# Get new leads
curl "http://localhost:8000/api/v1/extracted-entities?entity_type_id=1&status=NEW" \
-H "Authorization: Bearer $TOKEN"
# Export all matching leads as a CSV download (filter-aware, streamed, no
# pagination needed -- see the Entities API reference for the full column
# list, response headers, and truncation semantics)
curl "http://localhost:8000/api/v1/entities/export?entity_type_id=1&status=NEW" \
-H "Authorization: Bearer $TOKEN" \
-o leads.csv
# Mark as actioned
curl -X PATCH "http://localhost:8000/api/v1/extracted-entities/1" \
-H "Authorization: Bearer $TOKEN" \
-d '{"status": "ACTIONED", "notes": "Added to Salesforce"}'Configuration ​
Environment Variables ​
bash
# Model to use for entity extraction — inherits GNOSARI_DEFAULT_MODEL when unset
# (see api/docs/04-deployment/01-configuration.md "LLM Model Contract")
GNOSARI_EXTRACTION_MODEL=gpt-5.4-nano
# Temperature for extraction (0.0-1.0, lower = more consistent results)
ENTITY_EXTRACTION_TEMPERATURE=0.1
# Minimum confidence threshold (entities below this are discarded)
ENTITY_EXTRACTION_MIN_CONFIDENCE=0.5
# Extraction timeout in seconds
ENTITY_EXTRACTION_TIMEOUT=60Best Practices ​
Entity Type Design ​
Use clear ai_hints: Help the LLM understand what to extract
json{"ai_hint": "Full legal name including middle name if provided"}Mark truly required fields: Only mark fields as required if essential
json{"required": true} // Only for email, name, etc.Use appropriate types: Match field types to data
json{"type": "email"} // Not "string" for emails {"type": "date"} // Not "string" for datesProvide structure for arrays: Specify what goes in the list
json{"ai_hint": "List of specific product names mentioned, not general categories"}
Extraction Quality ​
- Review confidence scores: Set appropriate thresholds
- Test with sample conversations: Verify extraction accuracy
- Iterate on ai_hints: Refine prompts based on results
- Expect merge, not duplicates: repeated captures enrich the existing record for that kind; a later turn in the conversation replaces a conflicting value that extraction captured earlier; a value you edited by hand is never replaced by extraction, so your own corrections stay as you left them; nothing is silently dropped
See Also ​
Captured data can also be pushed out via webhooks as it is collected or updated, not just read through the API above:
- Webhook Events Reference - The event kinds and payload envelope
- Verifying Webhook Signatures - Confirm a delivery is genuine
Cross-References ​
- Entity Types API Reference - Full API documentation
- Extracted Entities API Reference - Entity management endpoints
- Entity Extraction System Architecture - Internal architecture