ConstCorrectness commited on
Commit
762a6f5
·
1 Parent(s): 50dcddc
.claude/commands/classify-intent.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Read the file `intents.yaml` in the current working directory.
2
+
3
+ Using the intent taxonomy from that file, classify the following user utterance:
4
+
5
+ "$ARGUMENTS"
6
+
7
+ Return your answer as:
8
+ - **Domain**: the top-level domain key (e.g. `shopping_assistant`)
9
+ - **Intent**: the action label within that domain (e.g. `add_item`)
10
+ - **Confidence**: high / medium / low
11
+ - **Reasoning**: one sentence explaining why
12
+
13
+ If the utterance matches no known intent, return `domain: unknown, intent: unknown` and suggest where in the taxonomy it might belong or whether a new intent is needed.
INTENT_RESTRUCTURE.md ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Intent Taxonomy Restructure — Rationale
2
+
3
+ ## Overview
4
+
5
+ The original intent specification (`intents.txt`) was reorganized into a structured YAML taxonomy (`intents.yaml`). This document explains what changed, why it was necessary, and how the new format benefits the system going forward.
6
+
7
+ ---
8
+
9
+ ## What the Original File Contained
10
+
11
+ The original file organized user phrases under package names (e.g. `BENEVOLENCE`, `MONEYSHARE`) using full natural language sentences as the primary identifier, with an optional `Alias` column for alternate phrasing.
12
+
13
+ **Example from the original:**
14
+
15
+ | Package | Intent | Alias |
16
+ |------------|---------------------------------|------------------------------|
17
+ | MONEYSHARE | I want to avoid a bank fee | |
18
+ | MONEYSHARE | I need a small loan | |
19
+ | MATH | compute xplusonesquared | Compute xsquared plus one |
20
+
21
+ ---
22
+
23
+ ## The Problem
24
+
25
+ ### 1. No Actual Intent Labels
26
+
27
+ In NLU (Natural Language Understanding) systems, an **intent** is a short, machine-readable label that represents *what the user wants to do* — for example `avoid_fee` or `request_loan`. The original file uses full sentences where the label should be, which means:
28
+
29
+ - There is no stable, referenceable name for any given intent
30
+ - Two sentences that mean the same thing appear as two different intents
31
+ - Code, APIs, and routing logic have no clean string to match against
32
+
33
+ ### 2. Conflation of Labels and Training Data
34
+
35
+ The sentences in the `Intent` column serve two completely different purposes in the original file:
36
+
37
+ - As an **intent identifier** (what the intent *is called*)
38
+ - As a **training utterance** (an example of what a user might say)
39
+
40
+ These are distinct concepts. A sentence like `"I am hungry"` is a training example — it teaches the system what the `request_food` intent sounds like. It should never be the name of the intent itself. Conflating the two makes the taxonomy brittle: renaming a phrase breaks the intent's identity.
41
+
42
+ ### 3. Duplicate Meaning Without Grouping
43
+
44
+ `"I'm hungry"` and `"I am hungry"` appear as separate rows under `FOODSHARE`, when they are clearly two utterances for the same intent. The original format has no mechanism to express that these map to a single action. This grows into a maintenance problem as more phrasings are added over time.
45
+
46
+ ### 4. No Structure for Aliasing
47
+
48
+ The `Alias` column is inconsistently populated and only appears for a few entries. In practice it was doing the job that a proper `utterances` list should do — providing alternate phrasings for the same intent.
49
+
50
+ ---
51
+
52
+ ## What We Changed
53
+
54
+ The new `intents.yaml` introduces a three-level hierarchy:
55
+
56
+ ```
57
+ domain → intent_label → utterances
58
+ ```
59
+
60
+ - **Domain**: the package or feature area (e.g. `moneyshare`, `shopping_assistant`)
61
+ - **Intent label**: a concise, snake_case action name (e.g. `avoid_fee`, `add_item`)
62
+ - **Utterances**: a list of example phrases a user might say to trigger that intent
63
+
64
+ **Equivalent example in the new format:**
65
+
66
+ ```yaml
67
+ moneyshare:
68
+ avoid_fee:
69
+ utterances:
70
+ - "I want to avoid a bank fee"
71
+ request_loan:
72
+ utterances:
73
+ - "I need a small loan"
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Why This Matters for an LLM Integration
79
+
80
+ When an LLM is used to classify user input against a set of intents, it needs:
81
+
82
+ 1. **A stable label to return** — the model's output needs to be a value your code can act on. `avoid_fee` is actionable; `"I want to avoid a bank fee"` is not.
83
+ 2. **Examples to reason from** — utterances serve as few-shot context that guides the model toward the correct classification.
84
+ 3. **A single source of truth** — keeping all phrasings for one intent together under one label means updating coverage requires editing one place, not hunting through a flat list.
85
+
86
+ The restructured file feeds directly into the `/classify` endpoint, which builds a system prompt from the taxonomy at runtime. Adding a new utterance to `intents.yaml` immediately improves classification accuracy with no code changes.
87
+
88
+ ---
89
+
90
+ ## Summary of Benefits
91
+
92
+ | Concern | Original Format | New Format |
93
+ |--------------------------|-------------------------|-----------------------------------|
94
+ | Machine-readable labels | None | Snake_case intent labels |
95
+ | Grouping of synonyms | Separate rows | Unified utterances list |
96
+ | Extensibility | Append rows to flat CSV | Add utterances under existing key |
97
+ | LLM/API integration | Not directly usable | Loads directly into system prompt |
98
+ | Aliasing | Partial, inconsistent | First-class `aliases` field |
99
+ | Readability | Moderate | Explicit hierarchy |
100
+
101
+ ---
102
+
103
+ ## Recommendation
104
+
105
+ The taxonomy is currently sparse in a few areas worth addressing before production:
106
+
107
+ - `math.compute_expression` ��� only covers two very specific phrasings; common variants like "calculate", "what is", or "evaluate" are missing
108
+ - `flow_planner.create_flow_plan` — the single utterance references "tomorrow" specifically, which will miss any other time reference
109
+ - `remembot` — has no coverage for listing or deleting memories, only storing and recalling
110
+
111
+ These can be expanded directly in `intents.yaml` without any engineering changes.
backend/intent_classifier.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+
5
+ import yaml
6
+ from openai import OpenAI
7
+
8
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
9
+
10
+ _INTENTS_PATH = Path(__file__).parent.parent / "intents.yaml"
11
+
12
+
13
+ def _load_taxonomy() -> dict:
14
+ with open(_INTENTS_PATH) as f:
15
+ return yaml.safe_load(f)["intents"]
16
+
17
+
18
+ def _build_system_prompt(taxonomy: dict) -> str:
19
+ lines = ["You are an intent classifier. Given a user message, return JSON with keys: domain, intent, confidence (high/medium/low)."]
20
+ lines.append("\nKnown intents (domain → intent: example utterances):\n")
21
+ for domain, intents in taxonomy.items():
22
+ for intent, data in intents.items():
23
+ utterances = data.get("utterances", [])
24
+ examples = "; ".join(utterances[:2])
25
+ lines.append(f" {domain}.{intent}: \"{examples}\"")
26
+ lines.append('\nIf nothing matches, return {"domain": "unknown", "intent": "unknown", "confidence": "low"}.')
27
+ lines.append("Respond with JSON only, no prose.")
28
+ return "\n".join(lines)
29
+
30
+
31
+ def classify(utterance: str) -> dict:
32
+ taxonomy = _load_taxonomy()
33
+ system_prompt = _build_system_prompt(taxonomy)
34
+
35
+ response = client.chat.completions.create(
36
+ model="gpt-4o-mini",
37
+ messages=[
38
+ {"role": "system", "content": system_prompt},
39
+ {"role": "user", "content": utterance},
40
+ ],
41
+ response_format={"type": "json_object"},
42
+ temperature=0,
43
+ )
44
+
45
+ return json.loads(response.choices[0].message.content)
backend/main.py CHANGED
@@ -9,6 +9,7 @@ from backend.jobs import create_job, get_job, update_job
9
  from backend.parser import parse_file, row_to_text, SUPPORTED_EXTENSIONS
10
  from backend.embedder import embed_texts, BATCH_SIZE
11
  from backend.vectordb import ensure_collection, upsert_points, search, list_source_files, get_all_vectors
 
12
 
13
  load_dotenv()
14
 
@@ -132,6 +133,17 @@ def embed_query(req: EmbedRequest):
132
  return {"vector": vectors[0]}
133
 
134
 
 
 
 
 
 
 
 
 
 
 
 
135
  @app.get("/health")
136
  def health():
137
  return {"status": "ok"}
 
9
  from backend.parser import parse_file, row_to_text, SUPPORTED_EXTENSIONS
10
  from backend.embedder import embed_texts, BATCH_SIZE
11
  from backend.vectordb import ensure_collection, upsert_points, search, list_source_files, get_all_vectors
12
+ from backend.intent_classifier import classify
13
 
14
  load_dotenv()
15
 
 
133
  return {"vector": vectors[0]}
134
 
135
 
136
+ class ClassifyRequest(BaseModel):
137
+ utterance: str
138
+
139
+
140
+ @app.post("/classify")
141
+ def classify_intent(req: ClassifyRequest):
142
+ if not req.utterance.strip():
143
+ raise HTTPException(status_code=400, detail="Utterance cannot be empty")
144
+ return classify(req.utterance)
145
+
146
+
147
  @app.get("/health")
148
  def health():
149
  return {"status": "ok"}
intents.yaml ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ intents:
2
+ benevolence:
3
+ view_events:
4
+ utterances:
5
+ - "I want to see events coming up"
6
+ view_wishlists:
7
+ utterances:
8
+ - "I want to see wishlists"
9
+ buy_gifts:
10
+ utterances:
11
+ - "I want to buy gifts for my loved ones"
12
+
13
+ remembot:
14
+ remember:
15
+ utterances:
16
+ - "I want to remember something"
17
+ recall:
18
+ utterances:
19
+ - "I want to recall something"
20
+
21
+ moneyshare:
22
+ avoid_fee:
23
+ utterances:
24
+ - "I want to avoid a bank fee"
25
+ request_loan:
26
+ utterances:
27
+ - "I need a small loan"
28
+
29
+ foodshare:
30
+ request_food:
31
+ utterances:
32
+ - "I'm hungry"
33
+ - "I am hungry"
34
+ share_food:
35
+ utterances:
36
+ - "I have food to share"
37
+ - "I want to share some food"
38
+
39
+ billpayshare:
40
+ request_bill_help:
41
+ utterances:
42
+ - "I need help with a bill"
43
+
44
+ bloodshare:
45
+ request_blood:
46
+ utterances:
47
+ - "I need blood"
48
+ share_blood:
49
+ utterances:
50
+ - "I have blood to share"
51
+
52
+ math:
53
+ compute_expression:
54
+ utterances:
55
+ - "compute xplusonesquared"
56
+ - "compute xsquaredplusone"
57
+ aliases:
58
+ - "Compute x squared plus one"
59
+ - "Compute x plus one squared"
60
+
61
+ shopping_assistant:
62
+ add_item:
63
+ utterances:
64
+ - "Add an item to my shopping list"
65
+ - "I want to add items to my shopping list"
66
+ remove_item:
67
+ utterances:
68
+ - "Remove an item from my shopping list"
69
+ view_list:
70
+ utterances:
71
+ - "What's on my shopping list"
72
+ - "I want to see my shopping list"
73
+ edit_list:
74
+ utterances:
75
+ - "I want to change my shopping list"
76
+ mark_purchased:
77
+ utterances:
78
+ - "I've purchased some items on my shopping list"
79
+ - "I have purchased some items on my shopping list"
80
+
81
+ bot_store:
82
+ add_package:
83
+ utterances:
84
+ - "I want to buy a package"
85
+ aliases:
86
+ - "I want to add a package"
87
+ remove_package:
88
+ utterances:
89
+ - "I want to stop using a package"
90
+ aliases:
91
+ - "I want to remove a package"
92
+
93
+ taskmaster_ai:
94
+ add_task:
95
+ utterances:
96
+ - "I want to add a to do list item to my list"
97
+ - "Add a to do list item"
98
+ - "I want to add a to do list item"
99
+ view_tasks:
100
+ utterances:
101
+ - "I want to see my to do list"
102
+ - "Show to do list"
103
+ - "Show my to do list"
104
+ edit_tasks:
105
+ utterances:
106
+ - "I want to change my to do list"
107
+ - "I want to edit my to do list"
108
+ remove_task:
109
+ utterances:
110
+ - "I want to remove items from my to do list"
111
+
112
+ flow_planner:
113
+ create_flow_plan:
114
+ utterances:
115
+ - "Create a flow plan for tomorrow"
requirements.txt CHANGED
@@ -7,6 +7,7 @@ qdrant-client==1.11.3
7
  pandas==2.2.3
8
  openpyxl==3.1.5
9
  python-dotenv==1.0.1
 
10
  httpx==0.27.2
11
  scikit-learn>=1.5.0
12
  plotly>=5.24.0
 
7
  pandas==2.2.3
8
  openpyxl==3.1.5
9
  python-dotenv==1.0.1
10
+ pyyaml>=6.0
11
  httpx==0.27.2
12
  scikit-learn>=1.5.0
13
  plotly>=5.24.0