Skip to content

Commit 53a767b

Browse files
authored
Merge pull request #803 from Pipelex/release/v0.23.3
Release v0.23.3
2 parents fbbcfb8 + 9827273 commit 53a767b

7 files changed

Lines changed: 118 additions & 5 deletions

File tree

.badges/tests.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"schemaVersion": 1,
33
"label": "tests",
4-
"message": "4044",
4+
"message": "4046",
55
"color": "blue",
66
"cacheSeconds": 300
77
}

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## [v0.23.3] - 2026-04-02
4+
5+
### Changed
6+
7+
- **Pipe spec output aliases**: `parse_pipe_spec` now accepts `output_concept` and `output_type` as aliases for the `output` field, with smart fallback when both alias and canonical field are present.
8+
9+
### Fixed
10+
11+
- **Gateway terms check**: Terms acceptance is now only required for inference operations, not for read-only operations like model spec fetching during validation.
12+
313
## [v0.23.2] - 2026-03-30
414

515
### Changed

pipelex/builder/operations/pipe_ops.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Any
99

1010
import tomlkit
11+
from pydantic import ValidationError
1112
from tomlkit.items import Table
1213

1314
from pipelex import log
@@ -27,6 +28,9 @@
2728
# Aliases that agents may use instead of "pipe_code". First found is promoted when canonical key is absent; extras are dropped.
2829
_PIPE_CODE_ALIASES = ("pipe", "the_pipe_code", "code", "name", "pipe_name", "pipe_ref")
2930

31+
# Aliases that agents may use instead of "output".
32+
_OUTPUT_ALIASES = ("output_concept", "output_type")
33+
3034

3135
def _normalize_sub_pipe_dict(data: dict[str, Any]) -> None:
3236
"""Normalize a step/branch dict: resolve pipe_code aliases and drop extraneous fields."""
@@ -106,6 +110,20 @@ def parse_pipe_spec(pipe_type: str, spec_data: dict[str, Any]) -> PipeSpec:
106110
else:
107111
spec_data.pop("expression")
108112

113+
# Accept output aliases (e.g. "output_concept", "output_type") → promote to "output".
114+
# When both "output" and an alias coexist, try the alias value first (agents often put
115+
# the correct concept name in the alias), falling back to the original "output" value.
116+
output_fallback: Any | None = None
117+
for alias in _OUTPUT_ALIASES:
118+
if alias in spec_data:
119+
alias_value = spec_data.pop(alias)
120+
if "output" not in spec_data:
121+
spec_data["output"] = alias_value
122+
else:
123+
output_fallback = spec_data["output"]
124+
spec_data["output"] = alias_value
125+
break
126+
109127
# Accept output as dict → extract the concept string
110128
# Agents sometimes structure the output like inputs (as a dict).
111129
# Handle {"type": "ConceptName"} and single-item dicts like {"result": "Text"}.
@@ -116,6 +134,13 @@ def parse_pipe_spec(pipe_type: str, spec_data: dict[str, Any]) -> PipeSpec:
116134
elif len(output_dict) == 1:
117135
spec_data["output"] = next(iter(output_dict.values()))
118136

137+
# When an output alias conflicted with an existing "output", try the alias value first
138+
# and fall back to the original value if validation fails.
139+
if output_fallback is not None:
140+
try:
141+
return spec_class.model_validate(spec_data)
142+
except ValidationError:
143+
spec_data["output"] = output_fallback
119144
return spec_class.model_validate(spec_data)
120145

121146

pipelex/pipelex.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,10 @@ def setup(
198198
gateway_model_specs = remote_config.backend_model_specs
199199
log.verbose("Using dummy remote config (inference not needed)")
200200
else:
201-
# Skip terms check for CI mode - automated CI/CD pipelines don't require human consent
202-
if integration_mode.requires_terms_acceptance:
201+
# Terms acceptance is only required for actual inference usage, not for
202+
# read-only operations like fetching model specs for validation.
203+
# Also skip for CI mode — automated pipelines don't require human consent.
204+
if needs_inference and integration_mode.requires_terms_acceptance:
203205
# Check if terms are accepted
204206
pipelex_service_config = load_pipelex_service_config_if_exists(config_dir=config_manager.global_config_dir)
205207
if pipelex_service_config is None or not pipelex_service_config.agreement.terms_accepted:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "pipelex"
3-
version = "0.23.2"
3+
version = "0.23.3"
44
description = "Execute composable AI methods declared in the MTHDS open standard"
55
authors = [{ name = "Evotis S.A.S.", email = "oss@pipelex.com" }]
66
maintainers = [{ name = "Pipelex staff", email = "oss@pipelex.com" }]
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Regression test: gateway terms check must be gated on needs_inference, not needs_model_specs.
2+
3+
Commands like ``pipelex-agent validate bundle`` use ``needs_inference=False, needs_model_specs=True``
4+
to load model specs for validation without actually calling inference APIs. These commands must
5+
NOT require gateway terms acceptance.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from typing import TYPE_CHECKING
11+
12+
import pytest
13+
14+
from pipelex.pipelex import Pipelex
15+
from pipelex.system.pipelex_service.exceptions import GatewayTermsNotAcceptedError
16+
from pipelex.system.runtime import IntegrationMode
17+
18+
if TYPE_CHECKING:
19+
from pytest_mock import MockerFixture
20+
21+
PIPELEX_MODULE = "pipelex.pipelex"
22+
23+
24+
class TestGatewayTermsCheck:
25+
"""Verify that the terms check in Pipelex.setup() respects needs_inference."""
26+
27+
@pytest.fixture
28+
def _gateway_enabled_terms_not_accepted(self, mocker: MockerFixture) -> None:
29+
"""Configure mocks: gateway is enabled but terms have NOT been accepted."""
30+
mocker.patch(f"{PIPELEX_MODULE}.is_pipelex_gateway_enabled", return_value=True)
31+
mocker.patch(f"{PIPELEX_MODULE}.load_pipelex_service_config_if_exists", return_value=None)
32+
33+
@pytest.mark.usefixtures("_gateway_enabled_terms_not_accepted")
34+
def test_needs_inference_true_raises_when_terms_not_accepted(
35+
self,
36+
) -> None:
37+
"""With needs_inference=True and terms not accepted, setup must raise GatewayTermsNotAcceptedError."""
38+
pipelex_instance = Pipelex.__new__(Pipelex)
39+
40+
with pytest.raises(GatewayTermsNotAcceptedError):
41+
pipelex_instance.setup(
42+
integration_mode=IntegrationMode.CLI,
43+
needs_inference=True,
44+
needs_model_specs=True,
45+
)
46+
47+
@pytest.mark.usefixtures("_gateway_enabled_terms_not_accepted")
48+
def test_needs_inference_false_does_not_raise_when_terms_not_accepted(
49+
self,
50+
mocker: MockerFixture,
51+
) -> None:
52+
"""With needs_inference=False and needs_model_specs=True, setup must NOT raise GatewayTermsNotAcceptedError.
53+
54+
It may fail later (e.g., during remote config fetch), but the terms check itself must be skipped.
55+
"""
56+
# Mock the remote config fetch so we don't hit the network
57+
mock_remote_config = mocker.MagicMock()
58+
mock_remote_config.backend_model_specs = {}
59+
mocker.patch(f"{PIPELEX_MODULE}.RemoteConfigFetcher.fetch_remote_config", return_value=mock_remote_config)
60+
61+
pipelex_instance = Pipelex.__new__(Pipelex)
62+
63+
# setup() will fail somewhere after the gateway check (telemetry, models, etc.)
64+
# but it must NOT fail with GatewayTermsNotAcceptedError
65+
try:
66+
pipelex_instance.setup(
67+
integration_mode=IntegrationMode.CLI,
68+
needs_inference=False,
69+
needs_model_specs=True,
70+
)
71+
except GatewayTermsNotAcceptedError:
72+
pytest.fail("setup() raised GatewayTermsNotAcceptedError even though needs_inference=False")
73+
except Exception: # noqa: S110
74+
# Expected: setup() will fail later in the init chain (telemetry, models, etc.)
75+
# We only care that it did NOT fail with GatewayTermsNotAcceptedError
76+
pass

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)