Metadata-Version: 2.4
Name: smartmdao-agents
Version: 0.1.2
Summary: Turns natural-language engineering tasks into validated, sandboxed, auditable workflows - powered by SmartMDAO.
Author: WGhami
Author-email: WGhami <prideful-dill-cape@duck.com>
License-Expression: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: License :: OSI Approved :: MIT License
Requires-Dist: cloudpickle>=3.1.2
Requires-Dist: smartmdao>=1.5.0
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# smartmdao-agents

[![CI](https://github.com/wghami/SmartMDAO-agents/actions/workflows/ci.yml/badge.svg)](https://github.com/wghami/SmartMDAO-agents/actions/workflows/ci.yml)
[![PyPI version](https://img.shields.io/pypi/v/smartmdao-agents.svg)](https://pypi.org/project/smartmdao-agents/)
[![Python versions](https://img.shields.io/pypi/pyversions/smartmdao-agents.svg)](https://pypi.org/project/smartmdao-agents/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/wghami/SmartMDAO-agents/blob/main/LICENSE)

Turns a natural-language 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](https://github.com/wghami/SmartMDAO), a Pythonic multidisciplinary analysis
& optimization (MDA/MDAO) framework — the engine underneath.

```
pip install smartmdao-agents
```

## Example: sizing a storage container

> 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).

```python
@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)],
)
```

Real, not hypothetical: this exact task description was sent to a freshly-spawned, isolated model
instance with no other context, and this is the exact code it produced - nothing hand-written,
nothing edited ([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)`) for
this problem - an independent check that the answer is actually right, not just that nothing
crashed. See [docs/dogfooding.md](docs/dogfooding.md) for the full methodology, three more real
examples, and the project roadmap.

## 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.

<p align="center">
  <img src="https://raw.githubusercontent.com/wghami/SmartMDAO-agents/b630b3a4f5bf03a8994119e4b15ff93589345cfd/assets/architecture.svg" alt="Architecture: call_model supplies the LLM call; run_agent_task builds the prompt, validates, sandboxes, and retries with the error fed back on failure" width="700"/>
</p>

<p align="center"><sub>Source: <a href="assets/architecture.mmd">assets/architecture.mmd</a> - regenerate the SVG at <a href="https://mermaid.ink">mermaid.ink</a> if the flow changes.</sub></p>

```python
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}'
```

## Known gotchas

Found by dogfooding (see [docs/dogfooding.md](docs/dogfooding.md)), fixed in `reference.md`, and
pinned down with regression tests so they can't silently regress.

| Gotcha | What goes wrong | Fix |
|---|---|---|
| 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. | Name the seeding step earlier alphabetically (e.g. `a_propose` / `b_review`). |
| Silent `objective` default | `OptimizationProblem.objective` defaults to the literal string `"objective"`, which almost never matches a real output name - fails with an opaque `KeyError` if left unset. | Always set `objective=` explicitly. |
| Constraint on a raw value | `ConstraintSpec` on a raw output only enforces `value >= 0`. A real threshold is silently ignored and the run still reports success - the most severe finding, since nothing looks wrong. | Encode the threshold as a slack (`value - threshold`) as the pipeline output, not the raw value. |

## Development

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

Depends on [`smartmdao`](https://pypi.org/project/smartmdao/) from PyPI (`>=1.5.0`, the first
release with non-numeric convergence support). License: [MIT](LICENSE).
