From 2583842e21963099c65c709732677a716c03e7ff Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 22:39:11 +0000 Subject: [PATCH 1/2] Type llm messages with ChatCompletionMessageParam instead of suppressing The gateway `complete()` call carried a bare `# type: ignore[arg-type]` because `build_messages` returned `list[dict[str, str]]`, which isn't assignable to the OpenAI SDK's `Iterable[ChatCompletionMessageParam]`. Type the messages list and both function boundaries with the SDK's own param type so the `create()` call type-checks without the suppression. Tighten one build_messages test to compare the whole dict (matching its siblings), avoiding a not-required-key subscript on the now-typed union. --- aai_cli/core/llm.py | 10 +++++----- tests/test_llm.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aai_cli/core/llm.py b/aai_cli/core/llm.py index 19b5747b..0c8cb0c7 100644 --- a/aai_cli/core/llm.py +++ b/aai_cli/core/llm.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from openai import OpenAI - from openai.types.chat import ChatCompletion + from openai.types.chat import ChatCompletion, ChatCompletionMessageParam # The LLM Gateway is OpenAI-compatible, so we talk to it through the OpenAI SDK # pointed at the active environment's gateway base (see _client / code_gen). @@ -76,7 +76,7 @@ def build_messages( system: str | None = None, transcript_id: str | None = None, transcript_text: str | None = None, -) -> list[dict[str, str]]: +) -> list[ChatCompletionMessageParam]: """Assemble the chat `messages` array for a transcript transform or plain prompt. With a `transcript_id`, the gateway injects the transcript server-side, so we @@ -88,7 +88,7 @@ def build_messages( content = f"{prompt}\n\nTranscript:\n{transcript_text}" else: content = prompt - messages: list[dict[str, str]] = [] + messages: list[ChatCompletionMessageParam] = [] if system: messages.append({"role": "system", "content": system}) messages.append({"role": "user", "content": content}) @@ -130,7 +130,7 @@ def complete( api_key: str, *, model: str, - messages: list[dict[str, str]], + messages: list[ChatCompletionMessageParam], max_tokens: int = DEFAULT_MAX_TOKENS, transcript_id: str | None = None, extra: dict[str, object] | None = None, @@ -153,7 +153,7 @@ def complete( try: return client.chat.completions.create( model=model, - messages=messages, # type: ignore[arg-type] + messages=messages, max_tokens=max_tokens, extra_body=extra_body or None, ) diff --git a/tests/test_llm.py b/tests/test_llm.py index e86eab3c..8316c7ca 100644 --- a/tests/test_llm.py +++ b/tests/test_llm.py @@ -164,7 +164,7 @@ def test_build_messages_transcript_id_uses_tag(): def test_build_messages_inline_text(): msgs = llm.build_messages("summarize", transcript_text="hello world") - assert msgs[0]["content"] == "summarize\n\nTranscript:\nhello world" + assert msgs == [{"role": "user", "content": "summarize\n\nTranscript:\nhello world"}] def test_build_messages_with_system_prompt(): From 091ae9f1ebeaa599723e99728d6cb4f4099660b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 13 Jun 2026 23:03:13 +0000 Subject: [PATCH 2/2] Narrow usage_of return to dict[str, object] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The token-usage block is only ever emitted as JSON, so its return type need not be dict[str, Any] — dict[str, object] matches the JSON-payload convention used throughout the codebase and drops the module's last Any (and the now-unused typing import). --- aai_cli/core/llm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aai_cli/core/llm.py b/aai_cli/core/llm.py index 0c8cb0c7..a10a8052 100644 --- a/aai_cli/core/llm.py +++ b/aai_cli/core/llm.py @@ -2,7 +2,7 @@ import json from collections.abc import Sequence -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from aai_cli.core import environments from aai_cli.core.errors import APIError, UsageError @@ -182,7 +182,7 @@ def content_of(response: ChatCompletion) -> str: return content or "" -def usage_of(response: ChatCompletion) -> dict[str, Any] | None: +def usage_of(response: ChatCompletion) -> dict[str, object] | None: """Return the token-usage block as a plain dict, if present.""" usage = response.usage if usage is None: