Skip to main content

Turns plain-English engineering tasks into validated, sandboxed, auditable workflows - powered by SmartMDAO.

Project description

smartmdao-agents

CI PyPI version Python versions License: MIT

Turns a plain-English engineering task into a validated, sandboxed, auditable engineering workflow — a coupled analysis, a design study, an optimization under constraints. Hand it a task description and a way to call your LLM; it hands back a resolved result, or a structured reason why not.

Built on SmartMDAO, a Pythonic multidisciplinary analysis & optimization (MDA/MDAO) framework — the engine underneath.

pip install smartmdao-agents

Status: Phase 1 (MDA) and Phase 2 (MDAO) complete. A separate, fast-moving project on purpose

  • smartmdao stays a general-purpose, agent-agnostic runtime; this repo is the orchestration layer on top (prompts, validation rules, sandboxing).

How it works

This repo never calls an LLM itself - no SDK dependency, no provider lock-in. You supply call_model: a plain function taking a prompt string and returning the model's raw text response. Everything else - building the prompt from the curated reference doc, extracting code from the response, validating it, running it in a sandboxed subprocess, and retrying with the error fed back on failure - is handled for you.

runpipeline
    runpipeline -->|"status != success,\nattempts remaining"| retry
    retry --> extract
end

callmodel <-.-> extract
runpipeline --> result["RunRecord: status, outputs, error"]

-->

Architecture: call_model supplies the LLM call; run_agent_task builds the prompt, validates, sandboxes, and retries with the error fed back on failure

from smartmdao_agents import run_agent_task

def call_model(prompt: str) -> str:
    return my_llm_client.complete(prompt)  # wrap whatever you're already using

record = run_agent_task(
    "compute the safety factor of a structural bracket given the applied load, "
    "cross-sectional area, and material yield strength",
    {"load_n": 5000.0, "area_mm2": 120.0, "yield_strength_mpa": 250.0},
    call_model=call_model,
    max_attempts=3,
)
record.status   # "success" | "validation_failed" | "execution_failed" | "timeout"
record.outputs  # every resolved variable, or None on failure

Already have code (not fresh off an LLM call) and just want it validated/sandboxed/recorded? run_agent_pipeline(code, inputs) skips the prompt/extract/retry machinery - run_agent_task is built on top of it, not a replacement. It's also what the CLI uses:

smartmdao-agents run --file pipeline.py --inputs '{"width": 2.0}'

Examples

Both of these are real: the exact task description that was sent, the exact code an isolated model produced from it - nothing hand-written, nothing edited - and the verified result of actually running that code. See docs/dogfooding.md for the full methodology and all four blind-test results (these two, plus a linear-MDA and an unconstrained-MDAO task).

Non-numeric MDA — the capability that differentiates this from OpenMDAO/GEMSEO

Write a smartmdao pipeline that resolves which spacecraft sensors can be enabled within a power budget. There is a fixed candidate list of sensor names and a dict mapping each sensor name to its power draw (define these as module-level constants). One step should propose enabling sensors one at a time, in a fixed deterministic order, as long as the cumulative power draw of already-enabled sensors stays within a power_budget input (float), stopping once the next sensor would exceed the budget. A second step should review/pass through the enabled set unchanged. Converge these two steps to a fixed point using smartmdao's non-numeric convergence support. Accept power_budget as the only external input.

@pipeline.step(outputs=["selection"])
def a_propose(
    power_budget: float,
    reviewed_selection: SensorSelection = SensorSelection(enabled=frozenset()),
) -> SensorSelection:
    enabled = set(reviewed_selection.enabled)
    total = sum(SENSOR_POWER_DRAW[name] for name in enabled)
    for name in CANDIDATE_SENSORS:
        if name in enabled:
            continue
        draw = SENSOR_POWER_DRAW[name]
        if total + draw <= power_budget:
            enabled.add(name)
            total += draw
        else:
            break
    return SensorSelection(enabled=frozenset(enabled))

@pipeline.step(outputs=["reviewed_selection"])
def b_review(selection: SensorSelection) -> SensorSelection:
    return selection

Full fixture: tests/fixtures/safe/sensor_power_budget.py. With power_budget=50.0, converges in 2 iterations to all 5 sensors enabled - HybridSolver detected the a_propose ↔ b_review cycle automatically, on a coupling variable that's never a number anywhere.

Constrained MDAO

Write a smartmdao MDAO problem for designing an open-top rectangular box (no lid) with a fixed height of 10. Design variables are width and depth. Minimize the total material used (surface area: the bottom plus the four sides), subject to the box holding at least a volume of 200 (width * depth * height >= 200).

@pipeline.step(outputs=["surface_area", "volume_slack"])
def evaluate_box(width, depth, height):
    bottom = width * depth
    sides = 2 * (width * height) + 2 * (depth * height)
    surface_area = bottom + sides
    volume = width * depth * height
    return surface_area, volume - MIN_VOLUME  # a slack, not the raw volume

evaluator = PipelineEvaluator(pipeline, design_vars=["width", "depth"], constants={"height": HEIGHT})
problem = OptimizationProblem(
    evaluator=evaluator,
    initial_guess=[5.0, 5.0],
    bounds=[(0.1, 100.0), (0.1, 100.0)],
    objective="surface_area",
    constraints=[ConstraintSpec(name="volume_slack", kind="ineq", multiplier=1.0)],
)

Full fixture: tests/fixtures/safe/mdao_constrained_box.py. Converges to width = depth ≈ 4.472, matching the textbook-optimal square-base ratio (w = d = √(V/h)) - an independent check that the answer is actually right, not just that nothing crashed.

Known gotchas

Found by the dogfooding in docs/dogfooding.md, fixed in reference.md, and pinned down with regression tests so they can't silently regress:

  • Cycle execution order. Cyclic steps run in alphabetical order by function name, every iteration. A step seeding a cycle via a Python default has to sort before the step consuming its output, or the first iteration fails.
  • OptimizationProblem.objective defaults silently to the literal string "objective" - fails with an opaque KeyError if you forget to set it to a real output name.
  • A constraint must encode a slack (value - threshold), never the raw value. Constraining the raw value only enforces value >= 0 - the optimizer will silently ignore any real threshold and still report success. This was the most severe finding: a wrong answer with no error anywhere.

Roadmap

  • Phase 1 (done) — MDA, protocol-agnostic: curated reference doc, AST validator, subprocess sandbox with rlimits/timeout, JSON audit trail, one entry point + CLI.
  • Phase 2 (done) — MDAO as an additive toggle: problem: OptimizationProblem auto-detected alongside pipeline: Pipeline, backend selectable per call (scipy/openturns).
  • Caller-side glue (done)run_agent_task + the retry-with-error-feedback loop, behind a single caller-supplied call_model function.
  • Phase 3 (not started) — protocol adapters (MCP, LangChain, etc.) over the same contract.

Development

uv sync
uv run pytest        # 100% coverage enforced via pytest.ini

Depends on smartmdao from PyPI (>=1.5.0, the first release with non-numeric convergence support). License: MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

smartmdao_agents-0.1.1.tar.gz (21.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

smartmdao_agents-0.1.1-py3-none-any.whl (26.8 kB view details)

Uploaded Python 3

File details

Details for the file smartmdao_agents-0.1.1.tar.gz.

File metadata

  • Download URL: smartmdao_agents-0.1.1.tar.gz
  • Upload date:
  • Size: 21.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for smartmdao_agents-0.1.1.tar.gz
Algorithm Hash digest
SHA256 416503a2b5861a1a194dc61f4f894c585afe9c803888d00ecafec10116f6278c
MD5 4b17a51cafea5a853e1d5fc64a2f5cfb
BLAKE2b-256 ca798ea81a6e386cdb636d9613bc1670dbdebd16ba4552b1ac5843d22f9c1fd2

See more details on using hashes here.

File details

Details for the file smartmdao_agents-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for smartmdao_agents-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a6b59705b8f32460e04200d19b0d0d269219388278c2109c1fab9e580367b1a1
MD5 0a74bf405e26fe81c4051604e747e1af
BLAKE2b-256 82ccbdb70e42912a5599003a4f99cd5321c59cc49f410bed006c4df2272b01a4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page