owo-parse 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- owo_parse-0.1.0/LICENSE +21 -0
- owo_parse-0.1.0/PKG-INFO +292 -0
- owo_parse-0.1.0/README.md +235 -0
- owo_parse-0.1.0/pyproject.toml +68 -0
- owo_parse-0.1.0/setup.cfg +4 -0
- owo_parse-0.1.0/src/owo/__init__.py +30 -0
- owo_parse-0.1.0/src/owo/_heuristic.py +439 -0
- owo_parse-0.1.0/src/owo/_prompt.py +43 -0
- owo_parse-0.1.0/src/owo/eval/__init__.py +1 -0
- owo_parse-0.1.0/src/owo/eval/__main__.py +247 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/balance_en.yaml +26 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/balance_ha.yaml +19 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/balance_ig.yaml +19 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/balance_pcm.yaml +29 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/balance_yo.yaml +19 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/bill_pay.yaml +49 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/buy_airtime.yaml +40 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/buy_data.yaml +38 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/crypto_sell.yaml +35 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/edge_cases.yaml +95 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/fallback_unknown.yaml +7 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/transfer_en_extended.yaml +47 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/transfer_en_missing_amount.yaml +10 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/transfer_en_sample.yaml +14 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/transfer_ha.yaml +32 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/transfer_ig.yaml +32 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/transfer_pcm.yaml +55 -0
- owo_parse-0.1.0/src/owo/eval/fixtures/transfer_yo.yaml +32 -0
- owo_parse-0.1.0/src/owo/providers/__init__.py +21 -0
- owo_parse-0.1.0/src/owo/providers/anthropic.py +65 -0
- owo_parse-0.1.0/src/owo/providers/openai.py +67 -0
- owo_parse-0.1.0/src/owo/providers/openrouter.py +73 -0
- owo_parse-0.1.0/src/owo/py.typed +0 -0
- owo_parse-0.1.0/src/owo/result.py +3 -0
- owo_parse-0.1.0/src/owo/schema.py +39 -0
- owo_parse-0.1.0/src/owo_parse.egg-info/PKG-INFO +292 -0
- owo_parse-0.1.0/src/owo_parse.egg-info/SOURCES.txt +40 -0
- owo_parse-0.1.0/src/owo_parse.egg-info/dependency_links.txt +1 -0
- owo_parse-0.1.0/src/owo_parse.egg-info/requires.txt +16 -0
- owo_parse-0.1.0/src/owo_parse.egg-info/top_level.txt +1 -0
- owo_parse-0.1.0/tests/test_parse.py +304 -0
- owo_parse-0.1.0/tests/test_providers.py +260 -0
owo_parse-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 owo contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
owo_parse-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: owo-parse
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Nigerian-language financial intent parser
|
|
5
|
+
Author: owo contributors
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 owo contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/LPMatrix/owo
|
|
29
|
+
Project-URL: Repository, https://github.com/LPMatrix/owo
|
|
30
|
+
Project-URL: Issues, https://github.com/LPMatrix/owo/issues
|
|
31
|
+
Project-URL: Changelog, https://github.com/LPMatrix/owo/blob/main/CHANGELOG.md
|
|
32
|
+
Keywords: nlp,fintech,nigeria,pidgin,yoruba,hausa,igbo,intent-parsing
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
41
|
+
Classifier: Typing :: Typed
|
|
42
|
+
Requires-Python: >=3.10
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
License-File: LICENSE
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
47
|
+
Requires-Dist: ruff>=0.8; extra == "dev"
|
|
48
|
+
Provides-Extra: eval
|
|
49
|
+
Requires-Dist: pyyaml>=6; extra == "eval"
|
|
50
|
+
Provides-Extra: anthropic
|
|
51
|
+
Requires-Dist: anthropic>=0.40; extra == "anthropic"
|
|
52
|
+
Provides-Extra: openai
|
|
53
|
+
Requires-Dist: openai>=1.0; extra == "openai"
|
|
54
|
+
Provides-Extra: openrouter
|
|
55
|
+
Requires-Dist: openai>=1.0; extra == "openrouter"
|
|
56
|
+
Dynamic: license-file
|
|
57
|
+
|
|
58
|
+
# owo
|
|
59
|
+
|
|
60
|
+
**Nigerian-language financial intent parser.**
|
|
61
|
+
|
|
62
|
+
`owo` takes a free-form financial instruction in English, Pidgin, Yoruba, Hausa, or Igbo — and returns structured JSON that any payment backend can consume.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from owo import parse
|
|
66
|
+
|
|
67
|
+
result = parse("Send 20k to Mama")
|
|
68
|
+
|
|
69
|
+
# OwoResult(intent='transfer', amount=20000.0, currency='NGN',
|
|
70
|
+
# recipient='Mama', confidence=0.85, flags=[], ...)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Why owo?
|
|
76
|
+
|
|
77
|
+
Nigerian fintech products that want a conversational layer have to solve the same hard problem: users don't speak in structured commands. They say *"abeg send 5k to Chidi"*, or *"jẹ kí n san owo ina mi"*, or *"biya wutar lantarki"*. Most NLU libraries weren't built for this. `owo` was.
|
|
78
|
+
|
|
79
|
+
It handles:
|
|
80
|
+
|
|
81
|
+
- **Code-switching** — mid-sentence language mixing ("Send am 5k abeg, GTBank")
|
|
82
|
+
- **Naija numerics** — `5k`, `2 bags`, `half a milli`
|
|
83
|
+
- **Intent ambiguity** — flags underspecified fields rather than guessing
|
|
84
|
+
- **Backend agnosticism** — plug in your own LLM provider
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Installation
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install owo-parse
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Requires Python 3.10+.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Quickstart
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from owo import parse
|
|
102
|
+
|
|
103
|
+
# English
|
|
104
|
+
parse("Buy 2GB data for 08012345678 on MTN")
|
|
105
|
+
|
|
106
|
+
# Pidgin
|
|
107
|
+
parse("Abeg top up my light, meter number 4512345678")
|
|
108
|
+
|
|
109
|
+
# Yoruba
|
|
110
|
+
parse("Jẹ kí n san ₦5,000 fún DSTV mi")
|
|
111
|
+
|
|
112
|
+
# Hausa
|
|
113
|
+
parse("Aika dubu goma zuwa ga Ahmad")
|
|
114
|
+
|
|
115
|
+
# Igbo
|
|
116
|
+
parse("Zipụ ego nde ise nye Emeka")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Every call returns an `OwoResult`:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
@dataclass
|
|
123
|
+
class OwoResult:
|
|
124
|
+
intent: str # transfer | bill_pay | buy_airtime | buy_data | crypto_sell | balance_check | unknown
|
|
125
|
+
amount: float | None
|
|
126
|
+
currency: str # always "NGN" for now
|
|
127
|
+
recipient: str | None
|
|
128
|
+
account_number: str | None
|
|
129
|
+
bank: str | None
|
|
130
|
+
service: str | None # MTN | DSTV | EKEDC | ...
|
|
131
|
+
language_detected: str # en | pcm | yo | ha | ig
|
|
132
|
+
confidence: float # 0.0 – 1.0
|
|
133
|
+
flags: list[str] # ["missing_amount", "ambiguous_recipient", ...]
|
|
134
|
+
raw: dict # parser metadata for debugging
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Configuration
|
|
140
|
+
|
|
141
|
+
By default, `parse()` runs an **offline heuristic** covering common transfer and
|
|
142
|
+
balance patterns across all five supported languages (English, Pidgin, Yoruba,
|
|
143
|
+
Hausa, and Igbo). No API key needed. Inputs that fall outside the heuristic's
|
|
144
|
+
rule set return `intent: "unknown"` with `needs_llm_provider` in `flags` — pass
|
|
145
|
+
a provider to handle those cases.
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
# Offline — works for common patterns in all five languages
|
|
149
|
+
parse("Abeg send 5k to Chidi, GTBank")
|
|
150
|
+
parse("Aika dubu goma zuwa ga Ahmad")
|
|
151
|
+
parse("Send half a milli to Kemi")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
To handle complex or ambiguous inputs, plug in an LLM provider. Three providers
|
|
155
|
+
ship with the package:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
pip install 'owo-parse[anthropic]' # Anthropic
|
|
159
|
+
pip install 'owo-parse[openai]' # OpenAI
|
|
160
|
+
pip install 'owo-parse[openrouter]' # OpenRouter (access to 200+ models)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from owo import parse
|
|
165
|
+
from owo.providers.anthropic import AnthropicProvider # ANTHROPIC_API_KEY
|
|
166
|
+
from owo.providers.openai import OpenAIProvider # OPENAI_API_KEY
|
|
167
|
+
from owo.providers.openrouter import OpenRouterProvider # OPENROUTER_API_KEY
|
|
168
|
+
|
|
169
|
+
result = parse(
|
|
170
|
+
"Buy 2GB data for 08012345678 on MTN",
|
|
171
|
+
provider=AnthropicProvider(), # or OpenAIProvider() / OpenRouterProvider()
|
|
172
|
+
)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The heuristic always runs first — the provider is only called when the input falls
|
|
176
|
+
outside the rule set (i.e. `needs_llm_provider` is in `result.flags`). This keeps
|
|
177
|
+
costs low for common transfer and balance patterns.
|
|
178
|
+
|
|
179
|
+
Or bring your own by subclassing `BaseProvider`:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from owo import BaseProvider
|
|
183
|
+
|
|
184
|
+
class MyProvider(BaseProvider):
|
|
185
|
+
def complete(self, prompt: str) -> str:
|
|
186
|
+
# Fallback — called when complete_messages() is not overridden.
|
|
187
|
+
# Receives the full system + user prompt as a single string.
|
|
188
|
+
...
|
|
189
|
+
|
|
190
|
+
def complete_messages(self, user_text: str) -> str:
|
|
191
|
+
# Preferred override — gives you the user text directly so you can
|
|
192
|
+
# pass the system prompt via your SDK's native system-message field.
|
|
193
|
+
response = my_llm_client.chat(
|
|
194
|
+
system=MY_SYSTEM_PROMPT, # use owo._prompt.SYSTEM_PROMPT
|
|
195
|
+
user=user_text,
|
|
196
|
+
)
|
|
197
|
+
return response.text
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`SYSTEM_PROMPT` from `owo._prompt` contains the full instruction block with few-shot examples across all five languages — use it as-is or extend it.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Handling ambiguity
|
|
205
|
+
|
|
206
|
+
`owo` never silently fills in missing fields. If it can't determine the amount, it says so:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
result = parse("Send money to Tunde")
|
|
210
|
+
|
|
211
|
+
result.amount # None
|
|
212
|
+
result.flags # ["missing_amount"]
|
|
213
|
+
result.confidence # 0.61
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Use `confidence` and `flags` to decide whether to ask the user for clarification before passing the result downstream.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Supported intents
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
| Intent | Example |
|
|
224
|
+
| --------------- | --------------------------------- |
|
|
225
|
+
| `transfer` | "Send 20k to Mama" |
|
|
226
|
+
| `bill_pay` | "Pay my DSTV, smart card 1234567" |
|
|
227
|
+
| `buy_airtime` | "Recharge 500 naira on Airtel" |
|
|
228
|
+
| `buy_data` | "Buy 5GB MTN data for my line" |
|
|
229
|
+
| `crypto_sell` | "Sell 50 USDT" |
|
|
230
|
+
| `balance_check` | "How much I get?" |
|
|
231
|
+
| `unknown` | Heuristic could not classify; use `flags` (`needs_llm_provider`) or an LLM provider |
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Supported languages
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
| Code | Language |
|
|
240
|
+
| ----- | --------------- |
|
|
241
|
+
| `en` | English |
|
|
242
|
+
| `pcm` | Nigerian Pidgin |
|
|
243
|
+
| `yo` | Yoruba |
|
|
244
|
+
| `ha` | Hausa |
|
|
245
|
+
| `ig` | Igbo |
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
Mixed-language input (code-switching) is handled automatically — `owo` detects the dominant language and resolves cross-language entities.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Running the eval suite
|
|
253
|
+
|
|
254
|
+
`owo` ships with a benchmark suite of curated test fixtures across all five languages:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
pip install owo-parse[eval]
|
|
258
|
+
python -m owo.eval
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Results are printed per-language, per-intent, with a breakdown of field-level accuracy.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Roadmap
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
| Version | Focus |
|
|
269
|
+
| ------- | ----------------------------------------------------- |
|
|
270
|
+
| `v0.1` | Core intent + entity extraction (English + Pidgin) |
|
|
271
|
+
| `v0.2` | Full multilingual support (Yoruba, Hausa, Igbo) |
|
|
272
|
+
| `v0.3` | Confidence scores, ambiguity flags, graceful fallback |
|
|
273
|
+
| `v1.0` | Provider abstraction, eval suite, docs, OSS-ready |
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Contributing
|
|
279
|
+
|
|
280
|
+
Contributions welcome — especially test fixtures in Yoruba, Hausa, and Igbo, which are the hardest to source.
|
|
281
|
+
|
|
282
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the fixture format and how to add a new language normalization map.
|
|
283
|
+
|
|
284
|
+
This project follows the [Contributor Covenant](./CODE_OF_CONDUCT.md). Security
|
|
285
|
+
disclosures: [SECURITY.md](./SECURITY.md). Changes are summarized in
|
|
286
|
+
[CHANGELOG.md](./CHANGELOG.md).
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# owo
|
|
2
|
+
|
|
3
|
+
**Nigerian-language financial intent parser.**
|
|
4
|
+
|
|
5
|
+
`owo` takes a free-form financial instruction in English, Pidgin, Yoruba, Hausa, or Igbo — and returns structured JSON that any payment backend can consume.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from owo import parse
|
|
9
|
+
|
|
10
|
+
result = parse("Send 20k to Mama")
|
|
11
|
+
|
|
12
|
+
# OwoResult(intent='transfer', amount=20000.0, currency='NGN',
|
|
13
|
+
# recipient='Mama', confidence=0.85, flags=[], ...)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Why owo?
|
|
19
|
+
|
|
20
|
+
Nigerian fintech products that want a conversational layer have to solve the same hard problem: users don't speak in structured commands. They say *"abeg send 5k to Chidi"*, or *"jẹ kí n san owo ina mi"*, or *"biya wutar lantarki"*. Most NLU libraries weren't built for this. `owo` was.
|
|
21
|
+
|
|
22
|
+
It handles:
|
|
23
|
+
|
|
24
|
+
- **Code-switching** — mid-sentence language mixing ("Send am 5k abeg, GTBank")
|
|
25
|
+
- **Naija numerics** — `5k`, `2 bags`, `half a milli`
|
|
26
|
+
- **Intent ambiguity** — flags underspecified fields rather than guessing
|
|
27
|
+
- **Backend agnosticism** — plug in your own LLM provider
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install owo-parse
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Requires Python 3.10+.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quickstart
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from owo import parse
|
|
45
|
+
|
|
46
|
+
# English
|
|
47
|
+
parse("Buy 2GB data for 08012345678 on MTN")
|
|
48
|
+
|
|
49
|
+
# Pidgin
|
|
50
|
+
parse("Abeg top up my light, meter number 4512345678")
|
|
51
|
+
|
|
52
|
+
# Yoruba
|
|
53
|
+
parse("Jẹ kí n san ₦5,000 fún DSTV mi")
|
|
54
|
+
|
|
55
|
+
# Hausa
|
|
56
|
+
parse("Aika dubu goma zuwa ga Ahmad")
|
|
57
|
+
|
|
58
|
+
# Igbo
|
|
59
|
+
parse("Zipụ ego nde ise nye Emeka")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Every call returns an `OwoResult`:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
@dataclass
|
|
66
|
+
class OwoResult:
|
|
67
|
+
intent: str # transfer | bill_pay | buy_airtime | buy_data | crypto_sell | balance_check | unknown
|
|
68
|
+
amount: float | None
|
|
69
|
+
currency: str # always "NGN" for now
|
|
70
|
+
recipient: str | None
|
|
71
|
+
account_number: str | None
|
|
72
|
+
bank: str | None
|
|
73
|
+
service: str | None # MTN | DSTV | EKEDC | ...
|
|
74
|
+
language_detected: str # en | pcm | yo | ha | ig
|
|
75
|
+
confidence: float # 0.0 – 1.0
|
|
76
|
+
flags: list[str] # ["missing_amount", "ambiguous_recipient", ...]
|
|
77
|
+
raw: dict # parser metadata for debugging
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
By default, `parse()` runs an **offline heuristic** covering common transfer and
|
|
85
|
+
balance patterns across all five supported languages (English, Pidgin, Yoruba,
|
|
86
|
+
Hausa, and Igbo). No API key needed. Inputs that fall outside the heuristic's
|
|
87
|
+
rule set return `intent: "unknown"` with `needs_llm_provider` in `flags` — pass
|
|
88
|
+
a provider to handle those cases.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# Offline — works for common patterns in all five languages
|
|
92
|
+
parse("Abeg send 5k to Chidi, GTBank")
|
|
93
|
+
parse("Aika dubu goma zuwa ga Ahmad")
|
|
94
|
+
parse("Send half a milli to Kemi")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
To handle complex or ambiguous inputs, plug in an LLM provider. Three providers
|
|
98
|
+
ship with the package:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install 'owo-parse[anthropic]' # Anthropic
|
|
102
|
+
pip install 'owo-parse[openai]' # OpenAI
|
|
103
|
+
pip install 'owo-parse[openrouter]' # OpenRouter (access to 200+ models)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from owo import parse
|
|
108
|
+
from owo.providers.anthropic import AnthropicProvider # ANTHROPIC_API_KEY
|
|
109
|
+
from owo.providers.openai import OpenAIProvider # OPENAI_API_KEY
|
|
110
|
+
from owo.providers.openrouter import OpenRouterProvider # OPENROUTER_API_KEY
|
|
111
|
+
|
|
112
|
+
result = parse(
|
|
113
|
+
"Buy 2GB data for 08012345678 on MTN",
|
|
114
|
+
provider=AnthropicProvider(), # or OpenAIProvider() / OpenRouterProvider()
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The heuristic always runs first — the provider is only called when the input falls
|
|
119
|
+
outside the rule set (i.e. `needs_llm_provider` is in `result.flags`). This keeps
|
|
120
|
+
costs low for common transfer and balance patterns.
|
|
121
|
+
|
|
122
|
+
Or bring your own by subclassing `BaseProvider`:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from owo import BaseProvider
|
|
126
|
+
|
|
127
|
+
class MyProvider(BaseProvider):
|
|
128
|
+
def complete(self, prompt: str) -> str:
|
|
129
|
+
# Fallback — called when complete_messages() is not overridden.
|
|
130
|
+
# Receives the full system + user prompt as a single string.
|
|
131
|
+
...
|
|
132
|
+
|
|
133
|
+
def complete_messages(self, user_text: str) -> str:
|
|
134
|
+
# Preferred override — gives you the user text directly so you can
|
|
135
|
+
# pass the system prompt via your SDK's native system-message field.
|
|
136
|
+
response = my_llm_client.chat(
|
|
137
|
+
system=MY_SYSTEM_PROMPT, # use owo._prompt.SYSTEM_PROMPT
|
|
138
|
+
user=user_text,
|
|
139
|
+
)
|
|
140
|
+
return response.text
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`SYSTEM_PROMPT` from `owo._prompt` contains the full instruction block with few-shot examples across all five languages — use it as-is or extend it.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Handling ambiguity
|
|
148
|
+
|
|
149
|
+
`owo` never silently fills in missing fields. If it can't determine the amount, it says so:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
result = parse("Send money to Tunde")
|
|
153
|
+
|
|
154
|
+
result.amount # None
|
|
155
|
+
result.flags # ["missing_amount"]
|
|
156
|
+
result.confidence # 0.61
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Use `confidence` and `flags` to decide whether to ask the user for clarification before passing the result downstream.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Supported intents
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
| Intent | Example |
|
|
167
|
+
| --------------- | --------------------------------- |
|
|
168
|
+
| `transfer` | "Send 20k to Mama" |
|
|
169
|
+
| `bill_pay` | "Pay my DSTV, smart card 1234567" |
|
|
170
|
+
| `buy_airtime` | "Recharge 500 naira on Airtel" |
|
|
171
|
+
| `buy_data` | "Buy 5GB MTN data for my line" |
|
|
172
|
+
| `crypto_sell` | "Sell 50 USDT" |
|
|
173
|
+
| `balance_check` | "How much I get?" |
|
|
174
|
+
| `unknown` | Heuristic could not classify; use `flags` (`needs_llm_provider`) or an LLM provider |
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Supported languages
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
| Code | Language |
|
|
183
|
+
| ----- | --------------- |
|
|
184
|
+
| `en` | English |
|
|
185
|
+
| `pcm` | Nigerian Pidgin |
|
|
186
|
+
| `yo` | Yoruba |
|
|
187
|
+
| `ha` | Hausa |
|
|
188
|
+
| `ig` | Igbo |
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
Mixed-language input (code-switching) is handled automatically — `owo` detects the dominant language and resolves cross-language entities.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Running the eval suite
|
|
196
|
+
|
|
197
|
+
`owo` ships with a benchmark suite of curated test fixtures across all five languages:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
pip install owo-parse[eval]
|
|
201
|
+
python -m owo.eval
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Results are printed per-language, per-intent, with a breakdown of field-level accuracy.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Roadmap
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
| Version | Focus |
|
|
212
|
+
| ------- | ----------------------------------------------------- |
|
|
213
|
+
| `v0.1` | Core intent + entity extraction (English + Pidgin) |
|
|
214
|
+
| `v0.2` | Full multilingual support (Yoruba, Hausa, Igbo) |
|
|
215
|
+
| `v0.3` | Confidence scores, ambiguity flags, graceful fallback |
|
|
216
|
+
| `v1.0` | Provider abstraction, eval suite, docs, OSS-ready |
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Contributing
|
|
222
|
+
|
|
223
|
+
Contributions welcome — especially test fixtures in Yoruba, Hausa, and Igbo, which are the hardest to source.
|
|
224
|
+
|
|
225
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the fixture format and how to add a new language normalization map.
|
|
226
|
+
|
|
227
|
+
This project follows the [Contributor Covenant](./CODE_OF_CONDUCT.md). Security
|
|
228
|
+
disclosures: [SECURITY.md](./SECURITY.md). Changes are summarized in
|
|
229
|
+
[CHANGELOG.md](./CHANGELOG.md).
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "owo-parse"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Nigerian-language financial intent parser"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "owo contributors" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"nlp",
|
|
15
|
+
"fintech",
|
|
16
|
+
"nigeria",
|
|
17
|
+
"pidgin",
|
|
18
|
+
"yoruba",
|
|
19
|
+
"hausa",
|
|
20
|
+
"igbo",
|
|
21
|
+
"intent-parsing",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Programming Language :: Python :: 3.13",
|
|
31
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
32
|
+
"Typing :: Typed",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
dependencies = []
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = ["pytest>=8", "ruff>=0.8"]
|
|
39
|
+
eval = ["pyyaml>=6"]
|
|
40
|
+
anthropic = ["anthropic>=0.40"]
|
|
41
|
+
openai = ["openai>=1.0"]
|
|
42
|
+
openrouter = ["openai>=1.0"]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/LPMatrix/owo"
|
|
46
|
+
Repository = "https://github.com/LPMatrix/owo"
|
|
47
|
+
Issues = "https://github.com/LPMatrix/owo/issues"
|
|
48
|
+
Changelog = "https://github.com/LPMatrix/owo/blob/main/CHANGELOG.md"
|
|
49
|
+
|
|
50
|
+
[project.scripts]
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.packages.find]
|
|
53
|
+
where = ["src"]
|
|
54
|
+
|
|
55
|
+
[tool.setuptools.package-data]
|
|
56
|
+
owo = ["py.typed", "eval/fixtures/*.yaml"]
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
pythonpath = ["src"]
|
|
61
|
+
|
|
62
|
+
[tool.ruff]
|
|
63
|
+
target-version = "py310"
|
|
64
|
+
src = ["src", "tests"]
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
68
|
+
ignore = ["E501"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from owo._heuristic import heuristic_parse as _heuristic_parse
|
|
4
|
+
from owo._heuristic import result_from_provider_json as _result_from_provider_json
|
|
5
|
+
from owo.providers import BaseProvider
|
|
6
|
+
from owo.schema import Intent, Language, OwoResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def parse(text: str, *, provider: BaseProvider | None = None) -> OwoResult:
|
|
10
|
+
"""
|
|
11
|
+
Parse a free-form financial instruction into structured fields.
|
|
12
|
+
|
|
13
|
+
The heuristic runs first in all cases. When a *provider* is supplied,
|
|
14
|
+
it is called only for inputs the heuristic cannot confidently handle
|
|
15
|
+
(i.e. the result carries a ``needs_llm_provider`` flag).
|
|
16
|
+
"""
|
|
17
|
+
result = _heuristic_parse(text)
|
|
18
|
+
if provider is None or "needs_llm_provider" not in result.flags:
|
|
19
|
+
return result
|
|
20
|
+
out = provider.complete_messages(text)
|
|
21
|
+
return _result_from_provider_json(out, text)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"BaseProvider",
|
|
26
|
+
"Intent",
|
|
27
|
+
"Language",
|
|
28
|
+
"OwoResult",
|
|
29
|
+
"parse",
|
|
30
|
+
]
|