axnwork-cli 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,51 @@
1
+ """LiteLLM backend — wraps existing call_llm() logic."""
2
+ from __future__ import annotations
3
+
4
+ import logging
5
+ import time
6
+ from datetime import datetime
7
+
8
+ from axon.backends.base import BackendResult
9
+ from axon.backends.registry import register
10
+
11
+ log = logging.getLogger("axon.backend.litellm")
12
+
13
+
14
+ def _now_iso() -> str:
15
+ return datetime.now().astimezone().isoformat(timespec="seconds")
16
+
17
+
18
+ @register("litellm")
19
+ class LiteLLMBackend:
20
+ name = "litellm"
21
+
22
+ def __init__(self, config: dict):
23
+ self._model = config.get("default_model", "anthropic/claude-sonnet-4-20250514")
24
+ self._api_base = config.get("api_base", "")
25
+
26
+ def call(self, prompt: str, task: dict) -> BackendResult:
27
+ from axon.llm import call_llm
28
+ started_at = _now_iso()
29
+ started_mono = time.monotonic()
30
+ log.info(
31
+ "LiteLLM start started_at=%s model=%s prompt_chars=%d api_base=%s",
32
+ started_at,
33
+ self._model,
34
+ len(prompt),
35
+ self._api_base or "",
36
+ )
37
+ thinking, answer, usage = call_llm(prompt, self._model, self._api_base)
38
+ log.info(
39
+ "LiteLLM finished started_at=%s finished_at=%s duration_s=%.2f answer_chars=%d thinking_chars=%d total_tokens=%s cost=%s",
40
+ started_at,
41
+ _now_iso(),
42
+ time.monotonic() - started_mono,
43
+ len(answer or ""),
44
+ len(thinking or ""),
45
+ usage.get("total_tokens", 0),
46
+ usage.get("cost", 0.0),
47
+ )
48
+ return BackendResult(thinking=thinking, answer=answer, usage=usage)
49
+
50
+ def display_name(self) -> str:
51
+ return self._model
@@ -0,0 +1,61 @@
1
+ """Backend registry and factory function."""
2
+ from __future__ import annotations
3
+
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from axon.backends.base import Backend
8
+
9
+ _REGISTRY: dict[str, type] = {}
10
+
11
+
12
+ def register(name: str):
13
+ """Decorator to register a backend class by name."""
14
+ def wrapper(cls):
15
+ _REGISTRY[name] = cls
16
+ return cls
17
+ return wrapper
18
+
19
+
20
+ def auto_detect_backend() -> str:
21
+ """Pick the best available backend: claude-cli > codex-cli > litellm."""
22
+ import shutil
23
+ if shutil.which("claude"):
24
+ return "claude-cli"
25
+ if shutil.which("codex"):
26
+ return "codex-cli"
27
+ return "litellm"
28
+
29
+
30
+ def create_backend(backend_name: str, config: dict) -> Backend:
31
+ """Create a backend instance by name.
32
+
33
+ If backend_name is "auto", detect the best available CLI tool.
34
+ Lazily imports backend modules so we don't pull in dependencies
35
+ (litellm, subprocess, etc.) until actually needed.
36
+ """
37
+ if backend_name == "auto":
38
+ backend_name = auto_detect_backend()
39
+
40
+ # Ensure all backends are registered
41
+ _ensure_loaded()
42
+
43
+ if backend_name not in _REGISTRY:
44
+ available = ", ".join(sorted(_REGISTRY.keys()))
45
+ raise ValueError(f"Unknown backend '{backend_name}'. Available: {available}")
46
+
47
+ return _REGISTRY[backend_name](config)
48
+
49
+
50
+ _loaded = False
51
+
52
+
53
+ def _ensure_loaded():
54
+ global _loaded
55
+ if _loaded:
56
+ return
57
+ _loaded = True
58
+ # Import backend modules to trigger @register decorators
59
+ import axon.backends.litellm_backend # noqa: F401
60
+ import axon.backends.claude_cli # noqa: F401
61
+ import axon.backends.codex_cli # noqa: F401