From c6cf087c00e73b3aad66d494f34c68aa0a4d7bb9 Mon Sep 17 00:00:00 2001 From: Kevin Messiaen Date: Tue, 12 Aug 2025 11:25:45 +0700 Subject: [PATCH 1/2] Chore: [GSK-4543] [counterpoint] Introduce specific error classes (e.g., CounterpointConfigError) --- src/counterpoint/__init__.py | 10 ++++++++ src/counterpoint/chat.py | 12 +++++++++- src/counterpoint/exceptions.py | 23 +++++++++++++++++++ src/counterpoint/rate_limiter.py | 13 +++++++++-- src/counterpoint/templates/prompts_manager.py | 3 ++- src/counterpoint/tools/tool.py | 5 ++-- tests/test_chat.py | 3 ++- tests/test_templates.py | 4 +++- 8 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 src/counterpoint/exceptions.py diff --git a/src/counterpoint/__init__.py b/src/counterpoint/__init__.py index b3f511a..fa734b0 100644 --- a/src/counterpoint/__init__.py +++ b/src/counterpoint/__init__.py @@ -1,5 +1,11 @@ from .chat import Chat, Message from .context import RunContext +from .exceptions import ( + CounterpointConfigError, + CounterpointError, + ToolError, + ToolDefinitionError, +) from .generators import Generator from .pipeline import Pipeline from .rate_limiter import RateLimiter, RateLimiterStrategy @@ -19,4 +25,8 @@ "RateLimiterStrategy", "RateLimiter", "RunContext", + "CounterpointError", + "CounterpointConfigError", + "ToolError", + "ToolDefinitionError", ] diff --git a/src/counterpoint/chat.py b/src/counterpoint/chat.py index 856a140..bb75449 100644 --- a/src/counterpoint/chat.py +++ b/src/counterpoint/chat.py @@ -4,6 +4,7 @@ from pydantic import BaseModel, Field from counterpoint.context import RunContext +from counterpoint.exceptions import CounterpointConfigError from counterpoint.tools import ToolCall Role = Literal["assistant", "user", "system", "tool"] @@ -81,6 +82,15 @@ def transcript(self) -> str: @property def output(self) -> OutputType: + """Parsed output as the configured model. + + Raises + ------ + CounterpointConfigError + If no `output_model` is configured on the chat/pipeline. + """ if self.output_model is None: - raise ValueError("Output model not set") + raise CounterpointConfigError( + "Output model not set. Call Pipeline.with_output(...) before accessing Chat.output." + ) return self.last.parse(self.output_model) diff --git a/src/counterpoint/exceptions.py b/src/counterpoint/exceptions.py new file mode 100644 index 0000000..4c3cff6 --- /dev/null +++ b/src/counterpoint/exceptions.py @@ -0,0 +1,23 @@ +"""Counterpoint-specific exception classes. + +These exceptions provide clearer error semantics for configuration, +template, tool, parsing, and pipeline related issues compared to using +generic built-ins. +""" + + +class CounterpointError(Exception): + """Base class for all Counterpoint errors.""" + + +class CounterpointConfigError(CounterpointError): + """Raised for invalid or missing configuration/state in Counterpoint.""" + + +class ToolError(CounterpointError): + """Base class for tool-related errors.""" + + +class ToolDefinitionError(ToolError): + """Raised when a tool is defined incorrectly (e.g., bad annotations).""" + diff --git a/src/counterpoint/rate_limiter.py b/src/counterpoint/rate_limiter.py index f7a8d59..d0f2959 100644 --- a/src/counterpoint/rate_limiter.py +++ b/src/counterpoint/rate_limiter.py @@ -5,6 +5,8 @@ from pydantic import BaseModel, Field, PrivateAttr +from .exceptions import CounterpointConfigError + class RateLimiterStrategy(BaseModel): min_interval: float @@ -91,7 +93,7 @@ def _register_rate_limiter(rate_limiter: RateLimiter) -> None: def get_rate_limiter( rate_limiter_id: str, ) -> RateLimiter: - """Get or create a rate limiter. + """Get a rate limiter by id. Parameters ---------- @@ -102,8 +104,15 @@ def get_rate_limiter( ------- RateLimiter The rate limiter. + + Raises + ------ + CounterpointConfigError + If a rate limiter with the given id is not registered. """ try: return _rate_limiters[rate_limiter_id] except KeyError as err: - raise ValueError(f"Rate limiter with id {rate_limiter_id} not found") from err + raise CounterpointConfigError( + f"Rate limiter with id {rate_limiter_id} not found" + ) from err diff --git a/src/counterpoint/templates/prompts_manager.py b/src/counterpoint/templates/prompts_manager.py index 23cb504..fe1cb8d 100644 --- a/src/counterpoint/templates/prompts_manager.py +++ b/src/counterpoint/templates/prompts_manager.py @@ -5,6 +5,7 @@ from pydantic import BaseModel, Field from counterpoint.chat import Message +from counterpoint.exceptions import CounterpointConfigError from .environment import create_message_environment @@ -35,7 +36,7 @@ async def render_messages_template( # 2. There are no message blocks. In this case, we will create a single user message with the rendered output. if messages: if rendered_output.strip(): - raise ValueError( + raise CounterpointConfigError( "Template contains message blocks but rendered output is not empty." ) return messages diff --git a/src/counterpoint/tools/tool.py b/src/counterpoint/tools/tool.py index 96b9a7f..7de3fea 100644 --- a/src/counterpoint/tools/tool.py +++ b/src/counterpoint/tools/tool.py @@ -7,6 +7,7 @@ from pydantic import BaseModel, Field, create_model from counterpoint.context import RunContext +from counterpoint.exceptions import ToolDefinitionError from ._docstring_parser import parse_docstring @@ -54,7 +55,7 @@ def from_callable(cls, fn: Callable) -> "Tool": Raises ------ - ValueError + ToolDefinitionError If the function lacks proper annotations or docstring. """ sig = inspect.signature(fn) @@ -65,7 +66,7 @@ def from_callable(cls, fn: Callable) -> "Tool": for name, param in sig.parameters.items(): if param.annotation is inspect.Parameter.empty: - raise ValueError( + raise ToolDefinitionError( f"Tool `{fn.__name__}` parameter `{name}` must have a type annotation" ) diff --git a/tests/test_chat.py b/tests/test_chat.py index e184588..11e5bb1 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -2,6 +2,7 @@ from pydantic import BaseModel from counterpoint.chat import Chat, Message +from counterpoint.exceptions import CounterpointConfigError def test_chat_output(): @@ -33,5 +34,5 @@ def test_chat_output_without_output_model(): ] chat = Chat(messages=messages) - with pytest.raises(ValueError): + with pytest.raises(CounterpointConfigError): chat.output diff --git a/tests/test_templates.py b/tests/test_templates.py index e7cb131..0f6d673 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -40,7 +40,9 @@ async def test_multi_message_template_parsing(prompts_manager): async def test_invalid_template(prompts_manager): - with pytest.raises(ValueError): + from counterpoint.exceptions import CounterpointConfigError + + with pytest.raises(CounterpointConfigError): await prompts_manager.render_template("invalid.j2") From 12716581fc6f6f31eb39c53d309acabf23b437ce Mon Sep 17 00:00:00 2001 From: Kevin Messiaen Date: Tue, 12 Aug 2025 11:29:34 +0700 Subject: [PATCH 2/2] Chore: [GSK-4543] [counterpoint] Introduce specific error classes (e.g., CounterpointConfigError) --- src/counterpoint/exceptions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/counterpoint/exceptions.py b/src/counterpoint/exceptions.py index 4c3cff6..e503d9a 100644 --- a/src/counterpoint/exceptions.py +++ b/src/counterpoint/exceptions.py @@ -20,4 +20,3 @@ class ToolError(CounterpointError): class ToolDefinitionError(ToolError): """Raised when a tool is defined incorrectly (e.g., bad annotations).""" -