|
35 | 35 | from agents.exceptions import MaxTurnsExceeded, ModelBehaviorError |
36 | 36 | from agents.version import __version__ as OPENAI_AGENTS_VERSION |
37 | 37 |
|
38 | | -from openai.types.responses import Response, ResponseUsage |
| 38 | +from openai.types.responses import ( |
| 39 | + ResponseCreatedEvent, |
| 40 | + ResponseTextDeltaEvent, |
| 41 | + ResponseCompletedEvent, |
| 42 | + Response, |
| 43 | + ResponseUsage, |
| 44 | +) |
39 | 45 | from openai.types.responses.response_usage import ( |
40 | 46 | InputTokensDetails, |
41 | 47 | OutputTokensDetails, |
|
80 | 86 | ) |
81 | 87 |
|
82 | 88 |
|
| 89 | +async def EXAMPLE_STREAMED_RESPONSE(*args, **kwargs): |
| 90 | + yield ResponseCreatedEvent( |
| 91 | + response=Response( |
| 92 | + id="chat-id", |
| 93 | + output=[], |
| 94 | + parallel_tool_calls=False, |
| 95 | + tool_choice="none", |
| 96 | + tools=[], |
| 97 | + created_at=10000000, |
| 98 | + model="response-model-id", |
| 99 | + object="response", |
| 100 | + ), |
| 101 | + type="response.created", |
| 102 | + sequence_number=0, |
| 103 | + ) |
| 104 | + |
| 105 | + yield ResponseCompletedEvent( |
| 106 | + response=Response( |
| 107 | + id="chat-id", |
| 108 | + output=[ |
| 109 | + ResponseOutputMessage( |
| 110 | + id="message-id", |
| 111 | + content=[ |
| 112 | + ResponseOutputText( |
| 113 | + annotations=[], |
| 114 | + text="the model response", |
| 115 | + type="output_text", |
| 116 | + ), |
| 117 | + ], |
| 118 | + role="assistant", |
| 119 | + status="completed", |
| 120 | + type="message", |
| 121 | + ), |
| 122 | + ], |
| 123 | + parallel_tool_calls=False, |
| 124 | + tool_choice="none", |
| 125 | + tools=[], |
| 126 | + created_at=10000000, |
| 127 | + model="response-model-id", |
| 128 | + object="response", |
| 129 | + usage=ResponseUsage( |
| 130 | + input_tokens=20, |
| 131 | + input_tokens_details=InputTokensDetails( |
| 132 | + cached_tokens=5, |
| 133 | + ), |
| 134 | + output_tokens=10, |
| 135 | + output_tokens_details=OutputTokensDetails( |
| 136 | + reasoning_tokens=8, |
| 137 | + ), |
| 138 | + total_tokens=30, |
| 139 | + ), |
| 140 | + ), |
| 141 | + type="response.completed", |
| 142 | + sequence_number=1, |
| 143 | + ) |
| 144 | + |
| 145 | + |
83 | 146 | @pytest.fixture |
84 | 147 | def mock_usage(): |
85 | 148 | return Usage( |
@@ -1172,6 +1235,90 @@ def simple_test_tool(message: str) -> str: |
1172 | 1235 | assert ai_client_span2["data"]["gen_ai.usage.total_tokens"] == 25 |
1173 | 1236 |
|
1174 | 1237 |
|
| 1238 | +@pytest.mark.asyncio |
| 1239 | +async def test_hosted_mcp_tool_propagation_header_streamed(sentry_init, test_agent): |
| 1240 | + """ |
| 1241 | + Test responses API is given trace propagation headers with HostedMCPTool. |
| 1242 | + """ |
| 1243 | + |
| 1244 | + hosted_tool = HostedMCPTool( |
| 1245 | + tool_config={ |
| 1246 | + "type": "mcp", |
| 1247 | + "server_label": "test_server", |
| 1248 | + "server_url": "http://example.com/", |
| 1249 | + "headers": { |
| 1250 | + "baggage": "custom=data", |
| 1251 | + }, |
| 1252 | + }, |
| 1253 | + ) |
| 1254 | + |
| 1255 | + client = AsyncOpenAI(api_key="z") |
| 1256 | + client.responses._post = AsyncMock(return_value=EXAMPLE_RESPONSE) |
| 1257 | + |
| 1258 | + model = OpenAIResponsesModel(model="gpt-4", openai_client=client) |
| 1259 | + |
| 1260 | + agent_with_tool = test_agent.clone( |
| 1261 | + tools=[hosted_tool], |
| 1262 | + model=model, |
| 1263 | + ) |
| 1264 | + |
| 1265 | + sentry_init( |
| 1266 | + integrations=[OpenAIAgentsIntegration()], |
| 1267 | + traces_sample_rate=1.0, |
| 1268 | + release="d08ebdb9309e1b004c6f52202de58a09c2268e42", |
| 1269 | + ) |
| 1270 | + |
| 1271 | + with patch.object( |
| 1272 | + model._client.responses, |
| 1273 | + "create", |
| 1274 | + side_effect=EXAMPLE_STREAMED_RESPONSE, |
| 1275 | + ) as create, mock.patch( |
| 1276 | + "sentry_sdk.tracing_utils.Random.randrange", return_value=500000 |
| 1277 | + ): |
| 1278 | + with sentry_sdk.start_transaction( |
| 1279 | + name="/interactions/other-dogs/new-dog", |
| 1280 | + op="greeting.sniff", |
| 1281 | + trace_id="01234567890123456789012345678901", |
| 1282 | + ) as transaction: |
| 1283 | + result = agents.Runner.run_streamed( |
| 1284 | + agent_with_tool, |
| 1285 | + "Please use the simple test tool", |
| 1286 | + run_config=test_run_config, |
| 1287 | + ) |
| 1288 | + |
| 1289 | + async for event in result.stream_events(): |
| 1290 | + pass |
| 1291 | + |
| 1292 | + ai_client_span = transaction._span_recorder.spans[-1] |
| 1293 | + |
| 1294 | + args, kwargs = create.call_args |
| 1295 | + |
| 1296 | + assert "tools" in kwargs |
| 1297 | + assert len(kwargs["tools"]) == 1 |
| 1298 | + hosted_mcp_tool = kwargs["tools"][0] |
| 1299 | + |
| 1300 | + assert hosted_mcp_tool["headers"][ |
| 1301 | + "sentry-trace" |
| 1302 | + ] == "{trace_id}-{parent_span_id}-{sampled}".format( |
| 1303 | + trace_id=transaction.trace_id, |
| 1304 | + parent_span_id=ai_client_span.span_id, |
| 1305 | + sampled=1, |
| 1306 | + ) |
| 1307 | + |
| 1308 | + expected_outgoing_baggage = ( |
| 1309 | + "custom=data," |
| 1310 | + "sentry-trace_id=01234567890123456789012345678901," |
| 1311 | + "sentry-sample_rand=0.500000," |
| 1312 | + "sentry-environment=production," |
| 1313 | + "sentry-release=d08ebdb9309e1b004c6f52202de58a09c2268e42," |
| 1314 | + "sentry-transaction=/interactions/other-dogs/new-dog," |
| 1315 | + "sentry-sample_rate=1.0," |
| 1316 | + "sentry-sampled=true" |
| 1317 | + ) |
| 1318 | + |
| 1319 | + assert hosted_mcp_tool["headers"]["baggage"] == expected_outgoing_baggage |
| 1320 | + |
| 1321 | + |
1175 | 1322 | @pytest.mark.asyncio |
1176 | 1323 | async def test_hosted_mcp_tool_propagation_headers(sentry_init, test_agent): |
1177 | 1324 | """ |
|
0 commit comments