math-ai-agent 0.0.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rubens Gomes
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.
@@ -0,0 +1,373 @@
1
+ Metadata-Version: 2.4
2
+ Name: math-ai-agent
3
+ Version: 0.0.1
4
+ Summary: Math AI Agent that uses LLM + Rubens calculator-mcp.
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Keywords: ai,math,llm,mcp,agentic
8
+ Author: Rubens Gomes
9
+ Author-email: rubens.s.gomes@gmail.com
10
+ Maintainer: Rubens Gomes
11
+ Maintainer-email: rubens.s.gomes@gmail.com
12
+ Requires-Python: >=3.14,<4.0.0
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Dist: colorlog (>=6.12.0,<7.0.0)
16
+ Requires-Dist: cryptography (>=50.0.1,<51.0.0)
17
+ Requires-Dist: fastapi (>=0.141.1,<1.0.0)
18
+ Requires-Dist: fastmcp (>=4.0.9,<5.0.0)
19
+ Requires-Dist: key-value (>=0.1.2,<1.0.0)
20
+ Requires-Dist: mcp (>=2.2.0,<3.0.0)
21
+ Requires-Dist: openai (>=3.19.1,<4.0.0)
22
+ Requires-Dist: py-key-value-aio (>=0.4.6,<1.0.0)
23
+ Requires-Dist: pydantic (>=2.13.5,<3.0.0)
24
+ Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
25
+ Requires-Dist: uvicorn (>=0.53.0,<1.0.0)
26
+ Project-URL: Documentation, https://github.com/rubensgomes/math-ai-agent/blob/main/README.md
27
+ Project-URL: Homepage, https://github.com/rubensgomes/math-ai-agent/
28
+ Project-URL: Repository, https://github.com/rubensgomes/math-ai-agent/
29
+ Description-Content-Type: text/markdown
30
+
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/rubensgomes-org/math-ai-agent/blob/main/LICENSE)
32
+ [![AI Assisted](https://img.shields.io/badge/AI--Assisted-Development-007ACC)](https://github.com/rubensgomes-org/math-ai-agent/blob/main/AI_DISCLAIMER.md)
33
+
34
+ # Math AI Agent
35
+
36
+ A prompt chat webapp that drives an LLM call inside an agentic loop using
37
+ `calculator_mcp` MCP server for arithmetic operations. The key constraint is
38
+ that the LLM is given explicit system instructions to use the `calculator_mcp`
39
+ for any arithmetic operations.
40
+
41
+ ## Features
42
+
43
+ - **FastAPI web UI** — simple form-based interface for submitting prompts
44
+ - **MCP client** — connects to a remote calculator MCP server with optional
45
+ OAuth authentication
46
+ - **Configurable** — MCP server URL, OAuth settings, LLM endpoint and model,
47
+ and logging are all driven by `config.yaml`
48
+ - **Plain-text answers** — the model is instructed to reply without LaTeX or
49
+ Markdown, since the web UI renders answers in a plain `<textarea>`
50
+
51
+ ## AI Disclaimer
52
+
53
+ This project includes code and documentation created with the assistance of AI
54
+ tools. For details on usage, limits, and review practices, please see the
55
+ [AI Disclaimer](https://github.com/rubensgomes-org/math-ai-agent/blob/main/AI_DISCLAIMER.md).
56
+
57
+ ## Prerequisites
58
+
59
+ - pip 26.2+
60
+ - poetry 2.4+
61
+ - python 3.14+
62
+
63
+ ## Installation
64
+
65
+ ### Installation Using GitHub Project Clone
66
+
67
+ - Clone the project from GitHub:
68
+
69
+ ```bash
70
+ git clone https://github.com/rubensgomes-org/math-ai-agent.git
71
+ ```
72
+
73
+ - Install depenencies and application into `poetry` virtual environment:
74
+
75
+ ```bash
76
+ # change to project git local directory
77
+ cd $(git rev-parse --show-toplevel) || exit
78
+ poetry install
79
+ ```
80
+
81
+ ## Configuration
82
+
83
+ The server ships with a default `config.yaml` bundled inside the PiPY
84
+ package. To override it, set the `MATHAIAGENT_CONFIG` environment
85
+ variable to the absolute path of your custom configuration file:
86
+
87
+ ```bash
88
+ export MATHAIAGENT_CONFIG=/path/to/your/config.yaml
89
+ ```
90
+
91
+ ## Running the Math AI Agent Server Using GitHub Cloned Project
92
+
93
+ ### Calculator MCP Server Running Locally
94
+
95
+ **NOTE:** requires the `calculator_mcp` running locally as per instructions at
96
+ [calculator-mcp](https://github.com/rubensgomes-org/calculator-mcp)
97
+
98
+ - Launch the `math-ai-agent` from the local Git repo folder. **NOTE** the
99
+ project must be previousley installed in `poetry` venv (e.g.,
100
+ `poetry install`):
101
+
102
+ ```bash
103
+ # change to project git local directory
104
+ cd $(git rev-parse --show-toplevel) || exit
105
+ # e.g. export MATHAIAGENT_CONFIG="${HOME}/github/rubens/dev/python/math-ai-agent/config/config_local.yaml"
106
+ export MATHAIAGENT_CONFIG="/path/to/your/config_local.yaml"
107
+ # ensure below port does not conflict with locally running MCP server.
108
+ poetry run uvicorn math_ai_agent.app:app \
109
+ --host '127.0.0.1' --port '9090' --reload
110
+ ```
111
+
112
+ ### Calculator MCP Server Running Remotely - OAuth Authentication
113
+
114
+ **NOTE:** requires OAuth authentication which currently only Rubens is able to
115
+ authorize using his personal GitHub account.
116
+
117
+ - Launch the `math-ai-agent` from the local Git repo folder. **NOTE** the
118
+ project must be previousley installed in `poetry` venv (e.g.,
119
+ `poetry install`):
120
+
121
+ ```bash
122
+ # change to project git local directory
123
+ cd $(git rev-parse --show-toplevel) || exit
124
+ # e.g. export MATHAIAGENT_CONFIG="${HOME}/github/rubens/dev/python/math-ai-agent/config/config_remote.yaml"
125
+ export MATHAIAGENT_CONFIG="/path/to/your/config_remote.yaml"
126
+ # clean up previously created OAuth tokens
127
+ # e.g. rm -fr "~/.calc-mcp-token"
128
+ rm -fr <token-dir-from-config>
129
+ # ensure below port does not conflict with locally running MCP server.
130
+ poetry run uvicorn math_ai_agent.app:app \
131
+ --host '127.0.0.1' --port '9090' --reload
132
+ ```
133
+
134
+
135
+ -
136
+
137
+ ```bash
138
+ # located at project root folder
139
+ export MATHAIAGENT_CONFIG=config/config_local.yaml
140
+ ```
141
+
142
+ 2. Ensure `calculator_mcp` running locally - Follow instructions of
143
+ `calculator_mcp` project.
144
+
145
+ 3.
146
+
147
+ ```bash
148
+ # download dependencies and install code in `poetry` virtual envirnoment
149
+ poetry install
150
+ poetry run uvicorn math_ai_agent.app:app --host 0.0.0.0 --port 8000 --reload
151
+ ```
152
+
153
+ - From now on
154
+
155
+ ### Model Notes
156
+
157
+ #### NVDIDIA
158
+
159
+ NVIDIA's catalog is public — `GET https://integrate.api.nvidia.com/v1/models`
160
+ lists every served model id without authentication. Get a key from
161
+ <https://build.nvidia.com> (free developer account); keys start with `nvapi-`.
162
+
163
+ #### OpenRouter
164
+
165
+ Two provider gotchas worth knowing. OpenRouter's `:free` model variants (for
166
+ example `nvidia/nemotron-3-super-120b-a12b:free`) are capped at 50 requests per
167
+ day, after which every call fails with `429 free-models-per-day`; the `:free`
168
+ suffix is OpenRouter slug syntax and is not a valid model id anywhere else.
169
+ NVIDIA serves the same model under the bare id at a higher free rate limit, but
170
+ responds noticeably slower per turn.
171
+
172
+ Any OpenAI-compatible endpoint works, since the app talks to it through the
173
+ OpenAI SDK. Which SDK surface it uses is controlled by `llm.api_style`.
174
+
175
+ ### Response vs Chat API
176
+
177
+ **Responses API caveats.** Several providers label `/v1/responses` beta or
178
+ experimental — OpenRouter's is beta and strictly stateless (it rejects
179
+ `store: true` and `previous_response_id` with HTTP 400), and NVIDIA's is marked
180
+ experimental. Both work with this app, as does Ollama v0.13.3+. The Responses
181
+ agent loop replays every output Item back as input on each turn rather than
182
+ relying on server-side state, which is what keeps it portable across all of
183
+ them and unchanged against `https://api.openai.com/v1`. Not every model in a
184
+ provider's catalog is
185
+ necessarily served over its Responses endpoint — if a model 404s or 400s under
186
+ `api_style: "responses"`, either pick a model that supports it or set
187
+ `api_style: "chat"`.
188
+
189
+ ### Server-Side Storage
190
+
191
+ **Server-side storage.** Both clients send `store=False` on every request, so
192
+ neither API retains the conversation. This matters most on the Responses API,
193
+ which stores by default; Chat Completions already defaults to not storing, but
194
+ the flag is sent there too because OpenAI accounts carry a separate
195
+ data-retention setting that can enable storage when the parameter is omitted.
196
+ Note this controls the API's own storage, not org-level dashboard logging.
197
+
198
+ See [OLLAMA.md](OLLAMA.md) for running models locally.
199
+
200
+ ## Setup
201
+
202
+ See [SETUP.md](SETUP.md) for detailed instructions on setting up the development
203
+ environment (pyenv, poetry, virtual environment, PyCharm, etc.).
204
+
205
+ ### Quick Start
206
+
207
+ ```bash
208
+ # Clone and install
209
+ git clone https://github.com/rubensgomes/math-ai-agent
210
+ cd math-ai-agent
211
+ poetry install
212
+
213
+ # Run the FastAPI server
214
+ poetry run uvicorn math_ai_agent.app:app --reload
215
+
216
+ # Change to the project root folder
217
+ cd $(git rev-parse --show-toplevel) || exit
218
+ # Run the MCP integration test client
219
+ poetry run python tests/integration/test_calc_client.py
220
+ # Run the FastAPI web server test
221
+ poetry run uvicorn tests.integration.test_app:app --reload
222
+ ```
223
+
224
+ The integration commands above need credentials and an OAuth authorization —
225
+ see [Testing](#testing) for the prerequisites and the full sequence.
226
+
227
+ ## Testing
228
+
229
+ There are two kinds of tests in this project:
230
+
231
+ - **Unit tests** (`tests/*.py`) — fast, fully mocked, no network. This is what
232
+ `pytest` collects.
233
+ - **Live integration tests** (`tests/integration/*.py`) — standalone scripts
234
+ that call the real LLM and the real MCP server. They contain no
235
+ `test_`-prefixed functions, so **`pytest` does not collect them**; they only
236
+ run as the scripts shown below.
237
+
238
+ ### Unit tests
239
+
240
+ ```bash
241
+ poetry run pytest
242
+
243
+ # With coverage (branch coverage, minimum 90%)
244
+ poetry run pytest --cov=src/ --cov-report=term-missing
245
+ ```
246
+
247
+ No credentials or network access required.
248
+
249
+ ### Live integration tests
250
+
251
+ These make real API calls that may cost money, and the MCP server requires a
252
+ one-time OAuth authorization in your browser.
253
+
254
+ #### Prerequisites
255
+
256
+ 1. **Check `config.yaml`.** Confirm `llm.model_base_url`, `llm.model`, and
257
+ `llm.api_key_env` point at the provider you intend to use, and that the base
258
+ URL has no `/chat/completions` suffix (see [Configuration](#configuration)).
259
+ A wrong model id fails on the first request with a 404 that looks a lot like
260
+ a URL problem.
261
+
262
+ 2. **Export the LLM API key**, using the variable name that `llm.api_key_env`
263
+ names:
264
+
265
+ ```bash
266
+ # the variable named by llm.api_key_env -- currently NVIDIA_API_KEY
267
+ export NVIDIA_API_KEY="nvapi-..."
268
+ ```
269
+
270
+ 3. **Export an OAuth storage key.** Generate a Fernet key once and reuse it —
271
+ changing it makes previously cached tokens unreadable and forces a
272
+ re-authorization:
273
+
274
+ ```bash
275
+ # Generate a key (do this once, then save it)
276
+ poetry run python -c \
277
+ 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'
278
+
279
+ export OAUTH_STORAGE_ENCRYPTION_KEY="<the generated key>"
280
+ ```
281
+
282
+ This is required whenever `server.calculator_mcp.is_oauth` is `true`. It is
283
+ read directly from the environment, so a missing value raises a bare
284
+ `KeyError`.
285
+
286
+ 4. **Free port 10000** for the OAuth callback
287
+ (`server.calculator_mcp.callback_port`).
288
+
289
+ #### Run them in order
290
+
291
+ Each step isolates one moving part, so a failure tells you exactly what broke.
292
+ Run all commands from the project root.
293
+
294
+ | # | Command | Exercises | LLM | MCP |
295
+ |---|------------------------------------------------------------------------|--------------------------------------------|-----|-----|
296
+ | 1 | `poetry run python tests/integration/test_openai_client.py` | API key, base URL, model id | ✅ | — |
297
+ | 2 | `poetry run python tests/integration/test_calc_client.py` | OAuth flow, MCP connection, tool discovery | — | ✅ |
298
+ | 3 | `poetry run python tests/integration/test_llm.py` | Tool schemas accepted by the model | ✅ | ✅ |
299
+ | 4 | `poetry run python tests/integration/test_llm_chat_completion_tool.py` | The Chat Completions agent loop | ✅ | ✅ |
300
+ | 5 | `poetry run python tests/integration/test_llm_responses_tool.py` | The Responses agent loop | ✅ | ✅ |
301
+ | 6 | `poetry run uvicorn math_ai_agent.app:app --reload` | The whole app end to end | ✅ | ✅ |
302
+
303
+ **Step 1 — LLM only.** Sends one question straight to the model, no MCP
304
+ involved. The `Connecting to <url> using model <model>` log line echoes exactly
305
+ what `config.yaml` supplied.
306
+
307
+ **Step 2 — MCP only.** The first run opens a browser for OAuth authorization;
308
+ the callback lands on port 10000. Tokens are cached encrypted under
309
+ `server.calculator_mcp.token_dir` (`/tmp/.fastmcp/oauth-tokens` by default), so
310
+ later runs skip the browser. Because `/tmp` is cleared on reboot, an unexpected
311
+ re-authorization prompt usually means exactly that.
312
+
313
+ **Step 3 — LLM + tool discovery.** Discovers the calculator tools, sends
314
+ `4+4?` with the tool definitions attached, and logs the reply. It does *not*
315
+ dispatch tool calls — it confirms the model accepts the schemas.
316
+
317
+ **Step 4 — the full agent loop.** Multi-turn: the model requests a calculator
318
+ tool, the result is fed back, and the model answers. Look for
319
+ `Calling calculator MCP tool <name> with <args>` in the output. If the model
320
+ answers with no such lines, it is doing arithmetic in its head and the system
321
+ prompt is not taking effect.
322
+
323
+ **Step 5 — the Responses agent loop.** Same idea as step 4, but against the
324
+ Responses API (`POST /v1/responses`) rather than Chat Completions. It calls the
325
+ real `_responses_agent_loop()`, so it exercises the code the app runs. Takes the
326
+ question on the command line, or prompts for it:
327
+
328
+ ```bash
329
+ poetry run python tests/integration/test_llm_responses_tool.py "What is 4 + 4 * 3?"
330
+ ```
331
+
332
+ Look for `function_call` items in the response and `Calling call_id: <id>,
333
+ tool_name: <name>` in the output. This step is independent of `llm.api_style` —
334
+ it always drives the Responses loop.
335
+
336
+ **Step 6 — the web app.** Open <http://127.0.0.1:8000>, type a math question,
337
+ and submit. `POST /prompt/` runs `agent_loop()`, which follows whichever path
338
+ `llm.api_style` selects.
339
+
340
+ #### Verifying that config drives the client
341
+
342
+ Watch for this line, emitted whenever the client is built:
343
+
344
+ ```
345
+ Initializing ChatCompletionClient with base_url=..., model=..., tool_count=N
346
+ ```
347
+
348
+ (the class name is `ResponsesClient` when `llm.api_style` is `responses`)
349
+
350
+ Change `llm.model` in `config.yaml`, rerun step 4 or 5, and the line should
351
+ report
352
+ the new value with no code change. `config.yaml` sets `DEBUG` for both the
353
+ `math_ai_agent` and `openai` loggers, so full request and response bodies appear
354
+ in the output.
355
+
356
+ #### A note on `tests/integration/test_app.py`
357
+
358
+ ```bash
359
+ poetry run uvicorn tests.integration.test_app:app --reload
360
+ ```
361
+
362
+ This serves the same web UI but echoes your prompt straight back without calling
363
+ any LLM. Use it to check the page and the `POST /prompt/` wiring in isolation —
364
+ it does not exercise the agent.
365
+
366
+ ## License
367
+
368
+ The project is licensed under
369
+ [MIT License](https://github.com/rubensgomes-org/math-ai-agent/blob/main/LICENSE).
370
+
371
+ ---
372
+ Author: [Rubens Gomes](https://rubensgomes.com/)
373
+