mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-30 04:40:16 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ffb212a8f | ||
|
|
ca7f743dab | ||
|
|
5cf59fdd88 |
@@ -28,6 +28,7 @@ The `semantica.llms` module provides a unified interface for connecting to Large
|
||||
## When To Use / When Not To Use
|
||||
|
||||
**Use LLM integrations for:**
|
||||
|
||||
- Text generation, summarization, and question-answering tasks
|
||||
- Complex reasoning that requires natural language understanding
|
||||
- Structured data extraction from unstructured text
|
||||
@@ -35,6 +36,7 @@ The `semantica.llms` module provides a unified interface for connecting to Large
|
||||
- Tasks where context, ambiguity, or domain knowledge matter
|
||||
|
||||
**Deterministic tools may be better for:**
|
||||
|
||||
- Pattern matching that regular expressions can handle
|
||||
- Simple rule-based classification with clear criteria
|
||||
- Mathematical calculations or statistical analysis
|
||||
@@ -42,6 +44,7 @@ The `semantica.llms` module provides a unified interface for connecting to Large
|
||||
- Data transformations with known logic
|
||||
|
||||
**A full LLM may be unnecessary for:**
|
||||
|
||||
- Simple keyword search or exact string matching
|
||||
- Deterministic workflows with predefined decision trees
|
||||
- High-frequency, low-latency operations where inference overhead matters
|
||||
@@ -143,6 +146,58 @@ risk_data = oai.generate_structured(
|
||||
|
||||
The default model `gpt-3.5-turbo` is fine for classification and light extraction. Switch to `gpt-4o` for complex multi-step regulatory reasoning or document understanding.
|
||||
|
||||
## Anthropic — Complex Reasoning and Structured Extraction
|
||||
|
||||
**Anthropic** provides the Claude model family, built with an emphasis on careful, instruction-following behavior and strong performance on multi-step reasoning, long-document analysis, and code-related tasks. Claude models tend to be more cautious about ambiguous instructions than other providers. That matters when the cost of a confidently wrong answer is high.
|
||||
|
||||
The `Anthropic` provider wraps the Claude API. Reach for it when the task involves reasoning through several dependent steps (not just single-turn extraction), when you're processing long source documents that need to stay in context, or when you need schema-validated structured output rather than best-effort JSON.
|
||||
|
||||
Install with `pip install "semantica[llm-anthropic]"` (or just `pip install anthropic`) before using this provider.
|
||||
|
||||
```python
|
||||
from semantica.llms import Anthropic
|
||||
|
||||
claude = Anthropic(model="claude-3-sonnet-20240229", api_key="YOUR_ANTHROPIC_KEY")
|
||||
# api_key falls back to the ANTHROPIC_API_KEY environment variable
|
||||
|
||||
# is_available() only confirms a client was constructed from some key.
|
||||
# It does not validate the key or check network reachability - an
|
||||
# invalid or expired key still passes this check and fails at generate().
|
||||
if not claude.is_available():
|
||||
raise RuntimeError("Anthropic provider not configured - set ANTHROPIC_API_KEY")
|
||||
|
||||
# Plain generation - multi-step reasoning over a contract clause
|
||||
verdict = claude.generate(
|
||||
"A vendor contract has a 30-day termination-for-convenience clause "
|
||||
"but a 90-day data-return obligation that survives termination. "
|
||||
"If the customer terminates on day 1, when must vendor-held data "
|
||||
"be returned? Answer with the date basis only.",
|
||||
temperature=0.1,
|
||||
)
|
||||
print(verdict)
|
||||
# "Day 120 from termination notice. The 90-day return period runs from
|
||||
# the termination date (day 30), not from the notice date."
|
||||
|
||||
# Structured, schema-validated output
|
||||
from pydantic import BaseModel
|
||||
|
||||
class ContractRisk(BaseModel):
|
||||
clause: str
|
||||
risk_level: str
|
||||
days_to_deadline: int
|
||||
|
||||
risk = claude.generate_typed(
|
||||
"Extract the termination clause risk from: vendor contract, "
|
||||
"30-day termination for convenience, 90-day post-termination "
|
||||
"data return obligation.",
|
||||
schema=ContractRisk,
|
||||
)
|
||||
print(risk.risk_level, risk.days_to_deadline)
|
||||
# "medium" 90
|
||||
```
|
||||
|
||||
Model selection follows the same tier structure as the other providers: a Haiku model for high-volume classification where cost matters more than depth, a Sonnet model as the default for most extraction and reasoning tasks, an Opus model when a task genuinely needs the deepest reasoning available and latency/cost are secondary. Check Anthropic's docs for the current model identifiers, since they're versioned and change over time.
|
||||
|
||||
## LiteLLM — One Interface, 100+ Providers
|
||||
|
||||
**LiteLLM** is a universal adapter that provides a single interface to over 100 different LLM providers, including Anthropic Claude, Azure OpenAI, AWS Bedrock, Google Vertex AI, and local Ollama instances. It acts as a translation layer, converting your unified API calls into provider-specific requests, enabling easy switching between providers without code changes.
|
||||
|
||||
@@ -10,9 +10,10 @@ Supported Providers:
|
||||
- OpenAI: OpenAI API (GPT-3.5, GPT-4, etc.)
|
||||
- HuggingFaceLLM: HuggingFace Transformers for local LLM inference
|
||||
- LiteLLM: Unified interface to 100+ LLM providers (OpenAI, Anthropic, Groq, Azure, Bedrock, Vertex AI, etc.)
|
||||
- Anthropic: Anthropic Claude API (Claude sonnet, Opus, Haiku, etc.)
|
||||
|
||||
Example Usage:
|
||||
>>> from semantica.llms import Groq, OpenAI, HuggingFaceLLM, LiteLLM
|
||||
>>> from semantica.llms import Groq, OpenAI, HuggingFaceLLM, LiteLLM, Anthropic
|
||||
>>>
|
||||
>>> # Groq provider
|
||||
>>> groq = Groq(model="llama-3.1-8b-instant", api_key="your-key")
|
||||
@@ -32,6 +33,10 @@ Example Usage:
|
||||
>>> # Or use other providers via LiteLLM
|
||||
>>> llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
|
||||
>>> response = llm.generate("Hello, world!")
|
||||
>>>
|
||||
>>> # Anthropic provider
|
||||
>>> claude = Anthropic(model="claude-3-sonnet-20240229", api_key="the-key")
|
||||
>>> response = claude.generate("Hello, world!")
|
||||
|
||||
Author: Semantica Contributors
|
||||
License: MIT
|
||||
@@ -41,6 +46,7 @@ from .groq import Groq
|
||||
from .openai import OpenAI
|
||||
from .huggingface import HuggingFaceLLM
|
||||
from .litellm import LiteLLM
|
||||
from .anthropic import Anthropic
|
||||
|
||||
__all__ = ["Groq", "OpenAI", "HuggingFaceLLM", "LiteLLM"]
|
||||
__all__ = ["Groq", "OpenAI", "HuggingFaceLLM", "LiteLLM", "Anthropic"]
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
Anthropic LLM Provider
|
||||
|
||||
Wrapper for Anthropic Claude API provider with clean interface
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from ..semantic_extract.providers import AnthropicProvider
|
||||
from ..utils.exceptions import ProcessingError
|
||||
from ..utils.logging import get_logger
|
||||
|
||||
logger = get_logger("llms.anthropic")
|
||||
|
||||
class Anthropic:
|
||||
"""
|
||||
Anthropic Claude LLM provider wrapper.
|
||||
|
||||
Provides clean interface to Anthropic's Claude API.
|
||||
|
||||
Example:
|
||||
>>> from semantica.llms import Anthropic
|
||||
>>> claude = Anthropic(model="claude-3-sonnet-20240229", api_key="the-key")
|
||||
>>> response = claude.generate("What is API key?")
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model: str = "claude-3-sonnet-20240229",
|
||||
api_key: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Init Anthropic provider.
|
||||
|
||||
Args:
|
||||
model: Model name (default: claude-3-sonnet-20240229)
|
||||
api_key: Anthropic API key (default: from ANTHROPIC_API_KEY env var)
|
||||
**kwargs: Addition provider options
|
||||
"""
|
||||
self.provider = AnthropicProvider(api_key=api_key, model=model, **kwargs)
|
||||
self.model = model
|
||||
self.api_key = api_key
|
||||
|
||||
|
||||
def is_available(self) -> bool:
|
||||
""" Check if Anthropic provider is available"""
|
||||
return self.provider.is_available()
|
||||
|
||||
def generate(self, prompt: str, **kwargs) -> str:
|
||||
"""
|
||||
Generate text from prompt.
|
||||
|
||||
Args:
|
||||
prompt: Input prompt text
|
||||
**kwargs: Generation options (temperature, max_tokens, etc.)
|
||||
|
||||
Returns:
|
||||
Generated text response
|
||||
|
||||
Raises:
|
||||
ProcessingError: If provider is not available or generation fails
|
||||
"""
|
||||
|
||||
if not self.is_available():
|
||||
raise ProcessingError(
|
||||
"Anthropic provider not available. set ANTHROPIC_API_KEY or pass api_key."
|
||||
)
|
||||
return self.provider.generate(prompt, **kwargs)
|
||||
|
||||
def generate_structured(self, prompt: str, **kwargs) -> Union[Dict[str, Any], List[Any]]:
|
||||
"""
|
||||
Generates structured JSON output.
|
||||
|
||||
Args:
|
||||
prompt: Input prompt text
|
||||
**kwargs: Generation options
|
||||
|
||||
Returns:
|
||||
Parsed JSON response. A dict for a top-level JSON object, or a
|
||||
list if the model returns a top-level JSON array.
|
||||
|
||||
Raises:
|
||||
ProcessingError: If provider is not available or generation fails
|
||||
"""
|
||||
if not self.is_available():
|
||||
raise ProcessingError(
|
||||
"Anthropic provider not available. Set ANTHROPIC_API_KEY or pass api_key."
|
||||
)
|
||||
return self.provider.generate_structured(prompt, **kwargs)
|
||||
|
||||
def generate_typed(self, prompt: str, schema: Any, max_retries: int =3, **kwargs) -> Any:
|
||||
"""
|
||||
Generate output validated against a Pydantic schema.
|
||||
|
||||
Args:
|
||||
prompt: Input prompt text
|
||||
schema: Pydantic model class to validate the output against
|
||||
max_retries: Number of retries if validation fails (default: 3)
|
||||
**kwargs: Generation options
|
||||
|
||||
Returns:
|
||||
An instance of `schema`, populated from model's reponse
|
||||
|
||||
Raises:
|
||||
ProcessingError: If provider is not available or generation fails
|
||||
"""
|
||||
|
||||
if not self.is_available():
|
||||
raise ProcessingError(
|
||||
"Anthropic provider not available. Set ANTHROPIC_API_KEY or pass api_key."
|
||||
)
|
||||
return self.provider.generate_typed(prompt, schema, max_retries=max_retries, **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
"""Tests for the Anthropic LLM provider wrapper (semantica.llms.Anthropic)."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from semantica.llms import Anthropic
|
||||
from semantica.utils.exceptions import ProcessingError
|
||||
|
||||
|
||||
def test_construction_stores_model_and_api_key():
|
||||
"""Anthropic(...) should not crash and should remember what it was given."""
|
||||
claude = Anthropic(model="claude-3-sonnet-20240229", api_key="fake-key")
|
||||
assert claude.model == "claude-3-sonnet-20240229"
|
||||
assert claude.api_key == "fake-key"
|
||||
|
||||
|
||||
def test_is_available_false_with_no_key(monkeypatch):
|
||||
"""Without a real key, is_available() must be a real False, not truthy junk.
|
||||
|
||||
api_key=None alone isn't enough to prove this: AnthropicProvider falls
|
||||
back to the ANTHROPIC_API_KEY environment variable, so this test has to
|
||||
clear it too or it would pass/fail depending on whoever's machine or CI
|
||||
runner happens to run it.
|
||||
"""
|
||||
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
|
||||
claude = Anthropic(api_key=None)
|
||||
assert claude.is_available() is False
|
||||
|
||||
|
||||
def test_generate_raises_clear_error_when_unavailable():
|
||||
"""generate() must fail loudly."""
|
||||
claude = Anthropic(api_key=None)
|
||||
with pytest.raises(ProcessingError, match="Anthropic provider not available"):
|
||||
claude.generate("hello")
|
||||
|
||||
|
||||
def test_generate_forwards_to_the_real_provider_when_available():
|
||||
"""When available, generate() must actually call through to the real provider."""
|
||||
claude = Anthropic(api_key="fake-key")
|
||||
|
||||
claude.provider = MagicMock()
|
||||
claude.provider.is_available.return_value = True
|
||||
claude.provider.generate.return_value = "a fake response"
|
||||
|
||||
result = claude.generate("hello", temperature=0.5)
|
||||
|
||||
assert result == "a fake response"
|
||||
claude.provider.generate.assert_called_once_with("hello", temperature=0.5)
|
||||
|
||||
|
||||
def test_generate_structured_forwards_to_the_real_provider():
|
||||
claude = Anthropic(api_key="fake-key")
|
||||
claude.provider = MagicMock()
|
||||
claude.provider.is_available.return_value = True
|
||||
claude.provider.generate_structured.return_value = {"key": "value"}
|
||||
|
||||
result = claude.generate_structured("hello")
|
||||
|
||||
assert result == {"key": "value"}
|
||||
claude.provider.generate_structured.assert_called_once_with("hello")
|
||||
|
||||
|
||||
def test_generate_typed_forwards_schema_and_max_retries():
|
||||
claude = Anthropic(api_key="fake-key")
|
||||
claude.provider = MagicMock()
|
||||
claude.provider.is_available.return_value = True
|
||||
fake_schema = object()
|
||||
claude.provider.generate_typed.return_value = "typed result"
|
||||
|
||||
result = claude.generate_typed("hello", fake_schema, max_retries=5)
|
||||
|
||||
assert result == "typed result"
|
||||
claude.provider.generate_typed.assert_called_once_with(
|
||||
"hello", fake_schema, max_retries=5
|
||||
)
|
||||
Reference in New Issue
Block a user