diff --git a/examples/launch-tutorials.sh b/examples/launch-tutorials.sh index 2705d257..e873ba37 100755 --- a/examples/launch-tutorials.sh +++ b/examples/launch-tutorials.sh @@ -118,9 +118,14 @@ run_tutorial() { print_colored $GREEN "🚀 Executing: cd .. && uv run agentex agents run --manifest examples/$manifest_path" print_colored $YELLOW "💡 Press Ctrl+C to stop the tutorial" echo "" - + # Run the tutorial directly (need to go to parent dir where uv project is) - (cd .. && uv run agentex agents run --manifest "examples/$manifest_path") + # Load .env file if it exists and pass variables to the subshell + if [[ -f "../.env" ]]; then + (cd .. && set -a && source .env && set +a && uv run agentex agents run --manifest "examples/$manifest_path") + else + (cd .. && uv run agentex agents run --manifest "examples/$manifest_path") + fi local exit_code=$? if [[ $exit_code -eq 0 ]]; then diff --git a/examples/tutorials/00_sync/000_hello_acp/dev.ipynb b/examples/tutorials/00_sync/000_hello_acp/dev.ipynb index 6cadb5a8..a50a29f3 100644 --- a/examples/tutorials/00_sync/000_hello_acp/dev.ipynb +++ b/examples/tutorials/00_sync/000_hello_acp/dev.ipynb @@ -93,9 +93,8 @@ "outputs": [], "source": [ "# Test streaming response\n", - "from agentex.types.task_message_update import StreamTaskMessageDelta, StreamTaskMessageFull\n", "from agentex.types.text_delta import TextDelta\n", - "\n", + "from agentex.types.task_message_update import StreamTaskMessageFull, StreamTaskMessageDelta\n", "\n", "# The result object of message/send will be a TaskMessageUpdate which is a union of the following types:\n", "# - StreamTaskMessageStart: \n", diff --git a/examples/tutorials/00_sync/000_hello_acp/project/acp.py b/examples/tutorials/00_sync/000_hello_acp/project/acp.py index 209dee4b..6e2b1a2a 100644 --- a/examples/tutorials/00_sync/000_hello_acp/project/acp.py +++ b/examples/tutorials/00_sync/000_hello_acp/project/acp.py @@ -1,11 +1,11 @@ -from typing import AsyncGenerator, Union -from agentex.lib.sdk.fastacp.fastacp import FastACP -from agentex.lib.types.acp import SendMessageParams +from typing import Union, AsyncGenerator -from agentex.types.task_message_update import TaskMessageUpdate +from agentex.lib.types.acp import SendMessageParams +from agentex.lib.utils.logging import make_logger from agentex.types.task_message import TaskMessageContent +from agentex.lib.sdk.fastacp.fastacp import FastACP +from agentex.types.task_message_update import TaskMessageUpdate from agentex.types.task_message_content import TextContent -from agentex.lib.utils.logging import make_logger logger = make_logger(__name__) @@ -21,8 +21,15 @@ async def handle_message_send( params: SendMessageParams ) -> Union[TaskMessageContent, AsyncGenerator[TaskMessageUpdate, None]]: """Default message handler with streaming support""" + # Extract content safely from the message + message_text = "" + if hasattr(params.content, 'content'): + content_val = getattr(params.content, 'content', '') + if isinstance(content_val, str): + message_text = content_val + return TextContent( author="agent", - content=f"Hello! I've received your message. Here's a generic response, but in future tutorials we'll see how you can get me to intelligently respond to your message. This is what I heard you say: {params.content.content}", + content=f"Hello! I've received your message. Here's a generic response, but in future tutorials we'll see how you can get me to intelligently respond to your message. This is what I heard you say: {message_text}", ) diff --git a/examples/tutorials/00_sync/010_multiturn/dev.ipynb b/examples/tutorials/00_sync/010_multiturn/dev.ipynb index c9f70cf3..d82cf577 100644 --- a/examples/tutorials/00_sync/010_multiturn/dev.ipynb +++ b/examples/tutorials/00_sync/010_multiturn/dev.ipynb @@ -93,9 +93,8 @@ "outputs": [], "source": [ "# Test streaming response\n", - "from agentex.types.task_message_update import StreamTaskMessageDelta, StreamTaskMessageFull\n", "from agentex.types.text_delta import TextDelta\n", - "\n", + "from agentex.types.task_message_update import StreamTaskMessageFull, StreamTaskMessageDelta\n", "\n", "# The result object of message/send will be a TaskMessageUpdate which is a union of the following types:\n", "# - StreamTaskMessageStart: \n", diff --git a/examples/tutorials/00_sync/010_multiturn/project/acp.py b/examples/tutorials/00_sync/010_multiturn/project/acp.py index f42a3b08..d8a38040 100644 --- a/examples/tutorials/00_sync/010_multiturn/project/acp.py +++ b/examples/tutorials/00_sync/010_multiturn/project/acp.py @@ -1,14 +1,14 @@ import os -from typing import AsyncGenerator, Union +from typing import Union, AsyncGenerator from agentex.lib import adk -from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.lib.types.acp import SendMessageParams -from agentex.lib.types.llm_messages import AssistantMessage, LLMConfig, SystemMessage, UserMessage -from agentex.types.task_message_update import TaskMessageUpdate from agentex.types.task_message import TaskMessageContent -from agentex.types.task_message_content import TextContent from agentex.lib.utils.model_utils import BaseModel +from agentex.lib.types.llm_messages import LLMConfig, UserMessage, SystemMessage, AssistantMessage +from agentex.lib.sdk.fastacp.fastacp import FastACP +from agentex.types.task_message_update import TaskMessageUpdate +from agentex.types.task_message_content import TextContent # Create an ACP server acp = FastACP.create( @@ -33,11 +33,11 @@ async def handle_message_send( # 0. Validate the message. ######################################################### - if params.content.type != "text": - raise ValueError(f"Expected text message, got {params.content.type}") + if not hasattr(params.content, 'type') or params.content.type != "text": + raise ValueError(f"Expected text message, got {getattr(params.content, 'type', 'unknown')}") - if params.content.author != "user": - raise ValueError(f"Expected user message, got {params.content.author}") + if not hasattr(params.content, 'author') or params.content.author != "user": + raise ValueError(f"Expected user message, got {getattr(params.content, 'author', 'unknown')}") if not os.environ.get("OPENAI_API_KEY"): return TextContent( @@ -74,9 +74,9 @@ async def handle_message_send( llm_messages = [ SystemMessage(content=state.system_prompt), *[ - UserMessage(content=message.content.content) if message.content.author == "user" else AssistantMessage(content=message.content.content) + UserMessage(content=getattr(message.content, 'content', '')) if getattr(message.content, 'author', None) == "user" else AssistantMessage(content=getattr(message.content, 'content', '')) for message in task_messages - if message.content.type == "text" + if getattr(message.content, 'type', None) == "text" ] ] diff --git a/examples/tutorials/00_sync/020_streaming/dev.ipynb b/examples/tutorials/00_sync/020_streaming/dev.ipynb index 4cd5fb8d..b4e517c3 100644 --- a/examples/tutorials/00_sync/020_streaming/dev.ipynb +++ b/examples/tutorials/00_sync/020_streaming/dev.ipynb @@ -93,9 +93,8 @@ "outputs": [], "source": [ "# Test streaming response\n", - "from agentex.types.task_message_update import StreamTaskMessageDelta, StreamTaskMessageFull\n", "from agentex.types.text_delta import TextDelta\n", - "\n", + "from agentex.types.task_message_update import StreamTaskMessageFull, StreamTaskMessageDelta\n", "\n", "# The result object of message/send will be a TaskMessageUpdate which is a union of the following types:\n", "# - StreamTaskMessageStart: \n", diff --git a/examples/tutorials/00_sync/020_streaming/project/acp.py b/examples/tutorials/00_sync/020_streaming/project/acp.py index e99cd724..71d1364f 100644 --- a/examples/tutorials/00_sync/020_streaming/project/acp.py +++ b/examples/tutorials/00_sync/020_streaming/project/acp.py @@ -1,14 +1,19 @@ import os -from typing import AsyncGenerator, Union +from typing import Union, AsyncGenerator from agentex.lib import adk -from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.lib.types.acp import SendMessageParams -from agentex.lib.types.llm_messages import AssistantMessage, LLMConfig, SystemMessage, UserMessage -from agentex.types.task_message_update import StreamTaskMessageDelta, StreamTaskMessageDone, StreamTaskMessageFull, TaskMessageUpdate -from agentex.types.task_message_delta import TextDelta from agentex.lib.utils.model_utils import BaseModel -from agentex.types.task_message_content import TaskMessageContent, TextContent +from agentex.lib.types.llm_messages import LLMConfig, UserMessage, SystemMessage, AssistantMessage +from agentex.lib.sdk.fastacp.fastacp import FastACP +from agentex.types.task_message_delta import TextDelta +from agentex.types.task_message_update import ( + TaskMessageUpdate, + StreamTaskMessageDone, + StreamTaskMessageFull, + StreamTaskMessageDelta, +) +from agentex.types.task_message_content import TextContent, TaskMessageContent # Create an ACP server acp = FastACP.create( @@ -36,11 +41,11 @@ async def handle_message_send( if not params.content: return - if params.content.type != "text": - raise ValueError(f"Expected text message, got {params.content.type}") + if not hasattr(params.content, 'type') or params.content.type != "text": + raise ValueError(f"Expected text message, got {getattr(params.content, 'type', 'unknown')}") - if params.content.author != "user": - raise ValueError(f"Expected user message, got {params.content.author}") + if not hasattr(params.content, 'author') or params.content.author != "user": + raise ValueError(f"Expected user message, got {getattr(params.content, 'author', 'unknown')}") if not os.environ.get("OPENAI_API_KEY"): yield StreamTaskMessageFull( @@ -67,9 +72,9 @@ async def handle_message_send( llm_messages = [ SystemMessage(content=state.system_prompt), *[ - UserMessage(content=message.content.content) if message.content.author == "user" else AssistantMessage(content=message.content.content) + UserMessage(content=getattr(message.content, 'content', '')) if getattr(message.content, 'author', None) == "user" else AssistantMessage(content=getattr(message.content, 'content', '')) for message in task_messages - if message.content and message.content.type == "text" + if message.content and getattr(message.content, 'type', None) == "text" ] ] @@ -92,7 +97,7 @@ async def handle_message_send( yield StreamTaskMessageDelta( type="delta", index=message_index, - delta=TextDelta(text_delta=chunk.choices[0].delta.content or ""), + delta=TextDelta(type="text", text_delta=chunk.choices[0].delta.content or ""), ) yield StreamTaskMessageDone( diff --git a/examples/tutorials/10_agentic/00_base/000_hello_acp/project/acp.py b/examples/tutorials/10_agentic/00_base/000_hello_acp/project/acp.py index 069dc0b3..f41d7b31 100644 --- a/examples/tutorials/10_agentic/00_base/000_hello_acp/project/acp.py +++ b/examples/tutorials/10_agentic/00_base/000_hello_acp/project/acp.py @@ -1,11 +1,11 @@ import json + from agentex.lib import adk -from agentex.lib.sdk.fastacp.fastacp import FastACP +from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams from agentex.lib.types.fastacp import AgenticACPConfig -from agentex.lib.types.acp import CancelTaskParams, CreateTaskParams, SendEventParams - -from agentex.types.text_content import TextContent from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent +from agentex.lib.sdk.fastacp.fastacp import FastACP logger = make_logger(__name__) diff --git a/examples/tutorials/10_agentic/00_base/010_multiturn/project/acp.py b/examples/tutorials/10_agentic/00_base/010_multiturn/project/acp.py index e6d6f8cc..baf797e4 100644 --- a/examples/tutorials/10_agentic/00_base/010_multiturn/project/acp.py +++ b/examples/tutorials/10_agentic/00_base/010_multiturn/project/acp.py @@ -2,23 +2,23 @@ from typing import List from agentex.lib import adk -from agentex.lib.core.tracing.tracing_processor_manager import ( - add_tracing_processor_config, -) -from agentex.lib.sdk.fastacp.fastacp import FastACP -from agentex.lib.types.acp import CancelTaskParams, CreateTaskParams, SendEventParams +from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams from agentex.lib.types.fastacp import AgenticACPConfig +from agentex.lib.types.tracing import SGPTracingProcessorConfig +from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent +from agentex.lib.utils.model_utils import BaseModel from agentex.lib.types.llm_messages import ( - AssistantMessage, - LLMConfig, Message, - SystemMessage, + LLMConfig, UserMessage, + SystemMessage, + AssistantMessage, +) +from agentex.lib.sdk.fastacp.fastacp import FastACP +from agentex.lib.core.tracing.tracing_processor_manager import ( + add_tracing_processor_config, ) -from agentex.lib.types.tracing import SGPTracingProcessorConfig -from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.model_utils import BaseModel -from agentex.types.text_content import TextContent logger = make_logger(__name__) @@ -97,13 +97,21 @@ async def handle_event_send(params: SendEventParams): ######################################################### task_state = await adk.state.get_by_task_and_agent(task_id=params.task.id, agent_id=params.agent.id) + if not task_state: + raise ValueError("Task state not found - ensure task was properly initialized") state = StateModel.model_validate(task_state.state) ######################################################### # 6. (👋) Add the new user message to the message history ######################################################### - state.messages.append(UserMessage(content=params.event.content.content)) + # Safely extract content from the event + content_text = "" + if hasattr(params.event.content, 'content'): + content_val = getattr(params.event.content, 'content', '') + if isinstance(content_val, str): + content_text = content_val + state.messages.append(UserMessage(content=content_text)) ######################################################### # 7. (👋) Call an LLM to respond to the user's message @@ -114,7 +122,10 @@ async def handle_event_send(params: SendEventParams): llm_config=LLMConfig(model="gpt-4o-mini", messages=state.messages), trace_id=params.task.id, ) - state.messages.append(AssistantMessage(content=chat_completion.choices[0].message.content)) + response_content = "" + if chat_completion.choices[0].message: + response_content = chat_completion.choices[0].message.content or "" + state.messages.append(AssistantMessage(content=response_content)) ######################################################### # 8. (👋) Send agent response to client diff --git a/examples/tutorials/10_agentic/00_base/020_streaming/project/acp.py b/examples/tutorials/10_agentic/00_base/020_streaming/project/acp.py index 4e6c698b..ea9f6998 100644 --- a/examples/tutorials/10_agentic/00_base/020_streaming/project/acp.py +++ b/examples/tutorials/10_agentic/00_base/020_streaming/project/acp.py @@ -2,13 +2,13 @@ from typing import List from agentex.lib import adk -from agentex.lib.sdk.fastacp.fastacp import FastACP -from agentex.lib.types.acp import CancelTaskParams, CreateTaskParams, SendEventParams +from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams from agentex.lib.types.fastacp import AgenticACPConfig -from agentex.lib.types.llm_messages import AssistantMessage, LLMConfig, Message, SystemMessage, UserMessage from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.model_utils import BaseModel from agentex.types.text_content import TextContent +from agentex.lib.utils.model_utils import BaseModel +from agentex.lib.types.llm_messages import Message, LLMConfig, UserMessage, SystemMessage, AssistantMessage +from agentex.lib.sdk.fastacp.fastacp import FastACP logger = make_logger(__name__) @@ -82,13 +82,21 @@ async def handle_event_send(params: SendEventParams): ######################################################### task_state = await adk.state.get_by_task_and_agent(task_id=params.task.id, agent_id=params.agent.id) + if not task_state: + raise ValueError("Task state not found - ensure task was properly initialized") state = StateModel.model_validate(task_state.state) ######################################################### # 6. Add the new user message to the message history ######################################################### - state.messages.append(UserMessage(content=params.event.content.content)) + # Safely extract content from the event + content_text = "" + if hasattr(params.event.content, 'content'): + content_val = getattr(params.event.content, 'content', '') + if isinstance(content_val, str): + content_text = content_val + state.messages.append(UserMessage(content=content_text)) ######################################################### # 7. (👋) Call an LLM to respond to the user's message @@ -109,7 +117,13 @@ async def handle_event_send(params: SendEventParams): trace_id=params.task.id, ) - state.messages.append(AssistantMessage(content=task_message.content.content)) + # Safely extract content from the task message + response_text = "" + if task_message.content and hasattr(task_message.content, 'content'): # type: ignore[union-attr] + content_val = getattr(task_message.content, 'content', '') # type: ignore[union-attr] + if isinstance(content_val, str): + response_text = content_val + state.messages.append(AssistantMessage(content=response_text)) ######################################################### # 8. Store the messages in the task state for the next turn diff --git a/examples/tutorials/10_agentic/00_base/030_tracing/project/acp.py b/examples/tutorials/10_agentic/00_base/030_tracing/project/acp.py index 04e626cf..6eef4af0 100644 --- a/examples/tutorials/10_agentic/00_base/030_tracing/project/acp.py +++ b/examples/tutorials/10_agentic/00_base/030_tracing/project/acp.py @@ -2,13 +2,13 @@ from typing import List from agentex.lib import adk -from agentex.lib.sdk.fastacp.fastacp import FastACP -from agentex.lib.types.acp import CancelTaskParams, CreateTaskParams, SendEventParams +from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams from agentex.lib.types.fastacp import AgenticACPConfig -from agentex.lib.types.llm_messages import AssistantMessage, LLMConfig, Message, SystemMessage, UserMessage from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.model_utils import BaseModel from agentex.types.text_content import TextContent +from agentex.lib.utils.model_utils import BaseModel +from agentex.lib.types.llm_messages import Message, LLMConfig, UserMessage, SystemMessage, AssistantMessage +from agentex.lib.sdk.fastacp.fastacp import FastACP logger = make_logger(__name__) @@ -62,11 +62,19 @@ async def handle_event_send(params: SendEventParams): ######################################################### task_state = await adk.state.get_by_task_and_agent(task_id=params.task.id, agent_id=params.agent.id) + if not task_state: + raise ValueError("Task state not found - ensure task was properly initialized") state = StateModel.model_validate(task_state.state) state.turn_number += 1 # Add the new user message to the message history - state.messages.append(UserMessage(content=params.event.content.content)) + # Safely extract content from the event + content_text = "" + if hasattr(params.event.content, 'content'): + content_val = getattr(params.event.content, 'content', '') + if isinstance(content_val, str): + content_text = content_val + state.messages.append(UserMessage(content=content_text)) ######################################################### # 4. (👋) Create a tracing span. @@ -122,7 +130,13 @@ async def handle_event_send(params: SendEventParams): parent_span_id=span.id if span else None, ) - state.messages.append(AssistantMessage(content=task_message.content.content)) + # Safely extract content from the task message + response_text = "" + if task_message.content and hasattr(task_message.content, 'content'): # type: ignore[union-attr] + content_val = getattr(task_message.content, 'content', '') # type: ignore[union-attr] + if isinstance(content_val, str): + response_text = content_val + state.messages.append(AssistantMessage(content=response_text)) ######################################################### # 8. Store the messages in the task state for the next turn @@ -144,7 +158,8 @@ async def handle_event_send(params: SendEventParams): # (👋) You can store an arbitrary pydantic model or dictionary in the span output. The idea of a span is that it easily allows you to compare the input and output of a span to see what the wrapped function did. # In this case, the state is comprehensive and expressive, so we just store the change in state that occured. - span.output = state + if span: + span.output = state # type: ignore[misc] @acp.on_task_cancel async def handle_task_cancel(params: CancelTaskParams): diff --git a/examples/tutorials/10_agentic/00_base/040_other_sdks/project/acp.py b/examples/tutorials/10_agentic/00_base/040_other_sdks/project/acp.py index 39d6d23a..3ca3b215 100644 --- a/examples/tutorials/10_agentic/00_base/040_other_sdks/project/acp.py +++ b/examples/tutorials/10_agentic/00_base/040_other_sdks/project/acp.py @@ -1,33 +1,33 @@ import os +import json from typing import Dict, List, Optional from contextlib import AsyncExitStack, asynccontextmanager -import json - -from agentex.lib import adk -from agentex.lib.core.services.adk.streaming import StreamingTaskMessageContext -from agentex.lib.sdk.fastacp.fastacp import FastACP -from agentex.lib.types.acp import CancelTaskParams, CreateTaskParams, SendEventParams -from agentex.lib.types.fastacp import AgenticACPConfig -from agentex.types.task_message_update import ( - StreamTaskMessageDelta, - StreamTaskMessageFull, -) -from agentex.types.task_message_delta import TextDelta -from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.model_utils import BaseModel -from agentex.types.text_content import TextContent -from agentex.types.task_message_content import ToolRequestContent, ToolResponseContent +from mcp import StdioServerParameters from agents import Agent, Runner +from pydantic import BaseModel from agents.mcp import MCPServerStdio -from mcp import StdioServerParameters from openai.types.responses import ( ResponseCompletedEvent, + ResponseTextDeltaEvent, ResponseFunctionToolCall, ResponseOutputItemDoneEvent, - ResponseTextDeltaEvent, ) -from pydantic import BaseModel + +from agentex.lib import adk +from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams +from agentex.lib.types.fastacp import AgenticACPConfig +from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent +from agentex.lib.utils.model_utils import BaseModel +from agentex.lib.sdk.fastacp.fastacp import FastACP +from agentex.types.task_message_delta import TextDelta +from agentex.types.task_message_update import ( + StreamTaskMessageFull, + StreamTaskMessageDelta, +) +from agentex.types.task_message_content import ToolRequestContent, ToolResponseContent +from agentex.lib.core.services.adk.streaming import StreamingTaskMessageContext logger = make_logger(__name__) @@ -86,6 +86,8 @@ async def handle_event_send(params: SendEventParams): # Retrieve the task state. Each event is handled as a new turn, so we need to get the state for the current turn. task_state = await adk.state.get_by_task_and_agent(task_id=params.task.id, agent_id=params.agent.id) + if not task_state: + raise ValueError("Task state not found - ensure task was properly initialized") state = StateModel.model_validate(task_state.state) state.turn_number += 1 @@ -149,7 +151,8 @@ async def handle_event_send(params: SendEventParams): ) # Set the span output to the state for the next turn - span.output = state + if span: + span.output = state @acp.on_task_cancel async def handle_task_cancel(params: CancelTaskParams): diff --git a/examples/tutorials/10_agentic/00_base/080_batch_events/dev.ipynb b/examples/tutorials/10_agentic/00_base/080_batch_events/dev.ipynb index 19a40f71..b0ad98d9 100644 --- a/examples/tutorials/10_agentic/00_base/080_batch_events/dev.ipynb +++ b/examples/tutorials/10_agentic/00_base/080_batch_events/dev.ipynb @@ -51,8 +51,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Send an event to the agent\n", "from agentex.types import Event\n", + "from agentex.lib.utils.dev_tools import subscribe_to_async_task_messages\n", "from agentex.types.agent_rpc_params import ParamsSendEventRequest\n", "\n", "# The response is expected to be a list of TaskMessage objects, which is a union of the following types:\n", @@ -91,10 +91,7 @@ "for event_message in concurrent_event_messages:\n", " rpc_response = client.agents.send_event(\n", " agent_name=AGENT_NAME,\n", - " params={\n", - " \"content\": {\"type\": \"text\", \"author\": \"user\", \"content\": \"Hello tell me the latest news about AI and AI startups\"},\n", - " \"task_id\": task.id,\n", - " }\n", + " params=event_message\n", " )\n", "\n", " event = rpc_response.result\n", @@ -153,4 +150,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/examples/tutorials/10_agentic/00_base/080_batch_events/project/acp.py b/examples/tutorials/10_agentic/00_base/080_batch_events/project/acp.py index eb72d68d..4b00e352 100644 --- a/examples/tutorials/10_agentic/00_base/080_batch_events/project/acp.py +++ b/examples/tutorials/10_agentic/00_base/080_batch_events/project/acp.py @@ -7,11 +7,11 @@ from enum import Enum from agentex.lib import adk -from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams from agentex.lib.types.fastacp import AgenticACPConfig from agentex.lib.utils.logging import make_logger from agentex.types.text_content import TextContent +from agentex.lib.sdk.fastacp.fastacp import FastACP logger = make_logger(__name__) @@ -199,19 +199,17 @@ async def handle_task_event_send(params: SendEventParams) -> None: logger.error(f"❌ Task cancelled: {e}") reset_to_ready = False finally: - if not reset_to_ready: - return - - # Always set status back to READY when done processing - try: - await adk.agent_task_tracker.update( - tracker_id=tracker.id, - status=Status.READY.value, - status_reason="Completed event processing - ready for new events" - ) - logger.info(f"🟢 Set status back to READY - agent available for new events") - except Exception as e: - logger.error(f"❌ Error setting status back to READY: {e}") + if reset_to_ready: + # Always set status back to READY when done processing + try: + await adk.agent_task_tracker.update( + tracker_id=tracker.id, + status=Status.READY.value, + status_reason="Completed event processing - ready for new events" + ) + logger.info(f"🟢 Set status back to READY - agent available for new events") + except Exception as e: + logger.error(f"❌ Error setting status back to READY: {e}") @acp.on_task_cancel diff --git a/examples/tutorials/10_agentic/00_base/080_batch_events/test_batch_events.py b/examples/tutorials/10_agentic/00_base/080_batch_events/test_batch_events.py index 216d7c16..b7a5397d 100644 --- a/examples/tutorials/10_agentic/00_base/080_batch_events/test_batch_events.py +++ b/examples/tutorials/10_agentic/00_base/080_batch_events/test_batch_events.py @@ -3,11 +3,12 @@ Simple script to test agent RPC endpoints using the actual schemas. """ -import httpx import json import uuid import asyncio +import httpx + # Configuration BASE_URL = "http://localhost:5003" # AGENT_ID = "b4f32d71-ff69-4ac9-84d1-eb2937fea0c7" diff --git a/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/creator.py b/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/creator.py index ab8c587d..d81755f4 100644 --- a/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/creator.py +++ b/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/creator.py @@ -1,25 +1,24 @@ # Creator Agent - Generates and revises content based on requests and feedback -import json import os +import sys +import json from typing import List +from pathlib import Path from agentex.lib import adk -from agentex.lib.sdk.fastacp.fastacp import FastACP +from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams from agentex.lib.types.fastacp import AgenticACPConfig -from agentex.lib.types.acp import CancelTaskParams, CreateTaskParams, SendEventParams +from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent from agentex.lib.types.llm_messages import ( - AssistantMessage, - LLMConfig, Message, - SystemMessage, + LLMConfig, UserMessage, + SystemMessage, + AssistantMessage, ) -from agentex.types.text_content import TextContent -from agentex.lib.utils.logging import make_logger - -import sys -from pathlib import Path +from agentex.lib.sdk.fastacp.fastacp import FastACP # Add the current directory to the Python path to enable imports current_dir = Path(__file__).parent @@ -27,6 +26,7 @@ sys.path.append(str(current_dir)) from models import CreatorRequest, CreatorResponse + from agentex.lib.utils.model_utils import BaseModel logger = make_logger(__name__) diff --git a/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/critic.py b/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/critic.py index d73c5788..d49fcb3b 100644 --- a/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/critic.py +++ b/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/critic.py @@ -1,25 +1,24 @@ # Critic Agent - Reviews content drafts against specified rules and provides feedback -import json import os +import sys +import json from typing import List +from pathlib import Path from agentex.lib import adk -from agentex.lib.sdk.fastacp.fastacp import FastACP +from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams from agentex.lib.types.fastacp import AgenticACPConfig -from agentex.lib.types.acp import CancelTaskParams, CreateTaskParams, SendEventParams +from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent from agentex.lib.types.llm_messages import ( - AssistantMessage, - LLMConfig, Message, - SystemMessage, + LLMConfig, UserMessage, + SystemMessage, + AssistantMessage, ) -from agentex.types.text_content import TextContent -from agentex.lib.utils.logging import make_logger - -import sys -from pathlib import Path +from agentex.lib.sdk.fastacp.fastacp import FastACP # Add the current directory to the Python path to enable imports current_dir = Path(__file__).parent @@ -27,6 +26,7 @@ sys.path.append(str(current_dir)) from models import CriticRequest, CriticResponse + from agentex.lib.utils.model_utils import BaseModel logger = make_logger(__name__) diff --git a/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/formatter.py b/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/formatter.py index f779d357..9fa2cb39 100644 --- a/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/formatter.py +++ b/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/formatter.py @@ -1,25 +1,24 @@ # Formatter Agent - Converts approved content to various target formats -import json import os +import sys +import json from typing import List +from pathlib import Path from agentex.lib import adk -from agentex.lib.sdk.fastacp.fastacp import FastACP +from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams from agentex.lib.types.fastacp import AgenticACPConfig -from agentex.lib.types.acp import CancelTaskParams, CreateTaskParams, SendEventParams +from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent from agentex.lib.types.llm_messages import ( - AssistantMessage, - LLMConfig, Message, - SystemMessage, + LLMConfig, UserMessage, + SystemMessage, + AssistantMessage, ) -from agentex.types.text_content import TextContent -from agentex.lib.utils.logging import make_logger - -import sys -from pathlib import Path +from agentex.lib.sdk.fastacp.fastacp import FastACP # Add the current directory to the Python path to enable imports current_dir = Path(__file__).parent @@ -27,6 +26,7 @@ sys.path.append(str(current_dir)) from models import FormatterRequest, FormatterResponse + from agentex.lib.utils.model_utils import BaseModel logger = make_logger(__name__) diff --git a/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/models.py b/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/models.py index 974489f1..e9aef6d7 100644 --- a/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/models.py +++ b/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/models.py @@ -3,9 +3,9 @@ This provides type safety and clear documentation of expected data formats. """ -from typing import List, Optional, Literal -from pydantic import BaseModel, Field +from typing import List, Literal, Optional +from pydantic import Field, BaseModel # Request Models diff --git a/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/orchestrator.py b/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/orchestrator.py index e71fa7d3..5000f8c3 100644 --- a/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/orchestrator.py +++ b/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/orchestrator.py @@ -1,28 +1,23 @@ # Orchestrator Agent - Coordinates the multi-agent content creation workflow +import sys import json +from pathlib import Path from agentex.lib import adk -from agentex.lib.sdk.fastacp.fastacp import FastACP +from agentex.lib.types.acp import SendEventParams, CancelTaskParams, CreateTaskParams from agentex.lib.types.fastacp import AgenticACPConfig -from agentex.lib.types.acp import CancelTaskParams, CreateTaskParams, SendEventParams -from agentex.types.text_content import TextContent from agentex.lib.utils.logging import make_logger - -import sys -from pathlib import Path +from agentex.types.text_content import TextContent +from agentex.lib.sdk.fastacp.fastacp import FastACP # Add the current directory to the Python path to enable imports current_dir = Path(__file__).parent if str(current_dir) not in sys.path: sys.path.append(str(current_dir)) -from state_machines.content_workflow import ( - ContentWorkflowStateMachine, - WorkflowData, - ContentWorkflowState -) -from models import OrchestratorRequest, CreatorResponse, CriticResponse, FormatterResponse +from models import CriticResponse, CreatorResponse, FormatterResponse, OrchestratorRequest +from state_machines.content_workflow import WorkflowData, ContentWorkflowState, ContentWorkflowStateMachine logger = make_logger(__name__) diff --git a/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/state_machines/content_workflow.py b/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/state_machines/content_workflow.py index fdc56382..1a042ef2 100644 --- a/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/state_machines/content_workflow.py +++ b/examples/tutorials/10_agentic/00_base/090_multi_agent_non_temporal/project/state_machines/content_workflow.py @@ -1,15 +1,16 @@ +# ruff: noqa: ARG002 import json import asyncio from enum import Enum from typing import Optional -from agentex.lib.utils.model_utils import BaseModel from agentex.lib import adk -from agentex.lib.sdk.state_machine.state_machine import StateMachine +from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent +from agentex.lib.utils.model_utils import BaseModel from agentex.lib.sdk.state_machine.state import State +from agentex.lib.sdk.state_machine.state_machine import StateMachine from agentex.lib.sdk.state_machine.state_workflow import StateWorkflow -from agentex.types.text_content import TextContent -from agentex.lib.utils.logging import make_logger logger = make_logger(__name__) diff --git a/examples/tutorials/10_agentic/10_temporal/000_hello_acp/project/acp.py b/examples/tutorials/10_agentic/10_temporal/000_hello_acp/project/acp.py index d3b8e52d..2e069423 100644 --- a/examples/tutorials/10_agentic/10_temporal/000_hello_acp/project/acp.py +++ b/examples/tutorials/10_agentic/10_temporal/000_hello_acp/project/acp.py @@ -1,8 +1,7 @@ import os -from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.lib.types.fastacp import TemporalACPConfig - +from agentex.lib.sdk.fastacp.fastacp import FastACP # Create the ACP server acp = FastACP.create( diff --git a/examples/tutorials/10_agentic/10_temporal/000_hello_acp/project/run_worker.py b/examples/tutorials/10_agentic/10_temporal/000_hello_acp/project/run_worker.py index 417ee95b..7db2fcdc 100644 --- a/examples/tutorials/10_agentic/10_temporal/000_hello_acp/project/run_worker.py +++ b/examples/tutorials/10_agentic/10_temporal/000_hello_acp/project/run_worker.py @@ -1,14 +1,11 @@ import asyncio -from agentex.lib.core.temporal.activities import get_all_activities -from agentex.lib.core.temporal.workers.worker import AgentexWorker -from agentex.lib.utils.logging import make_logger +from project.workflow import At000HelloAcpWorkflow from agentex.lib.utils.debug import setup_debug_if_enabled +from agentex.lib.utils.logging import make_logger from agentex.lib.environment_variables import EnvironmentVariables - -from project.workflow import At000HelloAcpWorkflow - - +from agentex.lib.core.temporal.activities import get_all_activities +from agentex.lib.core.temporal.workers.worker import AgentexWorker environment_variables = EnvironmentVariables.refresh() diff --git a/examples/tutorials/10_agentic/10_temporal/000_hello_acp/project/workflow.py b/examples/tutorials/10_agentic/10_temporal/000_hello_acp/project/workflow.py index 556a0afb..9d5406bb 100644 --- a/examples/tutorials/10_agentic/10_temporal/000_hello_acp/project/workflow.py +++ b/examples/tutorials/10_agentic/10_temporal/000_hello_acp/project/workflow.py @@ -4,12 +4,12 @@ from temporalio import workflow from agentex.lib import adk -from agentex.lib.types.acp import CreateTaskParams, SendEventParams -from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow -from agentex.lib.core.temporal.types.workflow import SignalName +from agentex.lib.types.acp import SendEventParams, CreateTaskParams from agentex.lib.utils.logging import make_logger from agentex.types.text_content import TextContent from agentex.lib.environment_variables import EnvironmentVariables +from agentex.lib.core.temporal.types.workflow import SignalName +from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow environment_variables = EnvironmentVariables.refresh() diff --git a/examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/acp.py b/examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/acp.py index d3b8e52d..2e069423 100644 --- a/examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/acp.py +++ b/examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/acp.py @@ -1,8 +1,7 @@ import os -from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.lib.types.fastacp import TemporalACPConfig - +from agentex.lib.sdk.fastacp.fastacp import FastACP # Create the ACP server acp = FastACP.create( diff --git a/examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/run_worker.py b/examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/run_worker.py index 00a3425b..31a3c98c 100644 --- a/examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/run_worker.py +++ b/examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/run_worker.py @@ -1,14 +1,11 @@ import asyncio -from agentex.lib.core.temporal.activities import get_all_activities -from agentex.lib.core.temporal.workers.worker import AgentexWorker -from agentex.lib.utils.logging import make_logger +from project.workflow import At010AgentChatWorkflow from agentex.lib.utils.debug import setup_debug_if_enabled +from agentex.lib.utils.logging import make_logger from agentex.lib.environment_variables import EnvironmentVariables - -from project.workflow import At010AgentChatWorkflow - - +from agentex.lib.core.temporal.activities import get_all_activities +from agentex.lib.core.temporal.workers.worker import AgentexWorker environment_variables = EnvironmentVariables.refresh() diff --git a/examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/workflow.py b/examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/workflow.py index 2ef81323..dc2e7afc 100644 --- a/examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/workflow.py +++ b/examples/tutorials/10_agentic/10_temporal/010_agent_chat/project/workflow.py @@ -1,25 +1,25 @@ import os import json -from typing import Dict, List, override, Any -from dotenv import load_dotenv +from typing import Any, Dict, List, override -from agentex.lib.utils.model_utils import BaseModel from mcp import StdioServerParameters -from temporalio import workflow from agents import ModelSettings, RunContextWrapper +from dotenv import load_dotenv +from temporalio import workflow from openai.types.shared import Reasoning from agentex.lib import adk -from agentex.lib.types.acp import CreateTaskParams, SendEventParams -from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow -from agentex.lib.core.temporal.types.workflow import SignalName +from agentex.lib.types.acp import SendEventParams, CreateTaskParams +from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent +from agentex.lib.utils.model_utils import BaseModel +from agentex.lib.environment_variables import EnvironmentVariables +from agentex.lib.core.temporal.types.workflow import SignalName +from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow from agentex.lib.core.tracing.tracing_processor_manager import ( add_tracing_processor_config, ) -from agentex.lib.types.tracing import SGPTracingProcessorConfig -from agentex.lib.environment_variables import EnvironmentVariables -from agentex.types.text_content import TextContent from agentex.lib.core.temporal.activities.adk.providers.openai_activities import ( FunctionTool, ) @@ -61,7 +61,7 @@ class StateModel(BaseModel): ] -async def calculator(context: RunContextWrapper, args: str) -> str: +async def calculator(context: RunContextWrapper, args: str) -> str: # noqa: ARG001 """ Simple calculator that can perform basic arithmetic operations. diff --git a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/acp.py b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/acp.py index d3b8e52d..2e069423 100644 --- a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/acp.py +++ b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/acp.py @@ -1,8 +1,7 @@ import os -from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.lib.types.fastacp import TemporalACPConfig - +from agentex.lib.sdk.fastacp.fastacp import FastACP # Create the ACP server acp = FastACP.create( diff --git a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/run_worker.py b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/run_worker.py index d8ce18b6..2f0059d5 100644 --- a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/run_worker.py +++ b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/run_worker.py @@ -1,13 +1,11 @@ import asyncio -from agentex.lib.core.temporal.activities import get_all_activities -from agentex.lib.core.temporal.workers.worker import AgentexWorker -from agentex.lib.utils.logging import make_logger +from project.workflow import At020StateMachineWorkflow from agentex.lib.utils.debug import setup_debug_if_enabled +from agentex.lib.utils.logging import make_logger from agentex.lib.environment_variables import EnvironmentVariables - -from project.workflow import At020StateMachineWorkflow - +from agentex.lib.core.temporal.activities import get_all_activities +from agentex.lib.core.temporal.workers.worker import AgentexWorker environment_variables = EnvironmentVariables.refresh() diff --git a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/state_machines/deep_research.py b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/state_machines/deep_research.py index add9c185..d1c4df00 100644 --- a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/state_machines/deep_research.py +++ b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/state_machines/deep_research.py @@ -1,8 +1,9 @@ from enum import Enum from typing import Dict, List, Optional, override -from agentex.types.span import Span + from pydantic import BaseModel +from agentex.types.span import Span from agentex.lib.sdk.state_machine import StateMachine diff --git a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflow.py b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflow.py index d29f4b4d..aa88de68 100644 --- a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflow.py +++ b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflow.py @@ -4,15 +4,14 @@ from temporalio import workflow from agentex.lib import adk -from agentex.lib.types.acp import CreateTaskParams, SendEventParams -from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow -from agentex.lib.core.temporal.types.workflow import SignalName +from agentex.lib.types.acp import SendEventParams, CreateTaskParams from agentex.lib.utils.logging import make_logger from agentex.types.text_content import TextContent from agentex.lib.environment_variables import EnvironmentVariables from agentex.lib.sdk.state_machine.state import State - -from project.state_machines.deep_research import DeepResearchStateMachine, DeepResearchState, DeepResearchData +from project.state_machines.deep_research import DeepResearchData, DeepResearchState, DeepResearchStateMachine +from agentex.lib.core.temporal.types.workflow import SignalName +from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow from project.workflows.deep_research.clarify_user_query import ClarifyUserQueryWorkflow from project.workflows.deep_research.waiting_for_user_input import WaitingForUserInputWorkflow from project.workflows.deep_research.performing_deep_research import PerformingDeepResearchWorkflow @@ -73,7 +72,13 @@ async def on_task_event_send(self, params: SendEventParams) -> None: # Check if we're in the middle of follow-up questions if deep_research_data.n_follow_up_questions_to_ask > 0: # User is responding to a follow-up question - deep_research_data.follow_up_responses.append(message.content) + # Safely extract content from message + content_text = "" + if hasattr(message, 'content'): + content_val = getattr(message, 'content', '') + if isinstance(content_val, str): + content_text = content_val + deep_research_data.follow_up_responses.append(content_text) # Add the Q&A to the agent input list as context if deep_research_data.follow_up_questions: @@ -116,11 +121,18 @@ async def on_task_event_send(self, params: SendEventParams) -> None: await self.state_machine.transition(DeepResearchState.CLARIFYING_USER_QUERY) # Echo back the user's message + # Safely extract content from message for display + message_content = "" + if hasattr(message, 'content'): + content_val = getattr(message, 'content', '') + if isinstance(content_val, str): + message_content = content_val + await adk.messages.create( task_id=task.id, content=TextContent( author="user", - content=message.content, + content=message_content, ), trace_id=task.id, parent_span_id=deep_research_data.current_span.id if deep_research_data.current_span else None, diff --git a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflows/deep_research/clarify_user_query.py b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflows/deep_research/clarify_user_query.py index c02e2c70..c8e756b2 100644 --- a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflows/deep_research/clarify_user_query.py +++ b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflows/deep_research/clarify_user_query.py @@ -1,12 +1,12 @@ from typing import Optional, override -from agentex.lib.sdk.state_machine.state_machine import StateMachine + +from project.state_machines.deep_research import DeepResearchData, DeepResearchState from agentex.lib import adk -from agentex.lib.sdk.state_machine.state_workflow import StateWorkflow -from agentex.lib.types.llm_messages import LLMConfig, SystemMessage, UserMessage from agentex.lib.utils.logging import make_logger - -from project.state_machines.deep_research import DeepResearchData, DeepResearchState +from agentex.lib.types.llm_messages import LLMConfig, UserMessage, SystemMessage +from agentex.lib.sdk.state_machine.state_machine import StateMachine +from agentex.lib.sdk.state_machine.state_workflow import StateWorkflow logger = make_logger(__name__) @@ -68,7 +68,12 @@ async def execute(self, state_machine: StateMachine, state_machine_data: Optiona trace_id=state_machine_data.task_id, parent_span_id=state_machine_data.current_span.id, ) - follow_up_question = task_message.content.content + # Safely extract content from task message + follow_up_question = "" + if task_message.content and hasattr(task_message.content, 'content'): + content_val = getattr(task_message.content, 'content', '') + if isinstance(content_val, str): + follow_up_question = content_val # Update with follow-up question state_machine_data.follow_up_questions.append(follow_up_question) diff --git a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflows/deep_research/performing_deep_research.py b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflows/deep_research/performing_deep_research.py index a35c0aa5..954a7566 100644 --- a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflows/deep_research/performing_deep_research.py +++ b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflows/deep_research/performing_deep_research.py @@ -1,16 +1,15 @@ -from datetime import datetime import os from typing import Optional, override +from datetime import datetime -from agentex.lib import adk -from agentex.lib.sdk.state_machine.state_machine import StateMachine from mcp import StdioServerParameters +from project.state_machines.deep_research import DeepResearchData, DeepResearchState -from agentex.lib.sdk.state_machine.state_workflow import StateWorkflow -from agentex.types.text_content import TextContent +from agentex.lib import adk from agentex.lib.utils.logging import make_logger - -from project.state_machines.deep_research import DeepResearchData, DeepResearchState +from agentex.types.text_content import TextContent +from agentex.lib.sdk.state_machine.state_machine import StateMachine +from agentex.lib.sdk.state_machine.state_workflow import StateWorkflow logger = make_logger(__name__) diff --git a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflows/deep_research/waiting_for_user_input.py b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflows/deep_research/waiting_for_user_input.py index c1a0f99d..2520771a 100644 --- a/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflows/deep_research/waiting_for_user_input.py +++ b/examples/tutorials/10_agentic/10_temporal/020_state_machine/project/workflows/deep_research/waiting_for_user_input.py @@ -1,14 +1,16 @@ from typing import override -from agentex.lib.sdk.state_machine import StateWorkflow, StateMachine -from agentex.lib.utils.logging import make_logger + from temporalio import workflow from project.state_machines.deep_research import DeepResearchData, DeepResearchState +from agentex.lib.utils.logging import make_logger +from agentex.lib.sdk.state_machine import StateMachine, StateWorkflow + logger = make_logger(__name__) class WaitingForUserInputWorkflow(StateWorkflow): @override - async def execute(self, state_machine: StateMachine, state_machine_data: DeepResearchData = None) -> str: + async def execute(self, state_machine: StateMachine, state_machine_data: DeepResearchData | None = None) -> str: logger.info("ActorWaitingForUserInputWorkflow: waiting for user input...") def condition(): current_state = state_machine.get_current_state() diff --git a/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/acp.py b/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/acp.py index f00ed82f..4deafed0 100644 --- a/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/acp.py +++ b/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/acp.py @@ -30,9 +30,8 @@ sys.exit(1) # === END DEBUG SETUP === -from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.lib.types.fastacp import TemporalACPConfig - +from agentex.lib.sdk.fastacp.fastacp import FastACP # Create the ACP server acp = FastACP.create( diff --git a/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/custom_activites.py b/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/custom_activites.py index 2abf582c..36b5c9d2 100644 --- a/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/custom_activites.py +++ b/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/custom_activites.py @@ -1,10 +1,11 @@ import asyncio -from typing import List, Any +from typing import Any, List from pydantic import BaseModel from temporalio import activity -from agentex.lib.utils.logging import make_logger + from agentex.lib import adk +from agentex.lib.utils.logging import make_logger from agentex.types.text_content import TextContent logger = make_logger(__name__) diff --git a/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/run_worker.py b/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/run_worker.py index cccae471..44ff5530 100644 --- a/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/run_worker.py +++ b/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/run_worker.py @@ -1,14 +1,12 @@ import asyncio -from agentex.lib.core.temporal.activities import get_all_activities -from agentex.lib.core.temporal.workers.worker import AgentexWorker -from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.debug import setup_debug_if_enabled -from agentex.lib.environment_variables import EnvironmentVariables - from project.workflow import At030CustomActivitiesWorkflow +from agentex.lib.utils.debug import setup_debug_if_enabled from project.custom_activites import CustomActivities - +from agentex.lib.utils.logging import make_logger +from agentex.lib.environment_variables import EnvironmentVariables +from agentex.lib.core.temporal.activities import get_all_activities +from agentex.lib.core.temporal.workers.worker import AgentexWorker environment_variables = EnvironmentVariables.refresh() diff --git a/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/workflow.py b/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/workflow.py index 3799640e..0fa85bbb 100644 --- a/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/workflow.py +++ b/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/workflow.py @@ -1,22 +1,25 @@ import asyncio -from typing import List, Any, override +from typing import Any, List, override +from datetime import timedelta from temporalio import workflow from temporalio.common import RetryPolicy -from datetime import timedelta from agentex.lib import adk -from agentex.lib.types.acp import CreateTaskParams, SendEventParams -from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow -from agentex.lib.core.temporal.types.workflow import SignalName +from agentex.lib.types.acp import SendEventParams, CreateTaskParams +from project.shared_models import StateModel, IncomingEventData +from project.workflow_utils import BatchProcessingUtils +from project.custom_activites import ( + REPORT_PROGRESS_ACTIVITY, + COMPLETE_WORKFLOW_ACTIVITY, + ReportProgressActivityParams, + CompleteWorkflowActivityParams, +) from agentex.lib.utils.logging import make_logger from agentex.types.text_content import TextContent from agentex.lib.environment_variables import EnvironmentVariables - - -from project.workflow_utils import BatchProcessingUtils -from project.shared_models import StateModel, IncomingEventData -from project.custom_activites import REPORT_PROGRESS_ACTIVITY, ReportProgressActivityParams, COMPLETE_WORKFLOW_ACTIVITY, CompleteWorkflowActivityParams +from agentex.lib.core.temporal.types.workflow import SignalName +from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow environment_variables = EnvironmentVariables.refresh() diff --git a/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/workflow_utils.py b/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/workflow_utils.py index 1217464c..da04a8da 100644 --- a/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/workflow_utils.py +++ b/examples/tutorials/10_agentic/10_temporal/030_custom_activities/project/workflow_utils.py @@ -1,18 +1,21 @@ import asyncio -from typing import List, Any, Dict +from typing import Any, Dict, List +from datetime import timedelta from temporalio import workflow from temporalio.common import RetryPolicy -from datetime import timedelta from agentex.lib import adk +from project.shared_models import StateModel +from project.custom_activites import ( + REPORT_PROGRESS_ACTIVITY, + PROCESS_BATCH_EVENTS_ACTIVITY, + ReportProgressActivityParams, + ProcessBatchEventsActivityParams, +) from agentex.lib.utils.logging import make_logger from agentex.types.text_content import TextContent -from project.custom_activites import PROCESS_BATCH_EVENTS_ACTIVITY, ProcessBatchEventsActivityParams, REPORT_PROGRESS_ACTIVITY, ReportProgressActivityParams -from project.shared_models import StateModel - - logger = make_logger(__name__) @@ -34,7 +37,7 @@ async def dequeue_pending_data(queue: asyncio.Queue[Any], data_to_process: List[ item = queue.get_nowait() data_to_process.append(item) items_dequeued += 1 - except: + except Exception: # Queue became empty while we were dequeuing break diff --git a/examples/tutorials/10_agentic/10_temporal/050_agent_chat_guardrails/project/acp.py b/examples/tutorials/10_agentic/10_temporal/050_agent_chat_guardrails/project/acp.py index d3b8e52d..2e069423 100644 --- a/examples/tutorials/10_agentic/10_temporal/050_agent_chat_guardrails/project/acp.py +++ b/examples/tutorials/10_agentic/10_temporal/050_agent_chat_guardrails/project/acp.py @@ -1,8 +1,7 @@ import os -from agentex.lib.sdk.fastacp.fastacp import FastACP from agentex.lib.types.fastacp import TemporalACPConfig - +from agentex.lib.sdk.fastacp.fastacp import FastACP # Create the ACP server acp = FastACP.create( diff --git a/examples/tutorials/10_agentic/10_temporal/050_agent_chat_guardrails/project/run_worker.py b/examples/tutorials/10_agentic/10_temporal/050_agent_chat_guardrails/project/run_worker.py index d5c05c59..636e9977 100644 --- a/examples/tutorials/10_agentic/10_temporal/050_agent_chat_guardrails/project/run_worker.py +++ b/examples/tutorials/10_agentic/10_temporal/050_agent_chat_guardrails/project/run_worker.py @@ -1,14 +1,11 @@ import asyncio -from agentex.lib.core.temporal.activities import get_all_activities -from agentex.lib.core.temporal.workers.worker import AgentexWorker -from agentex.lib.utils.logging import make_logger +from project.workflow import At050AgentChatGuardrailsWorkflow from agentex.lib.utils.debug import setup_debug_if_enabled +from agentex.lib.utils.logging import make_logger from agentex.lib.environment_variables import EnvironmentVariables - -from project.workflow import At050AgentChatGuardrailsWorkflow - - +from agentex.lib.core.temporal.activities import get_all_activities +from agentex.lib.core.temporal.workers.worker import AgentexWorker environment_variables = EnvironmentVariables.refresh() diff --git a/examples/tutorials/10_agentic/10_temporal/050_agent_chat_guardrails/project/workflow.py b/examples/tutorials/10_agentic/10_temporal/050_agent_chat_guardrails/project/workflow.py index f7df18b8..25d1a781 100644 --- a/examples/tutorials/10_agentic/10_temporal/050_agent_chat_guardrails/project/workflow.py +++ b/examples/tutorials/10_agentic/10_temporal/050_agent_chat_guardrails/project/workflow.py @@ -1,34 +1,35 @@ +# ruff: noqa: ARG001 import os import json -from typing import Dict, List, override, Any -from dotenv import load_dotenv +from typing import Any, Dict, List, override -from agentex.lib.utils.model_utils import BaseModel from mcp import StdioServerParameters +from dotenv import load_dotenv + +# Simple guardrail output model for this example +from pydantic import BaseModel from temporalio import workflow -from agentex.lib.adk.models import ModelSettings -from agentex.lib.core.base.run_context import RunContextWrapper from openai.types.shared import Reasoning from agentex.lib import adk -from agentex.lib.types.acp import CreateTaskParams, SendEventParams -from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow -from agentex.lib.core.temporal.types.workflow import SignalName +from agentex.lib.types.acp import SendEventParams, CreateTaskParams +from agentex.lib.adk.models import ModelSettings +from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger +from agentex.types.text_content import TextContent +from agentex.lib.utils.model_utils import BaseModel +from agentex.lib.core.base.run_context import RunContextWrapper +from agentex.lib.environment_variables import EnvironmentVariables +from agentex.lib.core.temporal.types.workflow import SignalName +from agentex.lib.core.temporal.workflows.workflow import BaseWorkflow from agentex.lib.core.tracing.tracing_processor_manager import ( add_tracing_processor_config, ) -from agentex.lib.types.tracing import SGPTracingProcessorConfig -from agentex.lib.environment_variables import EnvironmentVariables -from agentex.types.text_content import TextContent from agentex.lib.core.temporal.activities.adk.providers.openai_activities import ( # noqa: E501 FunctionTool, TemporalInputGuardrail, TemporalOutputGuardrail, ) -# Simple guardrail output model for this example -from pydantic import BaseModel -from typing import Dict, Any class GuardrailFunctionOutput(BaseModel): @@ -77,7 +78,7 @@ class StateModel(BaseModel): ] -async def calculator(context: RunContextWrapper, args: str) -> str: +async def calculator(context: RunContextWrapper, args: str) -> str: # noqa: ARG001 """ Simple calculator that can perform basic arithmetic operations. diff --git a/pyproject.toml b/pyproject.toml index 2fce97bd..3e4a0b49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,8 +116,7 @@ format = { chain = [ "check:importable" = "python -c 'import agentex'" typecheck = { chain = [ - "typecheck:pyright", - "typecheck:mypy" + "typecheck:pyright" ]} "typecheck:pyright" = "pyright" "typecheck:verify-types" = "pyright --verifytypes agentex --ignoreexternal" @@ -179,10 +178,8 @@ filterwarnings = [ ] [tool.pyright] -# this enables practically every flag given by pyright. -# there are a couple of flags that are still disabled by -# default in strict mode as they are experimental and niche. -typeCheckingMode = "strict" +# Default to basic type checking, but override for specific directories +typeCheckingMode = "basic" pythonVersion = "3.12" exclude = [ @@ -190,6 +187,8 @@ exclude = [ ".venv", ".nox", ".git", + "agentex-server", + "examples/tutorials", ] reportImplicitOverride = true @@ -198,6 +197,33 @@ reportOverlappingOverload = false reportImportCycles = false reportPrivateUsage = false +# Ignore common issues in generated SDK code +reportMissingTypeStubs = false +reportUnknownParameterType = false +reportUnknownMemberType = false +reportUnknownArgumentType = false +reportUnknownVariableType = false + +# Enable strict type checking only for hand-written code +[[tool.pyright.executionEnvironments]] +root = "src/agentex/lib" +typeCheckingMode = "strict" +# But allow some flexibility in OpenAI module for complex type boundaries +reportArgumentType = false + +[[tool.pyright.executionEnvironments]] +root = "examples" +typeCheckingMode = "strict" +# Allow type ignores in tutorials for readability +reportUnnecessaryTypeIgnoreComment = false + +[[tool.pyright.executionEnvironments]] +root = "tests" +typeCheckingMode = "basic" +# Be loose on typing in tests unless testing types specifically +reportOptionalMemberAccess = false +reportArgumentType = false + [tool.mypy] pretty = true show_error_codes = true @@ -208,7 +234,7 @@ show_error_codes = true # # We also exclude our `tests` as mypy doesn't always infer # types correctly and Pyright will still catch any type errors. -exclude = ['src/agentex/_files.py', '_dev/.*.py', 'tests/.*'] +exclude = ['src/agentex/_files.py', '_dev/.*.py', 'tests/.*', 'examples/tutorials/.*'] strict_equality = true implicit_reexport = true @@ -301,5 +327,10 @@ known-first-party = ["agentex", "tests"] [tool.ruff.lint.per-file-ignores] "bin/**.py" = ["T201", "T203"] "scripts/**.py" = ["T201", "T203"] -"tests/**.py" = ["T201", "T203"] +"tests/**.py" = ["T201", "T203", "ARG001", "ARG002", "ARG005"] "examples/**.py" = ["T201", "T203"] +"examples/**.ipynb" = ["T201", "T203"] +"examples/tutorials/**.py" = ["T201", "T203"] +"examples/tutorials/**.ipynb" = ["T201", "T203"] +"**/run_tests.py" = ["T201", "T203"] +"**/dev_tools/**.py" = ["T201", "T203"] diff --git a/src/agentex/__init__.py b/src/agentex/__init__.py index f2e665bc..50fd7ec6 100644 --- a/src/agentex/__init__.py +++ b/src/agentex/__init__.py @@ -40,7 +40,6 @@ from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient from ._utils._logs import setup_logging as _setup_logging - __all__ = [ "types", "__version__", diff --git a/src/agentex/_utils/_typing.py b/src/agentex/_utils/_typing.py index 193109f3..e548aa2d 100644 --- a/src/agentex/_utils/_typing.py +++ b/src/agentex/_utils/_typing.py @@ -53,7 +53,9 @@ def is_typevar(typ: type) -> bool: _TYPE_ALIAS_TYPES: tuple[type[typing_extensions.TypeAliasType], ...] = (typing_extensions.TypeAliasType,) if sys.version_info >= (3, 12): - _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType) + # NOTE: This type ignore will be overwritten by Stainless generator. + # TODO: Update Stainless config to include this type ignore or move to lib/ + _TYPE_ALIAS_TYPES = (*_TYPE_ALIAS_TYPES, typing.TypeAliasType) # type: ignore[assignment] def is_type_alias_type(tp: Any, /) -> TypeIs[typing_extensions.TypeAliasType]: diff --git a/src/agentex/lib/adk/__init__.py b/src/agentex/lib/adk/__init__.py index 9989fe5e..cc4e83db 100644 --- a/src/agentex/lib/adk/__init__.py +++ b/src/agentex/lib/adk/__init__.py @@ -1,3 +1,6 @@ +# ruff: noqa: I001 +# Import order matters here to avoid circular imports +# The _modules must be imported before providers/utils from agentex.lib.adk._modules.acp import ACPModule from agentex.lib.adk._modules.agents import AgentsModule diff --git a/src/agentex/lib/adk/_modules/acp.py b/src/agentex/lib/adk/_modules/acp.py index a7e390bc..7627a9e0 100644 --- a/src/agentex/lib/adk/_modules/acp.py +++ b/src/agentex/lib/adk/_modules/acp.py @@ -1,10 +1,12 @@ +# ruff: noqa: I001 +# Import order matters - AsyncTracer must come after client import to avoid circular imports from datetime import timedelta from typing import Any, List from agentex.types import Event from temporalio.common import RetryPolicy -from agentex import AsyncAgentex +from agentex import AsyncAgentex # noqa: F401 from agentex.lib.adk.utils._modules.client import create_async_agentex_client from agentex.lib.core.services.adk.acp.acp import ACPService from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers diff --git a/src/agentex/lib/adk/_modules/agent_task_tracker.py b/src/agentex/lib/adk/_modules/agent_task_tracker.py index 0a793bd2..b978efce 100644 --- a/src/agentex/lib/adk/_modules/agent_task_tracker.py +++ b/src/agentex/lib/adk/_modules/agent_task_tracker.py @@ -1,8 +1,10 @@ +# ruff: noqa: I001 +# Import order matters - AsyncTracer must come after client import to avoid circular imports from datetime import timedelta from temporalio.common import RetryPolicy -from agentex import AsyncAgentex +from agentex import AsyncAgentex # noqa: F401 from agentex.lib.adk.utils._modules.client import create_async_agentex_client from agentex.lib.core.services.adk.agent_task_tracker import AgentTaskTrackerService from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers diff --git a/src/agentex/lib/adk/_modules/agents.py b/src/agentex/lib/adk/_modules/agents.py index fcbf3cca..eee8b9f7 100644 --- a/src/agentex/lib/adk/_modules/agents.py +++ b/src/agentex/lib/adk/_modules/agents.py @@ -1,3 +1,5 @@ +# ruff: noqa: I001 +# Import order matters - AsyncTracer must come after client import to avoid circular imports from datetime import timedelta from typing import Optional @@ -5,7 +7,7 @@ from agentex.lib.core.temporal.activities.adk.agents_activities import AgentsActivityName, GetAgentParams from temporalio.common import RetryPolicy -from agentex import AsyncAgentex +from agentex import AsyncAgentex # noqa: F401 from agentex.lib.core.services.adk.agents import AgentsService from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers from agentex.lib.core.tracing.tracer import AsyncTracer diff --git a/src/agentex/lib/adk/_modules/events.py b/src/agentex/lib/adk/_modules/events.py index 3d771ab6..f2b4831b 100644 --- a/src/agentex/lib/adk/_modules/events.py +++ b/src/agentex/lib/adk/_modules/events.py @@ -1,8 +1,10 @@ +# ruff: noqa: I001 +# Import order matters - AsyncTracer must come after client import to avoid circular imports from datetime import timedelta from temporalio.common import RetryPolicy -from agentex import AsyncAgentex +from agentex import AsyncAgentex # noqa: F401 from agentex.lib.adk.utils._modules.client import create_async_agentex_client from agentex.lib.core.services.adk.events import EventsService from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers diff --git a/src/agentex/lib/adk/_modules/messages.py b/src/agentex/lib/adk/_modules/messages.py index cd45ae17..ad4d5b89 100644 --- a/src/agentex/lib/adk/_modules/messages.py +++ b/src/agentex/lib/adk/_modules/messages.py @@ -1,8 +1,10 @@ +# ruff: noqa: I001 +# Import order matters - AsyncTracer must come after client import to avoid circular imports from datetime import timedelta from temporalio.common import RetryPolicy -from agentex import AsyncAgentex +from agentex import AsyncAgentex # noqa: F401 from agentex.lib.adk.utils._modules.client import create_async_agentex_client from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository from agentex.lib.core.services.adk.messages import MessagesService diff --git a/src/agentex/lib/adk/_modules/state.py b/src/agentex/lib/adk/_modules/state.py index 8f8fd8a5..c3e1b2e0 100644 --- a/src/agentex/lib/adk/_modules/state.py +++ b/src/agentex/lib/adk/_modules/state.py @@ -1,10 +1,12 @@ +# ruff: noqa: I001 +# Import order matters - AsyncTracer must come after client import to avoid circular imports from datetime import timedelta from typing import Any from pydantic import BaseModel from temporalio.common import RetryPolicy -from agentex import AsyncAgentex +from agentex import AsyncAgentex # noqa: F401 from agentex.lib.adk.utils._modules.client import create_async_agentex_client from agentex.lib.core.services.adk.state import StateService from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers diff --git a/src/agentex/lib/adk/_modules/streaming.py b/src/agentex/lib/adk/_modules/streaming.py index 36af64c7..9f293fec 100644 --- a/src/agentex/lib/adk/_modules/streaming.py +++ b/src/agentex/lib/adk/_modules/streaming.py @@ -1,6 +1,8 @@ +# ruff: noqa: I001 +# Import order matters - AsyncTracer must come after client import to avoid circular imports from temporalio.common import RetryPolicy -from agentex import AsyncAgentex +from agentex import AsyncAgentex # noqa: F401 from agentex.lib.adk.utils._modules.client import create_async_agentex_client from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository from agentex.lib.core.services.adk.streaming import ( diff --git a/src/agentex/lib/adk/_modules/tasks.py b/src/agentex/lib/adk/_modules/tasks.py index 9b4b51ee..202b012e 100644 --- a/src/agentex/lib/adk/_modules/tasks.py +++ b/src/agentex/lib/adk/_modules/tasks.py @@ -1,8 +1,10 @@ +# ruff: noqa: I001 +# Import order matters - AsyncTracer must come after client import to avoid circular imports from datetime import timedelta from temporalio.common import RetryPolicy -from agentex import AsyncAgentex +from agentex import AsyncAgentex # noqa: F401 from agentex.lib.adk.utils._modules.client import create_async_agentex_client from agentex.lib.core.services.adk.tasks import TasksService from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers @@ -117,7 +119,7 @@ async def delete( heartbeat_timeout=heartbeat_timeout, ) else: - return await self._tasks_service.delete_task( + return await self._tasks_service.delete_task( # type: ignore[return-value] task_id=task_id, task_name=task_name, trace_id=trace_id, diff --git a/src/agentex/lib/adk/_modules/tracing.py b/src/agentex/lib/adk/_modules/tracing.py index cc45a899..0b16b127 100644 --- a/src/agentex/lib/adk/_modules/tracing.py +++ b/src/agentex/lib/adk/_modules/tracing.py @@ -1,3 +1,5 @@ +# ruff: noqa: I001 +# Import order matters - AsyncTracer must come after client import to avoid circular imports from collections.abc import AsyncGenerator from contextlib import asynccontextmanager from datetime import timedelta @@ -5,7 +7,7 @@ from temporalio.common import RetryPolicy -from agentex import AsyncAgentex +from agentex import AsyncAgentex # noqa: F401 from agentex.lib.adk.utils._modules.client import create_async_agentex_client from agentex.lib.core.services.adk.tracing import TracingService from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers diff --git a/src/agentex/lib/adk/providers/__init__.py b/src/agentex/lib/adk/providers/__init__.py index cf3696b8..9167396f 100644 --- a/src/agentex/lib/adk/providers/__init__.py +++ b/src/agentex/lib/adk/providers/__init__.py @@ -1,6 +1,6 @@ -from agentex.lib.adk.providers._modules.litellm import LiteLLMModule -from agentex.lib.adk.providers._modules.openai import OpenAIModule from agentex.lib.adk.providers._modules.sgp import SGPModule +from agentex.lib.adk.providers._modules.openai import OpenAIModule +from agentex.lib.adk.providers._modules.litellm import LiteLLMModule openai = OpenAIModule() litellm = LiteLLMModule() diff --git a/src/agentex/lib/adk/providers/_modules/litellm.py b/src/agentex/lib/adk/providers/_modules/litellm.py index f0feb89b..a127e0e7 100644 --- a/src/agentex/lib/adk/providers/_modules/litellm.py +++ b/src/agentex/lib/adk/providers/_modules/litellm.py @@ -1,26 +1,25 @@ -from collections.abc import AsyncGenerator from datetime import timedelta +from collections.abc import AsyncGenerator -from agentex.lib.adk.utils._modules.client import create_async_agentex_client from temporalio.common import RetryPolicy -from agentex import AsyncAgentex +from agentex.lib.utils.logging import make_logger +from agentex.lib.utils.temporal import in_temporal_workflow +from agentex.types.task_message import TaskMessage +from agentex.lib.types.llm_messages import LLMConfig, Completion +from agentex.lib.core.tracing.tracer import AsyncTracer +from agentex.lib.adk.utils._modules.client import create_async_agentex_client +from agentex.lib.core.services.adk.streaming import StreamingService from agentex.lib.core.adapters.llm.adapter_litellm import LiteLLMGateway from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository from agentex.lib.core.services.adk.providers.litellm import LiteLLMService -from agentex.lib.core.services.adk.streaming import StreamingService from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers from agentex.lib.core.temporal.activities.adk.providers.litellm_activities import ( - ChatCompletionAutoSendParams, + LiteLLMActivityName, ChatCompletionParams, + ChatCompletionAutoSendParams, ChatCompletionStreamAutoSendParams, - LiteLLMActivityName, ) -from agentex.lib.core.tracing.tracer import AsyncTracer -from agentex.lib.types.llm_messages import Completion, LLMConfig -from agentex.types.task_message import TaskMessage -from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.temporal import in_temporal_workflow logger = make_logger(__name__) diff --git a/src/agentex/lib/adk/providers/_modules/openai.py b/src/agentex/lib/adk/providers/_modules/openai.py index 6ad4566d..ea925bda 100644 --- a/src/agentex/lib/adk/providers/_modules/openai.py +++ b/src/agentex/lib/adk/providers/_modules/openai.py @@ -1,34 +1,33 @@ -from datetime import timedelta from typing import Any, Literal +from datetime import timedelta -from agentex.lib.adk.utils._modules.client import create_async_agentex_client +from mcp import StdioServerParameters from agents import Agent, RunResult, RunResultStreaming +from agents.tool import Tool from agents.agent import StopAtTools, ToolsToFinalOutputFunction from agents.guardrail import InputGuardrail, OutputGuardrail +from temporalio.common import RetryPolicy from agents.agent_output import AgentOutputSchemaBase from agents.model_settings import ModelSettings -from agents.tool import Tool -from mcp import StdioServerParameters -from temporalio.common import RetryPolicy -from agentex import AsyncAgentex -from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository -from agentex.lib.core.services.adk.providers.openai import OpenAIService +from agentex.lib.utils.logging import make_logger +from agentex.lib.utils.temporal import in_temporal_workflow +from agentex.lib.core.tracing.tracer import AsyncTracer +from agentex.lib.types.agent_results import ( + SerializableRunResult, + SerializableRunResultStreaming, +) +from agentex.lib.adk.utils._modules.client import create_async_agentex_client from agentex.lib.core.services.adk.streaming import StreamingService +from agentex.lib.core.services.adk.providers.openai import OpenAIService +from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers from agentex.lib.core.temporal.activities.adk.providers.openai_activities import ( + RunAgentParams, OpenAIActivityName, RunAgentAutoSendParams, - RunAgentParams, RunAgentStreamedAutoSendParams, ) -from agentex.lib.core.tracing.tracer import AsyncTracer -from agentex.lib.types.agent_results import ( - SerializableRunResult, - SerializableRunResultStreaming, -) -from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.temporal import in_temporal_workflow logger = make_logger(__name__) @@ -129,15 +128,15 @@ async def run_agent( agent_name=agent_name, agent_instructions=agent_instructions, handoff_description=handoff_description, - handoffs=handoffs, + handoffs=handoffs, # type: ignore[arg-type] model=model, - model_settings=model_settings, - tools=tools, + model_settings=model_settings, # type: ignore[arg-type] + tools=tools, # type: ignore[arg-type] output_type=output_type, - tool_use_behavior=tool_use_behavior, + tool_use_behavior=tool_use_behavior, # type: ignore[arg-type] mcp_timeout_seconds=mcp_timeout_seconds, - input_guardrails=input_guardrails, - output_guardrails=output_guardrails, + input_guardrails=input_guardrails, # type: ignore[arg-type] + output_guardrails=output_guardrails, # type: ignore[arg-type] max_turns=max_turns, previous_response_id=previous_response_id, ) @@ -238,15 +237,15 @@ async def run_agent_auto_send( agent_name=agent_name, agent_instructions=agent_instructions, handoff_description=handoff_description, - handoffs=handoffs, + handoffs=handoffs, # type: ignore[arg-type] model=model, - model_settings=model_settings, - tools=tools, + model_settings=model_settings, # type: ignore[arg-type] + tools=tools, # type: ignore[arg-type] output_type=output_type, - tool_use_behavior=tool_use_behavior, + tool_use_behavior=tool_use_behavior, # type: ignore[arg-type] mcp_timeout_seconds=mcp_timeout_seconds, - input_guardrails=input_guardrails, - output_guardrails=output_guardrails, + input_guardrails=input_guardrails, # type: ignore[arg-type] + output_guardrails=output_guardrails, # type: ignore[arg-type] max_turns=max_turns, previous_response_id=previous_response_id, ) diff --git a/src/agentex/lib/adk/providers/_modules/sgp.py b/src/agentex/lib/adk/providers/_modules/sgp.py index 52c20e09..3917ddde 100644 --- a/src/agentex/lib/adk/providers/_modules/sgp.py +++ b/src/agentex/lib/adk/providers/_modules/sgp.py @@ -1,20 +1,19 @@ from datetime import timedelta -from agentex.lib.adk.utils._modules.client import create_async_agentex_client from scale_gp import SGPClient, SGPClientError from temporalio.common import RetryPolicy -from agentex import AsyncAgentex +from agentex.lib.utils.logging import make_logger +from agentex.lib.utils.temporal import in_temporal_workflow +from agentex.lib.core.tracing.tracer import AsyncTracer +from agentex.lib.adk.utils._modules.client import create_async_agentex_client from agentex.lib.core.services.adk.providers.sgp import SGPService from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers from agentex.lib.core.temporal.activities.adk.providers.sgp_activities import ( + SGPActivityName, DownloadFileParams, FileContentResponse, - SGPActivityName, ) -from agentex.lib.core.tracing.tracer import AsyncTracer -from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.temporal import in_temporal_workflow logger = make_logger(__name__) diff --git a/src/agentex/lib/adk/utils/_modules/client.py b/src/agentex/lib/adk/utils/_modules/client.py index 49e303fc..72528963 100644 --- a/src/agentex/lib/adk/utils/_modules/client.py +++ b/src/agentex/lib/adk/utils/_modules/client.py @@ -1,8 +1,10 @@ +from typing import override + import httpx from agentex import AsyncAgentex -from agentex.lib.environment_variables import EnvironmentVariables from agentex.lib.utils.logging import make_logger +from agentex.lib.environment_variables import EnvironmentVariables logger = make_logger(__name__) @@ -11,6 +13,7 @@ class EnvAuth(httpx.Auth): def __init__(self, header_name="x-agent-api-key"): self.header_name = header_name + @override def auth_flow(self, request): # This gets called for every request env_vars = EnvironmentVariables.refresh() diff --git a/src/agentex/lib/adk/utils/_modules/templating.py b/src/agentex/lib/adk/utils/_modules/templating.py index 466c4ae9..57843505 100644 --- a/src/agentex/lib/adk/utils/_modules/templating.py +++ b/src/agentex/lib/adk/utils/_modules/templating.py @@ -1,19 +1,18 @@ -from datetime import timedelta from typing import Any +from datetime import timedelta -from agentex.lib.adk.utils._modules.client import create_async_agentex_client from temporalio.common import RetryPolicy -from agentex import AsyncAgentex +from agentex.lib.utils.logging import make_logger +from agentex.lib.utils.temporal import in_temporal_workflow +from agentex.lib.core.tracing.tracer import AsyncTracer +from agentex.lib.adk.utils._modules.client import create_async_agentex_client from agentex.lib.core.services.adk.utils.templating import TemplatingService from agentex.lib.core.temporal.activities.activity_helpers import ActivityHelpers from agentex.lib.core.temporal.activities.adk.utils.templating_activities import ( JinjaActivityName, RenderJinjaParams, ) -from agentex.lib.core.tracing.tracer import AsyncTracer -from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.temporal import in_temporal_workflow logger = make_logger(__name__) diff --git a/src/agentex/lib/cli/commands/agents.py b/src/agentex/lib/cli/commands/agents.py index 1cbb5132..eac8a7f4 100644 --- a/src/agentex/lib/cli/commands/agents.py +++ b/src/agentex/lib/cli/commands/agents.py @@ -1,37 +1,37 @@ import builtins from pathlib import Path -import questionary import typer +import questionary from rich import print_json -from rich.console import Console from rich.panel import Panel +from rich.console import Console +from agentex import Agentex +from agentex.lib.cli.debug import DebugMode, DebugConfig +from agentex.lib.utils.logging import make_logger +from agentex.lib.cli.utils.cli_utils import handle_questionary_cancellation +from agentex.lib.sdk.config.validation import ( + EnvironmentsValidationError, + generate_helpful_error_message, + validate_manifest_and_environments, +) +from agentex.lib.cli.utils.kubectl_utils import ( + validate_namespace, + check_and_switch_cluster_context, +) +from agentex.lib.sdk.config.agent_manifest import AgentManifest from agentex.lib.cli.handlers.agent_handlers import ( - build_agent, run_agent, + build_agent, ) -from agentex.lib.cli.debug import DebugConfig, DebugMode -from agentex.lib.cli.handlers.cleanup_handlers import cleanup_agent_workflows from agentex.lib.cli.handlers.deploy_handlers import ( - DeploymentError, HelmError, + DeploymentError, InputDeployOverrides, deploy_agent, ) -from agentex.lib.sdk.config.validation import ( - validate_manifest_and_environments, - EnvironmentsValidationError, - generate_helpful_error_message -) -from agentex.lib.cli.utils.cli_utils import handle_questionary_cancellation -from agentex.lib.cli.utils.kubectl_utils import ( - check_and_switch_cluster_context, - validate_namespace, -) -from agentex import Agentex -from agentex.lib.sdk.config.agent_manifest import AgentManifest -from agentex.lib.utils.logging import make_logger +from agentex.lib.cli.handlers.cleanup_handlers import cleanup_agent_workflows logger = make_logger(__name__) console = Console() @@ -293,10 +293,10 @@ def deploy( except EnvironmentsValidationError as e: error_msg = generate_helpful_error_message(e, "Environment validation failed") console.print(f"[red]Configuration Error:[/red]\n{error_msg}") - raise typer.Exit(1) + raise typer.Exit(1) from e except Exception as e: console.print(f"[red]Error:[/red] Failed to validate configuration: {e}") - raise typer.Exit(1) + raise typer.Exit(1) from e # Load manifest for credential validation manifest_obj = AgentManifest.from_yaml(str(manifest_path)) diff --git a/src/agentex/lib/cli/commands/init.py b/src/agentex/lib/cli/commands/init.py index 2de197ae..a9699665 100644 --- a/src/agentex/lib/cli/commands/init.py +++ b/src/agentex/lib/cli/commands/init.py @@ -1,12 +1,12 @@ from enum import Enum -from pathlib import Path from typing import Any, Dict +from pathlib import Path import questionary from jinja2 import Environment, FileSystemLoader -from rich.console import Console from rich.panel import Panel from rich.table import Table +from rich.console import Console from agentex.lib.utils.logging import make_logger @@ -86,7 +86,7 @@ def create_project_structure( console.print(f"\n[green]✓[/green] Created project structure at: {project_dir}") -def get_project_context(answers: Dict[str, Any], project_path: Path, manifest_root: Path) -> Dict[str, Any]: +def get_project_context(answers: Dict[str, Any], project_path: Path, manifest_root: Path) -> Dict[str, Any]: # noqa: ARG001 """Get the project context from user answers""" # Use agent_directory_name as project_name project_name = answers["agent_directory_name"].replace("-", "_") diff --git a/src/agentex/lib/cli/commands/main.py b/src/agentex/lib/cli/commands/main.py index 60863430..2b1c775c 100644 --- a/src/agentex/lib/cli/commands/main.py +++ b/src/agentex/lib/cli/commands/main.py @@ -1,10 +1,10 @@ import typer -from agentex.lib.cli.commands.agents import agents +from agentex.lib.cli.commands.uv import uv from agentex.lib.cli.commands.init import init -from agentex.lib.cli.commands.secrets import secrets from agentex.lib.cli.commands.tasks import tasks -from agentex.lib.cli.commands.uv import uv +from agentex.lib.cli.commands.agents import agents +from agentex.lib.cli.commands.secrets import secrets # Create the main Typer application app = typer.Typer( diff --git a/src/agentex/lib/cli/commands/secrets.py b/src/agentex/lib/cli/commands/secrets.py index 2aa671b4..741b7e8e 100644 --- a/src/agentex/lib/cli/commands/secrets.py +++ b/src/agentex/lib/cli/commands/secrets.py @@ -1,24 +1,24 @@ from pathlib import Path -import questionary import typer +import questionary from rich import print_json -from rich.console import Console from rich.panel import Panel +from rich.console import Console -from agentex.lib.cli.handlers.secret_handlers import ( - delete_secret, - get_kubernetes_secrets_by_type, - get_secret, - sync_secrets, -) +from agentex.lib.utils.logging import make_logger from agentex.lib.cli.utils.cli_utils import handle_questionary_cancellation from agentex.lib.cli.utils.kubectl_utils import ( - check_and_switch_cluster_context, validate_namespace, + check_and_switch_cluster_context, ) from agentex.lib.sdk.config.agent_manifest import AgentManifest -from agentex.lib.utils.logging import make_logger +from agentex.lib.cli.handlers.secret_handlers import ( + get_secret, + sync_secrets, + delete_secret, + get_kubernetes_secrets_by_type, +) logger = make_logger(__name__) console = Console() diff --git a/src/agentex/lib/cli/commands/tasks.py b/src/agentex/lib/cli/commands/tasks.py index b27144e0..189021f7 100644 --- a/src/agentex/lib/cli/commands/tasks.py +++ b/src/agentex/lib/cli/commands/tasks.py @@ -1,10 +1,12 @@ +from typing import Any + import typer from rich import print_json from rich.console import Console from agentex import Agentex -from agentex.lib.cli.handlers.cleanup_handlers import cleanup_agent_workflows from agentex.lib.utils.logging import make_logger +from agentex.lib.cli.handlers.cleanup_handlers import cleanup_agent_workflows logger = make_logger(__name__) console = Console() @@ -22,7 +24,7 @@ def get( logger.info(f"Getting task: {task_id}") client = Agentex() task = client.tasks.retrieve(task_id=task_id) - print(f"Full Task {task_id}:") + logger.info(f"Full Task {task_id}:") print_json(data=task.to_dict()) @@ -45,35 +47,33 @@ def list_running( """ client = Agentex() all_tasks = client.tasks.list() - running_tasks = [task for task in all_tasks if hasattr(task, 'status') and task.status == "RUNNING"] - + running_tasks = [task for task in all_tasks if hasattr(task, "status") and task.status == "RUNNING"] + if not running_tasks: console.print(f"[yellow]No running tasks found for agent '{agent_name}'[/yellow]") return - + console.print(f"[green]Found {len(running_tasks)} running task(s) for agent '{agent_name}':[/green]") - + # Convert to dict with proper datetime serialization - serializable_tasks = [] + serializable_tasks: list[dict[str, Any]] = [] # type: ignore[misc] for task in running_tasks: try: # Use model_dump with mode='json' for proper datetime handling - if hasattr(task, 'model_dump'): - serializable_tasks.append(task.model_dump(mode='json')) + if hasattr(task, "model_dump"): + serializable_tasks.append(task.model_dump(mode="json")) else: # Fallback for non-Pydantic objects - serializable_tasks.append({ - "id": getattr(task, 'id', 'unknown'), - "status": getattr(task, 'status', 'unknown') - }) + serializable_tasks.append( + {"id": getattr(task, "id", "unknown"), "status": getattr(task, "status", "unknown")} + ) except Exception as e: logger.warning(f"Failed to serialize task: {e}") # Minimal fallback - serializable_tasks.append({ - "id": getattr(task, 'id', 'unknown'), - "status": getattr(task, 'status', 'unknown') - }) - + serializable_tasks.append( + {"id": getattr(task, "id", "unknown"), "status": getattr(task, "status", "unknown")} + ) + print_json(data=serializable_tasks) @@ -93,25 +93,23 @@ def delete( @tasks.command() def cleanup( agent_name: str = typer.Option(..., help="Name of the agent to cleanup tasks for"), - force: bool = typer.Option(False, help="Force cleanup using direct Temporal termination (bypasses development check)"), + force: bool = typer.Option( + False, help="Force cleanup using direct Temporal termination (bypasses development check)" + ), ): """ Clean up all running tasks/workflows for an agent. - + By default, uses graceful cancellation via agent RPC. With --force, directly terminates workflows via Temporal client. """ try: console.print(f"[blue]Starting cleanup for agent '{agent_name}'...[/blue]") - - cleanup_agent_workflows( - agent_name=agent_name, - force=force, - development_only=True - ) - + + cleanup_agent_workflows(agent_name=agent_name, force=force, development_only=True) + console.print(f"[green]✓ Cleanup completed for agent '{agent_name}'[/green]") - + except Exception as e: console.print(f"[red]Cleanup failed: {str(e)}[/red]") logger.exception("Task cleanup failed") diff --git a/src/agentex/lib/cli/commands/uv.py b/src/agentex/lib/cli/commands/uv.py index d9556f5b..592155b8 100644 --- a/src/agentex/lib/cli/commands/uv.py +++ b/src/agentex/lib/cli/commands/uv.py @@ -1,6 +1,6 @@ import os -import subprocess import sys +import subprocess import typer diff --git a/src/agentex/lib/cli/debug/__init__.py b/src/agentex/lib/cli/debug/__init__.py index add2edc1..764b3565 100644 --- a/src/agentex/lib/cli/debug/__init__.py +++ b/src/agentex/lib/cli/debug/__init__.py @@ -4,7 +4,7 @@ Provides debug support for temporal workers and ACP servers during local development. """ -from .debug_config import DebugConfig, DebugMode +from .debug_config import DebugMode, DebugConfig from .debug_handlers import start_acp_server_debug, start_temporal_worker_debug __all__ = [ diff --git a/src/agentex/lib/cli/debug/debug_config.py b/src/agentex/lib/cli/debug/debug_config.py index 2b9b5890..3b30e68e 100644 --- a/src/agentex/lib/cli/debug/debug_config.py +++ b/src/agentex/lib/cli/debug/debug_config.py @@ -4,7 +4,6 @@ import socket from enum import Enum -from typing import Optional from agentex.lib.utils.model_utils import BaseModel @@ -62,7 +61,7 @@ def create_acp_debug( def create_both_debug( cls, worker_port: int = 5678, - acp_port: int = 5679, + _acp_port: int = 5679, wait_for_attach: bool = False, auto_port: bool = True ) -> "DebugConfig": diff --git a/src/agentex/lib/cli/debug/debug_handlers.py b/src/agentex/lib/cli/debug/debug_handlers.py index 692f8647..98746387 100644 --- a/src/agentex/lib/cli/debug/debug_handlers.py +++ b/src/agentex/lib/cli/debug/debug_handlers.py @@ -4,19 +4,21 @@ Provides debug-enabled versions of ACP server and temporal worker startup. """ -import asyncio import sys +import asyncio +import asyncio.subprocess +from typing import TYPE_CHECKING, Dict from pathlib import Path -from typing import Dict, TYPE_CHECKING from rich.console import Console if TYPE_CHECKING: - import asyncio.subprocess + pass -from .debug_config import DebugConfig, resolve_debug_port from agentex.lib.utils.logging import make_logger +from .debug_config import DebugConfig, resolve_debug_port + logger = make_logger(__name__) console = Console() diff --git a/src/agentex/lib/cli/handlers/agent_handlers.py b/src/agentex/lib/cli/handlers/agent_handlers.py index b1d4bc4d..81608018 100644 --- a/src/agentex/lib/cli/handlers/agent_handlers.py +++ b/src/agentex/lib/cli/handlers/agent_handlers.py @@ -2,14 +2,13 @@ from pathlib import Path -from python_on_whales import DockerException, docker from rich.console import Console +from python_on_whales import DockerException, docker -from agentex.lib.cli.handlers.run_handlers import RunError -from agentex.lib.cli.handlers.run_handlers import run_agent as _run_agent from agentex.lib.cli.debug import DebugConfig -from agentex.lib.sdk.config.agent_manifest import AgentManifest from agentex.lib.utils.logging import make_logger +from agentex.lib.cli.handlers.run_handlers import RunError, run_agent as _run_agent +from agentex.lib.sdk.config.agent_manifest import AgentManifest logger = make_logger(__name__) console = Console() @@ -25,9 +24,9 @@ def build_agent( repository_name: str | None, platforms: list[str], push: bool = False, - secret: str = None, - tag: str = None, - build_args: list[str] = None, + secret: str | None = None, + tag: str | None = None, + build_args: list[str] | None = None, ) -> str: """Build the agent locally and optionally push to registry @@ -66,14 +65,14 @@ def build_agent( # Log build context information for debugging logger.info(f"Build context path: {build_context.path}") logger.info( - f"Dockerfile path: {build_context.path / build_context.dockerfile_path}" + f"Dockerfile path: {build_context.path / build_context.dockerfile_path}" # type: ignore[operator] ) try: # Prepare build arguments docker_build_kwargs = { "context_path": str(build_context.path), - "file": str(build_context.path / build_context.dockerfile_path), + "file": str(build_context.path / build_context.dockerfile_path), # type: ignore[operator] "tags": [image_name], "platforms": platforms, } @@ -129,23 +128,23 @@ def build_agent( def run_agent(manifest_path: str, debug_config: "DebugConfig | None" = None): """Run an agent locally from the given manifest""" - import asyncio - import signal import sys + import signal + import asyncio # Flag to track if we're shutting down shutting_down = False - def signal_handler(signum, frame): + def signal_handler(signum, _frame): """Handle signals by raising KeyboardInterrupt""" nonlocal shutting_down if shutting_down: # If we're already shutting down and get another signal, force exit - print(f"\nForce exit on signal {signum}") + logger.info(f"Force exit on signal {signum}") sys.exit(1) - + shutting_down = True - print(f"\nReceived signal {signum}, shutting down...") + logger.info(f"Received signal {signum}, shutting down...") raise KeyboardInterrupt() # Set up signal handling for the main thread @@ -155,7 +154,7 @@ def signal_handler(signum, frame): try: asyncio.run(_run_agent(manifest_path, debug_config)) except KeyboardInterrupt: - print("Shutdown completed.") + logger.info("Shutdown completed.") sys.exit(0) except RunError as e: raise RuntimeError(str(e)) from e diff --git a/src/agentex/lib/cli/handlers/cleanup_handlers.py b/src/agentex/lib/cli/handlers/cleanup_handlers.py index 0bf66dfc..4f4433d5 100644 --- a/src/agentex/lib/cli/handlers/cleanup_handlers.py +++ b/src/agentex/lib/cli/handlers/cleanup_handlers.py @@ -1,5 +1,5 @@ -import asyncio import os +import asyncio from rich.console import Console diff --git a/src/agentex/lib/cli/handlers/deploy_handlers.py b/src/agentex/lib/cli/handlers/deploy_handlers.py index c970c442..d4d82018 100644 --- a/src/agentex/lib/cli/handlers/deploy_handlers.py +++ b/src/agentex/lib/cli/handlers/deploy_handlers.py @@ -25,20 +25,14 @@ class InputDeployOverrides(BaseModel): - repository: str | None = Field( - default=None, description="Override the repository for deployment" - ) - image_tag: str | None = Field( - default=None, description="Override the image tag for deployment" - ) + repository: str | None = Field(default=None, description="Override the repository for deployment") + image_tag: str | None = Field(default=None, description="Override the image tag for deployment") def check_helm_installed() -> bool: """Check if helm is installed and available""" try: - result = subprocess.run( - ["helm", "version", "--short"], capture_output=True, text=True, check=True - ) + result = subprocess.run(["helm", "version", "--short"], capture_output=True, text=True, check=True) logger.info(f"Helm version: {result.stdout.strip()}") return True except (subprocess.CalledProcessError, FileNotFoundError): @@ -49,9 +43,7 @@ def add_helm_repo(helm_repository_name: str, helm_repository_url: str) -> None: """Add the agentex helm repository if not already added""" try: # Check if repo already exists - result = subprocess.run( - ["helm", "repo", "list"], capture_output=True, text=True, check=True - ) + result = subprocess.run(["helm", "repo", "list"], capture_output=True, text=True, check=True) if helm_repository_name not in result.stdout: console.print("Adding agentex helm repository...") @@ -75,7 +67,6 @@ def add_helm_repo(helm_repository_name: str, helm_repository_url: str) -> None: raise HelmError(f"Failed to add helm repository: {e}") from e - def convert_env_vars_dict_to_list(env_vars: dict[str, str]) -> list[dict[str, str]]: """Convert a dictionary of environment variables to a list of dictionaries""" return [{"name": key, "value": value} for key, value in env_vars.items()] @@ -169,11 +160,11 @@ def merge_deployment_configs( # Priority: manifest -> environments.yaml -> secrets (highest) all_env_vars: dict[str, str] = {} secret_env_vars: list[dict[str, str]] = [] - + # Start with agent_config env vars from manifest if agent_config.env: all_env_vars.update(agent_config.env) - + # Override with environment config env vars if they exist if agent_env_config and agent_env_config.helm_overrides and "env" in agent_env_config.helm_overrides: env_overrides = agent_env_config.helm_overrides["env"] @@ -185,8 +176,6 @@ def merge_deployment_configs( env_override_dict[str(env_var["name"])] = str(env_var["value"]) all_env_vars.update(env_override_dict) - - # Handle credentials and check for conflicts if agent_config.credentials: for credential in agent_config.credentials: @@ -199,7 +188,7 @@ def merge_deployment_configs( env_var_name = credential.env_var_name secret_name = credential.secret_name secret_key = credential.secret_key - + # Check if the environment variable name conflicts with existing env vars if env_var_name in all_env_vars: logger.warning( @@ -208,7 +197,7 @@ def merge_deployment_configs( ) # Remove from regular env vars since secret takes precedence del all_env_vars[env_var_name] - + secret_env_vars.append( { "name": env_var_name, @@ -222,13 +211,14 @@ def merge_deployment_configs( # Add auth principal env var if environment config is set if agent_env_config.auth: from agentex.lib.cli.utils.auth_utils import _encode_principal_context_from_env_config + encoded_principal = _encode_principal_context_from_env_config(agent_env_config.auth) logger.info(f"Encoding auth principal from {agent_env_config.auth}") if encoded_principal: all_env_vars[EnvVarKeys.AUTH_PRINCIPAL_B64.value] = encoded_principal else: raise DeploymentError(f"Auth principal unable to be encoded for agent_env_config: {agent_env_config}") - + logger.info(f"Defined agent helm overrides: {agent_env_config.helm_overrides}") logger.info(f"Before-merge helm values: {helm_values}") if agent_env_config.helm_overrides: @@ -239,7 +229,7 @@ def merge_deployment_configs( # Environment variable precedence: manifest -> environments.yaml -> secrets (highest) if all_env_vars: helm_values["env"] = convert_env_vars_dict_to_list(all_env_vars) - + if secret_env_vars: helm_values["secretEnvVars"] = secret_env_vars @@ -252,30 +242,25 @@ def merge_deployment_configs( # Handle image pull secrets if manifest.deployment and manifest.deployment.imagePullSecrets: - pull_secrets = [ - pull_secret.model_dump() - for pull_secret in manifest.deployment.imagePullSecrets - ] + pull_secrets = [pull_secret.model_dump() for pull_secret in manifest.deployment.imagePullSecrets] helm_values["global"]["imagePullSecrets"] = pull_secrets helm_values["imagePullSecrets"] = pull_secrets # Add dynamic ACP command based on manifest configuration if command is not set in helm overrides - helm_overrides_command = agent_env_config and agent_env_config.helm_overrides and "command" in agent_env_config.helm_overrides + helm_overrides_command = ( + agent_env_config and agent_env_config.helm_overrides and "command" in agent_env_config.helm_overrides + ) if not helm_overrides_command: add_acp_command_to_helm_values(helm_values, manifest, manifest_path) - - console.print("Deploying with the following helm values: ", helm_values) + + logger.info("Deploying with the following helm values: %s", helm_values) return helm_values def _deep_merge(base_dict: dict[str, Any], override_dict: dict[str, Any]) -> None: """Deep merge override_dict into base_dict""" for key, value in override_dict.items(): - if ( - key in base_dict - and isinstance(base_dict[key], dict) - and isinstance(value, dict) - ): + if key in base_dict and isinstance(base_dict[key], dict) and isinstance(value, dict): _deep_merge(base_dict[key], value) else: base_dict[key] = value @@ -394,12 +379,8 @@ def deploy_agent( # Show success message with helpful commands console.print("\n[green]🎉 Deployment completed successfully![/green]") - console.print( - f"[blue]Check deployment status:[/blue] helm status {release_name} -n {namespace}" - ) - console.print( - f"[blue]View logs:[/blue] kubectl logs -l app.kubernetes.io/name=agentex-agent -n {namespace}" - ) + console.print(f"[blue]Check deployment status:[/blue] helm status {release_name} -n {namespace}") + console.print(f"[blue]View logs:[/blue] kubectl logs -l app.kubernetes.io/name=agentex-agent -n {namespace}") except subprocess.CalledProcessError as e: raise HelmError( diff --git a/src/agentex/lib/cli/handlers/run_handlers.py b/src/agentex/lib/cli/handlers/run_handlers.py index cfc49e61..a249dede 100644 --- a/src/agentex/lib/cli/handlers/run_handlers.py +++ b/src/agentex/lib/cli/handlers/run_handlers.py @@ -1,26 +1,21 @@ -import asyncio import os import sys +import asyncio from pathlib import Path -from rich.console import Console from rich.panel import Panel +from rich.console import Console -from agentex.lib.cli.handlers.cleanup_handlers import ( - cleanup_agent_workflows, - should_cleanup_on_restart -) +# Import debug functionality +from agentex.lib.cli.debug import DebugConfig, start_acp_server_debug, start_temporal_worker_debug +from agentex.lib.utils.logging import make_logger from agentex.lib.cli.utils.path_utils import ( get_file_paths, calculate_uvicorn_target_for_local, ) - from agentex.lib.environment_variables import EnvVarKeys from agentex.lib.sdk.config.agent_manifest import AgentManifest - -# Import debug functionality -from agentex.lib.cli.debug import DebugConfig, start_acp_server_debug, start_temporal_worker_debug -from agentex.lib.utils.logging import make_logger +from agentex.lib.cli.handlers.cleanup_handlers import cleanup_agent_workflows, should_cleanup_on_restart logger = make_logger(__name__) console = Console() @@ -242,6 +237,8 @@ async def start_temporal_worker( async def stream_process_output(process: asyncio.subprocess.Process, prefix: str): """Stream process output with prefix""" try: + if process.stdout is None: + return while True: line = await process.stdout.readline() if not line: @@ -297,11 +294,11 @@ async def run_agent(manifest_path: str, debug_config: "DebugConfig | None" = Non manifest_dir = Path(manifest_path).parent if debug_config and debug_config.should_debug_acp(): acp_process = await start_acp_server_debug( - file_paths["acp"], manifest.local_development.agent.port, agent_env, debug_config + file_paths["acp"], manifest.local_development.agent.port, agent_env, debug_config # type: ignore[union-attr] ) else: acp_process = await start_acp_server( - file_paths["acp"], manifest.local_development.agent.port, agent_env, manifest_dir + file_paths["acp"], manifest.local_development.agent.port, agent_env, manifest_dir # type: ignore[union-attr] ) process_manager.add_process(acp_process) @@ -325,7 +322,7 @@ async def run_agent(manifest_path: str, debug_config: "DebugConfig | None" = Non tasks.append(worker_task) console.print( - f"\n[green]✓ Agent running at: http://localhost:{manifest.local_development.agent.port}[/green]" + f"\n[green]✓ Agent running at: http://localhost:{manifest.local_development.agent.port}[/green]" # type: ignore[union-attr] ) console.print("[dim]Press Ctrl+C to stop[/dim]\n") @@ -369,8 +366,8 @@ def create_agent_environment(manifest: AgentManifest) -> dict[str, str]: "REDIS_URL": "redis://localhost:6379", "AGENT_NAME": manifest.agent.name, "ACP_TYPE": manifest.agent.acp_type, - "ACP_URL": f"http://{manifest.local_development.agent.host_address}", - "ACP_PORT": str(manifest.local_development.agent.port), + "ACP_URL": f"http://{manifest.local_development.agent.host_address}", # type: ignore[union-attr] + "ACP_PORT": str(manifest.local_development.agent.port), # type: ignore[union-attr] } # Add authorization principal if set - for local development, auth is optional diff --git a/src/agentex/lib/cli/handlers/secret_handlers.py b/src/agentex/lib/cli/handlers/secret_handlers.py index ef68ce5d..da5bbef5 100644 --- a/src/agentex/lib/cli/handlers/secret_handlers.py +++ b/src/agentex/lib/cli/handlers/secret_handlers.py @@ -1,28 +1,19 @@ -import base64 import json -from collections import defaultdict -from pathlib import Path +import base64 from typing import Any +from pathlib import Path +from collections import defaultdict -import questionary -import typer import yaml -from kubernetes.client.rest import ApiException +import typer +import questionary from rich.console import Console +from kubernetes.client.rest import ApiException +from agentex.lib.utils.logging import make_logger +from agentex.lib.types.credentials import CredentialMapping from agentex.lib.cli.utils.cli_utils import handle_questionary_cancellation from agentex.lib.cli.utils.kubectl_utils import get_k8s_client -from agentex.lib.cli.utils.kubernetes_secrets_utils import ( - KUBERNETES_SECRET_TO_MANIFEST_KEY, - KUBERNETES_SECRET_TYPE_DOCKERCONFIGJSON, - KUBERNETES_SECRET_TYPE_OPAQUE, - VALID_SECRET_TYPES, - create_image_pull_secret_with_data, - create_secret_with_data, - get_secret_data, - update_image_pull_secret_with_data, - update_secret_with_data, -) from agentex.lib.sdk.config.agent_config import AgentConfig from agentex.lib.sdk.config.agent_manifest import AgentManifest from agentex.lib.sdk.config.deployment_config import ( @@ -30,8 +21,17 @@ ImagePullSecretConfig, InjectedSecretsValues, ) -from agentex.lib.types.credentials import CredentialMapping -from agentex.lib.utils.logging import make_logger +from agentex.lib.cli.utils.kubernetes_secrets_utils import ( + VALID_SECRET_TYPES, + KUBERNETES_SECRET_TYPE_OPAQUE, + KUBERNETES_SECRET_TO_MANIFEST_KEY, + KUBERNETES_SECRET_TYPE_DOCKERCONFIGJSON, + get_secret_data, + create_secret_with_data, + update_secret_with_data, + create_image_pull_secret_with_data, + update_image_pull_secret_with_data, +) logger = make_logger(__name__) console = Console() @@ -131,16 +131,16 @@ def interactive_secret_input(secret_name: str, secret_key: str) -> str: return handle_questionary_cancellation(result, "text input") -def get_secret(name: str, namespace: str, context: str | None = None) -> dict: +def get_secret(name: str, namespace: str, context: str | None = None) -> dict[str, Any]: """Get details about a secret""" v1 = get_k8s_client(context) try: secret = v1.read_namespaced_secret(name=name, namespace=namespace) return { - "name": secret.metadata.name, + "name": secret.metadata.name, # type: ignore[union-attr] "namespace": namespace, - "created": secret.metadata.creation_timestamp.isoformat(), + "created": secret.metadata.creation_timestamp.isoformat(), # type: ignore[union-attr] "exists": True, } except ApiException as e: @@ -173,7 +173,7 @@ def delete_secret(name: str, namespace: str, context: str | None = None) -> None def get_kubernetes_secrets_by_type( namespace: str, context: str | None = None -) -> dict[str, list[dict]]: +) -> dict[str, list[dict[str, Any]]]: """List metadata about secrets in the namespace""" v1 = get_k8s_client(context) @@ -218,7 +218,7 @@ def sync_user_defined_secrets( cluster_secret_names = {secret["name"] for secret in found_secrets} # Get the secrets from the manifest agent_config: AgentConfig = manifest_obj.agent - manifest_credentials: list[CredentialMapping] = agent_config.credentials or [] + manifest_credentials: list[CredentialMapping] = agent_config.credentials or [] # type: ignore[assignment] if not manifest_credentials: console.print("[yellow]No credentials found in manifest[/yellow]") @@ -465,7 +465,7 @@ def sync_image_pull_secrets( } # Get the secrets from the manifest - deployment_config: DeploymentConfig = manifest_obj.deployment + deployment_config: DeploymentConfig = manifest_obj.deployment # type: ignore[assignment] manifest_image_pull_secrets: list[ImagePullSecretConfig] = ( deployment_config.imagePullSecrets or [] ) diff --git a/src/agentex/lib/cli/templates/temporal/project/acp.py.j2 b/src/agentex/lib/cli/templates/temporal/project/acp.py.j2 index f00ed82f..5bcc7cc3 100644 --- a/src/agentex/lib/cli/templates/temporal/project/acp.py.j2 +++ b/src/agentex/lib/cli/templates/temporal/project/acp.py.j2 @@ -5,23 +5,26 @@ import sys if os.getenv("AGENTEX_DEBUG_ENABLED") == "true": try: import debugpy + from agentex.lib.utils.logging import make_logger + + logger = make_logger(__name__) debug_port = int(os.getenv("AGENTEX_DEBUG_PORT", "5679")) debug_type = os.getenv("AGENTEX_DEBUG_TYPE", "acp") wait_for_attach = os.getenv("AGENTEX_DEBUG_WAIT_FOR_ATTACH", "false").lower() == "true" - + # Configure debugpy debugpy.configure(subProcess=False) debugpy.listen(debug_port) - - print(f"🐛 [{debug_type.upper()}] Debug server listening on port {debug_port}") - + + logger.info(f"🐛 [{debug_type.upper()}] Debug server listening on port {debug_port}") + if wait_for_attach: - print(f"⏳ [{debug_type.upper()}] Waiting for debugger to attach...") + logger.info(f"⏳ [{debug_type.upper()}] Waiting for debugger to attach...") debugpy.wait_for_client() - print(f"✅ [{debug_type.upper()}] Debugger attached!") + logger.info(f"✅ [{debug_type.upper()}] Debugger attached!") else: - print(f"📡 [{debug_type.upper()}] Ready for debugger attachment") - + logger.info(f"📡 [{debug_type.upper()}] Ready for debugger attachment") + except ImportError: print("❌ debugpy not available. Install with: pip install debugpy") sys.exit(1) diff --git a/src/agentex/lib/cli/utils/auth_utils.py b/src/agentex/lib/cli/utils/auth_utils.py index 9ffbc6be..e572f9e0 100644 --- a/src/agentex/lib/cli/utils/auth_utils.py +++ b/src/agentex/lib/cli/utils/auth_utils.py @@ -1,13 +1,14 @@ -import base64 import json +import base64 from typing import Any, Dict from agentex.lib.sdk.config.agent_manifest import AgentManifest from agentex.lib.sdk.config.environment_config import AgentAuthConfig + # DEPRECATED: Old function for backward compatibility # Will be removed in future version -def _encode_principal_context(manifest: AgentManifest) -> str | None: +def _encode_principal_context(manifest: AgentManifest) -> str | None: # noqa: ARG001 """ DEPRECATED: This function is deprecated as AgentManifest no longer contains auth. Use _encode_principal_context_from_env_config instead. diff --git a/src/agentex/lib/cli/utils/credential_utils.py b/src/agentex/lib/cli/utils/credential_utils.py index fae4dea6..5ad2471f 100644 --- a/src/agentex/lib/cli/utils/credential_utils.py +++ b/src/agentex/lib/cli/utils/credential_utils.py @@ -1,7 +1,7 @@ import subprocess +from rich.prompt import Prompt, Confirm from rich.console import Console -from rich.prompt import Confirm, Prompt from agentex.lib.types.credentials import CredentialMapping diff --git a/src/agentex/lib/cli/utils/kubectl_utils.py b/src/agentex/lib/cli/utils/kubectl_utils.py index abdcf2bf..c271acc6 100644 --- a/src/agentex/lib/cli/utils/kubectl_utils.py +++ b/src/agentex/lib/cli/utils/kubectl_utils.py @@ -1,11 +1,11 @@ import subprocess from kubernetes import client, config -from kubernetes.client.rest import ApiException from rich.console import Console +from kubernetes.client.rest import ApiException -from agentex.lib.cli.utils.exceptions import DeploymentError from agentex.lib.utils.logging import make_logger +from agentex.lib.cli.utils.exceptions import DeploymentError logger = make_logger(__name__) console = Console() @@ -59,7 +59,7 @@ def list_available_contexts() -> list[str]: """List all available kubectl contexts""" try: contexts, _ = config.list_kube_config_contexts() - return [ctx["name"] for ctx in contexts] + return [ctx["name"] for ctx in contexts] # type: ignore[index] except Exception as e: raise DeploymentError(f"Failed to list kubectl contexts: {e}") from e diff --git a/src/agentex/lib/cli/utils/kubernetes_secrets_utils.py b/src/agentex/lib/cli/utils/kubernetes_secrets_utils.py index 12a843bf..a96d8dc4 100644 --- a/src/agentex/lib/cli/utils/kubernetes_secrets_utils.py +++ b/src/agentex/lib/cli/utils/kubernetes_secrets_utils.py @@ -1,11 +1,11 @@ import base64 from kubernetes import client -from kubernetes.client.rest import ApiException from rich.console import Console +from kubernetes.client.rest import ApiException -from agentex.lib.cli.utils.kubectl_utils import get_k8s_client from agentex.lib.utils.logging import make_logger +from agentex.lib.cli.utils.kubectl_utils import get_k8s_client logger = make_logger(__name__) console = Console() @@ -172,11 +172,11 @@ def get_secret_data( v1 = get_k8s_client(context) try: secret = v1.read_namespaced_secret(name=name, namespace=namespace) - if secret.data: + if secret.data: # type: ignore[union-attr] # Decode base64 data return { key: base64.b64decode(value).decode("utf-8") - for key, value in secret.data.items() + for key, value in secret.data.items() # type: ignore[union-attr] } return {} except ApiException as e: diff --git a/src/agentex/lib/cli/utils/path_utils.py b/src/agentex/lib/cli/utils/path_utils.py index cdbb0a71..6217ead7 100644 --- a/src/agentex/lib/cli/utils/path_utils.py +++ b/src/agentex/lib/cli/utils/path_utils.py @@ -1,8 +1,8 @@ -from pathlib import Path from typing import Dict +from pathlib import Path -from agentex.lib.sdk.config.agent_manifest import AgentManifest from agentex.lib.utils.logging import make_logger +from agentex.lib.sdk.config.agent_manifest import AgentManifest logger = make_logger(__name__) diff --git a/src/agentex/lib/core/adapters/llm/adapter_litellm.py b/src/agentex/lib/core/adapters/llm/adapter_litellm.py index cbd69585..7935f5f4 100644 --- a/src/agentex/lib/core/adapters/llm/adapter_litellm.py +++ b/src/agentex/lib/core/adapters/llm/adapter_litellm.py @@ -1,15 +1,17 @@ -from collections.abc import AsyncGenerator, Generator +from typing import override +from collections.abc import Generator, AsyncGenerator import litellm as llm -from agentex.lib.core.adapters.llm.port import LLMGateway -from agentex.lib.types.llm_messages import Completion from agentex.lib.utils.logging import make_logger +from agentex.lib.types.llm_messages import Completion +from agentex.lib.core.adapters.llm.port import LLMGateway logger = make_logger(__name__) class LiteLLMGateway(LLMGateway): + @override def completion(self, *args, **kwargs) -> Completion: if kwargs.get("stream", True): raise ValueError( @@ -19,6 +21,7 @@ def completion(self, *args, **kwargs) -> Completion: response = llm.completion(*args, **kwargs) return Completion.model_validate(response) + @override def completion_stream(self, *args, **kwargs) -> Generator[Completion, None, None]: if not kwargs.get("stream"): raise ValueError("To use streaming, please set stream=True in the kwargs") @@ -26,6 +29,7 @@ def completion_stream(self, *args, **kwargs) -> Generator[Completion, None, None for chunk in llm.completion(*args, **kwargs): yield Completion.model_validate(chunk) + @override async def acompletion(self, *args, **kwargs) -> Completion: if kwargs.get("stream", True): raise ValueError( @@ -36,11 +40,12 @@ async def acompletion(self, *args, **kwargs) -> Completion: response = await llm.acompletion(*args, **kwargs) return Completion.model_validate(response) + @override async def acompletion_stream( self, *args, **kwargs ) -> AsyncGenerator[Completion, None]: if not kwargs.get("stream"): raise ValueError("To use streaming, please set stream=True in the kwargs") - async for chunk in await llm.acompletion(*args, **kwargs): + async for chunk in await llm.acompletion(*args, **kwargs): # type: ignore[misc] yield Completion.model_validate(chunk) diff --git a/src/agentex/lib/core/adapters/llm/adapter_sgp.py b/src/agentex/lib/core/adapters/llm/adapter_sgp.py index a14e66a2..0920a02e 100644 --- a/src/agentex/lib/core/adapters/llm/adapter_sgp.py +++ b/src/agentex/lib/core/adapters/llm/adapter_sgp.py @@ -1,11 +1,12 @@ import os -from collections.abc import AsyncGenerator, Generator +from typing import override +from collections.abc import Generator, AsyncGenerator -from scale_gp import AsyncSGPClient, SGPClient +from scale_gp import SGPClient, AsyncSGPClient -from agentex.lib.core.adapters.llm.port import LLMGateway -from agentex.lib.types.llm_messages import Completion from agentex.lib.utils.logging import make_logger +from agentex.lib.types.llm_messages import Completion +from agentex.lib.core.adapters.llm.port import LLMGateway logger = make_logger(__name__) @@ -17,6 +18,7 @@ def __init__(self, sgp_api_key: str | None = None): api_key=os.environ.get("SGP_API_KEY", sgp_api_key) ) + @override def completion(self, *args, **kwargs) -> Completion: if kwargs.get("stream", True): raise ValueError( @@ -26,6 +28,7 @@ def completion(self, *args, **kwargs) -> Completion: response = self.sync_client.beta.chat.completions.create(*args, **kwargs) return Completion.model_validate(response) + @override def completion_stream(self, *args, **kwargs) -> Generator[Completion, None, None]: if not kwargs.get("stream"): raise ValueError("To use streaming, please set stream=True in the kwargs") @@ -33,6 +36,7 @@ def completion_stream(self, *args, **kwargs) -> Generator[Completion, None, None for chunk in self.sync_client.beta.chat.completions.create(*args, **kwargs): yield Completion.model_validate(chunk) + @override async def acompletion(self, *args, **kwargs) -> Completion: if kwargs.get("stream", True): raise ValueError( @@ -43,13 +47,12 @@ async def acompletion(self, *args, **kwargs) -> Completion: response = await self.async_client.beta.chat.completions.create(*args, **kwargs) return Completion.model_validate(response) + @override async def acompletion_stream( self, *args, **kwargs ) -> AsyncGenerator[Completion, None]: if not kwargs.get("stream"): raise ValueError("To use streaming, please set stream=True in the kwargs") - async for chunk in await self.async_client.beta.chat.completions.create( - *args, **kwargs - ): + async for chunk in self.async_client.beta.chat.completions.create(*args, **kwargs): # type: ignore[misc] yield Completion.model_validate(chunk) diff --git a/src/agentex/lib/core/adapters/llm/port.py b/src/agentex/lib/core/adapters/llm/port.py index f9eeebeb..4daaade4 100644 --- a/src/agentex/lib/core/adapters/llm/port.py +++ b/src/agentex/lib/core/adapters/llm/port.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from collections.abc import AsyncGenerator, Generator +from collections.abc import Generator, AsyncGenerator from agentex.lib.types.llm_messages import Completion diff --git a/src/agentex/lib/core/adapters/streams/adapter_redis.py b/src/agentex/lib/core/adapters/streams/adapter_redis.py index d61f5407..51967a94 100644 --- a/src/agentex/lib/core/adapters/streams/adapter_redis.py +++ b/src/agentex/lib/core/adapters/streams/adapter_redis.py @@ -1,14 +1,14 @@ -import asyncio -import json import os +import json +import asyncio +from typing import Any, Annotated, override from collections.abc import AsyncIterator -from typing import Annotated, Any import redis.asyncio as redis from fastapi import Depends -from agentex.lib.core.adapters.streams.port import StreamRepository from agentex.lib.utils.logging import make_logger +from agentex.lib.core.adapters.streams.port import StreamRepository logger = make_logger(__name__) @@ -26,6 +26,7 @@ def __init__(self, redis_url: str | None = None): ) self.redis = redis.from_url(self.redis_url) + @override async def send_event(self, topic: str, event: dict[str, Any]) -> str: """ Send an event to a Redis stream. @@ -55,6 +56,7 @@ async def send_event(self, topic: str, event: dict[str, Any]) -> str: logger.error(f"Error publishing to Redis stream {topic}: {e}") raise + @override async def subscribe( self, topic: str, last_id: str = "$" ) -> AsyncIterator[dict[str, Any]]: @@ -108,6 +110,7 @@ async def subscribe( logger.error(f"Error reading from Redis stream: {e}") await asyncio.sleep(1) # Back off on errors + @override async def cleanup_stream(self, topic: str) -> None: """ Clean up a Redis stream. diff --git a/src/agentex/lib/core/adapters/streams/port.py b/src/agentex/lib/core/adapters/streams/port.py index 17992ab1..b2e58cd5 100644 --- a/src/agentex/lib/core/adapters/streams/port.py +++ b/src/agentex/lib/core/adapters/streams/port.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod -from collections.abc import AsyncIterator from typing import Any +from collections.abc import AsyncIterator class StreamRepository(ABC): diff --git a/src/agentex/lib/core/clients/temporal/temporal_client.py b/src/agentex/lib/core/clients/temporal/temporal_client.py index f8db8559..bdf11810 100644 --- a/src/agentex/lib/core/clients/temporal/temporal_client.py +++ b/src/agentex/lib/core/clients/temporal/temporal_client.py @@ -1,25 +1,29 @@ -from collections.abc import Callable -from datetime import timedelta from typing import Any +from datetime import timedelta +from collections.abc import Callable from temporalio.client import Client, WorkflowExecutionStatus -from temporalio.common import RetryPolicy as TemporalRetryPolicy -from temporalio.common import WorkflowIDReusePolicy +from temporalio.common import RetryPolicy as TemporalRetryPolicy, WorkflowIDReusePolicy from temporalio.service import RPCError, RPCStatusCode +from agentex.lib.utils.logging import make_logger +from agentex.lib.utils.model_utils import BaseModel from agentex.lib.core.clients.temporal.types import ( - DuplicateWorkflowPolicy, - RetryPolicy, TaskStatus, + RetryPolicy, WorkflowState, + DuplicateWorkflowPolicy, ) from agentex.lib.core.clients.temporal.utils import get_temporal_client -from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.model_utils import BaseModel logger = make_logger(__name__) -DEFAULT_RETRY_POLICY = RetryPolicy(maximum_attempts=1) +DEFAULT_RETRY_POLICY = RetryPolicy( + maximum_attempts=1, + initial_interval=timedelta(seconds=1), + backoff_coefficient=2.0, + maximum_interval=timedelta(minutes=10), +) TEMPORAL_STATUS_TO_UPLOAD_STATUS_AND_REASON = { @@ -36,8 +40,7 @@ ), WorkflowExecutionStatus.FAILED: WorkflowState( status=TaskStatus.FAILED, - reason="Task encountered terminal failure. " - "Please contact support if retrying does not resolve the issue.", + reason="Task encountered terminal failure. Please contact support if retrying does not resolve the issue.", is_terminal=True, ), WorkflowExecutionStatus.RUNNING: WorkflowState( @@ -72,9 +75,16 @@ class TemporalClient: def __init__(self, temporal_client: Client | None = None, plugins: list[Any] = []): - self._client: Client = temporal_client + self._client: Client | None = temporal_client self._plugins = plugins + @property + def client(self) -> Client: + """Get the temporal client, raising an error if not initialized.""" + if self._client is None: + raise RuntimeError("Temporal client not initialized - ensure temporal_address is properly configured") + return self._client + @classmethod async def create(cls, temporal_address: str, plugins: list[Any] = []): if temporal_address in [ @@ -89,18 +99,13 @@ async def create(cls, temporal_address: str, plugins: list[Any] = []): ]: _client = None else: - _client = await get_temporal_client( - temporal_address, - plugins=plugins - ) + _client = await get_temporal_client(temporal_address, plugins=plugins) return cls(_client, plugins) async def setup(self, temporal_address: str): - self._client = await self._get_temporal_client( - temporal_address=temporal_address - ) + self._client = await self._get_temporal_client(temporal_address=temporal_address) - async def _get_temporal_client(self, temporal_address: str) -> Client: + async def _get_temporal_client(self, temporal_address: str) -> Client | None: if temporal_address in [ "false", "False", @@ -113,10 +118,7 @@ async def _get_temporal_client(self, temporal_address: str) -> Client: ]: return None else: - return await get_temporal_client( - temporal_address, - plugins=self._plugins - ) + return await get_temporal_client(temporal_address, plugins=self._plugins) async def start_workflow( self, @@ -127,10 +129,8 @@ async def start_workflow( execution_timeout: timedelta = timedelta(seconds=86400), **kwargs: Any, ) -> str: - temporal_retry_policy = TemporalRetryPolicy( - **retry_policy.model_dump(exclude_unset=True) - ) - workflow_handle = await self._client.start_workflow( + temporal_retry_policy = TemporalRetryPolicy(**retry_policy.model_dump(exclude_unset=True)) + workflow_handle = await self.client.start_workflow( *args, retry_policy=temporal_retry_policy, task_timeout=task_timeout, @@ -146,8 +146,8 @@ async def send_signal( signal: str | Callable[[dict[str, Any] | list[Any] | str | int | float | bool | BaseModel], Any], payload: dict[str, Any] | list[Any] | str | int | float | bool | BaseModel, ) -> None: - handle = self._client.get_workflow_handle(workflow_id=workflow_id) - await handle.signal(signal, payload) + handle = self.client.get_workflow_handle(workflow_id=workflow_id) + await handle.signal(signal, payload) # type: ignore[misc] async def query_workflow( self, @@ -164,12 +164,12 @@ async def query_workflow( Returns: The result of the query """ - handle = self._client.get_workflow_handle(workflow_id=workflow_id) + handle = self.client.get_workflow_handle(workflow_id=workflow_id) return await handle.query(query) async def get_workflow_status(self, workflow_id: str) -> WorkflowState: try: - handle = self._client.get_workflow_handle(workflow_id=workflow_id) + handle = self.client.get_workflow_handle(workflow_id=workflow_id) description = await handle.describe() return TEMPORAL_STATUS_TO_UPLOAD_STATUS_AND_REASON[description.status] except RPCError as e: @@ -182,7 +182,7 @@ async def get_workflow_status(self, workflow_id: str) -> WorkflowState: raise async def terminate_workflow(self, workflow_id: str) -> None: - return await self._client.get_workflow_handle(workflow_id).terminate() + return await self.client.get_workflow_handle(workflow_id).terminate() async def cancel_workflow(self, workflow_id: str) -> None: - return await self._client.get_workflow_handle(workflow_id).cancel() + return await self.client.get_workflow_handle(workflow_id).cancel() diff --git a/src/agentex/lib/core/clients/temporal/types.py b/src/agentex/lib/core/clients/temporal/types.py index 91f3db54..4cd912ae 100644 --- a/src/agentex/lib/core/clients/temporal/types.py +++ b/src/agentex/lib/core/clients/temporal/types.py @@ -1,5 +1,5 @@ -from datetime import timedelta from enum import Enum +from datetime import timedelta from pydantic import Field diff --git a/src/agentex/lib/core/clients/temporal/utils.py b/src/agentex/lib/core/clients/temporal/utils.py index 4d9f4f73..0c2d5fff 100644 --- a/src/agentex/lib/core/clients/temporal/utils.py +++ b/src/agentex/lib/core/clients/temporal/utils.py @@ -1,7 +1,8 @@ from typing import Any + from temporalio.client import Client, Plugin as ClientPlugin +from temporalio.runtime import Runtime, TelemetryConfig, OpenTelemetryConfig from temporalio.contrib.pydantic import pydantic_data_converter -from temporalio.runtime import OpenTelemetryConfig, Runtime, TelemetryConfig # class DateTimeJSONEncoder(AdvancedJSONEncoder): # def default(self, o: Any) -> Any: @@ -42,10 +43,10 @@ def validate_client_plugins(plugins: list[Any]) -> None: """ Validate that all items in the plugins list are valid Temporal client plugins. - + Args: plugins: List of plugins to validate - + Raises: TypeError: If any plugin is not a valid ClientPlugin instance """ @@ -57,26 +58,22 @@ def validate_client_plugins(plugins: list[Any]) -> None: ) -async def get_temporal_client( - temporal_address: str, - metrics_url: str = None, - plugins: list[Any] = [] -) -> Client: +async def get_temporal_client(temporal_address: str, metrics_url: str | None = None, plugins: list[Any] = []) -> Client: """ Create a Temporal client with plugin integration. - + Args: temporal_address: Temporal server address - metrics_url: Optional metrics endpoint URL + metrics_url: Optional metrics endpoint URL plugins: List of Temporal plugins to include - + Returns: Configured Temporal client """ # Validate plugins if any are provided if plugins: validate_client_plugins(plugins) - + if not metrics_url: client = await Client.connect( target_host=temporal_address, diff --git a/src/agentex/lib/core/services/adk/acp/acp.py b/src/agentex/lib/core/services/adk/acp/acp.py index e07a6548..5ba3e39f 100644 --- a/src/agentex/lib/core/services/adk/acp/acp.py +++ b/src/agentex/lib/core/services/adk/acp/acp.py @@ -1,18 +1,18 @@ from typing import Any, List, cast from agentex import AsyncAgentex -from agentex.lib.core.tracing.tracer import AsyncTracer +from agentex.types.task import Task +from agentex.types.event import Event from agentex.lib.utils.logging import make_logger from agentex.lib.utils.temporal import heartbeat_if_in_workflow -from agentex.types.event import Event -from agentex.types.task import Task from agentex.types.task_message import TaskMessage -from agentex.types.task_message_content import TaskMessageContent -from agentex.types.task_message_content_param import TaskMessageContentParam from agentex.types.agent_rpc_params import ( - ParamsCancelTaskRequest as RpcParamsCancelTaskRequest, ParamsSendEventRequest as RpcParamsSendEventRequest, + ParamsCancelTaskRequest as RpcParamsCancelTaskRequest, ) +from agentex.lib.core.tracing.tracer import AsyncTracer +from agentex.types.task_message_content import TaskMessageContent +from agentex.types.task_message_content_param import TaskMessageContentParam logger = make_logger(__name__) diff --git a/src/agentex/lib/core/services/adk/agent_task_tracker.py b/src/agentex/lib/core/services/adk/agent_task_tracker.py index ad9dc42c..d7f2e1b8 100644 --- a/src/agentex/lib/core/services/adk/agent_task_tracker.py +++ b/src/agentex/lib/core/services/adk/agent_task_tracker.py @@ -1,6 +1,6 @@ from agentex import AsyncAgentex -from agentex.lib.core.tracing.tracer import AsyncTracer from agentex.lib.utils.logging import make_logger +from agentex.lib.core.tracing.tracer import AsyncTracer from agentex.types.agent_task_tracker import AgentTaskTracker logger = make_logger(__name__) diff --git a/src/agentex/lib/core/services/adk/agents.py b/src/agentex/lib/core/services/adk/agents.py index 7600c7be..1d26b9d5 100644 --- a/src/agentex/lib/core/services/adk/agents.py +++ b/src/agentex/lib/core/services/adk/agents.py @@ -1,10 +1,10 @@ from typing import Optional from agentex import AsyncAgentex -from agentex.lib.core.tracing.tracer import AsyncTracer from agentex.types.agent import Agent from agentex.lib.utils.logging import make_logger from agentex.lib.utils.temporal import heartbeat_if_in_workflow +from agentex.lib.core.tracing.tracer import AsyncTracer logger = make_logger(__name__) diff --git a/src/agentex/lib/core/services/adk/events.py b/src/agentex/lib/core/services/adk/events.py index 83994604..713c8349 100644 --- a/src/agentex/lib/core/services/adk/events.py +++ b/src/agentex/lib/core/services/adk/events.py @@ -1,7 +1,7 @@ from agentex import AsyncAgentex -from agentex.lib.core.tracing.tracer import AsyncTracer from agentex.types.event import Event from agentex.lib.utils.logging import make_logger +from agentex.lib.core.tracing.tracer import AsyncTracer logger = make_logger(__name__) diff --git a/src/agentex/lib/core/services/adk/messages.py b/src/agentex/lib/core/services/adk/messages.py index d7ab5216..4a4183f2 100644 --- a/src/agentex/lib/core/services/adk/messages.py +++ b/src/agentex/lib/core/services/adk/messages.py @@ -1,14 +1,13 @@ import asyncio -from typing import Any, Coroutine, cast +from typing import Any, Coroutine from agentex import AsyncAgentex -from agentex.lib.core.services.adk.streaming import StreamingService -from agentex.lib.core.tracing.tracer import AsyncTracer -from agentex.types.task_message_update import StreamTaskMessageFull, TaskMessageUpdate -from agentex.types.task_message import TaskMessage, TaskMessageContent from agentex.lib.utils.logging import make_logger from agentex.lib.utils.temporal import heartbeat_if_in_workflow -from agentex.types.task_message_content_param import TaskMessageContentParam +from agentex.types.task_message import TaskMessage, TaskMessageContent +from agentex.lib.core.tracing.tracer import AsyncTracer +from agentex.types.task_message_update import TaskMessageUpdate, StreamTaskMessageFull +from agentex.lib.core.services.adk.streaming import StreamingService logger = make_logger(__name__) diff --git a/src/agentex/lib/core/services/adk/providers/litellm.py b/src/agentex/lib/core/services/adk/providers/litellm.py index 072717d5..52c12a87 100644 --- a/src/agentex/lib/core/services/adk/providers/litellm.py +++ b/src/agentex/lib/core/services/adk/providers/litellm.py @@ -1,23 +1,23 @@ from collections.abc import AsyncGenerator from agentex import AsyncAgentex -from agentex.lib.core.adapters.llm.adapter_litellm import LiteLLMGateway -from agentex.lib.core.services.adk.streaming import StreamingService -from agentex.lib.core.tracing.tracer import AsyncTracer +from agentex.lib.utils import logging +from agentex.lib.utils.temporal import heartbeat_if_in_workflow +from agentex.types.task_message import TaskMessage +from agentex.lib.utils.completions import concat_completion_chunks from agentex.lib.types.llm_messages import ( - Completion, LLMConfig, + Completion, ) +from agentex.lib.core.tracing.tracer import AsyncTracer +from agentex.types.task_message_delta import TextDelta from agentex.types.task_message_update import ( - StreamTaskMessageDelta, StreamTaskMessageFull, + StreamTaskMessageDelta, ) -from agentex.types.task_message_delta import TextDelta -from agentex.types.task_message import TaskMessage from agentex.types.task_message_content import TextContent -from agentex.lib.utils import logging -from agentex.lib.utils.completions import concat_completion_chunks -from agentex.lib.utils.temporal import heartbeat_if_in_workflow +from agentex.lib.core.services.adk.streaming import StreamingService +from agentex.lib.core.adapters.llm.adapter_litellm import LiteLLMGateway logger = logging.make_logger(__name__) diff --git a/src/agentex/lib/core/services/adk/providers/openai.py b/src/agentex/lib/core/services/adk/providers/openai.py index 31631a94..6e2cb593 100644 --- a/src/agentex/lib/core/services/adk/providers/openai.py +++ b/src/agentex/lib/core/services/adk/providers/openai.py @@ -1,51 +1,51 @@ # Standard library imports -from contextlib import AsyncExitStack, asynccontextmanager from typing import Any, Literal +from contextlib import AsyncExitStack, asynccontextmanager +from mcp import StdioServerParameters from agents import Agent, Runner, RunResult, RunResultStreaming +from pydantic import BaseModel +from agents.mcp import MCPServerStdio from agents.agent import StopAtTools, ToolsToFinalOutputFunction from agents.guardrail import InputGuardrail, OutputGuardrail from agents.exceptions import InputGuardrailTripwireTriggered, OutputGuardrailTripwireTriggered -from agents.mcp import MCPServerStdio -from mcp import StdioServerParameters from openai.types.responses import ( ResponseCompletedEvent, + ResponseTextDeltaEvent, ResponseFunctionWebSearch, - ResponseCodeInterpreterToolCall, ResponseOutputItemDoneEvent, - ResponseTextDeltaEvent, - ResponseReasoningSummaryTextDeltaEvent, - ResponseReasoningSummaryTextDoneEvent, - ResponseReasoningTextDeltaEvent, ResponseReasoningTextDoneEvent, + ResponseCodeInterpreterToolCall, + ResponseReasoningTextDeltaEvent, + ResponseReasoningSummaryTextDoneEvent, + ResponseReasoningSummaryTextDeltaEvent, ) -from pydantic import BaseModel # Local imports from agentex import AsyncAgentex -from agentex.lib.core.services.adk.streaming import ( - StreamingService, - StreamingTaskMessageContext, -) +from agentex.lib.utils import logging +from agentex.lib.utils.mcp import redact_mcp_server_params +from agentex.lib.utils.temporal import heartbeat_if_in_workflow from agentex.lib.core.tracing.tracer import AsyncTracer -from agentex.types.task_message_update import ( - StreamTaskMessageDelta, - StreamTaskMessageFull, -) from agentex.types.task_message_delta import ( TextDelta, - ReasoningSummaryDelta, ReasoningContentDelta, + ReasoningSummaryDelta, +) +from agentex.types.task_message_update import ( + StreamTaskMessageFull, + StreamTaskMessageDelta, ) from agentex.types.task_message_content import ( - ReasoningContent, TextContent, + ReasoningContent, ToolRequestContent, ToolResponseContent, ) -from agentex.lib.utils import logging -from agentex.lib.utils.mcp import redact_mcp_server_params -from agentex.lib.utils.temporal import heartbeat_if_in_workflow +from agentex.lib.core.services.adk.streaming import ( + StreamingService, + StreamingTaskMessageContext, +) logger = logging.make_logger(__name__) @@ -141,15 +141,15 @@ def _extract_tool_response_info(self, tool_call_map: dict[str, Any], tool_output content = tool_output_item["output"] else: # Attribute access for structured objects - call_id = getattr(tool_output_item, "call_id", None) - content = getattr(tool_output_item, "output", None) + call_id = getattr(tool_output_item, "call_id", "") + content = getattr(tool_output_item, "output", "") # Get the name from the tool call map using generic approach tool_call = tool_call_map[call_id] if hasattr(tool_call, "name"): - tool_name = getattr(tool_call, "name") + tool_name = tool_call.name elif hasattr(tool_call, "type"): - tool_name = getattr(tool_call, "type") + tool_name = tool_call.type else: tool_name = type(tool_call).__name__ @@ -176,7 +176,7 @@ async def run_agent( input_guardrails: list[InputGuardrail] | None = None, output_guardrails: list[OutputGuardrail] | None = None, max_turns: int | None = None, - previous_response_id: str | None = None, + previous_response_id: str | None = None, # noqa: ARG002 ) -> RunResult: """ Run an agent without streaming or TaskMessage creation. @@ -208,6 +208,8 @@ async def run_agent( """ redacted_params = redact_mcp_server_params(mcp_server_params) + if self.tracer is None: + raise RuntimeError("Tracer not initialized - ensure tracer is provided to OpenAIService") trace = self.tracer.trace(trace_id) async with trace.span( parent_id=parent_span_id, @@ -230,8 +232,11 @@ async def run_agent( heartbeat_if_in_workflow("run agent") async with mcp_server_context(mcp_server_params, mcp_timeout_seconds) as servers: - tools = [tool.to_oai_function_tool() for tool in tools] if tools else [] - handoffs = [Agent(**handoff.model_dump()) for handoff in handoffs] if handoffs else [] + tools = [ + tool.to_oai_function_tool() if hasattr(tool, 'to_oai_function_tool') else tool # type: ignore[attr-defined] + for tool in tools + ] if tools else [] + handoffs = [Agent(**handoff.model_dump()) for handoff in handoffs] if handoffs else [] # type: ignore[misc] agent_kwargs = { "name": agent_name, @@ -245,7 +250,10 @@ async def run_agent( "tool_use_behavior": tool_use_behavior, } if model_settings is not None: - agent_kwargs["model_settings"] = model_settings.to_oai_model_settings() + agent_kwargs["model_settings"] = ( + model_settings.to_oai_model_settings() if hasattr(model_settings, 'to_oai_model_settings') # type: ignore[attr-defined] + else model_settings + ) if input_guardrails is not None: agent_kwargs["input_guardrails"] = input_guardrails if output_guardrails is not None: @@ -303,7 +311,7 @@ async def run_agent_auto_send( input_guardrails: list[InputGuardrail] | None = None, output_guardrails: list[OutputGuardrail] | None = None, max_turns: int | None = None, - previous_response_id: str | None = None, + previous_response_id: str | None = None, # noqa: ARG002 ) -> RunResult: """ Run an agent with automatic TaskMessage creation. @@ -337,6 +345,8 @@ async def run_agent_auto_send( redacted_params = redact_mcp_server_params(mcp_server_params) + if self.tracer is None: + raise RuntimeError("Tracer not initialized - ensure tracer is provided to OpenAIService") trace = self.tracer.trace(trace_id) async with trace.span( parent_id=parent_span_id, @@ -360,8 +370,11 @@ async def run_agent_auto_send( heartbeat_if_in_workflow("run agent auto send") async with mcp_server_context(mcp_server_params, mcp_timeout_seconds) as servers: - tools = [tool.to_oai_function_tool() for tool in tools] if tools else [] - handoffs = [Agent(**handoff.model_dump()) for handoff in handoffs] if handoffs else [] + tools = [ + tool.to_oai_function_tool() if hasattr(tool, 'to_oai_function_tool') else tool # type: ignore[attr-defined] + for tool in tools + ] if tools else [] + handoffs = [Agent(**handoff.model_dump()) for handoff in handoffs] if handoffs else [] # type: ignore[misc] agent_kwargs = { "name": agent_name, "instructions": agent_instructions, @@ -374,7 +387,10 @@ async def run_agent_auto_send( "tool_use_behavior": tool_use_behavior, } if model_settings is not None: - agent_kwargs["model_settings"] = model_settings.to_oai_model_settings() + agent_kwargs["model_settings"] = ( + model_settings.to_oai_model_settings() if hasattr(model_settings, 'to_oai_model_settings') # type: ignore[attr-defined] + else model_settings + ) if input_guardrails is not None: agent_kwargs["input_guardrails"] = input_guardrails if output_guardrails is not None: @@ -414,7 +430,7 @@ async def run_agent_auto_send( if item.type == "message_output_item": text_content = TextContent( author="agent", - content=item.raw_item.content[0].text, + content=item.raw_item.content[0].text, # type: ignore[union-attr] ) # Create message for the final result using streaming context async with self.streaming_service.streaming_task_message_context( @@ -504,7 +520,7 @@ async def run_agent_streamed( input_guardrails: list[InputGuardrail] | None = None, output_guardrails: list[OutputGuardrail] | None = None, max_turns: int | None = None, - previous_response_id: str | None = None, + previous_response_id: str | None = None, # noqa: ARG002 ) -> RunResultStreaming: """ Run an agent with streaming enabled but no TaskMessage creation. @@ -534,6 +550,8 @@ async def run_agent_streamed( Returns: RunResultStreaming: The result of the agent run with streaming. """ + if self.tracer is None: + raise RuntimeError("Tracer not initialized - ensure tracer is provided to OpenAIService") trace = self.tracer.trace(trace_id) redacted_params = redact_mcp_server_params(mcp_server_params) @@ -558,8 +576,11 @@ async def run_agent_streamed( heartbeat_if_in_workflow("run agent streamed") async with mcp_server_context(mcp_server_params, mcp_timeout_seconds) as servers: - tools = [tool.to_oai_function_tool() for tool in tools] if tools else [] - handoffs = [Agent(**handoff.model_dump()) for handoff in handoffs] if handoffs else [] + tools = [ + tool.to_oai_function_tool() if hasattr(tool, 'to_oai_function_tool') else tool # type: ignore[attr-defined] + for tool in tools + ] if tools else [] + handoffs = [Agent(**handoff.model_dump()) for handoff in handoffs] if handoffs else [] # type: ignore[misc] agent_kwargs = { "name": agent_name, "instructions": agent_instructions, @@ -572,7 +593,10 @@ async def run_agent_streamed( "tool_use_behavior": tool_use_behavior, } if model_settings is not None: - agent_kwargs["model_settings"] = model_settings.to_oai_model_settings() + agent_kwargs["model_settings"] = ( + model_settings.to_oai_model_settings() if hasattr(model_settings, 'to_oai_model_settings') # type: ignore[attr-defined] + else model_settings + ) if input_guardrails is not None: agent_kwargs["input_guardrails"] = input_guardrails if output_guardrails is not None: @@ -630,7 +654,7 @@ async def run_agent_streamed_auto_send( input_guardrails: list[InputGuardrail] | None = None, output_guardrails: list[OutputGuardrail] | None = None, max_turns: int | None = None, - previous_response_id: str | None = None, + previous_response_id: str | None = None, # noqa: ARG002 ) -> RunResultStreaming: """ Run an agent with streaming enabled and automatic TaskMessage creation. @@ -669,6 +693,8 @@ async def run_agent_streamed_auto_send( tool_call_map: dict[str, Any] = {} + if self.tracer is None: + raise RuntimeError("Tracer not initialized - ensure tracer is provided to OpenAIService") trace = self.tracer.trace(trace_id) redacted_params = redact_mcp_server_params(mcp_server_params) @@ -694,8 +720,11 @@ async def run_agent_streamed_auto_send( heartbeat_if_in_workflow("run agent streamed auto send") async with mcp_server_context(mcp_server_params, mcp_timeout_seconds) as servers: - tools = [tool.to_oai_function_tool() for tool in tools] if tools else [] - handoffs = [Agent(**handoff.model_dump()) for handoff in handoffs] if handoffs else [] + tools = [ + tool.to_oai_function_tool() if hasattr(tool, 'to_oai_function_tool') else tool # type: ignore[attr-defined] + for tool in tools + ] if tools else [] + handoffs = [Agent(**handoff.model_dump()) for handoff in handoffs] if handoffs else [] # type: ignore[misc] agent_kwargs = { "name": agent_name, "instructions": agent_instructions, @@ -708,7 +737,10 @@ async def run_agent_streamed_auto_send( "tool_use_behavior": tool_use_behavior, } if model_settings is not None: - agent_kwargs["model_settings"] = model_settings.to_oai_model_settings() + agent_kwargs["model_settings"] = ( + model_settings.to_oai_model_settings() if hasattr(model_settings, 'to_oai_model_settings') # type: ignore[attr-defined] + else model_settings + ) if input_guardrails is not None: agent_kwargs["input_guardrails"] = input_guardrails if output_guardrails is not None: diff --git a/src/agentex/lib/core/services/adk/providers/sgp.py b/src/agentex/lib/core/services/adk/providers/sgp.py index 4fbbd9f0..dce8ecff 100644 --- a/src/agentex/lib/core/services/adk/providers/sgp.py +++ b/src/agentex/lib/core/services/adk/providers/sgp.py @@ -1,13 +1,13 @@ -import base64 import os +import base64 import tempfile from scale_gp import SGPClient -from agentex.lib.core.tracing.tracer import AsyncTracer from agentex.lib.types.files import FileContentResponse from agentex.lib.utils.logging import make_logger from agentex.lib.utils.temporal import heartbeat_if_in_workflow +from agentex.lib.core.tracing.tracer import AsyncTracer logger = make_logger(__name__) @@ -91,7 +91,7 @@ async def download_file_content( ) # Record metadata for tracing - span.output = { + span.output = { # type: ignore[union-attr] "file_id": file_id, "mime_type": result.mime_type, "content_size": len(result.base64_content), diff --git a/src/agentex/lib/core/services/adk/state.py b/src/agentex/lib/core/services/adk/state.py index e27ee41f..49ae1825 100644 --- a/src/agentex/lib/core/services/adk/state.py +++ b/src/agentex/lib/core/services/adk/state.py @@ -1,9 +1,9 @@ from typing import Any, Dict from agentex import AsyncAgentex -from agentex.lib.core.tracing.tracer import AsyncTracer from agentex.types.state import State from agentex.lib.utils.logging import make_logger +from agentex.lib.core.tracing.tracer import AsyncTracer logger = make_logger(__name__) @@ -47,6 +47,10 @@ async def get_state( parent_span_id: str | None = None, ) -> State | None: trace = self._tracer.trace(trace_id) if self._tracer else None + if trace is None: + # Handle case without tracing - implement the core logic here + return await self._agentex_client.states.retrieve(state_id) + async with trace.span( parent_id=parent_span_id, name="get_state", diff --git a/src/agentex/lib/core/services/adk/streaming.py b/src/agentex/lib/core/services/adk/streaming.py index 57df2a14..4fa9f8b3 100644 --- a/src/agentex/lib/core/services/adk/streaming.py +++ b/src/agentex/lib/core/services/adk/streaming.py @@ -2,33 +2,33 @@ from typing import Literal from agentex import AsyncAgentex -from agentex.lib.core.adapters.streams.port import StreamRepository -from agentex.types.task_message_update import ( - TaskMessageDelta, - TaskMessageUpdate, - StreamTaskMessageStart, - StreamTaskMessageDelta, - StreamTaskMessageFull, - StreamTaskMessageDone, +from agentex.lib.utils.logging import make_logger +from agentex.types.data_content import DataContent +from agentex.types.task_message import ( + TaskMessage, + TaskMessageContent, ) +from agentex.types.text_content import TextContent +from agentex.types.reasoning_content import ReasoningContent from agentex.types.task_message_delta import ( - TextDelta, DataDelta, + TextDelta, ToolRequestDelta, ToolResponseDelta, - ReasoningSummaryDelta, ReasoningContentDelta, + ReasoningSummaryDelta, ) -from agentex.lib.utils.logging import make_logger -from agentex.types.data_content import DataContent -from agentex.types.task_message import ( - TaskMessage, - TaskMessageContent, +from agentex.types.task_message_update import ( + TaskMessageDelta, + TaskMessageUpdate, + StreamTaskMessageDone, + StreamTaskMessageFull, + StreamTaskMessageDelta, + StreamTaskMessageStart, ) -from agentex.types.text_content import TextContent from agentex.types.tool_request_content import ToolRequestContent from agentex.types.tool_response_content import ToolResponseContent -from agentex.types.reasoning_content import ReasoningContent +from agentex.lib.core.adapters.streams.port import StreamRepository logger = make_logger(__name__) @@ -265,7 +265,7 @@ async def stream_update( elif isinstance(update, StreamTaskMessageFull): await self._agentex_client.messages.update( task_id=self.task_id, - message_id=update.parent_task_message.id, + message_id=update.parent_task_message.id, # type: ignore[union-attr] content=update.content.model_dump(), streaming_status="DONE", ) @@ -306,7 +306,7 @@ async def stream_update( Returns: True if event was streamed successfully, False otherwise """ - stream_topic = _get_stream_topic(update.parent_task_message.task_id) + stream_topic = _get_stream_topic(update.parent_task_message.task_id) # type: ignore[union-attr] try: await self._stream_repository.send_event( diff --git a/src/agentex/lib/core/services/adk/tasks.py b/src/agentex/lib/core/services/adk/tasks.py index af4b8fbf..6c3ff7df 100644 --- a/src/agentex/lib/core/services/adk/tasks.py +++ b/src/agentex/lib/core/services/adk/tasks.py @@ -1,8 +1,9 @@ from agentex import AsyncAgentex -from agentex.lib.core.tracing.tracer import AsyncTracer from agentex.types.task import Task +from agentex.types.shared import DeleteResponse from agentex.lib.utils.logging import make_logger from agentex.lib.utils.temporal import heartbeat_if_in_workflow +from agentex.lib.core.tracing.tracer import AsyncTracer logger = make_logger(__name__) @@ -48,8 +49,13 @@ async def delete_task( task_name: str | None = None, trace_id: str | None = None, parent_span_id: str | None = None, - ) -> Task: + ) -> Task | DeleteResponse: trace = self._tracer.trace(trace_id) if self._tracer else None + if trace is None: + # Handle case without tracing + response = await self._agentex_client.tasks.delete(task_id) + return Task(**response.model_dump()) + async with trace.span( parent_id=parent_span_id, name="delete_task", diff --git a/src/agentex/lib/core/services/adk/tracing.py b/src/agentex/lib/core/services/adk/tracing.py index 0de69d21..f14cbc12 100644 --- a/src/agentex/lib/core/services/adk/tracing.py +++ b/src/agentex/lib/core/services/adk/tracing.py @@ -1,9 +1,10 @@ from typing import Any -from agentex.lib.core.tracing.tracer import AsyncTracer + from agentex.types.span import Span from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.model_utils import BaseModel from agentex.lib.utils.temporal import heartbeat_if_in_workflow +from agentex.lib.utils.model_utils import BaseModel +from agentex.lib.core.tracing.tracer import AsyncTracer logger = make_logger(__name__) diff --git a/src/agentex/lib/core/services/adk/utils/templating.py b/src/agentex/lib/core/services/adk/utils/templating.py index b9bf01b6..032aac27 100644 --- a/src/agentex/lib/core/services/adk/utils/templating.py +++ b/src/agentex/lib/core/services/adk/utils/templating.py @@ -1,10 +1,10 @@ -from datetime import datetime from typing import Any +from datetime import datetime from jinja2 import BaseLoader, Environment -from agentex.lib.core.tracing.tracer import AsyncTracer from agentex.lib.utils.temporal import heartbeat_if_in_workflow +from agentex.lib.core.tracing.tracer import AsyncTracer # Create a Jinja environment JINJA_ENV = Environment( @@ -38,6 +38,8 @@ async def render_jinja( Returns: The rendered template as a string """ + if self.tracer is None: + raise RuntimeError("Tracer not initialized - ensure tracer is provided to TemplatingService") trace = self.tracer.trace(trace_id) async with trace.span( parent_id=parent_span_id, diff --git a/src/agentex/lib/core/temporal/activities/__init__.py b/src/agentex/lib/core/temporal/activities/__init__.py index 352aeaf6..17792227 100644 --- a/src/agentex/lib/core/temporal/activities/__init__.py +++ b/src/agentex/lib/core/temporal/activities/__init__.py @@ -1,45 +1,45 @@ import httpx -from agentex.lib.adk.utils._modules.client import create_async_agentex_client from scale_gp import SGPClient, SGPClientError -from agentex import AsyncAgentex -from agentex.lib.core.adapters.llm.adapter_litellm import LiteLLMGateway -from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository -from agentex.lib.core.services.adk.acp.acp import ACPService -from agentex.lib.core.services.adk.agent_task_tracker import AgentTaskTrackerService -from agentex.lib.core.services.adk.events import EventsService -from agentex.lib.core.services.adk.messages import MessagesService -from agentex.lib.core.services.adk.providers.litellm import LiteLLMService -from agentex.lib.core.services.adk.providers.openai import OpenAIService -from agentex.lib.core.services.adk.providers.sgp import SGPService +from agentex import AsyncAgentex # noqa: F401 +from agentex.lib.core.tracing import AsyncTracer from agentex.lib.core.services.adk.state import StateService -from agentex.lib.core.services.adk.streaming import StreamingService from agentex.lib.core.services.adk.tasks import TasksService +from agentex.lib.core.services.adk.events import EventsService +from agentex.lib.adk.utils._modules.client import create_async_agentex_client +from agentex.lib.core.services.adk.acp.acp import ACPService from agentex.lib.core.services.adk.tracing import TracingService +from agentex.lib.core.services.adk.messages import MessagesService +from agentex.lib.core.services.adk.streaming import StreamingService +from agentex.lib.core.services.adk.providers.sgp import SGPService +from agentex.lib.core.adapters.llm.adapter_litellm import LiteLLMGateway +from agentex.lib.core.services.adk.providers.openai import OpenAIService from agentex.lib.core.services.adk.utils.templating import TemplatingService -from agentex.lib.core.temporal.activities.adk.acp.acp_activities import ACPActivities -from agentex.lib.core.temporal.activities.adk.agent_task_tracker_activities import ( - AgentTaskTrackerActivities, -) +from agentex.lib.core.adapters.streams.adapter_redis import RedisStreamRepository +from agentex.lib.core.services.adk.providers.litellm import LiteLLMService +from agentex.lib.core.services.adk.agent_task_tracker import AgentTaskTrackerService +from agentex.lib.core.temporal.activities.adk.state_activities import StateActivities +from agentex.lib.core.temporal.activities.adk.tasks_activities import TasksActivities from agentex.lib.core.temporal.activities.adk.events_activities import EventsActivities +from agentex.lib.core.temporal.activities.adk.acp.acp_activities import ACPActivities +from agentex.lib.core.temporal.activities.adk.tracing_activities import TracingActivities from agentex.lib.core.temporal.activities.adk.messages_activities import MessagesActivities -from agentex.lib.core.temporal.activities.adk.providers.litellm_activities import ( - LiteLLMActivities, +from agentex.lib.core.temporal.activities.adk.streaming_activities import ( + StreamingActivities, ) +from agentex.lib.core.temporal.activities.adk.providers.sgp_activities import SGPActivities from agentex.lib.core.temporal.activities.adk.providers.openai_activities import ( OpenAIActivities, ) -from agentex.lib.core.temporal.activities.adk.providers.sgp_activities import SGPActivities -from agentex.lib.core.temporal.activities.adk.state_activities import StateActivities -from agentex.lib.core.temporal.activities.adk.streaming_activities import ( - StreamingActivities, -) -from agentex.lib.core.temporal.activities.adk.tasks_activities import TasksActivities -from agentex.lib.core.temporal.activities.adk.tracing_activities import TracingActivities from agentex.lib.core.temporal.activities.adk.utils.templating_activities import ( TemplatingActivities, ) -from agentex.lib.core.tracing import AsyncTracer +from agentex.lib.core.temporal.activities.adk.providers.litellm_activities import ( + LiteLLMActivities, +) +from agentex.lib.core.temporal.activities.adk.agent_task_tracker_activities import ( + AgentTaskTrackerActivities, +) def get_all_activities(sgp_client=None): @@ -204,7 +204,7 @@ def get_all_activities(sgp_client=None): # SGP activities if sgp_client is not None: sgp_all_activities = [ - sgp_activities.download_file_content, + sgp_activities.download_file_content, # type: ignore[union-attr] ] activities.extend(sgp_all_activities) diff --git a/src/agentex/lib/core/temporal/activities/activity_helpers.py b/src/agentex/lib/core/temporal/activities/activity_helpers.py index f6df007c..ca812479 100644 --- a/src/agentex/lib/core/temporal/activities/activity_helpers.py +++ b/src/agentex/lib/core/temporal/activities/activity_helpers.py @@ -1,5 +1,5 @@ -from datetime import timedelta from typing import Any, TypeVar +from datetime import timedelta from pydantic import TypeAdapter from temporalio import workflow diff --git a/src/agentex/lib/core/temporal/activities/adk/acp/acp_activities.py b/src/agentex/lib/core/temporal/activities/adk/acp/acp_activities.py index be81e7ab..d3739f4d 100644 --- a/src/agentex/lib/core/temporal/activities/adk/acp/acp_activities.py +++ b/src/agentex/lib/core/temporal/activities/adk/acp/acp_activities.py @@ -3,13 +3,13 @@ from temporalio import activity -from agentex.lib.core.services.adk.acp.acp import ACPService -from agentex.types.event import Event -from agentex.types.task_message import TaskMessage -from agentex.types.task_message_content import TaskMessageContent from agentex.types.task import Task +from agentex.types.event import Event from agentex.lib.types.tracing import BaseModelWithTraceParams from agentex.lib.utils.logging import make_logger +from agentex.types.task_message import TaskMessage +from agentex.types.task_message_content import TaskMessageContent +from agentex.lib.core.services.adk.acp.acp import ACPService logger = make_logger(__name__) diff --git a/src/agentex/lib/core/temporal/activities/adk/agent_task_tracker_activities.py b/src/agentex/lib/core/temporal/activities/adk/agent_task_tracker_activities.py index 5bd974d5..a88520e7 100644 --- a/src/agentex/lib/core/temporal/activities/adk/agent_task_tracker_activities.py +++ b/src/agentex/lib/core/temporal/activities/adk/agent_task_tracker_activities.py @@ -2,10 +2,10 @@ from temporalio import activity -from agentex.lib.core.services.adk.agent_task_tracker import AgentTaskTrackerService from agentex.lib.types.tracing import BaseModelWithTraceParams from agentex.lib.utils.logging import make_logger from agentex.types.agent_task_tracker import AgentTaskTracker +from agentex.lib.core.services.adk.agent_task_tracker import AgentTaskTrackerService logger = make_logger(__name__) diff --git a/src/agentex/lib/core/temporal/activities/adk/agents_activities.py b/src/agentex/lib/core/temporal/activities/adk/agents_activities.py index ad51b72e..590bcfbf 100644 --- a/src/agentex/lib/core/temporal/activities/adk/agents_activities.py +++ b/src/agentex/lib/core/temporal/activities/adk/agents_activities.py @@ -1,12 +1,12 @@ from enum import Enum from typing import Optional -from agentex.lib.core.services.adk.agents import AgentsService -from agentex.types.agent import Agent from temporalio import activity +from agentex.types.agent import Agent from agentex.lib.types.tracing import BaseModelWithTraceParams from agentex.lib.utils.logging import make_logger +from agentex.lib.core.services.adk.agents import AgentsService logger = make_logger(__name__) diff --git a/src/agentex/lib/core/temporal/activities/adk/events_activities.py b/src/agentex/lib/core/temporal/activities/adk/events_activities.py index 224bd1a6..76d64d0d 100644 --- a/src/agentex/lib/core/temporal/activities/adk/events_activities.py +++ b/src/agentex/lib/core/temporal/activities/adk/events_activities.py @@ -2,10 +2,10 @@ from temporalio import activity -from agentex.lib.core.services.adk.events import EventsService from agentex.types.event import Event from agentex.lib.types.tracing import BaseModelWithTraceParams from agentex.lib.utils.logging import make_logger +from agentex.lib.core.services.adk.events import EventsService logger = make_logger(__name__) diff --git a/src/agentex/lib/core/temporal/activities/adk/messages_activities.py b/src/agentex/lib/core/temporal/activities/adk/messages_activities.py index 8fdad8dd..a61849db 100644 --- a/src/agentex/lib/core/temporal/activities/adk/messages_activities.py +++ b/src/agentex/lib/core/temporal/activities/adk/messages_activities.py @@ -2,11 +2,11 @@ from temporalio import activity -from agentex.lib.core.services.adk.messages import MessagesService -from agentex.types.task_message import TaskMessage -from agentex.types.task_message_content import TaskMessageContent from agentex.lib.types.tracing import BaseModelWithTraceParams from agentex.lib.utils.logging import make_logger +from agentex.types.task_message import TaskMessage +from agentex.types.task_message_content import TaskMessageContent +from agentex.lib.core.services.adk.messages import MessagesService logger = make_logger(__name__) diff --git a/src/agentex/lib/core/temporal/activities/adk/providers/litellm_activities.py b/src/agentex/lib/core/temporal/activities/adk/providers/litellm_activities.py index 70bb3c73..492ee305 100644 --- a/src/agentex/lib/core/temporal/activities/adk/providers/litellm_activities.py +++ b/src/agentex/lib/core/temporal/activities/adk/providers/litellm_activities.py @@ -2,11 +2,11 @@ from temporalio import activity -from agentex.lib.core.services.adk.providers.litellm import LiteLLMService -from agentex.lib.types.llm_messages import Completion, LLMConfig -from agentex.types.task_message import TaskMessage -from agentex.lib.types.tracing import BaseModelWithTraceParams from agentex.lib.utils import logging +from agentex.lib.types.tracing import BaseModelWithTraceParams +from agentex.types.task_message import TaskMessage +from agentex.lib.types.llm_messages import LLMConfig, Completion +from agentex.lib.core.services.adk.providers.litellm import LiteLLMService logger = logging.make_logger(__name__) diff --git a/src/agentex/lib/core/temporal/activities/adk/providers/sgp_activities.py b/src/agentex/lib/core/temporal/activities/adk/providers/sgp_activities.py index 98e93af6..3905eb16 100644 --- a/src/agentex/lib/core/temporal/activities/adk/providers/sgp_activities.py +++ b/src/agentex/lib/core/temporal/activities/adk/providers/sgp_activities.py @@ -2,10 +2,10 @@ from temporalio import activity -from agentex.lib.core.services.adk.providers.sgp import SGPService from agentex.lib.types.files import FileContentResponse from agentex.lib.types.tracing import BaseModelWithTraceParams from agentex.lib.utils.logging import make_logger +from agentex.lib.core.services.adk.providers.sgp import SGPService logger = make_logger(__name__) diff --git a/src/agentex/lib/core/temporal/activities/adk/state_activities.py b/src/agentex/lib/core/temporal/activities/adk/state_activities.py index 4b892c94..f16a8cf7 100644 --- a/src/agentex/lib/core/temporal/activities/adk/state_activities.py +++ b/src/agentex/lib/core/temporal/activities/adk/state_activities.py @@ -3,10 +3,10 @@ from temporalio import activity -from agentex.lib.core.services.adk.state import StateService from agentex.types.state import State from agentex.lib.types.tracing import BaseModelWithTraceParams from agentex.lib.utils.logging import make_logger +from agentex.lib.core.services.adk.state import StateService logger = make_logger(__name__) diff --git a/src/agentex/lib/core/temporal/activities/adk/streaming_activities.py b/src/agentex/lib/core/temporal/activities/adk/streaming_activities.py index 09059302..bc54c4dd 100644 --- a/src/agentex/lib/core/temporal/activities/adk/streaming_activities.py +++ b/src/agentex/lib/core/temporal/activities/adk/streaming_activities.py @@ -2,11 +2,11 @@ from temporalio import activity -from agentex.lib.core.services.adk.streaming import StreamingService -from agentex.types.task_message_update import TaskMessageUpdate from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.model_utils import BaseModel from agentex.lib.utils.temporal import heartbeat_if_in_workflow +from agentex.lib.utils.model_utils import BaseModel +from agentex.types.task_message_update import TaskMessageUpdate +from agentex.lib.core.services.adk.streaming import StreamingService logger = make_logger(__name__) diff --git a/src/agentex/lib/core/temporal/activities/adk/tasks_activities.py b/src/agentex/lib/core/temporal/activities/adk/tasks_activities.py index 6ce72a93..db00a999 100644 --- a/src/agentex/lib/core/temporal/activities/adk/tasks_activities.py +++ b/src/agentex/lib/core/temporal/activities/adk/tasks_activities.py @@ -2,10 +2,10 @@ from temporalio import activity -from agentex.lib.core.services.adk.tasks import TasksService from agentex.types.task import Task from agentex.lib.types.tracing import BaseModelWithTraceParams from agentex.lib.utils.logging import make_logger +from agentex.lib.core.services.adk.tasks import TasksService logger = make_logger(__name__) @@ -40,7 +40,7 @@ async def get_task(self, params: GetTaskParams) -> Task | None: @activity.defn(name=TasksActivityName.DELETE_TASK) async def delete_task(self, params: DeleteTaskParams) -> Task: - return await self._tasks_service.delete_task( + return await self._tasks_service.delete_task( # type: ignore[return-value] task_id=params.task_id, task_name=params.task_name, trace_id=params.trace_id, diff --git a/src/agentex/lib/core/temporal/activities/adk/tracing_activities.py b/src/agentex/lib/core/temporal/activities/adk/tracing_activities.py index 56bde068..3f012108 100644 --- a/src/agentex/lib/core/temporal/activities/adk/tracing_activities.py +++ b/src/agentex/lib/core/temporal/activities/adk/tracing_activities.py @@ -3,10 +3,10 @@ from temporalio import activity -from agentex.lib.core.services.adk.tracing import TracingService from agentex.types.span import Span from agentex.lib.utils.logging import make_logger from agentex.lib.utils.model_utils import BaseModel +from agentex.lib.core.services.adk.tracing import TracingService logger = make_logger(__name__) diff --git a/src/agentex/lib/core/temporal/activities/adk/utils/templating_activities.py b/src/agentex/lib/core/temporal/activities/adk/utils/templating_activities.py index 3c284c7a..a91c8963 100644 --- a/src/agentex/lib/core/temporal/activities/adk/utils/templating_activities.py +++ b/src/agentex/lib/core/temporal/activities/adk/utils/templating_activities.py @@ -3,8 +3,8 @@ from temporalio import activity -from agentex.lib.core.services.adk.utils.templating import TemplatingService from agentex.lib.types.tracing import BaseModelWithTraceParams +from agentex.lib.core.services.adk.utils.templating import TemplatingService class JinjaActivityName(str, Enum): diff --git a/src/agentex/lib/core/temporal/services/temporal_task_service.py b/src/agentex/lib/core/temporal/services/temporal_task_service.py index dabfc330..2e6be347 100644 --- a/src/agentex/lib/core/temporal/services/temporal_task_service.py +++ b/src/agentex/lib/core/temporal/services/temporal_task_service.py @@ -1,13 +1,13 @@ from typing import Any -from agentex.lib.core.clients.temporal.temporal_client import TemporalClient -from agentex.lib.core.clients.temporal.types import WorkflowState -from agentex.lib.core.temporal.types.workflow import SignalName -from agentex.lib.environment_variables import EnvironmentVariables -from agentex.lib.types.acp import CreateTaskParams -from agentex.lib.types.acp import SendEventParams + +from agentex.types.task import Task from agentex.types.agent import Agent from agentex.types.event import Event -from agentex.types.task import Task +from agentex.lib.types.acp import SendEventParams, CreateTaskParams +from agentex.lib.environment_variables import EnvironmentVariables +from agentex.lib.core.clients.temporal.types import WorkflowState +from agentex.lib.core.temporal.types.workflow import SignalName +from agentex.lib.core.clients.temporal.temporal_client import TemporalClient class TemporalTaskService: diff --git a/src/agentex/lib/core/temporal/workers/worker.py b/src/agentex/lib/core/temporal/workers/worker.py index 03d25761..636ba1f7 100644 --- a/src/agentex/lib/core/temporal/workers/worker.py +++ b/src/agentex/lib/core/temporal/workers/worker.py @@ -1,28 +1,28 @@ -import dataclasses -import datetime import os import uuid +import datetime +import dataclasses +from typing import Any, overload, override from collections.abc import Callable from concurrent.futures import ThreadPoolExecutor -from typing import Any, overload from aiohttp import web from temporalio.client import Client, Plugin as ClientPlugin +from temporalio.worker import ( + Plugin as WorkerPlugin, + Worker, + UnsandboxedWorkflowRunner, +) +from temporalio.runtime import Runtime, TelemetryConfig, OpenTelemetryConfig from temporalio.converter import ( - AdvancedJSONEncoder, - CompositePayloadConverter, DataConverter, + JSONTypeConverter, + AdvancedJSONEncoder, DefaultPayloadConverter, + CompositePayloadConverter, JSONPlainPayloadConverter, - JSONTypeConverter, _JSONTypeConverterUnhandled, ) -from temporalio.runtime import OpenTelemetryConfig, Runtime, TelemetryConfig -from temporalio.worker import ( - Plugin as WorkerPlugin, - UnsandboxedWorkflowRunner, - Worker, -) from agentex.lib.utils.logging import make_logger from agentex.lib.utils.registration import register_agent @@ -32,6 +32,7 @@ class DateTimeJSONEncoder(AdvancedJSONEncoder): + @override def default(self, o: Any) -> Any: if isinstance(o, datetime.datetime): return o.isoformat() @@ -39,9 +40,8 @@ def default(self, o: Any) -> Any: class DateTimeJSONTypeConverter(JSONTypeConverter): - def to_typed_value( - self, hint: type, value: Any - ) -> Any | None | _JSONTypeConverterUnhandled: + @override + def to_typed_value(self, hint: type, value: Any) -> Any | None | _JSONTypeConverterUnhandled: if hint == datetime.datetime: return datetime.datetime.fromisoformat(value) return JSONTypeConverter.Unhandled @@ -66,6 +66,7 @@ def __init__(self) -> None: payload_converter_class=DateTimePayloadConverter, ) + def _validate_plugins(plugins: list) -> None: """Validate that all items in the plugins list are valid Temporal plugins.""" for i, plugin in enumerate(plugins): @@ -76,10 +77,8 @@ def _validate_plugins(plugins: list) -> None: ) - -async def get_temporal_client(temporal_address: str, metrics_url: str = None, plugins: list = []) -> Client: - - if plugins != []: # We don't need to validate the plugins if they are empty +async def get_temporal_client(temporal_address: str, metrics_url: str | None = None, plugins: list = []) -> Client: + if plugins != []: # We don't need to validate the plugins if they are empty _validate_plugins(plugins) if not metrics_url: @@ -87,13 +86,12 @@ async def get_temporal_client(temporal_address: str, metrics_url: str = None, pl target_host=temporal_address, data_converter=custom_data_converter, plugins=plugins ) else: - runtime = Runtime( - telemetry=TelemetryConfig(metrics=OpenTelemetryConfig(url=metrics_url)) - ) + runtime = Runtime(telemetry=TelemetryConfig(metrics=OpenTelemetryConfig(url=metrics_url))) client = await Client.connect( target_host=temporal_address, data_converter=custom_data_converter, runtime=runtime, + plugins=plugins, ) return client @@ -123,7 +121,7 @@ async def run( *, workflow: type, ) -> None: ... - + @overload async def run( self, @@ -145,15 +143,15 @@ async def run( temporal_address=os.environ.get("TEMPORAL_ADDRESS", "localhost:7233"), plugins=self.plugins, ) - + # Enable debug mode if AgentEx debug is enabled (disables deadlock detection) debug_enabled = os.environ.get("AGENTEX_DEBUG_ENABLED", "false").lower() == "true" if debug_enabled: logger.info("🐛 [WORKER] Temporal debug mode enabled - deadlock detection disabled") - + if workflow is None and workflows is None: raise ValueError("Either workflow or workflows must be provided") - + worker = Worker( client=temporal_client, task_queue=self.task_queue, @@ -178,7 +176,7 @@ async def _health_check(self): async def start_health_check_server(self): if not self.health_check_server_running: app = web.Application() - app.router.add_get("/readyz", lambda request: self._health_check()) + app.router.add_get("/readyz", lambda request: self._health_check()) # noqa: ARG005 # Disable access logging runner = web.AppRunner(app, access_log=None) @@ -187,27 +185,19 @@ async def start_health_check_server(self): try: site = web.TCPSite(runner, "0.0.0.0", self.health_check_port) await site.start() - logger.info( - f"Health check server running on http://0.0.0.0:{self.health_check_port}/readyz" - ) + logger.info(f"Health check server running on http://0.0.0.0:{self.health_check_port}/readyz") self.health_check_server_running = True except OSError as e: - logger.error( - f"Failed to start health check server on port {self.health_check_port}: {e}" - ) + logger.error(f"Failed to start health check server on port {self.health_check_port}: {e}") # Try alternative port if default fails try: alt_port = self.health_check_port + 1 site = web.TCPSite(runner, "0.0.0.0", alt_port) await site.start() - logger.info( - f"Health check server running on alternative port http://0.0.0.0:{alt_port}/readyz" - ) + logger.info(f"Health check server running on alternative port http://0.0.0.0:{alt_port}/readyz") self.health_check_server_running = True except OSError as e: - logger.error( - f"Failed to start health check server on alternative port {alt_port}: {e}" - ) + logger.error(f"Failed to start health check server on alternative port {alt_port}: {e}") raise """ @@ -217,9 +207,10 @@ async def start_health_check_server(self): doing this on the worker side is required to make sure that both share the API key which is returned on registration and used to authenticate the worker with the Agentex server. """ + async def _register_agent(self): env_vars = EnvironmentVariables.refresh() if env_vars and env_vars.AGENTEX_BASE_URL: await register_agent(env_vars) else: - logger.warning("AGENTEX_BASE_URL not set, skipping worker registration") \ No newline at end of file + logger.warning("AGENTEX_BASE_URL not set, skipping worker registration") diff --git a/src/agentex/lib/core/temporal/workflows/workflow.py b/src/agentex/lib/core/temporal/workflows/workflow.py index 37325ee8..727f3ac8 100644 --- a/src/agentex/lib/core/temporal/workflows/workflow.py +++ b/src/agentex/lib/core/temporal/workflows/workflow.py @@ -2,9 +2,9 @@ from temporalio import workflow -from agentex.lib.core.temporal.types.workflow import SignalName -from agentex.lib.types.acp import CreateTaskParams, SendEventParams +from agentex.lib.types.acp import SendEventParams, CreateTaskParams from agentex.lib.utils.logging import make_logger +from agentex.lib.core.temporal.types.workflow import SignalName logger = make_logger(__name__) diff --git a/src/agentex/lib/core/tracing/__init__.py b/src/agentex/lib/core/tracing/__init__.py index f2f495f2..9f91f9ce 100644 --- a/src/agentex/lib/core/tracing/__init__.py +++ b/src/agentex/lib/core/tracing/__init__.py @@ -1,5 +1,5 @@ -from agentex.lib.core.tracing.trace import AsyncTrace, Trace -from agentex.lib.core.tracing.tracer import AsyncTracer, Tracer from agentex.types.span import Span +from agentex.lib.core.tracing.trace import Trace, AsyncTrace +from agentex.lib.core.tracing.tracer import Tracer, AsyncTracer __all__ = ["Trace", "AsyncTrace", "Span", "Tracer", "AsyncTracer"] diff --git a/src/agentex/lib/core/tracing/processors/agentex_tracing_processor.py b/src/agentex/lib/core/tracing/processors/agentex_tracing_processor.py index ac426271..56e4fa34 100644 --- a/src/agentex/lib/core/tracing/processors/agentex_tracing_processor.py +++ b/src/agentex/lib/core/tracing/processors/agentex_tracing_processor.py @@ -1,17 +1,17 @@ from typing import Any, Dict, override -from agentex import Agentex, AsyncAgentex +from agentex import Agentex +from agentex.types.span import Span +from agentex.lib.types.tracing import AgentexTracingProcessorConfig from agentex.lib.adk.utils._modules.client import create_async_agentex_client from agentex.lib.core.tracing.processors.tracing_processor_interface import ( - AsyncTracingProcessor, SyncTracingProcessor, + AsyncTracingProcessor, ) -from agentex.types.span import Span -from agentex.lib.types.tracing import AgentexTracingProcessorConfig class AgentexSyncTracingProcessor(SyncTracingProcessor): - def __init__(self, config: AgentexTracingProcessorConfig): + def __init__(self, config: AgentexTracingProcessorConfig): # noqa: ARG002 self.client = Agentex() @override @@ -65,7 +65,7 @@ def shutdown(self) -> None: class AgentexAsyncTracingProcessor(AsyncTracingProcessor): - def __init__(self, config: AgentexTracingProcessorConfig): + def __init__(self, config: AgentexTracingProcessorConfig): # noqa: ARG002 self.client = create_async_agentex_client() @override diff --git a/src/agentex/lib/core/tracing/processors/sgp_tracing_processor.py b/src/agentex/lib/core/tracing/processors/sgp_tracing_processor.py index f614f78e..c52d7319 100644 --- a/src/agentex/lib/core/tracing/processors/sgp_tracing_processor.py +++ b/src/agentex/lib/core/tracing/processors/sgp_tracing_processor.py @@ -1,17 +1,17 @@ from typing import override import scale_gp_beta.lib.tracing as tracing -from scale_gp_beta import AsyncSGPClient, SGPClient +from scale_gp_beta import SGPClient, AsyncSGPClient from scale_gp_beta.lib.tracing import create_span, flush_queue from scale_gp_beta.lib.tracing.span import Span as SGPSpan -from agentex.lib.core.tracing.processors.tracing_processor_interface import ( - AsyncTracingProcessor, - SyncTracingProcessor, -) from agentex.types.span import Span from agentex.lib.types.tracing import SGPTracingProcessorConfig from agentex.lib.utils.logging import make_logger +from agentex.lib.core.tracing.processors.tracing_processor_interface import ( + SyncTracingProcessor, + AsyncTracingProcessor, +) logger = make_logger(__name__) @@ -36,7 +36,7 @@ def on_span_start(self, span: Span) -> None: output=span.output, metadata=span.data, ) - sgp_span.start_time = span.start_time.isoformat() + sgp_span.start_time = span.start_time.isoformat() # type: ignore[union-attr] sgp_span.flush(blocking=False) self._spans[span.id] = sgp_span @@ -50,9 +50,9 @@ def on_span_end(self, span: Span) -> None: ) return - sgp_span.output = span.output - sgp_span.metadata = span.data - sgp_span.end_time = span.end_time.isoformat() + sgp_span.output = span.output # type: ignore[assignment] + sgp_span.metadata = span.data # type: ignore[assignment] + sgp_span.end_time = span.end_time.isoformat() # type: ignore[union-attr] sgp_span.flush(blocking=False) @override @@ -82,11 +82,11 @@ async def on_span_start(self, span: Span) -> None: output=span.output, metadata=span.data, ) - sgp_span.start_time = span.start_time.isoformat() + sgp_span.start_time = span.start_time.isoformat() # type: ignore[union-attr] if self.disabled: return - await self.sgp_async_client.spans.upsert_batch( + await self.sgp_async_client.spans.upsert_batch( # type: ignore[union-attr] items=[sgp_span.to_request_params()] ) @@ -101,19 +101,19 @@ async def on_span_end(self, span: Span) -> None: ) return - sgp_span.output = span.output - sgp_span.metadata = span.data - sgp_span.end_time = span.end_time.isoformat() + sgp_span.output = span.output # type: ignore[assignment] + sgp_span.metadata = span.data # type: ignore[assignment] + sgp_span.end_time = span.end_time.isoformat() # type: ignore[union-attr] if self.disabled: return - await self.sgp_async_client.spans.upsert_batch( + await self.sgp_async_client.spans.upsert_batch( # type: ignore[union-attr] items=[sgp_span.to_request_params()] ) @override async def shutdown(self) -> None: - await self.sgp_async_client.spans.upsert_batch( + await self.sgp_async_client.spans.upsert_batch( # type: ignore[union-attr] items=[sgp_span.to_request_params() for sgp_span in self._spans.values()] ) self._spans.clear() diff --git a/src/agentex/lib/core/tracing/processors/tracing_processor_interface.py b/src/agentex/lib/core/tracing/processors/tracing_processor_interface.py index 7b7cc718..4ab85dcf 100644 --- a/src/agentex/lib/core/tracing/processors/tracing_processor_interface.py +++ b/src/agentex/lib/core/tracing/processors/tracing_processor_interface.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod -from agentex.lib.types.tracing import TracingProcessorConfig from agentex.types.span import Span +from agentex.lib.types.tracing import TracingProcessorConfig class SyncTracingProcessor(ABC): diff --git a/src/agentex/lib/core/tracing/trace.py b/src/agentex/lib/core/tracing/trace.py index 6547dd43..0d0988f2 100644 --- a/src/agentex/lib/core/tracing/trace.py +++ b/src/agentex/lib/core/tracing/trace.py @@ -1,19 +1,19 @@ +import uuid import asyncio -from contextlib import asynccontextmanager, contextmanager -from datetime import UTC, datetime from typing import Any, AsyncGenerator -import uuid +from datetime import UTC, datetime +from contextlib import contextmanager, asynccontextmanager from pydantic import BaseModel from agentex import Agentex, AsyncAgentex +from agentex.types.span import Span +from agentex.lib.utils.logging import make_logger +from agentex.lib.utils.model_utils import recursive_model_dump from agentex.lib.core.tracing.processors.tracing_processor_interface import ( - AsyncTracingProcessor, SyncTracingProcessor, + AsyncTracingProcessor, ) -from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.model_utils import recursive_model_dump -from agentex.types.span import Span logger = make_logger(__name__) diff --git a/src/agentex/lib/core/tracing/tracer.py b/src/agentex/lib/core/tracing/tracer.py index 806d32ee..b9aa4ff5 100644 --- a/src/agentex/lib/core/tracing/tracer.py +++ b/src/agentex/lib/core/tracing/tracer.py @@ -1,8 +1,8 @@ from agentex import Agentex, AsyncAgentex -from agentex.lib.core.tracing.trace import AsyncTrace, Trace +from agentex.lib.core.tracing.trace import Trace, AsyncTrace from agentex.lib.core.tracing.tracing_processor_manager import ( - get_async_tracing_processors, get_sync_tracing_processors, + get_async_tracing_processors, ) diff --git a/src/agentex/lib/core/tracing/tracing_processor_manager.py b/src/agentex/lib/core/tracing/tracing_processor_manager.py index ffbfc6b1..d6d9d884 100644 --- a/src/agentex/lib/core/tracing/tracing_processor_manager.py +++ b/src/agentex/lib/core/tracing/tracing_processor_manager.py @@ -1,38 +1,53 @@ +from typing import TYPE_CHECKING from threading import Lock -from agentex.lib.core.tracing.processors.agentex_tracing_processor import ( - AgentexAsyncTracingProcessor, - AgentexSyncTracingProcessor, -) +from agentex.lib.types.tracing import TracingProcessorConfig, AgentexTracingProcessorConfig from agentex.lib.core.tracing.processors.sgp_tracing_processor import ( - SGPAsyncTracingProcessor, SGPSyncTracingProcessor, + SGPAsyncTracingProcessor, ) from agentex.lib.core.tracing.processors.tracing_processor_interface import ( - AsyncTracingProcessor, SyncTracingProcessor, + AsyncTracingProcessor, ) -from agentex.lib.types.tracing import AgentexTracingProcessorConfig, TracingProcessorConfig + +if TYPE_CHECKING: + from agentex.lib.core.tracing.processors.agentex_tracing_processor import ( # noqa: F401 + AgentexSyncTracingProcessor, + AgentexAsyncTracingProcessor, + ) class TracingProcessorManager: def __init__(self): # Mapping of processor config type to processor class + # Use lazy loading for agentex processors to avoid circular imports self.sync_config_registry: dict[str, type[SyncTracingProcessor]] = { - "agentex": AgentexSyncTracingProcessor, "sgp": SGPSyncTracingProcessor, } self.async_config_registry: dict[str, type[AsyncTracingProcessor]] = { - "agentex": AgentexAsyncTracingProcessor, "sgp": SGPAsyncTracingProcessor, } # Cache for processors self.sync_processors: list[SyncTracingProcessor] = [] self.async_processors: list[AsyncTracingProcessor] = [] self.lock = Lock() + self._agentex_registered = False + + def _ensure_agentex_registered(self): + """Lazily register agentex processors to avoid circular imports.""" + if not self._agentex_registered: + from agentex.lib.core.tracing.processors.agentex_tracing_processor import ( + AgentexSyncTracingProcessor, + AgentexAsyncTracingProcessor, + ) + self.sync_config_registry["agentex"] = AgentexSyncTracingProcessor + self.async_config_registry["agentex"] = AgentexAsyncTracingProcessor + self._agentex_registered = True def add_processor_config(self, processor_config: TracingProcessorConfig) -> None: with self.lock: + self._ensure_agentex_registered() sync_processor = self.sync_config_registry[processor_config.type] async_processor = self.async_config_registry[processor_config.type] self.sync_processors.append(sync_processor(processor_config)) @@ -55,8 +70,23 @@ def get_async_processors(self) -> list[AsyncTracingProcessor]: add_tracing_processor_config = GLOBAL_TRACING_PROCESSOR_MANAGER.add_processor_config set_tracing_processor_configs = GLOBAL_TRACING_PROCESSOR_MANAGER.set_processor_configs -get_sync_tracing_processors = GLOBAL_TRACING_PROCESSOR_MANAGER.get_sync_processors -get_async_tracing_processors = GLOBAL_TRACING_PROCESSOR_MANAGER.get_async_processors -# Add the Agentex tracing processor by default -add_tracing_processor_config(AgentexTracingProcessorConfig()) +# Lazy initialization to avoid circular imports +_default_initialized = False + +def _ensure_default_initialized(): + """Ensure default processor is initialized (lazy to avoid circular imports).""" + global _default_initialized + if not _default_initialized: + add_tracing_processor_config(AgentexTracingProcessorConfig()) + _default_initialized = True + +def get_sync_tracing_processors(): + """Get sync processors, initializing defaults if needed.""" + _ensure_default_initialized() + return GLOBAL_TRACING_PROCESSOR_MANAGER.get_sync_processors() + +def get_async_tracing_processors(): + """Get async processors, initializing defaults if needed.""" + _ensure_default_initialized() + return GLOBAL_TRACING_PROCESSOR_MANAGER.get_async_processors() diff --git a/src/agentex/lib/environment_variables.py b/src/agentex/lib/environment_variables.py index aff88b68..5740e0e0 100644 --- a/src/agentex/lib/environment_variables.py +++ b/src/agentex/lib/environment_variables.py @@ -5,9 +5,9 @@ from enum import Enum from pathlib import Path -from agentex.lib.utils.logging import make_logger from dotenv import load_dotenv +from agentex.lib.utils.logging import make_logger from agentex.lib.utils.model_utils import BaseModel PROJECT_ROOT = Path(__file__).resolve().parents[2] @@ -45,7 +45,7 @@ class Environment(str, Enum): PROD = "production" -refreshed_environment_variables: "EnvironmentVariables" | None = None +refreshed_environment_variables: EnvironmentVariables | None = None class EnvironmentVariables(BaseModel): @@ -80,13 +80,13 @@ def refresh(cls) -> EnvironmentVariables: # Load global .env file first global_env_path = PROJECT_ROOT / ".env" if global_env_path.exists(): - print(f"Loading global environment variables FROM: {global_env_path}") + logger.debug(f"Loading global environment variables FROM: {global_env_path}") load_dotenv(dotenv_path=global_env_path, override=False) # Load local project .env.local file (takes precedence) local_env_path = Path.cwd().parent / ".env.local" if local_env_path.exists(): - print(f"Loading local environment variables FROM: {local_env_path}") + logger.debug(f"Loading local environment variables FROM: {local_env_path}") load_dotenv(dotenv_path=local_env_path, override=True) # Create kwargs dict with environment variables, using None for missing values diff --git a/src/agentex/lib/sdk/config/agent_config.py b/src/agentex/lib/sdk/config/agent_config.py index 164ed12f..288bb3a7 100644 --- a/src/agentex/lib/sdk/config/agent_config.py +++ b/src/agentex/lib/sdk/config/agent_config.py @@ -2,10 +2,10 @@ from pydantic import Field -from agentex.lib.types.agent_configs import TemporalConfig, TemporalWorkflowConfig -from agentex.lib.types.credentials import CredentialMapping from agentex.lib.utils.logging import make_logger +from agentex.lib.types.credentials import CredentialMapping from agentex.lib.utils.model_utils import BaseModel +from agentex.lib.types.agent_configs import TemporalConfig, TemporalWorkflowConfig logger = make_logger(__name__) diff --git a/src/agentex/lib/sdk/config/agent_manifest.py b/src/agentex/lib/sdk/config/agent_manifest.py index 83737093..671a4b61 100644 --- a/src/agentex/lib/sdk/config/agent_manifest.py +++ b/src/agentex/lib/sdk/config/agent_manifest.py @@ -1,25 +1,25 @@ from __future__ import annotations import io +import time import shutil -import subprocess import tarfile import tempfile -import time -from collections.abc import Iterator -from contextlib import contextmanager -from pathlib import Path +import subprocess from typing import IO, Any +from pathlib import Path +from contextlib import contextmanager +from collections.abc import Iterator from pydantic import Field +from agentex.lib.utils.logging import make_logger +from agentex.lib.utils.model_utils import BaseModel from agentex.lib.sdk.config.agent_config import AgentConfig from agentex.lib.sdk.config.build_config import BuildConfig -from agentex.lib.sdk.config.environment_config import AgentEnvironmentsConfig from agentex.lib.sdk.config.deployment_config import DeploymentConfig +from agentex.lib.sdk.config.environment_config import AgentEnvironmentsConfig from agentex.lib.sdk.config.local_development_config import LocalDevelopmentConfig -from agentex.lib.utils.logging import make_logger -from agentex.lib.utils.model_utils import BaseModel logger = make_logger(__name__) diff --git a/src/agentex/lib/sdk/config/project_config.py b/src/agentex/lib/sdk/config/project_config.py index 9b91ce2c..b9b8df6b 100644 --- a/src/agentex/lib/sdk/config/project_config.py +++ b/src/agentex/lib/sdk/config/project_config.py @@ -1,10 +1,10 @@ import os import re -from pathlib import Path from typing import Any, TypeVar +from pathlib import Path import yaml -from jinja2 import BaseLoader, Environment, StrictUndefined, TemplateError +from jinja2 import BaseLoader, Environment, TemplateError, StrictUndefined T = TypeVar("T") @@ -35,10 +35,10 @@ def _extract_variables_section(raw_config_str: str) -> str: def ProjectConfigLoader( config_path: str, model: type[T] | None = None, env_path: str | None = None ) -> dict[str, Any] | T: - config_path = Path(config_path) - env_path = Path(env_path) if env_path else config_path.parent / ".env" - env = _load_env(env_path) - raw_config_str = _load_file_as_str(config_path) + config_path_obj = Path(config_path) + env_path_obj = Path(env_path) if env_path else config_path_obj.parent / ".env" + env = _load_env(env_path_obj) + raw_config_str = _load_file_as_str(config_path_obj) raw_config_str = _preprocess_template(raw_config_str) # Extract and render only the variables section diff --git a/src/agentex/lib/sdk/config/validation.py b/src/agentex/lib/sdk/config/validation.py index 361f46e3..4395c93d 100644 --- a/src/agentex/lib/sdk/config/validation.py +++ b/src/agentex/lib/sdk/config/validation.py @@ -5,11 +5,11 @@ with clear error messages and best practices enforcement. """ -from pathlib import Path from typing import Any, Dict, List, Optional +from pathlib import Path -from agentex.lib.sdk.config.environment_config import AgentEnvironmentsConfig, AgentEnvironmentConfig from agentex.lib.utils.logging import make_logger +from agentex.lib.sdk.config.environment_config import AgentEnvironmentConfig, AgentEnvironmentsConfig logger = make_logger(__name__) diff --git a/src/agentex/lib/sdk/fastacp/base/base_acp_server.py b/src/agentex/lib/sdk/fastacp/base/base_acp_server.py index 3942790a..73371502 100644 --- a/src/agentex/lib/sdk/fastacp/base/base_acp_server.py +++ b/src/agentex/lib/sdk/fastacp/base/base_acp_server.py @@ -1,34 +1,35 @@ +import uuid import asyncio import inspect -import uuid +from typing import Any from datetime import datetime -from collections.abc import AsyncGenerator, Awaitable, Callable from contextlib import asynccontextmanager -from typing import Any +from collections.abc import Callable, Awaitable, AsyncGenerator import uvicorn from fastapi import FastAPI, Request -from fastapi.responses import StreamingResponse from pydantic import TypeAdapter, ValidationError +from fastapi.responses import StreamingResponse from starlette.middleware.base import BaseHTTPMiddleware -# from agentex.lib.sdk.fastacp.types import BaseACPConfig -from agentex.lib.environment_variables import EnvironmentVariables, refreshed_environment_variables from agentex.lib.types.acp import ( - PARAMS_MODEL_BY_METHOD, RPC_SYNC_METHODS, - CancelTaskParams, - CreateTaskParams, + PARAMS_MODEL_BY_METHOD, RPCMethod, SendEventParams, + CancelTaskParams, + CreateTaskParams, SendMessageParams, ) +from agentex.lib.utils.logging import make_logger, ctx_var_request_id from agentex.lib.types.json_rpc import JSONRPCError, JSONRPCRequest, JSONRPCResponse -from agentex.types.task_message_update import StreamTaskMessageFull, TaskMessageUpdate -from agentex.types.task_message_content import TaskMessageContent -from agentex.lib.utils.logging import ctx_var_request_id, make_logger from agentex.lib.utils.model_utils import BaseModel from agentex.lib.utils.registration import register_agent + +# from agentex.lib.sdk.fastacp.types import BaseACPConfig +from agentex.lib.environment_variables import EnvironmentVariables, refreshed_environment_variables +from agentex.types.task_message_update import TaskMessageUpdate, StreamTaskMessageFull +from agentex.types.task_message_content import TaskMessageContent from agentex.lib.sdk.fastacp.base.constants import ( FASTACP_HEADER_SKIP_EXACT, FASTACP_HEADER_SKIP_PREFIXES, @@ -43,7 +44,7 @@ class RequestIDMiddleware(BaseHTTPMiddleware): """Middleware to extract or generate request IDs and add them to logs and response headers""" - async def dispatch(self, request: Request, call_next): + async def dispatch(self, request: Request, call_next): # type: ignore[override] # Extract request ID from header or generate a new one if there isn't one request_id = request.headers.get("x-request-id") or uuid.uuid4().hex logger.info(f"Request ID: {request_id}") @@ -90,7 +91,7 @@ def _setup_handlers(self): def get_lifespan_function(self): @asynccontextmanager - async def lifespan_context(app: FastAPI): + async def lifespan_context(app: FastAPI): # noqa: ARG001 env_vars = EnvironmentVariables.refresh() if env_vars.AGENTEX_BASE_URL: await register_agent(env_vars) diff --git a/src/agentex/lib/sdk/fastacp/fastacp.py b/src/agentex/lib/sdk/fastacp/fastacp.py index 2b657ad7..ddcb3603 100644 --- a/src/agentex/lib/sdk/fastacp/fastacp.py +++ b/src/agentex/lib/sdk/fastacp/fastacp.py @@ -1,18 +1,18 @@ -import inspect import os +import inspect +from typing import Literal from pathlib import Path -from typing import Any, Literal -from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer -from agentex.lib.sdk.fastacp.impl.agentic_base_acp import AgenticBaseACP -from agentex.lib.sdk.fastacp.impl.sync_acp import SyncACP -from agentex.lib.sdk.fastacp.impl.temporal_acp import TemporalACP from agentex.lib.types.fastacp import ( - AgenticACPConfig, BaseACPConfig, SyncACPConfig, + AgenticACPConfig, ) from agentex.lib.utils.logging import make_logger +from agentex.lib.sdk.fastacp.impl.sync_acp import SyncACP +from agentex.lib.sdk.fastacp.impl.temporal_acp import TemporalACP +from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer +from agentex.lib.sdk.fastacp.impl.agentic_base_acp import AgenticBaseACP # Add new mappings between ACP types and configs here # Add new mappings between ACP types and implementations here @@ -23,6 +23,7 @@ logger = make_logger(__name__) + class FastACP: """Factory for creating FastACP instances @@ -33,7 +34,7 @@ class FastACP: @staticmethod # Note: the config is optional and not used right now but is there to be extended in the future - def create_sync_acp(config: SyncACPConfig | None = None, **kwargs) -> SyncACP: + def create_sync_acp(config: SyncACPConfig | None = None, **kwargs) -> SyncACP: # noqa: ARG004 """Create a SyncACP instance""" return SyncACP.create(**kwargs) @@ -52,9 +53,9 @@ def create_agentic_acp(config: AgenticACPConfig, **kwargs) -> BaseACPServer: # Extract temporal_address and plugins from config if it's a TemporalACPConfig temporal_config = kwargs.copy() if hasattr(config, "temporal_address"): - temporal_config["temporal_address"] = config.temporal_address + temporal_config["temporal_address"] = config.temporal_address # type: ignore[attr-defined] if hasattr(config, "plugins"): - temporal_config["plugins"] = config.plugins + temporal_config["plugins"] = config.plugins # type: ignore[attr-defined] return implementation_class.create(**temporal_config) else: return implementation_class.create(**kwargs) @@ -69,9 +70,7 @@ def locate_build_info_path() -> None: @staticmethod def create( - acp_type: Literal["sync", "agentic"], - config: BaseACPConfig | None = None, - **kwargs + acp_type: Literal["sync", "agentic"], config: BaseACPConfig | None = None, **kwargs ) -> BaseACPServer | SyncACP | AgenticBaseACP | TemporalACP: """Main factory method to create any ACP type @@ -79,10 +78,10 @@ def create( acp_type: Type of ACP to create ("sync" or "agentic") config: Configuration object. Required for agentic type. **kwargs: Additional configuration parameters - """ + """ FastACP.locate_build_info_path() - + if acp_type == "sync": sync_config = config if isinstance(config, SyncACPConfig) else None return FastACP.create_sync_acp(sync_config, **kwargs) diff --git a/src/agentex/lib/sdk/fastacp/impl/agentic_base_acp.py b/src/agentex/lib/sdk/fastacp/impl/agentic_base_acp.py index 02d5a0b0..102e783c 100644 --- a/src/agentex/lib/sdk/fastacp/impl/agentic_base_acp.py +++ b/src/agentex/lib/sdk/fastacp/impl/agentic_base_acp.py @@ -1,15 +1,14 @@ from typing import Any - -from agentex.lib.adk.utils._modules.client import create_async_agentex_client from typing_extensions import override -from agentex import AsyncAgentex -from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer + from agentex.lib.types.acp import ( + SendEventParams, CancelTaskParams, CreateTaskParams, - SendEventParams, ) from agentex.lib.utils.logging import make_logger +from agentex.lib.adk.utils._modules.client import create_async_agentex_client +from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer logger = make_logger(__name__) diff --git a/src/agentex/lib/sdk/fastacp/impl/sync_acp.py b/src/agentex/lib/sdk/fastacp/impl/sync_acp.py index 68f6f4ef..1e3485df 100644 --- a/src/agentex/lib/sdk/fastacp/impl/sync_acp.py +++ b/src/agentex/lib/sdk/fastacp/impl/sync_acp.py @@ -1,16 +1,16 @@ -from collections.abc import AsyncGenerator from typing import Any, override +from collections.abc import AsyncGenerator -from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer from agentex.lib.types.acp import SendMessageParams +from agentex.lib.utils.logging import make_logger +from agentex.types.task_message_delta import TextDelta from agentex.types.task_message_update import ( - StreamTaskMessageDelta, - StreamTaskMessageFull, TaskMessageUpdate, + StreamTaskMessageFull, + StreamTaskMessageDelta, ) -from agentex.types.task_message_delta import TextDelta -from agentex.types.task_message_content import TaskMessageContent, TextContent -from agentex.lib.utils.logging import make_logger +from agentex.types.task_message_content import TextContent, TaskMessageContent +from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer logger = make_logger(__name__) diff --git a/src/agentex/lib/sdk/fastacp/impl/temporal_acp.py b/src/agentex/lib/sdk/fastacp/impl/temporal_acp.py index b691bf33..efce91d6 100644 --- a/src/agentex/lib/sdk/fastacp/impl/temporal_acp.py +++ b/src/agentex/lib/sdk/fastacp/impl/temporal_acp.py @@ -1,18 +1,18 @@ +from typing import Any, Callable, AsyncGenerator, override from contextlib import asynccontextmanager -from typing import Any, AsyncGenerator, Callable from fastapi import FastAPI -from agentex.lib.core.clients.temporal.temporal_client import TemporalClient -from agentex.lib.core.temporal.services.temporal_task_service import TemporalTaskService -from agentex.lib.environment_variables import EnvironmentVariables -from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer from agentex.lib.types.acp import ( + SendEventParams, CancelTaskParams, CreateTaskParams, - SendEventParams, ) from agentex.lib.utils.logging import make_logger +from agentex.lib.environment_variables import EnvironmentVariables +from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer +from agentex.lib.core.clients.temporal.temporal_client import TemporalClient +from agentex.lib.core.temporal.services.temporal_task_service import TemporalTaskService logger = make_logger(__name__) @@ -24,7 +24,10 @@ class TemporalACP(BaseACPServer): """ def __init__( - self, temporal_address: str, temporal_task_service: TemporalTaskService | None = None, plugins: list[Any] | None = None + self, + temporal_address: str, + temporal_task_service: TemporalTaskService | None = None, + plugins: list[Any] | None = None, ): super().__init__() self._temporal_task_service = temporal_task_service @@ -32,6 +35,7 @@ def __init__( self._plugins = plugins or [] @classmethod + @override def create(cls, temporal_address: str, plugins: list[Any] | None = None) -> "TemporalACP": logger.info("Initializing TemporalACP instance") @@ -41,7 +45,7 @@ def create(cls, temporal_address: str, plugins: list[Any] | None = None) -> "Tem logger.info("TemporalACP instance initialized now") return temporal_acp - # This is to override the lifespan function of the base + @override def get_lifespan_function(self) -> Callable[[FastAPI], AsyncGenerator[None, None]]: @asynccontextmanager async def lifespan(app: FastAPI): @@ -52,8 +56,7 @@ async def lifespan(app: FastAPI): if self._temporal_task_service is None: env_vars = EnvironmentVariables.refresh() temporal_client = await TemporalClient.create( - temporal_address=self._temporal_address, - plugins=self._plugins + temporal_address=self._temporal_address, plugins=self._plugins ) self._temporal_task_service = TemporalTaskService( temporal_client=temporal_client, @@ -61,11 +64,12 @@ async def lifespan(app: FastAPI): ) # Call parent lifespan for agent registration - async with super().get_lifespan_function()(app): + async with super().get_lifespan_function()(app): # type: ignore[misc] yield - return lifespan + return lifespan # type: ignore[return-value] + @override def _setup_handlers(self): """Set up the handlers for temporal workflow operations""" @@ -73,17 +77,21 @@ def _setup_handlers(self): async def handle_task_create(params: CreateTaskParams) -> None: """Default create task handler - logs the task""" logger.info(f"TemporalACP received task create rpc call for task {params.task.id}") - await self._temporal_task_service.submit_task(agent=params.agent, task=params.task, params=params.params) + if self._temporal_task_service is not None: + await self._temporal_task_service.submit_task( + agent=params.agent, task=params.task, params=params.params + ) @self.on_task_event_send async def handle_event_send(params: SendEventParams) -> None: """Forward messages to running workflows via TaskService""" try: - await self._temporal_task_service.send_event( - agent=params.agent, - task=params.task, - event=params.event, - ) + if self._temporal_task_service is not None: + await self._temporal_task_service.send_event( + agent=params.agent, + task=params.task, + event=params.event, + ) except Exception as e: logger.error(f"Failed to send message: {e}") @@ -93,7 +101,8 @@ async def handle_event_send(params: SendEventParams) -> None: async def handle_cancel(params: CancelTaskParams) -> None: """Cancel running workflows via TaskService""" try: - await self._temporal_task_service.cancel(task_id=params.task.id) + if self._temporal_task_service is not None: + await self._temporal_task_service.cancel(task_id=params.task.id) except Exception as e: logger.error(f"Failed to cancel task: {e}") raise diff --git a/src/agentex/lib/sdk/fastacp/tests/conftest.py b/src/agentex/lib/sdk/fastacp/tests/conftest.py index 59b9e101..8b75cf2e 100644 --- a/src/agentex/lib/sdk/fastacp/tests/conftest.py +++ b/src/agentex/lib/sdk/fastacp/tests/conftest.py @@ -1,28 +1,28 @@ -import asyncio -import socket import time +import socket +import asyncio from typing import Any from unittest.mock import AsyncMock, patch import httpx import pytest -import pytest_asyncio import uvicorn +import pytest_asyncio -from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer -from agentex.lib.sdk.fastacp.impl.agentic_base_acp import AgenticBaseACP -from agentex.lib.sdk.fastacp.impl.sync_acp import SyncACP -from agentex.lib.sdk.fastacp.impl.temporal_acp import TemporalACP +from agentex.types.task import Task +from agentex.types.agent import Agent from agentex.lib.types.acp import ( CancelTaskParams, CreateTaskParams, SendMessageParams, ) from agentex.lib.types.json_rpc import JSONRPCRequest -from agentex.types.agent import Agent from agentex.types.task_message import TaskMessageContent from agentex.types.task_message_content import TextContent -from agentex.types.task import Task +from agentex.lib.sdk.fastacp.impl.sync_acp import SyncACP +from agentex.lib.sdk.fastacp.impl.temporal_acp import TemporalACP +from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer +from agentex.lib.sdk.fastacp.impl.agentic_base_acp import AgenticBaseACP # Configure pytest-asyncio pytest_plugins = ("pytest_asyncio",) @@ -47,7 +47,7 @@ def free_port() -> int: def sample_task() -> Task: """Fixture that provides a sample Task object""" return Task( - id="test-task-123", agent_id="test-agent-456", status=TaskStatus.RUNNING + id="test-task-123", status="RUNNING" ) @@ -72,6 +72,8 @@ def sample_send_message_params( name="test-agent", description="test-agent", acp_type="sync", + created_at="2023-01-01T00:00:00Z", + updated_at="2023-01-01T00:00:00Z", ), task=sample_task, content=sample_message_content, @@ -83,8 +85,8 @@ def sample_send_message_params( def sample_cancel_task_params() -> CancelTaskParams: """Fixture that provides sample CancelTaskParams""" return CancelTaskParams( - agent=Agent(id="test-agent-456", name="test-agent", description="test-agent", acp_type="sync"), - task=Task(id="test-task-123", agent_id="test-agent-456", status="running"), + agent=Agent(id="test-agent-456", name="test-agent", description="test-agent", acp_type="sync", created_at="2023-01-01T00:00:00Z", updated_at="2023-01-01T00:00:00Z"), + task=Task(id="test-task-123", status="RUNNING"), ) @@ -92,7 +94,7 @@ def sample_cancel_task_params() -> CancelTaskParams: def sample_create_task_params(sample_task: Task) -> CreateTaskParams: """Fixture that provides sample CreateTaskParams""" return CreateTaskParams( - agent=Agent(id="test-agent-456", name="test-agent", description="test-agent", acp_type="sync"), + agent=Agent(id="test-agent-456", name="test-agent", description="test-agent", acp_type="sync", created_at="2023-01-01T00:00:00Z", updated_at="2023-01-01T00:00:00Z"), task=sample_task, params={}, ) @@ -202,7 +204,7 @@ async def async_sync_acp_server(): with patch.dict( "os.environ", {"AGENTEX_BASE_URL": ""} ): # Disable agent registration - server = await SyncACP.create() + server = SyncACP.create() return server @@ -222,7 +224,7 @@ async def async_agentic_base_acp_server(): with patch.dict( "os.environ", {"AGENTEX_BASE_URL": ""} ): # Disable agent registration - server = await AgenticBaseACP.create() + server = AgenticBaseACP.create() return server @@ -242,7 +244,7 @@ async def mock_temporal_acp_server(): mock_temporal_client.create.return_value = AsyncMock() mock_agentex_client.return_value = AsyncMock() - server = await TemporalACP.create(temporal_address="localhost:7233") + server = TemporalACP.create(temporal_address="localhost:7233") return server diff --git a/src/agentex/lib/sdk/fastacp/tests/run_tests.py b/src/agentex/lib/sdk/fastacp/tests/run_tests.py index eb08d9c1..8b23be16 100644 --- a/src/agentex/lib/sdk/fastacp/tests/run_tests.py +++ b/src/agentex/lib/sdk/fastacp/tests/run_tests.py @@ -10,9 +10,9 @@ - Run performance tests """ +import sys import argparse import subprocess -import sys from pathlib import Path diff --git a/src/agentex/lib/sdk/fastacp/tests/test_base_acp_server.py b/src/agentex/lib/sdk/fastacp/tests/test_base_acp_server.py index b0f26a81..0816ac43 100644 --- a/src/agentex/lib/sdk/fastacp/tests/test_base_acp_server.py +++ b/src/agentex/lib/sdk/fastacp/tests/test_base_acp_server.py @@ -1,15 +1,16 @@ +# ruff: noqa: ARG001 import asyncio from unittest.mock import patch import pytest from fastapi.testclient import TestClient -from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer from agentex.lib.types.acp import ( - CancelTaskParams, RPCMethod, SendEventParams, + CancelTaskParams, ) +from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer class TestBaseACPServerInitialization: @@ -21,7 +22,7 @@ def test_base_acp_server_init(self): server = BaseACPServer() # Check that FastAPI routes are set up - routes = [route.path for route in server.routes] + routes = [route.path for route in server.routes] # type: ignore[attr-defined] assert "/healthz" in routes assert "/api" in routes @@ -141,7 +142,6 @@ async def mock_handler(params): data = response.json() assert data["jsonrpc"] == "2.0" assert data["id"] == "test-1" - print("DATA", data) # Should return immediate acknowledgment assert data["result"]["status"] == "processing" diff --git a/src/agentex/lib/sdk/fastacp/tests/test_fastacp_factory.py b/src/agentex/lib/sdk/fastacp/tests/test_fastacp_factory.py index 62bdc125..a7272700 100644 --- a/src/agentex/lib/sdk/fastacp/tests/test_fastacp_factory.py +++ b/src/agentex/lib/sdk/fastacp/tests/test_fastacp_factory.py @@ -3,17 +3,17 @@ import pytest -from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer -from agentex.lib.sdk.fastacp.fastacp import FastACP -from agentex.lib.sdk.fastacp.impl.agentic_base_acp import AgenticBaseACP -from agentex.lib.sdk.fastacp.impl.sync_acp import SyncACP -from agentex.lib.sdk.fastacp.impl.temporal_acp import TemporalACP from agentex.lib.types.fastacp import ( - AgenticACPConfig, - AgenticBaseACPConfig, SyncACPConfig, + AgenticACPConfig, TemporalACPConfig, + AgenticBaseACPConfig, ) +from agentex.lib.sdk.fastacp.fastacp import FastACP +from agentex.lib.sdk.fastacp.impl.sync_acp import SyncACP +from agentex.lib.sdk.fastacp.impl.temporal_acp import TemporalACP +from agentex.lib.sdk.fastacp.base.base_acp_server import BaseACPServer +from agentex.lib.sdk.fastacp.impl.agentic_base_acp import AgenticBaseACP class TestFastACPInitialization: @@ -212,7 +212,7 @@ async def test_agentic_direct_method_requires_config(self): with patch.dict("os.environ", {"AGENTEX_BASE_URL": ""}): # This should raise TypeError since config is required parameter with pytest.raises(TypeError): - FastACP.create_agentic_acp() + FastACP.create_agentic_acp() # type: ignore[call-arg] def test_invalid_acp_type_string(self): """Test that invalid ACP type string raises ValueError""" diff --git a/src/agentex/lib/sdk/fastacp/tests/test_integration.py b/src/agentex/lib/sdk/fastacp/tests/test_integration.py index dad39003..d49f4dce 100644 --- a/src/agentex/lib/sdk/fastacp/tests/test_integration.py +++ b/src/agentex/lib/sdk/fastacp/tests/test_integration.py @@ -1,18 +1,19 @@ +# ruff: noqa: ARG001 import asyncio from unittest.mock import AsyncMock, MagicMock, patch import httpx import pytest -from agentex.lib.sdk.fastacp.impl.agentic_base_acp import AgenticBaseACP -from agentex.lib.sdk.fastacp.impl.sync_acp import SyncACP -from agentex.lib.sdk.fastacp.impl.temporal_acp import TemporalACP from agentex.lib.types.acp import ( - CancelTaskParams, - CreateTaskParams, RPCMethod, SendEventParams, + CancelTaskParams, + CreateTaskParams, ) +from agentex.lib.sdk.fastacp.impl.sync_acp import SyncACP +from agentex.lib.sdk.fastacp.impl.temporal_acp import TemporalACP +from agentex.lib.sdk.fastacp.impl.agentic_base_acp import AgenticBaseACP class TestImplementationBehavior: @@ -48,7 +49,7 @@ async def test_temporal_acp_creation_with_mocked_client(self): mock_temporal_instance.temporal_client = MagicMock() mock_create.return_value = mock_temporal_instance - temporal_acp = await TemporalACP.create() + temporal_acp = TemporalACP.create(temporal_address="localhost:7233") assert temporal_acp == mock_temporal_instance assert hasattr(temporal_acp, "temporal_client") @@ -67,8 +68,8 @@ async def message_handler(params: SendEventParams): messages_received.append( { "task_id": params.task.id, - "message_content": params.message.content, - "author": params.message.author, + "message_content": params.message.content, # type: ignore[attr-defined] + "author": params.message.author, # type: ignore[attr-defined] } ) return {"processed": True} @@ -127,7 +128,7 @@ async def message_handler(params: SendEventParams): @agentic_base_acp.on_task_cancel async def cancel_handler(params: CancelTaskParams): - task_events.append(("cancelled", params.task_id)) + task_events.append(("cancelled", params.task_id)) # type: ignore[attr-defined] runner = test_server_runner(agentic_base_acp, free_port) await runner.start() @@ -209,7 +210,7 @@ async def test_server_resilience_to_handler_failures( @sync_acp.on_task_event_send async def unreliable_handler(params: SendEventParams): nonlocal failure_count, success_count - if "fail" in params.message.content: + if "fail" in params.message.content: # type: ignore[attr-defined] failure_count += 1 raise RuntimeError("Simulated handler failure") else: @@ -326,7 +327,7 @@ async def test_notification_vs_request_behavior(self, sync_acp, free_port, test_ @sync_acp.on_task_event_send async def tracking_handler(params: SendEventParams): nonlocal notifications_received, requests_received - if "notification" in params.message.content: + if "notification" in params.message.content: # type: ignore[attr-defined] notifications_received += 1 else: requests_received += 1 @@ -397,7 +398,7 @@ async def test_unicode_message_handling(self, sync_acp, free_port, test_server_r @sync_acp.on_task_event_send async def unicode_handler(params: SendEventParams): nonlocal received_message - received_message = params.message.content + received_message = params.message.content # type: ignore[attr-defined] return {"unicode_handled": True} runner = test_server_runner(sync_acp, free_port) @@ -458,9 +459,9 @@ async def agentic_handler(params: SendEventParams): return {"agentic": True} # Create test parameters - message_params = SendEventParams( + message_params = SendEventParams( # type: ignore[call-arg] task={"id": "isolation-test-task", "agent_id": "test-agent", "status": "RUNNING"}, - message={"type": "text", "author": "user", "content": "Isolation test"}, + event={"type": "text", "author": "user", "content": "Isolation test"}, # type: ignore[misc] ) # Execute sync handler diff --git a/src/agentex/lib/sdk/state_machine/__init__.py b/src/agentex/lib/sdk/state_machine/__init__.py index 9f247d54..92dc35fe 100644 --- a/src/agentex/lib/sdk/state_machine/__init__.py +++ b/src/agentex/lib/sdk/state_machine/__init__.py @@ -1,5 +1,5 @@ -from .noop_workflow import NoOpWorkflow from .state import State +from .noop_workflow import NoOpWorkflow from .state_machine import StateMachine from .state_workflow import StateWorkflow diff --git a/src/agentex/lib/sdk/state_machine/noop_workflow.py b/src/agentex/lib/sdk/state_machine/noop_workflow.py index aa6fe59a..e43f820f 100644 --- a/src/agentex/lib/sdk/state_machine/noop_workflow.py +++ b/src/agentex/lib/sdk/state_machine/noop_workflow.py @@ -1,8 +1,9 @@ +from typing import TYPE_CHECKING, override + from pydantic import BaseModel -from agentex.lib.sdk.state_machine.state_workflow import StateWorkflow from agentex.lib.utils.logging import make_logger -from typing import TYPE_CHECKING +from agentex.lib.sdk.state_machine.state_workflow import StateWorkflow if TYPE_CHECKING: from agentex.lib.sdk.state_machine import StateMachine @@ -15,7 +16,8 @@ class NoOpWorkflow(StateWorkflow): Workflow that does nothing. This is commonly used as a terminal state. """ + @override async def execute( self, state_machine: "StateMachine", state_machine_data: BaseModel | None = None ) -> str: - pass + return state_machine.get_current_state() # Stay in current state diff --git a/src/agentex/lib/sdk/state_machine/state_machine.py b/src/agentex/lib/sdk/state_machine/state_machine.py index 7ca54b7b..18736650 100644 --- a/src/agentex/lib/sdk/state_machine/state_machine.py +++ b/src/agentex/lib/sdk/state_machine/state_machine.py @@ -2,9 +2,9 @@ from typing import Any, Generic, TypeVar from agentex.lib import adk +from agentex.lib.utils.model_utils import BaseModel from agentex.lib.sdk.state_machine.state import State from agentex.lib.sdk.state_machine.state_workflow import StateWorkflow -from agentex.lib.utils.model_utils import BaseModel T = TypeVar("T", bound=BaseModel) @@ -55,7 +55,13 @@ async def transition(self, target_state_name: str): raise ValueError(f"State {target_state_name} not found") self._current_state = self._state_map[target_state_name] - def get_state_machine_data(self) -> T: + def get_state_machine_data(self) -> T | None: + return self.state_machine_data + + def require_state_machine_data(self) -> T: + """Get state machine data, raising an error if not set.""" + if self.state_machine_data is None: + raise ValueError("State machine data not initialized - ensure data is provided") return self.state_machine_data @abstractmethod @@ -70,7 +76,10 @@ async def run(self): async def step(self) -> str: current_state_name = self.get_current_state() current_state = self._state_map.get(current_state_name) + if current_state is None: + raise ValueError(f"Current state '{current_state_name}' not found in state map") + span = None if self._trace_transitions: if self._task_id is None: raise ValueError( @@ -79,7 +88,7 @@ async def step(self) -> str: span = await adk.tracing.start_span( trace_id=self._task_id, name="state_transition", - input=self.state_machine_data.model_dump(), + input=self.require_state_machine_data().model_dump(), data={"input_state": current_state_name}, ) @@ -87,13 +96,10 @@ async def step(self) -> str: state_machine=self, state_machine_data=self.state_machine_data ) - if self._trace_transitions: - if self._task_id is None: - raise ValueError( - "Task ID is must be set before tracing can be enabled" - ) - span.output = self.state_machine_data.model_dump() - span.data["output_state"] = next_state_name + if self._trace_transitions and span is not None: + span.output = self.require_state_machine_data().model_dump() # type: ignore[assignment] + if span.data is not None: + span.data["output_state"] = next_state_name # type: ignore[index] await adk.tracing.end_span(trace_id=self._task_id, span=span) await self.transition(next_state_name) @@ -118,7 +124,7 @@ async def reset_to_initial_state(self): await self.transition(self._initial_state) if self._trace_transitions: - span.output = {"output_state": self._initial_state} + span.output = {"output_state": self._initial_state} # type: ignore[assignment,union-attr] await adk.tracing.end_span(trace_id=self._task_id, span=span) def dump(self) -> dict[str, Any]: @@ -168,7 +174,7 @@ async def load(cls, data: dict[str, Any], states: list[State]) -> "StateMachine[ state_machine_data = None if state_machine_data_dict is not None: # Get the actual model type from the class's type parameters - model_type = cls.__orig_bases__[0].__args__[0] + model_type = cls.__orig_bases__[0].__args__[0] # type: ignore[attr-defined] state_machine_data = model_type.model_validate(state_machine_data_dict) # Create a new instance diff --git a/src/agentex/lib/sdk/utils/messages.py b/src/agentex/lib/sdk/utils/messages.py index 98dd7cb9..91edeb60 100644 --- a/src/agentex/lib/sdk/utils/messages.py +++ b/src/agentex/lib/sdk/utils/messages.py @@ -2,17 +2,17 @@ from abc import ABC, abstractmethod from typing import Any, Literal, override +from agentex.types.data_content import DataContent +from agentex.types.task_message import TaskMessage +from agentex.types.text_content import TextContent from agentex.lib.types.llm_messages import ( - AssistantMessage, Message, ToolCall, - ToolCallRequest, ToolMessage, UserMessage, + ToolCallRequest, + AssistantMessage, ) -from agentex.types.data_content import DataContent -from agentex.types.task_message import TaskMessage -from agentex.types.text_content import TextContent from agentex.types.tool_request_content import ToolRequestContent from agentex.types.tool_response_content import ToolResponseContent diff --git a/src/agentex/lib/types/acp.py b/src/agentex/lib/types/acp.py index d93e2894..67fa132e 100644 --- a/src/agentex/lib/types/acp.py +++ b/src/agentex/lib/types/acp.py @@ -1,12 +1,12 @@ from enum import Enum from typing import Any -from pydantic import BaseModel, Field +from pydantic import Field, BaseModel +from agentex.types.task import Task from agentex.types.agent import Agent from agentex.types.event import Event from agentex.types.task_message_content import TaskMessageContent -from agentex.types.task import Task class RPCMethod(str, Enum): diff --git a/src/agentex/lib/types/agent_configs.py b/src/agentex/lib/types/agent_configs.py index 8d9548a8..41b9ccb4 100644 --- a/src/agentex/lib/types/agent_configs.py +++ b/src/agentex/lib/types/agent_configs.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, Field, model_validator, validator +from pydantic import Field, BaseModel, validator, model_validator class TemporalWorkflowConfig(BaseModel): diff --git a/src/agentex/lib/types/converters.py b/src/agentex/lib/types/converters.py index 5b876a1c..1114b74c 100644 --- a/src/agentex/lib/types/converters.py +++ b/src/agentex/lib/types/converters.py @@ -1,9 +1,11 @@ +import json + +from agents import TResponseInputItem + from agentex.types.task_message import TaskMessage from agentex.types.text_content import TextContent from agentex.types.tool_request_content import ToolRequestContent from agentex.types.tool_response_content import ToolResponseContent -import json -from agents import TResponseInputItem def convert_task_messages_to_oai_agents_inputs( diff --git a/src/agentex/lib/types/credentials.py b/src/agentex/lib/types/credentials.py index 71916512..7f4b79d1 100644 --- a/src/agentex/lib/types/credentials.py +++ b/src/agentex/lib/types/credentials.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, Field +from pydantic import Field, BaseModel class CredentialMapping(BaseModel): diff --git a/src/agentex/lib/types/fastacp.py b/src/agentex/lib/types/fastacp.py index 4862fe50..6b3dfa47 100644 --- a/src/agentex/lib/types/fastacp.py +++ b/src/agentex/lib/types/fastacp.py @@ -1,6 +1,7 @@ from typing import Any, Literal -from pydantic import BaseModel, Field, field_validator +from pydantic import Field, BaseModel, field_validator + from agentex.lib.core.clients.temporal.utils import validate_client_plugins @@ -48,11 +49,9 @@ class TemporalACPConfig(AgenticACPConfig): """ type: Literal["temporal"] = Field(default="temporal", frozen=True) - temporal_address: str = Field( - default="temporal-frontend.temporal.svc.cluster.local:7233", frozen=True - ) + temporal_address: str = Field(default="temporal-frontend.temporal.svc.cluster.local:7233", frozen=True) plugins: list[Any] = Field(default=[], frozen=True) - + @field_validator("plugins") @classmethod def validate_plugins(cls, v: list[Any]) -> list[Any]: diff --git a/src/agentex/lib/types/tracing.py b/src/agentex/lib/types/tracing.py index f9a0af3d..ede85d83 100644 --- a/src/agentex/lib/types/tracing.py +++ b/src/agentex/lib/types/tracing.py @@ -1,4 +1,4 @@ -from typing import Annotated, Literal +from typing import Literal, Annotated from pydantic import Field diff --git a/src/agentex/lib/utils/completions.py b/src/agentex/lib/utils/completions.py index 89114dcb..a479309e 100644 --- a/src/agentex/lib/utils/completions.py +++ b/src/agentex/lib/utils/completions.py @@ -1,18 +1,18 @@ from copy import deepcopy -from functools import reduce, singledispatch from typing import Any +from functools import reduce, singledispatch from agentex.lib.types.llm_messages import ( - Choice, - Completion, Delta, + Choice, ToolCall, + Completion, ToolCallRequest, ) @singledispatch -def _concat_chunks(a: None, b: Any): +def _concat_chunks(_a: None, b: Any): return b @@ -116,7 +116,7 @@ def concat_completion_chunks(chunks: list[Completion]) -> Completion: single `CompletionChunk`. Finally we convert the type to the appropriate non-streaming type `Completion` and return it. """ if not chunks: - return None + raise ValueError("Cannot concatenate empty chunks list") chunks_copy = chunks.copy() chunks_copy[0] = deepcopy(chunks_copy[0]) # _concat_chunks mutates first argument diff --git a/src/agentex/lib/utils/console.py b/src/agentex/lib/utils/console.py index 17f89198..50c0d5d3 100644 --- a/src/agentex/lib/utils/console.py +++ b/src/agentex/lib/utils/console.py @@ -1,6 +1,6 @@ from rich import box -from rich.console import Console from rich.table import Table +from rich.console import Console console = Console() diff --git a/src/agentex/lib/utils/debug.py b/src/agentex/lib/utils/debug.py index b955dfc8..69cbf6b1 100644 --- a/src/agentex/lib/utils/debug.py +++ b/src/agentex/lib/utils/debug.py @@ -5,8 +5,13 @@ """ import os + import debugpy # type: ignore +from agentex.lib.utils.logging import make_logger + +logger = make_logger(__name__) + def setup_debug_if_enabled() -> None: """ @@ -33,14 +38,14 @@ def setup_debug_if_enabled() -> None: debugpy.configure(subProcess=False) debugpy.listen(debug_port) - print(f"🐛 [{debug_type.upper()}] Debug server listening on port {debug_port}") - + logger.info(f"🐛 [{debug_type.upper()}] Debug server listening on port {debug_port}") + if wait_for_attach: - print(f"⏳ [{debug_type.upper()}] Waiting for debugger to attach...") + logger.info(f"⏳ [{debug_type.upper()}] Waiting for debugger to attach...") debugpy.wait_for_client() - print(f"✅ [{debug_type.upper()}] Debugger attached!") + logger.info(f"✅ [{debug_type.upper()}] Debugger attached!") else: - print(f"📡 [{debug_type.upper()}] Ready for debugger attachment") + logger.info(f"📡 [{debug_type.upper()}] Ready for debugger attachment") def is_debug_enabled() -> bool: diff --git a/src/agentex/lib/utils/dev_tools/async_messages.py b/src/agentex/lib/utils/dev_tools/async_messages.py index ec42033d..028e85c0 100644 --- a/src/agentex/lib/utils/dev_tools/async_messages.py +++ b/src/agentex/lib/utils/dev_tools/async_messages.py @@ -6,26 +6,25 @@ """ import json -from datetime import datetime, timezone from typing import List, Optional +from datetime import datetime, timezone -from yaspin.core import Yaspin +from yaspin import yaspin # type: ignore[import-untyped] +from rich.panel import Panel +from yaspin.core import Yaspin # type: ignore[import-untyped] +from rich.console import Console +from rich.markdown import Markdown from agentex import Agentex -from agentex.types import Task, TaskMessage, TextContent, ToolRequestContent, ToolResponseContent, ReasoningContent +from agentex.types import Task, TaskMessage, TextContent, ReasoningContent, ToolRequestContent, ToolResponseContent +from agentex.types.text_delta import TextDelta from agentex.types.task_message_update import ( TaskMessageUpdate, - StreamTaskMessageStart, - StreamTaskMessageDelta, + StreamTaskMessageDone, StreamTaskMessageFull, - StreamTaskMessageDone + StreamTaskMessageDelta, + StreamTaskMessageStart, ) -from agentex.types.text_delta import TextDelta - -from rich.console import Console -from rich.panel import Panel -from rich.markdown import Markdown -from yaspin import yaspin def print_task_message( diff --git a/src/agentex/lib/utils/iterables.py b/src/agentex/lib/utils/iterables.py index 31a27596..d3dc452c 100644 --- a/src/agentex/lib/utils/iterables.py +++ b/src/agentex/lib/utils/iterables.py @@ -1,5 +1,5 @@ -from collections.abc import AsyncGenerator from typing import Any +from collections.abc import AsyncGenerator async def async_enumerate( diff --git a/src/agentex/lib/utils/json_schema.py b/src/agentex/lib/utils/json_schema.py index da34b6d4..b657b658 100644 --- a/src/agentex/lib/utils/json_schema.py +++ b/src/agentex/lib/utils/json_schema.py @@ -10,10 +10,10 @@ def resolve_refs(schema: dict) -> dict: """ resolved = jsonref.replace_refs(schema, proxies=False, lazy_load=False) serializable = { - "type": resolved.get("type"), - "properties": resolved.get("properties"), - "required": list(resolved.get("required", [])), - "additionalProperties": resolved.get("additionalProperties", False), + "type": resolved.get("type"), # type: ignore[union-attr] + "properties": resolved.get("properties"), # type: ignore[union-attr] + "required": list(resolved.get("required", [])), # type: ignore[union-attr] + "additionalProperties": resolved.get("additionalProperties", False), # type: ignore[union-attr] } return serializable diff --git a/src/agentex/lib/utils/logging.py b/src/agentex/lib/utils/logging.py index 8a8b1b20..5bbaf61a 100644 --- a/src/agentex/lib/utils/logging.py +++ b/src/agentex/lib/utils/logging.py @@ -1,11 +1,11 @@ +import os import logging import contextvars + +import ddtrace +import json_log_formatter from rich.console import Console from rich.logging import RichHandler -import json_log_formatter -import os -import ddtrace -from ddtrace import tracer _is_datadog_configured = bool(os.environ.get("DD_AGENT_HOST")) @@ -13,7 +13,7 @@ class CustomJSONFormatter(json_log_formatter.JSONFormatter): - def json_record(self, message: str, extra: dict, record: logging.LogRecord) -> dict: + def json_record(self, message: str, extra: dict, record: logging.LogRecord) -> dict: # type: ignore[override] extra = super().json_record(message, extra, record) extra["level"] = record.levelname extra["name"] = record.name @@ -21,10 +21,10 @@ def json_record(self, message: str, extra: dict, record: logging.LogRecord) -> d extra["pathname"] = record.pathname extra["request_id"] = ctx_var_request_id.get(None) if _is_datadog_configured: - extra["dd.trace_id"] = tracer.get_log_correlation_context().get("dd.trace_id", None) or getattr( + extra["dd.trace_id"] = ddtrace.tracer.get_log_correlation_context().get("dd.trace_id", None) or getattr( # type: ignore[attr-defined] record, "dd.trace_id", 0 ) - extra["dd.span_id"] = tracer.get_log_correlation_context().get("dd.span_id", None) or getattr( + extra["dd.span_id"] = ddtrace.tracer.get_log_correlation_context().get("dd.span_id", None) or getattr( # type: ignore[attr-defined] record, "dd.span_id", 0 ) # add the env, service, and version configured for the tracer diff --git a/src/agentex/lib/utils/mcp.py b/src/agentex/lib/utils/mcp.py index b95126a6..e8f63d66 100644 --- a/src/agentex/lib/utils/mcp.py +++ b/src/agentex/lib/utils/mcp.py @@ -1,4 +1,5 @@ from typing import Any + from mcp import StdioServerParameters diff --git a/src/agentex/lib/utils/model_utils.py b/src/agentex/lib/utils/model_utils.py index 2fa1cffd..91f09bd6 100644 --- a/src/agentex/lib/utils/model_utils.py +++ b/src/agentex/lib/utils/model_utils.py @@ -1,9 +1,8 @@ -from collections.abc import Iterable, Mapping -from datetime import datetime from typing import Any, TypeVar +from datetime import datetime +from collections.abc import Mapping, Iterable -from pydantic import BaseModel as PydanticBaseModel -from pydantic import ConfigDict +from pydantic import BaseModel as PydanticBaseModel, ConfigDict from agentex.lib.utils.io import load_yaml_file @@ -28,7 +27,7 @@ def from_yaml(cls: type[T], file_path: str) -> T: def to_json(self, *args, **kwargs) -> str: return self.model_dump_json(*args, **kwargs) - def to_dict(self, *args, **kwargs) -> dict[str, Any]: + def to_dict(self, *_args, **_kwargs) -> dict[str, Any]: return recursive_model_dump(self) diff --git a/src/agentex/lib/utils/registration.py b/src/agentex/lib/utils/registration.py index dfc28448..fee247d1 100644 --- a/src/agentex/lib/utils/registration.py +++ b/src/agentex/lib/utils/registration.py @@ -1,11 +1,12 @@ -import base64 -import json import os -import httpx +import json +import base64 import asyncio -from agentex.lib.environment_variables import EnvironmentVariables, refreshed_environment_variables +import httpx + from agentex.lib.utils.logging import make_logger +from agentex.lib.environment_variables import EnvironmentVariables logger = make_logger(__name__) diff --git a/src/agentex/resources/agents.py b/src/agentex/resources/agents.py index 232d1d82..ad13186d 100644 --- a/src/agentex/resources/agents.py +++ b/src/agentex/resources/agents.py @@ -3,7 +3,7 @@ from __future__ import annotations import json -from typing import AsyncGenerator, Generator, Union, Optional +from typing import Union, Optional, Generator, AsyncGenerator from typing_extensions import Literal import httpx @@ -22,7 +22,14 @@ ) from ..types.agent import Agent from .._base_client import make_request_options -from ..types.agent_rpc_response import AgentRpcResponse, CancelTaskResponse, CreateTaskResponse, SendEventResponse, SendMessageResponse, SendMessageStreamResponse +from ..types.agent_rpc_response import ( + AgentRpcResponse, + SendEventResponse, + CancelTaskResponse, + CreateTaskResponse, + SendMessageResponse, + SendMessageStreamResponse, +) from ..types.agent_list_response import AgentListResponse from ..types.shared.delete_response import DeleteResponse @@ -1074,8 +1081,8 @@ async def send_message_stream( except json.JSONDecodeError: # Skip invalid JSON lines continue - except ValidationError: - raise ValueError(f"Invalid SendMessageStreamResponse returned: {line}") + except ValidationError as e: + raise ValueError(f"Invalid SendMessageStreamResponse returned: {line}") from e async def send_event( self, diff --git a/src/agentex/types/agent_rpc_response.py b/src/agentex/types/agent_rpc_response.py index d1b9af83..fdeeddcb 100644 --- a/src/agentex/types/agent_rpc_response.py +++ b/src/agentex/types/agent_rpc_response.py @@ -3,11 +3,11 @@ from typing import Union, Optional from typing_extensions import Literal -from .._models import BaseModel -from .agent_rpc_result import AgentRpcResult -from .event import Event from .task import Task +from .event import Event +from .._models import BaseModel from .task_message import TaskMessage +from .agent_rpc_result import AgentRpcResult from .task_message_update import TaskMessageUpdate __all__ = ["AgentRpcResponse"] diff --git a/src/agentex/types/data_content.py b/src/agentex/types/data_content.py index 5cb47992..f23212fe 100644 --- a/src/agentex/types/data_content.py +++ b/src/agentex/types/data_content.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, Optional +from typing import Dict from typing_extensions import Literal from .._models import BaseModel diff --git a/src/agentex/types/tool_request_content.py b/src/agentex/types/tool_request_content.py index 4fd52eb8..8282ac3b 100644 --- a/src/agentex/types/tool_request_content.py +++ b/src/agentex/types/tool_request_content.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, Optional +from typing import Dict from typing_extensions import Literal from .._models import BaseModel diff --git a/src/agentex/types/tool_response_content.py b/src/agentex/types/tool_response_content.py index 7f72443c..bf155974 100644 --- a/src/agentex/types/tool_response_content.py +++ b/src/agentex/types/tool_response_content.py @@ -1,6 +1,5 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional from typing_extensions import Literal from .._models import BaseModel diff --git a/tests/api_resources/messages/test_batch.py b/tests/api_resources/messages/test_batch.py index a572047a..01ac574a 100644 --- a/tests/api_resources/messages/test_batch.py +++ b/tests/api_resources/messages/test_batch.py @@ -8,9 +8,10 @@ import pytest from agentex import Agentex, AsyncAgentex -from tests.utils import assert_matches_type from agentex.types.messages import BatchCreateResponse, BatchUpdateResponse +from ...utils import assert_matches_type + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") diff --git a/tests/api_resources/test_agents.py b/tests/api_resources/test_agents.py index 7ef11072..4f2c58d0 100644 --- a/tests/api_resources/test_agents.py +++ b/tests/api_resources/test_agents.py @@ -8,7 +8,6 @@ import pytest from agentex import Agentex, AsyncAgentex -from tests.utils import assert_matches_type from agentex.types import ( Agent, AgentRpcResponse, @@ -16,6 +15,8 @@ ) from agentex.types.shared import DeleteResponse +from ..utils import assert_matches_type + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") diff --git a/tests/api_resources/test_events.py b/tests/api_resources/test_events.py index fad95592..ccf5f7bf 100644 --- a/tests/api_resources/test_events.py +++ b/tests/api_resources/test_events.py @@ -8,9 +8,10 @@ import pytest from agentex import Agentex, AsyncAgentex -from tests.utils import assert_matches_type from agentex.types import Event, EventListResponse +from ..utils import assert_matches_type + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") diff --git a/tests/api_resources/test_messages.py b/tests/api_resources/test_messages.py index 457d3e24..0c53d9b0 100644 --- a/tests/api_resources/test_messages.py +++ b/tests/api_resources/test_messages.py @@ -8,12 +8,13 @@ import pytest from agentex import Agentex, AsyncAgentex -from tests.utils import assert_matches_type from agentex.types import ( TaskMessage, MessageListResponse, ) +from ..utils import assert_matches_type + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") diff --git a/tests/api_resources/test_spans.py b/tests/api_resources/test_spans.py index b9c9b168..3617debf 100644 --- a/tests/api_resources/test_spans.py +++ b/tests/api_resources/test_spans.py @@ -8,10 +8,11 @@ import pytest from agentex import Agentex, AsyncAgentex -from tests.utils import assert_matches_type from agentex.types import Span, SpanListResponse from agentex._utils import parse_datetime +from ..utils import assert_matches_type + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") diff --git a/tests/api_resources/test_states.py b/tests/api_resources/test_states.py index b23ff49a..a1bc41d2 100644 --- a/tests/api_resources/test_states.py +++ b/tests/api_resources/test_states.py @@ -8,9 +8,10 @@ import pytest from agentex import Agentex, AsyncAgentex -from tests.utils import assert_matches_type from agentex.types import State, StateListResponse +from ..utils import assert_matches_type + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") diff --git a/tests/api_resources/test_tasks.py b/tests/api_resources/test_tasks.py index ac858a80..86fe14a2 100644 --- a/tests/api_resources/test_tasks.py +++ b/tests/api_resources/test_tasks.py @@ -8,10 +8,11 @@ import pytest from agentex import Agentex, AsyncAgentex -from tests.utils import assert_matches_type from agentex.types import Task, TaskListResponse from agentex.types.shared import DeleteResponse +from ..utils import assert_matches_type + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") diff --git a/tests/api_resources/test_tracker.py b/tests/api_resources/test_tracker.py index 431508c3..af8a60dd 100644 --- a/tests/api_resources/test_tracker.py +++ b/tests/api_resources/test_tracker.py @@ -8,9 +8,10 @@ import pytest from agentex import Agentex, AsyncAgentex -from tests.utils import assert_matches_type from agentex.types import AgentTaskTracker, TrackerListResponse +from ..utils import assert_matches_type + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") diff --git a/tests/test_function_tool.py b/tests/test_function_tool.py index 6f100408..595c1504 100644 --- a/tests/test_function_tool.py +++ b/tests/test_function_tool.py @@ -1,8 +1,10 @@ import json +from typing import Any, override + import pytest -from typing import Any +from pydantic import ValidationError -from src.agentex.lib.core.temporal.activities.adk.providers.openai_activities import ( +from src.agentex.lib.core.temporal.activities.adk.providers.openai_activities import ( # type: ignore[import-untyped] FunctionTool, ) @@ -189,6 +191,7 @@ class UnserializableCallable: def __call__(self, context, args): return "test" + @override def __getstate__(self): raise Exception("Cannot serialize this object") @@ -224,7 +227,7 @@ def test_deserialization_error_handling(self): ) # This should raise an error during model validation due to invalid base64 - with pytest.raises(Exception): # Could be ValidationError or ValueError + with pytest.raises((ValidationError, ValueError)): FunctionTool.model_validate(serialized_data) def test_full_roundtrip_with_serialization(self): diff --git a/tests/test_header_forwarding.py b/tests/test_header_forwarding.py index 6e5b242f..f1d25f26 100644 --- a/tests/test_header_forwarding.py +++ b/tests/test_header_forwarding.py @@ -1,5 +1,5 @@ # ruff: noqa: I001 -from typing import Any +from typing import Any, override import sys import types @@ -17,7 +17,7 @@ class _StubSpan: async def __aenter__(self): return self - async def __aexit__(self, exc_type, exc, tb): + async def __aexit__(self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: object) -> bool: return False class _StubTrace: @@ -32,13 +32,13 @@ def trace(self, trace_id: str | None = None) -> _StubTrace: # type: ignore[name class _StubTracer(_StubAsyncTracer): pass -tracer_stub.AsyncTracer = _StubAsyncTracer -tracer_stub.Tracer = _StubTracer +tracer_stub.AsyncTracer = _StubAsyncTracer # type: ignore[attr-defined] +tracer_stub.Tracer = _StubTracer # type: ignore[attr-defined] sys.modules["agentex.lib.core.tracing.tracer"] = tracer_stub tracing_pkg_stub = types.ModuleType("agentex.lib.core.tracing") -tracing_pkg_stub.AsyncTracer = _StubAsyncTracer -tracing_pkg_stub.Tracer = _StubTracer +tracing_pkg_stub.AsyncTracer = _StubAsyncTracer # type: ignore[attr-defined] +tracing_pkg_stub.Tracer = _StubTracer # type: ignore[attr-defined] sys.modules["agentex.lib.core.tracing"] = tracing_pkg_stub from agentex.lib.core.services.adk.acp.acp import ACPService @@ -54,7 +54,7 @@ def __init__(self, **_kwargs: Any) -> None: async def __aenter__(self): return self - async def __aexit__(self, exc_type, exc, tb): + async def __aexit__(self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: object) -> bool: return False @@ -128,13 +128,14 @@ async def test_header_forwarding() -> None: class TestServer(BaseACPServer): __test__ = False + @override def _setup_handlers(self): @self.on_message_send - async def handler(params: SendMessageParams): + async def handler(params: SendMessageParams): # type: ignore[reportUnusedFunction] headers = (params.request or {}).get("headers", {}) assert "x-agent-api-key" not in headers assert headers.get("x-user") == "a" - return TextContent(author="assistant", content="ok") + return TextContent(author="agent", content="ok") def test_excludes_agent_api_key_header(): diff --git a/tests/test_model_utils.py b/tests/test_model_utils.py index a672853a..9c570223 100644 --- a/tests/test_model_utils.py +++ b/tests/test_model_utils.py @@ -1,9 +1,10 @@ import json from datetime import datetime -from agentex.lib.utils.model_utils import recursive_model_dump from pydantic import BaseModel +from agentex.lib.utils.model_utils import recursive_model_dump + class SampleModel(BaseModel): """Sample model for testing recursive_model_dump functionality.""" @@ -56,7 +57,7 @@ def test_function_without_module_serialization(self): """Test function serialization when module info is not available.""" # Create a lambda function which might not have __module__ - def lambda_like_func(x): + def lambda_like_func(x: int) -> int: return x * 2 result = recursive_model_dump(lambda_like_func) @@ -135,11 +136,11 @@ def test_nested_structure_serialization(self): result = recursive_model_dump(data) assert isinstance(result, dict) - nested_func = result["level1"]["level2"][0]["function"] + nested_func: str = result["level1"]["level2"][0]["function"] # type: ignore[assignment] assert isinstance(nested_func, str) assert "another_function" in nested_func - nested_model = result["level1"]["level2"][0]["model"] + nested_model: dict[str, object] = result["level1"]["level2"][0]["model"] # type: ignore[assignment] assert nested_model["name"] == "deep" assert nested_model["value"] == 300 diff --git a/tests/test_task_cancel.py b/tests/test_task_cancel.py index 1483de64..aaa2c44f 100644 --- a/tests/test_task_cancel.py +++ b/tests/test_task_cancel.py @@ -1,12 +1,14 @@ """Tests for task cancellation bug fix.""" import os + import pytest from agentex import AsyncAgentex -from tests.utils import assert_matches_type from agentex.types import Task +from .utils import assert_matches_type + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")