SemOPT uses Monte Carlo Tree Search (MCTS) to guide multi-stage LLM generation, converting a natural-language optimization problem into executable Gurobi Python code, and using execution + audit-based scoring to drive the search.
The current implementation follows this state chain:
- Problem (natural language) → SolutionStrategy (JSON, 4 fields)
- SolutionStrategy → MathModel (JSON → formatted text)
The LLM returns a JSON object withsets/parameters/variables/objective/constraints, which the code formats into a readable “math model” text block. - MathModel → Gurobi Code (Python)
Note: the tree logger prints Depth 1 as “Structured Text”, but the actual state type is SolutionStrategyState.
pip install -r requirements.txtThis project depends on gurobipy. Make sure Gurobi is installed and licensed properly; otherwise, even correct generated code may fail to execute.
The CLI entry point is semopt/main.py. From the repo root, run it as a module:
export OPENAI_API_KEY="your-api-key-here"
python -m semopt.main --file benchmark/EasyLP/problem_001/desc.txt -n 8Benchmark problems in this repo use desc.txt as the problem file name.
export OPENAI_API_KEY="your-api-key-here"
python -m semopt.main "Your optimization problem description here" -n 8problem: positional argument, problem description (optional; you can use--file)--file, -f: read problem from file--iterations, -n: number of MCTS iterations (overridesconfig.num_iterations)--log-file, -l: path to the log file--no-console: disable console output (log file still works)
Configuration is loaded by Config.from_env() in semopt/config.py:
- OPENAI_API_KEY: required if either generation or evaluation uses API (by default evaluation uses API); optional only when
USE_LOCAL_MODEL=trueandUSE_LOCAL_FOR_EVALUATION=true - OPENAI_MODEL: model name for generation (default:
gpt-4.1-nano) - OPENAI_BASE_URL: OpenAI-compatible base URL
- SEED: random seed (default:
42) - NUM_ITERATIONS: default number of iterations (default:
8; can be overridden by-n/--iterations) - LOG_FILE: log file path (default: empty; if not provided, it falls back to
logs/mcts_run.log) - LAMBDA_VAL: mixing weight for prior vs backpropagated value in UCT (default:
0.5) - OMEGA: exploration constant used in UCT (default:
1.414) - GENERATION_MAX_PARALLELISM: max parallelism for Layer 1/2 generation (default:
4)
Local model (vLLM) related:
- USE_LOCAL_MODEL: use local model for generation (default:
false) - USE_LOCAL_FOR_EVALUATION: use local model for evaluation/audits (default:
false) - LOCAL_MODEL_PATH: local model path
- required when
USE_LOCAL_MODEL=trueorUSE_LOCAL_FOR_EVALUATION=true(validated in code)
- required when
- EVALUATION_MODEL: evaluation model name (default:
gemini-2.5-flash-lite, used for API mode) - EVALUATION_MODEL_PATH: local evaluation model path
A typical run produces:
- Tree-structured logs: console and/or log file (
--log-file/LOG_FILE; default fallback islogs/mcts_run.log) - Best Path: the selected Depth 0 → 3 path with scores
- Final code: printed in logs as “FINAL GENERATED CODE”
- debug_responses/: saved Layer 1/2 prompts and raw responses (useful for debugging JSON repair/parsing)
The executor parses the objective value from STDOUT (via regex), so the Layer 3 prompt enforces printing:
Optimal objective value: {model.objVal}
semopt/
├── states/ # States (Problem, SolutionStrategy, MathModel, Code)
├── mcts/ # MCTS (Node/Tree/RolloutController)
├── generation/ # LLM interface and Layer 1/2/3 generators
├── processing/ # Screener / SemanticMerger / SMTMerger / CandidateScorer
├── evaluation/ # GurobiExecutor / forward audit / backward recon / scorer
├── utils/ # logger, prompt templates, JSON repair, etc.
├── config.py # env-based configuration
└── main.py # CLI entry point and run_semopt