diff --git a/ak-py/src/agentkernel/core/__init__.py b/ak-py/src/agentkernel/core/__init__.py index cf28aa02..b419711d 100644 --- a/ak-py/src/agentkernel/core/__init__.py +++ b/ak-py/src/agentkernel/core/__init__.py @@ -28,3 +28,4 @@ from .service import AgentService from .hooks import PreHook, PostHook from .util.key_value_cache import KeyValueCache +from .multimodal import MultimodalContextHook, MultimodalMemoryHook, MultimodalModuleMixin diff --git a/ak-py/src/agentkernel/core/config.py b/ak-py/src/agentkernel/core/config.py index 5f3a370f..e191ca07 100644 --- a/ak-py/src/agentkernel/core/config.py +++ b/ak-py/src/agentkernel/core/config.py @@ -121,6 +121,23 @@ class _GmailConfig(BaseModel): label_filter: str = Field(default="INBOX", description="Gmail label to monitor (e.g., INBOX, UNREAD)") +class _MultimodalConfig(BaseModel): + """Configuration for multimodal memory.""" + + enabled: bool = Field( + default=True, + description="Enable multimodal memory. When enabled, images and files are persisted " + "in session cache and automatically injected into follow-up requests.", + ) + max_attachments: int = Field( + default=5, description="Maximum number of recent attachments to keep in context for follow-up questions" + ) + attachment_ttl: int = Field( + default=604800, + description="Time-to-live for attachments in seconds (default: 604800 = 1 week, matches session TTL)", + ) + + class _TraceConfig(BaseModel): enabled: bool = Field(default=False, description="Enable tracing") type: str = Field(default="langfuse", pattern="^(langfuse|openllmetry)$") @@ -161,6 +178,9 @@ class AKConfig(YamlBaseSettingsModified): description="Telegram Bot related configurations", default_factory=_TelegramConfig ) gmail: _GmailConfig = Field(description="Gmail related configurations", default_factory=_GmailConfig) + multimodal: _MultimodalConfig = Field( + description="Multimodal memory related configurations", default_factory=_MultimodalConfig + ) trace: _TraceConfig = Field(description="Tracing related configurations", default_factory=_TraceConfig) test: _TestConfig = Field(description="Test related configurations", default_factory=_TestConfig) diff --git a/ak-py/src/agentkernel/core/multimodal.py b/ak-py/src/agentkernel/core/multimodal.py new file mode 100644 index 00000000..9448109d --- /dev/null +++ b/ak-py/src/agentkernel/core/multimodal.py @@ -0,0 +1,296 @@ +""" +Shared multimodal hooks for all Agent Kernel frameworks. + +This module provides framework-agnostic hooks for multimodal conversation memory, + +Architecture: +- MultimodalContextHook (Pre-hook): Loads previous attachments from cache and + injects them into the current request so the model has context. +- MultimodalMemoryHook (Post-hook): Saves new attachments to non-volatile cache + (Redis/DynamoDB) for persistence across requests. +- MultimodalModuleMixin: Mixin class for framework modules to auto-register hooks. + +Example flow: + User: [sends image] "What's in this photo?" + → Post-hook saves image to cache + Bot: "I see a red car." + + User: "What color is the house in the background?" + → Pre-hook loads previous image and injects it + → Model receives: [previous image] + "What color is the house..." + Bot: "The house appears to be white." + +Configuration via environment variables: + AK_OPENAI__MULTIMODAL_MEMORY=true # Enable/disable (default: true) + AK_OPENAI__MAX_ATTACHMENTS=5 # Max attachments in context + AK_OPENAI__ATTACHMENT_TTL=604800 # Expiry in seconds (1 week) +""" + +import logging +import time +from typing import TYPE_CHECKING + +from .hooks import PostHook, PreHook +from .model import AgentRequestFile, AgentRequestImage + +if TYPE_CHECKING: + from .base import Agent + from .model import AgentReply, AgentRequest + from .session import Session + +# Default: keep last 5 attachments in context (configurable) +DEFAULT_MAX_ATTACHMENTS = 5 +# Default: attachments expire after 1 week (604800 seconds) - matches session TTL +DEFAULT_ATTACHMENT_TTL = 604800 + + +class MultimodalContextHook(PreHook): + """ + Pre-hook that loads previous images/files from non-volatile cache and injects + + """ + + def __init__(self, max_attachments: int = DEFAULT_MAX_ATTACHMENTS, ttl_seconds: int = DEFAULT_ATTACHMENT_TTL): + """ + Initialize the context hook. + + :param max_attachments: Maximum number of recent attachments to inject (default: 5) + :param ttl_seconds: Time-to-live for attachments in seconds (default: 604800 = 1 week) + """ + self._log = logging.getLogger("ak.hooks.multimodal_context") + self._max_attachments = max_attachments + self._ttl_seconds = ttl_seconds + + async def on_run(self, session: "Session", agent: "Agent", requests: list["AgentRequest"]) -> list["AgentRequest"]: + """ + Load previous attachments from cache and inject into the request. + + :param session: The current session + :param agent: The agent instance + :param requests: List of current agent requests + :return: Modified requests with previous attachments injected + """ + if not session: + return requests + + nv_cache = session.get_non_volatile_cache() + current_time = time.time() + + # Collect all stored attachments + attachments = [] + keys_to_check = nv_cache.keys() if hasattr(nv_cache, "keys") else [] + + if not keys_to_check: + # Try to retrieve using the index if available + index_data = nv_cache.get("_attachment_index") + if index_data: + keys_to_check = index_data.get("keys", []) + + for key in keys_to_check: + if not (key.startswith("attachment_image_") or key.startswith("attachment_file_")): + continue + + data = nv_cache.get(key) + if not data: + continue + + # Check TTL + timestamp = data.get("timestamp", 0) + if current_time - timestamp > self._ttl_seconds: + # Expired, skip (could also delete here) + self._log.debug(f"Skipping expired attachment: {key}") + continue + + attachments.append({"key": key, "data": data, "timestamp": timestamp}) + + if not attachments: + return requests + + # Sort by timestamp and limit + # Use secondary sort by key for consistent ordering when timestamps match + attachments.sort(key=lambda x: (x["timestamp"], x["key"])) + attachments = attachments[-self._max_attachments :] + + # Inject attachments at the beginning of requests + injected_requests = [] + for att in attachments: + data = att["data"] + att_type = data.get("type") + + if att_type == "image": + img_req = AgentRequestImage( + image_data=data.get("data"), + mime_type=data.get("mime_type"), + name=data.get("name", "previous_image"), + ) + # Mark as injected so post-hook doesn't re-save it + img_req._injected = True + injected_requests.append(img_req) + elif att_type == "file": + file_req = AgentRequestFile( + file_data=data.get("data"), mime_type=data.get("mime_type"), name=data.get("name", "previous_file") + ) + # Mark as injected so post-hook doesn't re-save it + file_req._injected = True + injected_requests.append(file_req) + + if injected_requests: + self._log.info(f"Injected {len(injected_requests)} previous attachment(s) into context") + # Prepend previous attachments, then add current requests + return injected_requests + list(requests) + + return requests + + def name(self) -> str: + """Return hook name""" + return "MultimodalContextHook" + + +class MultimodalMemoryHook(PostHook): + """ + Post-hook that stores images and files in non-volatile cache after agent processes them. + + This hook ensures multimodal attachments are persisted in the session cache + (Redis or DynamoDB) so they're available in subsequent requests via MultimodalContextHook. + + Works with all frameworks: OpenAI, ADK, LangGraph, CrewAI + """ + + def __init__(self, max_attachments: int = DEFAULT_MAX_ATTACHMENTS): + """ + Initialize the memory hook. + + :param max_attachments: Maximum number of attachments to keep in cache (default: 5) + """ + self._log = logging.getLogger("ak.hooks.multimodal_memory") + self._max_attachments = max_attachments + + async def on_run( + self, session: "Session", requests: list["AgentRequest"], agent: "Agent", agent_reply: "AgentReply" + ) -> "AgentReply": + """ + Save multimodal attachments to non-volatile cache after agent processes them. + + :param session: The current session + :param requests: List of agent requests (may contain AgentRequestImage/AgentRequestFile) + :param agent: The agent instance + :param agent_reply: The agent's reply + :return: The agent_reply unchanged + """ + if not session: + return agent_reply + + nv_cache = session.get_non_volatile_cache() + timestamp = time.time() + attachment_count = 0 + new_keys = [] + + # Store each image/file with timestamp + for i, req in enumerate(requests): + if isinstance(req, AgentRequestImage): + # Skip if this is an injected previous attachment + if getattr(req, "_injected", False): + continue + + cache_key = f"attachment_image_{timestamp}_{i}" + nv_cache.set( + cache_key, + { + "type": "image", + "data": req.image_data, + "mime_type": req.mime_type, + "name": getattr(req, "name", None), + "timestamp": timestamp, + }, + ) + new_keys.append(cache_key) + self._log.debug(f"Stored image to cache: {cache_key}") + attachment_count += 1 + + elif isinstance(req, AgentRequestFile): + # Skip if this is an injected previous attachment + if getattr(req, "_injected", False): + continue + + cache_key = f"attachment_file_{timestamp}_{i}" + nv_cache.set( + cache_key, + { + "type": "file", + "data": req.file_data, + "mime_type": req.mime_type, + "name": req.name, + "timestamp": timestamp, + }, + ) + new_keys.append(cache_key) + self._log.debug(f"Stored file to cache: {cache_key}") + attachment_count += 1 + + # Update attachment index for cache implementations without keys() method + if new_keys: + index_data = nv_cache.get("_attachment_index") or {"keys": []} + index_data["keys"].extend(new_keys) + # Keep only recent keys + index_data["keys"] = index_data["keys"][-self._max_attachments * 2 :] + nv_cache.set("_attachment_index", index_data) + + if attachment_count > 0: + self._log.info(f"Stored {attachment_count} multimodal attachment(s) to non-volatile cache") + + return agent_reply + + def name(self) -> str: + """Return hook name""" + return "MultimodalMemoryHook" + + +class MultimodalModuleMixin: + """ + Mixin class that provides multimodal memory hook registration for framework modules. + + Usage: + class OpenAIModule(MultimodalModuleMixin, Module): + def __init__(self, agents, runner=None): + super().__init__() + # ... setup ... + self._register_multimodal_hooks(agents) + + Configuration is read from AKConfig.openai settings: + - multimodal_memory: bool (default: True) + - max_attachments: int (default: 5) + - attachment_ttl: int (default: 604800 = 1 week) + """ + + def _register_multimodal_hooks(self, agents: list) -> None: + """ + Register multimodal memory hooks for all agents if enabled in config. + + :param agents: List of agents (framework-specific agent objects) + """ + from .config import AKConfig + from .runtime import GlobalRuntime + + config = AKConfig.get().multimodal + if not config.enabled: + return + + runtime = GlobalRuntime.instance() + + # Create hooks with config values + context_hook = MultimodalContextHook(max_attachments=config.max_attachments, ttl_seconds=config.attachment_ttl) + memory_hook = MultimodalMemoryHook(max_attachments=config.max_attachments) + + # Register hooks for each agent by attaching to the wrapped agent + for agent in agents: + # Get agent name from raw agent (different frameworks use different attributes) + agent_name = getattr(agent, "name", None) or getattr(agent, "role", None) + if not agent_name: + continue + + # Get wrapped agent from module + wrapped_agent = self.get_agent(agent_name) + if wrapped_agent: + # Attach hooks to wrapped agent (which has the methods) + wrapped_agent.attach_pre_hooks([context_hook]) + wrapped_agent.attach_post_hooks([memory_hook]) diff --git a/ak-py/src/agentkernel/framework/adk/adk.py b/ak-py/src/agentkernel/framework/adk/adk.py index bb8425ce..84826adf 100644 --- a/ak-py/src/agentkernel/framework/adk/adk.py +++ b/ak-py/src/agentkernel/framework/adk/adk.py @@ -24,6 +24,7 @@ from ...core import Runner as BaseRunner from ...core import Session from ...core.config import AKConfig +from ...core.multimodal import MultimodalModuleMixin from ...trace import Trace FRAMEWORK = "adk" @@ -204,9 +205,12 @@ def get_a2a_card(self): pass -class GoogleADKModule(Module): +class GoogleADKModule(MultimodalModuleMixin, Module): """ GoogleADKModule class provides a module for Google ADK-based agents. + + When multimodal_memory is enabled (default), this module automatically registers + hooks to provide ChatGPT-like conversation memory for images and files. """ def __init__(self, agents: list[BaseAgent], runner: GoogleADKRunner = None): @@ -224,6 +228,9 @@ def __init__(self, agents: list[BaseAgent], runner: GoogleADKRunner = None): self.runner = GoogleADKRunner() self.load(agents) + # Auto-register multimodal memory hooks (from MultimodalModuleMixin) + self._register_multimodal_hooks(agents) + def _wrap(self, agent: BaseAgent, agents: List[BaseAgent]) -> AKBaseAgent: """ Wraps the provided agent in a GoogleADKAgent instance. diff --git a/ak-py/src/agentkernel/framework/openai/openai.py b/ak-py/src/agentkernel/framework/openai/openai.py index 21131b6f..3d4c597c 100644 --- a/ak-py/src/agentkernel/framework/openai/openai.py +++ b/ak-py/src/agentkernel/framework/openai/openai.py @@ -21,6 +21,7 @@ from ...core import Runner as BaseRunner from ...core import Session from ...core.config import AKConfig +from ...core.multimodal import MultimodalModuleMixin from ...trace import Trace FRAMEWORK = "openai" @@ -215,9 +216,15 @@ def get_a2a_card(self): return self._generate_a2a_card(agent_name=self.name, description=self.agent.instructions, skills=skills) -class OpenAIModule(Module): +class OpenAIModule(MultimodalModuleMixin, Module): """ OpenAIModule class provides a module for OpenAI Agents SDK based agents. + + When multimodal memory is enabled (default), this module automatically registers + hooks to provide conversation memory for images and files: + - MultimodalContextHook (pre-hook): Injects previous attachments into follow-up requests + - MultimodalMemoryHook (post-hook): Saves new attachments to session cache + """ def __init__(self, agents: list[Agent], runner: OpenAIRunner = None): @@ -235,6 +242,9 @@ def __init__(self, agents: list[Agent], runner: OpenAIRunner = None): self.runner = OpenAIRunner() self.load(agents) + # Auto-register multimodal memory hooks (from MultimodalModuleMixin) + self._register_multimodal_hooks(agents) + def _wrap(self, agent: Agent, agents: List[Agent]) -> BaseAgent: """ Wraps the provided agent in an OpenAIAgent instance. diff --git a/ak-py/src/agentkernel/integration/messenger/README.md b/ak-py/src/agentkernel/integration/messenger/README.md index 9d3edd91..6e762013 100644 --- a/ak-py/src/agentkernel/integration/messenger/README.md +++ b/ak-py/src/agentkernel/integration/messenger/README.md @@ -68,6 +68,16 @@ export AK_MESSENGER__APP_SECRET="your_app_secret" # Optional, but strongly reco export AK_MESSENGER__API_VERSION="v21.0" # Optional, defaults to v24.0. Only change if you want to move to a different version ``` +### Multimodal Configuration + +For image and document support, configure these environment variables: + +```bash +export AK_MULTIMODAL__ENABLED=true # Enable multimodal support (default: true) +export AK_MULTIMODAL__MAX_ATTACHMENTS=5 # Keep last N files in session (default: 5) +export AK_MULTIMODAL__ATTACHMENT_TTL=604800 # File lifetime in seconds (default: 604800 = 1 week) +``` + ### Webhook Verification The handler automatically responds to Facebook's webhook verification challenge via `/messenger/webhook` (e.g., `http://localhost:8000/messenger/webhook`). When you configure the webhook URL in Facebook's developer portal, Facebook will send a GET request to verify the endpoint. The handler processes this automatically. diff --git a/ak-py/src/agentkernel/integration/telegram/README.md b/ak-py/src/agentkernel/integration/telegram/README.md index ef8ca92c..7f99878a 100644 --- a/ak-py/src/agentkernel/integration/telegram/README.md +++ b/ak-py/src/agentkernel/integration/telegram/README.md @@ -21,16 +21,17 @@ The `AgentTelegramRequestHandler` class handles conversations with agents via Te ### Configuration Steps 1. **Create a Telegram Bot** + - Open Telegram and search for [@BotFather](https://t.me/botfather) - Send `/newbot` command - Follow the prompts to name your bot - Save the bot token provided - 2. **Get Your Credentials** + - **Bot Token**: Provided by BotFather when you create the bot - **Bot Username**: The username you chose (ending in `bot`) - 3. **Configure Webhook** + - The webhook is automatically set when your server starts - Or manually set via: `https://api.telegram.org/bot/setWebhook?url=https://your-domain.com/telegram/webhook` @@ -85,15 +86,46 @@ It is strongly recommended not to keep secrets and keys in the config file. Set ### Supported Message Types - **Text Messages**: Standard text messages +- **Images**: Photos sent directly to the bot +- **Documents**: Files including PDFs, TXT, CSV, DOCX, and other document formats - **Commands**: Bot commands starting with `/` - **Replies**: Reply to bot messages +### Multi-Modal Support + +The Telegram integration **fully supports** sending images and documents to agents. When a user sends a message with attachments: + +1. **Images**: Photos are extracted, base64-encoded, and sent to the agent as `AgentRequestImage` +2. **Documents**: Files are downloaded, base64-encoded, and sent to the agent as `AgentRequestFile` +3. **Combined**: A message can include both text and files/images which are all processed together + +**Status:** + +- File/image detection and download infrastructure +- Base64 encoding of files/images +- Multi-modal requests forwarding (`service.run_multi()`) +- Session memory for follow-up questions with context +- Works with **OpenAI SDK** and **Google ADK** agents + +**Supported file types:** + +- **Images**: JPEG, PNG, GIF, WebP, and other image formats +- **Documents**: PDF, TXT, CSV, DOC, DOCX, and other document formats + +**Limitations:** + +- Maximum file size: ~2MB +- Processing time: Large files may take 30-60 seconds to download and analyze +- Session context: Cleared on server restart (can be persisted with external storage) +- Model requirements: Agent must support multimodal (OpenAI GPT-4o, Google ADK, etc.) + ### Message Handling - **Automatic Message Splitting**: Messages longer than 4096 characters are automatically split - **Session Management**: Uses chat ID as session ID to maintain conversation context - **Typing Indicator**: Shows typing status while processing - **Markdown Support**: Responses support Telegram's Markdown formatting +- **File Handling**: Supports images (JPEG, PNG, GIF, WebP) and documents (PDF, TXT, CSV, DOC, DOCX, etc.) ### Security @@ -107,11 +139,13 @@ It is strongly recommended not to keep secrets and keys in the config file. Set For local testing, use a tunneling service to expose your local server: **Using ngrok:** + ```bash ngrok http 8000 ``` **Using pinggy:** + ```bash ssh -p443 -R0:localhost:8000 a.pinggy.io ``` @@ -137,21 +171,31 @@ curl "https://api.telegram.org/bot/setWebhook?url=https://your-n ### Common Issues **Webhook not receiving messages:** + - Ensure webhook URL is HTTPS - Verify the URL is publicly accessible - Check bot token is correct - Use `getWebhookInfo` to debug: `https://api.telegram.org/bot/getWebhookInfo` **Messages not sending:** + - Verify bot token is correct - Check bot hasn't been blocked by user - Ensure chat_id is valid **Bot not responding:** + - Check server logs for errors - Verify agent is properly configured - Ensure OpenAI API key is set +**Files/Images not being processed:** + +- Verify your agent supports images/files (OpenAI SDK or Google ADK) +- Check the file size isn't too large +- Ensure `max_file_size` configuration allows the file +- Check logs for download errors from Telegram servers + ### Debug Logging Enable debug logging to troubleshoot: @@ -161,14 +205,24 @@ import logging logging.basicConfig(level=logging.DEBUG) ``` - ## API Rate Limits Telegram Bot API has rate limits: + - **Messages to same chat**: 1 message per second - **Bulk messages**: 30 messages per second to different chats - **Group messages**: 20 messages per minute per group +- **File downloads**: Files are downloaded on-demand when received + +## File Size Considerations + +- **Downloaded files**: Automatically downloaded and base64-encoded +- **Encoding overhead**: Base64 encoding increases size by ~33% +- **Agent framework limits**: OpenAI and Google ADK have their own file size limits +- **Performance**: Large files may take longer to download and process + ## References - [Telegram Bot API Documentation](https://core.telegram.org/bots/api) -- [BotFather](https://t.me/botfather) +- [Telegram Bot API Media Types](https://core.telegram.org/bots/api#mediagroupmessage) +- [BotFather](https://t.me/botfather) \ No newline at end of file diff --git a/ak-py/src/agentkernel/integration/telegram/telegram_chat.py b/ak-py/src/agentkernel/integration/telegram/telegram_chat.py index cb707287..b5e52365 100644 --- a/ak-py/src/agentkernel/integration/telegram/telegram_chat.py +++ b/ak-py/src/agentkernel/integration/telegram/telegram_chat.py @@ -1,4 +1,6 @@ +import base64 import logging +import mimetypes import traceback import httpx @@ -6,6 +8,7 @@ from ...api import RESTRequestHandler from ...core import AgentService, Config +from ...core.model import AgentRequestFile, AgentRequestImage, AgentRequestText class AgentTelegramRequestHandler(RESTRequestHandler): @@ -26,6 +29,8 @@ def __init__(self): self._webhook_secret = Config.get().telegram.webhook_secret self._api_version = Config.get().telegram.api_version or "bot" self._base_url = f"https://api.telegram.org/{self._api_version}{self._bot_token}" + self._http_timeout = 30.0 # Timeout for file downloads and API calls + self._max_file_size = 2 * 1024 * 1024 # 2MB if not self._bot_token: self._log.error("Telegram bot token is not configured. Please set bot_token.") @@ -95,25 +100,29 @@ async def _handle_message(self, message: dict): chat_id = message.get("chat", {}).get("id") message_id = message.get("message_id") - text = message.get("text") - - # Handle different message types - if not text: - - self._log.warning("Message has no text content") - return + # Get text from either 'text' field (regular messages) or 'caption' field (media with captions) + text = (message.get("text") or message.get("caption") or "").strip() if not chat_id or not message_id: self._log.warning("Message missing required fields (chat_id/message_id)") return - self._log.debug(f"Processing message {message_id} from chat {chat_id}: {text}") + # Check if message has text, files, or images + has_text = bool(text) + has_files = "document" in message + has_images = "photo" in message + + if not has_text and not has_files and not has_images: + self._log.warning("Message has no text, files, or images") + return + + self._log.debug(f"Processing message {message_id} from chat {chat_id}") - # Check if it's a bot command - if text.startswith("/"): + # Check if it's a bot command (only if it has text) + if has_text and text.startswith("/"): await self._handle_command(chat_id, text) else: - await self._process_agent_message(chat_id, text) + await self._process_agent_message(chat_id, text if has_text else "", message) async def _handle_edited_message(self, message: dict): """ @@ -167,12 +176,13 @@ async def _handle_command(self, chat_id: int, command: str): # Unknown command - process as regular message await self._process_agent_message(chat_id, command) - async def _process_agent_message(self, chat_id: int, message_text: str): + async def _process_agent_message(self, chat_id: int, message_text: str, message: dict | None = None): """ Process message through agent. :param chat_id: Chat ID :param message_text: Message text + :param message: Full message dict from Telegram (for accessing files/images) """ service = AgentService() session_id = str(chat_id) # Use chat_id as session_id @@ -188,8 +198,27 @@ async def _process_agent_message(self, chat_id: int, message_text: str): await self._send_message(chat_id, "Sorry, no agent is available to handle your request.") return - # Run the agent - result = await service.run(message_text) + # Build requests list with text and files/images + requests = [] + + # Add text if present + if message_text: + requests.append(AgentRequestText(text=message_text)) + + # Process files and images if message object is provided + if message: + failed_files = await self._process_files(message, requests) + if failed_files: + self._log.warning(f"Failed to process files: {failed_files}") + + # If no content at all, nothing to process + if not requests: + self._log.warning("No valid content found in message") + await self._send_message(chat_id, "Sorry, your message appears to be empty.") + return + + # Run the agent with all requests (text + files/images) + result = await service.run_multi(requests=requests) if hasattr(result, "raw"): response_text = str(result.raw) @@ -220,7 +249,7 @@ async def _send_message(self, chat_id: int, text: str, parse_mode: str = None, r max_length = 4096 messages = [text[i : i + max_length] for i in range(0, len(text), max_length)] - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(timeout=self._http_timeout) as client: for i, message_text in enumerate(messages): payload = { "chat_id": chat_id, @@ -264,7 +293,7 @@ async def _send_chat_action(self, chat_id: int, action: str = "typing"): } try: - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(timeout=self._http_timeout) as client: response = await client.post(url, json=payload) response.raise_for_status() self._log.debug(f"Chat action '{action}' sent to {chat_id}") @@ -291,9 +320,169 @@ async def _answer_callback_query(self, callback_query_id: str, text: str = None, payload["show_alert"] = show_alert try: - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(timeout=self._http_timeout) as client: response = await client.post(url, json=payload) response.raise_for_status() self._log.debug(f"Callback query answered: {callback_query_id}") except Exception as e: self._log.warning(f"Failed to answer callback query: {e}") + + async def _get_file_info(self, file_id: str): + """ + Get file information from Telegram API. + + :param file_id: File ID from Telegram + :return: File info dict with file_path + """ + url = f"{self._base_url}/getFile" + payload = {"file_id": file_id} + + try: + async with httpx.AsyncClient(timeout=self._http_timeout) as client: + response = await client.post(url, json=payload) + response.raise_for_status() + result = response.json() + if result.get("ok"): + return result.get("result") + else: + self._log.error(f"Failed to get file info: {result}") + return None + except Exception as e: + self._log.error(f"Error getting file info: {e}") + return None + + async def _download_telegram_file(self, file_path: str) -> bytes | None: + """ + Download file content from Telegram server. + + :param file_path: File path from getFile API + :return: File content as bytes + """ + url = f"https://api.telegram.org/file/bot{self._bot_token}/{file_path}" + + try: + async with httpx.AsyncClient(timeout=self._http_timeout) as client: + response = await client.get(url) + response.raise_for_status() + return response.content + except Exception as e: + self._log.error(f"Error downloading file from Telegram: {e}") + return None + + async def _process_files(self, message: dict, requests: list) -> list[str]: + """ + Process files and images in a Telegram message. + + :param message: Message object from Telegram + :param requests: List to append AgentRequestFile/AgentRequestImage objects + :return: List of failed file names + """ + failed_files = [] + + # Process photos (images) + if "photo" in message: + photos = message.get("photo", []) + if photos: + # Get the largest photo + largest_photo = photos[-1] + file_id = largest_photo.get("file_id") + + try: + self._log.debug(f"Processing photo: {file_id}") + + # Get file info + file_info = await self._get_file_info(file_id) + if file_info: + file_path = file_info.get("file_path") + file_size = file_info.get("file_size", 0) + + # Validate file size before download + if not file_size or file_size <= self._max_file_size: + # Download file + file_content = await self._download_telegram_file(file_path) + if file_content is not None: + # Base64 encode + image_data_base64 = base64.b64encode(file_content).decode("utf-8") + + # Detect actual MIME type from file path + photo_name = file_path.rsplit("/", 1)[-1] if file_path else "photo" + guessed_mime_type, _ = mimetypes.guess_type(file_path or "") + photo_mime_type = guessed_mime_type or "image/jpeg" + + # Add as image request + requests.append( + AgentRequestImage( + image_data=image_data_base64, + name=photo_name, + mime_type=photo_mime_type, + ) + ) + self._log.debug(f"Added photo to request (size: {file_size} bytes)") + else: + self._log.warning(f"Failed to download photo") + failed_files.append("photo") + else: + self._log.warning( + f"Photo is too large to process " + f"({file_size} bytes > {self._max_file_size} bytes). Skipping." + ) + failed_files.append("photo") + else: + self._log.warning("Failed to get photo file info") + failed_files.append("photo") + + except Exception as e: + self._log.error(f"Error processing photo: {e}\n{traceback.format_exc()}") + failed_files.append("photo") + + # Process documents (files) + if "document" in message: + document = message.get("document", {}) + file_id = document.get("file_id") + file_name = document.get("file_name", "document") + mime_type = document.get("mime_type", "application/octet-stream") + + try: + self._log.debug(f"Processing document: {file_id} ({file_name})") + + # Get file info + file_info = await self._get_file_info(file_id) + if file_info: + file_path = file_info.get("file_path") + file_size = file_info.get("file_size", 0) + + # Validate file size before download + if not file_size or file_size <= self._max_file_size: + # Download file + file_content = await self._download_telegram_file(file_path) + if file_content is not None: + # Base64 encode + file_data_base64 = base64.b64encode(file_content).decode("utf-8") + + # Add as file request + requests.append( + AgentRequestFile( + file_data=file_data_base64, + name=file_name, + mime_type=mime_type, + ) + ) + self._log.debug(f"Added file to request: {file_name} (size: {file_size} bytes)") + else: + self._log.warning(f"Failed to download file: {file_name}") + failed_files.append(file_name) + else: + self._log.warning( + f"File '{file_name}' is too large to process " + f"({file_size} bytes > {self._max_file_size} bytes). Skipping." + ) + failed_files.append(file_name) + else: + self._log.warning(f"Failed to get file info for {file_name}") + failed_files.append(file_name) + + except Exception as e: + self._log.error(f"Error processing document: {e}\n{traceback.format_exc()}") + failed_files.append(file_name) + + return failed_files diff --git a/docs/docs/integrations/telegram.md b/docs/docs/integrations/telegram.md index c8eecf6b..176cfec8 100644 --- a/docs/docs/integrations/telegram.md +++ b/docs/docs/integrations/telegram.md @@ -8,8 +8,8 @@ The `AgentTelegramRequestHandler` provides a seamless bridge between your Agent ### How it works: -1. User sends a message to your Telegram bot -2. Telegram delivers the message to your webhook endpoint +1. User sends a message to your Telegram bot +2. Telegram delivers the message to your webhook endpoint 3. Agent Kernel verifies and processes the message 4. Visual feedback is sent (typing indicator, etc.) 5. Agent generates a response and sends it back to the user @@ -45,17 +45,29 @@ export AK_TELEGRAM__BOT_TOKEN="your_bot_token" export AK_TELEGRAM__WEBHOOK_SECRET="your_secure_random_string" # Optional export OPENAI_API_KEY="your_openai_api_key" ``` +### Multimodal Configuration + +For image and document support, configure these environment variables: + +```bash +export AK_MULTIMODAL__ENABLED=true # Enable multimodal support (default: true) +export AK_MULTIMODAL__MAX_ATTACHMENTS=5 # Keep last N files in session (default: 5) +export AK_MULTIMODAL__ATTACHMENT_TTL=604800 # File lifetime in seconds (default: 604800 = 1 week) +``` + ### 4. Expose Local Server Use a tunneling service for webhook delivery: **ngrok:** + ```bash ngrok http 8000 ``` **pinggy:** + ```bash ssh -p443 -R0:localhost:8000 a.pinggy.io ``` @@ -102,30 +114,69 @@ curl "https://api.telegram.org/bot/getWebhookInfo" ## Features -- Text message and command handling -- AI-powered responses via Agent Kernel -- Typing indicator -- Inline keyboards -- Markdown formatting -- Session management (per chat) -- Automatic message splitting for long responses +- **Text messages and commands** - Standard messaging with `/start`, `/help`, etc. +- **Multi-modal support** - Send and analyze images, PDFs, and document files alongside text +- **Typing indicator** - Visual feedback while processing +- **Inline keyboards** - Interactive button-based interactions +- **Markdown formatting** - Rich text formatting in responses +- **Session management** - Per-chat conversation context with memory +- **Automatic message splitting** - Long responses split to respect Telegram's 4096 character limit -## Advanced Usage +## Multi-Modal Support (Images & Documents) -### Custom Command Handler +The Telegram integration supports sending images and documents alongside text messages for intelligent analysis: + +### Supported File Types + +| Type | Formats | Use Cases | +| ------------------- | ------------------------ | ----------------------------------------------- | +| **Images** | JPEG, PNG, WebP, GIF | Photo analysis, vision tasks, object detection | +| **Documents** | PDF, TXT, CSV, DOCX, DOC | Document summarization, Q&A, content extraction | +| **Text** | Plain messages | Standard conversations | + +### How It Works + +1. User sends an image or document with optional text caption +2. Telegram webhook delivers the message to your agent +3. Agent Kernel downloads and processes the file +4. File is base64-encoded and sent to the agent for analysis +5. Agent generates response based on file content + text +6. Response is sent back through Telegram + +### Example Scenarios + +**Image Analysis:** + +- User sends photo of a receipt: "What's the total?" +- Bot analyzes image and responds with extracted total + +**Document Q&A:** + +- User sends PDF and asks: "Summarize key points" +- Bot reads and summarizes the document + +**Follow-up Questions:** + +- Message 1: User sends image, asks "What is this?" +- Bot analyzes and responds +- Message 2: User asks "What colors are dominant?" +- Bot remembers the image from previous context and answers + +### Session Memory with Context + +Each chat maintains conversation history: + +- Previous messages are remembered +- Images/files from earlier messages can be referenced +- Multi-turn conversations with rich context +- Works seamlessly with OpenAI GPT-4o and compatible models + if command == "/status": + await self._send_message(chat_id, "✅ Bot is running!") + elif command == "/about": + await self._send_message(chat_id, "I'm powered by Agent Kernel and OpenAI") + else: + await super()._handle_command(chat_id, command) -```python -from agentkernel.telegram import AgentTelegramRequestHandler - -class CustomTelegramHandler(AgentTelegramRequestHandler): - async def _handle_command(self, chat_id: int, command: str): - if command == "/status": - await self._send_message(chat_id, "✅ Bot is running!") - elif command == "/about": - await self._send_message(chat_id, "I'm powered by Agent Kernel and OpenAI") - else: - await super()._handle_command(chat_id, command) -``` ### Multi-Agent Setup @@ -171,52 +222,12 @@ await self._send_message( ) ``` - ## Supported Message Types - Text messages - Commands (e.g., /start, /help) - Inline keyboards and callback queries -## Troubleshooting - -### Webhook Verification Issues - -**Problem:** Webhook setup fails or Telegram can't reach your endpoint - -**Solutions:** -- Ensure your bot token is correct -- Webhook URL must be HTTPS and publicly accessible -- Check webhook info: `curl "https://api.telegram.org/bot/getWebhookInfo"` -- Review server logs for errors -- Verify agent configuration and OpenAI API key - -### No Messages Received - -**Problem:** Webhook is set but messages aren't reaching your agent - -**Solutions:** -- Verify your server is running and accessible -- Check that your webhook URL path is `/telegram/webhook` -- Review server logs for incoming webhook requests - -### Message Sending Failures - -**Problem:** Agent processes messages but responses don't appear in Telegram - -**Solutions:** -- Confirm you're using a valid bot token -- Check for API error codes in logs -- Ensure you are not exceeding rate limits - -### Authentication Errors - -**Problem:** "Invalid secret token" or authentication-related errors - -**Solutions:** -- Verify `AK_TELEGRAM__WEBHOOK_SECRET` matches what you set in Telegram -- Ensure the secret hasn't been changed -- Check server logs for validation details ### Enable Debug Logging @@ -234,6 +245,7 @@ logging.basicConfig( ## API Rate Limits Telegram Bot API enforces rate limits: + - **Messages to same chat:** 1 message per second - **Bulk messages:** 30 messages per second to different chats - **Group messages:** 20 messages per minute per group @@ -244,11 +256,11 @@ Best practice: Implement queuing for high-volume scenarios. ### Pre-Launch Checklist -✅ Use environment variables for all secrets \ +✅ Use environment variables for all secrets ✅ Deploy behind a reverse proxy (nginx, Apache) \ ✅ Set up health checks and monitoring \ ✅ Implement error handling and retry logic \ -✅ Review Telegram Bot API documentation for compliance +✅ Review Telegram Bot API documentation for compliance ### Deployment Architecture @@ -258,23 +270,25 @@ Best practice: Implement queuing for high-volume scenarios. ## Telegram vs Messenger Comparison -| Feature | Telegram | Facebook Messenger | -|------------------------|-------------------------|---------------------------| -| Message Limit | 4096 characters | 2000 characters | -| User Identifier | Chat ID | Page-Scoped ID (PSID) | -| Visual Feedback | Typing indicators | Typing, seen receipts | -| Interactive Elements | Inline keyboards | Buttons, quick replies | -| Authentication | Bot token + secret | Page access token + secret| -| App Review | Not required | Required for public access| -| Rich Media | Media, basic attachments| Extensive template support| +| Feature | Telegram | Facebook Messenger | +| -------------------- | ------------------------ | -------------------------- | +| Message Limit | 4096 characters | 2000 characters | +| User Identifier | Chat ID | Page-Scoped ID (PSID) | +| Visual Feedback | Typing indicators | Typing, seen receipts | +| Interactive Elements | Inline keyboards | Buttons, quick replies | +| Authentication | Bot token + secret | Page access token + secret | +| App Review | Not required | Required for public access | +| Rich Media | Media, basic attachments | Extensive template support | ## Example Projects -- Basic Example: `examples/api/telegram/server.py` +- Basic Example: \ +`examples/api/telegram/server.py`\ +`examples/api/telegram/server_adk.py` ## References - [Telegram Bot API Documentation](https://core.telegram.org/bots/api) - [BotFather](https://t.me/botfather) -*** +--- \ No newline at end of file diff --git a/examples/api/a2a/multi/uv.lock b/examples/api/a2a/multi/uv.lock index 663af8b6..4a787600 100644 --- a/examples/api/a2a/multi/uv.lock +++ b/examples/api/a2a/multi/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.13'", @@ -2087,7 +2087,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -2098,9 +2098,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] diff --git a/examples/api/gmail/uv.lock b/examples/api/gmail/uv.lock index 9ea2d7e4..cb52e44a 100644 --- a/examples/api/gmail/uv.lock +++ b/examples/api/gmail/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", @@ -2000,7 +2000,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -2011,9 +2011,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] diff --git a/examples/api/hooks/uv.lock b/examples/api/hooks/uv.lock index b33429c6..62c1dbca 100644 --- a/examples/api/hooks/uv.lock +++ b/examples/api/hooks/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", @@ -1801,7 +1801,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -1812,9 +1812,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] diff --git a/examples/api/instagram/uv.lock b/examples/api/instagram/uv.lock index 10796028..e7c90fdb 100644 --- a/examples/api/instagram/uv.lock +++ b/examples/api/instagram/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", @@ -1883,7 +1883,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -1894,9 +1894,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] diff --git a/examples/api/mcp/multi/uv.lock b/examples/api/mcp/multi/uv.lock index b6398339..8a3249aa 100644 --- a/examples/api/mcp/multi/uv.lock +++ b/examples/api/mcp/multi/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.13'", @@ -2222,7 +2222,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -2233,9 +2233,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] diff --git a/examples/api/messenger/uv.lock b/examples/api/messenger/uv.lock index e7357fc7..f6db7a62 100644 --- a/examples/api/messenger/uv.lock +++ b/examples/api/messenger/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", @@ -10,7 +10,7 @@ resolution-markers = [ [[package]] name = "agentkernel" version = "0.2.9" -source = { registry = "https://pypi.org/simple" } +source = { registry = "../../../ak-py/dist" } dependencies = [ { name = "deprecated" }, { name = "pydantic" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/a4/c3b7b1663131bd275b99bb91d77d66983f598c9e0b686f57558ab9b6e1e2/agentkernel-0.2.9.tar.gz", hash = "sha256:976ccc343039f35ab4661e97f42f935ec88627ba1763980c68f983523ef09fdc", size = 76953, upload-time = "2025-12-18T12:28:27.293Z" } +sdist = { path = "agentkernel-0.2.9.tar.gz" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/6b/ea9652f7d83c29fdff8322bcb4a0ac8fa6c55214f9e0334a947a86ac2d95/agentkernel-0.2.9-py3-none-any.whl", hash = "sha256:307d6299baf7f9f579ab100492ddc2a66facbb8fc3b2960937b007f40bb66efa", size = 121841, upload-time = "2025-12-18T12:28:26.002Z" }, + { path = "agentkernel-0.2.9-py3-none-any.whl" }, ] [package.optional-dependencies] @@ -1299,7 +1299,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.2.3" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -1311,23 +1311,23 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/ea/8380184b287da43d3d2556475b985cf3e27569e9d8bbe33195600a98cabb/langchain_core-1.2.3.tar.gz", hash = "sha256:61f5197aa101cd5605879ef37f2b0ac56c079974d94d347849b8d4fe18949746", size = 803567, upload-time = "2025-12-18T20:13:10.574Z" } +sdist = { url = "https://files.pythonhosted.org/packages/70/45/3d63fd7dc873abd9a0b1960775554dcc2a45dd4905937ec0b3d101dd5f10/langchain_core-1.2.2.tar.gz", hash = "sha256:3f9c28ec6d0fe47636d28b19799794458d55da81f37309832b2b9d11c93c5e95", size = 803123, upload-time = "2025-12-16T20:25:53.788Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/57/cfc1d12e273d33d16bab7ce9a135244e6f5677a92a5a99e69a61b22b7d93/langchain_core-1.2.3-py3-none-any.whl", hash = "sha256:c3501cf0219daf67a0ae23f6d6bdf3b41ab695efd8f0f3070a566e368b8c3dc7", size = 476384, upload-time = "2025-12-18T20:13:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/59/97/57497c8b26829e38c8dd4abe972d75e38fc3904324a3042bb01d9e0753b8/langchain_core-1.2.2-py3-none-any.whl", hash = "sha256:3a83dc14217de5cba11b1a0bd43c48702401bbd18dc25cac2ffab5ac83a61cd0", size = 476125, upload-time = "2025-12-16T20:25:52.581Z" }, ] [[package]] name = "langchain-openai" -version = "1.1.6" +version = "1.1.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "openai" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/67/228dc28b4498ea16422577013b5bb4ba35a1b99f8be975d6747c7a9f7e6a/langchain_openai-1.1.6.tar.gz", hash = "sha256:e306612654330ae36fb6bbe36db91c98534312afade19e140c3061fe4208dac8", size = 1038310, upload-time = "2025-12-18T17:58:52.84Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/1c/008a6dd7b3523121be1a4f24701b099ae79193dab9b329dfb787bece08bf/langchain_openai-1.1.5.tar.gz", hash = "sha256:a8ca5f3919bd948867c7d427a575b34f7c141110ef7cbc14ea7bbc46363871de", size = 1038129, upload-time = "2025-12-17T19:14:36.392Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/5b/1f6521df83c1a8e8d3f52351883b59683e179c0aa1bec75d0a77a394c9e7/langchain_openai-1.1.6-py3-none-any.whl", hash = "sha256:c42d04a67a85cee1d994afe400800d2b09ebf714721345f0b651eb06a02c3948", size = 84701, upload-time = "2025-12-18T17:58:51.527Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c5/22b690a27ba6b1ca6876270473aab1610cb8767314e5038cb6b826d9b69b/langchain_openai-1.1.5-py3-none-any.whl", hash = "sha256:d3a3b0c39e1513bbb9e5d4526c194909a00c5733195dbe90bfea6619b00420ca", size = 84569, upload-time = "2025-12-17T19:14:35.529Z" }, ] [[package]] @@ -1387,15 +1387,15 @@ wheels = [ [[package]] name = "langgraph-sdk" -version = "0.3.1" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, { name = "orjson" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/d3/b6be0b0aba2a53a8920a2b0b4328a83121ec03eea9952e576d06a4182f6f/langgraph_sdk-0.3.1.tar.gz", hash = "sha256:f6dadfd2444eeff3e01405a9005c95fb3a028d4bd954ebec80ea6150084f92bb", size = 130312, upload-time = "2025-12-18T22:11:47.42Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/1b/f328afb4f24f6e18333ff357d9580a3bb5b133ff2c7aae34fef7f5b87f31/langgraph_sdk-0.3.0.tar.gz", hash = "sha256:4145bc3c34feae227ae918341f66d3ba7d1499722c1ef4a8aae5ea828897d1d4", size = 130366, upload-time = "2025-12-12T22:19:30.323Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/fe/0c1c9c01a154eba62b20b02fabe811fd94a2b810061ae9e4d8462b8cf85a/langgraph_sdk-0.3.1-py3-none-any.whl", hash = "sha256:0b856923bfd20bf3441ce9d03bef488aa333fb610e972618799a9d584436acad", size = 66517, upload-time = "2025-12-18T22:11:46.625Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/ee4d7afb3c3d38bd2ebe51a4d37f1ed7f1058dd242f35994b562203067aa/langgraph_sdk-0.3.0-py3-none-any.whl", hash = "sha256:c1ade483fba17ae354ee920e4779042b18d5aba875f2a858ba569f62f628f26f", size = 66489, upload-time = "2025-12-12T22:19:29.228Z" }, ] [[package]] @@ -1812,7 +1812,7 @@ wheels = [ [[package]] name = "openai" -version = "2.14.0" +version = "2.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1824,14 +1824,14 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/b1/12fe1c196bea326261718eb037307c1c1fe1dedc2d2d4de777df822e6238/openai-2.14.0.tar.gz", hash = "sha256:419357bedde9402d23bf8f2ee372fca1985a73348debba94bddff06f19459952", size = 626938, upload-time = "2025-12-19T03:28:45.742Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/39/8e347e9fda125324d253084bb1b82407e5e3c7777a03dc398f79b2d95626/openai-2.13.0.tar.gz", hash = "sha256:9ff633b07a19469ec476b1e2b5b26c5ef700886524a7a72f65e6f0b5203142d5", size = 626583, upload-time = "2025-12-16T18:19:44.387Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/4b/7c1a00c2c3fbd004253937f7520f692a9650767aa73894d7a34f0d65d3f4/openai-2.14.0-py3-none-any.whl", hash = "sha256:7ea40aca4ffc4c4a776e77679021b47eec1160e341f42ae086ba949c9dcc9183", size = 1067558, upload-time = "2025-12-19T03:28:43.727Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d5/eb52edff49d3d5ea116e225538c118699ddeb7c29fa17ec28af14bc10033/openai-2.13.0-py3-none-any.whl", hash = "sha256:746521065fed68df2f9c2d85613bb50844343ea81f60009b60e6a600c9352c79", size = 1066837, upload-time = "2025-12-16T18:19:43.124Z" }, ] [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -1842,9 +1842,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] @@ -3233,27 +3233,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/48/d9/97d5808e851f790e58f8a54efb5c7b9f404640baf9e295f424846040b316/ty-0.0.4.tar.gz", hash = "sha256:2ea47a0089d74730658ec4e988c8ef476a1e9bd92df3e56709c4003c2895ff3b", size = 4780289, upload-time = "2025-12-19T00:13:53.12Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/94/b32a962243cc8a16e8dc74cf1fe75e8bb013d0e13e71bb540e2c86214b61/ty-0.0.4-py3-none-linux_armv6l.whl", hash = "sha256:5225da65a8d1defeb21ee9d74298b1b97c6cbab36e235a310c1430d9079e4b6a", size = 9762399, upload-time = "2025-12-19T00:14:11.261Z" }, - { url = "https://files.pythonhosted.org/packages/d1/d2/7c76e0c22ddfc2fcd4a3458a65f87ce074070eb1c68c07ee475cc2b6ea68/ty-0.0.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f87770d7988f470b795a2043185082fa959dbe1979a11b4bfe20f1214d37bd6e", size = 9590410, upload-time = "2025-12-19T00:13:55.759Z" }, - { url = "https://files.pythonhosted.org/packages/a5/84/de4b1fc85669faca3622071d5a3f3ec7bfb239971f368c28fae461d3398a/ty-0.0.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf68b8ea48674a289d733b4786aecc259242a2d9a920b3ec8583db18c67496a", size = 9131113, upload-time = "2025-12-19T00:14:08.593Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ff/b5bf385b6983be56a470856bbcbac1b7e816bcd765a7e9d39ab2399e387d/ty-0.0.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efc396d76a57e527393cae4ee8faf23b93be3df9e93202f39925721a7a2bb7b8", size = 9599152, upload-time = "2025-12-19T00:13:40.484Z" }, - { url = "https://files.pythonhosted.org/packages/36/d6/9880ba106f2f20d13e6a5dca5d5ca44bfb3782936ee67ff635f89a2959c0/ty-0.0.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c893b968d2f9964a4d4db9992c9ba66b01f411b1f48dffcde08622e19cd6ab97", size = 9585368, upload-time = "2025-12-19T00:14:00.994Z" }, - { url = "https://files.pythonhosted.org/packages/3f/53/503cfc18bc4c7c4e02f89dd43debc41a6e343b41eb43df658dfb493a386d/ty-0.0.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:526c925b80d68a53c165044d2370fcfc0def1f119f7b7e483ee61d24da6fb891", size = 9998412, upload-time = "2025-12-19T00:14:18.653Z" }, - { url = "https://files.pythonhosted.org/packages/1d/bd/dd2d3e29834da5add2eda0ab5b433171ce9ce9a248c364d2e237f82073d7/ty-0.0.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:857f605a7fa366b6c6e6f38abc311d0606be513c2bee8977b5c8fd4bde1a82d5", size = 10853890, upload-time = "2025-12-19T00:13:50.891Z" }, - { url = "https://files.pythonhosted.org/packages/07/fe/28ba3be1672e6b8df46e43de66a02dc076ffba7853d391a5466421886225/ty-0.0.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4cc981aa3ebdac2c233421b1e58c80b0df6a8e6e6fa8b9e69fbdfd2f82768af", size = 10587263, upload-time = "2025-12-19T00:14:21.577Z" }, - { url = "https://files.pythonhosted.org/packages/26/9c/bb598772043f686afe5bc26cb386020709c1a0bcc164bc22ad9da2b4f55d/ty-0.0.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b03b2708b0bf67c76424a860f848aebaa4772c05529170c3761bfcaea93ec199", size = 10401204, upload-time = "2025-12-19T00:13:43.453Z" }, - { url = "https://files.pythonhosted.org/packages/ac/18/71765e9d63669bf09461c3fea84a7a63232ccb0e83b84676f07b987fc217/ty-0.0.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:469890e885544beb129c21e2f8f15321f0573d094aec13da68593c5f86389ff9", size = 10129713, upload-time = "2025-12-19T00:14:13.725Z" }, - { url = "https://files.pythonhosted.org/packages/c3/2d/c03eba570aa85e9c361de5ed36d60b9ab139e93ee91057f455ab4af48e54/ty-0.0.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:abfd928d09567e12068aeca875e920def3badf1978896f474aa4b85b552703c4", size = 9586203, upload-time = "2025-12-19T00:14:03.423Z" }, - { url = "https://files.pythonhosted.org/packages/61/f1/8c3c82a8df69bd4417c77be4f895d043db26dd47bfcc90b33dc109cd0096/ty-0.0.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:44b8e94f9d64df12eae4cf8031c5ca9a4c610b57092b26ad3d68d91bcc7af122", size = 9608230, upload-time = "2025-12-19T00:13:58.252Z" }, - { url = "https://files.pythonhosted.org/packages/51/0c/d8ba3a85c089c246ef6bd49d0f0b40bc0f9209bb819e8c02ccbea5cb4d57/ty-0.0.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9d6a439813e21a06769daf858105818c385d88018929d4a56970d4ddd5cd3df2", size = 9725125, upload-time = "2025-12-19T00:14:05.996Z" }, - { url = "https://files.pythonhosted.org/packages/4d/38/e30f64ad1e40905c766576ec70cffc69163591a5842ce14652672f6ab394/ty-0.0.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c3cfcf26cfe6c828e91d7a529cc2dda37bc3b51ba06909c9be07002a6584af52", size = 10237174, upload-time = "2025-12-19T00:14:23.858Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d7/8d650aa0be8936dd3ed74e2b0655230e2904caa6077c30c16a089b523cff/ty-0.0.4-py3-none-win32.whl", hash = "sha256:58bbf70dd27af6b00dedbdebeec92d5993aa238664f96fa5c0064930f7a0d30b", size = 9188434, upload-time = "2025-12-19T00:13:45.875Z" }, - { url = "https://files.pythonhosted.org/packages/82/d7/9fc0c81cf0b0d281ac9c18bfbdb4d6bae2173503ba79e40b210ab41c2c8b/ty-0.0.4-py3-none-win_amd64.whl", hash = "sha256:7c2db0f96218f08c140bd9d3fcbb1b3c8c5c4f0c9b0a5624487f0a2bf4b76163", size = 10019313, upload-time = "2025-12-19T00:14:15.968Z" }, - { url = "https://files.pythonhosted.org/packages/5f/b8/3e3246738eed1cd695c5964a401f3b9c757d20ac21fdae06281af9f40ef6/ty-0.0.4-py3-none-win_arm64.whl", hash = "sha256:69f14fc98e4a847afa9f8c5d5234d008820dbc09c7dcdb3ac1ba16628f5132df", size = 9561857, upload-time = "2025-12-19T00:13:48.382Z" }, +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/cd/aee86c0da3240960d6b7e807f3a41c89bae741495d81ca303200b0103dc9/ty-0.0.3.tar.gz", hash = "sha256:831259e22d3855436701472d4c0da200cd45041bc677eae79415d684f541de8a", size = 4769098, upload-time = "2025-12-18T02:16:49.773Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ef/2d0d18e8fe6b673d3e1ea642f18404d7edfa9d08310f7203e8f0e7dc862e/ty-0.0.3-py3-none-linux_armv6l.whl", hash = "sha256:cd035bb75acecb78ac1ba8c4cc696f57a586e29d36e84bd691bc3b5b8362794c", size = 9763890, upload-time = "2025-12-18T02:16:56.879Z" }, + { url = "https://files.pythonhosted.org/packages/bb/67/0ae31574619a7264df8cf8e641f246992db22ac1720c2a72953aa31cbe61/ty-0.0.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7708eaf73485e263efc7ef339f8e4487d3f5885779edbeec504fd72e4521c376", size = 9558276, upload-time = "2025-12-18T02:16:45.453Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f7/3b9c033e80910972fca3783e4a52ba9cb7cd5c8b6828a87986646d64082b/ty-0.0.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3113a633f46ec789f6df675b7afc5d3ab20c247c92ae4dbb9aa5b704768c18b2", size = 9094451, upload-time = "2025-12-18T02:17:01.155Z" }, + { url = "https://files.pythonhosted.org/packages/9a/29/9a90ed6bef00142a088965100b5e0a5d11805b9729c151ca598331bbd92b/ty-0.0.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a451f3f73a04bf18e551b1ebebb79b20fac5f09740a353f7e07b5f607b217c4f", size = 9568049, upload-time = "2025-12-18T02:16:28.643Z" }, + { url = "https://files.pythonhosted.org/packages/2f/ab/8daeb12912c2de8a3154db652931f4ad0d27c555faebcaf34af08bcfd0d2/ty-0.0.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f6e926b6de0becf0452e1afad75cb71f889a4777cd14269e5447d46c01b2770", size = 9547711, upload-time = "2025-12-18T02:16:54.464Z" }, + { url = "https://files.pythonhosted.org/packages/91/54/f5c1f293f647beda717fee2448cc927ac0d05f66bebe18647680a67e1d67/ty-0.0.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160e7974150f9f359c31d5808214676d1baa05321ab5a7b29fb09f4906dbdb38", size = 9983225, upload-time = "2025-12-18T02:17:05.672Z" }, + { url = "https://files.pythonhosted.org/packages/95/34/065962cfa2e87c10db839512229940a366b8ca1caffa2254a277b1694e5a/ty-0.0.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:726576df31d4e76934ffc64f2939d4a9bc195c7427452c8c159261ad00bd1b5e", size = 10851148, upload-time = "2025-12-18T02:16:38.354Z" }, + { url = "https://files.pythonhosted.org/packages/54/27/e2a8cbfc33999eef882ccd1b816ed615293f96e96f6df60cd12f84b69ca2/ty-0.0.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5014cf4744c94d9ea7b43314199ddaf52564a80b3d006e4ba0fe982bc42f4e8b", size = 10564441, upload-time = "2025-12-18T02:17:03.584Z" }, + { url = "https://files.pythonhosted.org/packages/91/6d/dcce3e222e59477c1f2b3a012cc76428d7032248138cd5544ad7f1cda7bd/ty-0.0.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a9a51dc040f2718725f34ae6ef51fe8f8bd689e21bd3e82f4e71767034928de", size = 10358651, upload-time = "2025-12-18T02:16:26.091Z" }, + { url = "https://files.pythonhosted.org/packages/53/36/b6d0154b83a5997d607bf1238200271c17223f68aab2c778ded5424f9c1e/ty-0.0.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e6188eddd3a228c449261bb398e8621d33b92c1fc03599afdfad4388327a48", size = 10120457, upload-time = "2025-12-18T02:16:51.864Z" }, + { url = "https://files.pythonhosted.org/packages/cc/46/05dc826674ee1a451406e4c253c71700a6f707bae88b706a4c9e9bba6919/ty-0.0.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5cc55e08d5d18edf1c5051af02456bd359716f07aae0a305e4cefe7735188540", size = 9551642, upload-time = "2025-12-18T02:16:33.518Z" }, + { url = "https://files.pythonhosted.org/packages/64/8a/f90b60d103fd5ec04ecbac091a64e607e6cd37cec6e718bba17cb2022644/ty-0.0.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:34b2d589412a81d1fd6d7fe461353068496c2bf1f7113742bd6d88d1d57ec3ad", size = 9572234, upload-time = "2025-12-18T02:16:31.013Z" }, + { url = "https://files.pythonhosted.org/packages/e8/72/5d3c6d34562d019ba7f3102b2a6d0c8e9e24ef39e70f09645c36a66765b7/ty-0.0.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8a065eb2959f141fe4adafc14d57463cfa34f6cc4844a4ed56b2dce1a53a419a", size = 9701682, upload-time = "2025-12-18T02:16:41.379Z" }, + { url = "https://files.pythonhosted.org/packages/ef/44/bda434f788b320c9550a48c549e4a8c507e3d8a6ccb04ba5bd098307ba1e/ty-0.0.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e7177421f830a493f98d22f86d940b5a38788866e6062f680881f19be35ba3bb", size = 10213714, upload-time = "2025-12-18T02:16:35.648Z" }, + { url = "https://files.pythonhosted.org/packages/53/a6/b76a787938026c3d209131e5773de32cf6fc41210e0dd97874aafa20f394/ty-0.0.3-py3-none-win32.whl", hash = "sha256:e3e590bf5f33cb118a53c6d5242eedf7924d45517a5ee676c7a16be3a1389d2f", size = 9160441, upload-time = "2025-12-18T02:16:43.404Z" }, + { url = "https://files.pythonhosted.org/packages/fe/db/da60eb8252768323aee0ce69a08b95011088c003f80204b12380fe562fd2/ty-0.0.3-py3-none-win_amd64.whl", hash = "sha256:5af25b1fed8a536ce8072a9ae6a70cd2b559aa5294d43f57071fbdcd31dd2b0e", size = 10034265, upload-time = "2025-12-18T02:16:47.602Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9c/9045cebdfc394c6f8c1e73a99d3aeda1bc639aace392e8ff4d695f1fab73/ty-0.0.3-py3-none-win_arm64.whl", hash = "sha256:29078b3100351a8b37339771615f13b8e4a4ff52b344d33f774f8d1a665a0ca5", size = 9513095, upload-time = "2025-12-18T02:16:59.073Z" }, ] [[package]] diff --git a/examples/api/openai/uv.lock b/examples/api/openai/uv.lock index 9f1bd857..3781b886 100644 --- a/examples/api/openai/uv.lock +++ b/examples/api/openai/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", @@ -1663,7 +1663,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -1674,9 +1674,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] diff --git a/examples/api/slack/uv.lock b/examples/api/slack/uv.lock index 62b35728..8afe9513 100644 --- a/examples/api/slack/uv.lock +++ b/examples/api/slack/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", @@ -1715,7 +1715,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -1726,9 +1726,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] diff --git a/examples/api/telegram/README.md b/examples/api/telegram/README.md index 5940d242..dc6e560a 100644 --- a/examples/api/telegram/README.md +++ b/examples/api/telegram/README.md @@ -33,6 +33,17 @@ export AK_TELEGRAM__WEBHOOK_SECRET="your_secure_random_string" export OPENAI_API_KEY="your_openai_api_key" ``` +### Multimodal Configuration + +For image and document support, configure these environment variables: + +```bash +export AK_MULTIMODAL__ENABLED=true # Enable multimodal support (default: true) +export AK_MULTIMODAL__MAX_ATTACHMENTS=5 # Keep last N files in session (default: 5) +export AK_MULTIMODAL__ATTACHMENT_TTL=604800 # File lifetime in seconds (default: 604800 = 1 week) +``` + + ### 3. Create Configuration File Create `config.yaml`: @@ -59,24 +70,50 @@ For local development with Agent Kernel source: ## Run -Start the server: +### Option 1: Using OpenAI SDK (Recommended for beginners) ```bash uv run server.py ``` -The server will start on `http://0.0.0.0:8000` by default. +**Features:** + +- Uses OpenAI GPT-4o-mini model +- Built-in image and document analysis +- Session memory for multi-turn conversations +- Simple, straightforward setup + +### Option 2: Using Google ADK + +```bash +uv run server_adk.py +``` + +**Features:** + +- Uses Google ADK framework with OpenAI model via LiteLLM +- Same image/file analysis capabilities +- Different agent framework (for framework comparison) +- Requires `google-adk` package + +**Switching between frameworks:** + +- Both use the same Telegram bot configuration +- Same `config.yaml` file works for both +- Just run different server files to test ## Expose Local Server For local testing, expose your server using a tunnel: ### Using ngrok: + ```bash ngrok http 8000 ``` ### Using pinggy: + ```bash ssh -p443 -R0:localhost:8000 a.pinggy.io ``` @@ -86,6 +123,7 @@ Copy the HTTPS URL (e.g., `https://abc123.ngrok-free.app`) ## Configure Telegram Webhook ### Option 1: Using curl + ```bash curl -X POST "https://api.telegram.org/bot/setWebhook" \ -H "Content-Type: application/json" \ @@ -96,6 +134,7 @@ curl -X POST "https://api.telegram.org/bot/setWebhook" \ ``` ### Option 2: Using Python script + ```python import requests @@ -119,6 +158,59 @@ print(response.json()) curl "https://api.telegram.org/bot/getWebhookInfo" ``` +## Multimodal Features + +This integration supports images and documents: + +### Supported File Types + +| Type | Format | Use Case | +| ------------------- | --------------- | ---------------------- | +| **Images** | JPEG, PNG, WebP | Analysis, vision tasks | +| **Documents** | PDF, DOCX, TXT | Content analysis, Q&A | +| **Text** | Plain messages | Standard conversations | + +### Using Multimodal + +1. **Text Only** - Send a regular message + + ```bash + What is the capital of France? + ``` +2. **Image Analysis** - Send an image with caption + + ```bash + [Send image] What is in this image? + ``` +3. **Document Q&A** - Send a PDF with question + + ```bash + [Send PDF] Summarize this document + ``` +4. **Follow-up Questions** - Ask about previous context + + ```bash + [Send image first] + User: What is this? → Bot analyzes + User: Can you identify the parts? → Bot remembers image and answers + ``` + +### Session Memory + +The bot maintains conversation history: + +- Each Telegram chat has its own session +- Previous messages and context are remembered +- Images/PDFs from earlier in conversation can be referenced +- Session persists until bot restart + +### Multimodal Limitations + +- File size: Max 2MB +- Timeout: Requests take longer (30-60 seconds for large files) +- Context: Session cleared on server restart +- Supported models: OpenAI GPT-4o or compatible + ## Testing 1. Open Telegram and find your bot (by username you set in BotFather) @@ -129,9 +221,28 @@ curl "https://api.telegram.org/bot/getWebhookInfo" - Respond with AI-generated message 5. Check server logs to see the request/response flow -### Test Message Examples +### Test Multimodal Examples +```bash +# Test 1: Image Analysis +1. Send an image of a dog +2. User: "What animal is this?" +3. Expected: Bot identifies it as a dog + +# Test 2: Document Analysis +1. Send a PDF document +2. User: "Summarize the main points" +3. Expected: Bot reads and summarizes the document + +# Test 3: Follow-up Context +1. Send an image +2. User: "What is this?" → Bot responds +3. User: "What colors are dominant?" → Bot remembers the image ``` + +### Test Message Examples + +```bash /start Hello What can you help me with? @@ -142,6 +253,7 @@ Can you answer technical questions? ## Bot Commands Built-in commands: + - `/start` - Start conversation with the bot - `/help` - Show help message @@ -238,11 +350,9 @@ await self._send_message( - Ensure `AK_TELEGRAM__WEBHOOK_SECRET` matches what you set in webhook - Remove secret token temporarily for testing: `unset AK_TELEGRAM__WEBHOOK_SECRET` - ## Resources - [Telegram Bot API Documentation](https://core.telegram.org/bots/api) - [BotFather Commands](https://core.telegram.org/bots#botfather-commands) - [Telegram Bot Examples](https://core.telegram.org/bots/samples) -- [Agent Kernel Documentation](../../../docs/) - +- [Agent Kernel Documentation](../../../docs/) \ No newline at end of file diff --git a/examples/api/telegram/build.sh b/examples/api/telegram/build.sh index 23624f2a..ec8230e5 100755 --- a/examples/api/telegram/build.sh +++ b/examples/api/telegram/build.sh @@ -8,5 +8,5 @@ if [[ ${1-} != "local" ]]; then else # For local development of agentkernel, you can force reinstall from local dist uv sync --find-links ../../../ak-py/dist --all-extras - uv pip install --force-reinstall --find-links ../../../ak-py/dist agentkernel[api,openai,telegram] || true + uv pip install --force-reinstall --find-links ../../../ak-py/dist agentkernel[api,openai,adk,telegram] || true fi diff --git a/examples/api/telegram/server_adk.py b/examples/api/telegram/server_adk.py new file mode 100644 index 00000000..f32f17d4 --- /dev/null +++ b/examples/api/telegram/server_adk.py @@ -0,0 +1,27 @@ +from agentkernel.adk import GoogleADKModule +from agentkernel.api import RESTAPI +from agentkernel.telegram import AgentTelegramRequestHandler +from google.adk.agents import Agent +from google.adk.models.lite_llm import LiteLlm + +# Create your Google ADK agent with correct API +general_agent = Agent( + name="general", + model=LiteLlm(model="openai/gpt-4o-mini"), + description="Agent for general questions", + instruction=""" + You provide assistance with general queries. + Give short and clear answers suitable for Telegram messaging. + When users send images or files, analyze them and remember them for follow-up questions. + """, +) + +# Initialize module with agent (multimodal hooks auto-registered) +GoogleADKModule([general_agent]) + + +if __name__ == "__main__": + print("Starting Telegram bot with Google ADK framework...") + print("Multimodal memory: ENABLED (images/files will be remembered)") + handler = AgentTelegramRequestHandler() + RESTAPI.run([handler]) diff --git a/examples/api/telegram/uv.lock b/examples/api/telegram/uv.lock index 90875f00..cf8845b3 100644 --- a/examples/api/telegram/uv.lock +++ b/examples/api/telegram/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", @@ -1856,7 +1856,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -1867,9 +1867,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] diff --git a/examples/api/whatsapp/uv.lock b/examples/api/whatsapp/uv.lock index e7c92bb9..f0622e6c 100644 --- a/examples/api/whatsapp/uv.lock +++ b/examples/api/whatsapp/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", @@ -10,7 +10,7 @@ resolution-markers = [ [[package]] name = "agentkernel" version = "0.2.9" -source = { registry = "../../../ak-py/dist" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecated" }, { name = "pydantic" }, @@ -18,9 +18,9 @@ dependencies = [ { name = "pyyaml" }, { name = "singleton-type" }, ] -sdist = { path = "agentkernel-0.2.9.tar.gz" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a4/c3b7b1663131bd275b99bb91d77d66983f598c9e0b686f57558ab9b6e1e2/agentkernel-0.2.9.tar.gz", hash = "sha256:976ccc343039f35ab4661e97f42f935ec88627ba1763980c68f983523ef09fdc", size = 76953, upload-time = "2025-12-18T12:28:27.293Z" } wheels = [ - { path = "agentkernel-0.2.9-py3-none-any.whl" }, + { url = "https://files.pythonhosted.org/packages/e6/6b/ea9652f7d83c29fdff8322bcb4a0ac8fa6c55214f9e0334a947a86ac2d95/agentkernel-0.2.9-py3-none-any.whl", hash = "sha256:307d6299baf7f9f579ab100492ddc2a66facbb8fc3b2960937b007f40bb66efa", size = 121841, upload-time = "2025-12-18T12:28:26.002Z" }, ] [package.optional-dependencies] diff --git a/examples/aws-containerized/openai-dynamodb/uv.lock b/examples/aws-containerized/openai-dynamodb/uv.lock index d2ef137c..b785bbd1 100644 --- a/examples/aws-containerized/openai-dynamodb/uv.lock +++ b/examples/aws-containerized/openai-dynamodb/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" [[package]] @@ -527,7 +527,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -538,9 +538,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] diff --git a/examples/aws-serverless/openai/uv.lock b/examples/aws-serverless/openai/uv.lock index 403794c0..51ff881d 100644 --- a/examples/aws-serverless/openai/uv.lock +++ b/examples/aws-serverless/openai/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" [[package]] @@ -449,7 +449,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -460,9 +460,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] diff --git a/examples/cli/multi/uv.lock b/examples/cli/multi/uv.lock index 0c0cb9ae..188e94e4 100644 --- a/examples/cli/multi/uv.lock +++ b/examples/cli/multi/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.13'", @@ -2031,7 +2031,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -2042,9 +2042,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] diff --git a/examples/cli/openai-dynamic/uv.lock b/examples/cli/openai-dynamic/uv.lock index 40771948..8ed195b8 100644 --- a/examples/cli/openai-dynamic/uv.lock +++ b/examples/cli/openai-dynamic/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", @@ -1647,7 +1647,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -1658,9 +1658,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]] diff --git a/examples/cli/openai/uv.lock b/examples/cli/openai/uv.lock index f3f9af08..21e44a1d 100644 --- a/examples/cli/openai/uv.lock +++ b/examples/cli/openai/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", diff --git a/examples/containerized/openai/uv.lock b/examples/containerized/openai/uv.lock index 9f1bd857..3781b886 100644 --- a/examples/containerized/openai/uv.lock +++ b/examples/containerized/openai/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14'", @@ -1663,7 +1663,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.6.4" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -1674,9 +1674,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/36/826ce8ad497904a1becdedef80326c2fe932c754646b77405a8dc4cd49f7/openai_agents-0.6.4.tar.gz", hash = "sha256:07836865ed9c37946523d44b2d87ad375673b6558e783fa086db004a892331ec", size = 2022961, upload-time = "2025-12-19T06:42:55.356Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/0b/1bfc1f47708ce5500ad6b05ba8a0a789232ee6f5b9dd68938131c4674533/openai_agents-0.6.3.tar.gz", hash = "sha256:436479f201910cfc466893854b47d0f3acbf7b3bdafa95eedb590ed0d40393ef", size = 2016166, upload-time = "2025-12-11T18:07:47.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/f2282debf62c52241959b111d2e35105b43b2ea6ff060833734405bb4d0f/openai_agents-0.6.4-py3-none-any.whl", hash = "sha256:d14635c1fa0ee39e79b81e5cab2f22dd5024772d3dbc0770d8307fd2548b3951", size = 241982, upload-time = "2025-12-19T06:42:53.92Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/d4bf0a8403ebc7d6b0fb2b45e41d6da6996b20f1dde1debffdac1b5ccb63/openai_agents-0.6.3-py3-none-any.whl", hash = "sha256:ada8b598f4db787939a62c8a291d07cbe68dae2d635955c44a0a0300746ee84f", size = 239015, upload-time = "2025-12-11T18:07:46.275Z" }, ] [[package]]