Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 97 additions & 2 deletions cognite/client/_api/agents/agents.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from __future__ import annotations

from collections.abc import Sequence
from typing import TYPE_CHECKING, overload
from typing import TYPE_CHECKING, Any, overload

from cognite.client._api_client import APIClient
from cognite.client.data_classes.agents import Agent, AgentList, AgentUpsert
from cognite.client.data_classes.agents import Agent, AgentList, AgentSession, AgentUpsert
from cognite.client.data_classes.agents.chat import (
Action,
ActionResult,
Expand Down Expand Up @@ -423,3 +423,98 @@ async def chat(
url_path=self._RESOURCE_PATH + "/chat", json=body, semaphore=self._get_semaphore("write")
)
return AgentChatResponse._load(response.json())

def create_session(
self,
agent_external_id: str,
actions: Sequence[Action] | None = None,
cursor: str | None = None,
**kwargs: Any,
) -> AgentSession:
"""Create a stateful agent session for multi-turn conversations.

The returned :class:`~cognite.client.data_classes.agents.AgentSession` stores
``agent_external_id``, ``actions``, and the current conversation cursor. Each
call to ``await session.chat(...)`` automatically threads the cursor from the
previous response into the next request, so callers do not have to manage
cursor state manually.

This method is available only on the async client
(:class:`~cognite.client.AsyncCogniteClient`). Sync users who need multi-turn
conversations should call :meth:`chat` directly and manage the cursor themselves.

Unknown keyword arguments are silently accepted for forward compatibility with
future parameters.

Args:
agent_external_id (str): External ID of the agent to chat with.
actions (Sequence[Action] | None): Client-side actions available to the agent
during the conversation. Passed unchanged to every ``chat()`` call.
Defaults to ``None`` (no client actions).
cursor (str | None): Resume an existing conversation from this cursor.
Defaults to ``None`` (fresh conversation).
**kwargs (Any): Reserved for future parameters; silently ignored in v1.

Returns:
AgentSession: A stateful session bound to the given agent.

Examples:

Simple multi-turn conversation:

>>> from cognite.client import AsyncCogniteClient
>>> from cognite.client.data_classes.agents import Message
>>> async def main():
... client = AsyncCogniteClient()
... session = client.agents.create_session(agent_external_id="my_agent")
... response = await session.chat(Message("Hello"))
... print(response.text)
... response = await session.chat(Message("Tell me more"))
... print(response.text)

Resume a prior conversation using a saved cursor:

>>> async def resume(saved_cursor: str):
... client = AsyncCogniteClient()
... session = client.agents.create_session(
... agent_external_id="my_agent",
... cursor=saved_cursor,
... )
... response = await session.chat(Message("Continue where we left off"))

With client-side actions:

>>> from cognite.client.data_classes.agents import ClientToolAction, ClientToolResult
>>> async def with_actions():
... client = AsyncCogniteClient()
... add = ClientToolAction(
... name="add",
... description="Add two numbers",
... parameters={
... "type": "object",
... "properties": {
... "a": {"type": "number"},
... "b": {"type": "number"},
... },
... "required": ["a", "b"],
... },
... )
... session = client.agents.create_session(
... agent_external_id="my_agent",
... actions=[add],
... )
... response = await session.chat(Message("What is 42 + 58?"))
... if response.action_calls:
... for call in response.action_calls:
... result = call.arguments["a"] + call.arguments["b"]
... response = await session.chat(
... ClientToolResult(action_id=call.action_id, content=str(result))
... )
"""
self._warnings.warn()
return AgentSession(
agents_api=self,
agent_external_id=agent_external_id,
actions=actions,
cursor=cursor,
)
2 changes: 1 addition & 1 deletion cognite/client/_sync_api/agents/agents.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
===============================================================================
178ce7222985b04d03174af7b7e0f525
47d95a8f86f0bf9f4eac8732b06a85cb
This file is auto-generated from the Async API modules, - do not edit manually!
===============================================================================
"""
Expand Down
2 changes: 2 additions & 0 deletions cognite/client/data_classes/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
UnknownActionCall,
UnknownContent,
)
from cognite.client.data_classes.agents.session import AgentSession

__all__ = [
"Action",
Expand All @@ -57,6 +58,7 @@
"AgentMessage",
"AgentMessageList",
"AgentReasoningItem",
"AgentSession",
"AgentTool",
"AgentToolList",
"AgentToolUpsert",
Expand Down
89 changes: 89 additions & 0 deletions cognite/client/data_classes/agents/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from __future__ import annotations

from collections.abc import Sequence
from typing import TYPE_CHECKING

from cognite.client.data_classes.agents.chat import (
Action,
ActionResult,
AgentChatResponse,
Message,
)

if TYPE_CHECKING:
from cognite.client._api.agents.agents import AgentsAPI


class AgentSession:
"""Stateful session for multi-turn conversations with a Cognite agent.

Created via :meth:`cognite.client._api.agents.agents.AgentsAPI.create_session`
on :class:`~cognite.client.AsyncCogniteClient`. Do not instantiate directly.

The session automatically threads the conversation cursor across successive
:meth:`chat` calls, so callers do not need to track cursor state themselves.

Each session is bound to one agent and one conversation; create a new session
via ``create_session()`` to start a new conversation (there is no ``reset()``
method). The session is not safe for concurrent ``await session.chat(...)``
calls on the same instance — use separate ``AgentSession`` objects for
parallel conversations.

Args:
agents_api (AgentsAPI): The async agents API used to make chat calls.
agent_external_id (str): External ID of the agent bound to this session.
actions (Sequence[Action] | None): Client-side actions available to the agent.
cursor (str | None): Initial cursor (``None`` for a fresh conversation, or
an existing cursor to resume a prior conversation).
"""

def __init__(
self,
agents_api: AgentsAPI,
agent_external_id: str,
actions: Sequence[Action] | None,
cursor: str | None,
) -> None:
self._agents_api = agents_api
self.agent_external_id = agent_external_id
self.actions = actions
self._cursor = cursor

@property
def cursor(self) -> str | None:
"""The current conversation cursor.

``None`` until the first successful :meth:`chat` call sets it. After each
successful response the cursor advances; if a response has no cursor the
previous non-null value is retained. If a chat request fails the cursor
is unchanged so the call can be retried.
"""
return self._cursor

async def chat(
self,
messages: Message | ActionResult | Sequence[Message | ActionResult],
) -> AgentChatResponse:
"""Send messages to the agent and receive a response.

The cursor from the previous response is threaded automatically into the
outgoing request. On success the session's cursor advances to the response
cursor (or is retained if the response has no cursor). On failure the
cursor is unchanged.

Args:
messages (Message | ActionResult | Sequence[Message | ActionResult]): One or
more messages and/or action results. Accepts the same types as
:meth:`cognite.client._api.agents.agents.AgentsAPI.chat`.

Returns:
AgentChatResponse: The agent's response.
"""
response = await self._agents_api.chat(
agent_external_id=self.agent_external_id,
messages=messages,
cursor=self._cursor,
actions=self.actions,
)
self._cursor = response.cursor or self._cursor
return response
Loading
Loading