From 7a9cef05f4a3d6e97c73ee7e47f53f4009ac13b1 Mon Sep 17 00:00:00 2001 From: edoigtrd Date: Tue, 23 Dec 2025 18:01:07 +0100 Subject: [PATCH 1/3] Add initial implementation of Nomos1 Solver with configuration support - Introduced config.toml for model and agent configurations. - Implemented mcp.py to handle problem-solving using FastMCP and SolveAgent. - Updated requirements.txt to ensure fastmcp is included. - Modified SolveAgent to allow optional problems_dir and improved submission handling. --- config.toml | 9 ++++++++ mcp.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + solve_agent.py | 36 ++++++++++++++++++++++++----- 4 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 config.toml create mode 100644 mcp.py diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..389fede --- /dev/null +++ b/config.toml @@ -0,0 +1,9 @@ +[models] +base_url = "http://localhost:30000/v1" +judge_model = "nomos-1" +solve_model = "nomos-1" + +[agent] +target_perfect_score=4 +max_concurrent=32 +time_limit_hours=3.0 \ No newline at end of file diff --git a/mcp.py b/mcp.py new file mode 100644 index 0000000..8dfdeb7 --- /dev/null +++ b/mcp.py @@ -0,0 +1,59 @@ +from fastmcp import FastMCP +from solve_agent import SolveAgent, ProblemState +import toml +import uuid +import asyncio +import time + + +with open("config.toml", "r") as f: + config = toml.load(f) + +mcp = FastMCP("Nomos1 Solver") + +@mcp.tool +async def solve(problem: str) -> str: + """Solve a complex mathematical or logical problem using Nomos1 Solver.""" + agent = SolveAgent( + problems_dir=None, + base_url=config["models"]["base_url"], + model=config["models"]["solve_model"], + judge_model=config["models"]["judge_model"], + target_perfect_scores=config["agent"]["target_perfect_score"], + max_concurrent=int(config["agent"]["max_concurrent"]), + time_limit_hours=float(config["agent"]["time_limit_hours"]) + ) + + suid = str(uuid.uuid4()) + + problem = ProblemState( + problem_id=suid, + problem_text=problem + ) + + problems = { + suid: problem + } + + agent.problems = problems + agent.start_time = time.time() # Initialize start_time before solving + + # Start watchdog in background (safe mode = doesn't kill process) + watchdog = asyncio.create_task(agent._deadline_watchdog(safe=True)) + + try: + await agent.run_solving_phase() + await agent.run_finalization_phase() + finally: + watchdog.cancel() + + solution = agent.get_problem_solution(suid) + + return solution if solution else "No solution generated" + + + +if __name__ == "__main__": + print("MCP Server sould be ran using :") + print("fastmcp run ./mcp.py --transport http --port 9000") + print("Then connect to it using any MCP client (tested on Insomnia).") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 4885089..03494fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ fire tenacity httpx rich +fastmcp \ No newline at end of file diff --git a/solve_agent.py b/solve_agent.py index 953b974..c7c9846 100644 --- a/solve_agent.py +++ b/solve_agent.py @@ -127,7 +127,7 @@ class SolveAgent: def __init__( self, - problems_dir: str, + problems_dir: Optional[str] = None, judge_prompt: str = "prompts/score.md", submissions_dir: Optional[str] = None, solve_prompt: Optional[str] = None, @@ -140,11 +140,12 @@ def __init__( judge_model: str = "nomos-1", base_url: str = "http://localhost:30000/v1", ): - self.problems_dir = Path(problems_dir) - if submissions_dir is None: + self.solution = None + self.problems_dir = Path(problems_dir) if problems_dir else None + if submissions_dir is None and problems_dir is not None: timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") submissions_dir = f"submissions/{self.problems_dir.name}-{timestamp}" - self.submissions_dir = Path(submissions_dir) + self.submissions_dir = Path(submissions_dir) if submissions_dir else None self.time_limit_seconds = time_limit_hours * 3600 # Stop solving 15 min before end, but ensure at least 50% of time for solving self.early_stop_seconds = max( @@ -169,6 +170,7 @@ def __init__( self.client = AsyncOpenAI( base_url=base_url, timeout=httpx.Timeout(99999, connect=60.0), + api_key=os.getenv("API_KEY") ) self.start_time: float = 0 self.stopping = False @@ -629,6 +631,8 @@ async def run_finalization_phase(self): def write_submissions(self): """Write final submissions to disk.""" + if self.submissions_dir is None: + return self.submissions_dir.mkdir(parents=True, exist_ok=True) for problem in self.problems.values(): @@ -652,8 +656,25 @@ def write_submissions(self): filepath.write_text(output, encoding="utf-8") safeprint(f"[green]Wrote {filepath}[/green]") + + def get_problem_solution(self, problem_id: str) -> Optional[str]: + """Get the final solution content for a given problem ID.""" + problem = self.problems.get(problem_id) + + safeprint(f"[bold]Retrieving solution for problem {problem.problem_id}[/bold]") + + if problem is None: + return None + + content = problem.final_submission.content if problem.final_submission else "" - async def _deadline_watchdog(self): + output = f"# {problem.problem_id}\n\n" + output += f"## Problem\n\n{problem.problem_text}\n\n" + output += f"## Submission\n\n{content}\n" + + return output + + async def _deadline_watchdog(self, safe=False): """Background task that enforces hard deadline by killing the process.""" # Trigger 1 min before deadline to allow time for writing submissions deadline = self.time_limit_seconds - 60 @@ -666,7 +687,10 @@ async def _deadline_watchdog(self): self.write_submissions() elapsed = time.time() - self.start_time safeprint(f"\n[bold green]Done in {elapsed/60:.1f} minutes[/bold green]") - os._exit(0) + if not safe: + os._exit(0) + else : + break async def run(self): """Main entry point.""" From 6dcf3a518aff4ee17f7222ba9d777a1002a71800 Mon Sep 17 00:00:00 2001 From: edoigtrd Date: Tue, 23 Dec 2025 18:01:21 +0100 Subject: [PATCH 2/3] Add initial Docker setup with Dockerfile, docker-compose, and example environment file --- .env.example | 1 + DOCKER.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 17 +++++++++++++++ docker-compose.yml | 19 ++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 .env.example create mode 100644 DOCKER.md create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3835e31 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +API_KEY=sk-xxxxxxxxxxxxxx diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..1fd60f9 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,54 @@ +# Nomos Solver MCP - Docker + +## Build +```bash +docker build -t nomos-solver-mcp . +``` + +## Run +```bash +docker run -p 9000:9000 nomos-solver-mcp +``` + +## With custom config +```bash +docker run -p 9000:9000 -v $(pwd)/config.toml:/app/config.toml nomos-solver-mcp +``` + +## Environment variables +Set `API_KEY` if needed: +```bash +docker run -p 9000:9000 -e API_KEY=your-key-here nomos-solver-mcp +``` + +--- + +## Docker Compose + +### Quick start +```bash +docker-compose up -d +``` + +### Build and run +```bash +docker-compose up --build +``` + +### Stop +```bash +docker-compose down +``` + +### View logs +```bash +docker-compose logs -f nomos-solver-mcp +``` + +### Environment variables +Create a `.env` file: +```env +API_KEY=your-key-here +``` + +**Note**: Adjust `base_url` in `config.toml` to point to your model server (use host IP instead of localhost when running in Docker). diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c20882a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Copy requirements and install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application files +COPY mcp.py solve_agent.py config.toml ./ +COPY prompts/ ./prompts/ + +# Expose MCP server port +EXPOSE 9000 + +# Run the MCP server +CMD ["fastmcp", "run", "mcp.py", "--transport", "http", "--port", "9000", "--host", "0.0.0.0"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3d686c0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +version: '3.8' + +services: + nomos-solver-mcp: + build: . + container_name: nomos-solver-mcp + ports: + - "9000:9000" + volumes: + - ./config.toml:/app/config.toml + environment: + - API_KEY=sk-xxxxxxxxxxxxx + restart: unless-stopped + networks: + - nomos-network + +networks: + nomos-network: + driver: bridge From 52cb6475280af09bc7781a33355cdf5f164e9d15 Mon Sep 17 00:00:00 2001 From: edoigtrd Date: Tue, 23 Dec 2025 18:24:57 +0100 Subject: [PATCH 3/3] Add toml to requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 03494fc..140281c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ fire tenacity httpx rich -fastmcp \ No newline at end of file +fastmcp +toml \ No newline at end of file