sysmlv2copilot 0.2.7__tar.gz → 0.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sysmlv2copilot-0.2.7/sysmlv2copilot.egg-info → sysmlv2copilot-0.2.8}/PKG-INFO +14 -1
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/PYPI_README.md +13 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/README.md +13 -11
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/pyproject.toml +1 -1
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/__init__.py +1 -1
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/cli.py +7 -3
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/client.py +8 -1
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/convenience.py +10 -8
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/input_utils.py +41 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8/sysmlv2copilot.egg-info}/PKG-INFO +14 -1
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/CHANGELOG.md +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/LICENSE +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/MANIFEST.in +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/examples/README.md +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/examples/client_sdk_basic.py +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/examples/repair_basic.py +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/examples/sdk_smoke_test.py +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/setup.cfg +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/__main__.py +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/exceptions.py +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/types.py +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot.egg-info/SOURCES.txt +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot.egg-info/dependency_links.txt +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot.egg-info/entry_points.txt +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot.egg-info/requires.txt +0 -0
- {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sysmlv2copilot
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Internal Python client SDK and CLI for the SysML refinement API used by the Visual Design and Engineering Lab at Carnegie Mellon University
|
|
5
5
|
Author: Chance LaVoie
|
|
6
6
|
License-Expression: MIT
|
|
@@ -103,12 +103,25 @@ repaired = repair(Path("broken.sysml"))
|
|
|
103
103
|
print(repaired)
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
If a `.txt` file contains SysML, `repair()` can use that too:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from pathlib import Path
|
|
110
|
+
|
|
111
|
+
from sysmlv2copilot import repair
|
|
112
|
+
|
|
113
|
+
repaired = repair(Path("broken.txt"))
|
|
114
|
+
print(repaired)
|
|
115
|
+
```
|
|
116
|
+
|
|
106
117
|
You can pass:
|
|
107
118
|
- raw strings
|
|
108
119
|
- `Path` objects
|
|
109
120
|
- path strings like `"broken.sysml"` or `"requirements.html"`
|
|
110
121
|
- open text files
|
|
111
122
|
|
|
123
|
+
Use `generate()` for natural-language requirements and `repair()` for SysML, regardless of whether the file extension is `.sysml` or `.txt`.
|
|
124
|
+
|
|
112
125
|
## CLI
|
|
113
126
|
|
|
114
127
|
Generate SysML from inline text:
|
|
@@ -62,12 +62,25 @@ repaired = repair(Path("broken.sysml"))
|
|
|
62
62
|
print(repaired)
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
If a `.txt` file contains SysML, `repair()` can use that too:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from pathlib import Path
|
|
69
|
+
|
|
70
|
+
from sysmlv2copilot import repair
|
|
71
|
+
|
|
72
|
+
repaired = repair(Path("broken.txt"))
|
|
73
|
+
print(repaired)
|
|
74
|
+
```
|
|
75
|
+
|
|
65
76
|
You can pass:
|
|
66
77
|
- raw strings
|
|
67
78
|
- `Path` objects
|
|
68
79
|
- path strings like `"broken.sysml"` or `"requirements.html"`
|
|
69
80
|
- open text files
|
|
70
81
|
|
|
82
|
+
Use `generate()` for natural-language requirements and `repair()` for SysML, regardless of whether the file extension is `.sysml` or `.txt`.
|
|
83
|
+
|
|
71
84
|
## CLI
|
|
72
85
|
|
|
73
86
|
Generate SysML from inline text:
|
|
@@ -8,7 +8,7 @@ This repository contains two distinct products:
|
|
|
8
8
|
|
|
9
9
|
This repository is API-only. Runtime code currently lives in the root-owned paths such as `app/`, `alembic/`, `sysmlv2copilot/`, `sysmlv2copilot_admin/`, and the main `scripts/` directory.
|
|
10
10
|
|
|
11
|
-
The hosted service accepts either natural-language requirements or SysML input, runs the compiler-in-the-loop workflow, returns SysML plus structured metadata, and stores each request with a
|
|
11
|
+
The hosted service accepts either natural-language requirements or SysML input, runs the compiler-in-the-loop workflow, returns SysML plus structured metadata, and stores each request with a full per-run artifact set including every refinement iteration.
|
|
12
12
|
|
|
13
13
|
The service itself is intentionally narrow:
|
|
14
14
|
- internal-only
|
|
@@ -135,7 +135,7 @@ cp .env.example .env
|
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
3. Fill in at least:
|
|
138
|
-
- one or both of `
|
|
138
|
+
- one or both of `OPENAI_API_KEY_5.1` (or shell-safe `OPENAI_API_KEY_5_1`) / `ANTHROPIC_API_KEY`
|
|
139
139
|
- one of `SYSIDE_VENV_PATH` or `SYSIDE_EXECUTABLE_PATH`
|
|
140
140
|
- `API_KEY_HASH_PEPPER`
|
|
141
141
|
- optionally a PostgreSQL `DATABASE_URL`
|
|
@@ -212,7 +212,7 @@ Example successful response:
|
|
|
212
212
|
"request_user_id": "usr_1234",
|
|
213
213
|
"metadata": {
|
|
214
214
|
"provider": "openai",
|
|
215
|
-
"upstream_model": "gpt-5-
|
|
215
|
+
"upstream_model": "gpt-5.1-codex",
|
|
216
216
|
"run_id": "run_20260312_161200_abcd",
|
|
217
217
|
"artifact_paths": {
|
|
218
218
|
"request_artifact": "/abs/path/request_artifact.json"
|
|
@@ -269,7 +269,7 @@ The hosted API now supports a repair-oriented workflow:
|
|
|
269
269
|
- run SysIDE immediately
|
|
270
270
|
- if the model already passes, return it unchanged with `iterations_completed = 0`
|
|
271
271
|
- if it fails, feed the broken SysML plus real compiler diagnostics into the repair loop
|
|
272
|
-
- persist the
|
|
272
|
+
- persist the full per-run artifact set used by generation requests, including iteration-level files
|
|
273
273
|
|
|
274
274
|
Repair metadata includes:
|
|
275
275
|
- `metadata.operation = "repair"`
|
|
@@ -301,16 +301,18 @@ More detail:
|
|
|
301
301
|
|
|
302
302
|
## Artifact Storage
|
|
303
303
|
|
|
304
|
-
Artifacts are stored under `ARTIFACT_ROOT`, defaulting to `./data/artifacts`. Each request gets a run directory with
|
|
304
|
+
Artifacts are stored under `ARTIFACT_ROOT`, defaulting to `./data/artifacts`. Each request gets a run directory with:
|
|
305
305
|
- `request_artifact.json`
|
|
306
|
+
- `original_input.txt`
|
|
307
|
+
- `final.sysml`
|
|
308
|
+
- `compiler_output.txt`
|
|
309
|
+
- `run_log.json`
|
|
310
|
+
- `run_meta.json`
|
|
311
|
+
- per-iteration prompt, candidate, raw-response, and compiler-output files under `iterations/`
|
|
306
312
|
|
|
307
|
-
|
|
308
|
-
- the original input text
|
|
309
|
-
- the final SysML output
|
|
310
|
-
- request statistics such as tokens, chars, duration, and iterations
|
|
311
|
-
- provider/model metadata and any terminal error
|
|
313
|
+
The request artifact stores the overall request summary, while `run_log.json` and the iteration files preserve the full loop history for later inspection or dataset creation.
|
|
312
314
|
|
|
313
|
-
Each
|
|
315
|
+
Each persisted file registers a row in the `artifacts` table. Temporary scratch files under `_work/` are still removed after the permanent artifacts are written.
|
|
314
316
|
|
|
315
317
|
## Tests
|
|
316
318
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sysmlv2copilot"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.8"
|
|
8
8
|
description = "Internal Python client SDK and CLI for the SysML refinement API used by the Visual Design and Engineering Lab at Carnegie Mellon University"
|
|
9
9
|
readme = "PYPI_README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -9,7 +9,7 @@ from typing import Any
|
|
|
9
9
|
from . import __version__
|
|
10
10
|
from .client import DEFAULT_BASE_URL, SysMLCopilot
|
|
11
11
|
from .exceptions import SysMLCopilotError
|
|
12
|
-
from .input_utils import ALLOWED_INPUT_SUFFIXES, coerce_input_text
|
|
12
|
+
from .input_utils import ALLOWED_INPUT_SUFFIXES, coerce_input_text, validate_operation_input
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def _add_operation_arguments(
|
|
@@ -105,8 +105,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
105
105
|
try:
|
|
106
106
|
with SysMLCopilot(api_key=args.api_key, base_url=args.base_url) as client:
|
|
107
107
|
if getattr(args, "operation", None) == "generate":
|
|
108
|
+
input_text = _read_input_text(args)
|
|
109
|
+
validate_operation_input(operation="generate", input_text=input_text)
|
|
108
110
|
response = client.responses.create(
|
|
109
|
-
input=
|
|
111
|
+
input=input_text,
|
|
110
112
|
model=args.model,
|
|
111
113
|
provider=args.provider,
|
|
112
114
|
upstream_model=args.upstream_model,
|
|
@@ -117,8 +119,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
117
119
|
_print_json(response.model_dump())
|
|
118
120
|
return 0
|
|
119
121
|
if getattr(args, "operation", None) == "repair":
|
|
122
|
+
input_text = _read_input_text(args)
|
|
123
|
+
validate_operation_input(operation="repair", input_text=input_text)
|
|
120
124
|
response = client.repairs.create(
|
|
121
|
-
input=
|
|
125
|
+
input=input_text,
|
|
122
126
|
model=args.model,
|
|
123
127
|
provider=args.provider,
|
|
124
128
|
upstream_model=args.upstream_model,
|
|
@@ -24,6 +24,7 @@ from .types import (
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
DEFAULT_BASE_URL = "https://sysml-refinement-api.lemontree-8a5c4550.eastus.azurecontainerapps.io"
|
|
27
|
+
DEFAULT_TIMEOUT = 600.0
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def _normalize_base_url(base_url: str) -> str:
|
|
@@ -136,7 +137,7 @@ class SysMLCopilot:
|
|
|
136
137
|
*,
|
|
137
138
|
api_key: str | None = None,
|
|
138
139
|
base_url: str | None = None,
|
|
139
|
-
timeout: float =
|
|
140
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
140
141
|
http_client: httpx.Client | None = None,
|
|
141
142
|
default_headers: Mapping[str, str] | None = None,
|
|
142
143
|
) -> None:
|
|
@@ -176,6 +177,12 @@ class SysMLCopilot:
|
|
|
176
177
|
def _request(self, method: str, path: str, *, json_body: Mapping[str, Any] | None = None) -> Any:
|
|
177
178
|
try:
|
|
178
179
|
response = self._client.request(method, path, json=json_body)
|
|
180
|
+
except httpx.ReadTimeout as exc:
|
|
181
|
+
raise APIConnectionError(
|
|
182
|
+
"Request timed out while waiting for the SysML API response. "
|
|
183
|
+
"For large inputs, try a longer timeout such as timeout=600. "
|
|
184
|
+
"Use generate() for natural-language requirements and repair() for SysML input."
|
|
185
|
+
) from exc
|
|
179
186
|
except httpx.HTTPError as exc:
|
|
180
187
|
raise APIConnectionError(f"Request failed: {exc}") from exc
|
|
181
188
|
if response.is_error:
|
|
@@ -3,8 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
from os import PathLike
|
|
4
4
|
from typing import Literal, Protocol, overload
|
|
5
5
|
|
|
6
|
-
from .client import SysMLCopilot
|
|
7
|
-
from .input_utils import coerce_input_text
|
|
6
|
+
from .client import DEFAULT_TIMEOUT, SysMLCopilot
|
|
7
|
+
from .input_utils import coerce_input_text, validate_operation_input
|
|
8
8
|
from .types import RepairObject, ResponseObject
|
|
9
9
|
|
|
10
10
|
|
|
@@ -30,7 +30,7 @@ def generate(
|
|
|
30
30
|
max_iters: int = 10,
|
|
31
31
|
max_total_tokens: int = 40_000,
|
|
32
32
|
temperature: float | None = None,
|
|
33
|
-
timeout: float =
|
|
33
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
34
34
|
return_response: Literal[False] = False,
|
|
35
35
|
) -> str:
|
|
36
36
|
...
|
|
@@ -48,7 +48,7 @@ def generate(
|
|
|
48
48
|
max_iters: int = 10,
|
|
49
49
|
max_total_tokens: int = 40_000,
|
|
50
50
|
temperature: float | None = None,
|
|
51
|
-
timeout: float =
|
|
51
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
52
52
|
return_response: Literal[True],
|
|
53
53
|
) -> ResponseObject:
|
|
54
54
|
...
|
|
@@ -65,10 +65,11 @@ def generate(
|
|
|
65
65
|
max_iters: int = 10,
|
|
66
66
|
max_total_tokens: int = 40_000,
|
|
67
67
|
temperature: float | None = None,
|
|
68
|
-
timeout: float =
|
|
68
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
69
69
|
return_response: bool = False,
|
|
70
70
|
) -> str | ResponseObject:
|
|
71
71
|
input_text = coerce_input_text(source)
|
|
72
|
+
validate_operation_input(operation="generate", input_text=input_text)
|
|
72
73
|
with SysMLCopilot(api_key=api_key, base_url=base_url, timeout=timeout) as client:
|
|
73
74
|
response = client.responses.create(
|
|
74
75
|
input=input_text,
|
|
@@ -98,7 +99,7 @@ def repair(
|
|
|
98
99
|
max_iters: int = 10,
|
|
99
100
|
max_total_tokens: int = 40_000,
|
|
100
101
|
temperature: float | None = None,
|
|
101
|
-
timeout: float =
|
|
102
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
102
103
|
return_response: Literal[False] = False,
|
|
103
104
|
) -> str:
|
|
104
105
|
...
|
|
@@ -116,7 +117,7 @@ def repair(
|
|
|
116
117
|
max_iters: int = 10,
|
|
117
118
|
max_total_tokens: int = 40_000,
|
|
118
119
|
temperature: float | None = None,
|
|
119
|
-
timeout: float =
|
|
120
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
120
121
|
return_response: Literal[True],
|
|
121
122
|
) -> RepairObject:
|
|
122
123
|
...
|
|
@@ -133,10 +134,11 @@ def repair(
|
|
|
133
134
|
max_iters: int = 10,
|
|
134
135
|
max_total_tokens: int = 40_000,
|
|
135
136
|
temperature: float | None = None,
|
|
136
|
-
timeout: float =
|
|
137
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
137
138
|
return_response: bool = False,
|
|
138
139
|
) -> str | RepairObject:
|
|
139
140
|
input_text = coerce_input_text(source)
|
|
141
|
+
validate_operation_input(operation="repair", input_text=input_text)
|
|
140
142
|
with SysMLCopilot(api_key=api_key, base_url=base_url, timeout=timeout) as client:
|
|
141
143
|
response = client.repairs.create(
|
|
142
144
|
input=input_text,
|
|
@@ -6,6 +6,20 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Protocol
|
|
7
7
|
|
|
8
8
|
ALLOWED_INPUT_SUFFIXES = {".txt", ".sysml", ".html", ".htm"}
|
|
9
|
+
_SYSML_MARKERS = (
|
|
10
|
+
"package ",
|
|
11
|
+
"part def",
|
|
12
|
+
"requirement def",
|
|
13
|
+
"constraint def",
|
|
14
|
+
"attribute ",
|
|
15
|
+
"public import",
|
|
16
|
+
"private import",
|
|
17
|
+
"port def",
|
|
18
|
+
"interface def",
|
|
19
|
+
"state def",
|
|
20
|
+
"action def",
|
|
21
|
+
"flow def",
|
|
22
|
+
)
|
|
9
23
|
_BLOCK_HTML_TAGS = {
|
|
10
24
|
"article",
|
|
11
25
|
"aside",
|
|
@@ -109,3 +123,30 @@ def _convert_html_to_text(raw_text: str) -> str:
|
|
|
109
123
|
parser.feed(raw_text)
|
|
110
124
|
parser.close()
|
|
111
125
|
return parser.get_text()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def looks_like_sysml(value: str) -> bool:
|
|
129
|
+
normalized = value.strip().lower()
|
|
130
|
+
if not normalized:
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
score = sum(1 for marker in _SYSML_MARKERS if marker in normalized)
|
|
134
|
+
if "{" in value and "}" in value:
|
|
135
|
+
score += 1
|
|
136
|
+
if ";" in value:
|
|
137
|
+
score += 1
|
|
138
|
+
return score >= 2
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def validate_operation_input(*, operation: str, input_text: str) -> None:
|
|
142
|
+
sysml_like = looks_like_sysml(input_text)
|
|
143
|
+
if operation == "repair" and not sysml_like:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
"repair() expects SysML input. This looks like natural-language requirements. "
|
|
146
|
+
"Use generate() instead. Function choice is based on file contents, not file extension."
|
|
147
|
+
)
|
|
148
|
+
if operation == "generate" and sysml_like:
|
|
149
|
+
raise ValueError(
|
|
150
|
+
"generate() expects natural-language requirements. This looks like SysML. "
|
|
151
|
+
"Use repair() instead. Function choice is based on file contents, not file extension."
|
|
152
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sysmlv2copilot
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Internal Python client SDK and CLI for the SysML refinement API used by the Visual Design and Engineering Lab at Carnegie Mellon University
|
|
5
5
|
Author: Chance LaVoie
|
|
6
6
|
License-Expression: MIT
|
|
@@ -103,12 +103,25 @@ repaired = repair(Path("broken.sysml"))
|
|
|
103
103
|
print(repaired)
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
If a `.txt` file contains SysML, `repair()` can use that too:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from pathlib import Path
|
|
110
|
+
|
|
111
|
+
from sysmlv2copilot import repair
|
|
112
|
+
|
|
113
|
+
repaired = repair(Path("broken.txt"))
|
|
114
|
+
print(repaired)
|
|
115
|
+
```
|
|
116
|
+
|
|
106
117
|
You can pass:
|
|
107
118
|
- raw strings
|
|
108
119
|
- `Path` objects
|
|
109
120
|
- path strings like `"broken.sysml"` or `"requirements.html"`
|
|
110
121
|
- open text files
|
|
111
122
|
|
|
123
|
+
Use `generate()` for natural-language requirements and `repair()` for SysML, regardless of whether the file extension is `.sysml` or `.txt`.
|
|
124
|
+
|
|
112
125
|
## CLI
|
|
113
126
|
|
|
114
127
|
Generate SysML from inline text:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|