Skip to main content

omagent

LLM-assisted modeling for OpenModelica — an open-source, headless Python agent that turns a natural-language task into a verified Modelica model:

natural language ──> generate ──> compile (omc) ──> simulate ──> verify physics
                        ▲                                            │
                        └──── structured error / verifier feedback ──┘

Unlike a plain code assistant, omagent closes the loop on physics, not just compilation: quantitative verifiers check trajectories against expected behavior (final values, settling windows, bounds, overshoot), and every failure — compiler diagnostics or physics complaints — is parsed into structured feedback for the next fix attempt.

Key capabilities:

  • Structured omc diagnostics — parses getErrorString(), simulation logs, and OMPython's exception format into records with severity, source location, and failure kind (syntax / lookup / type / balance / connect / initialization / runtime)
  • Environment-grounded fix hints — on Class X not found, omagent asks omc what the parent package actually contains (getClassNames) and puts near-miss suggestions into the fix prompt. This resolves the dominant observed failure mode: stale library knowledge (e.g. MSL 3.2 names such as Basic.EMF vs. MSL 4.x Basic.RotationalEMF)
  • Quantitative verification — reads CSV or Dymola-format .mat results; verifier complaints ("final value of x is 1.93, expected 2.0") drive tuning
  • Benchmark task ladder — 5 escalating, auto-gradable tasks with full transcript capture, in the format the OpenModelica benchmark discussion (OpenModelica#15385) calls for
  • Multi-run benchmarking — repeat runs for variance measurement and side-by-side cross-model comparison, aggregated into a single report
  • LLM-backend-agnostic — the loop depends on a one-method protocol; adapters ship for Anthropic and for any OpenAI-compatible endpoint (Ollama, LM Studio, llama.cpp, vLLM — local open-weight models included)
  • Tested — 101 unit tests run without OpenModelica installed; 4 integration tests validate against a live omc

Installation

Requires Python >= 3.10. The core package has zero hard dependencies; features are opt-in extras:

pip install -e .                    # parsers + loop only (no omc needed)
pip install -e ".[omc]"             # + OMPython (talk to a real omc)
pip install -e ".[results]"         # + scipy (.mat result files; CSV needs nothing)
pip install -e ".[llm]"             # + anthropic adapter
pip install -e ".[all]"             # everything, including pytest

To use it against a real compiler you need OpenModelica (tested with 1.26–1.27) with the Modelica Standard Library installed for omc:

echo 'installPackage(Modelica); getErrorString();' > /tmp/i.mos && omc /tmp/i.mos

Note: OMEdit installs the MSL for itself automatically; headless omc sessions do not. If models using Modelica.* fail with "Class ... not found", this is why.

Verify your setup:

pytest -m "not integration"   # unit tests, no omc required
pytest -m integration         # against your live omc (+MSL, scipy for .mat)

Quick start

from omagent import AgentLoop, OMSession, all_of, expect_bounds, expect_final
from omagent.llm import ClaudeLLM   # or any object with .propose(...)

# physics acceptance criteria — complaints feed back into the fix loop
verifier = all_of(
    expect_bounds("x", lo=-0.105, hi=0.105),
    expect_final("x", 0.0, atol=0.06, rtol=0.0),
)

loop = AgentLoop(
    OMSession(),                      # real omc via OMPython
    ClaudeLLM(),                      # needs ANTHROPIC_API_KEY
    max_attempts=4,
    simulate_options={"stopTime": 10.0, "outputFormat": "csv"},
    verifier=verifier,
)
result = loop.run(
    "A mass-spring-damper: m = 1 kg, c = 100 N/m, d = 1 N.s/m, released "
    "from x = 0.1 m at rest. Name position x and velocity v.")

print(result.success, result.model_name)
print(result.final_code)
for a in result.attempts:
    print(a.n, a.stage, a.complaint)

Local / open-weight models work through any OpenAI-compatible server (Ollama, LM Studio, llama.cpp server, vLLM) with no extra dependencies:

from omagent.llm import OpenAICompatLLM
llm = OpenAICompatLLM(model="qwen2.5-coder:14b")            # Ollama default URL
# llm = OpenAICompatLLM(model="...", base_url="http://localhost:1234/v1")  # LM Studio

Or bring your own LLM by implementing one method:

class MyLLM:
    def propose(self, task, previous_code, error_summary):
        # previous_code/error_summary are None on the first (fresh) call;
        # on retries they contain the failed model and structured feedback.
        return "... complete Modelica model ..."

Run the benchmark ladder

export ANTHROPIC_API_KEY=...
python examples/run_ladder.py                 # all 5 tiers
python examples/run_ladder.py --max-tier 3    # subset by difficulty
python examples/run_ladder.py --tasks dc_motor --model claude-opus-4-8

Tiers: (1) pure-equation dynamics, (2) MSL component composition, (3) hybrid events, (4) verifier-driven design — the requirement is given, the parameter is not, (5) multi-domain electro-mechanical. Per-task JSON transcripts (attempt history, diagnostics, code, LLM rounds) land in transcripts/, with summary.json aggregating results.

Compare models with repeated runs

from omagent import OMSession, run_comparison
from omagent.llm import ClaudeLLM, OpenAICompatLLM

comparison = run_comparison(
    session_factory=OMSession,          # fresh omc session per run
    llm_factories={
        "claude-sonnet": lambda: ClaudeLLM(model="claude-sonnet-4-6"),
        "local-qwen":    lambda: OpenAICompatLLM(model="qwen2.5-coder:14b"),
    },
    repeats=3,                          # runs per model for variance
)

Each (model, task, run) triple runs in isolation, so variance across repeats reflects LLM/omc nondeterminism rather than state contamination. Transcripts land in transcripts/<model>/rep<k>/; comparison.json aggregates pass rates per model and per task, plus mean/spread of attempts and wall time. Use --tasks/--max-tier equivalents via task_ids/max_tier, and verbose=True for progress and a final table.

Warning-level quality gates

Some omc diagnostics come as warnings yet mean the model is sloppy — under/over-specified initial conditions, inconsistent units, over-determined systems. Quality gates turn those into verifier-style complaints that feed the fix loop, without outright failing the operation:

from omagent import AgentLoop, OMSession, warning_gate_complaints

loop = AgentLoop(
    OMSession(), ClaudeLLM(), max_attempts=4,
    verifier=my_verifier,
    warning_gate=warning_gate_complaints,   # opt-in; None by default
)

Gated attempts report stage "quality" and the gate complaint is appended to the fix prompt's structured feedback. run_ladder(..., warning_gate=...) threads the gate through the benchmark so scores can be produced under either strictness. Custom gates are just callables over list[Diagnostic] -> Optional[str]; WARNING_GATE_PATTERNS is the default rule table you can extend.

Use pieces standalone

from omagent import OMSession, parse_error_string, summarize_for_llm, load_result

s = OMSession()
r = s.load_string(my_modelica_code)     # honest success verdict + diagnostics
print(summarize_for_llm(r.diagnostics)) # deduplicated digest for any prompt

sim = s.simulate("MyModel", stopTime=5.0, outputFormat="csv")
res = load_result(sim.value["resultFile"])
times, x = res.series("x")

Project layout

omagent/
  errors.py    # omc diagnostic parsing + classification
  session.py   # OMSession: testable wrapper over OMPython/omc
  loop.py      # AgentLoop + lookup-suggestion feedback
  results.py   # CSV/.mat readers + quantitative verifiers
  llm.py       # Anthropic adapter (protocol: bring your own)
  tasks.py     # benchmark task ladder definitions
  runner.py    # ladder execution + transcript persistence
examples/      # first_run.py, run_ladder.py
tests/         # 101 unit + 4 integration tests

Design notes

  • Testable by construction. OMSession talks to any object with sendExpression(); tests replay recorded omc output, so the full agent loop is unit-tested without a compiler or an API key.
  • Both OMPython contracts. Older OMPython returns and lets you read getErrorString(); newer OMPython raises OMCSessionException on error-level messages. Both yield identical structured failures.
  • Environment failures are not model failures. The ladder runner loads the MSL when a task requires it and reports load problems as environment outcomes with zero attempts charged to the LLM.

Roadmap

  • Optional MCP tool surface, composing with OMEdit's built-in MCP server
  • More ladder tiers targeting thermal/fluid domains and third-party libraries

License

BSD-3-Clause — see LICENSE.

Release files for omagent 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for omagent 0.3.0
File Size Uploaded
omagent-0.3.0.tar.gz 40.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for omagent 0.3.0
File Interpreter ABI Platform
omagent-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 68.1 kB

Release files / omagent-0.3.0.tar.gz

Download URL omagent-0.3.0.tar.gz
Size 40.8 kB
Tags Source
SHA-256 checksum
How to use checksums
8455c00f0ba7ce5ef6eea654585032acb1ec33e710cef42275bbb9142146b132
BLAKE2b-256 checksum
How to use checksums
113bce44f949479b466313f46fb069e04d2732462e35796a8524d376cdedfb0d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 7, 2026.

Transparency log

Release files / omagent-0.3.0-py3-none-any.whl

Download URL omagent-0.3.0-py3-none-any.whl
Size 27.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
86b1f71d8473c29b8f16469d134b0feea352934322539b5288f47255d23ef29e
BLAKE2b-256 checksum
How to use checksums
5ed83a299c89da42c09cb726560823c02075573412bc06c76346acd10f4b1a8b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page