Skip to content

Commit eaa9261

Browse files
dpageclaude
andcommitted
Validate api_url and use incomplete_details from Responses API.
- Strip known endpoint suffixes (/chat/completions, /responses) from api_url in __init__ to prevent doubled paths if a user provides a full endpoint URL instead of a base URL. - Use incomplete_details.reason from the Responses API to properly distinguish between max_output_tokens and content_filter when the response status is 'incomplete', in both the non-streaming and streaming parsers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4959c75 commit eaa9261

1 file changed

Lines changed: 36 additions & 3 deletions

File tree

web/pgadmin/llm/providers/openai.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,13 @@ def __init__(self, api_key: Optional[str] = None,
6868
"""
6969
self._api_key = api_key or ''
7070
self._model = model or DEFAULT_MODEL
71-
self._base_url = (api_url or DEFAULT_API_BASE_URL).rstrip('/')
71+
base_url = (api_url or DEFAULT_API_BASE_URL).rstrip('/')
72+
# Strip known endpoint suffixes in case the user provided a full URL
73+
for suffix in ('/chat/completions', '/responses'):
74+
if base_url.endswith(suffix):
75+
base_url = base_url[:-len(suffix)].rstrip('/')
76+
break
77+
self._base_url = base_url
7278
self._use_responses_api = False
7379

7480
@property
@@ -543,14 +549,22 @@ def _parse_responses_response(self, data: dict) -> LLMResponse:
543549
arguments=arguments
544550
))
545551

546-
# Determine stop reason
552+
# Determine stop reason from status and incomplete_details
547553
status = data.get('status', '')
548554
if tool_calls:
549555
stop_reason = StopReason.TOOL_USE
550556
elif status == 'completed':
551557
stop_reason = StopReason.END_TURN
552558
elif status == 'incomplete':
553-
stop_reason = StopReason.MAX_TOKENS
559+
reason = data.get(
560+
'incomplete_details', {}
561+
).get('reason', '')
562+
if reason == 'content_filter':
563+
stop_reason = StopReason.STOP_SEQUENCE
564+
elif reason == 'max_output_tokens':
565+
stop_reason = StopReason.MAX_TOKENS
566+
else:
567+
stop_reason = StopReason.MAX_TOKENS
554568
else:
555569
stop_reason = StopReason.UNKNOWN
556570

@@ -566,6 +580,13 @@ def _parse_responses_response(self, data: dict) -> LLMResponse:
566580
if not content and not tool_calls:
567581
if stop_reason == StopReason.MAX_TOKENS:
568582
self._raise_max_tokens_error(usage.input_tokens)
583+
elif stop_reason == StopReason.STOP_SEQUENCE:
584+
raise LLMClientError(LLMError(
585+
message='Response blocked by content filter.',
586+
code='content_filter',
587+
provider=self.provider_name,
588+
retryable=False
589+
))
569590

570591
return LLMResponse(
571592
content=content,
@@ -822,6 +843,8 @@ def _read_responses_stream(
822843
tool_calls_data = {}
823844
model_name = self._model
824845
usage = Usage()
846+
resp_status = ''
847+
resp_incomplete = {}
825848

826849
while True:
827850
line_bytes = response.readline()
@@ -881,6 +904,8 @@ def _read_responses_stream(
881904
total_tokens=u.get('total_tokens', 0)
882905
)
883906
model_name = resp.get('model', model_name)
907+
resp_status = resp.get('status', '')
908+
resp_incomplete = resp.get('incomplete_details', {})
884909

885910
# Build final response
886911
content = ''.join(content_parts)
@@ -897,8 +922,16 @@ def _read_responses_stream(
897922
arguments=arguments
898923
))
899924

925+
# Determine stop reason from final response status
900926
if tool_calls:
901927
stop_reason = StopReason.TOOL_USE
928+
elif resp_status == 'incomplete':
929+
reason = resp_incomplete.get('reason', '') \
930+
if resp_incomplete else ''
931+
if reason == 'content_filter':
932+
stop_reason = StopReason.STOP_SEQUENCE
933+
else:
934+
stop_reason = StopReason.MAX_TOKENS
902935
elif content:
903936
stop_reason = StopReason.END_TURN
904937
else:

0 commit comments

Comments
 (0)