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.
Files changed (26) hide show
  1. {sysmlv2copilot-0.2.7/sysmlv2copilot.egg-info → sysmlv2copilot-0.2.8}/PKG-INFO +14 -1
  2. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/PYPI_README.md +13 -0
  3. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/README.md +13 -11
  4. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/pyproject.toml +1 -1
  5. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/__init__.py +1 -1
  6. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/cli.py +7 -3
  7. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/client.py +8 -1
  8. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/convenience.py +10 -8
  9. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/input_utils.py +41 -0
  10. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8/sysmlv2copilot.egg-info}/PKG-INFO +14 -1
  11. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/CHANGELOG.md +0 -0
  12. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/LICENSE +0 -0
  13. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/MANIFEST.in +0 -0
  14. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/examples/README.md +0 -0
  15. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/examples/client_sdk_basic.py +0 -0
  16. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/examples/repair_basic.py +0 -0
  17. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/examples/sdk_smoke_test.py +0 -0
  18. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/setup.cfg +0 -0
  19. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/__main__.py +0 -0
  20. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/exceptions.py +0 -0
  21. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot/types.py +0 -0
  22. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot.egg-info/SOURCES.txt +0 -0
  23. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot.egg-info/dependency_links.txt +0 -0
  24. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot.egg-info/entry_points.txt +0 -0
  25. {sysmlv2copilot-0.2.7 → sysmlv2copilot-0.2.8}/sysmlv2copilot.egg-info/requires.txt +0 -0
  26. {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.7
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 single persisted artifact bundle.
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 `OPENAI_API_KEY` / `ANTHROPIC_API_KEY`
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-mini",
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 same request tracking and single-artifact bundle used by generation requests
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 a single persisted artifact:
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
- That JSON bundle stores:
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 request registers exactly one row in the `artifacts` table. Temporary iteration files used during SysIDE checks are removed before persistence.
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"
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"
@@ -26,7 +26,7 @@ from .types import (
26
26
  try:
27
27
  __version__ = version("sysmlv2copilot")
28
28
  except PackageNotFoundError:
29
- __version__ = "0.2.7"
29
+ __version__ = "0.2.8"
30
30
 
31
31
  __all__ = [
32
32
  "APIConnectionError",
@@ -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=_read_input_text(args),
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=_read_input_text(args),
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 = 120.0,
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 = 120.0,
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 = 120.0,
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 = 120.0,
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 = 120.0,
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.0,
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 = 120.0,
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.7
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