Spaces:
Running
Running
|
Download engagement-research.md from IDKHowToCodeFr/vera-engine: direct link, hf CLI and curl.
- Browser
- Download file 13.3 kB
-
https://huggingface.co/spaces/IDKHowToCodeFr/vera-engine/resolve/main/engagement-research.md
- Command line
-
hf download hf://spaces/IDKHowToCodeFr/vera-engine/engagement-research.md
-
curl -L -o engagement-research.md https://huggingface.co/spaces/IDKHowToCodeFr/vera-engine/resolve/main/engagement-research.md
13.3 kB
| # Vera Engagement Framework β Research: Current Merchant Data Access | |
| **Status**: Research notes β companion to `engagement-design.md`. | |
| **Last updated**: 2026-04-26 | |
| **Scope**: How the existing system loads merchant + customer data at conversation init and during a turn. Used to inform what the new framework must build vs. adapt. | |
| --- | |
| ## TL;DR | |
| Two distinct paths exist today β **merchant-facing** (Vera β Dr. Meera) and **customer-facing** (a customer asks Vera *about* Dr. Meera). They share infrastructure (vera-mcp + merchant-support-mcp + Redis) but compose context very differently. | |
| Most of what the proposed `MerchantContext` needs **already exists in scattered form** (`_merchant_snapshot`, `_behavioral_profile`, `_session_scenario`). The genuinely new pieces are: | |
| - `CategoryContext` (no existing equivalent) | |
| - `TriggerContext` (no normalized abstraction today β every cron emits ad-hoc payloads) | |
| - `CustomerContext.relationship` (visit history aggregation doesn't exist) | |
| - `EngagementComposer` (no shared composer β each agent has its own prompt-builder) | |
| The aryan dependency for `category` and `locality` is the soft underbelly β every customer-info-pack call hits aryan synchronously. | |
| --- | |
| ## Path 1 β Merchant-facing (`VeraMerchantAgent`) | |
| ### Init (per session, instance-level) | |
| `agents/vera/merchant_agent.py:402` β `__init__`: | |
| - Spawns its own `vera_mcp_client` (`HTTPMCPClient` β `VERA_MCP_SERVER_URL`, default `vera-mcp:8000`). | |
| - Inherits `BaseAgent._merchant_mcp_client` β class-level shared singleton pointing to `merchant-support-mcp` (`https://search5.magicpin.com/staging/mcp-server/mcp`). | |
| - **No merchant data is loaded at init** β agent doesn't know who it's talking to yet. | |
| ### Per-turn (every inbound message) | |
| `agents/vera/merchant_agent.py:2171` β `handle_input(agent_input)`: | |
| 1. Extract `merchant_id` from `metadata.context_data.merchant_id`, falling back to regex on the message body (`\d{7,}`). | |
| 2. Fire 2 parallel tasks: | |
| - `_detect_state(merchant_id)` β GBP connection + subscription status checks. | |
| - `_prefetch_merchant_context(merchant_id)` β full snapshot + behavioral profile. | |
| ### `_prefetch_merchant_context` β `merchant_agent.py:740` | |
| Cache-first design: | |
| 1. **Redis check**: `vera:merchant_ctx:{merchant_id}` β TTL **30 min**. Hit β return immediately, skip everything below. | |
| 2. Parallel via `asyncio.gather`: | |
| - `vera_merchant_snapshot` (vera-mcp tool) | |
| - `vera_get_merchant_profile` (vera-mcp tool) | |
| 3. If snapshot is empty: fall back to `get_aggregated_unassociated_merchant_data` (merchant-support-mcp) β basic GBP health, no commercial data. Wrap it in a snapshot-shaped envelope. | |
| 4. Sequential enrichments: | |
| - `_prefetch_product_context` β fans out to `vera_get_subscription_context`, `vera_get_performance_summary`, and (only if DA subscribed) `da_get_campaign_context`. ~2KB total, embedded into snapshot. | |
| - `_enrich_snapshot_with_pricing` β direct HTTP to `https://vera.magicpin.com/api/v1/merchant/pricing/get?mid=...` (vera-mcp's pricing path is unreachable locally). Mounted into snapshot as `pricing_recommendation`. | |
| 5. Stuff result into Redis at the same key, TTL 30 min. | |
| Final state: `self._merchant_snapshot` and `self._behavioral_profile` are populated. `_get_system_prompt()` reads from these and serializes the snapshot directly into the LLM system prompt (`merchant_agent.py:996`). | |
| ### What `vera_merchant_snapshot` actually fetches | |
| `vera-mcp/src/services/merchant_snapshot.py:51` β `build_merchant_snapshot()`: | |
| 1. **Resolve identity** via `gbp_resolve_merchant` β gets `place_id`, `location_name`, `merchant_title`. | |
| 2. **One async fan-out** of up to 13 tool calls in parallel: | |
| | Bucket | Tools | | |
| |---|---| | |
| | Merchant-level (no GBP needed) | `vera_get_subscription_status`, `vera_get_pricing_by_merchant`, `vera_get_merchant_pain_points`, `vera_get_merchant_offer`, `vera_get_merchant_config`, `vera_get_onboarding_status`, `vera_get_enhancement_suggestions` | | |
| | Location-level (skipped if no place_id) | `gbp_get_location`, `gbp_get_profile_completeness`, `gbp_get_performance_summary`, `gbp_get_search_keywords`, `gbp_get_review_stats`, `gbp_list_posts` | | |
| 3. Composes 7 sections: `identity`, `profile`, `reputation`, `growth`, `commercial`, `conversation_hooks`, `issues`. | |
| > **Note**: `category` and `locality` are not first-class fields on the snapshot β they're buried inside `profile.business_info` (from `gbp_get_location`) and surfaced via `aryan_client.get_merchant_v2()` only when `vera_get_customer_info_pack` is called. | |
| ### Mid-conversation tool calls | |
| The LLM gets the full toolset from `MERCHANT_MCP_ALLOWED_TOOLS` (declared on the agent class) merged with `_vera_mcp_tools_cache`. Mid-turn tool calls flow through: | |
| - `self.vera_mcp_client.call_tool(name, args)` β for vera-mcp tools (HTTP) | |
| - `BaseAgent._merchant_mcp_client.call_tool(name, args)` β for merchant-support-mcp tools (HTTP) | |
| No re-prefetch of the snapshot mid-turn. The agent relies on the prefetched snapshot + LLM-initiated lookups when it needs fresh data. | |
| --- | |
| ## Path 2 β Customer-facing (`CustomerIncomingAgent`) | |
| `agents/vera/customer_incoming_agent.py:91` β different agent, different state model. | |
| ### Init + per-turn | |
| The instance carries: | |
| - `_merchant_id` β the merchant the customer is asking about | |
| - `_merchant_data`, `_merchant_name`, `_category`, `_offers`, `_jd_info` β about the **business** | |
| - `_customer_phone`, `_customer_profile` β about the **customer** (the one chatting) | |
| - `_chat_history`, `_session_state` β conversation continuity | |
| ### Merchant data load β `_load_merchant_data` (`customer_incoming_agent.py:221`) | |
| Single shape, no Redis cache: | |
| 1. **Primary call**: `vera_get_customer_info_pack(merchant_id)` β one MCP call returns `business_info` + `reviews` + `photos` + `offers` + `metadata` in one shot. The aggregated customer-facing endpoint. | |
| 2. **Fallback chain** if `info_pack` is empty: | |
| - `get_unassociated_merchant_data` β basic merchant info | |
| - `vera_list_merchant_offers` β separate offer list | |
| 3. **Supplementary** (non-blocking): `vera_get_merchant_jd_info` β JustDial crawl data for additional name / address / phone. | |
| Caching is at the request level (in-memory on the agent instance) β re-using the same `merchant_id` in a session avoids re-fetching. | |
| ### What `vera_get_customer_info_pack` does | |
| `vera-mcp/src/tools/merchant_info.py:188`: | |
| 1. **Resolve place_id**: `_resolve_place_id(merchant_id)` β chain of MongoDB (`gbp_status`) β local JSON β `aryan_client.get_mapping()`. | |
| 2. **Parallel fetch**: | |
| - GBP data via `get_or_fetch(place_id)` β 24h cache in MongoDB, falls through to Google Places API | |
| - `_get_active_offers(merchant_id)` β direct MongoDB read of `offers` collection where `status=active` | |
| - `_get_merchant_metadata(merchant_id)` β `aryan_client.get_merchant_v2()` β returns `name`, `category`, `locality` | |
| 3. **Last-resort fallback**: `_fetch_embed_data(place_id)` β vera REST `/embed` endpoint if everything else came back empty. | |
| ### Customer profile (the *caller*) | |
| `_customer_profile` is loaded separately β populated in `_init_customer_data` from past conversation tickets keyed off `_customer_phone`. There's no rich CRM behind it today; it's mostly conversation continuity (last visit, last topic). | |
| --- | |
| ## Cross-cutting infrastructure | |
| | Concern | Implementation | | |
| |---|---| | |
| | MCP transport | `HTTPMCPClient` (vera client) β keeps a session, calls `/mcp/tools/{name}` HTTP POST | | |
| | Auth | Not required for challenge bot endpoints | | |
| | Cache key for merchant context | `vera:merchant_ctx:{merchant_id}` β Redis, TTL 30 min, written by `_prefetch_merchant_context` | | |
| | Cache key for GBP data | `gbp_health_report:{place_id}` β MongoDB, TTL 24h, in vera-mcp | | |
| | Snapshot freshness for sends | Whatever's in Redis β not refreshed on send unless agent is in active conversation | | |
| | Source of truth for `category` | aryan `get_merchant_v2` API (via `aryan_client`) β used in `_get_merchant_metadata` | | |
| | Source of truth for `name` | aryan first, GBP `business_info` second | | |
| --- | |
| ## Observations relevant to the engagement framework | |
| ### What already exists | |
| 1. **Most of `MerchantContext` already exists** β spread across `_merchant_snapshot`, `_behavioral_profile`, `_session_scenario`, `_jd_info`. A `MerchantContext.from_existing(agent_state)` adapter could load ~80% of the fields without any new fetching. | |
| 2. **The customer agent already has half a `CustomerContext`** β `_customer_phone` + `_customer_profile` give us identity + conversation continuity. Missing: visit history, services received, lapse state. | |
| 3. **Two MCP servers, one orchestration** β vera-mcp (instance-level) for vera tools, merchant-support-mcp (class-level shared) for fallback merchant tools. The composer can just consume whatever `MerchantContext` already collected β no new MCP wiring required. | |
| 4. **Cache TTL of 30 min is fine** for engagement nudges that fire daily/weekly. Redis hits during conversation are plenty fresh for composition. | |
| ### What does NOT exist | |
| 1. **No `CategoryContext`.** Category is just a string buried in metadata. Voice rules, peer benchmarks, knowledge digests β none of it exists. Biggest greenfield area, but also the most leveraged (one CategoryContext serves all merchants in the vertical). | |
| 2. **No `customer_aggregate` field on the merchant snapshot.** No pipeline today aggregates per-merchant customer roster stats (active count, lapsed count, retention rate). | |
| 3. **No `TriggerContext` abstraction.** Every nudge type today has its own cron + its own fetch logic + its own send code. The proposed `TriggerContext` is the genuinely new architectural primitive; everything else is reorganization. | |
| 4. **No visit-history aggregation per (merchant, customer_phone).** Required for `CustomerContext.relationship`. BOTOPS chat history has the raw data; no derived view exists. | |
| 5. **No shared composer.** Each agent has its own prompt builder embedded in `_get_system_prompt()`. The proposed `EngagementComposer` is net new. | |
| ### Operational risks to mitigate | |
| 1. **Aryan is the synchronous bottleneck.** `aryan_client.get_merchant_v2()` is the only path to category and locality, and it's a remote HTTP call. If aryan is slow, every customer-info-pack call is slow. Worth caching aryan responses per merchant for ~24h before scaling engagement frequency. | |
| 2. **The 30-min Redis cache is keyed per-merchant** β fine for in-conversation reuse, but a daily engagement cron will miss this cache 100% of the time and pay the full snapshot-build cost per send. Consider a longer-TTL background-warmed cache for the engagement loop specifically. | |
| 3. **No version tracking on prompts today.** Every send loses the prompt-version provenance. The composer should record prompt version + context hash on every send so we can replay and A/B. | |
| --- | |
| ## Concrete recommendation for Phase 1 of the framework | |
| Phase 1 is mostly an **adapter layer**, not new infrastructure: | |
| | Layer | Effort | What it actually does | | |
| |---|---|---| | |
| | `CategoryContext` | Net new | Build for dentistry first (offer catalog, voice, peer stats, weekly digest, patient-content seed) | | |
| | `MerchantContext` | Adapter | Wrap existing `_merchant_snapshot` + `_behavioral_profile` + a new `_customer_aggregate` derived from BOTOPS chat history | | |
| | `TriggerContext` | Net new abstraction | Normalize the payloads existing crons emit into a single shape; new triggers (research_digest, recall_due) emit it natively | | |
| | `CustomerContext` | Partial adapter | Wrap existing `_customer_profile`, plus a new visit-history aggregator | | |
| | `EngagementComposer` | Net new | Single LLM-prompted module with versioned prompt; consumes the above | | |
| Phase 1 should NOT require modifying any existing agent. The adapter reads from existing state; the composer is a new module that runs in parallel; the new triggers (research digest, recall) are new crons that don't touch the existing matrix-followup or campaign-engagement loops. | |
| --- | |
| ## Appendix: file pointers for follow-up implementation | |
| - `agents/vera/merchant_agent.py:402` β VeraMerchantAgent init | |
| - `agents/vera/merchant_agent.py:740` β `_prefetch_merchant_context` (the main load) | |
| - `agents/vera/merchant_agent.py:899` β `_prefetch_product_context` (subscription + DA campaign + perf summary) | |
| - `agents/vera/merchant_agent.py:996` β system-prompt builder reads `_merchant_snapshot` | |
| - `agents/vera/merchant_agent.py:2171` β `handle_input` per-turn entry | |
| - `agents/vera/customer_incoming_agent.py:91` β CustomerIncomingAgent class | |
| - `agents/vera/customer_incoming_agent.py:221` β `_load_merchant_data` (single info-pack call + fallbacks) | |
| - `agents/base_agent.py:80-82` β MERCHANT_MCP_SERVER_URL config | |
| - `agents/base_agent.py:237-244` β class-level shared `_merchant_mcp_client` | |
| - `vera-mcp/src/tools/merchant_snapshot.py` β `vera_merchant_snapshot` tool | |
| - `vera-mcp/src/services/merchant_snapshot.py:51` β `build_merchant_snapshot` (the 13-call fan-out) | |
| - `vera-mcp/src/tools/merchant_info.py:188` β `vera_get_customer_info_pack` tool | |
| - `vera-mcp/src/tools/merchant_info.py:30` β `_resolve_place_id` chain (MongoDB β JSON β aryan) | |
| - `vera-mcp/src/services/aryan_client.py` β aryan HTTP client (`get_merchant_v2`, `get_mapping`) | |