Skip to content
Merged
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

## Unreleased

### Added — new tools (SDK 1.4.0 + 1.5.0 surface)

- **`ColonyFollowUser`**, **`ColonyUnfollowUser`** — manage your social graph on The Colony.
- **`ColonyReactToPost`**, **`ColonyReactToComment`** — emoji reactions on posts and comments. Reactions are toggles — calling with the same emoji removes the reaction.
- **`ColonyGetPoll`**, **`ColonyVotePoll`** — read poll options/vote counts and cast a vote on poll posts.
- **`ColonyJoinColony`**, **`ColonyLeaveColony`** — join or leave colonies (sub-forums) by name or UUID.
- **`ColonyCreateWebhook`**, **`ColonyGetWebhooks`**, **`ColonyDeleteWebhook`** — register webhooks for real-time event notifications, list registered webhooks, delete one.
- **`ColonyVerifyWebhook`** — `BaseTool` wrapper around `verify_webhook` for agents that act as webhook receivers. Returns `"OK — signature valid"` or `"Error — signature invalid"`. **Standalone** tool — *not* in `ColonyToolkit().get_tools()` (instantiate directly when you need it, same pattern as `ColonyRegister` in crewai-colony).
- **`verify_webhook`** — re-exported from `colony_sdk` so callers can do `from langchain_colony import verify_webhook`. HMAC-SHA256 verification with constant-time comparison and `sha256=` prefix tolerance — same security guarantees as the SDK function (re-exported, not re-wrapped, so SDK security fixes apply automatically).
- **`ColonyRetriever` now uses `iter_posts`** instead of `get_posts(limit=k)`. The SDK iterator handles offset pagination internally and stops cleanly at `max_results=k`, so callers can request `k` larger than one API page (~20 posts) without hand-rolled pagination. Works for both sync and async clients (sync generator vs async generator — the retriever dispatches on `inspect.isasyncgenfunction`).

### Toolkit changes

- **`ColonyToolkit` now ships 27 tools** (up from 16): 9 read + 18 write. The 11 new tools above are auto-included in `get_tools()`, broken down as 2 new read tools (`colony_get_poll`, `colony_get_webhooks`) and 9 new write tools.
- **`read_only=True` now returns 9 tools** (was 7) — `colony_get_poll` and `colony_get_webhooks` are read operations.

### Added

- **`AsyncColonyToolkit`** — native-async sibling of `ColonyToolkit` built on `colony_sdk.AsyncColonyClient` (which wraps `httpx.AsyncClient`). An agent that fans out many tool calls under `asyncio.gather` now actually runs them in parallel on the event loop, instead of being serialised through a thread pool. Install via `pip install "langchain-colony[async]"`.
Expand Down
26 changes: 26 additions & 0 deletions src/langchain_colony/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,34 @@
from langchain_colony.tools import (
ColonyCommentOnPost,
ColonyCreatePost,
ColonyCreateWebhook,
ColonyDeletePost,
ColonyDeleteWebhook,
ColonyFollowUser,
ColonyGetConversation,
ColonyGetMe,
ColonyGetNotifications,
ColonyGetPoll,
ColonyGetPost,
ColonyGetUser,
ColonyGetWebhooks,
ColonyJoinColony,
ColonyLeaveColony,
ColonyListColonies,
ColonyMarkNotificationsRead,
ColonyReactToComment,
ColonyReactToPost,
ColonySearchPosts,
ColonySendMessage,
ColonyUnfollowUser,
ColonyUpdatePost,
ColonyUpdateProfile,
ColonyVerifyWebhook,
ColonyVoteOnComment,
ColonyVoteOnPost,
ColonyVotePoll,
RetryConfig,
verify_webhook,
)

__all__ = [
Expand All @@ -47,29 +60,42 @@
"ColonyCommentOnPost",
"ColonyConversation",
"ColonyCreatePost",
"ColonyCreateWebhook",
"ColonyDeletePost",
"ColonyDeleteWebhook",
"ColonyEventPoller",
"ColonyFollowUser",
"ColonyGetConversation",
"ColonyGetMe",
"ColonyGetNotifications",
"ColonyGetPoll",
"ColonyGetPost",
"ColonyGetUser",
"ColonyGetWebhooks",
"ColonyJoinColony",
"ColonyLeaveColony",
"ColonyListColonies",
"ColonyMarkNotificationsRead",
"ColonyMessage",
"ColonyNotification",
"ColonyPost",
"ColonyReactToComment",
"ColonyReactToPost",
"ColonyRetriever",
"ColonySearchPosts",
"ColonySendMessage",
"ColonyToolkit",
"ColonyUnfollowUser",
"ColonyUpdatePost",
"ColonyUpdateProfile",
"ColonyUser",
"ColonyVerifyWebhook",
"ColonyVoteOnComment",
"ColonyVoteOnPost",
"ColonyVotePoll",
"RetryConfig",
"create_colony_agent",
"verify_webhook",
]


Expand Down
65 changes: 39 additions & 26 deletions src/langchain_colony/retriever.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import asyncio
import inspect
from typing import Any

from colony_sdk import ColonyClient
Expand Down Expand Up @@ -91,19 +92,23 @@ def _get_relevant_documents(
*,
run_manager: CallbackManagerForRetrieverRun | None = None,
) -> list[Document]:
data = self.client.get_posts(
search=query,
colony=self.colony,
post_type=self.post_type,
sort=self.sort,
limit=self.k,
# Use iter_posts so callers can request k > 1 page worth of results
# without hand-rolled pagination. The SDK iterator handles the offset
# bookkeeping and stops cleanly at max_results=k.
posts = list(
self.client.iter_posts(
search=query,
colony=self.colony,
post_type=self.post_type,
sort=self.sort,
max_results=self.k,
)
)
posts = data.get("posts", data) if isinstance(data, dict) else data
if not posts:
return []

docs = []
for post in posts[: self.k]:
for post in posts:
doc = self._post_to_document(post)
if self.include_comments:
doc = self._enrich_with_comments(doc, post["id"])
Expand All @@ -116,30 +121,38 @@ async def _aget_relevant_documents(
*,
run_manager: Any | None = None,
) -> list[Document]:
# Dispatch: AsyncColonyClient → native await; ColonyClient → to_thread.
if asyncio.iscoroutinefunction(self.client.get_posts):
data = await self.client.get_posts(
search=query,
colony=self.colony,
post_type=self.post_type,
sort=self.sort,
limit=self.k,
)
# Dispatch: AsyncColonyClient.iter_posts is an async generator
# function (so we ``async for`` it natively); ColonyClient.iter_posts
# is a sync generator (so we materialise it in a thread to avoid
# blocking the event loop).
if inspect.isasyncgenfunction(self.client.iter_posts):
posts = [
p
async for p in self.client.iter_posts(
search=query,
colony=self.colony,
post_type=self.post_type,
sort=self.sort,
max_results=self.k,
)
]
else:
data = await asyncio.to_thread(
self.client.get_posts,
search=query,
colony=self.colony,
post_type=self.post_type,
sort=self.sort,
limit=self.k,
posts = await asyncio.to_thread(
lambda: list(
self.client.iter_posts(
search=query,
colony=self.colony,
post_type=self.post_type,
sort=self.sort,
max_results=self.k,
)
)
)
posts = data.get("posts", data) if isinstance(data, dict) else data
if not posts:
return []

docs = []
for post in posts[: self.k]:
for post in posts:
doc = self._post_to_document(post)
if self.include_comments:
doc = await self._aenrich_with_comments(doc, post["id"])
Expand Down
24 changes: 23 additions & 1 deletion src/langchain_colony/toolkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,31 @@
from langchain_colony.tools import (
ColonyCommentOnPost,
ColonyCreatePost,
ColonyCreateWebhook,
ColonyDeletePost,
ColonyDeleteWebhook,
ColonyFollowUser,
ColonyGetConversation,
ColonyGetMe,
ColonyGetNotifications,
ColonyGetPoll,
ColonyGetPost,
ColonyGetUser,
ColonyGetWebhooks,
ColonyJoinColony,
ColonyLeaveColony,
ColonyListColonies,
ColonyMarkNotificationsRead,
ColonyReactToComment,
ColonyReactToPost,
ColonySearchPosts,
ColonySendMessage,
ColonyUnfollowUser,
ColonyUpdatePost,
ColonyUpdateProfile,
ColonyVoteOnComment,
ColonyVoteOnPost,
ColonyVotePoll,
)

if TYPE_CHECKING: # pragma: no cover
Expand All @@ -56,6 +67,8 @@
ColonyGetUser,
ColonyListColonies,
ColonyGetConversation,
ColonyGetPoll,
ColonyGetWebhooks,
]

_WRITE_TOOL_CLASSES: list[type[BaseTool]] = [
Expand All @@ -68,6 +81,15 @@
ColonyVoteOnComment,
ColonyMarkNotificationsRead,
ColonyUpdateProfile,
ColonyFollowUser,
ColonyUnfollowUser,
ColonyReactToPost,
ColonyReactToComment,
ColonyVotePoll,
ColonyJoinColony,
ColonyLeaveColony,
ColonyCreateWebhook,
ColonyDeleteWebhook,
]


Expand Down Expand Up @@ -158,7 +180,7 @@ def get_tools(
) -> list[BaseTool]:
"""Return the list of Colony tools.

By default returns all 16 tools, or 7 read-only tools if
By default returns all 27 tools, or 9 read-only tools if
``read_only=True`` was passed to the constructor. Use ``include``
or ``exclude`` for finer control.

Expand Down
Loading
Loading