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(