codeBOKER commited on
Commit
bb9b63f
ยท
1 Parent(s): 96f3b64

feat: LLM-driven driver message cleanup for phone numbers and names

Browse files

- Add driver_message parameter to add_trip_by_driver tool schema
- Update system_driver.md prompt to instruct LLM to clean messages
(remove phone numbers, surrounding text, and driver names)
- Update handler to use LLM-provided driver_message
- Add app/utils/phone.py with regex-based phone number detection
- Add tests/test_phone.py with 25 test cases
- Add test_add_trip_by_driver_uses_llm_cleaned_message test

app/ai/tool_schemas.py CHANGED
@@ -243,6 +243,14 @@ _ADD_TRIP_BY_DRIVER = {
243
  "minimum": 0,
244
  "description": "Optional trip price.",
245
  },
 
 
 
 
 
 
 
 
246
  },
247
  "required": ["departure", "destination", "departure_date", "departure_time"],
248
  "additionalProperties": False,
 
243
  "minimum": 0,
244
  "description": "Optional trip price.",
245
  },
246
+ "driver_message": {
247
+ "type": "string",
248
+ "description": (
249
+ "Optional cleaned version of the driver's original message. "
250
+ "Remove phone numbers, personal info, and driver name. "
251
+ "Keep emojis and decorations. Only set when >2 emojis."
252
+ ),
253
+ },
254
  },
255
  "required": ["departure", "destination", "departure_date", "departure_time"],
256
  "additionalProperties": False,
app/tools/handlers.py CHANGED
@@ -587,7 +587,8 @@ class FalsaToolHandlers:
587
  if self.current_message:
588
  raw_msg = self.current_message.get("message") or ""
589
  if count_emojis(raw_msg) > 2:
590
- driver_message = raw_msg
 
591
  use_driver_message = True
592
 
593
  trip = await self.repository.create_driver_trip(
 
587
  if self.current_message:
588
  raw_msg = self.current_message.get("message") or ""
589
  if count_emojis(raw_msg) > 2:
590
+ llm_message = _optional_string(arguments.get("driver_message"))
591
+ driver_message = llm_message or raw_msg
592
  use_driver_message = True
593
 
594
  trip = await self.repository.create_driver_trip(
app/utils/phone.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+
3
+ _PHONE_RE = re.compile(
4
+ r"(?:\+|00)\d[\d\s\-\.\(\)]{6,18}\d"
5
+ )
6
+
7
+ _LOCAL_PHONE_RE = re.compile(
8
+ r"(?<!\d)"
9
+ r"(?:\d[\d\s\-\.\(\)]{7,18}\d)"
10
+ r"(?!\d)"
11
+ )
12
+
13
+
14
+ def count_digits(text: str) -> int:
15
+ return sum(1 for c in text if c.isdigit())
16
+
17
+
18
+ def strip_phone_numbers(text: str, replacement: str = "[ุฑู‚ู… ุงู„ู‡ุงุชู]") -> str:
19
+ if not text:
20
+ return text
21
+
22
+ def _replace(match: re.Match) -> str:
23
+ if count_digits(match.group()) >= 7:
24
+ return replacement
25
+ return match.group()
26
+
27
+ result = _PHONE_RE.sub(_replace, text)
28
+ result = _LOCAL_PHONE_RE.sub(_replace, result)
29
+ return result
prompts/system_driver.md CHANGED
@@ -8,3 +8,11 @@
8
  - For delete/modify: initiate_trip_action (sends numbered list). Driver replies with number.
9
  - delete_trip_by_number(trip_number) / modify_trip_by_number(trip_number, field, value). No trip_id.
10
  - To search/book as traveler: switch_to_passenger (name optional).
 
 
 
 
 
 
 
 
 
8
  - For delete/modify: initiate_trip_action (sends numbered list). Driver replies with number.
9
  - delete_trip_by_number(trip_number) / modify_trip_by_number(trip_number, field, value). No trip_id.
10
  - To search/book as traveler: switch_to_passenger (name optional).
11
+
12
+ Driver message cleanup:
13
+ - When the driver's message has more than 2 emojis, set `driver_message` in add_trip_by_driver.
14
+ - Remove ALL phone numbers AND the surrounding text (e.g. "ู„ู„ุชูˆุงุตู„ ุงู„ุงุชุตุงู„ ุนู„ู‰ ุงู„ุฑู‚ู… ุงู„ุขุชูŠ๐Ÿ“ž:-770026665" โ†’ remove entirely).
15
+ - Remove the driver's name wherever it appears in the message.
16
+ - Keep emojis, decorative elements, route info, dates, times, and seat info.
17
+ - Example: "๐Ÿšุจุต ูู‡ุฏ ู…ุฑูŠุญ ๐Ÿš. ู†ุงุฒู„ ุงู„ุฌู…ุนุฉ ูง ุตุจุงุญุง ๐Ÿ‘ˆุชุฑูŠู… ุงู„ู…ูƒู„ุง๐Ÿ‘‰ ุจู† ุจูƒุฑ ๐Ÿ“ž770026665 ๐Ÿ’ุจู† ุจูƒุฑ๐Ÿ’"
18
+ โ†’ "๐Ÿšุจุต ูู‡ุฏ ู…ุฑูŠุญ ๐Ÿš. ู†ุงุฒู„ ุงู„ุฌู…ุนุฉ ูง ุตุจุงุญุง ๐Ÿ‘ˆุชุฑูŠู… ุงู„ู…ูƒู„ุง๐Ÿ‘‰ ๐Ÿซต๐Ÿป ๐Ÿ“ž ๐Ÿ’"
tests/test_phone.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.utils.phone import count_digits, strip_phone_numbers
2
+
3
+
4
+ class TestCountDigits:
5
+ def test_no_digits(self):
6
+ assert count_digits("hello world") == 0
7
+
8
+ def test_all_digits(self):
9
+ assert count_digits("1234567") == 7
10
+
11
+ def test_mixed(self):
12
+ assert count_digits("abc123def456") == 6
13
+
14
+ def test_empty(self):
15
+ assert count_digits("") == 0
16
+
17
+
18
+ class TestStripPhoneNumbers:
19
+ def test_yemeni_number_with_plus(self):
20
+ result = strip_phone_numbers("ุงุชุตู„ ุนู„ู‰ +967712345678")
21
+ assert result == "ุงุชุตู„ ุนู„ู‰ [ุฑู‚ู… ุงู„ู‡ุงุชู]"
22
+
23
+ def test_yemeni_number_with_00(self):
24
+ result = strip_phone_numbers("ุงุชุตู„ ุนู„ู‰ 00967712345678")
25
+ assert result == "ุงุชุตู„ ุนู„ู‰ [ุฑู‚ู… ุงู„ู‡ุงุชู]"
26
+
27
+ def test_international_with_dashes(self):
28
+ result = strip_phone_numbers("Call +1-234-567-8901")
29
+ assert result == "Call [ุฑู‚ู… ุงู„ู‡ุงุชู]"
30
+
31
+ def test_international_with_spaces(self):
32
+ result = strip_phone_numbers("Call +44 20 7946 0958")
33
+ assert result == "Call [ุฑู‚ู… ุงู„ู‡ุงุชู]"
34
+
35
+ def test_international_with_dots(self):
36
+ result = strip_phone_numbers("Call +49.30.1234567")
37
+ assert result == "Call [ุฑู‚ู… ุงู„ู‡ุงุชู]"
38
+
39
+ def test_international_with_parentheses(self):
40
+ result = strip_phone_numbers("Call +1 (234) 567-8901")
41
+ assert result == "Call [ุฑู‚ู… ุงู„ู‡ุงุชู]"
42
+
43
+ def test_multiple_numbers(self):
44
+ result = strip_phone_numbers("Call +967712345678 or +967776543210")
45
+ assert result == "Call [ุฑู‚ู… ุงู„ู‡ุงุชู] or [ุฑู‚ู… ุงู„ู‡ุงุชู]"
46
+
47
+ def test_no_phone_numbers(self):
48
+ text = "ุฑุญู„ุฉ ู…ู† ุตู†ุนุงุก ุฅู„ู‰ ุชุนุฒ"
49
+ result = strip_phone_numbers(text)
50
+ assert result == text
51
+
52
+ def test_short_number_not_matched(self):
53
+ result = strip_phone_numbers("Room 123")
54
+ assert result == "Room 123"
55
+
56
+ def test_empty_string(self):
57
+ assert strip_phone_numbers("") == ""
58
+
59
+ def test_none_returns_none(self):
60
+ assert strip_phone_numbers(None) is None
61
+
62
+ def test_custom_replacement(self):
63
+ result = strip_phone_numbers("Call +967712345678", replacement="***")
64
+ assert result == "Call ***"
65
+
66
+ def test_number_at_start(self):
67
+ result = strip_phone_numbers("+967712345678 ุงุชุตู„")
68
+ assert result == "[ุฑู‚ู… ุงู„ู‡ุงุชู] ุงุชุตู„"
69
+
70
+ def test_number_at_end(self):
71
+ result = strip_phone_numbers("ุงุชุตู„ +967712345678")
72
+ assert result == "ุงุชุตู„ [ุฑู‚ู… ุงู„ู‡ุงุชู]"
73
+
74
+ def test_number_in_middle_of_text(self):
75
+ result = strip_phone_numbers("ุงุชุตู„ ุนู„ู‰ +967712345678 ู„ู„ุญุฌุฒ")
76
+ assert result == "ุงุชุตู„ ุนู„ู‰ [ุฑู‚ู… ุงู„ู‡ุงุชู] ู„ู„ุญุฌุฒ"
77
+
78
+ def test_yemeni_local_format(self):
79
+ result = strip_phone_numbers("0712345678")
80
+ assert result == "[ุฑู‚ู… ุงู„ู‡ุงุชู]"
81
+
82
+ def test_number_with_country_code_no_plus(self):
83
+ result = strip_phone_numbers("967712345678")
84
+ assert result == "[ุฑู‚ู… ุงู„ู‡ุงุชู]"
85
+
86
+ def test_long_number_with_separators(self):
87
+ result = strip_phone_numbers("+1 (555) 123-4567 ext 890")
88
+ assert result == "[ุฑู‚ู… ุงู„ู‡ุงุชู] ext 890"
89
+
90
+ def test_local_yemeni_9_digit(self):
91
+ result = strip_phone_numbers("770026665")
92
+ assert result == "[ุฑู‚ู… ุงู„ู‡ุงุชู]"
93
+
94
+ def test_local_yemeni_in_message(self):
95
+ result = strip_phone_numbers("ู„ู„ุชูˆุงุตู„ ุนู„ู‰ ุงู„ุฑู‚ู… 770026665")
96
+ assert result == "ู„ู„ุชูˆุงุตู„ ุนู„ู‰ ุงู„ุฑู‚ู… [ุฑู‚ู… ุงู„ู‡ุงุชู]"
97
+
98
+ def test_local_yemeni_with_dash_prefix(self):
99
+ result = strip_phone_numbers("ุงู„ุฑู‚ู…:-770026665")
100
+ assert result == "ุงู„ุฑู‚ู…:-[ุฑู‚ู… ุงู„ู‡ุงุชู]"
tests/test_tools.py CHANGED
@@ -625,6 +625,43 @@ async def test_add_trip_by_driver_stores_driver_message_with_emojis():
625
  assert created_trip["driver_message"] == "ุจุงุต ู…ู† ุนุฏู† ุงู„ู‰ ุงู„ู…ูƒู„ุง ๐Ÿ˜Š๐Ÿ˜Š๐Ÿ˜Š"
626
 
627
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
628
  @pytest.mark.asyncio
629
  async def test_add_trip_by_driver_no_driver_message_with_few_emojis():
630
  repository = FakeRepository()
 
625
  assert created_trip["driver_message"] == "ุจุงุต ู…ู† ุนุฏู† ุงู„ู‰ ุงู„ู…ูƒู„ุง ๐Ÿ˜Š๐Ÿ˜Š๐Ÿ˜Š"
626
 
627
 
628
+ @pytest.mark.asyncio
629
+ async def test_add_trip_by_driver_uses_llm_cleaned_message():
630
+ repository = FakeRepository()
631
+ repository.drivers_by_remote_jid["967700000010"] = {
632
+ "id": "driver-1",
633
+ "name": "Ali",
634
+ "remoteJid": "967700000010",
635
+ }
636
+ repository.driver_cars_by_driver["driver-1"] = [
637
+ {"id": "car-1", "car_type": "SUV", "seat_count": 4},
638
+ ]
639
+ raw_msg = "๐Ÿšุจุต ูู‡ุฏ ู…ุฑูŠุญ ๐Ÿš. ู†ุงุฒู„ ุงู„ุฌู…ุนุฉ ูง ุตุจุงุญุง ๐Ÿ‘ˆุชุฑูŠู… ุงู„ู…ูƒู„ุง๐Ÿ‘‰ ุจู† ุจูƒุฑ ๐Ÿ“ž770026665 ๐Ÿ’ุจู† ุจูƒุฑ๐Ÿ’"
640
+ cleaned_msg = "๐Ÿšุจุต ูู‡ุฏ ู…ุฑูŠุญ ๐Ÿš. ู†ุงุฒู„ ุงู„ุฌู…ุนุฉ ูง ุตุจุงุญุง ๐Ÿ‘ˆุชุฑูŠู… ุงู„ู…ูƒู„ุง๐Ÿ‘‰ ๐Ÿ’"
641
+ current_message = {"message": raw_msg}
642
+ handlers = make_handlers(repository=repository, sender_phone="967700000010")
643
+ handlers.current_message = current_message
644
+
645
+ result = await handlers.add_trip_by_driver(
646
+ {
647
+ "departure": "ุชุฑูŠู…",
648
+ "destination": "ุงู„ู…ูƒู„ุง",
649
+ "departure_date": "2026-06-10",
650
+ "departure_time": "morning",
651
+ "vehicle_type": "SUV",
652
+ "available_seats": 2,
653
+ "total_seats": 4,
654
+ "price": 50,
655
+ "driver_message": cleaned_msg,
656
+ }
657
+ )
658
+
659
+ assert result.ok is True
660
+ created_trip = repository.created_trips[-1]
661
+ assert created_trip["use_driver_message"] is True
662
+ assert created_trip["driver_message"] == cleaned_msg
663
+
664
+
665
  @pytest.mark.asyncio
666
  async def test_add_trip_by_driver_no_driver_message_with_few_emojis():
667
  repository = FakeRepository()