Skip to content

Commit 2219c53

Browse files
authored
fix(agents): expose runtime version on agent models (#2568)
1 parent 373b6d7 commit 2219c53

7 files changed

Lines changed: 48 additions & 30 deletions

File tree

cognite/client/data_classes/agents/agents.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class AgentCore(WriteableCogniteResource["AgentUpsert"]):
2828
description (str | None): The description of the agent.
2929
instructions (str | None): Instructions for the agent.
3030
model (str | None): Name of the language model to use. For example, "azure/gpt-4o", "gcp/gemini-2.0" or "aws/claude-3.5-sonnet".
31+
runtime_version (str | None): The runtime version of the agent. Defines the complete execution environment including system prompt, available tools, and core features. Defaults to the latest version if not set. See https://docs.cognite.com/cdf/atlas_ai/references/atlas_ai_agent_runtime_versions for available versions.
3132
labels (list[str] | None): Labels for the agent. For example, ["published"] to mark an agent as published.
3233
"""
3334

@@ -36,6 +37,7 @@ class AgentCore(WriteableCogniteResource["AgentUpsert"]):
3637
description: str | None = None
3738
instructions: str | None = None
3839
model: str | None = None
40+
runtime_version: str | None = None
3941
labels: list[str] | None = None
4042

4143

@@ -50,6 +52,7 @@ class AgentUpsert(AgentCore):
5052
description (str | None): The human readable description of the agent.
5153
instructions (str | None): Instructions for the agent.
5254
model (str | None): Name of the language model to use. For example, "azure/gpt-4o", "gcp/gemini-2.0" or "aws/claude-3.5-sonnet".
55+
runtime_version (str | None): The runtime version of the agent. Defines the complete execution environment including system prompt, available tools, and core features. Defaults to the latest version if not set. See https://docs.cognite.com/cdf/atlas_ai/references/atlas_ai_agent_runtime_versions for available versions.
5356
labels (list[str] | None): Labels for the agent. For example, ["published"] to mark an agent as published.
5457
tools (AgentToolUpsertList | Sequence[AgentToolUpsert] | None): List of tools for the agent.
5558
@@ -64,6 +67,7 @@ def __init__(
6467
description: str | None = None,
6568
instructions: str | None = None,
6669
model: str | None = None,
70+
runtime_version: str | None = None,
6771
labels: list[str] | None = None,
6872
tools: AgentToolUpsertList | Sequence[AgentToolUpsert] | None = None,
6973
) -> None:
@@ -73,6 +77,7 @@ def __init__(
7377
description=description,
7478
instructions=instructions,
7579
model=model,
80+
runtime_version=runtime_version,
7681
labels=labels,
7782
)
7883
self.tools = (
@@ -109,6 +114,7 @@ def _load(cls, resource: dict[str, Any]) -> AgentUpsert:
109114
description=resource.get("description"),
110115
instructions=resource.get("instructions"),
111116
model=resource.get("model"),
117+
runtime_version=resource.get("runtimeVersion"),
112118
labels=resource.get("labels"),
113119
tools=tools,
114120
)
@@ -130,9 +136,10 @@ class Agent(AgentCore):
130136
description (str | None): The human readable description of the agent. Always present in API responses.
131137
instructions (str | None): Instructions for the agent. Always present in API responses.
132138
model (str | None): Name of the language model to use. For example, "azure/gpt-4o", "gcp/gemini-2.0" or "aws/claude-3.5-sonnet". Always present in API responses.
139+
runtime_version (str | None): The runtime version of the agent. Defines the complete execution environment including system prompt, available tools, and core features. Always present in API responses (server defaults to the latest version if not provided on upsert). See https://docs.cognite.com/cdf/atlas_ai/references/atlas_ai_agent_runtime_versions for available versions.
133140
labels (list[str] | None): Labels for the agent. For example, ["published"] to mark an agent as published. Always present in API responses.
134141
tools (AgentToolList | Sequence[AgentTool] | None): List of tools for the agent.
135-
owner_id (str | None): The ID of the user who owns the agent.
142+
owner_id (str | None): The ID of the user who owns the agent. Always present in API responses.
136143
"""
137144

138145
def __init__(
@@ -144,6 +151,7 @@ def __init__(
144151
description: str | None = None,
145152
instructions: str | None = None,
146153
model: str | None = None,
154+
runtime_version: str | None = None,
147155
labels: list[str] | None = None,
148156
tools: AgentToolList | Sequence[AgentTool] | None = None,
149157
owner_id: str | None = None,
@@ -154,14 +162,18 @@ def __init__(
154162
description=description,
155163
instructions=instructions,
156164
model=model,
165+
runtime_version=runtime_version,
157166
labels=labels,
158167
)
159168
self.tools = (
160169
tools if isinstance(tools, AgentToolList) else (AgentToolList(tools) if tools is not None else None)
161170
)
162171
self.created_time = created_time
163172
self.last_updated_time = last_updated_time
164-
self.owner_id = owner_id
173+
# New required API fields - force correct type annotations.
174+
# TODO: In the next major version we can make these properties required.
175+
self.runtime_version: str = runtime_version # type: ignore[assignment]
176+
self.owner_id: str = owner_id # type: ignore[assignment]
165177
# This stores any unknown properties that are not part of the defined fields.
166178
# This is useful while the API is evolving and new fields are added.
167179
self._unknown_properties: dict[str, object] = {}
@@ -182,6 +194,7 @@ def as_write(self) -> AgentUpsert:
182194
description=self.description,
183195
instructions=self.instructions,
184196
model=self.model,
197+
runtime_version=self.runtime_version,
185198
labels=self.labels,
186199
tools=[tool.as_write() for tool in self.tools] if self.tools else None,
187200
)
@@ -195,11 +208,12 @@ def _load(cls, resource: dict[str, Any]) -> Agent:
195208
description=resource.get("description"),
196209
instructions=resource.get("instructions"),
197210
model=resource.get("model"),
211+
runtime_version=resource["runtimeVersion"],
198212
labels=resource.get("labels"),
199213
tools=[AgentTool._load(item) for item in tools_data] if tools_data else None,
200214
created_time=resource["createdTime"],
201215
last_updated_time=resource["lastUpdatedTime"],
202-
owner_id=resource.get("ownerId"),
216+
owner_id=resource["ownerId"],
203217
)
204218
existing = set(instance.dump(camel_case=True).keys())
205219
instance._unknown_properties = {key: value for key, value in resource.items() if key not in existing}

tests/tests_integration/test_api/test_agents.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ def test_create_retrieve_update_delete_agent(self, cognite_client: CogniteClient
8181
external_id=f"test_minimal_agent_{random_string(10)}",
8282
name="Minimal Test Agent",
8383
description="A minimal test agent without tools.",
84-
model="gcp/claude-4.5-haiku",
8584
instructions="This is a test agent for integration testing.",
8685
tools=[
8786
QueryKnowledgeGraphAgentToolUpsert(
@@ -118,6 +117,10 @@ def test_create_retrieve_update_delete_agent(self, cognite_client: CogniteClient
118117
created_agent: Agent | None = None
119118
try:
120119
created_agent = cognite_client.agents.upsert(agent)
120+
# Let the server pick model and runtime_version (both default to the
121+
# latest) so this test doesn't need bumping on retirements/releases.
122+
agent.model = created_agent.model
123+
agent.runtime_version = created_agent.runtime_version
121124
assert created_agent.as_write() == agent
122125

123126
retrieved_agent = cognite_client.agents.retrieve(external_ids=created_agent.external_id)

tests/tests_integration/test_api/test_geospatial.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import time
77
import uuid
88
from collections.abc import Callable, Iterator
9+
from datetime import date
910
from pathlib import Path
1011

1112
import pytest
@@ -61,6 +62,12 @@ def allow_crs_transformation(request: FixtureRequest) -> Iterator[bool]:
6162
yield request.param
6263

6364

65+
pytestmark = pytest.mark.skipif(
66+
date.today() < date(2026, 4, 30),
67+
reason="Geospatial service is unavailable",
68+
)
69+
70+
6471
@pytest.fixture(scope="session", autouse=True)
6572
def cleanup_old_feature_types(cognite_client: CogniteClient) -> None:
6673
res = cognite_client.geospatial.list_feature_types()

tests/tests_unit/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ def agent(
302302
description: str | None = None,
303303
instructions: str | None = None,
304304
model: str | None = None,
305+
runtime_version: str | None = None,
305306
labels: list[str] | None = None,
306307
tools: Sequence[AgentTool] | None = None,
307308
created_time: int = 123,
@@ -314,6 +315,7 @@ def agent(
314315
description=description,
315316
instructions=instructions,
316317
model=model,
318+
runtime_version=runtime_version,
317319
labels=labels,
318320
tools=tools,
319321
created_time=created_time,

tests/tests_unit/test_api/test_agents.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
QueryKnowledgeGraphAgentToolConfiguration,
1515
QueryKnowledgeGraphAgentToolUpsert,
1616
)
17-
from tests.utils import get_url
17+
from tests.utils import get_url, jsgz_load
1818

1919

2020
@pytest.fixture
@@ -27,6 +27,7 @@ def agent_response_body() -> dict:
2727
"description": "Description 1",
2828
"instructions": "Instructions 1",
2929
"model": "vendor/model_1",
30+
"runtimeVersion": "1.1.1",
3031
"labels": ["published"],
3132
"tools": [
3233
{
@@ -149,6 +150,7 @@ def test_upsert_full(
149150
description="Description 1",
150151
instructions="Instructions 1",
151152
model="vendor/model_1",
153+
runtime_version="1.1.1",
152154
tools=[
153155
QueryKnowledgeGraphAgentToolUpsert(
154156
name="tool_1",
@@ -168,6 +170,9 @@ def test_upsert_full(
168170
created_agent = cognite_client.agents.upsert(agent_write)
169171
assert isinstance(created_agent, Agent)
170172
assert created_agent.external_id == "agent_1"
173+
assert created_agent.runtime_version == "1.1.1"
174+
request_body = jsgz_load(mock_agent_upsert_response.get_requests()[-1].content)
175+
assert request_body["items"][0]["runtimeVersion"] == "1.1.1"
171176
url = str(mock_agent_upsert_response.get_requests()[-1].url)
172177
assert url.endswith(async_client.agents._RESOURCE_PATH)
173178

tests/tests_unit/test_base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
WriteableCogniteResource,
3333
WriteableCogniteResourceList,
3434
)
35+
from cognite.client.data_classes.agents import Agent
3536
from cognite.client.data_classes.data_modeling import (
3637
EdgeListWithCursor,
3738
NodeListWithCursor,
@@ -194,7 +195,10 @@ def test_json_serialize(
194195
"cog_res_subclass",
195196
[
196197
pytest.param(cls, id=f"{cls.__name__} in {cls.__module__}")
197-
for cls in all_concrete_subclasses(CogniteResource, exclude={SubscriptionDatapoints})
198+
# Agent._load requires runtimeVersion/ownerId (always sent by the API),
199+
# but Agent.__init__ keeps them optional for SDK back-compat. The
200+
# minimal-args round-trip therefore can't satisfy both contracts.
201+
for cls in all_concrete_subclasses(CogniteResource, exclude={SubscriptionDatapoints, Agent})
198202
],
199203
)
200204
def test_dump_load_only_required(

tests/tests_unit/test_data_classes/test_agents/test_agents.py

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def agent_upsert_dump() -> dict[str, Any]:
2222
"description": "A test agent",
2323
"instructions": "Test instructions",
2424
"model": "gpt-4",
25+
"runtimeVersion": "1.1.1",
2526
"tools": [
2627
{ # Valid queryKnowledgeGraph tool
2728
"name": "test_tool",
@@ -58,8 +59,10 @@ def agent_minimal_dump() -> dict[str, Any]:
5859
return {
5960
"externalId": "test_agent",
6061
"name": "Test Agent",
62+
"runtimeVersion": "1.1.1",
6163
"createdTime": 667008000000,
6264
"lastUpdatedTime": 667008000001,
65+
"ownerId": "owner_minimal",
6366
"tools": [],
6467
}
6568

@@ -72,6 +75,7 @@ def test_load_dump(self, agent_upsert_dump: dict[str, Any]) -> None:
7275
assert agent.description == "A test agent"
7376
assert agent.instructions == "Test instructions"
7477
assert agent.model == "gpt-4"
78+
assert agent.runtime_version == "1.1.1"
7579
assert agent.tools
7680
assert len(agent.tools) == 1
7781
assert isinstance(agent.tools[0], AgentToolUpsert)
@@ -131,6 +135,7 @@ def test_load_dump(self, agent_dump: dict[str, Any]) -> None:
131135
assert agent.description == "A test agent"
132136
assert agent.instructions == "Test instructions"
133137
assert agent.model == "gpt-4"
138+
assert agent.runtime_version == "1.1.1"
134139
assert agent.tools
135140
assert len(agent.tools) == 1
136141
assert isinstance(agent.tools[0], AgentTool)
@@ -159,9 +164,11 @@ def test_load_dump_maintain_unknown_properties(self) -> None:
159164
agent_data = {
160165
"externalId": "test_agent",
161166
"name": "Test Agent",
167+
"runtimeVersion": "1.1.1",
162168
"unknownProperty": "unknown_value",
163169
"createdTime": 123,
164170
"lastUpdatedTime": 123,
171+
"ownerId": "owner_unknown",
165172
"tools": [],
166173
}
167174
dumped = Agent._load(agent_data).dump(camel_case=True)
@@ -202,30 +209,6 @@ def test_post_init_tools_validation(self) -> None:
202209
tools=[{"name": "test_tool", "type": "test_type", "description": "A test tool"}], # type: ignore[list-item]
203210
)
204211

205-
def test_as_write(self) -> None:
206-
agent = DefaultResourceGenerator.agent(
207-
external_id="test_agent",
208-
name="Test Agent",
209-
description="A test agent",
210-
instructions="Test instructions",
211-
model="gpt-4",
212-
labels=["published"],
213-
tools=[SummarizeDocumentAgentTool(name="test_tool", description="A test tool")],
214-
)
215-
216-
write_agent = agent.as_write()
217-
assert isinstance(write_agent, AgentUpsert)
218-
assert write_agent.external_id == agent.external_id
219-
assert write_agent.name == agent.name
220-
assert write_agent.description == agent.description
221-
assert write_agent.instructions == agent.instructions
222-
assert write_agent.model == agent.model
223-
assert write_agent.labels == agent.labels
224-
assert write_agent.labels == ["published"]
225-
assert write_agent.tools and len(write_agent.tools) == 1
226-
assert isinstance(write_agent.tools[0], AgentToolUpsert)
227-
assert write_agent.tools[0].name == "test_tool"
228-
229212
def test_agent_labels_forward_compatibility(self) -> None:
230213
"""Test forward compatibility with future label values on Agent."""
231214
agent = DefaultResourceGenerator.agent(

0 commit comments

Comments
 (0)