math-ai-agent 0.0.1__py3-none-any.whl

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,39 @@
1
+ # General Disclaimer
2
+ #
3
+ # **AI Generated Content**
4
+ #
5
+ # This project's source code and documentation were generated predominantly
6
+ # by an Artificial Intelligence Large Language Model (AI LLM). The project
7
+ # lead, [Rubens Gomes](https://rubensgomes.com), provided initial prompts,
8
+ # reviewed, and made refinements to the generated output. While human review and
9
+ # refinement have occurred, users should be aware that the output may contain
10
+ # inaccuracies, errors, or security vulnerabilities
11
+ #
12
+ # **Third-Party Content Notice**
13
+ #
14
+ # This software may include components or snippets derived from third-party
15
+ # sources. The software's users and distributors are responsible for ensuring
16
+ # compliance with any underlying licenses applicable to such components.
17
+ #
18
+ # **Copyright Status Statement**
19
+ #
20
+ # Copyright protection, if any, is limited to the original
21
+ # human contributions and modifications made to this project.
22
+ # The AI-generated portions of the code and
23
+ # documentation are not subject to copyright and are considered to be in the
24
+ # public domain.
25
+ #
26
+ # **Limitation of liability**
27
+ #
28
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
29
+ # DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR
30
+ # OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE
31
+ # OR OTHER DEALINGS IN THE SOFTWARE.
32
+ #
33
+ # **No-Warranty Disclaimer**
34
+ #
35
+ # THIS SOFTWARE IS PROVIDED 'AS IS,' WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
37
+ # FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
38
+
39
+ """math_ai_agent — Math AI Agent package."""
math_ai_agent/app.py ADDED
@@ -0,0 +1,109 @@
1
+ # General Disclaimer
2
+ #
3
+ # **AI Generated Content**
4
+ #
5
+ # This project's source code and documentation were generated predominantly
6
+ # by an Artificial Intelligence Large Language Model (AI LLM). The project
7
+ # lead, [Rubens Gomes](https://rubensgomes.com), provided initial prompts,
8
+ # reviewed, and made refinements to the generated output. While human review and
9
+ # refinement have occurred, users should be aware that the output may contain
10
+ # inaccuracies, errors, or security vulnerabilities
11
+ #
12
+ # **Third-Party Content Notice**
13
+ #
14
+ # This software may include components or snippets derived from third-party
15
+ # sources. The software's users and distributors are responsible for ensuring
16
+ # compliance with any underlying licenses applicable to such components.
17
+ #
18
+ # **Copyright Status Statement**
19
+ #
20
+ # Copyright protection, if any, is limited to the original
21
+ # human contributions and modifications made to this project.
22
+ # The AI-generated portions of the code and
23
+ # documentation are not subject to copyright and are considered to be in the
24
+ # public domain.
25
+ #
26
+ # **Limitation of liability**
27
+ #
28
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
29
+ # DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR
30
+ # OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE
31
+ # OR OTHER DEALINGS IN THE SOFTWARE.
32
+ #
33
+ # **No-Warranty Disclaimer**
34
+ #
35
+ # THIS SOFTWARE IS PROVIDED 'AS IS,' WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
37
+ # FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
38
+
39
+ """The FastAPI web server app.
40
+
41
+ Launches a FastAPI web server with the following endpoints:
42
+ - `GET /` returns the index.html page.
43
+ - `POST /prompt/` submits user prompt and returns AI response.
44
+
45
+ From the project root folder run::
46
+
47
+ poetry run uvicorn math_ai_agent.app:app --reload
48
+ """
49
+
50
+ import json
51
+ import logging
52
+ from pathlib import Path
53
+
54
+ from fastapi import FastAPI
55
+ from fastapi.responses import HTMLResponse
56
+ from fastapi.staticfiles import StaticFiles
57
+
58
+ from math_ai_agent.config.config import configure_logging
59
+ from math_ai_agent.llm import agent_loop
60
+ from math_ai_agent.prompt import Prompt
61
+
62
+ configure_logging()
63
+ logger = logging.getLogger(__name__)
64
+
65
+ # folder to HTML file
66
+ _STATIC_DIR = Path(__file__).parent / "static"
67
+
68
+ # -------------------------------------------------
69
+ # Create the FastAPI app instance
70
+ # -------------------------------------------------
71
+ app = FastAPI()
72
+ app.mount("/static", StaticFiles(directory=_STATIC_DIR), name="static")
73
+
74
+
75
+ # -------------------------------------------------
76
+ # Routes
77
+ # -------------------------------------------------
78
+ @app.get("/", response_class=HTMLResponse)
79
+ async def root() -> str:
80
+ """Serve the main HTML page.
81
+
82
+ Returns:
83
+ The HTML content of the index page.
84
+ """
85
+ logger.debug("Serving root HTML page: %s%s", _STATIC_DIR, "/index.html")
86
+ return (_STATIC_DIR / "index.html").read_text()
87
+
88
+
89
+ @app.post("/prompt/")
90
+ async def prompt(payload: Prompt) -> dict[str, str]:
91
+ """Accept a prompt text from the user and return an answer.
92
+
93
+ Args:
94
+ payload: The validated question from the request body.
95
+
96
+ Returns:
97
+ A dict containing the `answer` key with the response.
98
+
99
+ Raises:
100
+ RuntimeError: If the agent loop encounters a token limit
101
+ or content filter error.
102
+ ValueError: If the LLM returns an unknown finish reason.
103
+ """
104
+ logger.debug("Received prompt: %s", payload.text)
105
+ prompt_text = payload.text.strip()
106
+ logger.debug("Calling LLM with user prompt: %s", prompt_text)
107
+ output = await agent_loop(prompt_text)
108
+ logger.debug("Output:\n%s", json.dumps(output, indent=2))
109
+ return {"answer": output}
@@ -0,0 +1,39 @@
1
+ # General Disclaimer
2
+ #
3
+ # **AI Generated Content**
4
+ #
5
+ # This project's source code and documentation were generated predominantly
6
+ # by an Artificial Intelligence Large Language Model (AI LLM). The project
7
+ # lead, [Rubens Gomes](https://rubensgomes.com), provided initial prompts,
8
+ # reviewed, and made refinements to the generated output. While human review and
9
+ # refinement have occurred, users should be aware that the output may contain
10
+ # inaccuracies, errors, or security vulnerabilities
11
+ #
12
+ # **Third-Party Content Notice**
13
+ #
14
+ # This software may include components or snippets derived from third-party
15
+ # sources. The software's users and distributors are responsible for ensuring
16
+ # compliance with any underlying licenses applicable to such components.
17
+ #
18
+ # **Copyright Status Statement**
19
+ #
20
+ # Copyright protection, if any, is limited to the original
21
+ # human contributions and modifications made to this project.
22
+ # The AI-generated portions of the code and
23
+ # documentation are not subject to copyright and are considered to be in the
24
+ # public domain.
25
+ #
26
+ # **Limitation of liability**
27
+ #
28
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
29
+ # DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR
30
+ # OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE
31
+ # OR OTHER DEALINGS IN THE SOFTWARE.
32
+ #
33
+ # **No-Warranty Disclaimer**
34
+ #
35
+ # THIS SOFTWARE IS PROVIDED 'AS IS,' WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
37
+ # FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
38
+
39
+ """config — configuration sub-package for math_ai_agent."""
@@ -0,0 +1,148 @@
1
+ # General Disclaimer
2
+ #
3
+ # **AI Generated Content**
4
+ #
5
+ # This project's source code and documentation were generated predominantly
6
+ # by an Artificial Intelligence Large Language Model (AI LLM). The project
7
+ # lead, [Rubens Gomes](https://rubensgomes.com), provided initial prompts,
8
+ # reviewed, and made refinements to the generated output. While human review and
9
+ # refinement have occurred, users should be aware that the output may contain
10
+ # inaccuracies, errors, or security vulnerabilities
11
+ #
12
+ # **Third-Party Content Notice**
13
+ #
14
+ # This software may include components or snippets derived from third-party
15
+ # sources. The software's users and distributors are responsible for ensuring
16
+ # compliance with any underlying licenses applicable to such components.
17
+ #
18
+ # **Copyright Status Statement**
19
+ #
20
+ # Copyright protection, if any, is limited to the original
21
+ # human contributions and modifications made to this project.
22
+ # The AI-generated portions of the code and
23
+ # documentation are not subject to copyright and are considered to be in the
24
+ # public domain.
25
+ #
26
+ # **Limitation of liability**
27
+ #
28
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
29
+ # DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR
30
+ # OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE
31
+ # OR OTHER DEALINGS IN THE SOFTWARE.
32
+ #
33
+ # **No-Warranty Disclaimer**
34
+ #
35
+ # THIS SOFTWARE IS PROVIDED 'AS IS,' WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
37
+ # FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
38
+
39
+ """Configuration helpers — loads config.yaml and configures logging."""
40
+
41
+ import functools
42
+ import logging
43
+ import logging.config
44
+ import os
45
+ from importlib.resources import files
46
+ from pathlib import Path
47
+ from typing import Any, Literal
48
+
49
+ import yaml
50
+ from pydantic import BaseModel
51
+
52
+ logger = logging.getLogger(__name__)
53
+
54
+
55
+ class LLMConfig(BaseModel):
56
+ """The ``llm`` section of config.yaml."""
57
+
58
+ api_style: Literal["chat", "responses"] = "chat"
59
+ model_base_url: str
60
+ model: str
61
+ api_key_env: str
62
+ system_instructions: str
63
+
64
+
65
+ class CalculatorMCPConfig(BaseModel):
66
+ """The ``server.calculator_mcp`` section of config.yaml."""
67
+
68
+ url: str
69
+ is_oauth: bool = False
70
+ token_dir: str
71
+ callback_port: int
72
+
73
+
74
+ class ServerConfig(BaseModel):
75
+ """The ``server`` section of config.yaml."""
76
+
77
+ calculator_mcp: CalculatorMCPConfig
78
+
79
+
80
+ class AppConfig(BaseModel):
81
+ """The full config.yaml; ``logging`` is a ``dictConfig`` mapping."""
82
+
83
+ llm: LLMConfig
84
+ server: ServerConfig
85
+ logging: dict[str, Any]
86
+
87
+
88
+ def _resolve_config_path() -> Path:
89
+ """Return the config.yaml path.
90
+
91
+ Resolution order:
92
+
93
+ 1. The ``MATHAIAGENT_CONFIG`` environment variable, when set.
94
+ 2. The ``config.yaml`` bundled inside the
95
+ ``math_ai_agent.config`` package.
96
+
97
+ Returns:
98
+ The resolved path to config.yaml.
99
+ """
100
+ env_path = os.environ.get("MATHAIAGENT_CONFIG")
101
+ if env_path:
102
+ return Path(env_path)
103
+ return Path(str(files("math_ai_agent.config").joinpath("config.yaml")))
104
+
105
+
106
+ def load_config(path: Path) -> AppConfig:
107
+ """Parse and validate the config file at ``path``.
108
+
109
+ Raises:
110
+ pydantic.ValidationError: If the file does not match the models.
111
+ """
112
+ with open(path, encoding="utf-8") as f:
113
+ return AppConfig.model_validate(yaml.safe_load(f))
114
+
115
+
116
+ @functools.cache
117
+ def get_config() -> AppConfig:
118
+ """Return the application config, loaded once on first call."""
119
+ return load_config(_resolve_config_path())
120
+
121
+
122
+ def configure_logging() -> None:
123
+ """Apply the logging configuration from config.yaml."""
124
+ logging.config.dictConfig(get_config().logging)
125
+ logger.debug("Loaded config from %s", _resolve_config_path())
126
+
127
+
128
+ def get_api_key() -> str:
129
+ """Return the LLM API key from the environment.
130
+
131
+ The config.yaml ``llm.api_key_env`` setting names the environment
132
+ variable holding the key; the key value itself is never stored in
133
+ config.yaml. Only the variable name is logged, never the key.
134
+
135
+ Returns:
136
+ The API key read from the configured environment variable.
137
+
138
+ Raises:
139
+ RuntimeError: If the environment variable is not set or empty.
140
+ """
141
+ env_name = get_config().llm.api_key_env
142
+ logger.info("LLM API key environment variable: %s", env_name)
143
+ api_key = os.environ.get(env_name)
144
+ if not api_key:
145
+ error = f"{env_name} environment variable is not set."
146
+ logger.error(error)
147
+ raise RuntimeError(error)
148
+ return api_key
@@ -0,0 +1,143 @@
1
+ # =============================================================================
2
+ # LLM Configuration
3
+ # =============================================================================
4
+ llm:
5
+ # Which OpenAI API the LLM client uses:
6
+ # "responses" -> POST /v1/responses (primary OpenAI API)
7
+ # "chat" -> POST /v1/chat/completions (legacy, still supported)
8
+ # Both work against NVIDIA nemotron models
9
+ api_style: "responses"
10
+ # Base NVIDIA LLM models URL
11
+ model_base_url: "https://integrate.api.nvidia.com/v1"
12
+ # Nemotron frontier NVIDIA models for agentic workflows, coding and tool use
13
+ # See list of NVIDIA models: https://integrate.api.nvidia.com/v1/models
14
+ model: "nvidia/nemotron-3-super-120b-a12b"
15
+ # Name of the environment variable holding the API key.
16
+ # Get a key from https://build.nvidia.com (keys start with "nvapi-").
17
+ api_key_env: "NVIDIA_API_KEY"
18
+ # System prompt sent to the **stateless** LLM on every request.
19
+ system_instructions: >-
20
+ You are a careful math assistant tutor helping solve math problems.
21
+ Always write a short plan first. Do NOT do arithmetic in your head.
22
+ For every math operation, request a tool call to the calculator.
23
+ After tool results, continue. Provide final answer with explanation.
24
+ Respond in plain text only. Do NOT use LaTeX, Markdown, or any other
25
+ special formatting: no backslashes, no asterisks, no dollar-sign or
26
+ parenthesis math delimiters. Write math inline, like 4 x 3 = 12.
27
+ Your answer is shown in a plain text box that cannot render
28
+ formatting.
29
+
30
+ # OpenRouter alternative. Its free tier allows only 50 requests per
31
+ # day, after which requests fail with 429 free-models-per-day. The
32
+ # ":free" suffix is OpenRouter slug syntax and is not a valid model
33
+ # id on any other provider.
34
+ # model_base_url: "https://openrouter.ai/api/v1"
35
+ # model: "nvidia/nemotron-3-super-120b-a12b:free"
36
+ # api_key_env: "OPENROUTER_API_KEY"
37
+ # Local Ollama alternative (see OLLAMA.md):
38
+ # model_base_url: "http://localhost:11434/v1"
39
+ # model: "phi"
40
+
41
+ # =============================================================================
42
+ # Calculator MCP Server Configuration
43
+ # =============================================================================
44
+ server:
45
+ calculator_mcp:
46
+ # URL of the Calculator MCP Server End-Point
47
+ # url: "http://127.0.0.1:8080/mcp"
48
+ # Hosted, deployed and running for free at Prefect
49
+ # https://www.prefect.io/horizon
50
+ url: "https://rubens-calculator-mcp.fastmcp.app/mcp"
51
+ # is_oauth: false
52
+ # Prefect requires OAuth authentication. Currently only Rubens is able
53
+ # to authorize using his personal GitHub account.
54
+ is_oauth: true
55
+ # location to store OAuth token
56
+ token_dir: "~/.mathaiagent"
57
+ # local port temporarily used by the OAuth callback server during
58
+ # OAuth handshaking.
59
+ callback_port: 10000
60
+
61
+ # =============================================================================
62
+ # Logging Configuration
63
+ # =============================================================================
64
+ logging:
65
+ version: 1
66
+ disable_existing_loggers: false
67
+ formatters:
68
+ # Colors level name and message; plain if stderr is not a TTY or NO_COLOR
69
+ # set environment variable NO_COLOR=true for plain text with no color.
70
+ standard:
71
+ (): colorlog.ColoredFormatter
72
+ fmt: "%(asctime)s %(log_color)s[%(levelname)s]%(reset)s %(fg_147)s%(name)s %(filename)s:%(lineno)d%(reset)s: %(log_color)s%(message)s%(reset)s"
73
+ datefmt: "%H:%M:%S"
74
+ log_colors:
75
+ DEBUG: cyan
76
+ INFO: light_green
77
+ WARNING: yellow
78
+ ERROR: red
79
+ CRITICAL: bold_red
80
+ stream: ext://sys.stderr
81
+ handlers:
82
+ console:
83
+ class: logging.StreamHandler
84
+ formatter: standard
85
+ stream: ext://sys.stderr
86
+ loggers:
87
+ math_ai_agent:
88
+ level: DEBUG
89
+ handlers:
90
+ - console
91
+ propagate: false
92
+ # MCP protocol tracing — set to DEBUG to see full JSON-RPC messages
93
+ mcp.client.streamable_http:
94
+ level: INFO
95
+ handlers:
96
+ - console
97
+ propagate: false
98
+ # HTTP request/response summaries — set to DEBUG for detail
99
+ httpx:
100
+ level: INFO
101
+ handlers:
102
+ - console
103
+ propagate: false
104
+ # OpenAI SDK logging — set to DEBUG for request/response detail
105
+ openai:
106
+ level: INFO
107
+ handlers:
108
+ - console
109
+ propagate: false
110
+ # HTTP wire-level tracing (headers, TCP) — set to DEBUG for detail
111
+ httpcore:
112
+ level: INFO
113
+ handlers:
114
+ - console
115
+ propagate: false
116
+ # Server: startup, shutdown, and errors
117
+ uvicorn:
118
+ level: INFO
119
+ handlers:
120
+ - console
121
+ propagate: false
122
+ # Server: HTTP request lines (method, path, status)
123
+ uvicorn.access:
124
+ level: INFO
125
+ handlers:
126
+ - console
127
+ propagate: false
128
+ # Integration tests (via pytest)
129
+ tests:
130
+ level: DEBUG
131
+ handlers:
132
+ - console
133
+ propagate: false
134
+ # Integration tests (run standalone)
135
+ __main__:
136
+ level: DEBUG
137
+ handlers:
138
+ - console
139
+ propagate: false
140
+ root:
141
+ level: WARNING
142
+ handlers:
143
+ - console
@@ -0,0 +1,47 @@
1
+ # General Disclaimer
2
+ #
3
+ # **AI Generated Content**
4
+ #
5
+ # This project's source code and documentation were generated predominantly
6
+ # by an Artificial Intelligence Large Language Model (AI LLM). The project
7
+ # lead, [Rubens Gomes](https://rubensgomes.com), provided initial prompts,
8
+ # reviewed, and made refinements to the generated output. While human review and
9
+ # refinement have occurred, users should be aware that the output may contain
10
+ # inaccuracies, errors, or security vulnerabilities
11
+ #
12
+ # **Third-Party Content Notice**
13
+ #
14
+ # This software may include components or snippets derived from third-party
15
+ # sources. The software's users and distributors are responsible for ensuring
16
+ # compliance with any underlying licenses applicable to such components.
17
+ #
18
+ # **Copyright Status Statement**
19
+ #
20
+ # Copyright protection, if any, is limited to the original
21
+ # human contributions and modifications made to this project.
22
+ # The AI-generated portions of the code and
23
+ # documentation are not subject to copyright and are considered to be in the
24
+ # public domain.
25
+ #
26
+ # **Limitation of liability**
27
+ #
28
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
29
+ # DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR
30
+ # OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE
31
+ # OR OTHER DEALINGS IN THE SOFTWARE.
32
+ #
33
+ # **No-Warranty Disclaimer**
34
+ #
35
+ # THIS SOFTWARE IS PROVIDED 'AS IS,' WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
37
+ # FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
38
+
39
+ """llm — LLM sub-package for math_ai_agent.
40
+
41
+ Re-exports ``agent_loop`` so callers can use
42
+ ``from math_ai_agent.llm import agent_loop``.
43
+ """
44
+
45
+ from math_ai_agent.llm.agent import agent_loop
46
+
47
+ __all__ = ["agent_loop"]