dhisana 0.0.1.dev270__py3-none-any.whl → 0.0.1.dev271__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dhisana/utils/generate_structured_output_internal.py +51 -17
- {dhisana-0.0.1.dev270.dist-info → dhisana-0.0.1.dev271.dist-info}/METADATA +1 -1
- {dhisana-0.0.1.dev270.dist-info → dhisana-0.0.1.dev271.dist-info}/RECORD +6 -6
- {dhisana-0.0.1.dev270.dist-info → dhisana-0.0.1.dev271.dist-info}/WHEEL +0 -0
- {dhisana-0.0.1.dev270.dist-info → dhisana-0.0.1.dev271.dist-info}/entry_points.txt +0 -0
- {dhisana-0.0.1.dev270.dist-info → dhisana-0.0.1.dev271.dist-info}/top_level.txt +0 -0
|
@@ -320,6 +320,9 @@ async def _get_structured_output_with_web_search(
|
|
|
320
320
|
Handles structured output with web search using Google search and URL scraping tools.
|
|
321
321
|
Works with both OpenAI and Azure OpenAI.
|
|
322
322
|
"""
|
|
323
|
+
logging.info(f"[WebSearch] Starting web search structured output: model={model}, effort={effort}")
|
|
324
|
+
logging.debug(f"[WebSearch] Prompt length: {len(prompt)} chars")
|
|
325
|
+
|
|
323
326
|
tools = [SEARCH_GOOGLE_TOOL, FETCH_URL_CONTENT_TOOL]
|
|
324
327
|
|
|
325
328
|
system_content = (
|
|
@@ -342,6 +345,7 @@ async def _get_structured_output_with_web_search(
|
|
|
342
345
|
|
|
343
346
|
while tool_iteration < max_tool_iterations:
|
|
344
347
|
tool_iteration += 1
|
|
348
|
+
logging.info(f"[WebSearch] Tool iteration {tool_iteration}/{max_tool_iterations}, conversation_history length: {len(conversation_history)}")
|
|
345
349
|
|
|
346
350
|
# Build request with current conversation history
|
|
347
351
|
request = {
|
|
@@ -358,7 +362,9 @@ async def _get_structured_output_with_web_search(
|
|
|
358
362
|
# Retry logic for rate limits
|
|
359
363
|
for attempt in range(2):
|
|
360
364
|
try:
|
|
365
|
+
logging.debug(f"[WebSearch] Sending request attempt {attempt + 1}")
|
|
361
366
|
completion = await client_async.responses.create(**request)
|
|
367
|
+
logging.info(f"[WebSearch] Response received, output items: {len(completion.output) if completion and completion.output else 0}")
|
|
362
368
|
break
|
|
363
369
|
except (RateLimitError, OpenAIError) as e:
|
|
364
370
|
is_rl = (
|
|
@@ -368,99 +374,127 @@ async def _get_structured_output_with_web_search(
|
|
|
368
374
|
)
|
|
369
375
|
if attempt == 0 and is_rl:
|
|
370
376
|
wait_time = 20 + random.uniform(0, 2.0)
|
|
371
|
-
logging.warning(f"Rate-limit hit (429). Waiting {wait_time:.2f}s then retrying.")
|
|
377
|
+
logging.warning(f"[WebSearch] Rate-limit hit (429). Waiting {wait_time:.2f}s then retrying.")
|
|
372
378
|
await asyncio.sleep(wait_time)
|
|
373
379
|
continue
|
|
374
|
-
logging.error(f"OpenAI API error: {e}")
|
|
375
|
-
raise HTTPException(status_code=502, detail="Error communicating with the OpenAI API
|
|
380
|
+
logging.error(f"[WebSearch] OpenAI API error: {e}")
|
|
381
|
+
raise HTTPException(status_code=502, detail=f"Error communicating with the OpenAI API: {str(e)}")
|
|
376
382
|
|
|
377
383
|
if not completion:
|
|
384
|
+
logging.error("[WebSearch] No completion returned after retries")
|
|
378
385
|
raise HTTPException(status_code=502, detail="OpenAI request failed.")
|
|
379
386
|
|
|
380
387
|
# Check for function tool calls in the response
|
|
381
388
|
tool_calls = []
|
|
382
389
|
for item in (completion.output or []):
|
|
383
390
|
item_type = getattr(item, "type", None)
|
|
391
|
+
logging.debug(f"[WebSearch] Output item type: {item_type}")
|
|
384
392
|
if item_type == "function_call":
|
|
385
393
|
tool_calls.append(item)
|
|
386
394
|
|
|
387
395
|
if not tool_calls:
|
|
388
396
|
# No tool calls, we have the final response
|
|
397
|
+
logging.info(f"[WebSearch] No tool calls in iteration {tool_iteration}, returning final response")
|
|
389
398
|
break
|
|
390
399
|
|
|
391
|
-
# Add the model's complete output to conversation history (includes function_call items)
|
|
392
|
-
# Per OpenAI docs: "input_list += response.output"
|
|
393
|
-
for item in (completion.output or []):
|
|
394
|
-
conversation_history.append(item)
|
|
395
|
-
|
|
396
400
|
# Execute tool calls and add results to conversation history
|
|
397
|
-
|
|
401
|
+
# Note: With store=False, we can't append raw output items (they have IDs that Azure
|
|
402
|
+
# can't resolve). We must create clean dicts with only the required fields.
|
|
403
|
+
logging.info(f"[WebSearch] Processing {len(tool_calls)} web search tool call(s) in iteration {tool_iteration}")
|
|
398
404
|
|
|
399
405
|
for tc in tool_calls:
|
|
400
406
|
func_name = getattr(tc, "name", "")
|
|
401
407
|
call_id = getattr(tc, "call_id", "")
|
|
402
408
|
args_str = getattr(tc, "arguments", "{}")
|
|
403
409
|
|
|
410
|
+
logging.info(f"[WebSearch] Tool call: {func_name}, call_id: {call_id}, args: {args_str[:200]}...")
|
|
411
|
+
|
|
404
412
|
try:
|
|
405
413
|
args = json.loads(args_str) if args_str else {}
|
|
406
|
-
except json.JSONDecodeError:
|
|
414
|
+
except json.JSONDecodeError as e:
|
|
415
|
+
logging.warning(f"[WebSearch] Failed to parse tool arguments: {e}")
|
|
407
416
|
args = {}
|
|
408
417
|
|
|
418
|
+
# Add the function_call to conversation history as a clean dict (no id field)
|
|
419
|
+
# Per OpenAI docs: function_call items need type, call_id, name, arguments
|
|
420
|
+
conversation_history.append({
|
|
421
|
+
"type": "function_call",
|
|
422
|
+
"call_id": call_id,
|
|
423
|
+
"name": func_name,
|
|
424
|
+
"arguments": args_str,
|
|
425
|
+
})
|
|
426
|
+
|
|
409
427
|
# Execute the tool
|
|
410
428
|
tool_result = await _execute_web_search_tool(func_name, args, tool_config)
|
|
411
429
|
|
|
412
|
-
# Add tool result to conversation history
|
|
430
|
+
# Add tool result to conversation history
|
|
413
431
|
conversation_history.append({
|
|
414
432
|
"type": "function_call_output",
|
|
415
433
|
"call_id": call_id,
|
|
416
434
|
"output": tool_result,
|
|
417
435
|
})
|
|
418
436
|
|
|
419
|
-
logging.info(f"Executed
|
|
437
|
+
logging.info(f"[WebSearch] Executed tool {func_name}, result length: {len(tool_result)}")
|
|
438
|
+
|
|
439
|
+
logging.info(f"[WebSearch] Tool loop completed after {tool_iteration} iteration(s)")
|
|
420
440
|
|
|
421
441
|
# Parse and return the final response
|
|
422
|
-
|
|
442
|
+
result, status = _parse_completion_response(completion, response_format, cache_key)
|
|
443
|
+
logging.info(f"[WebSearch] Parse result status: {status}")
|
|
444
|
+
return result, status
|
|
423
445
|
|
|
424
446
|
|
|
425
447
|
def _parse_completion_response(completion, response_format: BaseModel, cache_key: str):
|
|
426
448
|
"""Parse completion response and return structured output."""
|
|
449
|
+
logging.debug(f"[ParseResponse] Parsing completion, has output: {bool(completion and completion.output)}")
|
|
450
|
+
|
|
427
451
|
if completion and completion.output and len(completion.output) > 0:
|
|
452
|
+
logging.debug(f"[ParseResponse] Output items count: {len(completion.output)}")
|
|
428
453
|
raw_text = None
|
|
429
454
|
for out in completion.output:
|
|
455
|
+
logging.debug(f"[ParseResponse] Checking output item type: {out.type}")
|
|
430
456
|
if out.type == "message" and out.content:
|
|
431
457
|
for content_item in out.content:
|
|
432
458
|
if hasattr(content_item, "text"):
|
|
433
459
|
raw_text = content_item.text
|
|
460
|
+
logging.debug(f"[ParseResponse] Found text content, length: {len(raw_text) if raw_text else 0}")
|
|
434
461
|
break
|
|
435
462
|
else:
|
|
436
|
-
logging.warning("
|
|
463
|
+
logging.warning("[ParseResponse] Request refused: %s", str(content_item))
|
|
437
464
|
return "Request refused.", "FAIL"
|
|
438
465
|
if raw_text:
|
|
439
466
|
break
|
|
440
467
|
|
|
441
468
|
if not raw_text or not raw_text.strip():
|
|
469
|
+
logging.warning("[ParseResponse] No text returned (possibly refusal or empty response)")
|
|
442
470
|
return "No text returned (possibly refusal or empty response)", "FAIL"
|
|
443
471
|
|
|
472
|
+
logging.debug(f"[ParseResponse] Raw text (first 500 chars): {raw_text[:500] if raw_text else 'None'}...")
|
|
473
|
+
|
|
444
474
|
try:
|
|
445
475
|
parsed_obj = response_format.parse_raw(raw_text)
|
|
446
476
|
cache_output_tools.cache_output(
|
|
447
477
|
"get_structured_output_internal", cache_key, parsed_obj.json()
|
|
448
478
|
)
|
|
479
|
+
logging.info("[ParseResponse] Successfully parsed response")
|
|
449
480
|
return parsed_obj, "SUCCESS"
|
|
450
481
|
|
|
451
|
-
except Exception:
|
|
452
|
-
logging.warning("
|
|
482
|
+
except Exception as e:
|
|
483
|
+
logging.warning(f"[ParseResponse] Could not parse JSON from model output: {e}")
|
|
453
484
|
try:
|
|
454
485
|
fixed_json = repair_json(raw_text)
|
|
455
486
|
parsed_obj = response_format.parse_raw(fixed_json)
|
|
456
487
|
cache_output_tools.cache_output(
|
|
457
488
|
"get_structured_output_internal", cache_key, parsed_obj.json()
|
|
458
489
|
)
|
|
490
|
+
logging.info("[ParseResponse] Successfully parsed response after JSON repair")
|
|
459
491
|
return parsed_obj, "SUCCESS"
|
|
460
492
|
except Exception as e2:
|
|
461
|
-
logging.warning("JSON repair failed:
|
|
493
|
+
logging.warning(f"[ParseResponse] JSON repair also failed: {e2}")
|
|
494
|
+
logging.debug(f"[ParseResponse] Raw text that failed parsing: {raw_text[:1000] if raw_text else 'None'}...")
|
|
462
495
|
return raw_text, "FAIL"
|
|
463
496
|
else:
|
|
497
|
+
logging.warning("[ParseResponse] No output returned from completion")
|
|
464
498
|
return "No output returned", "FAIL"
|
|
465
499
|
|
|
466
500
|
|
|
@@ -45,7 +45,7 @@ dhisana/utils/generate_flow.py,sha256=QMn6bWo0nH0fBvy2Ebub1XfH5udnVAqsPsbIqCtQPX
|
|
|
45
45
|
dhisana/utils/generate_leads_salesnav.py,sha256=FG7q6GSm9IywZ9TgQnn5_N3QNfiI-Qk2gaO_3GS99nY,12236
|
|
46
46
|
dhisana/utils/generate_linkedin_connect_message.py,sha256=QxsxDiT-3eQOqAAbW13d0HGJXV36WYPvC-7Zsw_2VTI,10208
|
|
47
47
|
dhisana/utils/generate_linkedin_response_message.py,sha256=mWoSs5p2JSTIoFZFGm86x1kgs67J7dHPvGKZPzcdGdU,14569
|
|
48
|
-
dhisana/utils/generate_structured_output_internal.py,sha256=
|
|
48
|
+
dhisana/utils/generate_structured_output_internal.py,sha256=gJ6Bdwz2VexL505crMOgYYHOhmZo0MCghRoDkLP62WM,33654
|
|
49
49
|
dhisana/utils/google_custom_search.py,sha256=5rQ4uAF-hjFpd9ooJkd6CjRvSmhZHhqM0jfHItsbpzk,10071
|
|
50
50
|
dhisana/utils/google_oauth_tools.py,sha256=ReG5lCpXL3_e_s0yn6ai4U7B4-feOWHJVtbv_c0g0rE,28525
|
|
51
51
|
dhisana/utils/google_workspace_tools.py,sha256=fuV0UcvAqF9drLzj7-p6D5zh7d5jMXl1jNJTICk4XOo,50224
|
|
@@ -95,8 +95,8 @@ dhisana/workflow/agent.py,sha256=esv7_i_XuMkV2j1nz_UlsHov_m6X5WZZiZm_tG4OBHU,565
|
|
|
95
95
|
dhisana/workflow/flow.py,sha256=xWE3qQbM7j2B3FH8XnY3zOL_QXX4LbTW4ArndnEYJE0,1638
|
|
96
96
|
dhisana/workflow/task.py,sha256=HlWz9mtrwLYByoSnePOemBUBrMEcj7KbgNjEE1oF5wo,1830
|
|
97
97
|
dhisana/workflow/test.py,sha256=E7lRnXK0PguTNzyasHytLzTJdkqIPxG5_4qk4hMEeKc,3399
|
|
98
|
-
dhisana-0.0.1.
|
|
99
|
-
dhisana-0.0.1.
|
|
100
|
-
dhisana-0.0.1.
|
|
101
|
-
dhisana-0.0.1.
|
|
102
|
-
dhisana-0.0.1.
|
|
98
|
+
dhisana-0.0.1.dev271.dist-info/METADATA,sha256=t746AJjraGpmdydXXdzST-85-XvYPv2WueN1RfXINm8,1190
|
|
99
|
+
dhisana-0.0.1.dev271.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
100
|
+
dhisana-0.0.1.dev271.dist-info/entry_points.txt,sha256=jujxteZmNI9EkEaK-pOCoWuBujU8TCevdkfl9ZcKHek,49
|
|
101
|
+
dhisana-0.0.1.dev271.dist-info/top_level.txt,sha256=NETTHt6YifG_P7XtRHbQiXZlgSFk9Qh9aR-ng1XTf4s,8
|
|
102
|
+
dhisana-0.0.1.dev271.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|