Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
9014f6b
feat: add file support for messenger integration
pulinduvidmal Dec 19, 2025
5222577
feat: add multimodal file/image support for telegram
pulinduvidmal Dec 23, 2025
54def41
feat: add multimodal file/image support for telegram
pulinduvidmal Dec 23, 2025
d0199b6
fix: resolve messenger integration conflicts from rebase
pulinduvidmal Dec 23, 2025
698747d
docs: update doc
pulinduvidmal Dec 23, 2025
bb4c1b3
fix: resolve messenger integration conflicts from rebase
pulinduvidmal Dec 23, 2025
a906b91
feat: Add multimodal memory support
pulinduvidmal Dec 28, 2025
1cc6d53
feat: Add multimodal memory support
pulinduvidmal Dec 29, 2025
5cb693f
Merge remote-tracking branch 'origin/develop' into feature/add_file_s…
pulinduvidmal Dec 29, 2025
d99ae92
fix: Add multimodal memory support
pulinduvidmal Dec 29, 2025
d1f68e5
fix: Add multimodal memory support
pulinduvidmal Dec 29, 2025
7c6d40c
fix: telegram_chat.py
pulinduvidmal Dec 29, 2025
84330c1
docs: Add multimodal memory support
pulinduvidmal Dec 29, 2025
7e08a7c
fix telegram_chat.py
pulinduvidmal Dec 29, 2025
d9e601b
fix telegram_chat.py
pulinduvidmal Dec 29, 2025
8f7733c
fix telegram_chat.py
pulinduvidmal Dec 29, 2025
23d7c61
feat: add teams integration
pulinduvidmal Jan 5, 2026
930ed98
feat: add teams integration
pulinduvidmal Jan 8, 2026
c0f5652
feat: add teams integration
pulinduvidmal Jan 8, 2026
94a350f
feat: add file/image attachment support with two-step LLM
pulinduvidmal Jan 12, 2026
94cf7a9
feat: add file/image attachment support with two-step LLM
pulinduvidmal Jan 12, 2026
e6d9ba1
refactor: split multimodal into package
pulinduvidmal Jan 12, 2026
b529b66
refactor: split multimodal into package
pulinduvidmal Jan 12, 2026
28e26ba
feat: add file/image attachment support with two-step LLM
pulinduvidmal Jan 13, 2026
fd36019
feat: add file/image attachment support with two-step LLM
pulinduvidmal Jan 13, 2026
06bfb32
feat(multimodal): add tools integration for attachment support
pulinduvidmal Jan 14, 2026
acfeba4
feat: add tools integration for attachment support
pulinduvidmal Jan 14, 2026
c5c2303
feat: add tools integration for attachment support
pulinduvidmal Jan 14, 2026
b0a52ec
feat: update tenant id
pulinduvidmal Jan 21, 2026
216d5a7
docs: update documents for the attachment tool
pulinduvidmal Jan 21, 2026
5e64084
feat: add integration testing for instagram
pulinduvidmal Jan 21, 2026
a1de5c1
feat: add teams integration
pulinduvidmal Jan 26, 2026
ba10bce
Merge feature/add_file_support_for_telegram resolving uv.lock conflict
pulinduvidmal Jan 26, 2026
d6623d0
feat: add teams integration
pulinduvidmal Jan 26, 2026
220eabf
fix: URL substring sanitization
pulinduvidmal Jan 26, 2026
df0ec13
fix: system prompt injection and multimodal architecture
pulinduvidmal Jan 28, 2026
158f146
fix: system prompt injection and multimodal architecture
pulinduvidmal Jan 29, 2026
4d7a95d
fix: system prompt injection and multimodal architecture
pulinduvidmal Jan 29, 2026
79d0172
Merge branch 'develop' into feature/add_file_support_for_telegram
pulinduvidmal Jan 29, 2026
b9d6d6d
Merge branch 'develop' into feature/add_file_support_for_telegram
pulinduvidmal Jan 29, 2026
155237a
fix: system prompt injection and multimodal architecture
pulinduvidmal Jan 29, 2026
e83fe4c
fix: system prompt injection and multimodal architecture
pulinduvidmal Jan 29, 2026
915520c
fix: system prompt injection and multimodal architecture
pulinduvidmal Jan 29, 2026
5fe3dad
fix: system prompt injection and multimodal architecture
pulinduvidmal Jan 29, 2026
32a5f07
fix: system prompt injection and multimodal architecture
pulinduvidmal Jan 29, 2026
5b9b01b
fix: system prompt injection and multimodal architecture
pulinduvidmal Jan 30, 2026
535e160
fix: system prompt injection and multimodal architecture
pulinduvidmal Jan 30, 2026
639c14d
updating version of examples/api/adk .toml file
lakindu-yl Jan 30, 2026
1315b80
Revert "updating version of examples/api/adk .toml file"
lakindu-yl Jan 30, 2026
ad68ede
version revert in ak-py pyproject.toml
lakindu-yl Jan 30, 2026
3b6675e
fix: version mismatch
pulinduvidmal Feb 2, 2026
c19478f
fix: version mismatch
pulinduvidmal Feb 2, 2026
655384d
fix: version mismatch
pulinduvidmal Feb 2, 2026
df51a35
fix: session id issue in adk
pulinduvidmal Feb 2, 2026
14ff432
Merge
pulinduvidmal Feb 2, 2026
b08c323
fix:
pulinduvidmal Feb 2, 2026
6578af5
fix: session id issue
pulinduvidmal Feb 3, 2026
a5e6c7b
fix: session id issue
pulinduvidmal Feb 3, 2026
82f59b5
feat: add test
pulinduvidmal Feb 3, 2026
b312706
fix: session id issue
pulinduvidmal Feb 3, 2026
ccaa21c
fix: session id issue
pulinduvidmal Feb 3, 2026
e81891e
feat: microsoft teams
pulinduvidmal Feb 12, 2026
10be41a
Merge branch 'develop' into feature/social_media_integration_testing
pulinduvidmal Feb 12, 2026
674e21c
feat: add Slack integration test with test_mode support
pulinduvidmal Feb 12, 2026
d8a84c6
chore: update uv.lock
pulinduvidmal Feb 12, 2026
d56e95f
Merge remote-tracking branch 'origin/develop' into feature/social_med…
pulinduvidmal Feb 16, 2026
eb1435a
feat: shared server fixture and CI secrets for Slack integration test
pulinduvidmal Feb 16, 2026
6d7ac44
feat: add Slack integration test
pulinduvidmal Feb 16, 2026
8a15d9f
feat: add Slack integration test
pulinduvidmal Feb 17, 2026
0bf3318
feat: add Slack integration test
pulinduvidmal Feb 17, 2026
411180e
feat: add Slack integration test
pulinduvidmal Feb 17, 2026
a7bdea1
fix: Test.compare()
pulinduvidmal Feb 17, 2026
3307aa7
fix rename test file
pulinduvidmal Feb 17, 2026
e727abe
feat add real telegram test
pulinduvidmal Feb 17, 2026
9f02645
feat add real telegram test
pulinduvidmal Feb 17, 2026
2da6ce7
Merge branch 'develop' into feature/add_file_support_for_telegram
pulinduvidmal Feb 19, 2026
d77f725
Merge origin/develop
pulinduvidmal Feb 19, 2026
faca290
fix: add tool injection to telegram
pulinduvidmal Feb 19, 2026
20183d4
refactor: simplify attachment IDs
pulinduvidmal Feb 19, 2026
93fbc22
refactor: simplify attachment IDs
pulinduvidmal Feb 19, 2026
8751d9f
add: abstract method
pulinduvidmal Feb 19, 2026
fa7b70f
fix: test
pulinduvidmal Feb 19, 2026
5c5e25e
fix: test
pulinduvidmal Feb 20, 2026
3675084
fix: test
pulinduvidmal Feb 20, 2026
c453539
fix: change server_test adk to openai
pulinduvidmal Feb 20, 2026
c5142fb
fix: tool adding in openai and adk
pulinduvidmal Feb 20, 2026
ae45069
fix: tool
pulinduvidmal Feb 20, 2026
e85e905
fix: multimodel config
pulinduvidmal Feb 20, 2026
c857a0a
fix: typo
pulinduvidmal Feb 25, 2026
aa540ec
fix: typo
pulinduvidmal Feb 25, 2026
9933d5e
Merge add_file_support_for_telegram: Integrate Teams with multimodal …
pulinduvidmal Feb 25, 2026
3770ded
Merge add_file_support_for_telegram: Integrate Teams with multimodal …
pulinduvidmal Feb 25, 2026
aa895b6
chore: remove garbage file from repository
pulinduvidmal Feb 25, 2026
b1cd302
Merge branch 'feature/add_teams_integration'
pulinduvidmal Mar 2, 2026
74cfc87
Restore multimodal changes from Teams branch
pulinduvidmal Mar 2, 2026
8f296f4
fix: config
pulinduvidmal Mar 2, 2026
fa78f65
feat: add whatsapp real test
pulinduvidmal Mar 2, 2026
46873bc
feat: add gmail test
pulinduvidmal Mar 2, 2026
ecda8f2
feat: add gmail test
pulinduvidmal Mar 2, 2026
d97475f
feat: add messenger test
pulinduvidmal Mar 3, 2026
bf6c998
feat: add messenger test
pulinduvidmal Mar 3, 2026
1260d02
feat: add instagram test
pulinduvidmal Mar 3, 2026
8262056
feat: add Teams integration test
pulinduvidmal Mar 3, 2026
caa21a8
feat: add Teams integration test
pulinduvidmal Mar 3, 2026
6064728
feat: add tests to nightly integration tests
pulinduvidmal Mar 3, 2026
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
2 changes: 2 additions & 0 deletions .github/integration-test-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ nightly:
path: examples/api/instagram
- type: api
path: examples/api/gmail
- type: api
path: examples/api/teams

weekly:
# Tests that run once a week
Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/integration-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ jobs:
TF_VAR_openai_api_key: ${{ secrets.OPENAI_API_KEY }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_SIGNING_SECRET: ${{ secrets.SLACK_SIGNING_SECRET }}
AK_TEST_SLACK_CHANNEL_ID: ${{ secrets.AK_TEST_SLACK_CHANNEL_ID }}
run: |
# Deploy
python .github/scripts/run_single_test.py \
Expand Down Expand Up @@ -175,12 +176,49 @@ jobs:
if: matrix.type == 'aws-containerized' || matrix.type == 'aws-serverless'
run: python3 scripts/deploy/inject_dependencies.py

- name: Restore Gmail token
if: matrix.path == 'examples/api/gmail'
run: echo "${{ secrets.AK_GMAIL_TOKEN_PICKLE_B64 }}" | base64 -d > examples/api/gmail/token.pickle

- name: Run Test - ${{ matrix.path }}
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
TF_VAR_openai_api_key: ${{ secrets.OPENAI_API_KEY }}
# Slack
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_SIGNING_SECRET: ${{ secrets.SLACK_SIGNING_SECRET }}
AK_TEST_SLACK_CHANNEL_ID: ${{ secrets.AK_TEST_SLACK_CHANNEL_ID }}
# Telegram
AK_TELEGRAM__BOT_TOKEN: ${{ secrets.AK_TELEGRAM__BOT_TOKEN }}
AK_TELEGRAM__WEBHOOK_SECRET: ${{ secrets.AK_TELEGRAM__WEBHOOK_SECRET }}
AK_TEST_TELEGRAM_CHAT_ID: ${{ secrets.AK_TEST_TELEGRAM_CHAT_ID }}
# WhatsApp
AK_WHATSAPP__ACCESS_TOKEN: ${{ secrets.AK_WHATSAPP__ACCESS_TOKEN }}
AK_WHATSAPP__PHONE_NUMBER_ID: ${{ secrets.AK_WHATSAPP__PHONE_NUMBER_ID }}
AK_WHATSAPP__VERIFY_TOKEN: ${{ secrets.AK_WHATSAPP__VERIFY_TOKEN }}
AK_TEST_WHATSAPP_TO_NUMBER: ${{ secrets.AK_TEST_WHATSAPP_TO_NUMBER }}
# Messenger
AK_MESSENGER__ACCESS_TOKEN: ${{ secrets.AK_MESSENGER__ACCESS_TOKEN }}
AK_MESSENGER__VERIFY_TOKEN: ${{ secrets.AK_MESSENGER__VERIFY_TOKEN }}
AK_MESSENGER__APP_SECRET: ${{ secrets.AK_MESSENGER__APP_SECRET }}
AK_TEST_MESSENGER_USER_ID: ${{ secrets.AK_TEST_MESSENGER_USER_ID }}
AK_MESSENGER__PAGE_ID: ${{ secrets.AK_MESSENGER__PAGE_ID }}
# Instagram
AK_INSTAGRAM__ACCESS_TOKEN: ${{ secrets.AK_INSTAGRAM__ACCESS_TOKEN }}
AK_INSTAGRAM__VERIFY_TOKEN: ${{ secrets.AK_INSTAGRAM__VERIFY_TOKEN }}
AK_INSTAGRAM__APP_SECRET: ${{ secrets.AK_INSTAGRAM__APP_SECRET }}
AK_TEST_INSTAGRAM_USER_ID: ${{ secrets.AK_TEST_INSTAGRAM_USER_ID }}
AK_INSTAGRAM__PAGE_ID: ${{ secrets.AK_INSTAGRAM__PAGE_ID }}
# Gmail
AK_TEST_GMAIL_ADDRESS: ${{ secrets.AK_TEST_GMAIL_ADDRESS }}
AK_GMAIL__CLIENT_ID: ${{ secrets.AK_GMAIL__CLIENT_ID }}
AK_GMAIL__CLIENT_SECRET: ${{ secrets.AK_GMAIL__CLIENT_SECRET }}
# Teams
AK_TEAMS__APP_ID: ${{ secrets.AK_TEAMS__APP_ID }}
AK_TEAMS__APP_PASSWORD: ${{ secrets.AK_TEAMS__APP_PASSWORD }}
AK_TEAMS__TENANT_ID: ${{ secrets.AK_TEAMS__TENANT_ID }}
AK_TEST_TEAMS_SERVICE_URL: ${{ secrets.AK_TEST_TEAMS_SERVICE_URL }}
AK_TEST_TEAMS_CONVERSATION_ID: ${{ secrets.AK_TEST_TEAMS_CONVERSATION_ID }}
run: |
# Deploy (for AWS types)
if [[ "${{ matrix.type }}" == "aws-containerized" || "${{ matrix.type }}" == "aws-serverless" ]]; then
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test-reusable.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_SIGNING_SECRET: ${{ secrets.SLACK_SIGNING_SECRET }}
AK_TEST_SLACK_CHANNEL_ID: ${{ secrets.AK_TEST_SLACK_CHANNEL_ID }}
run: |
python .github/scripts/run_single_test.py \
--type ${{ matrix.type }} \
Expand Down
5 changes: 5 additions & 0 deletions ak-py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ instagram = [
telegram = [
"httpx>=0.27.0"
]
teams = [
"httpx>=0.27.0",
"botbuilder-core>=4.15.0",
"msal>=1.30.0"
]
gmail = [
"google-auth>=2.0.0",
"google-auth-oauthlib>=1.0.0",
Expand Down
66 changes: 66 additions & 0 deletions ak-py/src/agentkernel/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,75 @@ def get_description(self) -> str:
"""
pass

@abstractmethod
def get_wrapped(self):
"""
Returns the underlying agent object.
"""
pass

@abstractmethod
def override_system_prompt(self, session: "Session", prompt: str) -> None:
"""
Overrides the system prompt of the agent via Session injection.
"""
pass

@abstractmethod
def attach_tool(self, tool: Any) -> None:
"""
Attaches a tool to the agent.
:param tool: The tool to attach.
"""
pass

@abstractmethod
def get_a2a_card(self) -> Any:
"""
Returns the A2A AgentCard associated with the agent.
"""
pass

@staticmethod
def get_system_prompt_suffix() -> str:
"""
Returns the system prompt suffix to be appended to the agent's system prompt.
:return: The system prompt suffix.
"""
tool_instruction = (
"User has attached files/images. Their IDs and descriptions are listed in the user's message.\n"
"Available tool:\n"
"- analyze_attachments(attachment_ids, prompt): Analyze attachments using litellm.\n"
" Returns only analysis text (no raw data), perfect for saving clean conversation history.\n"
"Use this tool when asked about attached images or files.\n"
"IMPORTANT: The descriptions above are brief summaries. If the user asks for SPECIFIC DETAILS (numbers, quotes, tables) found in the files, you MUST use the `analyze_attachments` tool to inspect the file content again. Do not guess based on the summary."
)
return tool_instruction

def _setup_system_prompt(self) -> None:
"""
Appends the Agent Kernel system prompt suffix to the agent's system prompt at init time.
Only applies when multimodal is enabled in AKConfig.
Subclasses should call this in __init__ after setting up the underlying agent.
The actual injection is done by override_system_prompt() which each subclass implements.
"""
from .config import AKConfig

config = getattr(AKConfig.get(), "multimodal", None)
if config and config.enabled:
self.override_system_prompt(session=None, prompt=self.get_system_prompt_suffix())
Comment on lines +425 to +426
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

override_system_prompt is declared to require a Session, but _setup_system_prompt() calls it with session=None. This is inconsistent with the abstract method contract and may break implementations that assume a real session. Consider typing the parameter as Session | None (and updating implementations) or refactor init-time prompt injection to not require a session object.

Suggested change
if config and config.enabled:
self.override_system_prompt(session=None, prompt=self.get_system_prompt_suffix())
session = getattr(self, "session", None)
if config and config.enabled and session is not None:
self.override_system_prompt(session=session, prompt=self.get_system_prompt_suffix())

Copilot uses AI. Check for mistakes.

def _attach_system_tools(self) -> None:
"""
Attaches Agent Kernel system-level tools to the agent at init time.
Only applies when multimodal is enabled in AKConfig.
Calls self.attach_tool() with each raw Callable — each framework's attach_tool
implementation is responsible for wrapping it into the framework-specific tool format.
"""
from .config import AKConfig

config = getattr(AKConfig.get(), "multimodal", None)
if config and config.enabled:
from .multimodal import analyze_attachments

self.attach_tool(analyze_attachments)
36 changes: 35 additions & 1 deletion ak-py/src/agentkernel/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class _APIConfig(BaseModel):
port: int = Field(default=8000, description="API port")
enabled_routes: _RoutesConfig = Field(description="API route flags", default_factory=_RoutesConfig)
custom_router_prefix: str = Field(default="/custom", description="Custom router prefix")
max_file_size: int = Field(default=2097152, description="Maximum file size in bytes (default: 2 MB)")
max_file_size: int = Field(default=20971520, description="Maximum file size in bytes (default: 20 MB)")


class _A2AConfig(BaseModel):
Expand All @@ -85,6 +85,18 @@ class _SlackConfig(BaseModel):
default="",
description="The message to send as an acknowledgement when a Slack message is received",
)
test_mode: bool = Field(default=False, description="Enable test mode for Slack integration")


class _TeamsConfig(BaseModel):
agent: str = Field(default="", description="Default agent to use for Microsoft Teams interactions")
agent_acknowledgement: str = Field(
default="",
description="The message to send as an acknowledgement when a Teams message is received",
)
app_id: str = Field(default="", description="Microsoft App ID (Application/Client ID) from Azure AD App Registration")
app_password: str = Field(default="", description="Microsoft App Password (Client Secret) from Azure AD App Registration")
tenant_id: str = Field(default="", description="Optional Tenant ID for Single Tenant App registration")


class _WhatsAppConfig(BaseModel):
Expand All @@ -98,6 +110,7 @@ class _WhatsAppConfig(BaseModel):
app_secret: str = Field(default="", description="WhatsApp app secret for signature verification")
phone_number_id: str = Field(default="", description="WhatsApp Business phone number ID")
api_version: str = Field(default="v24.0", description="WhatsApp API version")
api_base_url: str = Field(default="https://graph.facebook.com", description="WhatsApp API base URL")


class _MessengerConfig(BaseModel):
Expand Down Expand Up @@ -131,6 +144,25 @@ class _GmailConfig(BaseModel):
label_filter: str = Field(default="INBOX", description="Gmail label to monitor (e.g., INBOX, UNREAD)")


class _MultimodalConfig(BaseModel):
"""Configuration for multimodal attachment memory."""

enabled: bool = Field(
default=False,
description="Enable multimodal memory for images and files.",
)
max_attachments: int = Field(default=20, description="Maximum number of attachments to keep per session")
description_max_length: int = Field(default=200, description="Maximum length of attachment description text")
description_model: str = Field(
default="gpt-4o",
description="LiteLLM model used to generate brief descriptions when an attachment is first received (called by the pre-hook)",
)
analysis_model: str = Field(
default="gpt-4o",
description="LiteLLM model used by the analyze_attachments tool when the agent requests a full analysis of an attachment",
)
Comment on lines +154 to +163
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

description_max_length is introduced in _MultimodalConfig, but it isn’t referenced anywhere else in the codebase. Either enforce this limit when generating/storing descriptions (e.g., in describe_attachment_briefly or the multimodal pre-hook) or remove the config option to avoid dead/unused configuration knobs.

Copilot uses AI. Check for mistakes.


class _TraceConfig(BaseModel):
enabled: bool = Field(default=False, description="Enable tracing")
type: str = Field(default="langfuse", pattern="^(langfuse|openllmetry)$")
Expand Down Expand Up @@ -174,11 +206,13 @@ class AKConfig(YamlBaseSettingsModified):
default_factory=_MCPConfig,
)
slack: _SlackConfig = Field(description="Slack related configurations", default_factory=_SlackConfig)
teams: _TeamsConfig = Field(description="Microsoft Teams related configurations", default_factory=_TeamsConfig)
whatsapp: _WhatsAppConfig = Field(description="WhatsApp related configurations", default_factory=_WhatsAppConfig)
messenger: _MessengerConfig = Field(description="Facebook Messenger related configurations", default_factory=_MessengerConfig)
instagram: _InstagramConfig = Field(description="Instagram Business API related configurations", default_factory=_InstagramConfig)
telegram: _TelegramConfig = Field(description="Telegram Bot related configurations", default_factory=_TelegramConfig)
gmail: _GmailConfig = Field(description="Gmail related configurations", default_factory=_GmailConfig)
multimodal: _MultimodalConfig = Field(description="Multimodal attachment memory configurations", default_factory=_MultimodalConfig)

trace: _TraceConfig = Field(description="Tracing related configurations", default_factory=_TraceConfig)
test: _TestConfig = Field(description="Test related configurations", default_factory=_TestConfig)
Expand Down
21 changes: 21 additions & 0 deletions ak-py/src/agentkernel/core/multimodal/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
Multimodal module for Agent Kernel.

This module provides:
- Storage functions for saving/retrieving attachments
- PreHook for processing current images and injecting descriptions
- Tools for LLM to access image data (framework-agnostic)
"""

from .hooks import MultimodalPreHook, MultimodalPreHookFactory, NoOpPreHook
from .storage import (
AttachmentData,
AttachmentStorageDriver,
CacheStorageDriver,
get_attachment_data,
save_attachment,
)
from .tools import (
analyze_attachments,
describe_attachment_briefly,
)
Loading
Loading