Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- `client.agents` resource wrapping the public `/agents` API: `list`, `create`,
`get`, `update` (PATCH), `delete`, `list_models`, and `set_models`, with typed
`CustomAgent` / `CustomAgentSummary` / `LLMModelSummary` models (names chosen
to avoid colliding with the chat-thread `Agent`).

### Changed
- `__version__` is now resolved at runtime from installed package metadata
(`importlib.metadata.version("cominty-sdk")`) instead of the removed
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,25 @@ await client.threads.update(thread_id, name="Renamed", starred=True)
await client.threads.archive(thread_id)
```

### Manage agents

`client.agents` creates and updates custom agents with the same API key used for
chat. Pass the returned `id` to `chat.start` as `agent_id`.

```python
models = await client.agents.list_models()
agent = await client.agents.create(
name="Researcher",
instructions="Cite sources.",
scope="organization",
model_ids=[models[0].id],
)
await client.agents.update(agent.id, description="Org research helper")

run = await client.chat.start(agent_id=agent.id, message="Summarize Q3.")
print(await run.text())
```

## Examples

Runnable scripts for each scenario live in [`examples/`](examples/):
Expand Down
14 changes: 14 additions & 0 deletions src/cominty_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
ServerError,
StreamInterrupted,
)
from .models.agents import (
CreateAgentParams,
CustomAgent,
CustomAgentSummary,
LLMModelSummary,
SetAgentModelsParams,
UpdateAgentParams,
)
from .models.chat import (
Agent,
ContentOrigin,
Expand Down Expand Up @@ -69,16 +77,22 @@
"Agent",
"ContentOrigin",
"ConversationFile",
"CreateAgentParams",
"CustomAgent",
"CustomAgentSummary",
"DisablableTool",
"HumanMessage",
"LLMModelSummary",
"Message",
"MessageRole",
"MessageStatus",
"Question",
"SetAgentModelsParams",
"ShareLink",
"StartChatOptions",
"StartChatParams",
"Thread",
"ThreadSummary",
"UpdateAgentParams",
"UpdateThreadParams",
]
2 changes: 2 additions & 0 deletions src/cominty_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ._config import Config
from ._transport import AsyncTransport
from .models.chat import validate_user_id
from .resources.agents import AgentsResource
from .resources.chat import ChatResource
from .resources.threads import ThreadsResource

Expand Down Expand Up @@ -53,6 +54,7 @@ def __init__(
self._transport = AsyncTransport(self._config)
self.chat = ChatResource(self._transport, user_id=self._config.user_id)
self.threads = ThreadsResource(self._transport, user_id=self._config.user_id)
self.agents = AgentsResource(self._transport)

@property
def user_id(self) -> str:
Expand Down
14 changes: 14 additions & 0 deletions src/cominty_sdk/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

from __future__ import annotations

from .agents import (
CreateAgentParams,
CustomAgent,
CustomAgentSummary,
LLMModelSummary,
SetAgentModelsParams,
UpdateAgentParams,
)
from .chat import (
Agent,
ContentOrigin,
Expand All @@ -23,15 +31,21 @@
"Agent",
"ContentOrigin",
"ConversationFile",
"CreateAgentParams",
"CustomAgent",
"CustomAgentSummary",
"DisablableTool",
"HumanMessage",
"LLMModelSummary",
"Message",
"MessageRole",
"MessageStatus",
"Question",
"SetAgentModelsParams",
"ShareLink",
"StartChatOptions",
"StartChatParams",
"Thread",
"ThreadSummary",
"UpdateAgentParams",
]
84 changes: 84 additions & 0 deletions src/cominty_sdk/models/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""Request and response models for the agents resource.

Request models are strict (``strict=True, extra="forbid"``). Response models are
lenient (``extra="ignore"``). Named ``CustomAgent*`` to avoid colliding with the
chat-thread :class:`~.chat.Agent` (``{id, name}`` only).
"""

from __future__ import annotations

from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field

__all__ = [
"CreateAgentParams",
"UpdateAgentParams",
"SetAgentModelsParams",
"LLMModelSummary",
"CustomAgentSummary",
"CustomAgent",
]

AgentScope = Literal["organization", "private"]


# --------------------------------------------------------------------------- #
# Request models (strict — reject unknown fields, no coercion)
# --------------------------------------------------------------------------- #
class CreateAgentParams(BaseModel):
model_config = ConfigDict(strict=True, extra="forbid")

name: str
description: str | None = None
instructions: str | None = None
scope: AgentScope = "private"
execution_options: dict[str, Any] | None = None
allow_execution_options_override: bool = False
model_ids: list[str] | None = Field(default=None, min_length=1)


class UpdateAgentParams(BaseModel):
"""Mutable agent fields. Only the fields you pass are sent (``exclude_none``)."""

model_config = ConfigDict(strict=True, extra="forbid")

name: str | None = None
description: str | None = None
instructions: str | None = None
execution_options: dict[str, Any] | None = None
allow_execution_options_override: bool | None = None


class SetAgentModelsParams(BaseModel):
model_config = ConfigDict(strict=True, extra="forbid")

model_ids: list[str] = Field(min_length=1)


# --------------------------------------------------------------------------- #
# Response models (lenient — ignore unknown fields)
# --------------------------------------------------------------------------- #
class LLMModelSummary(BaseModel):
model_config = ConfigDict(extra="ignore")

id: str
name: str


class CustomAgentSummary(BaseModel):
model_config = ConfigDict(extra="ignore")

id: str
name: str
description: str | None
execution_options: dict[str, Any] | None
managed: bool
scope: str
owner: str


class CustomAgent(CustomAgentSummary):
instructions: str | None
allow_execution_options_override: bool
models: list[LLMModelSummary]
3 changes: 2 additions & 1 deletion src/cominty_sdk/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from __future__ import annotations

from .agents import AgentsResource
from .chat import ChatResource
from .threads import ThreadsResource

__all__ = ["ChatResource", "ThreadsResource"]
__all__ = ["AgentsResource", "ChatResource", "ThreadsResource"]
113 changes: 113 additions & 0 deletions src/cominty_sdk/resources/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""The agents resource: list, create, read, update, delete, and model assignment."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Literal

from pydantic import ValidationError

from ..exceptions import InvalidParams
from ..models.agents import (
CreateAgentParams,
CustomAgent,
CustomAgentSummary,
LLMModelSummary,
SetAgentModelsParams,
UpdateAgentParams,
)

if TYPE_CHECKING:
from .._transport import AsyncTransport

__all__ = ["AgentsResource"]

AgentScope = Literal["organization", "private"]


class AgentsResource:
"""Manage custom agents via ``/agents`` (API-key auth; no ``user_id`` query)."""

def __init__(self, transport: AsyncTransport) -> None:
self._transport = transport

async def list(self) -> list[CustomAgentSummary]:
"""List agents visible to the API key (``GET /agents``)."""
raw = await self._transport.request("GET", "/agents")
return [CustomAgentSummary.model_validate(item) for item in raw]

async def create(
self,
*,
name: str,
description: str | None = None,
instructions: str | None = None,
scope: AgentScope = "private",
execution_options: dict[str, Any] | None = None,
allow_execution_options_override: bool = False,
model_ids: list[str] | None = None,
) -> CustomAgent:
"""Create a custom agent (``POST /agents``)."""
try:
params = CreateAgentParams(
name=name,
description=description,
instructions=instructions,
scope=scope,
execution_options=execution_options,
allow_execution_options_override=allow_execution_options_override,
model_ids=model_ids,
)
except ValidationError as exc:
raise InvalidParams.from_validation_error(exc, context="agents.create") from None
body = params.model_dump(mode="json", exclude_none=True)
raw = await self._transport.request("POST", "/agents", json_body=body)
return CustomAgent.model_validate(raw)

async def get(self, agent_id: str) -> CustomAgent:
"""Fetch one agent with instructions and models (``GET /agents/{id}``)."""
raw = await self._transport.request("GET", f"/agents/{agent_id}")
return CustomAgent.model_validate(raw)

async def update(
self,
agent_id: str,
*,
name: str | None = None,
description: str | None = None,
instructions: str | None = None,
execution_options: dict[str, Any] | None = None,
allow_execution_options_override: bool | None = None,
) -> CustomAgent:
"""Partial update (``PATCH /agents/{id}``). Only passed fields are sent."""
try:
params = UpdateAgentParams(
name=name,
description=description,
instructions=instructions,
execution_options=execution_options,
allow_execution_options_override=allow_execution_options_override,
)
except ValidationError as exc:
raise InvalidParams.from_validation_error(exc, context="agents.update") from None
body = params.model_dump(mode="json", exclude_none=True)
raw = await self._transport.request("PATCH", f"/agents/{agent_id}", json_body=body)
return CustomAgent.model_validate(raw)

async def delete(self, agent_id: str) -> None:
"""Delete a custom agent (``DELETE /agents/{id}``)."""
await self._transport.request("DELETE", f"/agents/{agent_id}")

async def list_models(self) -> list[LLMModelSummary]:
"""List models selectable for custom agents (``GET /agents/models``)."""
raw = await self._transport.request("GET", "/agents/models")
return [LLMModelSummary.model_validate(item) for item in raw]

async def set_models(self, agent_id: str, *, model_ids: list[str]) -> CustomAgent:
"""Replace an agent's model chain (``PUT /agents/{id}/models``)."""
try:
params = SetAgentModelsParams(model_ids=model_ids)
except ValidationError as exc:
raise InvalidParams.from_validation_error(exc, context="agents.set_models") from None
body = params.model_dump(mode="json")
raw = await self._transport.request("PUT", f"/agents/{agent_id}/models", json_body=body)
return CustomAgent.model_validate(raw)
Loading
Loading