Skip to content

Conversation

@anticorrelator
Copy link
Contributor

@anticorrelator anticorrelator commented Oct 23, 2025

Adds LLM adapters for calling LLMs via the Anthropic SDK and google-genai SDK


Note

Adds Anthropic and Google GenAI LLM adapters (sync/async text and object generation) with client factories and updates registries to use package metadata for dependency checks; fixes LangChain dependency names.

  • LLM Adapters:
    • anthropic:
      • New AnthropicAdapter with client identification, sync/async generate_text, and object generation via tool-calling; schema validation and default model; rate limit errors via SDK.
      • Client factory create_anthropic_client with AnthropicClientWrapper.
    • google-genai:
      • New GoogleGenAIAdapter with client identification, sync/async generate_text, object generation via structured output or tool-calling; schema validation and custom rate-limit error handling.
      • Client factory create_google_genai_client.
  • Registry (phoenix/evals/llm/registries.py):
    • Dependency checks now use importlib.metadata.version; handle PackageNotFoundError; dependency coloring updated accordingly; docstring clarifies pip package names.
  • Adapters Init (adapters/__init__.py):
    • Export/register AnthropicAdapter and GoogleGenAIAdapter.
  • LangChain Adapter:
    • Correct dependency names to langchain-openai and langchain-anthropic.

Written by Cursor Bugbot for commit 4e563c2. This will update automatically on new commits. Configure here.

@anticorrelator anticorrelator requested a review from a team as a code owner October 23, 2025 22:47
@github-project-automation github-project-automation bot moved this to 📘 Todo in phoenix Oct 23, 2025
@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Oct 23, 2025
cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

client = genai.Client(**kwargs)
return client.aio if is_async else client
except ImportError:
raise ImportError("Google GenAI package not installed. Run: pip install google-genai")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Model Parameter Ignored in Client Factory

The create_google_genai_client factory accepts a model parameter but doesn't use or store it. This causes the GoogleGenAIAdapter to default to 'gemini-2.0-flash-exp' instead of the specified model, inconsistent with other adapters that preserve model information.

Fix in Cursor Fix in Web

def _check_if_async_client(self) -> bool:
if hasattr(self.client, "aio"):
return False
return True
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Client Type Detection Fails

The _check_if_async_client method incorrectly determines if the Google GenAI client is synchronous or asynchronous. It relies on the presence of the .aio attribute, which may not reliably distinguish between client types. This misidentification can lead to ValueError exceptions when attempting to use the wrong client method (sync vs. async).

Fix in Cursor Fix in Web

if hasattr(response.content[0], "text"):
return cast(str, response.content[0].text)
else:
raise ValueError("Anthropic returned unexpected content format")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be useful to add the response body to this error message so users know what is being returned?

messages=messages,
tools=[tool_definition],
tool_choice={"type": "tool", "name": "extract_structured_data"},
max_tokens=4096,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't max_tokens be one of the configurable kwargs?


def _schema_to_tool(self, schema: Dict[str, Any]) -> Dict[str, Any]:
description = schema.get(
"description", "Extract structured data according to the provided schema"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit: the task is more like "Respond in a format matching the provided schema" than it is about extracting structured data. This may not impact things much, but LLMs are sensitive to wording so 🤷🏼‍♀️

logger = logging.getLogger(__name__)


class GoogleGenAIRateLimitError(Exception):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no google version of a rate limit error? What about an error code?


def _supports_tool_calls(self) -> bool:
model_name = self.model_name.lower()
if "gemini-1.5" in model_name or "gemini-2" in model_name:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we are gonna hard code this for now?

return prompt

text_parts: list[str] = []
for part in prompt.parts:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for now the solution is just to collapse all the message content into one prompt string without roles, yeah? We need to figure out how to handle this when we move to a new prompt abstraction

@ehutt ehutt self-requested a review October 25, 2025 00:55
@github-project-automation github-project-automation bot moved this from 📘 Todo to 👍 Approved in phoenix Oct 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

Status: 👍 Approved

Development

Successfully merging this pull request may close these issues.

2 participants