claude-code-workflow 6.2.7 → 6.3.0
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.
- package/.claude/CLAUDE.md +16 -1
- package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +11 -4
- package/.claude/workflows/cli-templates/protocols/write-protocol.md +10 -75
- package/.claude/workflows/cli-tools-usage.md +14 -24
- package/.codex/AGENTS.md +51 -1
- package/.codex/prompts/compact.md +378 -0
- package/.gemini/GEMINI.md +57 -20
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +21 -8
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/cli.d.ts +2 -0
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +129 -8
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/commands/hook.d.ts.map +1 -1
- package/ccw/dist/commands/hook.js +3 -2
- package/ccw/dist/commands/hook.js.map +1 -1
- package/ccw/dist/config/litellm-api-config-manager.d.ts +180 -0
- package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -0
- package/ccw/dist/config/litellm-api-config-manager.js +770 -0
- package/ccw/dist/config/litellm-api-config-manager.js.map +1 -0
- package/ccw/dist/config/provider-models.d.ts +73 -0
- package/ccw/dist/config/provider-models.d.ts.map +1 -0
- package/ccw/dist/config/provider-models.js +172 -0
- package/ccw/dist/config/provider-models.js.map +1 -0
- package/ccw/dist/core/cache-manager.d.ts.map +1 -1
- package/ccw/dist/core/cache-manager.js +3 -5
- package/ccw/dist/core/cache-manager.js.map +1 -1
- package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
- package/ccw/dist/core/dashboard-generator.js +3 -1
- package/ccw/dist/core/dashboard-generator.js.map +1 -1
- package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/cli-routes.js +169 -0
- package/ccw/dist/core/routes/cli-routes.js.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.js +234 -18
- package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.js +30 -32
- package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.d.ts +21 -0
- package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/litellm-api-routes.js +780 -0
- package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -0
- package/ccw/dist/core/routes/litellm-routes.d.ts +20 -0
- package/ccw/dist/core/routes/litellm-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/litellm-routes.js +85 -0
- package/ccw/dist/core/routes/litellm-routes.js.map +1 -0
- package/ccw/dist/core/routes/mcp-routes.js +2 -2
- package/ccw/dist/core/routes/mcp-routes.js.map +1 -1
- package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/status-routes.js +39 -0
- package/ccw/dist/core/routes/status-routes.js.map +1 -1
- package/ccw/dist/core/routes/system-routes.js +1 -1
- package/ccw/dist/core/routes/system-routes.js.map +1 -1
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +15 -1
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/mcp-server/index.js +1 -1
- package/ccw/dist/mcp-server/index.js.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.d.ts +82 -0
- package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -0
- package/ccw/dist/tools/claude-cli-tools.js +216 -0
- package/ccw/dist/tools/claude-cli-tools.js.map +1 -0
- package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor.js +76 -14
- package/ccw/dist/tools/cli-executor.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts +9 -2
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +114 -9
- package/ccw/dist/tools/codex-lens.js.map +1 -1
- package/ccw/dist/tools/context-cache-store.d.ts +136 -0
- package/ccw/dist/tools/context-cache-store.d.ts.map +1 -0
- package/ccw/dist/tools/context-cache-store.js +256 -0
- package/ccw/dist/tools/context-cache-store.js.map +1 -0
- package/ccw/dist/tools/context-cache.d.ts +56 -0
- package/ccw/dist/tools/context-cache.d.ts.map +1 -0
- package/ccw/dist/tools/context-cache.js +294 -0
- package/ccw/dist/tools/context-cache.js.map +1 -0
- package/ccw/dist/tools/core-memory.d.ts.map +1 -1
- package/ccw/dist/tools/core-memory.js +33 -19
- package/ccw/dist/tools/core-memory.js.map +1 -1
- package/ccw/dist/tools/index.d.ts.map +1 -1
- package/ccw/dist/tools/index.js +2 -0
- package/ccw/dist/tools/index.js.map +1 -1
- package/ccw/dist/tools/litellm-client.d.ts +85 -0
- package/ccw/dist/tools/litellm-client.d.ts.map +1 -0
- package/ccw/dist/tools/litellm-client.js +188 -0
- package/ccw/dist/tools/litellm-client.js.map +1 -0
- package/ccw/dist/tools/litellm-executor.d.ts +34 -0
- package/ccw/dist/tools/litellm-executor.d.ts.map +1 -0
- package/ccw/dist/tools/litellm-executor.js +192 -0
- package/ccw/dist/tools/litellm-executor.js.map +1 -0
- package/ccw/dist/tools/pattern-parser.d.ts +55 -0
- package/ccw/dist/tools/pattern-parser.d.ts.map +1 -0
- package/ccw/dist/tools/pattern-parser.js +237 -0
- package/ccw/dist/tools/pattern-parser.js.map +1 -0
- package/ccw/dist/tools/smart-search.d.ts +1 -0
- package/ccw/dist/tools/smart-search.d.ts.map +1 -1
- package/ccw/dist/tools/smart-search.js +117 -41
- package/ccw/dist/tools/smart-search.js.map +1 -1
- package/ccw/dist/types/litellm-api-config.d.ts +294 -0
- package/ccw/dist/types/litellm-api-config.d.ts.map +1 -0
- package/ccw/dist/types/litellm-api-config.js +8 -0
- package/ccw/dist/types/litellm-api-config.js.map +1 -0
- package/ccw/src/cli.ts +258 -244
- package/ccw/src/commands/cli.ts +153 -9
- package/ccw/src/commands/hook.ts +3 -2
- package/ccw/src/config/.litellm-api-config-manager.ts.2025-12-23T11-57-43-727Z.bak +441 -0
- package/ccw/src/config/litellm-api-config-manager.ts +1012 -0
- package/ccw/src/config/provider-models.ts +222 -0
- package/ccw/src/core/cache-manager.ts +292 -294
- package/ccw/src/core/dashboard-generator.ts +3 -1
- package/ccw/src/core/routes/cli-routes.ts +192 -0
- package/ccw/src/core/routes/codexlens-routes.ts +241 -19
- package/ccw/src/core/routes/hooks-routes.ts +399 -405
- package/ccw/src/core/routes/litellm-api-routes.ts +930 -0
- package/ccw/src/core/routes/litellm-routes.ts +107 -0
- package/ccw/src/core/routes/mcp-routes.ts +1271 -1271
- package/ccw/src/core/routes/status-routes.ts +51 -0
- package/ccw/src/core/routes/system-routes.ts +1 -1
- package/ccw/src/core/server.ts +15 -1
- package/ccw/src/mcp-server/index.ts +1 -1
- package/ccw/src/templates/dashboard-css/12-cli-legacy.css +44 -0
- package/ccw/src/templates/dashboard-css/31-api-settings.css +2265 -0
- package/ccw/src/templates/dashboard-js/components/cli-history.js +15 -8
- package/ccw/src/templates/dashboard-js/components/cli-status.js +323 -9
- package/ccw/src/templates/dashboard-js/components/navigation.js +329 -313
- package/ccw/src/templates/dashboard-js/i18n.js +583 -1
- package/ccw/src/templates/dashboard-js/views/api-settings.js +3362 -0
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +199 -24
- package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +1265 -27
- package/ccw/src/templates/dashboard.html +840 -831
- package/ccw/src/tools/claude-cli-tools.ts +300 -0
- package/ccw/src/tools/cli-executor.ts +83 -14
- package/ccw/src/tools/codex-lens.ts +146 -9
- package/ccw/src/tools/context-cache-store.ts +368 -0
- package/ccw/src/tools/context-cache.ts +393 -0
- package/ccw/src/tools/core-memory.ts +33 -19
- package/ccw/src/tools/index.ts +2 -0
- package/ccw/src/tools/litellm-client.ts +246 -0
- package/ccw/src/tools/litellm-executor.ts +241 -0
- package/ccw/src/tools/pattern-parser.ts +329 -0
- package/ccw/src/tools/smart-search.ts +142 -41
- package/ccw/src/types/litellm-api-config.ts +402 -0
- package/ccw-litellm/README.md +180 -0
- package/ccw-litellm/pyproject.toml +35 -0
- package/ccw-litellm/src/ccw_litellm/__init__.py +47 -0
- package/ccw-litellm/src/ccw_litellm/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/__pycache__/cli.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/cli.py +108 -0
- package/ccw-litellm/src/ccw_litellm/clients/__init__.py +12 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_llm.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/litellm_embedder.py +251 -0
- package/ccw-litellm/src/ccw_litellm/clients/litellm_llm.py +165 -0
- package/ccw-litellm/src/ccw_litellm/config/__init__.py +22 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/loader.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/models.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/loader.py +316 -0
- package/ccw-litellm/src/ccw_litellm/config/models.py +130 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__init__.py +14 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/embedder.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/llm.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/embedder.py +52 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/llm.py +45 -0
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/commands.py +378 -23
- package/codex-lens/src/codexlens/cli/embedding_manager.py +660 -56
- package/codex-lens/src/codexlens/cli/model_manager.py +31 -18
- package/codex-lens/src/codexlens/cli/output.py +12 -1
- package/codex-lens/src/codexlens/config.py +93 -0
- package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/chain_search.py +6 -2
- package/codex-lens/src/codexlens/search/hybrid_search.py +44 -21
- package/codex-lens/src/codexlens/search/ranking.py +1 -1
- package/codex-lens/src/codexlens/semantic/__init__.py +42 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/base.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/factory.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/base.py +61 -0
- package/codex-lens/src/codexlens/semantic/chunker.py +43 -20
- package/codex-lens/src/codexlens/semantic/embedder.py +60 -13
- package/codex-lens/src/codexlens/semantic/factory.py +98 -0
- package/codex-lens/src/codexlens/semantic/gpu_support.py +225 -3
- package/codex-lens/src/codexlens/semantic/litellm_embedder.py +144 -0
- package/codex-lens/src/codexlens/semantic/rotational_embedder.py +434 -0
- package/codex-lens/src/codexlens/semantic/vector_store.py +33 -8
- package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/path_mapper.py +27 -1
- package/package.json +15 -5
- package/.codex/prompts.zip +0 -0
- package/ccw/package.json +0 -65
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""CLI entry point for ccw-litellm."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main() -> int:
|
|
12
|
+
"""Main CLI entry point."""
|
|
13
|
+
parser = argparse.ArgumentParser(
|
|
14
|
+
prog="ccw-litellm",
|
|
15
|
+
description="Unified LiteLLM interface for ccw and codex-lens",
|
|
16
|
+
)
|
|
17
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
18
|
+
|
|
19
|
+
# config command
|
|
20
|
+
config_parser = subparsers.add_parser("config", help="Show configuration")
|
|
21
|
+
config_parser.add_argument(
|
|
22
|
+
"--path",
|
|
23
|
+
type=Path,
|
|
24
|
+
help="Configuration file path",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# embed command
|
|
28
|
+
embed_parser = subparsers.add_parser("embed", help="Generate embeddings")
|
|
29
|
+
embed_parser.add_argument("texts", nargs="+", help="Texts to embed")
|
|
30
|
+
embed_parser.add_argument(
|
|
31
|
+
"--model",
|
|
32
|
+
default="default",
|
|
33
|
+
help="Embedding model name (default: default)",
|
|
34
|
+
)
|
|
35
|
+
embed_parser.add_argument(
|
|
36
|
+
"--output",
|
|
37
|
+
choices=["json", "shape"],
|
|
38
|
+
default="shape",
|
|
39
|
+
help="Output format (default: shape)",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# chat command
|
|
43
|
+
chat_parser = subparsers.add_parser("chat", help="Chat with LLM")
|
|
44
|
+
chat_parser.add_argument("message", help="Message to send")
|
|
45
|
+
chat_parser.add_argument(
|
|
46
|
+
"--model",
|
|
47
|
+
default="default",
|
|
48
|
+
help="LLM model name (default: default)",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# version command
|
|
52
|
+
subparsers.add_parser("version", help="Show version")
|
|
53
|
+
|
|
54
|
+
args = parser.parse_args()
|
|
55
|
+
|
|
56
|
+
if args.command == "version":
|
|
57
|
+
from . import __version__
|
|
58
|
+
|
|
59
|
+
print(f"ccw-litellm {__version__}")
|
|
60
|
+
return 0
|
|
61
|
+
|
|
62
|
+
if args.command == "config":
|
|
63
|
+
from .config import get_config
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
config = get_config(config_path=args.path if hasattr(args, "path") else None)
|
|
67
|
+
print(config.model_dump_json(indent=2))
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print(f"Error loading config: {e}", file=sys.stderr)
|
|
70
|
+
return 1
|
|
71
|
+
return 0
|
|
72
|
+
|
|
73
|
+
if args.command == "embed":
|
|
74
|
+
from .clients import LiteLLMEmbedder
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
embedder = LiteLLMEmbedder(model=args.model)
|
|
78
|
+
vectors = embedder.embed(args.texts)
|
|
79
|
+
|
|
80
|
+
if args.output == "json":
|
|
81
|
+
print(json.dumps(vectors.tolist()))
|
|
82
|
+
else:
|
|
83
|
+
print(f"Shape: {vectors.shape}")
|
|
84
|
+
print(f"Dimensions: {embedder.dimensions}")
|
|
85
|
+
except Exception as e:
|
|
86
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
87
|
+
return 1
|
|
88
|
+
return 0
|
|
89
|
+
|
|
90
|
+
if args.command == "chat":
|
|
91
|
+
from .clients import LiteLLMClient
|
|
92
|
+
from .interfaces import ChatMessage
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
client = LiteLLMClient(model=args.model)
|
|
96
|
+
response = client.chat([ChatMessage(role="user", content=args.message)])
|
|
97
|
+
print(response.content)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
100
|
+
return 1
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
parser.print_help()
|
|
104
|
+
return 0
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
if __name__ == "__main__":
|
|
108
|
+
sys.exit(main())
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""LiteLLM embedder implementation for text embeddings."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Sequence
|
|
7
|
+
|
|
8
|
+
import litellm
|
|
9
|
+
import numpy as np
|
|
10
|
+
from numpy.typing import NDArray
|
|
11
|
+
|
|
12
|
+
from ..config import LiteLLMConfig, get_config
|
|
13
|
+
from ..interfaces.embedder import AbstractEmbedder
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LiteLLMEmbedder(AbstractEmbedder):
|
|
19
|
+
"""LiteLLM embedder implementation.
|
|
20
|
+
|
|
21
|
+
Supports multiple embedding providers (OpenAI, etc.) through LiteLLM's unified interface.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
embedder = LiteLLMEmbedder(model="default")
|
|
25
|
+
vectors = embedder.embed(["Hello world", "Another text"])
|
|
26
|
+
print(vectors.shape) # (2, 1536)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
model: str = "default",
|
|
32
|
+
config: LiteLLMConfig | None = None,
|
|
33
|
+
**litellm_kwargs: Any,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Initialize LiteLLM embedder.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
model: Model name from configuration (default: "default")
|
|
39
|
+
config: Configuration instance (default: use global config)
|
|
40
|
+
**litellm_kwargs: Additional arguments to pass to litellm.embedding()
|
|
41
|
+
"""
|
|
42
|
+
self._config = config or get_config()
|
|
43
|
+
self._model_name = model
|
|
44
|
+
self._litellm_kwargs = litellm_kwargs
|
|
45
|
+
|
|
46
|
+
# Get embedding model configuration
|
|
47
|
+
try:
|
|
48
|
+
self._model_config = self._config.get_embedding_model(model)
|
|
49
|
+
except ValueError as e:
|
|
50
|
+
logger.error(f"Failed to get embedding model configuration: {e}")
|
|
51
|
+
raise
|
|
52
|
+
|
|
53
|
+
# Get provider configuration
|
|
54
|
+
try:
|
|
55
|
+
self._provider_config = self._config.get_provider(self._model_config.provider)
|
|
56
|
+
except ValueError as e:
|
|
57
|
+
logger.error(f"Failed to get provider configuration: {e}")
|
|
58
|
+
raise
|
|
59
|
+
|
|
60
|
+
# Set up LiteLLM environment
|
|
61
|
+
self._setup_litellm()
|
|
62
|
+
|
|
63
|
+
def _setup_litellm(self) -> None:
|
|
64
|
+
"""Configure LiteLLM with provider settings."""
|
|
65
|
+
provider = self._model_config.provider
|
|
66
|
+
|
|
67
|
+
# Set API key
|
|
68
|
+
if self._provider_config.api_key:
|
|
69
|
+
litellm.api_key = self._provider_config.api_key
|
|
70
|
+
# Also set environment-specific keys
|
|
71
|
+
if provider == "openai":
|
|
72
|
+
litellm.openai_key = self._provider_config.api_key
|
|
73
|
+
elif provider == "anthropic":
|
|
74
|
+
litellm.anthropic_key = self._provider_config.api_key
|
|
75
|
+
|
|
76
|
+
# Set API base
|
|
77
|
+
if self._provider_config.api_base:
|
|
78
|
+
litellm.api_base = self._provider_config.api_base
|
|
79
|
+
|
|
80
|
+
def _format_model_name(self) -> str:
|
|
81
|
+
"""Format model name for LiteLLM.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Formatted model name (e.g., "openai/text-embedding-3-small")
|
|
85
|
+
"""
|
|
86
|
+
provider = self._model_config.provider
|
|
87
|
+
model = self._model_config.model
|
|
88
|
+
|
|
89
|
+
# For some providers, LiteLLM expects explicit prefix
|
|
90
|
+
if provider in ["azure", "vertex_ai", "bedrock"]:
|
|
91
|
+
return f"{provider}/{model}"
|
|
92
|
+
|
|
93
|
+
# For providers with custom api_base (OpenAI-compatible endpoints),
|
|
94
|
+
# use openai/ prefix to tell LiteLLM to use OpenAI API format
|
|
95
|
+
if self._provider_config.api_base and provider not in ["openai", "anthropic"]:
|
|
96
|
+
return f"openai/{model}"
|
|
97
|
+
|
|
98
|
+
return model
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def dimensions(self) -> int:
|
|
102
|
+
"""Embedding vector size."""
|
|
103
|
+
return self._model_config.dimensions
|
|
104
|
+
|
|
105
|
+
def _estimate_tokens(self, text: str) -> int:
|
|
106
|
+
"""Estimate token count for a text using fast heuristic.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
text: Text to estimate tokens for
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Estimated token count (len/4 is a reasonable approximation)
|
|
113
|
+
"""
|
|
114
|
+
return len(text) // 4
|
|
115
|
+
|
|
116
|
+
def _create_batches(
|
|
117
|
+
self,
|
|
118
|
+
texts: list[str],
|
|
119
|
+
max_tokens: int = 30000
|
|
120
|
+
) -> list[list[str]]:
|
|
121
|
+
"""Split texts into batches that fit within token limits.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
texts: List of texts to batch
|
|
125
|
+
max_tokens: Maximum tokens per batch (default: 30000, safe margin for 40960 limit)
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
List of text batches
|
|
129
|
+
"""
|
|
130
|
+
batches = []
|
|
131
|
+
current_batch = []
|
|
132
|
+
current_tokens = 0
|
|
133
|
+
|
|
134
|
+
for text in texts:
|
|
135
|
+
text_tokens = self._estimate_tokens(text)
|
|
136
|
+
|
|
137
|
+
# If single text exceeds limit, truncate it
|
|
138
|
+
if text_tokens > max_tokens:
|
|
139
|
+
logger.warning(f"Text with {text_tokens} estimated tokens exceeds limit, truncating")
|
|
140
|
+
# Truncate to fit (rough estimate: 4 chars per token)
|
|
141
|
+
max_chars = max_tokens * 4
|
|
142
|
+
text = text[:max_chars]
|
|
143
|
+
text_tokens = self._estimate_tokens(text)
|
|
144
|
+
|
|
145
|
+
# Start new batch if current would exceed limit
|
|
146
|
+
if current_tokens + text_tokens > max_tokens and current_batch:
|
|
147
|
+
batches.append(current_batch)
|
|
148
|
+
current_batch = []
|
|
149
|
+
current_tokens = 0
|
|
150
|
+
|
|
151
|
+
current_batch.append(text)
|
|
152
|
+
current_tokens += text_tokens
|
|
153
|
+
|
|
154
|
+
# Add final batch
|
|
155
|
+
if current_batch:
|
|
156
|
+
batches.append(current_batch)
|
|
157
|
+
|
|
158
|
+
return batches
|
|
159
|
+
|
|
160
|
+
def embed(
|
|
161
|
+
self,
|
|
162
|
+
texts: str | Sequence[str],
|
|
163
|
+
*,
|
|
164
|
+
batch_size: int | None = None,
|
|
165
|
+
max_tokens_per_batch: int = 30000,
|
|
166
|
+
**kwargs: Any,
|
|
167
|
+
) -> NDArray[np.floating]:
|
|
168
|
+
"""Embed one or more texts.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
texts: Single text or sequence of texts
|
|
172
|
+
batch_size: Batch size for processing (deprecated, use max_tokens_per_batch)
|
|
173
|
+
max_tokens_per_batch: Maximum estimated tokens per API call (default: 30000)
|
|
174
|
+
**kwargs: Additional arguments for litellm.embedding()
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
A numpy array of shape (n_texts, dimensions).
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
Exception: If LiteLLM embedding fails
|
|
181
|
+
"""
|
|
182
|
+
# Normalize input to list
|
|
183
|
+
if isinstance(texts, str):
|
|
184
|
+
text_list = [texts]
|
|
185
|
+
else:
|
|
186
|
+
text_list = list(texts)
|
|
187
|
+
|
|
188
|
+
if not text_list:
|
|
189
|
+
# Return empty array with correct shape
|
|
190
|
+
return np.empty((0, self.dimensions), dtype=np.float32)
|
|
191
|
+
|
|
192
|
+
# Merge kwargs
|
|
193
|
+
embedding_kwargs = {**self._litellm_kwargs, **kwargs}
|
|
194
|
+
|
|
195
|
+
# For OpenAI-compatible endpoints, ensure encoding_format is set
|
|
196
|
+
if self._provider_config.api_base and "encoding_format" not in embedding_kwargs:
|
|
197
|
+
embedding_kwargs["encoding_format"] = "float"
|
|
198
|
+
|
|
199
|
+
# Split into token-aware batches
|
|
200
|
+
batches = self._create_batches(text_list, max_tokens_per_batch)
|
|
201
|
+
|
|
202
|
+
if len(batches) > 1:
|
|
203
|
+
logger.info(f"Split {len(text_list)} texts into {len(batches)} batches for embedding")
|
|
204
|
+
|
|
205
|
+
all_embeddings = []
|
|
206
|
+
|
|
207
|
+
for batch_idx, batch in enumerate(batches):
|
|
208
|
+
try:
|
|
209
|
+
# Build call kwargs with explicit api_base
|
|
210
|
+
call_kwargs = {**embedding_kwargs}
|
|
211
|
+
if self._provider_config.api_base:
|
|
212
|
+
call_kwargs["api_base"] = self._provider_config.api_base
|
|
213
|
+
if self._provider_config.api_key:
|
|
214
|
+
call_kwargs["api_key"] = self._provider_config.api_key
|
|
215
|
+
|
|
216
|
+
# Call LiteLLM embedding for this batch
|
|
217
|
+
response = litellm.embedding(
|
|
218
|
+
model=self._format_model_name(),
|
|
219
|
+
input=batch,
|
|
220
|
+
**call_kwargs,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Extract embeddings
|
|
224
|
+
batch_embeddings = [item["embedding"] for item in response.data]
|
|
225
|
+
all_embeddings.extend(batch_embeddings)
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.error(f"LiteLLM embedding failed for batch {batch_idx + 1}/{len(batches)}: {e}")
|
|
229
|
+
raise
|
|
230
|
+
|
|
231
|
+
# Convert to numpy array
|
|
232
|
+
result = np.array(all_embeddings, dtype=np.float32)
|
|
233
|
+
|
|
234
|
+
# Validate dimensions
|
|
235
|
+
if result.shape[1] != self.dimensions:
|
|
236
|
+
logger.warning(
|
|
237
|
+
f"Expected {self.dimensions} dimensions, got {result.shape[1]}. "
|
|
238
|
+
f"Configuration may be incorrect."
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return result
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def model_name(self) -> str:
|
|
245
|
+
"""Get configured model name."""
|
|
246
|
+
return self._model_name
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def provider(self) -> str:
|
|
250
|
+
"""Get configured provider name."""
|
|
251
|
+
return self._model_config.provider
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""LiteLLM client implementation for LLM operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Sequence
|
|
7
|
+
|
|
8
|
+
import litellm
|
|
9
|
+
|
|
10
|
+
from ..config import LiteLLMConfig, get_config
|
|
11
|
+
from ..interfaces.llm import AbstractLLMClient, ChatMessage, LLMResponse
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LiteLLMClient(AbstractLLMClient):
|
|
17
|
+
"""LiteLLM client implementation.
|
|
18
|
+
|
|
19
|
+
Supports multiple providers (OpenAI, Anthropic, etc.) through LiteLLM's unified interface.
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
client = LiteLLMClient(model="default")
|
|
23
|
+
response = client.chat([
|
|
24
|
+
ChatMessage(role="user", content="Hello!")
|
|
25
|
+
])
|
|
26
|
+
print(response.content)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
model: str = "default",
|
|
32
|
+
config: LiteLLMConfig | None = None,
|
|
33
|
+
**litellm_kwargs: Any,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Initialize LiteLLM client.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
model: Model name from configuration (default: "default")
|
|
39
|
+
config: Configuration instance (default: use global config)
|
|
40
|
+
**litellm_kwargs: Additional arguments to pass to litellm.completion()
|
|
41
|
+
"""
|
|
42
|
+
self._config = config or get_config()
|
|
43
|
+
self._model_name = model
|
|
44
|
+
self._litellm_kwargs = litellm_kwargs
|
|
45
|
+
|
|
46
|
+
# Get model configuration
|
|
47
|
+
try:
|
|
48
|
+
self._model_config = self._config.get_llm_model(model)
|
|
49
|
+
except ValueError as e:
|
|
50
|
+
logger.error(f"Failed to get model configuration: {e}")
|
|
51
|
+
raise
|
|
52
|
+
|
|
53
|
+
# Get provider configuration
|
|
54
|
+
try:
|
|
55
|
+
self._provider_config = self._config.get_provider(self._model_config.provider)
|
|
56
|
+
except ValueError as e:
|
|
57
|
+
logger.error(f"Failed to get provider configuration: {e}")
|
|
58
|
+
raise
|
|
59
|
+
|
|
60
|
+
# Set up LiteLLM environment
|
|
61
|
+
self._setup_litellm()
|
|
62
|
+
|
|
63
|
+
def _setup_litellm(self) -> None:
|
|
64
|
+
"""Configure LiteLLM with provider settings."""
|
|
65
|
+
provider = self._model_config.provider
|
|
66
|
+
|
|
67
|
+
# Set API key
|
|
68
|
+
if self._provider_config.api_key:
|
|
69
|
+
env_var = f"{provider.upper()}_API_KEY"
|
|
70
|
+
litellm.api_key = self._provider_config.api_key
|
|
71
|
+
# Also set environment-specific keys
|
|
72
|
+
if provider == "openai":
|
|
73
|
+
litellm.openai_key = self._provider_config.api_key
|
|
74
|
+
elif provider == "anthropic":
|
|
75
|
+
litellm.anthropic_key = self._provider_config.api_key
|
|
76
|
+
|
|
77
|
+
# Set API base
|
|
78
|
+
if self._provider_config.api_base:
|
|
79
|
+
litellm.api_base = self._provider_config.api_base
|
|
80
|
+
|
|
81
|
+
def _format_model_name(self) -> str:
|
|
82
|
+
"""Format model name for LiteLLM.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Formatted model name (e.g., "gpt-4", "claude-3-opus-20240229")
|
|
86
|
+
"""
|
|
87
|
+
# LiteLLM expects model names in format: "provider/model" or just "model"
|
|
88
|
+
# If provider is explicit, use provider/model format
|
|
89
|
+
provider = self._model_config.provider
|
|
90
|
+
model = self._model_config.model
|
|
91
|
+
|
|
92
|
+
# For some providers, LiteLLM expects explicit prefix
|
|
93
|
+
if provider in ["anthropic", "azure", "vertex_ai", "bedrock"]:
|
|
94
|
+
return f"{provider}/{model}"
|
|
95
|
+
|
|
96
|
+
return model
|
|
97
|
+
|
|
98
|
+
def chat(
|
|
99
|
+
self,
|
|
100
|
+
messages: Sequence[ChatMessage],
|
|
101
|
+
**kwargs: Any,
|
|
102
|
+
) -> LLMResponse:
|
|
103
|
+
"""Chat completion for a sequence of messages.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
messages: Sequence of chat messages
|
|
107
|
+
**kwargs: Additional arguments for litellm.completion()
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
LLM response with content and raw response
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
Exception: If LiteLLM completion fails
|
|
114
|
+
"""
|
|
115
|
+
# Convert messages to LiteLLM format
|
|
116
|
+
litellm_messages = [
|
|
117
|
+
{"role": msg.role, "content": msg.content} for msg in messages
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
# Merge kwargs
|
|
121
|
+
completion_kwargs = {**self._litellm_kwargs, **kwargs}
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
# Call LiteLLM
|
|
125
|
+
response = litellm.completion(
|
|
126
|
+
model=self._format_model_name(),
|
|
127
|
+
messages=litellm_messages,
|
|
128
|
+
**completion_kwargs,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Extract content
|
|
132
|
+
content = response.choices[0].message.content or ""
|
|
133
|
+
|
|
134
|
+
return LLMResponse(content=content, raw=response)
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(f"LiteLLM completion failed: {e}")
|
|
138
|
+
raise
|
|
139
|
+
|
|
140
|
+
def complete(self, prompt: str, **kwargs: Any) -> LLMResponse:
|
|
141
|
+
"""Text completion for a prompt.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
prompt: Input prompt
|
|
145
|
+
**kwargs: Additional arguments for litellm.completion()
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
LLM response with content and raw response
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
Exception: If LiteLLM completion fails
|
|
152
|
+
"""
|
|
153
|
+
# Convert to chat format (most modern models use chat interface)
|
|
154
|
+
messages = [ChatMessage(role="user", content=prompt)]
|
|
155
|
+
return self.chat(messages, **kwargs)
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def model_name(self) -> str:
|
|
159
|
+
"""Get configured model name."""
|
|
160
|
+
return self._model_name
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def provider(self) -> str:
|
|
164
|
+
"""Get configured provider name."""
|
|
165
|
+
return self._model_config.provider
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Configuration management for LiteLLM integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .loader import get_config, load_config, reset_config
|
|
6
|
+
from .models import (
|
|
7
|
+
EmbeddingModelConfig,
|
|
8
|
+
LiteLLMConfig,
|
|
9
|
+
LLMModelConfig,
|
|
10
|
+
ProviderConfig,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"LiteLLMConfig",
|
|
15
|
+
"ProviderConfig",
|
|
16
|
+
"LLMModelConfig",
|
|
17
|
+
"EmbeddingModelConfig",
|
|
18
|
+
"load_config",
|
|
19
|
+
"get_config",
|
|
20
|
+
"reset_config",
|
|
21
|
+
]
|
|
22
|
+
|