|
| 1 | +""" |
| 2 | +Vektori — Agent-type extraction customisation examples |
| 3 | +======================================================= |
| 4 | +
|
| 5 | +Shows how to tailor what Vektori extracts at L0 (facts) and L1 (episodes) |
| 6 | +for different agent personas. The same conversation is run through three |
| 7 | +differently-configured Vektori instances so you can see the effect. |
| 8 | +
|
| 9 | +Quick-start |
| 10 | +----------- |
| 11 | + export OPENAI_API_KEY=... |
| 12 | + python examples/agent_type_extraction.py |
| 13 | +
|
| 14 | +Levels of customisation |
| 15 | +----------------------- |
| 16 | + Level 1 — agent_type preset (zero effort): |
| 17 | + Vektori(agent_type="presales") |
| 18 | +
|
| 19 | + Level 2 — domain hints on top of a preset: |
| 20 | + Vektori(extraction_config=ExtractionConfig( |
| 21 | + agent_type="presales", |
| 22 | + focus_on=["ICP fit", "executive sponsor"], |
| 23 | + ignore=["pleasantries"], |
| 24 | + )) |
| 25 | +
|
| 26 | + Level 3 — prompt suffix (low effort, precise control): |
| 27 | + Vektori(extraction_config=ExtractionConfig( |
| 28 | + agent_type="sales", |
| 29 | + facts_prompt_suffix="Always extract the exact dollar amount when pricing is mentioned.", |
| 30 | + )) |
| 31 | +
|
| 32 | + Level 4 — full prompt override (escape hatch): |
| 33 | + Vektori(extraction_config=ExtractionConfig( |
| 34 | + custom_facts_prompt=MY_PROMPT_TEMPLATE, |
| 35 | + )) |
| 36 | +""" |
| 37 | + |
| 38 | +import asyncio |
| 39 | +import json |
| 40 | + |
| 41 | +from vektori import ExtractionConfig, Vektori |
| 42 | + |
| 43 | +# --------------------------------------------------------------------------- |
| 44 | +# A realistic pre-sales discovery call snippet |
| 45 | +# --------------------------------------------------------------------------- |
| 46 | +PRESALES_CONVERSATION = [ |
| 47 | + { |
| 48 | + "role": "user", |
| 49 | + "content": ( |
| 50 | + "We're a Series B fintech, around 200 engineers. " |
| 51 | + "Right now our AI agents lose context between sessions — support keeps " |
| 52 | + "repeating the same questions to customers. It's killing CSAT scores." |
| 53 | + ), |
| 54 | + }, |
| 55 | + { |
| 56 | + "role": "assistant", |
| 57 | + "content": ( |
| 58 | + "That's a common friction point at your scale. What does your current " |
| 59 | + "session storage look like — are you on something like Redis, or more ad hoc?" |
| 60 | + ), |
| 61 | + }, |
| 62 | + { |
| 63 | + "role": "user", |
| 64 | + "content": ( |
| 65 | + "Ad hoc, honestly. Each team rolls their own. Our CTO wants a unified " |
| 66 | + "memory layer by Q3 — we've got maybe a $60–80K budget for tooling this half. " |
| 67 | + "We tried Mem0 briefly but the graph retrieval wasn't granular enough." |
| 68 | + ), |
| 69 | + }, |
| 70 | + { |
| 71 | + "role": "assistant", |
| 72 | + "content": ( |
| 73 | + "Got it. So you need something that preserves the full conversation story, " |
| 74 | + "not just entity triples — that's exactly what Vektori's three-layer graph " |
| 75 | + "is built for. Who else is involved in the decision besides your CTO?" |
| 76 | + ), |
| 77 | + }, |
| 78 | + { |
| 79 | + "role": "user", |
| 80 | + "content": ( |
| 81 | + "Our VP Eng and the platform team lead, Aisha. She's the one who'd actually " |
| 82 | + "integrate it. They're both pretty hands-on technically." |
| 83 | + ), |
| 84 | + }, |
| 85 | +] |
| 86 | + |
| 87 | +# --------------------------------------------------------------------------- |
| 88 | +# A sales closing call |
| 89 | +# --------------------------------------------------------------------------- |
| 90 | +SALES_CONVERSATION = [ |
| 91 | + { |
| 92 | + "role": "user", |
| 93 | + "content": ( |
| 94 | + "We reviewed the proposal. Legal flagged the data residency clause — " |
| 95 | + "they need EU hosting confirmed before we can sign." |
| 96 | + ), |
| 97 | + }, |
| 98 | + { |
| 99 | + "role": "assistant", |
| 100 | + "content": ( |
| 101 | + "Understood. Our EU region is live on AWS eu-west-1 — I'll get that " |
| 102 | + "confirmed in writing by tomorrow. Are there any other open items?" |
| 103 | + ), |
| 104 | + }, |
| 105 | + { |
| 106 | + "role": "user", |
| 107 | + "content": ( |
| 108 | + "Just that. We're looking at the $48K/year enterprise tier. " |
| 109 | + "If legal clears it this week we can sign by Friday the 18th." |
| 110 | + ), |
| 111 | + }, |
| 112 | + { |
| 113 | + "role": "assistant", |
| 114 | + "content": ( |
| 115 | + "Perfect. I'll loop in our legal team tonight. I'll also prep the " |
| 116 | + "countersigned order form so it's ready to go the moment you get approval." |
| 117 | + ), |
| 118 | + }, |
| 119 | +] |
| 120 | + |
| 121 | + |
| 122 | +async def run_demo(): |
| 123 | + print("=" * 70) |
| 124 | + print("Vektori ExtractionConfig demo") |
| 125 | + print("=" * 70) |
| 126 | + |
| 127 | + # ------------------------------------------------------------------ |
| 128 | + # Example 1: pre-sales preset — zero effort |
| 129 | + # ------------------------------------------------------------------ |
| 130 | + print("\n[1] agent_type='presales' — built-in preset\n") |
| 131 | + |
| 132 | + presales_v = Vektori(agent_type="presales") |
| 133 | + captured: list[dict] = [] |
| 134 | + await presales_v.add( |
| 135 | + messages=PRESALES_CONVERSATION, |
| 136 | + session_id="demo-presales-001", |
| 137 | + user_id="demo-user", |
| 138 | + # _capture_out is an internal debug hook used in tests |
| 139 | + ) |
| 140 | + # Give async extraction a moment to complete for the demo |
| 141 | + await asyncio.sleep(3) |
| 142 | + memory = await presales_v.search("budget and decision makers", user_id="demo-user") |
| 143 | + print("Facts retrieved (presales):") |
| 144 | + for f in memory.get("facts", []): |
| 145 | + print(f" • {f['text']}") |
| 146 | + await presales_v.close() |
| 147 | + |
| 148 | + # ------------------------------------------------------------------ |
| 149 | + # Example 2: sales preset + domain hints |
| 150 | + # ------------------------------------------------------------------ |
| 151 | + print("\n[2] agent_type='sales' + focus_on=['contract value', 'close date']\n") |
| 152 | + |
| 153 | + sales_v = Vektori( |
| 154 | + extraction_config=ExtractionConfig( |
| 155 | + agent_type="sales", |
| 156 | + focus_on=["contract value", "close date", "legal blockers"], |
| 157 | + ) |
| 158 | + ) |
| 159 | + await sales_v.add( |
| 160 | + messages=SALES_CONVERSATION, |
| 161 | + session_id="demo-sales-001", |
| 162 | + user_id="demo-sales-user", |
| 163 | + ) |
| 164 | + await asyncio.sleep(3) |
| 165 | + memory = await sales_v.search("deal status and blockers", user_id="demo-sales-user") |
| 166 | + print("Facts retrieved (sales):") |
| 167 | + for f in memory.get("facts", []): |
| 168 | + print(f" • {f['text']}") |
| 169 | + await sales_v.close() |
| 170 | + |
| 171 | + # ------------------------------------------------------------------ |
| 172 | + # Example 3: same sales call, general (no preset) — shows the difference |
| 173 | + # ------------------------------------------------------------------ |
| 174 | + print("\n[3] agent_type='general' — no domain bias (baseline)\n") |
| 175 | + |
| 176 | + general_v = Vektori() # default, no agent_type |
| 177 | + await general_v.add( |
| 178 | + messages=SALES_CONVERSATION, |
| 179 | + session_id="demo-general-001", |
| 180 | + user_id="demo-general-user", |
| 181 | + ) |
| 182 | + await asyncio.sleep(3) |
| 183 | + memory = await general_v.search("deal status and blockers", user_id="demo-general-user") |
| 184 | + print("Facts retrieved (general):") |
| 185 | + for f in memory.get("facts", []): |
| 186 | + print(f" • {f['text']}") |
| 187 | + await general_v.close() |
| 188 | + |
| 189 | + print("\nDone.") |
| 190 | + |
| 191 | + |
| 192 | +if __name__ == "__main__": |
| 193 | + asyncio.run(run_demo()) |
0 commit comments