Skip to content

Commit 489c703

Browse files
authored
feat: add labels property to agents SDK (#2372)
1 parent 787926e commit 489c703

File tree

4 files changed

+92
-6
lines changed

4 files changed

+92
-6
lines changed

cognite/client/_api/agents/agents.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def upsert(self, agents: AgentUpsert | Sequence[AgentUpsert]) -> Agent | AgentLi
7575
>>> agent = AgentUpsert(
7676
... external_id="my_agent",
7777
... name="My Agent",
78+
... labels=["published"],
7879
... tools=[find_assets_tool]
7980
... )
8081
>>> client.agents.upsert(agents=[agent])
@@ -150,6 +151,7 @@ def upsert(self, agents: AgentUpsert | Sequence[AgentUpsert]) -> Agent | AgentLi
150151
... name="My agent",
151152
... description="An agent with many tools",
152153
... instructions="You are a helpful assistant that can query knowledge graphs, summarize documents, answer questions about documents, and query time series data points.",
154+
... labels=["published"],
153155
... tools=[find_assets_tool, find_files_tool, find_time_series_tool, summarize_tool, ask_doc_tool, ts_tool]
154156
... )
155157
>>> client.agents.upsert(agents=[agent])

cognite/client/data_classes/agents/agents.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ class AgentCore(WriteableCogniteResource["AgentUpsert"]):
3131
description (str | None): The description of the agent.
3232
instructions (str | None): Instructions for the agent.
3333
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".
34+
labels (list[str] | None): Labels for the agent. For example, ["published"] to mark an agent as published.
3435
"""
3536

3637
external_id: str
3738
name: str
3839
description: str | None = None
3940
instructions: str | None = None
4041
model: str | None = None
42+
labels: list[str] | None = None
4143

4244

4345
class AgentUpsert(AgentCore):
@@ -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+
labels (list[str] | None): Labels for the agent. For example, ["published"] to mark an agent as published.
5356
tools (Sequence[AgentToolUpsert] | None): List of tools for the agent.
5457
5558
"""
@@ -63,10 +66,16 @@ def __init__(
6366
description: str | None = None,
6467
instructions: str | None = None,
6568
model: str | None = None,
69+
labels: list[str] | None = None,
6670
tools: Sequence[AgentToolUpsert] | None = None,
6771
) -> None:
6872
super().__init__(
69-
external_id=external_id, name=name, description=description, instructions=instructions, model=model
73+
external_id=external_id,
74+
name=name,
75+
description=description,
76+
instructions=instructions,
77+
model=model,
78+
labels=labels,
7079
)
7180
self.tools: AgentToolUpsertList | None = AgentToolUpsertList(tools) if tools is not None else None
7281
# This stores any unknown properties that are not part of the defined fields.
@@ -99,6 +108,7 @@ def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None =
99108
description=resource.get("description"),
100109
instructions=resource.get("instructions"),
101110
model=resource.get("model"),
111+
labels=resource.get("labels"),
102112
tools=tools,
103113
)
104114
existing = set(instances.dump(camel_case=True).keys())
@@ -113,9 +123,10 @@ class Agent(AgentCore):
113123
Args:
114124
external_id (str): The external ID provided by the client. Must be unique for the resource type.
115125
name (str): The name of the agent, for use in user interfaces.
116-
description (str | None): The human readable description of the agent.
117-
instructions (str | None): Instructions for the agent.
118-
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".
126+
description (str | None): The human readable description of the agent. Always present in API responses.
127+
instructions (str | None): Instructions for the agent. Always present in API responses.
128+
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.
129+
labels (list[str] | None): Labels for the agent. For example, ["published"] to mark an agent as published. Always present in API responses.
119130
tools (Sequence[AgentTool] | None): List of tools for the agent.
120131
created_time (int | None): The time the agent was created, in milliseconds since Thursday, 1 January 1970 00:00:00 UTC, minus leap seconds.
121132
last_updated_time (int | None): The time the agent was last updated, in milliseconds since Thursday, 1 January 1970 00:00:00 UTC, minus leap seconds.
@@ -134,14 +145,26 @@ def __init__(
134145
description: str | None = None,
135146
instructions: str | None = None,
136147
model: str | None = None,
148+
labels: list[str] | None = None,
137149
tools: Sequence[AgentTool] | None = None,
138150
created_time: int | None = None,
139151
last_updated_time: int | None = None,
140152
owner_id: str | None = None,
141153
) -> None:
142154
super().__init__(
143-
external_id=external_id, name=name, description=description, instructions=instructions, model=model
155+
external_id=external_id,
156+
name=name,
157+
description=description,
158+
instructions=instructions,
159+
model=model,
160+
labels=labels,
144161
)
162+
# These fields are always present in API responses, but optional when creating.
163+
# Force the type to be non-optional for read instances.
164+
self.description: str = description # type: ignore[assignment]
165+
self.instructions: str = instructions # type: ignore[assignment]
166+
self.model: str = model # type: ignore[assignment]
167+
self.labels: list[str] = labels # type: ignore[assignment]
145168
self.tools: AgentToolList | None = AgentToolList(tools) if tools is not None else None
146169
self.created_time = created_time
147170
self.last_updated_time = last_updated_time
@@ -166,6 +189,7 @@ def as_write(self) -> AgentUpsert:
166189
description=self.description,
167190
instructions=self.instructions,
168191
model=self.model,
192+
labels=self.labels,
169193
tools=[tool.as_write() for tool in self.tools] if self.tools else None,
170194
)
171195

@@ -183,6 +207,7 @@ def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None =
183207
description=resource.get("description"),
184208
instructions=resource.get("instructions"),
185209
model=resource.get("model"),
210+
labels=resource.get("labels"),
186211
tools=tools,
187212
created_time=resource.get("createdTime"),
188213
last_updated_time=resource.get("lastUpdatedTime"),

tests/tests_unit/test_api/test_agents.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def agent_response_body() -> dict:
2525
"description": "Description 1",
2626
"instructions": "Instructions 1",
2727
"model": "vendor/model_1",
28+
"labels": ["published"],
2829
"tools": [
2930
{
3031
"name": "tool_1",
@@ -176,3 +177,22 @@ def test_upsert_ensure_retry(
176177
created = cognite_client.agents.upsert(AgentUpsert(external_id="agent_1", name="Agent 1"))
177178

178179
assert isinstance(created, Agent)
180+
181+
def test_upsert_with_labels(self, cognite_client: CogniteClient, mock_agent_upsert_response: MagicMock) -> None:
182+
agent_write = AgentUpsert(
183+
external_id="agent_1",
184+
name="Agent 1",
185+
labels=["published"],
186+
)
187+
created_agent = cognite_client.agents.upsert(agent_write)
188+
assert isinstance(created_agent, Agent)
189+
assert created_agent.external_id == "agent_1"
190+
assert created_agent.labels == ["published"]
191+
192+
def test_retrieve_agent_with_labels(
193+
self, cognite_client: CogniteClient, mock_agent_retrieve_response: MagicMock
194+
) -> None:
195+
retrieved_agent = cognite_client.agents.retrieve("agent_1")
196+
assert isinstance(retrieved_agent, Agent)
197+
assert retrieved_agent.external_id == "agent_1"
198+
assert retrieved_agent.labels == ["published"]

tests/tests_unit/test_data_classes/test_agents/test_agents.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,35 @@ def test_load_dump_maintain_unknown_properties(self) -> None:
7979
"externalId": "test_agent",
8080
"name": "Test Agent",
8181
"unknownProperty": "unknown_value",
82-
"labels": ["published"],
8382
}
8483
dumped = AgentUpsert._load(agent_data).dump(camel_case=True)
8584

8685
assert dumped == agent_data
8786

87+
def test_create_agent_with_labels(self) -> None:
88+
"""Test creating an agent with labels."""
89+
agent = AgentUpsert(
90+
external_id="test_agent",
91+
name="Test Agent",
92+
labels=["published"],
93+
)
94+
assert agent.labels == ["published"]
95+
96+
dumped = agent.dump(camel_case=True)
97+
assert dumped["labels"] == ["published"]
98+
99+
def test_labels_forward_compatibility(self) -> None:
100+
"""Test forward compatibility with future label values."""
101+
agent = AgentUpsert(
102+
external_id="test_agent",
103+
name="Test Agent",
104+
labels=["published", "charts"],
105+
)
106+
assert agent.labels == ["published", "charts"]
107+
108+
dumped = agent.dump(camel_case=True)
109+
assert dumped["labels"] == ["published", "charts"]
110+
88111
def test_as_write(self) -> None:
89112
agent_upsert = AgentUpsert(
90113
external_id="test_agent",
@@ -185,6 +208,7 @@ def test_as_write(self) -> None:
185208
description="A test agent",
186209
instructions="Test instructions",
187210
model="gpt-4",
211+
labels=["published"],
188212
tools=[SummarizeDocumentAgentTool(name="test_tool", description="A test tool")],
189213
)
190214

@@ -195,10 +219,25 @@ def test_as_write(self) -> None:
195219
assert write_agent.description == agent.description
196220
assert write_agent.instructions == agent.instructions
197221
assert write_agent.model == agent.model
222+
assert write_agent.labels == agent.labels
223+
assert write_agent.labels == ["published"]
198224
assert len(write_agent.tools) == 1
199225
assert isinstance(write_agent.tools[0], AgentToolUpsert)
200226
assert write_agent.tools[0].name == "test_tool"
201227

228+
def test_agent_labels_forward_compatibility(self) -> None:
229+
"""Test forward compatibility with future label values on Agent."""
230+
agent = Agent(
231+
external_id="test_agent",
232+
name="Test Agent",
233+
labels=["published", "charts"],
234+
)
235+
assert agent.labels == ["published", "charts"]
236+
237+
# Test that as_write() preserves labels
238+
write_agent = agent.as_write()
239+
assert write_agent.labels == ["published", "charts"]
240+
202241

203242
class TestAgentList:
204243
def test_as_write(self) -> None:

0 commit comments

Comments
 (0)