Files
PolyGateway/tests/e2e/test_openai_tools_live.py
T
iomgaa 9185eab8c9 feat: preserve governed OpenAI tool messages in 1.4.0
Add faithful single-choice chat results, tool stream activity, error-frame retries, and isolated protocol caching.

Reject empty cached completions with the same value predicate as transport, add regression coverage, and adopt Apache-2.0.
2026-10-01 14:32:19 -04:00

115 lines
4.2 KiB
Python

"""真实网关工具双轮冒烟;复用安全 Markdown 报告,不运行工具副作用。"""
import dataclasses
import json
import os
from pathlib import Path
from uuid import uuid4
import pytest
from dotenv import dotenv_values
from polygateway import GatewayClient, GatewaySettings
from tests.live_evidence import write_live_round
_ENV = {k: v for k, v in {**dotenv_values(".env"), **os.environ}.items() if v is not None}
pytestmark = [pytest.mark.slow, pytest.mark.requires_llm]
@pytest.mark.parametrize("stream", [True, False])
async def test_tools_two_rounds(stream):
"""已配置源中选首源,只改变测试隔离与 raw-only 意图,不收紧生产超时。"""
if not any(k.startswith("LLM__") and k.endswith("__API_KEY") for k in _ENV):
pytest.skip("未配置真实源,未覆盖")
settings = GatewaySettings.from_env("LLM", env=_ENV)
source = dataclasses.replace(settings.sources[0], enable_thinking=None, reasoning_effort=None)
settings = dataclasses.replace(
settings,
sources=(source,),
cache_backend="none",
cache_namespace=None,
cache_ttl_s=None,
telemetry_backend="none",
telemetry_auto_migrate=False,
limiter_backend="memory",
breaker_backend="memory",
hedge_after_s=None,
hedge_stall_after_s=None,
)
tools = [
{
"type": "function",
"function": {
"name": "echo_value",
"description": "Return the supplied value",
"parameters": {
"type": "object",
"properties": {"value": {"type": "string"}},
"required": ["value"],
},
},
}
]
messages = [
{
"role": "user",
"content": "Call echo_value with value hello. After receiving the tool result, reply with hello.",
}
]
status, error_type, counts = "FAIL", None, {}
try:
async with GatewayClient.from_settings(settings) as client:
first = await client.chat(
messages,
stream=stream,
response_mode="openai",
overlay={
"tools": tools,
"tool_choice": {"type": "function", "function": {"name": "echo_value"}},
},
)
completion = first.completion
assert completion is not None and completion.message["role"] == "assistant"
assert completion.response_id and completion.finish_reason == "tool_calls"
calls = completion.message["tool_calls"]
counts["tools"] = len(calls)
assert calls
messages.append(completion.message)
for call in calls:
assert call["function"]["name"] == "echo_value"
arguments = json.loads(call["function"]["arguments"])
assert arguments["value"] == "hello"
messages.append(
{"role": "tool", "tool_call_id": call["id"], "content": arguments["value"]}
)
second = await client.chat(
messages,
stream=stream,
response_mode="openai",
overlay={"tools": tools, "tool_choice": "none"},
)
assert (
second.completion is not None and second.completion.message["role"] == "assistant"
)
assert "hello" in (second.completion.message.get("content") or "").lower()
counts["attempts"] = first.call_stats.attempts + second.call_stats.attempts
status = "PASS"
except Exception as exc:
error_type = type(exc).__name__
raise
finally:
write_live_round(
Path("tests/outputs/issue29/live"),
run_id=uuid4().hex,
matrix_id="tools",
round_index=1,
safe_fields={
"status": status,
"stream": stream,
"error_type": error_type,
"counts": counts,
"planned_rounds": 2,
"completed_rounds": 2 if status == "PASS" else 0,
},
)