From 9467ec4cc8b0b58c8245b0d860025f3cc57c3b76 Mon Sep 17 00:00:00 2001 From: Abhinavexist Date: Sat, 18 Jul 2026 04:00:17 +0530 Subject: [PATCH] housekeeping: forecast task + merge task/guard into system message (#6) - Add forecast to TASK_NAMES / TaskName: the server accepts forecast (ALLOWED_USER_TOOLS, messaging.ts:97-105), so it should be typeable. (#6.1) - prepare() merges / tags into the caller's first system message instead of prepending a second one. The server reads only the first system message (getAllRequiredMessages), so a prepended tag-only message displaced the caller's real system prompt. (#6.3) #6.2 (mixed-shape precontext fixture) is already added in #7. --- src/interfaze/_chat.py | 14 +++++++++++++- src/interfaze/_constants.py | 21 ++++++++++++++++++--- src/interfaze/_types.py | 21 ++++++++++++++++++--- tests/test_chat.py | 27 +++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 7 deletions(-) diff --git a/src/interfaze/_chat.py b/src/interfaze/_chat.py index de9ce15..82aa4b2 100644 --- a/src/interfaze/_chat.py +++ b/src/interfaze/_chat.py @@ -48,7 +48,19 @@ def prepare( tags = " ".join( t for t in (f"{task}" if task else None, guard_tag(guard) if guard else None) if t ) - msgs = [{"role": "system", "content": tags}, *messages] if tags else list(messages) + msgs = list(messages) + if tags: + idx = next( + (i for i, m in enumerate(msgs) if isinstance(m, dict) and m.get("role") == "system"), + None, + ) + if idx is not None and isinstance(msgs[idx].get("content"), str): + merged = dict(msgs[idx]) + existing = merged["content"] + merged["content"] = f"{tags}\n{existing}" if existing else tags + msgs[idx] = merged + else: + msgs = [{"role": "system", "content": tags}, *msgs] strip = isinstance(rf, dict) and rf.get("type") == "json_object" return msgs, (model or INTERFAZE_MODEL), rf, strip diff --git a/src/interfaze/_constants.py b/src/interfaze/_constants.py index 044b6cc..14e36e1 100644 --- a/src/interfaze/_constants.py +++ b/src/interfaze/_constants.py @@ -12,13 +12,28 @@ "scraper", "translate", "speech_to_text", + "forecast", ) # Guardrail categories (ALL enables everything). GUARD_CODES = ( - "S1", "S2", "S3", "S4", "S5", "S6", "S7", - "S8", "S9", "S10", "S11", "S12", "S13", "S14", - "S1_IMAGE", "S12_IMAGE", "S15_IMAGE", + "S1", + "S2", + "S3", + "S4", + "S5", + "S6", + "S7", + "S8", + "S9", + "S10", + "S11", + "S12", + "S13", + "S14", + "S1_IMAGE", + "S12_IMAGE", + "S15_IMAGE", "ALL", ) diff --git a/src/interfaze/_types.py b/src/interfaze/_types.py index 9292424..2ceae98 100644 --- a/src/interfaze/_types.py +++ b/src/interfaze/_types.py @@ -13,12 +13,27 @@ "scraper", "translate", "speech_to_text", + "forecast", ] GuardCode = Literal[ - "S1", "S2", "S3", "S4", "S5", "S6", "S7", - "S8", "S9", "S10", "S11", "S12", "S13", "S14", - "S1_IMAGE", "S12_IMAGE", "S15_IMAGE", + "S1", + "S2", + "S3", + "S4", + "S5", + "S6", + "S7", + "S8", + "S9", + "S10", + "S11", + "S12", + "S13", + "S14", + "S1_IMAGE", + "S12_IMAGE", + "S15_IMAGE", "ALL", ] diff --git a/tests/test_chat.py b/tests/test_chat.py index 892caa7..abef288 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -61,6 +61,33 @@ def test_guard_serialization(): assert "S1, S12_IMAGE" in last_body(route)["messages"][0]["content"] +@respx.mock +def test_forecast_is_a_valid_task(): + route = mock_json(BASIC) + Interfaze(api_key="t").chat.completions.create( + task="forecast", messages=[{"role": "user", "content": "x"}] + ) + assert "forecast" in last_body(route)["messages"][0]["content"] + + +@respx.mock +def test_tags_merge_into_existing_system_message(): + route = mock_json(TASK_OCR) + Interfaze(api_key="t").chat.completions.create( + task="ocr", + guard=["S1"], + messages=[ + {"role": "system", "content": "You are helpful."}, + {"role": "user", "content": "x"}, + ], + ) + systems = [m for m in last_body(route)["messages"] if m["role"] == "system"] + assert len(systems) == 1 + assert "ocr" in systems[0]["content"] + assert "S1" in systems[0]["content"] + assert "You are helpful." in systems[0]["content"] + + def test_task_plus_nonempty_schema_raises(): with pytest.raises(InterfazeError, match="non-empty"): Interfaze(api_key="t").chat.completions.create(