glaip-sdk 0.1.3__py3-none-any.whl → 0.6.10__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 +5 -2
- glaip_sdk/_version.py +9 -0
- glaip_sdk/agents/__init__.py +27 -0
- glaip_sdk/agents/base.py +1191 -0
- glaip_sdk/branding.py +13 -0
- glaip_sdk/cli/account_store.py +540 -0
- glaip_sdk/cli/auth.py +254 -15
- glaip_sdk/cli/commands/__init__.py +2 -2
- glaip_sdk/cli/commands/accounts.py +746 -0
- glaip_sdk/cli/commands/agents.py +213 -73
- glaip_sdk/cli/commands/common_config.py +101 -0
- glaip_sdk/cli/commands/configure.py +729 -113
- glaip_sdk/cli/commands/mcps.py +241 -72
- glaip_sdk/cli/commands/models.py +11 -5
- glaip_sdk/cli/commands/tools.py +49 -57
- glaip_sdk/cli/commands/transcripts.py +755 -0
- glaip_sdk/cli/config.py +48 -4
- glaip_sdk/cli/constants.py +38 -0
- glaip_sdk/cli/context.py +8 -0
- glaip_sdk/cli/core/__init__.py +79 -0
- glaip_sdk/cli/core/context.py +124 -0
- glaip_sdk/cli/core/output.py +846 -0
- glaip_sdk/cli/core/prompting.py +649 -0
- glaip_sdk/cli/core/rendering.py +187 -0
- glaip_sdk/cli/display.py +35 -19
- glaip_sdk/cli/hints.py +57 -0
- glaip_sdk/cli/io.py +6 -3
- glaip_sdk/cli/main.py +228 -119
- glaip_sdk/cli/masking.py +21 -33
- glaip_sdk/cli/pager.py +9 -10
- glaip_sdk/cli/parsers/__init__.py +1 -3
- glaip_sdk/cli/slash/__init__.py +0 -9
- glaip_sdk/cli/slash/accounts_controller.py +578 -0
- glaip_sdk/cli/slash/accounts_shared.py +75 -0
- glaip_sdk/cli/slash/agent_session.py +62 -21
- glaip_sdk/cli/slash/prompt.py +21 -0
- glaip_sdk/cli/slash/remote_runs_controller.py +566 -0
- glaip_sdk/cli/slash/session.py +771 -140
- glaip_sdk/cli/slash/tui/__init__.py +9 -0
- glaip_sdk/cli/slash/tui/accounts.tcss +86 -0
- glaip_sdk/cli/slash/tui/accounts_app.py +876 -0
- glaip_sdk/cli/slash/tui/background_tasks.py +72 -0
- glaip_sdk/cli/slash/tui/loading.py +58 -0
- glaip_sdk/cli/slash/tui/remote_runs_app.py +628 -0
- glaip_sdk/cli/transcript/__init__.py +12 -52
- glaip_sdk/cli/transcript/cache.py +255 -44
- glaip_sdk/cli/transcript/capture.py +27 -1
- glaip_sdk/cli/transcript/history.py +815 -0
- glaip_sdk/cli/transcript/viewer.py +72 -499
- glaip_sdk/cli/update_notifier.py +14 -5
- glaip_sdk/cli/utils.py +243 -1252
- glaip_sdk/cli/validators.py +5 -6
- glaip_sdk/client/__init__.py +2 -1
- glaip_sdk/client/_agent_payloads.py +45 -9
- glaip_sdk/client/agent_runs.py +147 -0
- glaip_sdk/client/agents.py +287 -29
- glaip_sdk/client/base.py +1 -0
- glaip_sdk/client/main.py +19 -10
- glaip_sdk/client/mcps.py +122 -12
- glaip_sdk/client/run_rendering.py +133 -88
- glaip_sdk/client/shared.py +21 -0
- glaip_sdk/client/tools.py +155 -10
- glaip_sdk/config/constants.py +11 -0
- glaip_sdk/mcps/__init__.py +21 -0
- glaip_sdk/mcps/base.py +345 -0
- glaip_sdk/models/__init__.py +90 -0
- glaip_sdk/models/agent.py +47 -0
- glaip_sdk/models/agent_runs.py +116 -0
- glaip_sdk/models/common.py +42 -0
- glaip_sdk/models/mcp.py +33 -0
- glaip_sdk/models/tool.py +33 -0
- glaip_sdk/payload_schemas/__init__.py +1 -13
- glaip_sdk/registry/__init__.py +55 -0
- glaip_sdk/registry/agent.py +164 -0
- glaip_sdk/registry/base.py +139 -0
- glaip_sdk/registry/mcp.py +253 -0
- glaip_sdk/registry/tool.py +232 -0
- glaip_sdk/rich_components.py +58 -2
- glaip_sdk/runner/__init__.py +59 -0
- glaip_sdk/runner/base.py +84 -0
- glaip_sdk/runner/deps.py +115 -0
- glaip_sdk/runner/langgraph.py +706 -0
- glaip_sdk/runner/mcp_adapter/__init__.py +13 -0
- glaip_sdk/runner/mcp_adapter/base_mcp_adapter.py +43 -0
- glaip_sdk/runner/mcp_adapter/langchain_mcp_adapter.py +257 -0
- glaip_sdk/runner/mcp_adapter/mcp_config_builder.py +95 -0
- glaip_sdk/runner/tool_adapter/__init__.py +18 -0
- glaip_sdk/runner/tool_adapter/base_tool_adapter.py +44 -0
- glaip_sdk/runner/tool_adapter/langchain_tool_adapter.py +219 -0
- glaip_sdk/tools/__init__.py +22 -0
- glaip_sdk/tools/base.py +435 -0
- glaip_sdk/utils/__init__.py +58 -12
- glaip_sdk/utils/a2a/__init__.py +34 -0
- glaip_sdk/utils/a2a/event_processor.py +188 -0
- glaip_sdk/utils/bundler.py +267 -0
- glaip_sdk/utils/client.py +111 -0
- glaip_sdk/utils/client_utils.py +39 -7
- glaip_sdk/utils/datetime_helpers.py +58 -0
- glaip_sdk/utils/discovery.py +78 -0
- glaip_sdk/utils/display.py +23 -15
- glaip_sdk/utils/export.py +143 -0
- glaip_sdk/utils/general.py +0 -33
- glaip_sdk/utils/import_export.py +12 -7
- glaip_sdk/utils/import_resolver.py +492 -0
- glaip_sdk/utils/instructions.py +101 -0
- glaip_sdk/utils/rendering/__init__.py +115 -1
- glaip_sdk/utils/rendering/formatting.py +5 -30
- glaip_sdk/utils/rendering/layout/__init__.py +64 -0
- glaip_sdk/utils/rendering/{renderer → layout}/panels.py +9 -0
- glaip_sdk/utils/rendering/{renderer → layout}/progress.py +70 -1
- glaip_sdk/utils/rendering/layout/summary.py +74 -0
- glaip_sdk/utils/rendering/layout/transcript.py +606 -0
- glaip_sdk/utils/rendering/models.py +1 -0
- glaip_sdk/utils/rendering/renderer/__init__.py +9 -47
- glaip_sdk/utils/rendering/renderer/base.py +217 -1476
- glaip_sdk/utils/rendering/renderer/debug.py +26 -20
- glaip_sdk/utils/rendering/renderer/factory.py +138 -0
- glaip_sdk/utils/rendering/renderer/stream.py +4 -12
- glaip_sdk/utils/rendering/renderer/thinking.py +273 -0
- glaip_sdk/utils/rendering/renderer/tool_panels.py +442 -0
- glaip_sdk/utils/rendering/renderer/transcript_mode.py +162 -0
- glaip_sdk/utils/rendering/state.py +204 -0
- glaip_sdk/utils/rendering/steps/__init__.py +34 -0
- glaip_sdk/utils/rendering/{steps.py → steps/event_processor.py} +53 -440
- glaip_sdk/utils/rendering/steps/format.py +176 -0
- glaip_sdk/utils/rendering/steps/manager.py +387 -0
- glaip_sdk/utils/rendering/timing.py +36 -0
- glaip_sdk/utils/rendering/viewer/__init__.py +21 -0
- glaip_sdk/utils/rendering/viewer/presenter.py +184 -0
- glaip_sdk/utils/resource_refs.py +25 -13
- glaip_sdk/utils/runtime_config.py +425 -0
- glaip_sdk/utils/serialization.py +18 -0
- glaip_sdk/utils/sync.py +142 -0
- glaip_sdk/utils/tool_detection.py +33 -0
- glaip_sdk/utils/validation.py +16 -24
- {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/METADATA +42 -4
- glaip_sdk-0.6.10.dist-info/RECORD +159 -0
- {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/WHEEL +1 -1
- glaip_sdk/models.py +0 -240
- glaip_sdk-0.1.3.dist-info/RECORD +0 -83
- {glaip_sdk-0.1.3.dist-info → glaip_sdk-0.6.10.dist-info}/entry_points.txt +0 -0
glaip_sdk/agents/base.py
ADDED
|
@@ -0,0 +1,1191 @@
|
|
|
1
|
+
"""Agent class for GL AIP platform.
|
|
2
|
+
|
|
3
|
+
This module provides the Agent class that serves as the foundation
|
|
4
|
+
for defining agents in glaip-sdk. The Agent class supports both:
|
|
5
|
+
- Direct instantiation for simple agents
|
|
6
|
+
- Subclassing for complex, reusable agent definitions
|
|
7
|
+
|
|
8
|
+
Authors:
|
|
9
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
10
|
+
|
|
11
|
+
Example - Direct Instantiation:
|
|
12
|
+
>>> from glaip_sdk.agents import Agent
|
|
13
|
+
>>>
|
|
14
|
+
>>> agent = Agent(
|
|
15
|
+
... name="hello_agent",
|
|
16
|
+
... instruction="You are a helpful assistant.",
|
|
17
|
+
... )
|
|
18
|
+
>>> agent.deploy()
|
|
19
|
+
>>> result = agent.run("Hello!")
|
|
20
|
+
|
|
21
|
+
Example - Subclassing:
|
|
22
|
+
>>> from glaip_sdk.agents import Agent
|
|
23
|
+
>>>
|
|
24
|
+
>>> class WeatherAgent(Agent):
|
|
25
|
+
... @property
|
|
26
|
+
... def name(self) -> str:
|
|
27
|
+
... return "weather_agent"
|
|
28
|
+
...
|
|
29
|
+
... @property
|
|
30
|
+
... def instruction(self) -> str:
|
|
31
|
+
... return "You are a helpful weather assistant."
|
|
32
|
+
...
|
|
33
|
+
... @property
|
|
34
|
+
... def tools(self) -> list:
|
|
35
|
+
... return [WeatherAPITool, "web_search"]
|
|
36
|
+
>>>
|
|
37
|
+
>>> # Deploy and run the agent
|
|
38
|
+
>>> agent = WeatherAgent()
|
|
39
|
+
>>> agent.deploy()
|
|
40
|
+
>>> result = agent.run("What's the weather?")
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
import inspect
|
|
46
|
+
import logging
|
|
47
|
+
import warnings
|
|
48
|
+
from collections.abc import AsyncGenerator
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
from pathlib import Path
|
|
52
|
+
from typing import TYPE_CHECKING, Any
|
|
53
|
+
|
|
54
|
+
from glaip_sdk.registry import get_agent_registry, get_mcp_registry, get_tool_registry
|
|
55
|
+
from glaip_sdk.runner import get_default_runner
|
|
56
|
+
from glaip_sdk.runner.deps import (
|
|
57
|
+
check_local_runtime_available,
|
|
58
|
+
get_local_runtime_missing_message,
|
|
59
|
+
)
|
|
60
|
+
from glaip_sdk.utils.discovery import find_agent
|
|
61
|
+
from glaip_sdk.utils.resource_refs import is_uuid
|
|
62
|
+
from glaip_sdk.utils.runtime_config import normalize_runtime_config_keys
|
|
63
|
+
|
|
64
|
+
if TYPE_CHECKING:
|
|
65
|
+
from glaip_sdk.models import AgentResponse
|
|
66
|
+
from glaip_sdk.registry import AgentRegistry, MCPRegistry, ToolRegistry
|
|
67
|
+
|
|
68
|
+
logger = logging.getLogger(__name__)
|
|
69
|
+
|
|
70
|
+
_AGENT_NOT_DEPLOYED_MSG = "Agent must be deployed before running. Call deploy() first."
|
|
71
|
+
_CLIENT_NOT_AVAILABLE_MSG = "Client not available. Agent may not have been deployed properly."
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Agent:
|
|
75
|
+
"""Agent class for GL AIP platform.
|
|
76
|
+
|
|
77
|
+
Supports both direct instantiation and subclassing.
|
|
78
|
+
The deploy() method updates the agent in-place, so you can use the
|
|
79
|
+
same instance for deployment and running.
|
|
80
|
+
|
|
81
|
+
Direct instantiation (simple):
|
|
82
|
+
>>> agent = Agent(
|
|
83
|
+
... name="my_agent",
|
|
84
|
+
... instruction="You are helpful.",
|
|
85
|
+
... tools=["web_search"],
|
|
86
|
+
... )
|
|
87
|
+
>>> agent.deploy()
|
|
88
|
+
>>> result = agent.run("Hello!")
|
|
89
|
+
|
|
90
|
+
Subclassing (complex):
|
|
91
|
+
>>> class MyAgent(Agent):
|
|
92
|
+
... @property
|
|
93
|
+
... def name(self) -> str:
|
|
94
|
+
... return "my_agent"
|
|
95
|
+
...
|
|
96
|
+
... @property
|
|
97
|
+
... def instruction(self) -> str:
|
|
98
|
+
... return self.load_instruction_from_file("instructions.md")
|
|
99
|
+
...
|
|
100
|
+
>>> agent = MyAgent()
|
|
101
|
+
>>> agent.deploy()
|
|
102
|
+
>>> result = agent.run("Hello!")
|
|
103
|
+
|
|
104
|
+
Properties (override in subclass OR pass to __init__):
|
|
105
|
+
- name: Agent name on the AIP platform (required)
|
|
106
|
+
- instruction: Agent instruction text (required)
|
|
107
|
+
- description: Agent description (default: "")
|
|
108
|
+
- tools: List of tools (default: [])
|
|
109
|
+
- agents: List of sub-agents (default: [])
|
|
110
|
+
- mcps: List of MCPs (default: [])
|
|
111
|
+
- timeout: Timeout in seconds (default: 300)
|
|
112
|
+
- metadata: Optional metadata dict (default: None)
|
|
113
|
+
- model: Optional model override (default: None)
|
|
114
|
+
- framework: Agent framework (default: "langchain")
|
|
115
|
+
- version: Agent version (default: "1.0.0")
|
|
116
|
+
- agent_type: Agent type (default: "config")
|
|
117
|
+
- agent_config: Agent execution config (default: None)
|
|
118
|
+
- tool_configs: Per-tool config overrides (default: None)
|
|
119
|
+
- mcp_configs: Per-MCP config overrides (default: None)
|
|
120
|
+
- a2a_profile: A2A profile config (default: None)
|
|
121
|
+
|
|
122
|
+
Instance attributes (set after deployment or from_response):
|
|
123
|
+
- id: The agent's unique ID on the platform
|
|
124
|
+
- _client: Client reference for run/update/delete operations
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
# Sentinel value to detect unset optional params
|
|
128
|
+
_UNSET = object()
|
|
129
|
+
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
name: str | None = None,
|
|
133
|
+
instruction: str | None = None,
|
|
134
|
+
*,
|
|
135
|
+
id: str | None = None, # noqa: A002 - Allow shadowing builtin for API compat
|
|
136
|
+
description: str | None = _UNSET, # type: ignore[assignment]
|
|
137
|
+
tools: list | None = None,
|
|
138
|
+
agents: list | None = None,
|
|
139
|
+
mcps: list | None = None,
|
|
140
|
+
model: str | None = _UNSET, # type: ignore[assignment]
|
|
141
|
+
_client: Any = None,
|
|
142
|
+
**kwargs: Any,
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Initialize an Agent.
|
|
145
|
+
|
|
146
|
+
For direct instantiation, name and instruction are required.
|
|
147
|
+
For subclassing, override the properties instead.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
name: Agent name (required for direct instantiation).
|
|
151
|
+
instruction: Agent instruction (required for direct instantiation).
|
|
152
|
+
id: Agent ID (set after deployment or when created from API response).
|
|
153
|
+
description: Agent description.
|
|
154
|
+
tools: List of tools (Tool classes, SDK Tool objects, or strings).
|
|
155
|
+
agents: List of sub-agents (Agent classes, instances, or strings).
|
|
156
|
+
mcps: List of MCPs.
|
|
157
|
+
model: Model identifier.
|
|
158
|
+
_client: Internal client reference (set automatically).
|
|
159
|
+
**kwargs: Additional configuration parameters:
|
|
160
|
+
- timeout: Execution timeout in seconds.
|
|
161
|
+
- metadata: Optional metadata dictionary.
|
|
162
|
+
- framework: Agent framework identifier.
|
|
163
|
+
- version: Agent version string.
|
|
164
|
+
- agent_type: Agent type identifier.
|
|
165
|
+
- agent_config: Agent execution configuration.
|
|
166
|
+
- tool_configs: Per-tool configuration overrides.
|
|
167
|
+
- mcp_configs: Per-MCP configuration overrides.
|
|
168
|
+
- a2a_profile: A2A profile configuration.
|
|
169
|
+
"""
|
|
170
|
+
# Instance attributes for deployed agents
|
|
171
|
+
self._id = id
|
|
172
|
+
self._client = _client
|
|
173
|
+
self._created_at: str | None = None
|
|
174
|
+
self._updated_at: str | None = None
|
|
175
|
+
|
|
176
|
+
# Store values (None/UNSET means "use property default or override")
|
|
177
|
+
self._name = name
|
|
178
|
+
self._instruction = instruction
|
|
179
|
+
self._description = description
|
|
180
|
+
self._tools = tools
|
|
181
|
+
self._agents = agents
|
|
182
|
+
self._mcps = mcps
|
|
183
|
+
self._model = model
|
|
184
|
+
self._language_model_id: str | None = None
|
|
185
|
+
# Extract parameters from kwargs with _UNSET defaults
|
|
186
|
+
self._timeout = kwargs.pop("timeout", Agent._UNSET) # type: ignore[assignment]
|
|
187
|
+
self._metadata = kwargs.pop("metadata", Agent._UNSET) # type: ignore[assignment]
|
|
188
|
+
self._framework = kwargs.pop("framework", Agent._UNSET) # type: ignore[assignment]
|
|
189
|
+
self._version = kwargs.pop("version", Agent._UNSET) # type: ignore[assignment]
|
|
190
|
+
self._agent_type = kwargs.pop("agent_type", Agent._UNSET) # type: ignore[assignment]
|
|
191
|
+
self._agent_config = kwargs.pop("agent_config", Agent._UNSET) # type: ignore[assignment]
|
|
192
|
+
self._tool_configs = kwargs.pop("tool_configs", Agent._UNSET) # type: ignore[assignment]
|
|
193
|
+
self._mcp_configs = kwargs.pop("mcp_configs", Agent._UNSET) # type: ignore[assignment]
|
|
194
|
+
self._a2a_profile = kwargs.pop("a2a_profile", Agent._UNSET) # type: ignore[assignment]
|
|
195
|
+
|
|
196
|
+
# Warn about unexpected kwargs
|
|
197
|
+
if kwargs:
|
|
198
|
+
warnings.warn(
|
|
199
|
+
f"Unexpected keyword arguments: {list(kwargs.keys())}. These will be ignored.",
|
|
200
|
+
UserWarning,
|
|
201
|
+
stacklevel=2,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# ─────────────────────────────────────────────────────────────────
|
|
205
|
+
# Properties (override in subclasses OR pass to __init__)
|
|
206
|
+
# ─────────────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def id(self) -> str | None: # noqa: A003 - Allow shadowing builtin for API compat
|
|
210
|
+
"""Agent ID on the platform.
|
|
211
|
+
|
|
212
|
+
This is set after deployment or when created from an API response.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
The unique ID or None if not deployed yet.
|
|
216
|
+
"""
|
|
217
|
+
return self._id
|
|
218
|
+
|
|
219
|
+
@id.setter
|
|
220
|
+
def id(self, value: str | None) -> None: # noqa: A003
|
|
221
|
+
"""Set the agent ID."""
|
|
222
|
+
self._id = value
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def created_at(self) -> str | None:
|
|
226
|
+
"""Timestamp when the agent was created.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
ISO format timestamp string or None if not deployed.
|
|
230
|
+
"""
|
|
231
|
+
return self._created_at
|
|
232
|
+
|
|
233
|
+
@created_at.setter
|
|
234
|
+
def created_at(self, value: str | None) -> None:
|
|
235
|
+
"""Set the created_at timestamp."""
|
|
236
|
+
self._created_at = value
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def updated_at(self) -> str | None:
|
|
240
|
+
"""Timestamp when the agent was last updated.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
ISO format timestamp string or None if not deployed.
|
|
244
|
+
"""
|
|
245
|
+
return self._updated_at
|
|
246
|
+
|
|
247
|
+
@updated_at.setter
|
|
248
|
+
def updated_at(self, value: str | None) -> None:
|
|
249
|
+
"""Set the updated_at timestamp."""
|
|
250
|
+
self._updated_at = value
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def name(self) -> str:
|
|
254
|
+
"""Agent name on the AIP platform.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
The unique identifier name for this agent.
|
|
258
|
+
|
|
259
|
+
Raises:
|
|
260
|
+
ValueError: If name is not provided via __init__ or property override.
|
|
261
|
+
"""
|
|
262
|
+
if self._name is None:
|
|
263
|
+
raise ValueError(
|
|
264
|
+
"Agent name is required. Either pass name to __init__() or override the name property in a subclass."
|
|
265
|
+
)
|
|
266
|
+
return self._name
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def instruction(self) -> str:
|
|
270
|
+
"""Agent instruction text.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
The instruction/prompt for the agent.
|
|
274
|
+
|
|
275
|
+
Raises:
|
|
276
|
+
ValueError: If instruction is not provided via __init__ or property
|
|
277
|
+
override.
|
|
278
|
+
"""
|
|
279
|
+
if self._instruction is None:
|
|
280
|
+
raise ValueError(
|
|
281
|
+
"Agent instruction is required. Either pass instruction to __init__() "
|
|
282
|
+
"or override the instruction property in a subclass."
|
|
283
|
+
)
|
|
284
|
+
return self._instruction
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def description(self) -> str:
|
|
288
|
+
"""Agent description.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
A description of what this agent does. Defaults to empty string.
|
|
292
|
+
"""
|
|
293
|
+
if self._description is not self._UNSET:
|
|
294
|
+
return self._description or ""
|
|
295
|
+
return ""
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def tools(self) -> list:
|
|
299
|
+
"""Tools available to this agent.
|
|
300
|
+
|
|
301
|
+
Tools can be:
|
|
302
|
+
- Tool class: Custom tool that will be auto-uploaded
|
|
303
|
+
- glaip_sdk.models.Tool: SDK Tool object (uses tool.id)
|
|
304
|
+
- str: Native tool name or ID (resolved by platform)
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
List of tool references. Defaults to empty list.
|
|
308
|
+
"""
|
|
309
|
+
return self._tools or []
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def agents(self) -> list:
|
|
313
|
+
"""Sub-agents available to this agent.
|
|
314
|
+
|
|
315
|
+
Sub-agents can be:
|
|
316
|
+
- Agent class: Will be deployed recursively
|
|
317
|
+
- glaip_sdk.models.Agent: SDK Agent object (uses agent.id)
|
|
318
|
+
- str: Agent name or ID (resolved by platform)
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
List of sub-agent references. Defaults to empty list.
|
|
322
|
+
"""
|
|
323
|
+
return self._agents or []
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def timeout(self) -> int:
|
|
327
|
+
"""Agent timeout in seconds.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Timeout value in seconds. Defaults to 300.
|
|
331
|
+
"""
|
|
332
|
+
if self._timeout is not self._UNSET and self._timeout is not None:
|
|
333
|
+
return self._timeout
|
|
334
|
+
return 300
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def metadata(self) -> dict[str, Any] | None:
|
|
338
|
+
"""Optional metadata dictionary.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Metadata dict or None.
|
|
342
|
+
"""
|
|
343
|
+
if self._metadata is not self._UNSET:
|
|
344
|
+
return self._metadata
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def model(self) -> str | None:
|
|
349
|
+
"""Optional model override.
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Model identifier string or None to use default.
|
|
353
|
+
"""
|
|
354
|
+
if self._model is not self._UNSET:
|
|
355
|
+
return self._model
|
|
356
|
+
return None
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def language_model_id(self) -> str | None:
|
|
360
|
+
"""Language model ID from the API.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
The language model UUID or None.
|
|
364
|
+
"""
|
|
365
|
+
return self._language_model_id
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def framework(self) -> str:
|
|
369
|
+
"""Agent framework identifier.
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
The framework name. Defaults to "langchain".
|
|
373
|
+
"""
|
|
374
|
+
if self._framework is not self._UNSET and self._framework is not None:
|
|
375
|
+
return self._framework
|
|
376
|
+
return "langchain"
|
|
377
|
+
|
|
378
|
+
@property
|
|
379
|
+
def version(self) -> str:
|
|
380
|
+
"""Agent version string.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
The version identifier. Defaults to "1.0.0".
|
|
384
|
+
"""
|
|
385
|
+
if self._version is not self._UNSET and self._version is not None:
|
|
386
|
+
return self._version
|
|
387
|
+
return "1.0.0"
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def agent_type(self) -> str:
|
|
391
|
+
"""Agent type identifier.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
The agent type. Defaults to "config".
|
|
395
|
+
"""
|
|
396
|
+
if self._agent_type is not self._UNSET and self._agent_type is not None:
|
|
397
|
+
return self._agent_type
|
|
398
|
+
return "config"
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
def agent_config(self) -> dict[str, Any] | None:
|
|
402
|
+
"""Agent configuration for execution settings.
|
|
403
|
+
|
|
404
|
+
This is used for agent-level settings like execution_timeout.
|
|
405
|
+
Note: The `timeout` property is a convenience shortcut that
|
|
406
|
+
maps to agent_config.execution_timeout.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
Agent configuration dict or None.
|
|
410
|
+
"""
|
|
411
|
+
if self._agent_config is not self._UNSET:
|
|
412
|
+
return self._agent_config
|
|
413
|
+
return None
|
|
414
|
+
|
|
415
|
+
@property
|
|
416
|
+
def tool_configs(self) -> dict[Any, dict[str, Any]] | None:
|
|
417
|
+
"""Per-tool configuration overrides.
|
|
418
|
+
|
|
419
|
+
A mapping of tool references (classes, names, or IDs) to their
|
|
420
|
+
configuration overrides. Supports both class keys and string keys.
|
|
421
|
+
|
|
422
|
+
Example:
|
|
423
|
+
>>> @property
|
|
424
|
+
... def tool_configs(self):
|
|
425
|
+
... return {
|
|
426
|
+
... MyToolClass: {"api_key": "xxx"}, # Class key
|
|
427
|
+
... "other_tool": {"setting": "value"}, # String key
|
|
428
|
+
... }
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
Tool configurations dict or None.
|
|
432
|
+
"""
|
|
433
|
+
if self._tool_configs is not self._UNSET:
|
|
434
|
+
return self._tool_configs
|
|
435
|
+
return None
|
|
436
|
+
|
|
437
|
+
@property
|
|
438
|
+
def mcps(self) -> list:
|
|
439
|
+
"""MCPs (Model Context Protocols) available to this agent.
|
|
440
|
+
|
|
441
|
+
MCPs can be:
|
|
442
|
+
- glaip_sdk.models.MCP: SDK MCP object (uses mcp.id)
|
|
443
|
+
- str: MCP name or ID (resolved by platform)
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
List of MCP references. Defaults to empty list.
|
|
447
|
+
"""
|
|
448
|
+
return self._mcps or []
|
|
449
|
+
|
|
450
|
+
@property
|
|
451
|
+
def mcp_configs(self) -> dict[str, Any] | None:
|
|
452
|
+
"""Per-MCP configuration overrides.
|
|
453
|
+
|
|
454
|
+
A mapping of MCP IDs to their configuration overrides.
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
MCP configurations dict or None.
|
|
458
|
+
"""
|
|
459
|
+
if self._mcp_configs is not self._UNSET:
|
|
460
|
+
return self._mcp_configs
|
|
461
|
+
return None
|
|
462
|
+
|
|
463
|
+
@property
|
|
464
|
+
def a2a_profile(self) -> dict[str, Any] | None:
|
|
465
|
+
"""A2A (Agent-to-Agent) profile configuration.
|
|
466
|
+
|
|
467
|
+
Configuration for agent-to-agent communication capabilities.
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
A2A profile dict or None.
|
|
471
|
+
"""
|
|
472
|
+
if self._a2a_profile is not self._UNSET:
|
|
473
|
+
return self._a2a_profile
|
|
474
|
+
return None
|
|
475
|
+
|
|
476
|
+
def deploy(self) -> Agent:
|
|
477
|
+
"""Deploy this agent (with tools and sub-agents) to GL AIP.
|
|
478
|
+
|
|
479
|
+
Performs the following steps:
|
|
480
|
+
1. Upload custom tools (Tool classes) - cached by ToolRegistry
|
|
481
|
+
2. Deploy sub-agents recursively (Agent classes) - cached by AgentRegistry
|
|
482
|
+
3. Resolve tool_configs (requires tools to be in registry first)
|
|
483
|
+
4. Create/update this agent
|
|
484
|
+
|
|
485
|
+
Tools and agents are cached globally by their respective registries
|
|
486
|
+
to avoid duplicate uploads across deployments.
|
|
487
|
+
|
|
488
|
+
After deployment, this Agent instance is updated in-place with:
|
|
489
|
+
- id: The agent's unique ID on the platform
|
|
490
|
+
- _client: A client reference for run/update/delete operations
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
Self with id and _client set, ready for run() calls.
|
|
494
|
+
|
|
495
|
+
Raises:
|
|
496
|
+
Exception: If deployment fails due to API error.
|
|
497
|
+
|
|
498
|
+
Example:
|
|
499
|
+
>>> agent = Agent(
|
|
500
|
+
... name="my_agent",
|
|
501
|
+
... instruction="You are helpful.",
|
|
502
|
+
... )
|
|
503
|
+
>>> agent.deploy()
|
|
504
|
+
>>> print(f"Deployed: {agent.name} (ID: {agent.id})")
|
|
505
|
+
>>> result = agent.run("Hello!")
|
|
506
|
+
"""
|
|
507
|
+
logger.info("Deploying agent: %s", self.name)
|
|
508
|
+
|
|
509
|
+
# Resolve tools FIRST - this uploads them and populates the registry
|
|
510
|
+
tool_ids = self._resolve_tools(get_tool_registry())
|
|
511
|
+
|
|
512
|
+
# Resolve agents
|
|
513
|
+
agent_ids = self._resolve_agents(get_agent_registry())
|
|
514
|
+
|
|
515
|
+
# Now build config - tool_configs can be resolved because tools are in registry
|
|
516
|
+
config = self._build_config(get_tool_registry(), get_mcp_registry())
|
|
517
|
+
config["tools"] = tool_ids
|
|
518
|
+
config["agents"] = agent_ids
|
|
519
|
+
|
|
520
|
+
from glaip_sdk.utils.client import get_client # noqa: PLC0415
|
|
521
|
+
|
|
522
|
+
client = get_client()
|
|
523
|
+
response = self._create_or_update_agent(config, client, find_agent)
|
|
524
|
+
|
|
525
|
+
# Update self with deployed info
|
|
526
|
+
self._id = response.id
|
|
527
|
+
self._client = client
|
|
528
|
+
|
|
529
|
+
return self
|
|
530
|
+
|
|
531
|
+
def _build_config(self, tool_registry: ToolRegistry, mcp_registry: MCPRegistry) -> dict[str, Any]:
|
|
532
|
+
"""Build the base configuration dictionary.
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
tool_registry: The tool registry for resolving tool configs.
|
|
536
|
+
mcp_registry: The MCP registry for resolving MCPs.
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
Dictionary with agent configuration.
|
|
540
|
+
"""
|
|
541
|
+
config: dict[str, Any] = {
|
|
542
|
+
"name": self.name,
|
|
543
|
+
"instruction": self.instruction,
|
|
544
|
+
"description": self.description,
|
|
545
|
+
"framework": self.framework,
|
|
546
|
+
"version": self.version,
|
|
547
|
+
"agent_type": self.agent_type,
|
|
548
|
+
"model": self.model,
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
# Handle metadata (default to empty dict if None)
|
|
552
|
+
config["metadata"] = self.metadata or {}
|
|
553
|
+
|
|
554
|
+
# Handle agent_config with timeout
|
|
555
|
+
# The timeout property is a convenience that maps to agent_config.execution_timeout
|
|
556
|
+
agent_config = dict(self.agent_config) if self.agent_config else {}
|
|
557
|
+
if self.timeout and "execution_timeout" not in agent_config:
|
|
558
|
+
agent_config["execution_timeout"] = self.timeout
|
|
559
|
+
if agent_config:
|
|
560
|
+
config["agent_config"] = agent_config
|
|
561
|
+
|
|
562
|
+
# Handle tool_configs - resolve tool names/classes to IDs
|
|
563
|
+
if self.tool_configs:
|
|
564
|
+
config["tool_configs"] = self._resolve_tool_configs(tool_registry)
|
|
565
|
+
|
|
566
|
+
# Handle MCPs
|
|
567
|
+
if self.mcps:
|
|
568
|
+
config["mcps"] = self._resolve_mcps(mcp_registry)
|
|
569
|
+
|
|
570
|
+
# Handle mcp_configs - normalize keys to MCP IDs
|
|
571
|
+
if self.mcp_configs:
|
|
572
|
+
config["mcp_configs"] = self._resolve_mcp_configs(mcp_registry)
|
|
573
|
+
|
|
574
|
+
# Handle a2a_profile
|
|
575
|
+
if self.a2a_profile:
|
|
576
|
+
config["a2a_profile"] = self.a2a_profile
|
|
577
|
+
|
|
578
|
+
return config
|
|
579
|
+
|
|
580
|
+
def _resolve_mcps(self, registry: MCPRegistry) -> list[str]:
|
|
581
|
+
"""Resolve MCP references to IDs using MCPRegistry.
|
|
582
|
+
|
|
583
|
+
Uses the global MCPRegistry to cache MCP objects across deployments.
|
|
584
|
+
The registry handles all MCP types: MCP helpers, strings, dicts,
|
|
585
|
+
and glaip_sdk.models.MCP objects.
|
|
586
|
+
|
|
587
|
+
Args:
|
|
588
|
+
registry: The MCP registry.
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
List of resolved MCP IDs for the API payload.
|
|
592
|
+
"""
|
|
593
|
+
if not self.mcps:
|
|
594
|
+
return []
|
|
595
|
+
|
|
596
|
+
return [registry.resolve(mcp_ref).id for mcp_ref in self.mcps]
|
|
597
|
+
|
|
598
|
+
def _resolve_tools(self, registry: ToolRegistry) -> list[str]:
|
|
599
|
+
"""Resolve tool references to IDs using ToolRegistry.
|
|
600
|
+
|
|
601
|
+
Uses the global ToolRegistry to cache Tool objects across deployments.
|
|
602
|
+
The registry handles all tool types: Tool classes, LangChain BaseTool,
|
|
603
|
+
glaip_sdk.models.Tool, and string names.
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
registry: The tool registry.
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
List of resolved tool IDs for the API payload.
|
|
610
|
+
"""
|
|
611
|
+
if not self.tools:
|
|
612
|
+
return []
|
|
613
|
+
|
|
614
|
+
# Resolve each tool reference to a Tool object, extract ID
|
|
615
|
+
return [registry.resolve(tool_ref).id for tool_ref in self.tools]
|
|
616
|
+
|
|
617
|
+
def _resolve_tool_configs(self, registry: ToolRegistry) -> dict[str, Any]:
|
|
618
|
+
"""Resolve tool_configs keys from tool names/classes to tool IDs.
|
|
619
|
+
|
|
620
|
+
Allows tool_configs to be defined with tool names, class names, or
|
|
621
|
+
Tool classes as keys. These are resolved to actual tool IDs using
|
|
622
|
+
the ToolRegistry.
|
|
623
|
+
|
|
624
|
+
Supported key formats:
|
|
625
|
+
- Tool class: SyncReportSchedulerTool
|
|
626
|
+
- Tool name string: "sync_report_scheduler"
|
|
627
|
+
- Tool ID (UUID): Passed through unchanged
|
|
628
|
+
|
|
629
|
+
Args:
|
|
630
|
+
registry: The tool registry.
|
|
631
|
+
|
|
632
|
+
Returns:
|
|
633
|
+
Dict with resolved tool IDs as keys and configs as values.
|
|
634
|
+
|
|
635
|
+
Example:
|
|
636
|
+
>>> class MyAgent(Agent):
|
|
637
|
+
... @property
|
|
638
|
+
... def tool_configs(self):
|
|
639
|
+
... return {
|
|
640
|
+
... SyncReportSchedulerTool: {"api_key": "xxx"},
|
|
641
|
+
... "other_tool": {"setting": "value"},
|
|
642
|
+
... }
|
|
643
|
+
"""
|
|
644
|
+
if not self.tool_configs:
|
|
645
|
+
return {}
|
|
646
|
+
|
|
647
|
+
resolved: dict[str, Any] = {}
|
|
648
|
+
|
|
649
|
+
for key, config in self.tool_configs.items():
|
|
650
|
+
# If key is already a UUID-like string, pass through
|
|
651
|
+
if isinstance(key, str) and is_uuid(key):
|
|
652
|
+
resolved[key] = config
|
|
653
|
+
continue
|
|
654
|
+
|
|
655
|
+
try:
|
|
656
|
+
# Resolve key (tool name/class) to Tool object, get ID
|
|
657
|
+
tool = registry.resolve(key)
|
|
658
|
+
resolved[tool.id] = config
|
|
659
|
+
except (ValueError, KeyError) as e:
|
|
660
|
+
raise ValueError(f"Failed to resolve tool config key: {key}") from e
|
|
661
|
+
|
|
662
|
+
return resolved
|
|
663
|
+
|
|
664
|
+
def _resolve_mcp_configs(self, registry: MCPRegistry) -> dict[str, Any]:
|
|
665
|
+
"""Resolve mcp_configs keys from MCP names/objects to MCP IDs.
|
|
666
|
+
|
|
667
|
+
Allows mcp_configs to be defined with MCP names, MCP objects, or UUIDs
|
|
668
|
+
as keys. Keys are resolved to MCP IDs using the MCPRegistry.
|
|
669
|
+
|
|
670
|
+
Supported key formats:
|
|
671
|
+
- MCP object (with id): uses id directly
|
|
672
|
+
- MCP name string: resolved via registry to ID
|
|
673
|
+
- MCP ID (UUID string): passed through unchanged
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
registry: The MCP registry.
|
|
677
|
+
|
|
678
|
+
Returns:
|
|
679
|
+
Dict with resolved MCP IDs as keys and configs as values.
|
|
680
|
+
"""
|
|
681
|
+
if not self.mcp_configs:
|
|
682
|
+
return {}
|
|
683
|
+
|
|
684
|
+
resolved: dict[str, Any] = {}
|
|
685
|
+
|
|
686
|
+
for key, config in self.mcp_configs.items():
|
|
687
|
+
try:
|
|
688
|
+
# If key is already a UUID-like string, pass through
|
|
689
|
+
if isinstance(key, str) and is_uuid(key):
|
|
690
|
+
resolved_id = key
|
|
691
|
+
else:
|
|
692
|
+
mcp = registry.resolve(key)
|
|
693
|
+
resolved_id = mcp.id
|
|
694
|
+
|
|
695
|
+
if resolved_id in resolved:
|
|
696
|
+
raise ValueError(
|
|
697
|
+
f"Duplicate mcp_configs entries resolve to the same MCP id '{resolved_id}' (key={key!r})"
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
resolved[resolved_id] = config
|
|
701
|
+
except (ValueError, KeyError) as exc:
|
|
702
|
+
raise ValueError(
|
|
703
|
+
f"Failed to resolve mcp config key {key!r} (type={type(key).__name__}): {exc}"
|
|
704
|
+
) from exc
|
|
705
|
+
|
|
706
|
+
return resolved
|
|
707
|
+
|
|
708
|
+
def _resolve_agents(self, registry: AgentRegistry) -> list:
|
|
709
|
+
"""Resolve sub-agent references using AgentRegistry.
|
|
710
|
+
|
|
711
|
+
Uses the global AgentRegistry to cache Agent objects across deployments.
|
|
712
|
+
The registry handles all agent types: Agent classes, instances,
|
|
713
|
+
glaip_sdk.models.Agent, and string names.
|
|
714
|
+
|
|
715
|
+
Args:
|
|
716
|
+
registry: The agent registry.
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
List of resolved agent IDs for the API payload.
|
|
720
|
+
"""
|
|
721
|
+
if not self.agents:
|
|
722
|
+
return []
|
|
723
|
+
|
|
724
|
+
# Resolve each agent reference to a deployed Agent, extract ID
|
|
725
|
+
return [registry.resolve(agent_ref).id for agent_ref in self.agents]
|
|
726
|
+
|
|
727
|
+
def _create_or_update_agent(
|
|
728
|
+
self,
|
|
729
|
+
config: dict[str, Any],
|
|
730
|
+
client: Any,
|
|
731
|
+
find_agent_fn: Any,
|
|
732
|
+
) -> AgentResponse:
|
|
733
|
+
"""Create or update the agent on the platform.
|
|
734
|
+
|
|
735
|
+
Args:
|
|
736
|
+
config: The agent configuration dictionary.
|
|
737
|
+
client: The API client.
|
|
738
|
+
find_agent_fn: Function to find existing agent by name.
|
|
739
|
+
|
|
740
|
+
Returns:
|
|
741
|
+
The created or updated AgentResponse from the API.
|
|
742
|
+
"""
|
|
743
|
+
existing = find_agent_fn(self.name)
|
|
744
|
+
|
|
745
|
+
if existing:
|
|
746
|
+
logger.info("Updating existing agent: %s", self.name)
|
|
747
|
+
updated = client.update_agent(agent_id=existing.id, **config)
|
|
748
|
+
logger.info("✓ Agent '%s' updated successfully", self.name)
|
|
749
|
+
return updated
|
|
750
|
+
|
|
751
|
+
logger.info("Creating new agent: %s", self.name)
|
|
752
|
+
created = client.create_agent(**config)
|
|
753
|
+
logger.info("✓ Agent '%s' created successfully", self.name)
|
|
754
|
+
return created
|
|
755
|
+
|
|
756
|
+
@staticmethod
|
|
757
|
+
def load_instruction_from_file(
|
|
758
|
+
path: str,
|
|
759
|
+
base_path: Path | None = None,
|
|
760
|
+
variables: dict[str, str] | None = None,
|
|
761
|
+
) -> str:
|
|
762
|
+
"""Load instruction text from a markdown file.
|
|
763
|
+
|
|
764
|
+
Args:
|
|
765
|
+
path: File path (absolute or relative to base_path).
|
|
766
|
+
base_path: Base directory for relative paths. If None, auto-detected
|
|
767
|
+
from the caller's file location.
|
|
768
|
+
variables: Template variables for {{variable}} substitution.
|
|
769
|
+
|
|
770
|
+
Returns:
|
|
771
|
+
Loaded instruction text with variables substituted.
|
|
772
|
+
|
|
773
|
+
Raises:
|
|
774
|
+
FileNotFoundError: If the instruction file doesn't exist.
|
|
775
|
+
|
|
776
|
+
Example:
|
|
777
|
+
>>> instruction = Agent.load_instruction_from_file(
|
|
778
|
+
... "instructions/agent.md",
|
|
779
|
+
... variables={"agent_name": "Weather Bot"}
|
|
780
|
+
... )
|
|
781
|
+
"""
|
|
782
|
+
file_path = Path(path)
|
|
783
|
+
|
|
784
|
+
if not file_path.is_absolute():
|
|
785
|
+
if base_path is None:
|
|
786
|
+
caller_frame = inspect.stack()[1]
|
|
787
|
+
base_path = Path(caller_frame.filename).parent
|
|
788
|
+
file_path = base_path / path
|
|
789
|
+
|
|
790
|
+
if not file_path.exists():
|
|
791
|
+
raise FileNotFoundError(f"Instruction file not found: {file_path}")
|
|
792
|
+
|
|
793
|
+
content = file_path.read_text(encoding="utf-8")
|
|
794
|
+
|
|
795
|
+
if variables:
|
|
796
|
+
for key, value in variables.items():
|
|
797
|
+
content = content.replace(f"{{{{{key}}}}}", value)
|
|
798
|
+
|
|
799
|
+
return content
|
|
800
|
+
|
|
801
|
+
# =========================================================================
|
|
802
|
+
# API Methods - Available after deploy()
|
|
803
|
+
# =========================================================================
|
|
804
|
+
|
|
805
|
+
def model_dump(self, *, exclude_none: bool = False) -> dict[str, Any]:
|
|
806
|
+
"""Return a dict representation of the Agent.
|
|
807
|
+
|
|
808
|
+
Provides Pydantic-style serialization for backward compatibility.
|
|
809
|
+
|
|
810
|
+
Args:
|
|
811
|
+
exclude_none: If True, exclude None values from the output.
|
|
812
|
+
|
|
813
|
+
Returns:
|
|
814
|
+
Dictionary containing Agent attributes.
|
|
815
|
+
"""
|
|
816
|
+
data = {
|
|
817
|
+
"id": self._id,
|
|
818
|
+
"name": self.name,
|
|
819
|
+
"instruction": self.instruction,
|
|
820
|
+
"description": self.description,
|
|
821
|
+
"type": self.agent_type,
|
|
822
|
+
"framework": self.framework,
|
|
823
|
+
"version": self.version,
|
|
824
|
+
"tools": self.tools,
|
|
825
|
+
"agents": self.agents,
|
|
826
|
+
"mcps": self.mcps,
|
|
827
|
+
"timeout": self.timeout,
|
|
828
|
+
"metadata": self.metadata,
|
|
829
|
+
"agent_config": self.agent_config,
|
|
830
|
+
"tool_configs": self.tool_configs,
|
|
831
|
+
"mcp_configs": self.mcp_configs,
|
|
832
|
+
"a2a_profile": self.a2a_profile,
|
|
833
|
+
"created_at": self._created_at,
|
|
834
|
+
"updated_at": self._updated_at,
|
|
835
|
+
}
|
|
836
|
+
if exclude_none:
|
|
837
|
+
return {k: v for k, v in data.items() if v is not None}
|
|
838
|
+
return data
|
|
839
|
+
|
|
840
|
+
def _set_client(self, client: Any) -> Agent:
|
|
841
|
+
"""Set the client for API operations.
|
|
842
|
+
|
|
843
|
+
Args:
|
|
844
|
+
client: The Glaip client instance.
|
|
845
|
+
|
|
846
|
+
Returns:
|
|
847
|
+
Self for method chaining.
|
|
848
|
+
"""
|
|
849
|
+
self._client = client
|
|
850
|
+
return self
|
|
851
|
+
|
|
852
|
+
def _prepare_run_kwargs(
|
|
853
|
+
self,
|
|
854
|
+
message: str,
|
|
855
|
+
verbose: bool,
|
|
856
|
+
runtime_config: dict[str, Any] | None,
|
|
857
|
+
**kwargs: Any,
|
|
858
|
+
) -> tuple[Any, dict[str, Any]]:
|
|
859
|
+
"""Prepare common arguments for run/arun methods.
|
|
860
|
+
|
|
861
|
+
Args:
|
|
862
|
+
message: The message to send to the agent.
|
|
863
|
+
verbose: If True, print streaming output to console.
|
|
864
|
+
runtime_config: Optional runtime configuration.
|
|
865
|
+
**kwargs: Additional arguments to pass to the run API.
|
|
866
|
+
|
|
867
|
+
Returns:
|
|
868
|
+
Tuple of (agent_client, call_kwargs).
|
|
869
|
+
|
|
870
|
+
Raises:
|
|
871
|
+
ValueError: If the agent hasn't been deployed yet.
|
|
872
|
+
RuntimeError: If client is not available.
|
|
873
|
+
"""
|
|
874
|
+
if not self.id: # pragma: no cover - defensive: called only when self.id is truthy
|
|
875
|
+
raise ValueError(_AGENT_NOT_DEPLOYED_MSG)
|
|
876
|
+
if not self._client:
|
|
877
|
+
raise RuntimeError(_CLIENT_NOT_AVAILABLE_MSG)
|
|
878
|
+
|
|
879
|
+
agent_client = getattr(self._client, "agents", self._client)
|
|
880
|
+
|
|
881
|
+
call_kwargs: dict[str, Any] = {
|
|
882
|
+
"agent_id": self.id,
|
|
883
|
+
"message": message,
|
|
884
|
+
"verbose": verbose,
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if runtime_config is not None:
|
|
888
|
+
call_kwargs["runtime_config"] = normalize_runtime_config_keys(
|
|
889
|
+
runtime_config,
|
|
890
|
+
tool_registry=get_tool_registry(),
|
|
891
|
+
mcp_registry=get_mcp_registry(),
|
|
892
|
+
agent_registry=get_agent_registry(),
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
call_kwargs.update(kwargs)
|
|
896
|
+
return agent_client, call_kwargs
|
|
897
|
+
|
|
898
|
+
def _get_local_runner_or_raise(self) -> Any:
|
|
899
|
+
"""Get the local runner if available, otherwise raise ValueError.
|
|
900
|
+
|
|
901
|
+
Returns:
|
|
902
|
+
The default local runner instance.
|
|
903
|
+
|
|
904
|
+
Raises:
|
|
905
|
+
ValueError: If local runtime is not available.
|
|
906
|
+
"""
|
|
907
|
+
if check_local_runtime_available():
|
|
908
|
+
return get_default_runner()
|
|
909
|
+
raise ValueError(f"{_AGENT_NOT_DEPLOYED_MSG}\n\n{get_local_runtime_missing_message()}")
|
|
910
|
+
|
|
911
|
+
def _prepare_local_runner_kwargs(
|
|
912
|
+
self,
|
|
913
|
+
message: str,
|
|
914
|
+
verbose: bool,
|
|
915
|
+
runtime_config: dict[str, Any] | None,
|
|
916
|
+
chat_history: list[dict[str, str]] | None,
|
|
917
|
+
**kwargs: Any,
|
|
918
|
+
) -> dict[str, Any]:
|
|
919
|
+
"""Prepare kwargs for local runner execution.
|
|
920
|
+
|
|
921
|
+
Args:
|
|
922
|
+
message: The message to send to the agent.
|
|
923
|
+
verbose: If True, print streaming output to console.
|
|
924
|
+
runtime_config: Optional runtime configuration.
|
|
925
|
+
chat_history: Optional list of prior conversation messages.
|
|
926
|
+
**kwargs: Additional arguments.
|
|
927
|
+
|
|
928
|
+
Returns:
|
|
929
|
+
Dictionary of prepared kwargs for runner.run() or runner.arun().
|
|
930
|
+
"""
|
|
931
|
+
return {
|
|
932
|
+
"agent": self,
|
|
933
|
+
"message": message,
|
|
934
|
+
"verbose": verbose,
|
|
935
|
+
"runtime_config": runtime_config,
|
|
936
|
+
"chat_history": chat_history,
|
|
937
|
+
**kwargs,
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
def run(
|
|
941
|
+
self,
|
|
942
|
+
message: str,
|
|
943
|
+
verbose: bool = False,
|
|
944
|
+
runtime_config: dict[str, Any] | None = None,
|
|
945
|
+
chat_history: list[dict[str, str]] | None = None,
|
|
946
|
+
**kwargs: Any,
|
|
947
|
+
) -> str:
|
|
948
|
+
"""Run the agent synchronously with a message.
|
|
949
|
+
|
|
950
|
+
Supports two execution modes:
|
|
951
|
+
- **Server-backed**: When the agent is deployed (has an ID), execution
|
|
952
|
+
happens via the AIP backend server.
|
|
953
|
+
- **Local**: When the agent is not deployed and glaip-sdk[local] is installed,
|
|
954
|
+
execution happens locally via aip-agents (no server required).
|
|
955
|
+
|
|
956
|
+
Args:
|
|
957
|
+
message: The message to send to the agent.
|
|
958
|
+
verbose: If True, print streaming output to console. Defaults to False.
|
|
959
|
+
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
960
|
+
Keys can be SDK objects, UUIDs, or names. Example:
|
|
961
|
+
{
|
|
962
|
+
"tool_configs": {"tool-id": {"param": "value"}},
|
|
963
|
+
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
964
|
+
"agent_config": {"planning": True},
|
|
965
|
+
}
|
|
966
|
+
Defaults to None.
|
|
967
|
+
chat_history: Optional list of prior conversation messages for context.
|
|
968
|
+
Each message is a dict with "role" and "content" keys.
|
|
969
|
+
Defaults to None.
|
|
970
|
+
**kwargs: Additional arguments to pass to the run API.
|
|
971
|
+
|
|
972
|
+
Returns:
|
|
973
|
+
The agent's response as a string.
|
|
974
|
+
|
|
975
|
+
Raises:
|
|
976
|
+
ValueError: If the agent is not deployed and local runtime is not available.
|
|
977
|
+
RuntimeError: If server-backed execution fails due to client issues.
|
|
978
|
+
"""
|
|
979
|
+
# Backend routing: deployed agents use server, undeployed use local (if available)
|
|
980
|
+
if self.id:
|
|
981
|
+
# Server-backed execution path (agent is deployed)
|
|
982
|
+
agent_client, call_kwargs = self._prepare_run_kwargs(
|
|
983
|
+
message, verbose, runtime_config or kwargs.get("runtime_config"), **kwargs
|
|
984
|
+
)
|
|
985
|
+
if chat_history is not None:
|
|
986
|
+
call_kwargs["chat_history"] = chat_history
|
|
987
|
+
return agent_client.run_agent(**call_kwargs)
|
|
988
|
+
|
|
989
|
+
# Local execution path (agent is not deployed)
|
|
990
|
+
runner = self._get_local_runner_or_raise()
|
|
991
|
+
local_kwargs = self._prepare_local_runner_kwargs(message, verbose, runtime_config, chat_history, **kwargs)
|
|
992
|
+
return runner.run(**local_kwargs)
|
|
993
|
+
|
|
994
|
+
async def arun(
|
|
995
|
+
self,
|
|
996
|
+
message: str,
|
|
997
|
+
verbose: bool = False,
|
|
998
|
+
runtime_config: dict[str, Any] | None = None,
|
|
999
|
+
chat_history: list[dict[str, str]] | None = None,
|
|
1000
|
+
**kwargs: Any,
|
|
1001
|
+
) -> AsyncGenerator[dict, None]:
|
|
1002
|
+
"""Run the agent asynchronously with streaming output.
|
|
1003
|
+
|
|
1004
|
+
Supports two execution modes:
|
|
1005
|
+
- **Server-backed**: When the agent is deployed (has an ID), execution
|
|
1006
|
+
happens via the AIP backend server with streaming.
|
|
1007
|
+
- **Local**: When the agent is not deployed and glaip-sdk[local] is installed,
|
|
1008
|
+
execution happens locally via aip-agents (no server required).
|
|
1009
|
+
|
|
1010
|
+
Args:
|
|
1011
|
+
message: The message to send to the agent.
|
|
1012
|
+
verbose: If True, print streaming output to console. Defaults to False.
|
|
1013
|
+
runtime_config: Optional runtime configuration for tools, MCPs, and agents.
|
|
1014
|
+
Keys can be SDK objects, UUIDs, or names. Example:
|
|
1015
|
+
{
|
|
1016
|
+
"tool_configs": {"tool-id": {"param": "value"}},
|
|
1017
|
+
"mcp_configs": {"mcp-id": {"setting": "on"}},
|
|
1018
|
+
"agent_config": {"planning": True},
|
|
1019
|
+
}
|
|
1020
|
+
Defaults to None.
|
|
1021
|
+
chat_history: Optional list of prior conversation messages for context.
|
|
1022
|
+
Each message is a dict with "role" and "content" keys.
|
|
1023
|
+
Defaults to None.
|
|
1024
|
+
**kwargs: Additional arguments to pass to the run API.
|
|
1025
|
+
|
|
1026
|
+
Yields:
|
|
1027
|
+
Streaming response chunks from the agent.
|
|
1028
|
+
|
|
1029
|
+
Raises:
|
|
1030
|
+
ValueError: If the agent is not deployed and local runtime is not available.
|
|
1031
|
+
RuntimeError: If server-backed execution fails due to client issues.
|
|
1032
|
+
"""
|
|
1033
|
+
# Backend routing: deployed agents use server, undeployed use local (if available)
|
|
1034
|
+
if self.id:
|
|
1035
|
+
# Server-backed execution path (agent is deployed)
|
|
1036
|
+
agent_client, call_kwargs = self._prepare_run_kwargs(
|
|
1037
|
+
message, verbose, runtime_config or kwargs.get("runtime_config"), **kwargs
|
|
1038
|
+
)
|
|
1039
|
+
if chat_history is not None:
|
|
1040
|
+
call_kwargs["chat_history"] = chat_history
|
|
1041
|
+
|
|
1042
|
+
async for chunk in agent_client.arun_agent(**call_kwargs):
|
|
1043
|
+
yield chunk
|
|
1044
|
+
return
|
|
1045
|
+
|
|
1046
|
+
# Local execution path (agent is not deployed)
|
|
1047
|
+
runner = self._get_local_runner_or_raise()
|
|
1048
|
+
local_kwargs = self._prepare_local_runner_kwargs(message, verbose, runtime_config, chat_history, **kwargs)
|
|
1049
|
+
result = await runner.arun(**local_kwargs)
|
|
1050
|
+
# Yield a final_response event for consistency with server-backed execution
|
|
1051
|
+
# Include event_type for A2A event shape parity
|
|
1052
|
+
yield {
|
|
1053
|
+
"event_type": "final_response",
|
|
1054
|
+
"metadata": {"kind": "final_response"},
|
|
1055
|
+
"content": result,
|
|
1056
|
+
"is_final": True,
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
def update(self, **kwargs: Any) -> Agent:
|
|
1060
|
+
"""Update the deployed agent with new configuration.
|
|
1061
|
+
|
|
1062
|
+
Args:
|
|
1063
|
+
**kwargs: Agent properties to update (name, instruction, etc.).
|
|
1064
|
+
|
|
1065
|
+
Returns:
|
|
1066
|
+
Self with updated properties.
|
|
1067
|
+
|
|
1068
|
+
Raises:
|
|
1069
|
+
ValueError: If the agent hasn't been deployed yet.
|
|
1070
|
+
RuntimeError: If client is not available.
|
|
1071
|
+
"""
|
|
1072
|
+
if not self.id:
|
|
1073
|
+
raise ValueError(_AGENT_NOT_DEPLOYED_MSG)
|
|
1074
|
+
if not self._client:
|
|
1075
|
+
raise RuntimeError(_CLIENT_NOT_AVAILABLE_MSG)
|
|
1076
|
+
|
|
1077
|
+
# _client can be either main Client or AgentClient
|
|
1078
|
+
agent_client = getattr(self._client, "agents", self._client)
|
|
1079
|
+
response = agent_client.update_agent(agent_id=self.id, **kwargs)
|
|
1080
|
+
|
|
1081
|
+
# Update local properties from response (read-only props via private attrs)
|
|
1082
|
+
name = getattr(response, "name", None)
|
|
1083
|
+
if name:
|
|
1084
|
+
self._name = name
|
|
1085
|
+
|
|
1086
|
+
instruction = getattr(response, "instruction", None)
|
|
1087
|
+
if instruction:
|
|
1088
|
+
self._instruction = instruction
|
|
1089
|
+
|
|
1090
|
+
# Populate remaining fields like description, metadata, updated_at, etc.
|
|
1091
|
+
type(self)._populate_from_response(self, response)
|
|
1092
|
+
|
|
1093
|
+
return self
|
|
1094
|
+
|
|
1095
|
+
def delete(self) -> None:
|
|
1096
|
+
"""Delete the deployed agent.
|
|
1097
|
+
|
|
1098
|
+
Raises:
|
|
1099
|
+
ValueError: If the agent hasn't been deployed yet.
|
|
1100
|
+
RuntimeError: If client is not available.
|
|
1101
|
+
"""
|
|
1102
|
+
if not self.id:
|
|
1103
|
+
raise ValueError(_AGENT_NOT_DEPLOYED_MSG)
|
|
1104
|
+
if not self._client:
|
|
1105
|
+
raise RuntimeError(_CLIENT_NOT_AVAILABLE_MSG)
|
|
1106
|
+
|
|
1107
|
+
# _client can be either main Client or AgentClient
|
|
1108
|
+
agent_client = getattr(self._client, "agents", self._client)
|
|
1109
|
+
agent_client.delete_agent(self.id)
|
|
1110
|
+
self.id = None
|
|
1111
|
+
self._client = None
|
|
1112
|
+
|
|
1113
|
+
@classmethod
|
|
1114
|
+
def _populate_from_response(cls, agent: Agent, response: AgentResponse) -> None:
|
|
1115
|
+
"""Populate agent fields from API response."""
|
|
1116
|
+
# Field mappings: (response_attr, agent_attr, require_truthy)
|
|
1117
|
+
field_mappings = [
|
|
1118
|
+
("description", "_description", True),
|
|
1119
|
+
("model", "_model", True),
|
|
1120
|
+
("type", "_agent_type", True),
|
|
1121
|
+
("framework", "_framework", True),
|
|
1122
|
+
("version", "_version", True),
|
|
1123
|
+
("timeout", "_timeout", True),
|
|
1124
|
+
("metadata", "_metadata", True),
|
|
1125
|
+
("agent_config", "_agent_config", True),
|
|
1126
|
+
("tool_configs", "_tool_configs", True),
|
|
1127
|
+
("mcp_configs", "_mcp_configs", True),
|
|
1128
|
+
("a2a_profile", "_a2a_profile", True),
|
|
1129
|
+
("language_model_id", "_language_model_id", True),
|
|
1130
|
+
("created_at", "_created_at", False),
|
|
1131
|
+
("updated_at", "_updated_at", False),
|
|
1132
|
+
]
|
|
1133
|
+
|
|
1134
|
+
for resp_attr, agent_attr, require_truthy in field_mappings:
|
|
1135
|
+
value = getattr(response, resp_attr, None)
|
|
1136
|
+
if require_truthy:
|
|
1137
|
+
if value:
|
|
1138
|
+
setattr(agent, agent_attr, value)
|
|
1139
|
+
else:
|
|
1140
|
+
if value is not None:
|
|
1141
|
+
setattr(agent, agent_attr, value)
|
|
1142
|
+
|
|
1143
|
+
# Copy relationship refs as-is (preserve full objects for serialization)
|
|
1144
|
+
# Registry resolution handles extracting IDs during deployment
|
|
1145
|
+
tools_val = getattr(response, "tools", None)
|
|
1146
|
+
if tools_val:
|
|
1147
|
+
agent._tools = tools_val
|
|
1148
|
+
|
|
1149
|
+
agents_val = getattr(response, "agents", None)
|
|
1150
|
+
if agents_val:
|
|
1151
|
+
agent._agents = agents_val
|
|
1152
|
+
|
|
1153
|
+
mcps_val = getattr(response, "mcps", None)
|
|
1154
|
+
if mcps_val:
|
|
1155
|
+
agent._mcps = mcps_val
|
|
1156
|
+
|
|
1157
|
+
@classmethod
|
|
1158
|
+
def from_response(
|
|
1159
|
+
cls,
|
|
1160
|
+
response: AgentResponse,
|
|
1161
|
+
client: Any = None,
|
|
1162
|
+
) -> Agent:
|
|
1163
|
+
"""Create an Agent instance from an API response.
|
|
1164
|
+
|
|
1165
|
+
This allows you to work with agents retrieved from the API
|
|
1166
|
+
as full Agent instances with all methods available.
|
|
1167
|
+
|
|
1168
|
+
Args:
|
|
1169
|
+
response: The AgentResponse from an API call.
|
|
1170
|
+
client: The Glaip client instance for API operations.
|
|
1171
|
+
|
|
1172
|
+
Returns:
|
|
1173
|
+
An Agent instance initialized from the response.
|
|
1174
|
+
|
|
1175
|
+
Example:
|
|
1176
|
+
>>> response = client.agents.get("agent-123")
|
|
1177
|
+
>>> agent = Agent.from_response(response, client)
|
|
1178
|
+
>>> result = agent.run("Hello!")
|
|
1179
|
+
"""
|
|
1180
|
+
agent = cls(
|
|
1181
|
+
name=response.name,
|
|
1182
|
+
instruction=response.instruction or "",
|
|
1183
|
+
id=response.id,
|
|
1184
|
+
)
|
|
1185
|
+
|
|
1186
|
+
cls._populate_from_response(agent, response)
|
|
1187
|
+
|
|
1188
|
+
if client:
|
|
1189
|
+
agent._set_client(client)
|
|
1190
|
+
|
|
1191
|
+
return agent
|