glaip-sdk 0.0.5b1__py3-none-any.whl → 0.0.7__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.
- glaip_sdk/__init__.py +1 -1
- glaip_sdk/_version.py +42 -19
- glaip_sdk/branding.py +3 -2
- glaip_sdk/cli/commands/__init__.py +1 -1
- glaip_sdk/cli/commands/agents.py +452 -285
- glaip_sdk/cli/commands/configure.py +14 -13
- glaip_sdk/cli/commands/mcps.py +30 -20
- glaip_sdk/cli/commands/models.py +5 -3
- glaip_sdk/cli/commands/tools.py +111 -106
- glaip_sdk/cli/display.py +48 -27
- glaip_sdk/cli/io.py +1 -1
- glaip_sdk/cli/main.py +26 -5
- glaip_sdk/cli/resolution.py +5 -4
- glaip_sdk/cli/utils.py +437 -188
- glaip_sdk/cli/validators.py +7 -2
- glaip_sdk/client/agents.py +276 -153
- glaip_sdk/client/base.py +69 -27
- glaip_sdk/client/tools.py +44 -26
- glaip_sdk/client/validators.py +154 -94
- glaip_sdk/config/constants.py +0 -2
- glaip_sdk/models.py +5 -4
- glaip_sdk/utils/__init__.py +7 -7
- glaip_sdk/utils/client_utils.py +191 -101
- glaip_sdk/utils/display.py +4 -2
- glaip_sdk/utils/general.py +8 -6
- glaip_sdk/utils/import_export.py +58 -25
- glaip_sdk/utils/rendering/formatting.py +12 -6
- glaip_sdk/utils/rendering/models.py +1 -1
- glaip_sdk/utils/rendering/renderer/base.py +523 -332
- glaip_sdk/utils/rendering/renderer/console.py +6 -5
- glaip_sdk/utils/rendering/renderer/debug.py +94 -52
- glaip_sdk/utils/rendering/renderer/stream.py +93 -48
- glaip_sdk/utils/rendering/steps.py +103 -39
- glaip_sdk/utils/rich_utils.py +1 -1
- glaip_sdk/utils/run_renderer.py +1 -1
- glaip_sdk/utils/serialization.py +9 -3
- glaip_sdk/utils/validation.py +2 -2
- glaip_sdk-0.0.7.dist-info/METADATA +183 -0
- glaip_sdk-0.0.7.dist-info/RECORD +55 -0
- glaip_sdk-0.0.5b1.dist-info/METADATA +0 -645
- glaip_sdk-0.0.5b1.dist-info/RECORD +0 -55
- {glaip_sdk-0.0.5b1.dist-info → glaip_sdk-0.0.7.dist-info}/WHEEL +0 -0
- {glaip_sdk-0.0.5b1.dist-info → glaip_sdk-0.0.7.dist-info}/entry_points.txt +0 -0
glaip_sdk/cli/validators.py
CHANGED
|
@@ -7,7 +7,9 @@ Authors:
|
|
|
7
7
|
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
from collections.abc import Callable
|
|
10
11
|
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
11
13
|
|
|
12
14
|
import click
|
|
13
15
|
|
|
@@ -189,7 +191,7 @@ def validate_api_key_cli(api_key: str) -> str:
|
|
|
189
191
|
raise click.ClickException(str(e))
|
|
190
192
|
|
|
191
193
|
|
|
192
|
-
def coerce_timeout_cli(value) -> int:
|
|
194
|
+
def coerce_timeout_cli(value: int | float | str) -> int:
|
|
193
195
|
"""Coerce timeout value to integer with CLI-friendly error handling.
|
|
194
196
|
|
|
195
197
|
Args:
|
|
@@ -208,7 +210,10 @@ def coerce_timeout_cli(value) -> int:
|
|
|
208
210
|
|
|
209
211
|
|
|
210
212
|
def validate_name_uniqueness_cli(
|
|
211
|
-
_client
|
|
213
|
+
_client: Any,
|
|
214
|
+
name: str,
|
|
215
|
+
resource_type: str,
|
|
216
|
+
finder_func: Callable[..., list[Any]],
|
|
212
217
|
) -> None:
|
|
213
218
|
"""Validate that a resource name is unique.
|
|
214
219
|
|
glaip_sdk/client/agents.py
CHANGED
|
@@ -42,6 +42,9 @@ from glaip_sdk.utils.validation import validate_agent_instruction
|
|
|
42
42
|
# API endpoints
|
|
43
43
|
AGENTS_ENDPOINT = "/agents/"
|
|
44
44
|
|
|
45
|
+
# SSE content type
|
|
46
|
+
SSE_CONTENT_TYPE = "text/event-stream"
|
|
47
|
+
|
|
45
48
|
# Set up module-level logger
|
|
46
49
|
logger = logging.getLogger("glaip_sdk.agents")
|
|
47
50
|
|
|
@@ -49,7 +52,12 @@ logger = logging.getLogger("glaip_sdk.agents")
|
|
|
49
52
|
class AgentClient(BaseClient):
|
|
50
53
|
"""Client for agent operations."""
|
|
51
54
|
|
|
52
|
-
def __init__(
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
*,
|
|
58
|
+
parent_client: BaseClient | None = None,
|
|
59
|
+
**kwargs: Any,
|
|
60
|
+
) -> None:
|
|
53
61
|
"""Initialize the agent client.
|
|
54
62
|
|
|
55
63
|
Args:
|
|
@@ -161,7 +169,7 @@ class AgentClient(BaseClient):
|
|
|
161
169
|
tools: list[str | Any] | None = None,
|
|
162
170
|
agents: list[str | Any] | None = None,
|
|
163
171
|
timeout: int = DEFAULT_AGENT_RUN_TIMEOUT,
|
|
164
|
-
**kwargs,
|
|
172
|
+
**kwargs: Any,
|
|
165
173
|
) -> dict[str, Any]:
|
|
166
174
|
"""Build payload for agent creation with proper LM selection and metadata handling.
|
|
167
175
|
|
|
@@ -365,7 +373,10 @@ class AgentClient(BaseClient):
|
|
|
365
373
|
agent_config.pop(key, None)
|
|
366
374
|
|
|
367
375
|
def _finalize_update_payload(
|
|
368
|
-
self,
|
|
376
|
+
self,
|
|
377
|
+
update_data: dict[str, Any],
|
|
378
|
+
current_agent: "Agent",
|
|
379
|
+
**kwargs: Any,
|
|
369
380
|
) -> dict[str, Any]:
|
|
370
381
|
"""Finalize the update payload with metadata and additional kwargs."""
|
|
371
382
|
# Handle metadata preservation
|
|
@@ -386,7 +397,7 @@ class AgentClient(BaseClient):
|
|
|
386
397
|
name: str | None = None,
|
|
387
398
|
instruction: str | None = None,
|
|
388
399
|
model: str | None = None,
|
|
389
|
-
**kwargs,
|
|
400
|
+
**kwargs: Any,
|
|
390
401
|
) -> dict[str, Any]:
|
|
391
402
|
"""Build payload for agent update with proper LM selection and current state preservation.
|
|
392
403
|
|
|
@@ -434,7 +445,7 @@ class AgentClient(BaseClient):
|
|
|
434
445
|
tools: list[str | Any] | None = None,
|
|
435
446
|
agents: list[str | Any] | None = None,
|
|
436
447
|
timeout: int = DEFAULT_AGENT_RUN_TIMEOUT,
|
|
437
|
-
**kwargs,
|
|
448
|
+
**kwargs: Any,
|
|
438
449
|
) -> "Agent":
|
|
439
450
|
"""Create a new agent."""
|
|
440
451
|
# Client-side validation
|
|
@@ -470,7 +481,7 @@ class AgentClient(BaseClient):
|
|
|
470
481
|
name: str | None = None,
|
|
471
482
|
instruction: str | None = None,
|
|
472
483
|
model: str | None = None,
|
|
473
|
-
**kwargs,
|
|
484
|
+
**kwargs: Any,
|
|
474
485
|
) -> "Agent":
|
|
475
486
|
"""Update an existing agent."""
|
|
476
487
|
# First, get the current agent data
|
|
@@ -493,39 +504,67 @@ class AgentClient(BaseClient):
|
|
|
493
504
|
"""Delete an agent."""
|
|
494
505
|
self._request("DELETE", f"/agents/{agent_id}")
|
|
495
506
|
|
|
496
|
-
def
|
|
497
|
-
self,
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
507
|
+
def _prepare_sync_request_data(
|
|
508
|
+
self,
|
|
509
|
+
message: str,
|
|
510
|
+
files: list[str | BinaryIO] | None,
|
|
511
|
+
tty: bool,
|
|
512
|
+
**kwargs: Any,
|
|
513
|
+
) -> tuple[dict | None, dict | None, list | None, dict, Any | None]:
|
|
514
|
+
"""Prepare request data for synchronous agent runs with renderer support.
|
|
515
|
+
|
|
516
|
+
Args:
|
|
517
|
+
message: Message to send
|
|
518
|
+
files: Optional files to include
|
|
519
|
+
tty: Whether to enable TTY mode
|
|
520
|
+
**kwargs: Additional request parameters
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
Tuple of (payload, data_payload, files_payload, headers, multipart_data)
|
|
524
|
+
"""
|
|
525
|
+
headers = {"Accept": SSE_CONTENT_TYPE}
|
|
502
526
|
|
|
503
527
|
if files:
|
|
528
|
+
# Handle multipart data for file uploads
|
|
504
529
|
multipart_data = prepare_multipart_data(message, files)
|
|
505
|
-
# Inject optional multipart extras expected by backend
|
|
506
530
|
if "chat_history" in kwargs and kwargs["chat_history"] is not None:
|
|
507
531
|
multipart_data.data["chat_history"] = kwargs["chat_history"]
|
|
508
532
|
if "pii_mapping" in kwargs and kwargs["pii_mapping"] is not None:
|
|
509
533
|
multipart_data.data["pii_mapping"] = kwargs["pii_mapping"]
|
|
510
534
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
535
|
+
return (
|
|
536
|
+
None,
|
|
537
|
+
multipart_data.data,
|
|
538
|
+
multipart_data.files,
|
|
539
|
+
headers,
|
|
540
|
+
multipart_data,
|
|
541
|
+
)
|
|
518
542
|
else:
|
|
519
|
-
payload
|
|
543
|
+
# Simple JSON payload for text-only requests
|
|
544
|
+
payload = {"input": message, "stream": True, **kwargs}
|
|
520
545
|
if tty:
|
|
521
546
|
payload["tty"] = True
|
|
522
|
-
payload
|
|
523
|
-
|
|
524
|
-
|
|
547
|
+
return payload, None, None, headers, None
|
|
548
|
+
|
|
549
|
+
def _get_timeout_values(
|
|
550
|
+
self, timeout: float | None, **kwargs: Any
|
|
551
|
+
) -> tuple[float, float]:
|
|
552
|
+
"""Get request timeout and execution timeout values.
|
|
553
|
+
|
|
554
|
+
Args:
|
|
555
|
+
timeout: Request timeout (overrides instance timeout)
|
|
556
|
+
**kwargs: Additional parameters including execution timeout
|
|
525
557
|
|
|
526
|
-
|
|
558
|
+
Returns:
|
|
559
|
+
Tuple of (request_timeout, execution_timeout)
|
|
560
|
+
"""
|
|
561
|
+
request_timeout = timeout or self.timeout
|
|
562
|
+
execution_timeout = kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
563
|
+
return request_timeout, execution_timeout
|
|
527
564
|
|
|
528
|
-
def _create_renderer(
|
|
565
|
+
def _create_renderer(
|
|
566
|
+
self, renderer: RichStreamRenderer | None, **kwargs: Any
|
|
567
|
+
) -> RichStreamRenderer:
|
|
529
568
|
"""Create appropriate renderer based on configuration."""
|
|
530
569
|
if isinstance(renderer, RichStreamRenderer):
|
|
531
570
|
return renderer
|
|
@@ -544,7 +583,7 @@ class AgentClient(BaseClient):
|
|
|
544
583
|
else:
|
|
545
584
|
return self._create_default_renderer(verbose)
|
|
546
585
|
|
|
547
|
-
def _create_silent_renderer(self):
|
|
586
|
+
def _create_silent_renderer(self) -> RichStreamRenderer:
|
|
548
587
|
"""Create a silent renderer that suppresses all output."""
|
|
549
588
|
silent_config = RendererConfig(
|
|
550
589
|
live=False,
|
|
@@ -558,7 +597,7 @@ class AgentClient(BaseClient):
|
|
|
558
597
|
verbose=False,
|
|
559
598
|
)
|
|
560
599
|
|
|
561
|
-
def _create_minimal_renderer(self):
|
|
600
|
+
def _create_minimal_renderer(self) -> RichStreamRenderer:
|
|
562
601
|
"""Create a minimal renderer with basic output."""
|
|
563
602
|
minimal_config = RendererConfig(
|
|
564
603
|
live=False,
|
|
@@ -572,7 +611,7 @@ class AgentClient(BaseClient):
|
|
|
572
611
|
verbose=False,
|
|
573
612
|
)
|
|
574
613
|
|
|
575
|
-
def _create_verbose_renderer(self):
|
|
614
|
+
def _create_verbose_renderer(self) -> RichStreamRenderer:
|
|
576
615
|
"""Create a verbose renderer for detailed output."""
|
|
577
616
|
verbose_config = RendererConfig(
|
|
578
617
|
theme="dark",
|
|
@@ -587,7 +626,7 @@ class AgentClient(BaseClient):
|
|
|
587
626
|
verbose=True,
|
|
588
627
|
)
|
|
589
628
|
|
|
590
|
-
def _create_default_renderer(self, verbose: bool):
|
|
629
|
+
def _create_default_renderer(self, verbose: bool) -> RichStreamRenderer:
|
|
591
630
|
"""Create the default renderer."""
|
|
592
631
|
if verbose:
|
|
593
632
|
return self._create_verbose_renderer()
|
|
@@ -595,22 +634,22 @@ class AgentClient(BaseClient):
|
|
|
595
634
|
default_config = RendererConfig(show_delegate_tool_panels=True)
|
|
596
635
|
return RichStreamRenderer(console=_Console(), cfg=default_config)
|
|
597
636
|
|
|
598
|
-
def
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
"""Process streaming events and accumulate response."""
|
|
602
|
-
final_text = ""
|
|
603
|
-
stats_usage = {}
|
|
604
|
-
started_monotonic = None
|
|
605
|
-
finished_monotonic = None
|
|
606
|
-
meta = {
|
|
637
|
+
def _initialize_stream_metadata(self, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
638
|
+
"""Initialize stream metadata."""
|
|
639
|
+
return {
|
|
607
640
|
"agent_name": kwargs.get("agent_name", ""),
|
|
608
641
|
"model": kwargs.get("model"),
|
|
609
642
|
"run_id": None,
|
|
610
643
|
"input_message": "", # Will be set from kwargs if available
|
|
611
644
|
}
|
|
612
645
|
|
|
613
|
-
|
|
646
|
+
def _capture_request_id(
|
|
647
|
+
self,
|
|
648
|
+
stream_response: httpx.Response,
|
|
649
|
+
meta: dict[str, Any],
|
|
650
|
+
renderer: RichStreamRenderer,
|
|
651
|
+
) -> None:
|
|
652
|
+
"""Capture request ID from response headers."""
|
|
614
653
|
req_id = stream_response.headers.get(
|
|
615
654
|
"x-request-id"
|
|
616
655
|
) or stream_response.headers.get("x-run-id")
|
|
@@ -618,50 +657,98 @@ class AgentClient(BaseClient):
|
|
|
618
657
|
meta["run_id"] = req_id
|
|
619
658
|
renderer.on_start(meta)
|
|
620
659
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
660
|
+
def _should_start_timer(self, ev: dict[str, Any]) -> bool:
|
|
661
|
+
"""Check if timer should be started for this event."""
|
|
662
|
+
return "content" in ev or "status" in ev or ev.get("metadata")
|
|
663
|
+
|
|
664
|
+
def _handle_content_event(self, ev: dict[str, Any], final_text: str) -> str:
|
|
665
|
+
"""Handle content events."""
|
|
666
|
+
content = ev.get("content", "")
|
|
667
|
+
if not content.startswith("Artifact received:"):
|
|
668
|
+
return content
|
|
669
|
+
return final_text
|
|
670
|
+
|
|
671
|
+
def _handle_usage_event(
|
|
672
|
+
self, ev: dict[str, Any], stats_usage: dict[str, Any]
|
|
673
|
+
) -> None:
|
|
674
|
+
"""Handle usage events."""
|
|
675
|
+
stats_usage.update(ev.get("usage") or {})
|
|
676
|
+
|
|
677
|
+
def _handle_run_info_event(
|
|
678
|
+
self, ev: dict[str, Any], meta: dict[str, Any], renderer: RichStreamRenderer
|
|
679
|
+
) -> None:
|
|
680
|
+
"""Handle run info events."""
|
|
681
|
+
if ev.get("model"):
|
|
682
|
+
meta["model"] = ev["model"]
|
|
683
|
+
renderer.on_start(meta)
|
|
684
|
+
if ev.get("run_id"):
|
|
685
|
+
meta["run_id"] = ev["run_id"]
|
|
686
|
+
renderer.on_start(meta)
|
|
687
|
+
|
|
688
|
+
def _process_single_event(
|
|
689
|
+
self,
|
|
690
|
+
event: dict[str, Any],
|
|
691
|
+
renderer: RichStreamRenderer,
|
|
692
|
+
final_text: str,
|
|
693
|
+
stats_usage: dict[str, Any],
|
|
694
|
+
meta: dict[str, Any],
|
|
695
|
+
) -> tuple[str, dict[str, Any]]:
|
|
696
|
+
"""Process a single streaming event."""
|
|
697
|
+
try:
|
|
698
|
+
ev = json.loads(event["data"])
|
|
699
|
+
except json.JSONDecodeError:
|
|
700
|
+
logger.debug("Non-JSON SSE fragment skipped")
|
|
701
|
+
return final_text, stats_usage
|
|
702
|
+
|
|
703
|
+
kind = (ev.get("metadata") or {}).get("kind")
|
|
704
|
+
renderer.on_event(ev)
|
|
705
|
+
|
|
706
|
+
# Skip artifacts from content accumulation
|
|
707
|
+
if kind == "artifact":
|
|
708
|
+
return final_text, stats_usage
|
|
709
|
+
|
|
710
|
+
# Handle different event types
|
|
711
|
+
if kind == "final_response" and ev.get("content"):
|
|
712
|
+
final_text = ev.get("content", "")
|
|
713
|
+
elif ev.get("content"):
|
|
714
|
+
final_text = self._handle_content_event(ev, final_text)
|
|
715
|
+
elif kind == "usage":
|
|
716
|
+
self._handle_usage_event(ev, stats_usage)
|
|
717
|
+
elif kind == "run_info":
|
|
718
|
+
self._handle_run_info_event(ev, meta, renderer)
|
|
719
|
+
|
|
720
|
+
return final_text, stats_usage
|
|
627
721
|
|
|
722
|
+
def _process_stream_events(
|
|
723
|
+
self,
|
|
724
|
+
stream_response: httpx.Response,
|
|
725
|
+
renderer: RichStreamRenderer,
|
|
726
|
+
timeout_seconds: float,
|
|
727
|
+
agent_name: str | None,
|
|
728
|
+
kwargs: dict[str, Any],
|
|
729
|
+
) -> tuple[str, dict[str, Any], float | None, float | None]:
|
|
730
|
+
"""Process streaming events and accumulate response."""
|
|
731
|
+
final_text = ""
|
|
732
|
+
stats_usage = {}
|
|
733
|
+
started_monotonic = None
|
|
734
|
+
finished_monotonic = None
|
|
735
|
+
|
|
736
|
+
meta = self._initialize_stream_metadata(kwargs)
|
|
737
|
+
self._capture_request_id(stream_response, meta, renderer)
|
|
738
|
+
|
|
739
|
+
for event in iter_sse_events(stream_response, timeout_seconds, agent_name):
|
|
628
740
|
# Start timer at first meaningful event
|
|
629
|
-
if started_monotonic is None
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
# Accumulate assistant content
|
|
642
|
-
if ev.get("content"):
|
|
643
|
-
if not ev["content"].startswith("Artifact received:"):
|
|
644
|
-
final_text = ev["content"]
|
|
645
|
-
continue
|
|
646
|
-
|
|
647
|
-
# Handle final response
|
|
648
|
-
if kind == "final_response" and ev.get("content"):
|
|
649
|
-
final_text = ev["content"]
|
|
650
|
-
continue
|
|
651
|
-
|
|
652
|
-
# Handle usage stats
|
|
653
|
-
if kind == "usage":
|
|
654
|
-
stats_usage.update(ev.get("usage") or {})
|
|
655
|
-
continue
|
|
656
|
-
|
|
657
|
-
# Handle run info updates
|
|
658
|
-
if kind == "run_info":
|
|
659
|
-
if ev.get("model"):
|
|
660
|
-
meta["model"] = ev["model"]
|
|
661
|
-
renderer.on_start(meta)
|
|
662
|
-
if ev.get("run_id"):
|
|
663
|
-
meta["run_id"] = ev["run_id"]
|
|
664
|
-
renderer.on_start(meta)
|
|
741
|
+
if started_monotonic is None:
|
|
742
|
+
try:
|
|
743
|
+
ev = json.loads(event["data"])
|
|
744
|
+
if self._should_start_timer(ev):
|
|
745
|
+
started_monotonic = monotonic()
|
|
746
|
+
except json.JSONDecodeError:
|
|
747
|
+
pass
|
|
748
|
+
|
|
749
|
+
final_text, stats_usage = self._process_single_event(
|
|
750
|
+
event, renderer, final_text, stats_usage, meta
|
|
751
|
+
)
|
|
665
752
|
|
|
666
753
|
finished_monotonic = monotonic()
|
|
667
754
|
return final_text, stats_usage, started_monotonic, finished_monotonic
|
|
@@ -678,9 +765,13 @@ class AgentClient(BaseClient):
|
|
|
678
765
|
) -> str:
|
|
679
766
|
"""Run an agent with a message, streaming via a renderer."""
|
|
680
767
|
# Prepare request payload and headers
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
768
|
+
(
|
|
769
|
+
payload,
|
|
770
|
+
data_payload,
|
|
771
|
+
files_payload,
|
|
772
|
+
headers,
|
|
773
|
+
multipart_data,
|
|
774
|
+
) = self._prepare_sync_request_data(message, files, tty, **kwargs)
|
|
684
775
|
|
|
685
776
|
# Create renderer
|
|
686
777
|
r = self._create_renderer(renderer, **kwargs)
|
|
@@ -712,10 +803,13 @@ class AgentClient(BaseClient):
|
|
|
712
803
|
timeout_seconds = kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
713
804
|
agent_name = kwargs.get("agent_name")
|
|
714
805
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
806
|
+
(
|
|
807
|
+
final_text,
|
|
808
|
+
stats_usage,
|
|
809
|
+
started_monotonic,
|
|
810
|
+
finished_monotonic,
|
|
811
|
+
) = self._process_stream_events(
|
|
812
|
+
stream_response, r, timeout_seconds, agent_name, kwargs
|
|
719
813
|
)
|
|
720
814
|
|
|
721
815
|
except KeyboardInterrupt:
|
|
@@ -749,6 +843,75 @@ class AgentClient(BaseClient):
|
|
|
749
843
|
r.on_complete(st)
|
|
750
844
|
return final_payload
|
|
751
845
|
|
|
846
|
+
def _prepare_request_data(
|
|
847
|
+
self,
|
|
848
|
+
message: str,
|
|
849
|
+
files: list[str | BinaryIO] | None,
|
|
850
|
+
**kwargs,
|
|
851
|
+
) -> tuple[dict | None, dict | None, dict | None, dict | None]:
|
|
852
|
+
"""Prepare request data for async agent runs.
|
|
853
|
+
|
|
854
|
+
Returns:
|
|
855
|
+
Tuple of (payload, data_payload, files_payload, headers)
|
|
856
|
+
"""
|
|
857
|
+
if files:
|
|
858
|
+
# Handle multipart data for file uploads
|
|
859
|
+
multipart_data = prepare_multipart_data(message, files)
|
|
860
|
+
# Inject optional multipart extras expected by backend
|
|
861
|
+
if "chat_history" in kwargs and kwargs["chat_history"] is not None:
|
|
862
|
+
multipart_data.data["chat_history"] = kwargs["chat_history"]
|
|
863
|
+
if "pii_mapping" in kwargs and kwargs["pii_mapping"] is not None:
|
|
864
|
+
multipart_data.data["pii_mapping"] = kwargs["pii_mapping"]
|
|
865
|
+
|
|
866
|
+
headers = {"Accept": SSE_CONTENT_TYPE}
|
|
867
|
+
return None, multipart_data.data, multipart_data.files, headers
|
|
868
|
+
else:
|
|
869
|
+
# Simple JSON payload for text-only requests
|
|
870
|
+
payload = {"input": message, "stream": True, **kwargs}
|
|
871
|
+
headers = {"Accept": SSE_CONTENT_TYPE}
|
|
872
|
+
return payload, None, None, headers
|
|
873
|
+
|
|
874
|
+
def _create_async_client_config(
|
|
875
|
+
self, timeout: float | None, headers: dict | None
|
|
876
|
+
) -> dict:
|
|
877
|
+
"""Create async client configuration with proper headers and timeout."""
|
|
878
|
+
config = self._build_async_client(timeout or self.timeout)
|
|
879
|
+
if headers:
|
|
880
|
+
config["headers"] = {**config["headers"], **headers}
|
|
881
|
+
return config
|
|
882
|
+
|
|
883
|
+
async def _stream_agent_response(
|
|
884
|
+
self,
|
|
885
|
+
async_client: httpx.AsyncClient,
|
|
886
|
+
agent_id: str,
|
|
887
|
+
payload: dict | None,
|
|
888
|
+
data_payload: dict | None,
|
|
889
|
+
files_payload: dict | None,
|
|
890
|
+
headers: dict | None,
|
|
891
|
+
timeout_seconds: float,
|
|
892
|
+
agent_name: str | None,
|
|
893
|
+
) -> AsyncGenerator[dict, None]:
|
|
894
|
+
"""Stream the agent response and yield parsed JSON chunks."""
|
|
895
|
+
async with async_client.stream(
|
|
896
|
+
"POST",
|
|
897
|
+
f"/agents/{agent_id}/run",
|
|
898
|
+
json=payload,
|
|
899
|
+
data=data_payload,
|
|
900
|
+
files=files_payload,
|
|
901
|
+
headers=headers,
|
|
902
|
+
) as stream_response:
|
|
903
|
+
stream_response.raise_for_status()
|
|
904
|
+
|
|
905
|
+
async for event in aiter_sse_events(
|
|
906
|
+
stream_response, timeout_seconds, agent_name
|
|
907
|
+
):
|
|
908
|
+
try:
|
|
909
|
+
chunk = json.loads(event["data"])
|
|
910
|
+
yield chunk
|
|
911
|
+
except json.JSONDecodeError:
|
|
912
|
+
logger.debug("Non-JSON SSE fragment skipped")
|
|
913
|
+
continue
|
|
914
|
+
|
|
752
915
|
async def arun_agent(
|
|
753
916
|
self,
|
|
754
917
|
agent_id: str,
|
|
@@ -775,74 +938,34 @@ class AgentClient(BaseClient):
|
|
|
775
938
|
httpx.TimeoutException: When general timeout occurs
|
|
776
939
|
Exception: For other unexpected errors
|
|
777
940
|
"""
|
|
778
|
-
# Prepare
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if files:
|
|
783
|
-
multipart_data = prepare_multipart_data(message, files)
|
|
784
|
-
# Inject optional multipart extras expected by backend
|
|
785
|
-
if "chat_history" in kwargs and kwargs["chat_history"] is not None:
|
|
786
|
-
multipart_data.data["chat_history"] = kwargs["chat_history"]
|
|
787
|
-
if "pii_mapping" in kwargs and kwargs["pii_mapping"] is not None:
|
|
788
|
-
multipart_data.data["pii_mapping"] = kwargs["pii_mapping"]
|
|
789
|
-
headers = None # Let httpx set proper multipart boundaries
|
|
790
|
-
|
|
791
|
-
# When streaming, explicitly prefer SSE
|
|
792
|
-
headers = {**(headers or {}), "Accept": "text/event-stream"}
|
|
941
|
+
# Prepare request data
|
|
942
|
+
payload, data_payload, files_payload, headers = self._prepare_request_data(
|
|
943
|
+
message, files, **kwargs
|
|
944
|
+
)
|
|
793
945
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
# Use multipart data
|
|
797
|
-
data_payload = multipart_data.data
|
|
798
|
-
files_payload = multipart_data.files
|
|
799
|
-
else:
|
|
800
|
-
payload = {"input": message, **kwargs}
|
|
801
|
-
# Explicitly send stream intent both ways
|
|
802
|
-
payload["stream"] = True
|
|
803
|
-
data_payload = None
|
|
804
|
-
files_payload = None
|
|
946
|
+
# Create async client configuration
|
|
947
|
+
async_client_config = self._create_async_client_config(timeout, headers)
|
|
805
948
|
|
|
806
|
-
#
|
|
807
|
-
|
|
949
|
+
# Get execution timeout for streaming control
|
|
950
|
+
timeout_seconds = kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
951
|
+
agent_name = kwargs.get("agent_name")
|
|
808
952
|
|
|
809
953
|
try:
|
|
810
|
-
|
|
811
|
-
if headers:
|
|
812
|
-
async_client_config["headers"] = {
|
|
813
|
-
**async_client_config["headers"],
|
|
814
|
-
**headers,
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
# Create async client for this request
|
|
954
|
+
# Create async client and stream response
|
|
818
955
|
async with httpx.AsyncClient(**async_client_config) as async_client:
|
|
819
|
-
async
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
headers
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
# Prefer parameter timeout, otherwise use default
|
|
831
|
-
timeout_seconds = kwargs.get("timeout", DEFAULT_AGENT_RUN_TIMEOUT)
|
|
832
|
-
|
|
833
|
-
agent_name = kwargs.get("agent_name")
|
|
834
|
-
|
|
835
|
-
async for event in aiter_sse_events(
|
|
836
|
-
stream_response, timeout_seconds, agent_name
|
|
837
|
-
):
|
|
838
|
-
try:
|
|
839
|
-
chunk = json.loads(event["data"])
|
|
840
|
-
yield chunk
|
|
841
|
-
except json.JSONDecodeError:
|
|
842
|
-
logger.debug("Non-JSON SSE fragment skipped")
|
|
843
|
-
continue
|
|
956
|
+
async for chunk in self._stream_agent_response(
|
|
957
|
+
async_client,
|
|
958
|
+
agent_id,
|
|
959
|
+
payload,
|
|
960
|
+
data_payload,
|
|
961
|
+
files_payload,
|
|
962
|
+
headers,
|
|
963
|
+
timeout_seconds,
|
|
964
|
+
agent_name,
|
|
965
|
+
):
|
|
966
|
+
yield chunk
|
|
844
967
|
|
|
845
968
|
finally:
|
|
846
|
-
# Ensure
|
|
847
|
-
|
|
848
|
-
|
|
969
|
+
# Ensure cleanup - this is handled by the calling context
|
|
970
|
+
# but we keep this for safety in case of future changes
|
|
971
|
+
pass
|