review: add comments for manual iteration rationale, mask password in test print, clarify Anthropic URL/models support

This commit is contained in:
Roland Tannous 2026-03-30 19:38:43 +00:00
commit 379a9c920f
2 changed files with 22 additions and 4 deletions

View file

@ -117,6 +117,14 @@ class ExternalProviderClient:
)
return
# NOTE: manual __anext__ loop instead of `async for` is intentional.
# On Python 3.13 + httpcore 1.0.x, `async for` auto-calls aclose() on
# early exit (break/return/GeneratorExit) BEFORE our finally block runs.
# That propagates GeneratorExit into PoolByteStream.__aiter__() while it
# calls `await self.aclose()` inside `with AsyncShieldCancellation()`,
# triggering "RuntimeError: async generator ignored GeneratorExit".
# Fix: call response.aclose() FIRST (sets PoolByteStream._closed=True),
# then lines_gen.aclose() is a no-op and GeneratorExit re-raises cleanly.
lines_gen = response.aiter_lines().__aiter__()
try:
while True:
@ -217,7 +225,8 @@ class ExternalProviderClient:
}
)
else:
# Remote URL — Anthropic supports url source type natively
# Remote URL — Anthropic supports url source type natively.
# See: https://docs.anthropic.com/en/docs/build-with-claude/vision#url-based-images
anthropic_parts.append(
{
"type": "image",
@ -273,6 +282,7 @@ class ExternalProviderClient:
)
return
# NOTE: same manual __anext__ loop as stream_chat_completion — see comment there.
lines_gen = response.aiter_lines().__aiter__()
try:
while True:
@ -375,7 +385,12 @@ class ExternalProviderClient:
max_tokens: Optional[int] = None,
presence_penalty: float = 0.0,
) -> dict[str, Any]:
"""Non-streaming chat completion. Returns the full response dict."""
"""Non-streaming chat completion. Returns the full response dict.
Note: only valid for OpenAI-compatible providers. Anthropic requires its
own Messages API; use stream_chat_completion (with stream=False) instead
if a non-streaming Anthropic path is needed in the future.
"""
body: dict[str, Any] = {
"model": model,
"messages": messages,
@ -401,6 +416,10 @@ class ExternalProviderClient:
Returns a list of model dicts with at least 'id' and optionally
'created', 'owned_by', etc.
All supported providers expose a /models endpoint:
- OpenAI-compatible: standard {"data": [...]} response
- Anthropic: https://api.anthropic.com/v1/models same {"data": [...]} shape
"""
try:
response = await self._client.get(

View file

@ -148,8 +148,7 @@ def auth_headers() -> dict[str, str]:
token = change_resp.json()["access_token"]
print(
f"\n NOTE: Bootstrap password changed automatically.\n"
f" New password: {new_password!r}\n"
f" Set STUDIO_TEST_PASSWORD={new_password!r} for future runs."
f" Set STUDIO_TEST_PASSWORD to your STUDIO_TEST_NEW_PASSWORD value for future runs."
)
return {"Authorization": f"Bearer {token}"}