codeBOKER commited on
Commit
6ab175e
·
1 Parent(s): 1b62af7

compress tool descriptions and deduplicate error string

Browse files
app/ai/tool_schemas.py CHANGED
@@ -33,57 +33,37 @@ _SEARCH_TRIPS = {
33
  "function": {
34
  "name": "search_trips",
35
  "description": (
36
- "Search active car or bus trips. "
37
- "Use when the customer asks for travel options. "
38
- "You can search with only departure or only destination"
39
- " — the other will match any location. "
40
- "Matching trips are sent as WhatsApp cards and user is prompted to pick one. "
41
- "Do NOT add follow-up text — response is handled automatically."
42
  ),
43
  "parameters": {
44
  "type": "object",
45
  "properties": {
46
  "departure": {
47
  "type": "string",
48
- "description": (
49
- "Departure city or area in Arabic. At least one of departure or"
50
- " destination is required — the other can be omitted to match anywhere."
51
- ),
52
  },
53
  "destination": {
54
  "type": "string",
55
- "description": (
56
- "Destination city or area in Arabic. At least one of departure or"
57
- " destination is required — the other can be omitted to match anywhere."
58
- ),
59
  },
60
  "travel_datetime": {
61
  "type": "string",
62
- "description": (
63
- "Optional requested date/time in ISO format. Times are interpreted "
64
- "in Asia/Aden and normalized to morning, noon, or night."
65
- ),
66
  },
67
  "travel_date": {
68
  "type": "string",
69
- "description": (
70
- "Optional requested trip date as YYYY-MM-DD in Asia/Aden. Use English digits."
71
- ),
72
  },
73
  "travel_time": {
74
  "type": "string",
75
  "enum": ["صباح", "ظهر", "ليل"],
76
- "description": (
77
- "Optional requested trip time bucket in Arabic. If the customer also provides "
78
- "an exact time, provide the matching Arabic bucket: صباح before 12:00, ظهر from 12:00-17:59, ليل from 18:00."
79
- ),
80
  },
81
  "travel_time_exact": {
82
  "type": "string",
83
- "description": (
84
- "Optional exact requested time as HH:MM in Asia/Aden, for example "
85
- "06:00. Use English digits and colon formatting. Also provide the corresponding Arabic travel_time bucket when possible."
86
- ),
87
  },
88
  "seats": {
89
  "type": "integer",
@@ -96,10 +76,7 @@ _SEARCH_TRIPS = {
96
  },
97
  "vector_query_text": {
98
  "type": "string",
99
- "description": (
100
- "Optional natural-language semantic search text. "
101
- "Automatically constructed from the other fields if not provided."
102
- ),
103
  },
104
  },
105
  "required": [],
@@ -113,15 +90,13 @@ _CREATE_BOOKING_LEAD = {
113
  "function": {
114
  "name": "create_booking_lead",
115
  "description": (
116
- "Create a pending booking lead (1 seat by default) and notify the driver. "
117
- "Call this immediately when the user replies to a trip card trip_id is "
118
- "auto-detected and requested_seats defaults to 1. "
119
- "Does not reserve seats or confirm payment."
120
  ),
121
  "parameters": {
122
  "type": "object",
123
  "properties": {
124
- "trip_id": {"type": "string", "description": "Selected trip ID. Optional if the customer replied to a trip card message."},
125
  "requested_seats": {
126
  "type": "integer",
127
  "minimum": 1,
@@ -142,10 +117,7 @@ _CREATE_DRIVER_ACCOUNT = {
142
  "type": "function",
143
  "function": {
144
  "name": "create_driver_account",
145
- "description": (
146
- "Register the current WhatsApp sender as a FALSA driver. "
147
- "Phone number is taken automatically from the chat session."
148
- ),
149
  "parameters": {
150
  "type": "object",
151
  "properties": {
@@ -164,10 +136,7 @@ _CHECK_DRIVER_INFO = {
164
  "type": "function",
165
  "function": {
166
  "name": "check_driver_info",
167
- "description": (
168
- "Retrieve the registered driver's account details, registered vehicles, and active trip summary. "
169
- "Use when a driver asks about their own profile or vehicle status."
170
- ),
171
  "parameters": {
172
  "type": "object",
173
  "properties": {},
@@ -181,10 +150,7 @@ _CHECK_DRIVER_TRIPS = {
181
  "type": "function",
182
  "function": {
183
  "name": "check_driver_trips",
184
- "description": (
185
- "List all upcoming active trips for the registered driver. "
186
- "A trip is considered upcoming if it has status active and has not yet departed."
187
- ),
188
  "parameters": {
189
  "type": "object",
190
  "properties": {},
@@ -198,10 +164,7 @@ _ADD_DRIVER_CAR = {
198
  "type": "function",
199
  "function": {
200
  "name": "add_driver_car",
201
- "description": (
202
- "Register a new vehicle for the current WhatsApp driver. "
203
- "Only the car name is required; plate number and seat count are optional."
204
- ),
205
  "parameters": {
206
  "type": "object",
207
  "properties": {
@@ -230,9 +193,8 @@ _ADD_TRIP_BY_DRIVER = {
230
  "function": {
231
  "name": "add_trip_by_driver",
232
  "description": (
233
- "Create an active trip for the registered driver on this WhatsApp number. "
234
- "Phone is taken from the chat session. Optional car, seat, and price fields "
235
- "default from the driver's most recent trip or sole registered vehicle."
236
  ),
237
  "parameters": {
238
  "type": "object",
@@ -251,17 +213,11 @@ _ADD_TRIP_BY_DRIVER = {
251
  },
252
  "departure_time": {
253
  "type": "string",
254
- "description": (
255
- "Trip time bucket: morning, noon, night, or Arabic "
256
- "صباح / ظهر / ليل."
257
- ),
258
  },
259
  "vehicle_type": {
260
  "type": "string",
261
- "description": (
262
- "Optional vehicle name or type in Arabic, for example "
263
- "سيارة or باص. Matched against the driver's registered cars."
264
- ),
265
  },
266
  "available_seats": {
267
  "type": "integer",
@@ -289,10 +245,7 @@ _INITIATE_TRIP_ACTION = {
289
  "type": "function",
290
  "function": {
291
  "name": "initiate_trip_action",
292
- "description": (
293
- "Start delete or modify flow for one of the driver's upcoming trips. "
294
- "The system sends a numbered list of active trips to the driver in plain text."
295
- ),
296
  "parameters": {
297
  "type": "object",
298
  "properties": {
@@ -303,15 +256,12 @@ _INITIATE_TRIP_ACTION = {
303
  },
304
  "travel_date": {
305
  "type": "string",
306
- "description": (
307
- "Optional trip date filter as YYYY-MM-DD in Asia/Aden when the "
308
- "driver mentioned a specific day such as today or tomorrow."
309
- ),
310
  },
311
  "travel_time": {
312
  "type": "string",
313
  "enum": ["صباح", "ظهر", "ليل"],
314
- "description": "Optional departure time bucket filter in Arabic.",
315
  },
316
  },
317
  "required": ["action_type"],
@@ -324,10 +274,7 @@ _UPDATE_TRIP_FIELD = {
324
  "type": "function",
325
  "function": {
326
  "name": "update_trip_field",
327
- "description": (
328
- "Update one field on the trip the driver selected for editing. "
329
- "trip_id is resolved automatically from the active edit session."
330
- ),
331
  "parameters": {
332
  "type": "object",
333
  "properties": {
@@ -348,11 +295,7 @@ _UPDATE_TRIP_FIELD = {
348
  },
349
  "value": {
350
  "type": "string",
351
- "description": (
352
- "New value for the field. Use YYYY-MM-DD for dates, HH:MM for "
353
- "pickup_time, Arabic city names for route fields, and English "
354
- "digits for seats and price."
355
- ),
356
  },
357
  },
358
  "required": ["field", "value"],
@@ -365,10 +308,7 @@ _DELETE_TRIP_BY_NUMBER = {
365
  "type": "function",
366
  "function": {
367
  "name": "delete_trip_by_number",
368
- "description": (
369
- "Cancel an upcoming active trip using the driver-visible trip number. "
370
- "Trip numbers are assigned in ascending order from oldest to newest."
371
- ),
372
  "parameters": {
373
  "type": "object",
374
  "properties": {
@@ -388,10 +328,7 @@ _MODIFY_TRIP_BY_NUMBER = {
388
  "type": "function",
389
  "function": {
390
  "name": "modify_trip_by_number",
391
- "description": (
392
- "Modify one field of an active driver trip using the driver-visible trip number. "
393
- "Trip numbers are assigned in ascending order from oldest to newest."
394
- ),
395
  "parameters": {
396
  "type": "object",
397
  "properties": {
@@ -417,11 +354,7 @@ _MODIFY_TRIP_BY_NUMBER = {
417
  },
418
  "value": {
419
  "type": "string",
420
- "description": (
421
- "New value for the field. Use YYYY-MM-DD for dates, HH:MM for "
422
- "pickup_time, Arabic city names for route fields, and English "
423
- "digits for seats and price."
424
- ),
425
  },
426
  },
427
  "required": ["trip_number", "field", "value"],
@@ -434,10 +367,7 @@ _SWITCH_TO_DRIVER = {
434
  "type": "function",
435
  "function": {
436
  "name": "switch_to_driver",
437
- "description": (
438
- "Switch this sender to driver mode. "
439
- "Requires an existing driver account; use create_driver_account first if needed."
440
- ),
441
  "parameters": {
442
  "type": "object",
443
  "properties": {},
@@ -451,9 +381,7 @@ _SWITCH_TO_PASSENGER = {
451
  "type": "function",
452
  "function": {
453
  "name": "switch_to_passenger",
454
- "description": (
455
- "Switch this sender to passenger mode so they can search and book trips."
456
- ),
457
  "parameters": {
458
  "type": "object",
459
  "properties": {
 
33
  "function": {
34
  "name": "search_trips",
35
  "description": (
36
+ "Search active trips by departure/destination (either can be omitted). "
37
+ "Results sent as WhatsApp cards automatically. "
38
+ "Times Asia/Aden. Buckets: صباح before 12, ظهر 12-17:59, ليل 18+."
 
 
 
39
  ),
40
  "parameters": {
41
  "type": "object",
42
  "properties": {
43
  "departure": {
44
  "type": "string",
45
+ "description": "Departure city/area in Arabic. Required if destination omitted.",
 
 
 
46
  },
47
  "destination": {
48
  "type": "string",
49
+ "description": "Destination city/area in Arabic. Required if departure omitted.",
 
 
 
50
  },
51
  "travel_datetime": {
52
  "type": "string",
53
+ "description": "Optional ISO date+time in Asia/Aden; normalized to the matching time bucket.",
 
 
 
54
  },
55
  "travel_date": {
56
  "type": "string",
57
+ "description": "Optional trip date YYYY-MM-DD (Asia/Aden).",
 
 
58
  },
59
  "travel_time": {
60
  "type": "string",
61
  "enum": ["صباح", "ظهر", "ليل"],
62
+ "description": "Optional time bucket (Arabic).",
 
 
 
63
  },
64
  "travel_time_exact": {
65
  "type": "string",
66
+ "description": "Optional exact time HH:MM (Asia/Aden). Also set the corresponding travel_time bucket.",
 
 
 
67
  },
68
  "seats": {
69
  "type": "integer",
 
76
  },
77
  "vector_query_text": {
78
  "type": "string",
79
+ "description": "Optional semantic text; auto-built from other fields.",
 
 
 
80
  },
81
  },
82
  "required": [],
 
90
  "function": {
91
  "name": "create_booking_lead",
92
  "description": (
93
+ "Create pending booking (default 1 seat), notify driver. "
94
+ "Call on trip-card reply. Does not reserve or confirm."
 
 
95
  ),
96
  "parameters": {
97
  "type": "object",
98
  "properties": {
99
+ "trip_id": {"type": "string", "description": "Trip ID. Optional if replying to a trip card."},
100
  "requested_seats": {
101
  "type": "integer",
102
  "minimum": 1,
 
117
  "type": "function",
118
  "function": {
119
  "name": "create_driver_account",
120
+ "description": "Register this WhatsApp sender as a FALSA driver. Phone from chat session.",
 
 
 
121
  "parameters": {
122
  "type": "object",
123
  "properties": {
 
136
  "type": "function",
137
  "function": {
138
  "name": "check_driver_info",
139
+ "description": "Get driver account, vehicles & active trip summary.",
 
 
 
140
  "parameters": {
141
  "type": "object",
142
  "properties": {},
 
150
  "type": "function",
151
  "function": {
152
  "name": "check_driver_trips",
153
+ "description": "List upcoming active trips (status=active, not departed).",
 
 
 
154
  "parameters": {
155
  "type": "object",
156
  "properties": {},
 
164
  "type": "function",
165
  "function": {
166
  "name": "add_driver_car",
167
+ "description": "Register vehicle for current driver. Only name required.",
 
 
 
168
  "parameters": {
169
  "type": "object",
170
  "properties": {
 
193
  "function": {
194
  "name": "add_trip_by_driver",
195
  "description": (
196
+ "Create a trip for the registered driver. Phone from chat session. "
197
+ "Optional car/seats/price default from latest trip or sole registered vehicle."
 
198
  ),
199
  "parameters": {
200
  "type": "object",
 
213
  },
214
  "departure_time": {
215
  "type": "string",
216
+ "description": "Time bucket: morning/noon/night or صباح/ظهر/ليل.",
 
 
 
217
  },
218
  "vehicle_type": {
219
  "type": "string",
220
+ "description": "Optional vehicle name/type in Arabic (e.g. سيارة/باص).",
 
 
 
221
  },
222
  "available_seats": {
223
  "type": "integer",
 
245
  "type": "function",
246
  "function": {
247
  "name": "initiate_trip_action",
248
+ "description": "Start delete/modify flow. Sends numbered trip list.",
 
 
 
249
  "parameters": {
250
  "type": "object",
251
  "properties": {
 
256
  },
257
  "travel_date": {
258
  "type": "string",
259
+ "description": "Optional trip date filter YYYY-MM-DD (Asia/Aden).",
 
 
 
260
  },
261
  "travel_time": {
262
  "type": "string",
263
  "enum": ["صباح", "ظهر", "ليل"],
264
+ "description": "Departure time bucket in Arabic.",
265
  },
266
  },
267
  "required": ["action_type"],
 
274
  "type": "function",
275
  "function": {
276
  "name": "update_trip_field",
277
+ "description": "Update one field on the trip the driver is editing. trip_id from active session.",
 
 
 
278
  "parameters": {
279
  "type": "object",
280
  "properties": {
 
295
  },
296
  "value": {
297
  "type": "string",
298
+ "description": "New value. Dates YYYY-MM-DD, time HH:MM, routes Arabic, seats/price digits.",
 
 
 
 
299
  },
300
  },
301
  "required": ["field", "value"],
 
308
  "type": "function",
309
  "function": {
310
  "name": "delete_trip_by_number",
311
+ "description": "Cancel a trip by driver-visible number (oldest=1).",
 
 
 
312
  "parameters": {
313
  "type": "object",
314
  "properties": {
 
328
  "type": "function",
329
  "function": {
330
  "name": "modify_trip_by_number",
331
+ "description": "Modify a trip field by driver-visible number (oldest=1).",
 
 
 
332
  "parameters": {
333
  "type": "object",
334
  "properties": {
 
354
  },
355
  "value": {
356
  "type": "string",
357
+ "description": "New value. Dates YYYY-MM-DD, time HH:MM, routes Arabic, seats/price digits.",
 
 
 
 
358
  },
359
  },
360
  "required": ["trip_number", "field", "value"],
 
367
  "type": "function",
368
  "function": {
369
  "name": "switch_to_driver",
370
+ "description": "Switch sender to driver mode. Requires existing driver account.",
 
 
 
371
  "parameters": {
372
  "type": "object",
373
  "properties": {},
 
381
  "type": "function",
382
  "function": {
383
  "name": "switch_to_passenger",
384
+ "description": "Switch sender to passenger mode to search/book trips.",
 
 
385
  "parameters": {
386
  "type": "object",
387
  "properties": {
app/services/conversation_service.py CHANGED
@@ -3,7 +3,7 @@ from pathlib import Path
3
  from typing import Any
4
 
5
  from app.ai.orchestrator import AIOrchestrator
6
- from app.ai.tool_schemas import get_tool_schemas
7
  from app.config import Settings
8
  from app.database.supabase import SupabaseRepository
9
  from app.models.domain import UserMode, WhatsAppInboundMessage
@@ -23,35 +23,6 @@ _PROMPT_PATHS: dict[UserMode, Path] = {
23
  "passenger": Path("prompts/system_passenger.md"),
24
  }
25
 
26
- _TOOLS_BY_MODE: dict[UserMode, list[str]] = {
27
- "new_user": [
28
- "about_falsa",
29
- "create_driver_account",
30
- "switch_to_driver",
31
- "switch_to_passenger",
32
- ],
33
- "driver": [
34
- "about_falsa",
35
- "check_driver_info",
36
- "check_driver_trips",
37
- "add_driver_car",
38
- "add_trip_by_driver",
39
- "delete_trip_by_number",
40
- "modify_trip_by_number",
41
- "initiate_trip_action",
42
- "update_trip_field",
43
- "switch_to_passenger",
44
- ],
45
- "passenger": [
46
- "about_falsa",
47
- "search_trips",
48
- "create_booking_lead",
49
- "create_driver_account",
50
- "switch_to_driver",
51
- ],
52
- }
53
-
54
-
55
  class ConversationService:
56
  def __init__(
57
  self,
 
3
  from typing import Any
4
 
5
  from app.ai.orchestrator import AIOrchestrator
6
+ from app.ai.tool_schemas import _TOOLS_BY_MODE, get_tool_schemas
7
  from app.config import Settings
8
  from app.database.supabase import SupabaseRepository
9
  from app.models.domain import UserMode, WhatsAppInboundMessage
 
23
  "passenger": Path("prompts/system_passenger.md"),
24
  }
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  class ConversationService:
27
  def __init__(
28
  self,
app/tools/handlers.py CHANGED
@@ -4,6 +4,11 @@ from typing import Any
4
 
5
  logger = logging.getLogger(__name__)
6
 
 
 
 
 
 
7
  from app.database.supabase import SupabaseRepository
8
  from app.models.domain import ToolResult
9
  from app.services.embedding_service import JinaEmbeddingService
@@ -323,11 +328,8 @@ class FalsaToolHandlers:
323
  return ToolResult(
324
  ok=False,
325
  data={"action": "create_driver_account"},
326
- error=(
327
- "No driver account for this WhatsApp number. "
328
- "Ask the sender to register with create_driver_account first."
329
- ),
330
- )
331
 
332
  customer_info = driver.get("customers", {})
333
  cars = await self.repository.list_driver_cars(str(driver["id"]))
@@ -361,10 +363,7 @@ class FalsaToolHandlers:
361
  return ToolResult(
362
  ok=False,
363
  data={"action": "create_driver_account"},
364
- error=(
365
- "No driver account for this WhatsApp number. "
366
- "Ask the sender to register with create_driver_account first."
367
- ),
368
  )
369
 
370
  trips = await self.repository.list_driver_trips(str(driver["id"]))
@@ -388,11 +387,8 @@ class FalsaToolHandlers:
388
  return ToolResult(
389
  ok=False,
390
  data={"action": "create_driver_account"},
391
- error=(
392
- "No driver account for this WhatsApp number. "
393
- "Ask the sender to register with create_driver_account first."
394
- ),
395
- )
396
 
397
  car_type = _optional_string(arguments.get("name"))
398
  if not car_type:
@@ -427,11 +423,8 @@ class FalsaToolHandlers:
427
  return ToolResult(
428
  ok=False,
429
  data={"action": "create_driver_account"},
430
- error=(
431
- "No driver account for this WhatsApp number. "
432
- "Ask the sender to register with create_driver_account first."
433
- ),
434
- )
435
 
436
  departure = _optional_string(arguments.get("departure"))
437
  destination = _optional_string(arguments.get("destination"))
@@ -601,10 +594,7 @@ class FalsaToolHandlers:
601
  return ToolResult(
602
  ok=False,
603
  data={"action": "create_driver_account"},
604
- error=(
605
- "No driver account for this WhatsApp number. "
606
- "Ask the sender to register with create_driver_account first."
607
- ),
608
  )
609
 
610
  trips = await self.repository.list_driver_trips(str(driver["id"]))
@@ -658,10 +648,7 @@ class FalsaToolHandlers:
658
  return ToolResult(
659
  ok=False,
660
  data={"action": "create_driver_account"},
661
- error=(
662
- "No driver account for this WhatsApp number. "
663
- "Ask the sender to register with create_driver_account first."
664
- ),
665
  )
666
 
667
  trip_number = _optional_int(arguments.get("trip_number"))
@@ -697,10 +684,7 @@ class FalsaToolHandlers:
697
  return ToolResult(
698
  ok=False,
699
  data={"action": "create_driver_account"},
700
- error=(
701
- "No driver account for this WhatsApp number. "
702
- "Ask the sender to register with create_driver_account first."
703
- ),
704
  )
705
 
706
  trip_number = _optional_int(arguments.get("trip_number"))
@@ -754,11 +738,8 @@ class FalsaToolHandlers:
754
  return ToolResult(
755
  ok=False,
756
  data={"action": "create_driver_account"},
757
- error=(
758
- "No driver account for this WhatsApp number. "
759
- "Ask the sender to register with create_driver_account first."
760
- ),
761
- )
762
 
763
  session = await self.repository.get_customer_session(str(self.customer["id"]))
764
  trip_id = _optional_string(session.get("active_edit_trip_id"))
 
4
 
5
  logger = logging.getLogger(__name__)
6
 
7
+ _NO_DRIVER_ERROR = (
8
+ "No driver account for this WhatsApp number. "
9
+ "Ask the sender to register with create_driver_account first."
10
+ )
11
+
12
  from app.database.supabase import SupabaseRepository
13
  from app.models.domain import ToolResult
14
  from app.services.embedding_service import JinaEmbeddingService
 
328
  return ToolResult(
329
  ok=False,
330
  data={"action": "create_driver_account"},
331
+ error=_NO_DRIVER_ERROR,
332
+ )
 
 
 
333
 
334
  customer_info = driver.get("customers", {})
335
  cars = await self.repository.list_driver_cars(str(driver["id"]))
 
363
  return ToolResult(
364
  ok=False,
365
  data={"action": "create_driver_account"},
366
+ error=_NO_DRIVER_ERROR,
 
 
 
367
  )
368
 
369
  trips = await self.repository.list_driver_trips(str(driver["id"]))
 
387
  return ToolResult(
388
  ok=False,
389
  data={"action": "create_driver_account"},
390
+ error=_NO_DRIVER_ERROR,
391
+ )
 
 
 
392
 
393
  car_type = _optional_string(arguments.get("name"))
394
  if not car_type:
 
423
  return ToolResult(
424
  ok=False,
425
  data={"action": "create_driver_account"},
426
+ error=_NO_DRIVER_ERROR,
427
+ )
 
 
 
428
 
429
  departure = _optional_string(arguments.get("departure"))
430
  destination = _optional_string(arguments.get("destination"))
 
594
  return ToolResult(
595
  ok=False,
596
  data={"action": "create_driver_account"},
597
+ error=_NO_DRIVER_ERROR,
 
 
 
598
  )
599
 
600
  trips = await self.repository.list_driver_trips(str(driver["id"]))
 
648
  return ToolResult(
649
  ok=False,
650
  data={"action": "create_driver_account"},
651
+ error=_NO_DRIVER_ERROR,
 
 
 
652
  )
653
 
654
  trip_number = _optional_int(arguments.get("trip_number"))
 
684
  return ToolResult(
685
  ok=False,
686
  data={"action": "create_driver_account"},
687
+ error=_NO_DRIVER_ERROR,
 
 
 
688
  )
689
 
690
  trip_number = _optional_int(arguments.get("trip_number"))
 
738
  return ToolResult(
739
  ok=False,
740
  data={"action": "create_driver_account"},
741
+ error=_NO_DRIVER_ERROR,
742
+ )
 
 
 
743
 
744
  session = await self.repository.get_customer_session(str(self.customer["id"]))
745
  trip_id = _optional_string(session.get("active_edit_trip_id"))