glaip-sdk 0.7.13__py3-none-any.whl → 0.7.15__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/agents/base.py CHANGED
@@ -54,10 +54,13 @@ from glaip_sdk.utils.resource_refs import is_uuid
54
54
 
55
55
  if TYPE_CHECKING:
56
56
  from glaip_sdk.client.schedules import AgentScheduleManager
57
+ from glaip_sdk.models import AgentResponse, Model
57
58
  from glaip_sdk.guardrails import GuardrailManager
58
- from glaip_sdk.models import AgentResponse
59
59
  from glaip_sdk.registry import AgentRegistry, MCPRegistry, ToolRegistry
60
60
 
61
+ # Import model validation utility
62
+ from glaip_sdk.models._validation import _validate_model
63
+
61
64
  logger = logging.getLogger(__name__)
62
65
 
63
66
  _AGENT_NOT_DEPLOYED_MSG = "Agent must be deployed before running. Call deploy() first."
@@ -99,11 +102,11 @@ class Agent:
99
102
  - instruction: Agent instruction text (required)
100
103
  - description: Agent description (default: "")
101
104
  - tools: List of tools (default: [])
105
+ - model: Optional model override (default: None)
102
106
  - agents: List of sub-agents (default: [])
103
107
  - mcps: List of MCPs (default: [])
104
108
  - timeout: Timeout in seconds (default: 300)
105
109
  - metadata: Optional metadata dict (default: None)
106
- - model: Optional model override (default: None)
107
110
  - framework: Agent framework (default: "langchain")
108
111
  - version: Agent version (default: "1.0.0")
109
112
  - agent_type: Agent type (default: "config")
@@ -130,7 +133,7 @@ class Agent:
130
133
  tools: list | None = None,
131
134
  agents: list | None = None,
132
135
  mcps: list | None = None,
133
- model: str | None = _UNSET, # type: ignore[assignment]
136
+ model: str | Model | None = _UNSET, # type: ignore[assignment]
134
137
  guardrail: GuardrailManager | None = None,
135
138
  _client: Any = None,
136
139
  **kwargs: Any,
@@ -148,7 +151,7 @@ class Agent:
148
151
  tools: List of tools (Tool classes, SDK Tool objects, or strings).
149
152
  agents: List of sub-agents (Agent classes, instances, or strings).
150
153
  mcps: List of MCPs.
151
- model: Model identifier.
154
+ model: Model identifier or Model configuration object.
152
155
  guardrail: The guardrail manager for content safety.
153
156
  _client: Internal client reference (set automatically).
154
157
 
@@ -176,7 +179,7 @@ class Agent:
176
179
  self._tools = tools
177
180
  self._agents = agents
178
181
  self._mcps = mcps
179
- self._model = model
182
+ self._model = self._validate_and_set_model(model)
180
183
  self._guardrail = guardrail
181
184
  self._language_model_id: str | None = None
182
185
  # Extract parameters from kwargs with _UNSET defaults
@@ -199,7 +202,11 @@ class Agent:
199
202
 
200
203
  self._agent_config = kwargs.pop("agent_config", Agent._UNSET) # type: ignore[assignment]
201
204
  self._tool_configs = kwargs.pop("tool_configs", Agent._UNSET) # type: ignore[assignment]
202
- self._mcp_configs = kwargs.pop("mcp_configs", Agent._UNSET) # type: ignore[assignment]
205
+ mcp_configs = kwargs.pop("mcp_configs", Agent._UNSET)
206
+ if mcp_configs is not Agent._UNSET and isinstance(mcp_configs, dict):
207
+ self._mcp_configs = self._normalize_mcp_configs(mcp_configs)
208
+ else:
209
+ self._mcp_configs = mcp_configs # type: ignore[assignment]
203
210
  self._a2a_profile = kwargs.pop("a2a_profile", Agent._UNSET) # type: ignore[assignment]
204
211
 
205
212
  # Warn about unexpected kwargs
@@ -210,6 +217,30 @@ class Agent:
210
217
  stacklevel=2,
211
218
  )
212
219
 
220
+ def _validate_and_set_model(self, model: str | Any) -> str | Any:
221
+ """Validate and normalize model parameter.
222
+
223
+ Supports both string model identifiers and Model objects:
224
+ - String: Simple model identifier (e.g., "openai/gpt-4o" or OpenAI.GPT_4O)
225
+ - Model: Model object with credentials/hyperparameters for local execution
226
+
227
+ Args:
228
+ model: Model identifier (string) or Model object.
229
+
230
+ Returns:
231
+ Validated model (string or Model object).
232
+ """
233
+ if model is None or model is Agent._UNSET:
234
+ return model
235
+
236
+ from glaip_sdk.models import Model # noqa: PLC0415
237
+
238
+ if isinstance(model, str):
239
+ return _validate_model(model)
240
+ elif isinstance(model, Model):
241
+ return model
242
+ return model
243
+
213
244
  # ─────────────────────────────────────────────────────────────────
214
245
  # Properties (override in subclasses OR pass to __init__)
215
246
  # ─────────────────────────────────────────────────────────────────
@@ -354,11 +385,11 @@ class Agent:
354
385
  return None
355
386
 
356
387
  @property
357
- def model(self) -> str | None:
388
+ def model(self) -> str | Model | None:
358
389
  """Optional model override.
359
390
 
360
391
  Returns:
361
- Model identifier string or None to use default.
392
+ Model identifier string, Model object, or None to use default.
362
393
  """
363
394
  if self._model is not self._UNSET:
364
395
  return self._model
@@ -561,9 +592,14 @@ class Agent:
561
592
  "framework": self.framework,
562
593
  "version": self.version,
563
594
  "agent_type": self.agent_type,
564
- "model": self.model,
565
595
  }
566
596
 
597
+ if self.model:
598
+ if isinstance(self.model, str):
599
+ config["model"] = self.model
600
+ else:
601
+ config["model"] = self.model.id
602
+
567
603
  # Handle metadata (default to empty dict if None)
568
604
  config["metadata"] = self.metadata or {}
569
605
 
@@ -751,6 +787,47 @@ class Agent:
751
787
 
752
788
  return resolved
753
789
 
790
+ def _normalize_mcp_configs(self, mcp_configs: dict[Any, Any]) -> dict[Any, Any]:
791
+ """Normalize mcp_configs by wrapping misplaced transport keys in 'config'.
792
+
793
+ This ensures that flat transport settings (e.g. {'url': '...'}) provided
794
+ by the user are correctly moved into the 'config' block required by the
795
+ Platform, ensuring parity between local and remote execution.
796
+
797
+ Args:
798
+ mcp_configs: The raw mcp_configs dictionary.
799
+
800
+ Returns:
801
+ Normalized mcp_configs dictionary.
802
+ """
803
+ from glaip_sdk.runner.langgraph import _MCP_TRANSPORT_KEYS # noqa: PLC0415
804
+
805
+ normalized = {}
806
+ for mcp_key, override in mcp_configs.items():
807
+ if not isinstance(override, dict):
808
+ normalized[mcp_key] = override
809
+ continue
810
+
811
+ misplaced = {k: v for k, v in override.items() if k in _MCP_TRANSPORT_KEYS}
812
+
813
+ if misplaced:
814
+ new_override = override.copy()
815
+ config_block = new_override.get("config", {})
816
+ if not isinstance(config_block, dict):
817
+ config_block = {}
818
+
819
+ config_block.update(misplaced)
820
+ new_override["config"] = config_block
821
+
822
+ for k in misplaced:
823
+ new_override.pop(k, None)
824
+
825
+ normalized[mcp_key] = new_override
826
+ else:
827
+ normalized[mcp_key] = override
828
+
829
+ return normalized
830
+
754
831
  def _resolve_agents(self, registry: AgentRegistry) -> list[str]:
755
832
  """Resolve sub-agent references using AgentRegistry.
756
833
 
@@ -11,37 +11,30 @@ Authors:
11
11
  # Import from submodules
12
12
  from glaip_sdk.cli.commands.agents._common import ( # noqa: E402
13
13
  AGENT_NOT_FOUND_ERROR,
14
- _get_agent_for_update,
15
- _resolve_agent,
16
- agents_group,
17
14
  _coerce_mapping_candidate,
18
15
  _display_agent_details,
19
16
  _emit_verbose_guidance,
20
17
  _fetch_full_agent_details,
18
+ _get_agent_for_update,
21
19
  _get_agent_model_name,
22
20
  _get_language_model_display_name,
23
21
  _model_from_config,
24
22
  _prepare_agent_output,
23
+ _resolve_agent,
25
24
  _resolve_resources_by_name,
25
+ agents_group,
26
26
  console,
27
27
  )
28
28
  from glaip_sdk.cli.commands.agents.create import create # noqa: E402
29
29
  from glaip_sdk.cli.commands.agents.delete import delete # noqa: E402
30
30
  from glaip_sdk.cli.commands.agents.get import get # noqa: E402
31
31
  from glaip_sdk.cli.commands.agents.list import list_agents # noqa: E402
32
- from glaip_sdk.cli.commands.agents.run import run, _maybe_attach_transcript_toggle # noqa: E402
32
+ from glaip_sdk.cli.commands.agents.run import _maybe_attach_transcript_toggle, run # noqa: E402
33
33
  from glaip_sdk.cli.commands.agents.sync_langflow import sync_langflow # noqa: E402
34
34
  from glaip_sdk.cli.commands.agents.update import update # noqa: E402
35
35
 
36
36
  # Import core functions for test compatibility
37
37
  from glaip_sdk.cli.core.context import get_client # noqa: E402
38
- from glaip_sdk.cli.core.rendering import with_client_and_spinner # noqa: E402
39
-
40
- # Import display functions for test compatibility
41
- from glaip_sdk.cli.display import ( # noqa: E402
42
- handle_json_output,
43
- handle_rich_output,
44
- )
45
38
 
46
39
  # Import core output functions for test compatibility
47
40
  from glaip_sdk.cli.core.output import ( # noqa: E402
@@ -52,11 +45,20 @@ from glaip_sdk.cli.core.output import ( # noqa: E402
52
45
  # Import rendering functions for test compatibility
53
46
  from glaip_sdk.cli.core.rendering import ( # noqa: E402
54
47
  build_renderer,
48
+ with_client_and_spinner, # noqa: E402
55
49
  )
56
50
 
57
51
  # Import display functions for test compatibility
58
- from glaip_sdk.cli.display import ( # noqa: E402
52
+ # Import display functions for test compatibility
53
+ from glaip_sdk.cli.display import ( # noqa: E402 # noqa: E402
59
54
  display_agent_run_suggestions,
55
+ handle_json_output,
56
+ handle_rich_output,
57
+ )
58
+
59
+ # Import IO functions for test compatibility
60
+ from glaip_sdk.cli.io import ( # noqa: E402
61
+ fetch_raw_resource_details,
60
62
  )
61
63
 
62
64
  # Import rich helpers for test compatibility
@@ -70,11 +72,6 @@ from glaip_sdk.cli.transcript import ( # noqa: E402
70
72
  store_transcript_for_session,
71
73
  )
72
74
 
73
- # Import IO functions for test compatibility
74
- from glaip_sdk.cli.io import ( # noqa: E402
75
- fetch_raw_resource_details,
76
- )
77
-
78
75
  # Import utils for test compatibility
79
76
  from glaip_sdk.utils import ( # noqa: E402
80
77
  is_uuid,
@@ -19,8 +19,11 @@ from glaip_sdk.branding import (
19
19
  WARNING_STYLE,
20
20
  )
21
21
  from glaip_sdk.cli.constants import DEFAULT_AGENT_INSTRUCTION_PREVIEW_LIMIT
22
- from glaip_sdk.config.constants import AGENT_CONFIG_FIELDS, DEFAULT_MODEL
23
22
  from glaip_sdk.cli.context import get_ctx_value
23
+ from glaip_sdk.cli.core.output import (
24
+ output_result,
25
+ )
26
+ from glaip_sdk.cli.core.rendering import spinner_context
24
27
  from glaip_sdk.cli.display import (
25
28
  build_resource_result_data,
26
29
  handle_json_output,
@@ -31,10 +34,8 @@ from glaip_sdk.cli.hints import in_slash_mode
31
34
  from glaip_sdk.cli.io import fetch_raw_resource_details
32
35
  from glaip_sdk.cli.resolution import resolve_resource_reference
33
36
  from glaip_sdk.cli.rich_helpers import markup_text
34
- from glaip_sdk.cli.core.output import (
35
- output_result,
36
- )
37
- from glaip_sdk.cli.core.rendering import spinner_context
37
+ from glaip_sdk.config.constants import AGENT_CONFIG_FIELDS
38
+ from glaip_sdk.models.constants import DEFAULT_MODEL
38
39
  from glaip_sdk.icons import ICON_AGENT
39
40
  from glaip_sdk.utils import format_datetime, is_uuid
40
41
 
@@ -11,19 +11,19 @@ from typing import Any
11
11
  import click
12
12
 
13
13
  from glaip_sdk.cli.context import output_flags
14
+ from glaip_sdk.cli.core.context import get_client
14
15
  from glaip_sdk.cli.display import (
15
16
  display_agent_run_suggestions,
16
17
  display_creation_success,
17
18
  handle_json_output,
18
19
  handle_rich_output,
19
20
  )
20
- from glaip_sdk.cli.core.context import get_client
21
- from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
22
21
  from glaip_sdk.cli.validators import (
23
22
  validate_agent_instruction_cli as validate_agent_instruction,
24
- validate_agent_name_cli as validate_agent_name,
25
- validate_timeout_cli as validate_timeout,
26
23
  )
24
+ from glaip_sdk.cli.validators import validate_agent_name_cli as validate_agent_name
25
+ from glaip_sdk.cli.validators import validate_timeout_cli as validate_timeout
26
+ from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT
27
27
  from glaip_sdk.utils.validation import coerce_timeout
28
28
 
29
29
  from ._common import (
@@ -67,7 +67,11 @@ def _handle_creation_exception(ctx: Any, e: Exception) -> None:
67
67
  @click.option("--instruction", help="Agent instruction (prompt)")
68
68
  @click.option(
69
69
  "--model",
70
- help=f"Language model to use (e.g., {DEFAULT_MODEL}, default: {DEFAULT_MODEL})",
70
+ help=(
71
+ "Language model in 'provider/model' format "
72
+ "(e.g., openai/gpt-4o-mini, bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0). "
73
+ "Use 'aip models list' to see available models."
74
+ ),
71
75
  )
72
76
  @click.option("--tools", multiple=True, help="Tool names or IDs to attach")
73
77
  @click.option("--agents", multiple=True, help="Sub-agent names or IDs to attach")
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env python3
1
+ # pylint: disable=duplicate-code
2
2
  """Agent client for AIP SDK.
3
3
 
4
4
  Authors:
@@ -9,6 +9,7 @@ Authors:
9
9
  import asyncio
10
10
  import json
11
11
  import logging
12
+ import warnings
12
13
  from collections.abc import AsyncGenerator, Callable, Iterator, Mapping
13
14
  from contextlib import asynccontextmanager
14
15
  from os import PathLike
@@ -21,15 +22,15 @@ if TYPE_CHECKING:
21
22
 
22
23
  import httpx
23
24
  from glaip_sdk.agents import Agent
25
+ from glaip_sdk.client.agent_runs import AgentRunsClient
26
+ from glaip_sdk.client.base import BaseClient
27
+ from glaip_sdk.client.mcps import MCPClient
24
28
  from glaip_sdk.client.payloads.agent import (
25
29
  AgentCreateRequest,
26
30
  AgentListParams,
27
31
  AgentListResult,
28
32
  AgentUpdateRequest,
29
33
  )
30
- from glaip_sdk.client.agent_runs import AgentRunsClient
31
- from glaip_sdk.client.base import BaseClient
32
- from glaip_sdk.client.mcps import MCPClient
33
34
  from glaip_sdk.client.run_rendering import (
34
35
  AgentRunRenderingManager,
35
36
  compute_timeout_seconds,
@@ -42,10 +43,10 @@ from glaip_sdk.config.constants import (
42
43
  DEFAULT_AGENT_RUN_TIMEOUT,
43
44
  DEFAULT_AGENT_TYPE,
44
45
  DEFAULT_AGENT_VERSION,
45
- DEFAULT_MODEL,
46
46
  )
47
47
  from glaip_sdk.exceptions import NotFoundError, ValidationError
48
48
  from glaip_sdk.models import AgentResponse
49
+ from glaip_sdk.models.constants import DEFAULT_MODEL
49
50
  from glaip_sdk.payload_schemas.agent import list_server_only_fields
50
51
  from glaip_sdk.utils.agent_config import normalize_agent_config_for_import
51
52
  from glaip_sdk.utils.client_utils import (
@@ -255,13 +256,16 @@ class AgentClient(BaseClient):
255
256
  self,
256
257
  *,
257
258
  parent_client: BaseClient | None = None,
259
+ lm_cache_ttl: float = 3600.0,
258
260
  **kwargs: Any,
259
261
  ) -> None:
260
262
  """Initialize the agent client.
261
263
 
262
264
  Args:
263
- parent_client: Parent client to adopt session/config from
264
- **kwargs: Additional arguments for standalone initialization
265
+ parent_client: Parent client to adopt session/config from.
266
+ lm_cache_ttl: TTL for the language model list cache in seconds.
267
+ Defaults to 3600 (1 hour).
268
+ **kwargs: Additional arguments for standalone initialization.
265
269
  """
266
270
  super().__init__(parent_client=parent_client, **kwargs)
267
271
  self._renderer_manager = AgentRunRenderingManager(logger)
@@ -270,6 +274,117 @@ class AgentClient(BaseClient):
270
274
  self._runs_client: AgentRunsClient | None = None
271
275
  self._schedule_client: ScheduleClient | None = None
272
276
 
277
+ self._lm_cache: list[dict[str, Any]] | None = None
278
+ self._lm_cache_time: float = 0.0
279
+ self._lm_cache_ttl: float = lm_cache_ttl
280
+
281
+ def clear_language_model_cache(self) -> None:
282
+ """Invalidate the language model list cache.
283
+
284
+ Forces the next call to list_language_models() to fetch a fresh list
285
+ from the server.
286
+ """
287
+ self._lm_cache = None
288
+ self._lm_cache_time = 0.0
289
+ logger.debug("Language model cache invalidated.")
290
+
291
+ def _resolve_language_model_id(self, model_str: str | None) -> str | None:
292
+ """Resolve a friendly model name to a server language model ID.
293
+
294
+ Handles provider name mapping (e.g., 'deepinfra/model' → 'openai-compatible/model')
295
+ by checking both the original provider name and its driver equivalent.
296
+
297
+ Args:
298
+ model_str: The model string to resolve (e.g., 'openai/gpt-4o', 'deepinfra/Qwen3-30B').
299
+
300
+ Returns:
301
+ The resolved server model ID (UUID), or None if not found.
302
+
303
+ Examples:
304
+ >>> _resolve_language_model_id("openai/gpt-4o")
305
+ "uuid-1234-..."
306
+ >>> _resolve_language_model_id("deepinfra/Qwen3-30B") # Maps to openai-compatible
307
+ "uuid-5678-..."
308
+ """
309
+ if not model_str:
310
+ return None
311
+
312
+ # If resolution is explicitly disabled (e.g. in unit tests to avoid extra API calls), skip it
313
+ if getattr(self, "_skip_model_resolution", False):
314
+ return None
315
+
316
+ try:
317
+ models = self.list_language_models()
318
+
319
+ # Try exact match first
320
+ model_id = self._find_exact_model_match(model_str, models)
321
+ if model_id:
322
+ return model_id
323
+
324
+ # Try with provider-to-driver mapping
325
+ return self._try_resolve_with_driver_mapping(model_str, models)
326
+ except Exception:
327
+ pass
328
+
329
+ return None
330
+
331
+ def _find_exact_model_match(self, model_str: str, models: list[dict[str, Any]]) -> str | None:
332
+ """Find exact model match in models list.
333
+
334
+ Args:
335
+ model_str: Model string to match.
336
+ models: List of language model dictionaries from server.
337
+
338
+ Returns:
339
+ Model ID (UUID) if found, None otherwise.
340
+ """
341
+ for model_info in models:
342
+ provider = model_info.get("provider")
343
+ name = model_info.get("name")
344
+ if provider and name:
345
+ full_name = f"{provider}/{name}"
346
+ if full_name == model_str:
347
+ return model_info.get("id")
348
+ if name == model_str:
349
+ return model_info.get("id")
350
+ return None
351
+
352
+ def _try_resolve_with_driver_mapping(self, model_str: str, models: list[dict[str, Any]]) -> str | None:
353
+ """Try to resolve model using provider-to-driver mapping.
354
+
355
+ Maps provider names to their driver implementations (e.g., deepinfra → openai-compatible)
356
+ and searches the models list with the driver name.
357
+
358
+ Args:
359
+ model_str: Model string in provider/model format (e.g., "deepinfra/Qwen3-30B").
360
+ models: List of language model dictionaries from server.
361
+
362
+ Returns:
363
+ Model ID (UUID) if found, None otherwise.
364
+ """
365
+ if "/" not in model_str:
366
+ return None
367
+
368
+ from glaip_sdk.models._provider_mappings import get_driver # noqa: PLC0415
369
+
370
+ provider, model_name = model_str.split("/", 1)
371
+ driver = get_driver(provider)
372
+
373
+ # Only try with driver if it's different from provider
374
+ if driver == provider:
375
+ return None
376
+
377
+ driver_model_str = f"{driver}/{model_name}"
378
+ for model_info in models:
379
+ provider_field = model_info.get("provider")
380
+ name_field = model_info.get("name")
381
+ if provider_field and name_field:
382
+ full_name = f"{provider_field}/{name_field}"
383
+ if full_name == driver_model_str:
384
+ return model_info.get("id")
385
+
386
+ return None
387
+
273
388
  def list_agents(
274
389
  self,
275
390
  query: AgentListParams | None = None,
@@ -461,11 +576,18 @@ class AgentClient(BaseClient):
461
576
  Returns:
462
577
  Final text string.
463
578
  """
464
- from glaip_sdk.client.run_rendering import finalize_render_manager # noqa: PLC0415
579
+ from glaip_sdk.client.run_rendering import ( # noqa: PLC0415
580
+ finalize_render_manager,
581
+ )
465
582
 
466
583
  manager = self._get_renderer_manager()
467
584
  return finalize_render_manager(
468
- manager, renderer, final_text, stats_usage, started_monotonic, finished_monotonic
585
+ manager,
586
+ renderer,
587
+ final_text,
588
+ stats_usage,
589
+ started_monotonic,
590
+ finished_monotonic,
469
591
  )
470
592
 
471
593
  def _get_tool_client(self) -> ToolClient:
@@ -769,10 +891,18 @@ class AgentClient(BaseClient):
769
891
  plural_label="MCPs",
770
892
  )
771
893
 
772
- def _create_agent_from_payload(self, payload: Mapping[str, Any]) -> "Agent":
773
- """Create an agent using a fully prepared payload mapping."""
774
- known, extras = _split_known_and_extra(payload, AgentCreateRequest.__dataclass_fields__)
894
+ def _validate_agent_basics(self, known: dict[str, Any]) -> tuple[str, str]:
895
+ """Validate and extract basic agent fields.
896
+
897
+ Args:
898
+ known: Known fields dictionary.
899
+
900
+ Returns:
901
+ Tuple of (name, validated_instruction).
775
902
 
903
+ Raises:
904
+ ValueError: If name or instruction is empty/whitespace.
905
+ """
776
906
  name = known.pop("name", None)
777
907
  instruction = known.pop("instruction", None)
778
908
  if not name or not str(name).strip():
@@ -781,9 +911,20 @@ class AgentClient(BaseClient):
781
911
  raise ValueError("Agent instruction cannot be empty or whitespace")
782
912
 
783
913
  validated_instruction = validate_agent_instruction(str(instruction))
784
- _normalise_sequence_fields(known)
914
+ return str(name).strip(), validated_instruction
785
915
 
786
- resolved_model = known.pop("model", None) or DEFAULT_MODEL
916
+ def _resolve_all_resources(
917
+ self, known: dict[str, Any], extras: dict[str, Any]
918
+ ) -> tuple[list[str] | None, list[str] | None, list[str] | None]:
919
+ """Resolve all resource IDs (tools, agents, mcps).
920
+
921
+ Args:
922
+ known: Known fields dictionary.
923
+ extras: Extra fields dictionary.
924
+
925
+ Returns:
926
+ Tuple of (resolved_tools, resolved_agents, resolved_mcps).
927
+ """
787
928
  tool_refs = extras.pop("_tool_refs", None)
788
929
  agent_refs = extras.pop("_agent_refs", None)
789
930
  mcp_refs = extras.pop("_mcp_refs", None)
@@ -796,10 +937,56 @@ class AgentClient(BaseClient):
796
937
  resolved_agents = self._resolve_agent_ids(agents_raw, agent_refs)
797
938
  resolved_mcps = self._resolve_mcp_ids(mcps_raw, mcp_refs)
798
939
 
940
+ return resolved_tools, resolved_agents, resolved_mcps
941
+
942
+ def _process_model_fields(
943
+ self, resolved_model: Any, known: dict[str, Any]
944
+ ) -> tuple[str, str | None, str | None, str | None]:
945
+ """Process model fields and extract language model ID.
946
+
947
+ Args:
948
+ resolved_model: Resolved model (string or Model object).
949
+ known: Known fields dictionary.
950
+
951
+ Returns:
952
+ Tuple of (resolved_model_str, language_model_id, provider, model_name).
953
+ """
954
+ from glaip_sdk.models import Model # noqa: PLC0415
955
+
956
+ if isinstance(resolved_model, Model):
957
+ if resolved_model.credentials or resolved_model.hyperparameters or resolved_model.base_url:
958
+ warnings.warn(
959
+ "Model object contains local configuration (credentials, hyperparameters, or base_url) "
960
+ "which is ignored for remote deployment. These fields are only used for local execution.",
961
+ UserWarning,
962
+ stacklevel=2,
963
+ )
964
+ resolved_model = resolved_model.id
965
+
966
+ # Validate and normalize string models (handles bare name deprecation)
967
+ if isinstance(resolved_model, str):
968
+ from glaip_sdk.models._validation import _validate_model # noqa: PLC0415
969
+
970
+ resolved_model = _validate_model(resolved_model)
971
+
799
972
  language_model_id = known.pop("language_model_id", None)
973
+ if not language_model_id and isinstance(resolved_model, str):
974
+ language_model_id = self._resolve_language_model_id(resolved_model)
975
+
800
976
  provider = known.pop("provider", None)
801
977
  model_name = known.pop("model_name", None)
802
978
 
979
+ return resolved_model, language_model_id, provider, model_name
980
+
981
+ def _extract_agent_metadata(self, known: dict[str, Any]) -> tuple[str, str, str]:
982
+ """Extract agent type, framework, and version.
983
+
984
+ Args:
985
+ known: Known fields dictionary.
986
+
987
+ Returns:
988
+ Tuple of (agent_type, framework, version).
989
+ """
803
990
  agent_type_value = known.pop("agent_type", None)
804
991
  fallback_type_value = known.pop("type", None)
805
992
  if agent_type_value is None:
@@ -807,6 +994,26 @@ class AgentClient(BaseClient):
807
994
 
808
995
  framework_value = known.pop("framework", None) or DEFAULT_AGENT_FRAMEWORK
809
996
  version_value = known.pop("version", None) or DEFAULT_AGENT_VERSION
997
+
998
+ return agent_type_value, framework_value, version_value
999
+
1000
+ def _create_agent_from_payload(self, payload: Mapping[str, Any]) -> "Agent":
1001
+ """Create an agent using a fully prepared payload mapping."""
1002
+ known, extras = _split_known_and_extra(payload, AgentCreateRequest.__dataclass_fields__)
1003
+
1004
+ # Validate and extract basic fields
1005
+ name, validated_instruction = self._validate_agent_basics(known)
1006
+ _normalise_sequence_fields(known)
1007
+
1008
+ # Resolve model and resources
1009
+ resolved_model = known.pop("model", None) or DEFAULT_MODEL
1010
+ resolved_tools, resolved_agents, resolved_mcps = self._resolve_all_resources(known, extras)
1011
+
1012
+ # Process model and language model ID
1013
+ resolved_model, language_model_id, provider, model_name = self._process_model_fields(resolved_model, known)
1014
+
1015
+ # Extract agent type, framework, version
1016
+ agent_type_value, framework_value, version_value = self._extract_agent_metadata(known)
810
1017
  account_id = known.pop("account_id", None)
811
1018
  description = known.pop("description", None)
812
1019
  metadata = _prepare_agent_metadata(known.pop("metadata", None))
@@ -819,7 +1026,7 @@ class AgentClient(BaseClient):
819
1026
  final_extras.setdefault("model", resolved_model)
820
1027
 
821
1028
  request = AgentCreateRequest(
822
- name=str(name).strip(),
1029
+ name=name,
823
1030
  instruction=validated_instruction,
824
1031
  model=resolved_model,
825
1032
  language_model_id=language_model_id,
@@ -897,6 +1104,29 @@ class AgentClient(BaseClient):
897
1104
  """Backward-compatible helper to create an agent from a configuration file."""
898
1105
  return self.create_agent(file=file_path, **overrides)
899
1106
 
1107
+ def _resolve_update_model_fields(self, known: dict[str, Any]) -> None:
1108
+ """Resolve model fields in-place for update payload if present.
1109
+
1110
+ If 'model' or 'language_model_id' keys exist in the known fields dict,
1111
+ this method resolves them into 'language_model_id', 'provider', and 'model_name'
1112
+ using the standard resolution logic, ensuring consistency with create_agent.
1113
+
1114
+ Args:
1115
+ known: The dictionary of known fields to check and update in-place.
1116
+ """
1117
+ if "model" in known or "language_model_id" in known:
1118
+ model_val = known.pop("model", None)
1119
+ r_model, r_id, r_prov, r_name = self._process_model_fields(model_val, known)
1120
+
1121
+ if r_model is not None:
1122
+ known["model"] = r_model
1123
+ if r_id is not None:
1124
+ known["language_model_id"] = r_id
1125
+ if r_prov is not None:
1126
+ known["provider"] = r_prov
1127
+ if r_name is not None:
1128
+ known["model_name"] = r_name
1129
+
900
1130
  def _update_agent_from_payload(
901
1131
  self,
902
1132
  agent_id: str,
@@ -922,6 +1152,8 @@ class AgentClient(BaseClient):
922
1152
  if mcps_value is not None:
923
1153
  mcps_value = self._resolve_mcp_ids(mcps_value, mcp_refs) # pragma: no cover
924
1154
 
1155
+ self._resolve_update_model_fields(known)
1156
+
925
1157
  request = AgentUpdateRequest(
926
1158
  name=known.pop("name", None),
927
1159
  instruction=known.pop("instruction", None),