Skip to content

Commit 318ac3d

Browse files
✅ Add e2e tests for compound generation via OpenAI Responses API
1 parent 1c038db commit 318ac3d

File tree

1 file changed

+250
-0
lines changed

1 file changed

+250
-0
lines changed
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
"""End-to-end tests for OpenAI compound (Responses API) generation.
2+
3+
These tests make actual API calls to the OpenAI Responses API.
4+
Requires OPENAI_API_KEY environment variable to be set.
5+
6+
Run with: pytest tests/e2e/test_openai_compound.py -v --e2e
7+
"""
8+
9+
import os
10+
11+
import pytest
12+
13+
from tarash.tarash_gateway import (
14+
generate_compound,
15+
generate_compound_async,
16+
)
17+
from tarash.tarash_gateway.models import (
18+
CompoundGenerationConfig,
19+
CompoundGenerationRequest,
20+
CompoundGenerationResponse,
21+
TextOutputItem,
22+
ImageOutputItem,
23+
)
24+
25+
26+
# ==================== Fixtures ====================
27+
28+
29+
@pytest.fixture(scope="module")
30+
def openai_api_key():
31+
"""Get OpenAI API key from environment."""
32+
api_key = os.getenv("OPENAI_API_KEY")
33+
if not api_key:
34+
pytest.skip("OPENAI_API_KEY environment variable not set")
35+
return api_key
36+
37+
38+
# ==================== E2E Tests ====================
39+
40+
41+
@pytest.mark.e2e
42+
@pytest.mark.asyncio
43+
async def test_compound_text_only_async(openai_api_key):
44+
"""Test compound generation with text-only output (async).
45+
46+
This tests:
47+
- Basic text generation via Responses API
48+
- No tools used (empty allowed_tools)
49+
- Response structure validation
50+
"""
51+
config = CompoundGenerationConfig(
52+
provider="openai",
53+
model="gpt-4o-mini",
54+
api_key=openai_api_key,
55+
timeout=120,
56+
allowed_tools=[],
57+
)
58+
59+
request = CompoundGenerationRequest(
60+
prompt="What is 2 + 2? Answer in one word.",
61+
)
62+
63+
print(f"\nGenerating compound output with model: {config.model}")
64+
response = await generate_compound_async(config, request)
65+
66+
# Validate response structure
67+
assert isinstance(response, CompoundGenerationResponse)
68+
assert response.request_id is not None
69+
assert response.status == "completed"
70+
assert isinstance(response.raw_response, dict)
71+
72+
# Validate text output
73+
assert len(response.items) > 0
74+
assert any(isinstance(item, TextOutputItem) for item in response.items)
75+
assert len(response.text) > 0
76+
77+
# No images expected (no tools)
78+
assert len(response.images) == 0
79+
80+
print("Compound generation completed successfully")
81+
print(f" Request ID: {response.request_id}")
82+
print(f" Text: {response.text[:200]}")
83+
print(f" Items: {len(response.items)}")
84+
if response.cost:
85+
print(f" Cost: ${response.cost.amount_usd}")
86+
87+
88+
@pytest.mark.e2e
89+
def test_compound_text_only_sync(openai_api_key):
90+
"""Test compound generation with text-only output (sync).
91+
92+
This tests:
93+
- Sync execution
94+
- Text-only generation
95+
- Cost tracking
96+
"""
97+
config = CompoundGenerationConfig(
98+
provider="openai",
99+
model="gpt-4o-mini",
100+
api_key=openai_api_key,
101+
timeout=120,
102+
allowed_tools=[],
103+
)
104+
105+
request = CompoundGenerationRequest(
106+
prompt="Name three primary colors. One word each, comma separated.",
107+
)
108+
109+
print(f"\nGenerating compound output with model: {config.model}")
110+
response = generate_compound(config, request)
111+
112+
# Validate response
113+
assert isinstance(response, CompoundGenerationResponse)
114+
assert response.request_id is not None
115+
assert response.status == "completed"
116+
assert len(response.items) > 0
117+
assert len(response.text) > 0
118+
119+
print("Compound generation completed successfully")
120+
print(f" Request ID: {response.request_id}")
121+
print(f" Text: {response.text[:200]}")
122+
if response.cost:
123+
print(f" Cost: ${response.cost.amount_usd}")
124+
125+
126+
@pytest.mark.e2e
127+
@pytest.mark.asyncio
128+
async def test_compound_with_image_generation(openai_api_key):
129+
"""Test compound generation with image_generation tool (async).
130+
131+
This tests:
132+
- Text + image mixed output via Responses API
133+
- image_generation tool invocation
134+
- ImageOutputItem parsing
135+
- Mixed output ordering
136+
"""
137+
config = CompoundGenerationConfig(
138+
provider="openai",
139+
model="gpt-4o-mini",
140+
api_key=openai_api_key,
141+
timeout=300,
142+
allowed_tools=["image_generation"],
143+
)
144+
145+
request = CompoundGenerationRequest(
146+
prompt="Generate an image of a sunset over a mountain lake.",
147+
)
148+
149+
print(f"\nGenerating compound output with image tool: {config.model}")
150+
response = await generate_compound_async(config, request)
151+
152+
# Validate response structure
153+
assert isinstance(response, CompoundGenerationResponse)
154+
assert response.request_id is not None
155+
assert response.status == "completed"
156+
assert len(response.items) > 0
157+
158+
# Should have at least one image
159+
images = response.images
160+
assert len(images) >= 1, f"Expected at least 1 image, got {len(images)}"
161+
for img in images:
162+
assert isinstance(img, ImageOutputItem)
163+
# Image should have either url or base64
164+
assert img.url is not None or img.base64 is not None
165+
166+
print("Compound generation with image completed successfully")
167+
print(f" Request ID: {response.request_id}")
168+
print(f" Total items: {len(response.items)}")
169+
print(
170+
f" Text items: {len([i for i in response.items if isinstance(i, TextOutputItem)])}"
171+
)
172+
print(f" Image items: {len(images)}")
173+
if images[0].url:
174+
print(f" First image URL: {images[0].url[:100]}...")
175+
if images[0].revised_prompt:
176+
print(f" Revised prompt: {images[0].revised_prompt[:100]}...")
177+
if response.cost:
178+
print(f" Cost: ${response.cost.amount_usd}")
179+
180+
181+
@pytest.mark.e2e
182+
@pytest.mark.asyncio
183+
async def test_compound_with_instructions(openai_api_key):
184+
"""Test compound generation with system instructions.
185+
186+
This tests:
187+
- System instructions passed to the model
188+
- Instructions influence output
189+
"""
190+
config = CompoundGenerationConfig(
191+
provider="openai",
192+
model="gpt-4o-mini",
193+
api_key=openai_api_key,
194+
timeout=120,
195+
allowed_tools=[],
196+
instructions="You are a pirate. Always respond in pirate speak.",
197+
)
198+
199+
request = CompoundGenerationRequest(
200+
prompt="Say hello.",
201+
)
202+
203+
print(f"\nGenerating with instructions: {config.model}")
204+
response = await generate_compound_async(config, request)
205+
206+
assert isinstance(response, CompoundGenerationResponse)
207+
assert response.status == "completed"
208+
assert len(response.text) > 0
209+
210+
print("Instructions test completed")
211+
print(f" Text: {response.text[:200]}")
212+
213+
214+
@pytest.mark.e2e
215+
@pytest.mark.asyncio
216+
async def test_compound_with_multi_turn(openai_api_key):
217+
"""Test compound generation with multi-turn input messages.
218+
219+
This tests:
220+
- Structured input messages (not just a prompt string)
221+
- Multi-turn conversation support
222+
"""
223+
config = CompoundGenerationConfig(
224+
provider="openai",
225+
model="gpt-4o-mini",
226+
api_key=openai_api_key,
227+
timeout=120,
228+
allowed_tools=[],
229+
)
230+
231+
request = CompoundGenerationRequest(
232+
prompt="ignored when input is set",
233+
input=[
234+
{"role": "user", "content": "My name is Alice."},
235+
{"role": "assistant", "content": "Hello Alice!"},
236+
{"role": "user", "content": "What is my name?"},
237+
],
238+
)
239+
240+
print(f"\nGenerating with multi-turn input: {config.model}")
241+
response = await generate_compound_async(config, request)
242+
243+
assert isinstance(response, CompoundGenerationResponse)
244+
assert response.status == "completed"
245+
assert len(response.text) > 0
246+
# The model should recall the name
247+
assert "alice" in response.text.lower()
248+
249+
print("Multi-turn test completed")
250+
print(f" Text: {response.text[:200]}")

0 commit comments

Comments
 (0)