add anthropic extra and fix backend-missing hints for uv tool installs
uv tool install graphifyy runs graphify in an isolated venv, so when a backend needs anthropic/openai/boto3 the old 'Run: pip install anthropic' advice never reached it. Worse, claude was the only backend with no [extra] at all, so a user with ANTHROPIC_API_KEY set could not satisfy it without --with anthropic. Add an anthropic optional extra (and include it in [all]), and replace the package-missing ImportError messages with a shared _backend_pkg_hint that points at 'uv tool install graphifyy[<extra>] --force' first, then the pip/venv path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+21
-12
@@ -395,6 +395,21 @@ def _default_model_for_backend(backend: str) -> str:
|
||||
return cfg["default_model"]
|
||||
|
||||
|
||||
def _backend_pkg_hint(pkg: str, extra: str) -> str:
|
||||
"""Package-missing message that works for the recommended `uv tool` install.
|
||||
|
||||
`uv tool install graphifyy` puts graphify in an isolated venv, so a plain
|
||||
`pip install <pkg>` never reaches it - the friction a user hits when a
|
||||
backend needs anthropic/openai/boto3 and the only advice was "pip install".
|
||||
Point at the extra and the uv path first, then the pip/venv fallback.
|
||||
"""
|
||||
return (
|
||||
f"the '{pkg}' package is required for this backend but is not installed. "
|
||||
f"Install it with: uv tool install \"graphifyy[{extra}]\" --force "
|
||||
f"(uv tool), or pip install {pkg} (pip/venv install)."
|
||||
)
|
||||
|
||||
|
||||
def _call_openai_compat(
|
||||
base_url: str,
|
||||
api_key: str,
|
||||
@@ -411,11 +426,8 @@ def _call_openai_compat(
|
||||
try:
|
||||
from openai import OpenAI
|
||||
except ImportError as exc:
|
||||
pkg_hint = "graphifyy[kimi]" if backend == "kimi" else "openai"
|
||||
raise ImportError(
|
||||
"Gemini/Kimi/Ollama/OpenAI-compatible extraction requires the openai package. "
|
||||
f"Run: pip install {pkg_hint}"
|
||||
) from exc
|
||||
extra = backend if backend in ("kimi", "gemini", "openai", "ollama") else "openai"
|
||||
raise ImportError(_backend_pkg_hint("openai", extra)) from exc
|
||||
|
||||
# Local backends (ollama, llama.cpp, vLLM) routinely take >60s for a
|
||||
# single chunk on a large model — far longer than the openai SDK's
|
||||
@@ -536,10 +548,7 @@ def _call_claude(api_key: str, model: str, user_message: str, max_tokens: int =
|
||||
try:
|
||||
import anthropic
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Claude direct extraction requires the anthropic package. "
|
||||
"Run: pip install anthropic"
|
||||
) from exc
|
||||
raise ImportError(_backend_pkg_hint("anthropic", "anthropic")) from exc
|
||||
|
||||
client = anthropic.Anthropic(api_key=api_key)
|
||||
resp = client.messages.create(
|
||||
@@ -1172,7 +1181,7 @@ def _call_llm(prompt: str, *, backend: str, max_tokens: int = 200) -> str:
|
||||
try:
|
||||
import anthropic
|
||||
except ImportError as exc:
|
||||
raise ImportError("anthropic package required for claude backend") from exc
|
||||
raise ImportError(_backend_pkg_hint("anthropic", "anthropic")) from exc
|
||||
client = anthropic.Anthropic(api_key=key)
|
||||
resp = client.messages.create(
|
||||
model=mdl,
|
||||
@@ -1206,7 +1215,7 @@ def _call_llm(prompt: str, *, backend: str, max_tokens: int = 200) -> str:
|
||||
try:
|
||||
import boto3
|
||||
except ImportError as exc:
|
||||
raise ImportError("boto3 required for bedrock backend") from exc
|
||||
raise ImportError(_backend_pkg_hint("boto3", "bedrock")) from exc
|
||||
region = os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION") or "us-east-1"
|
||||
profile = os.environ.get("AWS_PROFILE")
|
||||
session = boto3.Session(profile_name=profile, region_name=region)
|
||||
@@ -1222,7 +1231,7 @@ def _call_llm(prompt: str, *, backend: str, max_tokens: int = 200) -> str:
|
||||
try:
|
||||
from openai import OpenAI
|
||||
except ImportError as exc:
|
||||
raise ImportError("openai package required for this backend") from exc
|
||||
raise ImportError(_backend_pkg_hint("openai", "openai")) from exc
|
||||
client = OpenAI(api_key=key, base_url=cfg["base_url"])
|
||||
kwargs: dict = {
|
||||
"model": mdl,
|
||||
|
||||
+2
-1
@@ -60,6 +60,7 @@ video = ["faster-whisper; python_version >= '3.11'", "yt-dlp"]
|
||||
kimi = ["openai", "tiktoken"]
|
||||
ollama = ["openai"]
|
||||
bedrock = ["boto3"]
|
||||
anthropic = ["anthropic"]
|
||||
gemini = ["openai", "tiktoken"]
|
||||
openai = ["openai", "tiktoken"]
|
||||
chinese = ["jieba"]
|
||||
@@ -68,7 +69,7 @@ sql = ["tree-sitter-sql"]
|
||||
# must compile from source (needs a C toolchain + python3-dev). Keeping it optional
|
||||
# avoids breaking the default `uv tool install graphifyy` for everyone (#1104).
|
||||
dm = ["tree-sitter-dm"]
|
||||
all = ["mcp", "neo4j", "pypdf", "markdownify", "watchdog", "graspologic; python_version < '3.13'", "python-docx", "openpyxl", "faster-whisper; python_version >= '3.11'", "yt-dlp", "matplotlib", "openai", "tiktoken", "boto3", "tree-sitter-sql", "jieba", "tree-sitter-dm"]
|
||||
all = ["mcp", "neo4j", "pypdf", "markdownify", "watchdog", "graspologic; python_version < '3.13'", "python-docx", "openpyxl", "faster-whisper; python_version >= '3.11'", "yt-dlp", "matplotlib", "openai", "tiktoken", "boto3", "anthropic", "tree-sitter-sql", "jieba", "tree-sitter-dm"]
|
||||
|
||||
[project.scripts]
|
||||
graphify = "graphify.__main__:main"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"""The claude backend must be installable via an extra, and the missing-package
|
||||
message must point uv-tool users at the right command.
|
||||
|
||||
Friction this guards: `uv tool install graphifyy` puts graphify in an isolated
|
||||
venv. A user with ANTHROPIC_API_KEY set then hit "anthropic package required"
|
||||
with no extra to satisfy it (claude was the only backend with no `[extra]`), and
|
||||
the message said `pip install anthropic`, which does not reach a uv tool venv.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from graphify.llm import _backend_pkg_hint
|
||||
|
||||
try:
|
||||
import tomllib
|
||||
except ModuleNotFoundError: # Python 3.10
|
||||
import tomli as tomllib # type: ignore[no-redef]
|
||||
|
||||
PYPROJECT = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
||||
|
||||
|
||||
def _extras():
|
||||
data = tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))
|
||||
return data["project"]["optional-dependencies"]
|
||||
|
||||
|
||||
def test_anthropic_extra_exists():
|
||||
extras = _extras()
|
||||
assert "anthropic" in extras, "claude backend needs a [anthropic] extra"
|
||||
assert any("anthropic" in dep for dep in extras["anthropic"])
|
||||
|
||||
|
||||
def test_anthropic_in_all_extra():
|
||||
extras = _extras()
|
||||
assert any("anthropic" in dep for dep in extras["all"]), "[all] must include anthropic"
|
||||
|
||||
|
||||
def test_backend_pkg_hint_points_at_uv_tool_and_extra():
|
||||
msg = _backend_pkg_hint("anthropic", "anthropic")
|
||||
assert "uv tool install" in msg
|
||||
assert 'graphifyy[anthropic]' in msg
|
||||
assert "pip install anthropic" in msg # pip/venv fallback still mentioned
|
||||
@@ -29,6 +29,25 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anthropic"
|
||||
version = "0.105.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "distro" },
|
||||
{ name = "docstring-parser" },
|
||||
{ name = "httpx" },
|
||||
{ name = "jiter" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/46/46/47581b8c689c743ceabf6a0f9ff48472160900ce802d26c0fb50423997b3/anthropic-0.105.2.tar.gz", hash = "sha256:0e26b90841c2dced7cc6e98d21d5517d0be33f1876b8e779f478202e28bcaa07", size = 853789, upload-time = "2026-05-29T00:21:14.104Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/83/75/be0c357e33a5a56c8f9db5b4212f886138d2bf59c0952d858f6b75d710ef/anthropic-0.105.2-py3-none-any.whl", hash = "sha256:e53ed5f6bf36fb1ecb9b25d8634cfd30e02fab9fb3374a0c2d5c585874757230", size = 837507, upload-time = "2026-05-29T00:21:15.528Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.13.0"
|
||||
@@ -931,6 +950,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "docstring-parser"
|
||||
version = "0.18.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/4d/f332313098c1de1b2d2ff91cf2674415cc7cddab2ca1b01ae29774bd5fdf/docstring_parser-0.18.0.tar.gz", hash = "sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015", size = 29341, upload-time = "2026-04-14T04:09:19.867Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dparse"
|
||||
version = "0.6.4"
|
||||
@@ -1146,6 +1174,7 @@ dependencies = [
|
||||
|
||||
[package.optional-dependencies]
|
||||
all = [
|
||||
{ name = "anthropic" },
|
||||
{ name = "boto3" },
|
||||
{ name = "faster-whisper", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "graspologic", marker = "python_full_version < '3.13'" },
|
||||
@@ -1164,6 +1193,9 @@ all = [
|
||||
{ name = "watchdog" },
|
||||
{ name = "yt-dlp" },
|
||||
]
|
||||
anthropic = [
|
||||
{ name = "anthropic" },
|
||||
]
|
||||
bedrock = [
|
||||
{ name = "boto3" },
|
||||
]
|
||||
@@ -1243,6 +1275,8 @@ dev = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "anthropic", marker = "extra == 'all'" },
|
||||
{ name = "anthropic", marker = "extra == 'anthropic'" },
|
||||
{ name = "boto3", marker = "extra == 'all'" },
|
||||
{ name = "boto3", marker = "extra == 'bedrock'" },
|
||||
{ name = "datasketch" },
|
||||
@@ -1313,7 +1347,7 @@ requires-dist = [
|
||||
{ name = "yt-dlp", marker = "extra == 'all'" },
|
||||
{ name = "yt-dlp", marker = "extra == 'video'" },
|
||||
]
|
||||
provides-extras = ["mcp", "neo4j", "pdf", "watch", "svg", "leiden", "office", "google", "video", "kimi", "ollama", "bedrock", "gemini", "openai", "chinese", "sql", "dm", "all"]
|
||||
provides-extras = ["mcp", "neo4j", "pdf", "watch", "svg", "leiden", "office", "google", "video", "kimi", "ollama", "bedrock", "anthropic", "gemini", "openai", "chinese", "sql", "dm", "all"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
|
||||
Reference in New Issue
Block a user