AI assistant making up CRM data — investigate hallucination source #39
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_biz#39
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Overview
The CRM AI surface returns invented contacts / projects / tasks instead of grounding on real OSIS data. Casper hit this; Timur is investigating.
Why
Meeting 2026-05-06: "asked timur to view the issue / AI making up stuff / explained to Casper / Timur helping Casper on blockers".
This is related to but distinct from home#215 (assistant non-functional from missing Groq key). The hallucination here happens even when the assistant responds — the response is fluent but factually wrong.
Likely candidates for the root cause:
Acceptance
Related
Owner: timur (investigating) + casper (reporting).
Source: meeting notes 2026-05-06.
casper-stevens referenced this issue2026-05-07 07:42:06 +00:00
The dominant cause is likely candidate 2 — the AI tool surface may not exist yet. From reading the code, there appear to be no tool definitions, no function-calling schema, and no OSIS calls from the AI layer.
build_entity_context()loads the currently-viewed entity and injects it as markdown into the prompt; that's likely the only OSIS data the LLM sees. Anything outside that one entity would then get fabricated.Candidate 1 (context routing) is likely not the cause here. hero_biz → hero_osis traffic goes over HTTP through hero_router, and that path appears to already pass
X-Hero-Contextcorrectly. The UDS header bug in hero_rpc#42 is real but likely affects a different code path.What would unblock this: wire CRM read tools into the assistant (
search_persons,search_companies,list_tasks,get_deal) with a proper function-calling schema and dispatch in theassistant_chat()handler. Adding a grounding guardrail to the system prompt would help as a safety net regardless.Implementation Spec — Issue #39: Ground the AI Assistant on Real OSIS Data
Objective
Eliminate hallucination from the
assistant_chatendpoint by dispatchingStorequeries based on the parsedIntentand injecting the results into the LLM prompt before the main model call. Add a system prompt guardrail instructing the LLM to refuse to answer from memory when data is not present in context.Root Cause (confirmed)
process_messageinassistant.rscallsanalyze_intentand receives a correctly-populatedIntentstruct, then discards it — no store query is dispatched. The LLM receives only the single currently-viewed entity's markdown (context_data) and must answer all broader queries from its parametric memory, which it fabricates.Requirements
analyze_intentreturns, inspectintent.actionandintent.entity_typeto decide what additional data to fetch from theStore.## Retrieved Datasection to the system prompt containing the fetched records as compact markdown (capped at 10 results).assistant_chat), not inAssistant::process_message, soAssistant's API surface stays unchanged except for one new method.AssistantContext.context_datafield.Files to Modify
crates/hero_biz_admin/src/ai/assistant.rs— add guardrail rule tobuild_system_prompt; addprocess_message_preanalyzedto avoid double intent-analysis LLM callcrates/hero_biz_admin/src/web/handlers/mod.rs— adddispatch_intent_to_storehelper; wire it intoassistant_chatImplementation Plan
Step 1 — Guardrail +
process_message_preanalyzedinassistant.rsFile:
crates/hero_biz_admin/src/ai/assistant.rsbuild_system_prompt, append rule 7 to the INSTRUCTIONS block:analyze_intentsystem prompt, explicitly list validactionvalues:"search","list","get_info","get_related","add_investment","update_contact","create_deal","ask_question".process_message_preanalyzed(&self, user_input, context, conversation_history, intent: Option<Intent>) -> Result<AssistantResponse>. This is the currentprocess_messagebody with the internalanalyze_intentcall replaced by the passed-inintent. Refactorprocess_messageto delegate toprocess_message_preanalyzed.Step 2 —
dispatch_intent_to_store+ wiring inhandlers/mod.rsFile:
crates/hero_biz_admin/src/web/handlers/mod.rsAdd
async fn dispatch_intent_to_store(store, searcher, ctx, intent, current_context_type, current_context_id) -> Option<String>nearbuild_entity_context. It:action == "search": callssearcher.search/searcher.search_typeand formats up to 10 results.action == "list" | "get_info" | "get_related": calls the appropriateStoremethod based onentity_typeand optionalentity_id(uses relationship methods when an ID is present).## Retrieved Data.In
assistant_chat, before callingprocess_message:assistant.analyze_intentup-front.dispatch_intent_to_storewith the result.context_data(append\n\n## Retrieved Data\n{extra}).assistant.process_message_preanalyzedwith the augmented context and the already-fetched intent (avoids a second intent-analysis LLM call).Acceptance Criteria
get_deals_for_person, results cited correctlycargo buildpasses with no new warningsNotes
AppStatealready carries bothstoreandsearcher; no struct changes needed.Searcherinsearch/fuzzy.rscovers all entity types.Test Results
All tests passed. Tests are in the
hero_biz_admincrate:services::tests::path_traversal_sequences_rejectedservices::tests::null_byte_is_rejectedservices::tests::empty_string_is_rejectedservices::tests::dot_dot_is_rejectedservices::tests::slash_is_rejectedparser::tests::test_name_fixservices::tests::valid_components_passImplementation Summary
Root Cause
process_messageinassistant.rscalledanalyze_intentand received a correctly-populatedIntentstruct, then discarded it — no store query was ever dispatched. The LLM received only the single currently-viewed entity's markdown and had to answer all broader queries from its parametric memory, producing fabricated results.Changes Made
crates/hero_biz_admin/src/ai/assistant.rsbuild_system_prompt: hard guardrail forbidding the LLM from inventing names, IDs, amounts, dates, or relationships not present in CURRENT CONTEXT or RETRIEVED DATAactionvalues in theanalyze_intentsystem prompt so the LLM reliably emits a dispatchable action stringprocess_message_preanalyzedmethod that accepts a pre-computedOption<Intent>, avoiding a double intent-analysis LLM call when the caller has already run intent analysisprocess_messageto delegate toprocess_message_preanalyzedcrates/hero_biz_admin/src/web/handlers/mod.rsdispatch_intent_to_storeasync helper that mapsIntent.action+Intent.entity_typeto concreteStorequeries (list, relationship lookup, or fuzzy search viaSearcher)## Retrieved Datasection in the promptassistant_chat: intent is now analysed up-front, dispatched to the store, and the augmented context is passed toprocess_message_preanalyzed— a single intent-analysis LLM call per requestTest Results
7/7 tests passed (cargo test, hero_biz_admin crate).