From e37d5264515d1a603d2c98977848e3aa103bf8c9 Mon Sep 17 00:00:00 2001 From: Thorrester Date: Sat, 24 Jan 2026 10:29:28 -0500 Subject: [PATCH 1/3] generic output type --- py-potato/python/potato_head/_potato_head.pyi | 110 +++++++++++------- 1 file changed, 68 insertions(+), 42 deletions(-) diff --git a/py-potato/python/potato_head/_potato_head.pyi b/py-potato/python/potato_head/_potato_head.pyi index 498d22b..a182804 100644 --- a/py-potato/python/potato_head/_potato_head.pyi +++ b/py-potato/python/potato_head/_potato_head.pyi @@ -219,7 +219,9 @@ PromptMessage: TypeAlias = Union[ List[Union[str, "ChatMessage", "MessageParam", "GeminiContent"]], ] -class Prompt: +OutputType = TypeVar("OutputType", default=str) + +class Prompt(Generic[OutputType]): """Prompt for interacting with an LLM API. The Prompt class handles message parsing, provider-specific formatting, and @@ -233,7 +235,7 @@ class Prompt: provider: Provider | str, system_instructions: Optional[PromptMessage] = None, model_settings: Optional[ModelSettings | OpenAIChatSettings | GeminiSettings | AnthropicSettings] = None, - output_type: Optional[Any] = None, + output_type: Optional[OutputType] = None, ) -> None: """Initialize a Prompt object. @@ -257,8 +259,9 @@ class Prompt: model_settings (Optional[ModelSettings | OpenAIChatSettings | GeminiSettings | AnthropicSettings]): Optional model-specific settings (temperature, max_tokens, etc.) If None, provider default settings will be used - output_type (Optional[Pydantic BaseModel | Score]): - Optional structured output format.The provided format will be parsed into a JSON schema for structured outputs + output_type (Optional[OutputT]): + Optional structured output type.The provided format will be parsed into a JSON schema for structured outputs. + This is typically a pydantic BaseModel. Raises: TypeError: If message types are invalid or incompatible with the provider @@ -715,16 +718,29 @@ _ResponseType: TypeAlias = Union[ "AnthropicMessageResponse", ] -class AgentResponse: +OutT = TypeVar( + "OutT", + default=str, +) + +class AgentResponse(Generic[OutT]): + """Agent response generic over OutputDataT. + + The structured_output property returns OutputDataT type. + + Examples: + >>> agent = Agent(provider=Provider.OpenAI) + >>> response: AgentResponse[WeatherData] = agent.execute_prompt(prompt, output_type=WeatherData) + >>> weather: WeatherData = response.structured_output + """ + @property def id(self) -> str: """The ID of the agent response.""" @property def response(self) -> _ResponseType: - """The response of the agent. This can be an OpenAIChatResponse, GenerateContentResponse, - or AnthropicMessageResponse depending on the provider used. - """ + """The response of the agent.""" @property def token_usage(self) -> Any: @@ -732,13 +748,15 @@ class AgentResponse: @property def log_probs(self) -> ResponseLogProbs: - """Returns the log probabilities of the agent response if supported. - This is primarily used for debugging and analysis purposes. - """ + """Returns the log probabilities of the agent response if supported.""" @property - def structured_output(self) -> Any: - """Returns the structured output of the agent response if supported.""" + def structured_output(self) -> OutT: + """Returns the structured output of the agent response. + + The type is determined by the Agent's OutputType generic parameter + or the output_type argument passed to execute_task/execute_prompt. + """ def response_text(self) -> str: """The response text from the agent if available, otherwise an empty string.""" @@ -747,7 +765,7 @@ class Task: def __init__( self, agent_id: str, - prompt: Prompt, + prompt: Prompt[OutputType], id: Optional[str] = None, dependencies: List[str] = [], max_retries: int = 3, @@ -757,7 +775,7 @@ class Task: Args: agent_id (str): The ID of the agent that will execute the task. - prompt (Prompt): + prompt (Prompt[OutputType]): The prompt to use for the task. id (Optional[str]): The ID of the task. If None, a random uuid7 will be generated. @@ -794,6 +812,27 @@ class TaskList: """Dictionary of tasks in the TaskList where keys are task IDs and values are Task objects.""" class Agent: + """Create an Agent object. + + Generic over OutputType which determines the structured output type. + By default, OutputType is str if no output_type is specified. + + Examples: + >>> # Default agent (OutputType = str) + >>> agent = Agent(provider=Provider.OpenAI) + >>> response = agent.execute_prompt(prompt) + >>> text: str = response.structured_output + + >>> # Typed agent with Pydantic model + >>> class WeatherData(BaseModel): + ... temperature: float + ... condition: str + >>> + >>> agent = Agent(provider=Provider.OpenAI) + >>> response = agent.execute_prompt(prompt, output_type=WeatherData) + >>> weather: WeatherData = response.structured_output + """ + def __init__( self, provider: Provider | str, @@ -803,58 +842,45 @@ class Agent: Args: provider (Provider | str): - The provider to use for the agent. This can be a Provider enum or a string - representing the provider. + The provider to use for the agent. system_instruction (Optional[PromptMessage]): - The system message to use for the agent. This can be a string, a list of strings, - a Message object, or a list of Message objects. If None, no system message will be used. - This is added to all tasks that the agent executes. If a given task contains it's own - system message, the agent's system message will be prepended to the task's system message. - - Example: - ```python - agent = Agent( - provider=Provider.OpenAI, - system_instructions="You are a helpful assistant.", - ) - ``` + The system message to use for the agent. """ @property def system_instruction(self) -> List[Any]: - """The system message to use for the agent. This is a list of Message objects.""" + """The system message to use for the agent.""" def execute_task( self, task: Task, - output_type: Optional[Any] = None, - ) -> AgentResponse: + output_type: type[OutT] | None = None, + ) -> AgentResponse[OutT]: """Execute a task. Args: task (Task): The task to execute. - output_type (Optional[Any]): - The output type to use for the task. This can either be a Pydantic `BaseModel` class - or a supported PotatoHead response type such as `Score`. + output_type (Optional[OutT]): + The output type to use for the task. + Returns: - AgentResponse: + AgentResponse[OutT]: The response from the agent after executing the task. """ def execute_prompt( self, prompt: Prompt, - output_type: Optional[Any] = None, - ) -> AgentResponse: + output_type: type[OutT] | None = None, + ) -> AgentResponse[OutT]: """Execute a prompt. Args: prompt (Prompt): The prompt to execute. - output_type (Optional[Any]): - The output type to use for the task. This can either be a Pydantic `BaseModel` class - or a supported potato_head response type such as `Score`. + output_type (Optional[OutT]): + The output type to use for the task. Returns: AgentResponse: @@ -863,7 +889,7 @@ class Agent: @property def id(self) -> str: - """The ID of the agent. This is a random uuid7 that is generated when the agent is created.""" + """The ID of the agent.""" class Workflow: def __init__(self, name: str) -> None: From a69698ccf946a71b615d0d6e455e4da8b17aed3c Mon Sep 17 00:00:00 2001 From: Thorrester Date: Sat, 24 Jan 2026 11:04:39 -0500 Subject: [PATCH 2/3] Update _potato_head.pyi --- py-potato/python/potato_head/_potato_head.pyi | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/py-potato/python/potato_head/_potato_head.pyi b/py-potato/python/potato_head/_potato_head.pyi index a182804..8d3e723 100644 --- a/py-potato/python/potato_head/_potato_head.pyi +++ b/py-potato/python/potato_head/_potato_head.pyi @@ -2,17 +2,9 @@ import datetime from pathlib import Path -from typing import ( - Any, - Dict, - Generic, - List, - Optional, - TypeAlias, - TypeVar, - Union, - overload, -) +from typing import Any, Dict, Generic, List, Optional, TypeAlias, Union, overload + +from typing_extensions import TypeVar ###### __potatohead__.main module ###### From 5993f501c82bac5b2f40e15dcec97dafb60feecb Mon Sep 17 00:00:00 2001 From: Thorrester Date: Sat, 24 Jan 2026 12:52:52 -0500 Subject: [PATCH 3/3] remove pylint (use ruff + mypy) --- py-potato/Makefile | 6 ++-- py-potato/pyproject.toml | 1 - py-potato/uv.lock | 60 ---------------------------------------- 3 files changed, 2 insertions(+), 65 deletions(-) diff --git a/py-potato/Makefile b/py-potato/Makefile index 4c0ae24..d660ccd 100644 --- a/py-potato/Makefile +++ b/py-potato/Makefile @@ -16,10 +16,8 @@ lints.ruff: uv run ruff check ${SOURCE_OBJECTS} lints.mypy: uv run mypy ${SOURCE_OBJECTS} -lints.pylint: - uv run pylint ${SOURCE_OBJECTS} -lints: lints.ruff lints.pylint lints.mypy -lints.ci: lints.format_check lints.ruff lints.pylint lints.mypy +lints: lints.ruff lints.mypy +lints.ci: lints.format_check lints.ruff lints.mypy setup.project: uv sync --all-extras --group dev --group docs diff --git a/py-potato/pyproject.toml b/py-potato/pyproject.toml index 09f96e6..32b4553 100644 --- a/py-potato/pyproject.toml +++ b/py-potato/pyproject.toml @@ -22,7 +22,6 @@ dev = [ "ruff >= 0.1.0, < 1.0.0", "mypy >= 1.0.0, < 2.0.0", "black >= 24.3.0, < 25.0.0", - "pylint >= 3.0.0, < 4.0.0", "isort >= 5.13.2, < 6.0.0", "pydantic>=2.10.5", "pydantic-ai>=0.0.41", diff --git a/py-potato/uv.lock b/py-potato/uv.lock index a1c8340..0c12ce8 100644 --- a/py-potato/uv.lock +++ b/py-potato/uv.lock @@ -167,18 +167,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708, upload-time = "2025-04-03T04:57:01.591Z" }, ] -[[package]] -name = "astroid" -version = "3.3.11" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/18/74/dfb75f9ccd592bbedb175d4a32fc643cf569d7c218508bfbd6ea7ef9c091/astroid-3.3.11.tar.gz", hash = "sha256:1e5a5011af2920c7c67a53f65d536d65bfa7116feeaf2354d8b94f29573bb0ce", size = 400439, upload-time = "2025-07-13T18:04:23.177Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/0f/3b8fdc946b4d9cc8cc1e8af42c4e409468c84441b933d037e101b3d72d86/astroid-3.3.11-py3-none-any.whl", hash = "sha256:54c760ae8322ece1abd213057c4b5bba7c49818853fc901ef09719a60dbf9dec", size = 275612, upload-time = "2025-07-13T18:04:21.07Z" }, -] - [[package]] name = "async-timeout" version = "5.0.1" @@ -637,15 +625,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, ] -[[package]] -name = "dill" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, -] - [[package]] name = "distro" version = "1.9.0" @@ -1494,15 +1473,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/06/3d/74e75874b75fc82e4774f2ed78ad546fda3e127bae4a971db3611bdab285/maturin-1.9.1-py3-none-win_arm64.whl", hash = "sha256:0e6e2ddc83999ac3999576b06649a327536a51d57c917fa01416e40f53106bda", size = 6936614, upload-time = "2025-07-08T04:54:41.888Z" }, ] -[[package]] -name = "mccabe" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, -] - [[package]] name = "mcp" version = "1.11.0" @@ -2226,7 +2196,6 @@ dev = [ { name = "pandas" }, { name = "pydantic" }, { name = "pydantic-ai" }, - { name = "pylint" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "ruff" }, @@ -2251,7 +2220,6 @@ dev = [ { name = "pandas", specifier = ">=2.3.2" }, { name = "pydantic", specifier = ">=2.10.5" }, { name = "pydantic-ai", specifier = ">=0.0.41" }, - { name = "pylint", specifier = ">=3.0.0,<4.0.0" }, { name = "pytest", specifier = ">=7.0.0,<8.0.0" }, { name = "pytest-cov", specifier = ">=5.0.0,<6.0.0" }, { name = "ruff", specifier = ">=0.1.0,<1.0.0" }, @@ -2614,25 +2582,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] -[[package]] -name = "pylint" -version = "3.3.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "astroid" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "dill" }, - { name = "isort" }, - { name = "mccabe" }, - { name = "platformdirs" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "tomlkit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1c/e4/83e487d3ddd64ab27749b66137b26dc0c5b5c161be680e6beffdc99070b3/pylint-3.3.7.tar.gz", hash = "sha256:2b11de8bde49f9c5059452e0c310c079c746a0a8eeaa789e5aa966ecc23e4559", size = 1520709, upload-time = "2025-05-04T17:07:51.089Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/83/bff755d09e31b5d25cc7fdc4bf3915d1a404e181f1abf0359af376845c24/pylint-3.3.7-py3-none-any.whl", hash = "sha256:43860aafefce92fca4cf6b61fe199cdc5ae54ea28f9bf4cd49de267b5195803d", size = 522565, upload-time = "2025-05-04T17:07:48.714Z" }, -] - [[package]] name = "pymdown-extensions" version = "10.16" @@ -3151,15 +3100,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] -[[package]] -name = "tomlkit" -version = "0.13.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, -] - [[package]] name = "tqdm" version = "4.67.1"