claude-code-workflow 7.2.29 → 7.2.30

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.
Files changed (124) hide show
  1. package/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json +2 -2
  2. package/.ccw/workflows/cli-templates/schemas/task-schema.json +14 -7
  3. package/.claude/agents/action-planning-agent.md +7 -4
  4. package/.claude/agents/cli-explore-agent.md +77 -63
  5. package/.claude/agents/cli-lite-planning-agent.md +11 -10
  6. package/.claude/agents/issue-plan-agent.md +421 -426
  7. package/.claude/commands/workflow/spec/setup.md +1 -1
  8. package/.claude/skills/ccw-chain/SKILL.md +119 -0
  9. package/.claude/skills/ccw-chain/chains/ccw-cycle.json +21 -0
  10. package/.claude/skills/ccw-chain/chains/ccw-exploration.json +47 -0
  11. package/.claude/skills/ccw-chain/chains/ccw-issue.json +33 -0
  12. package/.claude/skills/ccw-chain/chains/ccw-lightweight.json +57 -0
  13. package/.claude/skills/ccw-chain/chains/ccw-main.json +52 -0
  14. package/.claude/skills/ccw-chain/chains/ccw-standard.json +39 -0
  15. package/.claude/skills/ccw-chain/chains/ccw-team.json +10 -0
  16. package/.claude/skills/ccw-chain/chains/ccw-with-file.json +31 -0
  17. package/.claude/skills/ccw-chain/phases/analyze-with-file.md +788 -0
  18. package/.claude/skills/ccw-chain/phases/brainstorm/SKILL.md +408 -0
  19. package/.claude/skills/ccw-chain/phases/brainstorm/phases/01-mode-routing.md +207 -0
  20. package/.claude/skills/ccw-chain/phases/brainstorm/phases/02-artifacts.md +567 -0
  21. package/.claude/skills/ccw-chain/phases/brainstorm/phases/03-role-analysis.md +748 -0
  22. package/.claude/skills/ccw-chain/phases/brainstorm/phases/04-synthesis.md +827 -0
  23. package/.claude/skills/ccw-chain/phases/brainstorm-with-file.md +482 -0
  24. package/.claude/skills/ccw-chain/phases/collaborative-plan-with-file.md +639 -0
  25. package/.claude/skills/ccw-chain/phases/debug-with-file.md +656 -0
  26. package/.claude/skills/ccw-chain/phases/integration-test-cycle.md +936 -0
  27. package/.claude/skills/ccw-chain/phases/issue-convert-to-plan.md +720 -0
  28. package/.claude/skills/ccw-chain/phases/issue-discover.md +483 -0
  29. package/.claude/skills/ccw-chain/phases/issue-execute.md +629 -0
  30. package/.claude/skills/ccw-chain/phases/issue-from-brainstorm.md +382 -0
  31. package/.claude/skills/ccw-chain/phases/issue-plan.md +343 -0
  32. package/.claude/skills/ccw-chain/phases/issue-queue.md +464 -0
  33. package/.claude/skills/ccw-chain/phases/refactor-cycle.md +852 -0
  34. package/.claude/skills/ccw-chain/phases/review-cycle/SKILL.md +132 -0
  35. package/.claude/skills/ccw-chain/phases/review-cycle/phases/review-fix.md +760 -0
  36. package/.claude/skills/ccw-chain/phases/review-cycle/phases/review-module.md +764 -0
  37. package/.claude/skills/ccw-chain/phases/review-cycle/phases/review-session.md +775 -0
  38. package/.claude/skills/ccw-chain/phases/roadmap-with-file.md +544 -0
  39. package/.claude/skills/ccw-chain/phases/spec-generator/SKILL.md +338 -0
  40. package/.claude/skills/ccw-chain/phases/spec-generator/phases/01-5-requirement-clarification.md +404 -0
  41. package/.claude/skills/ccw-chain/phases/spec-generator/phases/01-discovery.md +257 -0
  42. package/.claude/skills/ccw-chain/phases/spec-generator/phases/02-product-brief.md +274 -0
  43. package/.claude/skills/ccw-chain/phases/spec-generator/phases/03-requirements.md +184 -0
  44. package/.claude/skills/ccw-chain/phases/spec-generator/phases/04-architecture.md +248 -0
  45. package/.claude/skills/ccw-chain/phases/spec-generator/phases/05-epics-stories.md +178 -0
  46. package/.claude/skills/ccw-chain/phases/spec-generator/phases/06-5-auto-fix.md +144 -0
  47. package/.claude/skills/ccw-chain/phases/spec-generator/phases/06-readiness-check.md +480 -0
  48. package/.claude/skills/ccw-chain/phases/team-planex.md +123 -0
  49. package/.claude/skills/ccw-chain/phases/ui-design-explore-auto.md +678 -0
  50. package/.claude/skills/ccw-chain/phases/unified-execute-with-file.md +870 -0
  51. package/.claude/skills/ccw-chain/phases/workflow-execute/SKILL.md +625 -0
  52. package/.claude/skills/ccw-chain/phases/workflow-execute/phases/06-review.md +215 -0
  53. package/.claude/skills/ccw-chain/phases/workflow-lite-plan.md +616 -0
  54. package/.claude/skills/ccw-chain/phases/workflow-multi-cli-plan.md +424 -0
  55. package/.claude/skills/ccw-chain/phases/workflow-plan/SKILL.md +466 -0
  56. package/.claude/skills/ccw-chain/phases/workflow-plan/phases/01-session-discovery.md +99 -0
  57. package/.claude/skills/ccw-chain/phases/workflow-plan/phases/02-context-gathering.md +338 -0
  58. package/.claude/skills/ccw-chain/phases/workflow-plan/phases/03-conflict-resolution.md +422 -0
  59. package/.claude/skills/ccw-chain/phases/workflow-plan/phases/04-task-generation.md +440 -0
  60. package/.claude/skills/ccw-chain/phases/workflow-plan/phases/05-plan-verify.md +395 -0
  61. package/.claude/skills/ccw-chain/phases/workflow-plan/phases/06-replan.md +594 -0
  62. package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/SKILL.md +527 -0
  63. package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/01-session-discovery.md +57 -0
  64. package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/02-context-gathering.md +407 -0
  65. package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/03-test-coverage-analysis.md +172 -0
  66. package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/04-conflict-resolution.md +426 -0
  67. package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/05-tdd-task-generation.md +473 -0
  68. package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/06-tdd-structure-validation.md +189 -0
  69. package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/07-tdd-verify.md +635 -0
  70. package/.claude/skills/ccw-chain/phases/workflow-test-fix/SKILL.md +482 -0
  71. package/.claude/skills/ccw-chain/phases/workflow-test-fix/phases/01-session-start.md +60 -0
  72. package/.claude/skills/ccw-chain/phases/workflow-test-fix/phases/02-test-context-gather.md +493 -0
  73. package/.claude/skills/ccw-chain/phases/workflow-test-fix/phases/03-test-concept-enhanced.md +150 -0
  74. package/.claude/skills/ccw-chain/phases/workflow-test-fix/phases/04-test-task-generate.md +346 -0
  75. package/.claude/skills/ccw-chain/phases/workflow-test-fix/phases/05-test-cycle-execute.md +538 -0
  76. package/.claude/skills/ccw-chain/specs/auto-mode.md +47 -0
  77. package/.claude/skills/ccw-chain/specs/intent-patterns.md +60 -0
  78. package/.claude/skills/chain-loader/SKILL.md +78 -0
  79. package/.claude/skills/chain-loader/phases/01-analyze-skill.md +53 -0
  80. package/.claude/skills/chain-loader/phases/02-design-graph.md +73 -0
  81. package/.claude/skills/chain-loader/phases/03-generate-validate.md +75 -0
  82. package/.claude/skills/chain-loader/specs/chain-schema.md +99 -0
  83. package/.claude/skills/chain-loader/specs/design-patterns.md +99 -0
  84. package/.claude/skills/chain-loader/templates/chain-json.md +63 -0
  85. package/.claude/skills/review-cycle/phases/review-module.md +764 -764
  86. package/.claude/skills/review-cycle/phases/review-session.md +775 -775
  87. package/.claude/skills/workflow-multi-cli-plan/SKILL.md +2 -2
  88. package/.claude/skills/workflow-plan/phases/03-conflict-resolution.md +422 -422
  89. package/.claude/skills/workflow-plan/phases/05-plan-verify.md +395 -395
  90. package/.claude/skills/workflow-tdd-plan/phases/02-context-gathering.md +407 -407
  91. package/.claude/skills/workflow-tdd-plan/phases/04-conflict-resolution.md +426 -426
  92. package/.claude/skills/workflow-test-fix/phases/02-test-context-gather.md +493 -493
  93. package/README.md +14 -0
  94. package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -1
  95. package/ccw/dist/core/routes/litellm-api-routes.js +0 -23
  96. package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -1
  97. package/ccw/dist/tools/chain-loader.d.ts +10 -0
  98. package/ccw/dist/tools/chain-loader.d.ts.map +1 -0
  99. package/ccw/dist/tools/chain-loader.js +642 -0
  100. package/ccw/dist/tools/chain-loader.js.map +1 -0
  101. package/ccw/dist/tools/index.d.ts.map +1 -1
  102. package/ccw/dist/tools/index.js +2 -0
  103. package/ccw/dist/tools/index.js.map +1 -1
  104. package/ccw/dist/tools/json-builder.js +20 -0
  105. package/ccw/dist/tools/json-builder.js.map +1 -1
  106. package/ccw/dist/types/chain-types.d.ts +72 -0
  107. package/ccw/dist/types/chain-types.d.ts.map +1 -0
  108. package/ccw/dist/types/chain-types.js +5 -0
  109. package/ccw/dist/types/chain-types.js.map +1 -0
  110. package/ccw/scripts/prepublish-clean.mjs +0 -1
  111. package/package.json +1 -3
  112. package/ccw-litellm/README.md +0 -180
  113. package/ccw-litellm/pyproject.toml +0 -35
  114. package/ccw-litellm/src/ccw_litellm/__init__.py +0 -47
  115. package/ccw-litellm/src/ccw_litellm/cli.py +0 -108
  116. package/ccw-litellm/src/ccw_litellm/clients/__init__.py +0 -12
  117. package/ccw-litellm/src/ccw_litellm/clients/litellm_embedder.py +0 -270
  118. package/ccw-litellm/src/ccw_litellm/clients/litellm_llm.py +0 -198
  119. package/ccw-litellm/src/ccw_litellm/config/__init__.py +0 -22
  120. package/ccw-litellm/src/ccw_litellm/config/loader.py +0 -343
  121. package/ccw-litellm/src/ccw_litellm/config/models.py +0 -162
  122. package/ccw-litellm/src/ccw_litellm/interfaces/__init__.py +0 -14
  123. package/ccw-litellm/src/ccw_litellm/interfaces/embedder.py +0 -52
  124. package/ccw-litellm/src/ccw_litellm/interfaces/llm.py +0 -45
@@ -1,47 +0,0 @@
1
- """ccw-litellm package.
2
-
3
- This package provides a small, stable interface layer around LiteLLM to share
4
- between the ccw and codex-lens projects.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from .clients import LiteLLMClient, LiteLLMEmbedder
10
- from .config import (
11
- EmbeddingModelConfig,
12
- LiteLLMConfig,
13
- LLMModelConfig,
14
- ProviderConfig,
15
- get_config,
16
- load_config,
17
- reset_config,
18
- )
19
- from .interfaces import (
20
- AbstractEmbedder,
21
- AbstractLLMClient,
22
- ChatMessage,
23
- LLMResponse,
24
- )
25
-
26
- __version__ = "0.1.0"
27
-
28
- __all__ = [
29
- "__version__",
30
- # Abstract interfaces
31
- "AbstractEmbedder",
32
- "AbstractLLMClient",
33
- "ChatMessage",
34
- "LLMResponse",
35
- # Client implementations
36
- "LiteLLMClient",
37
- "LiteLLMEmbedder",
38
- # Configuration
39
- "LiteLLMConfig",
40
- "ProviderConfig",
41
- "LLMModelConfig",
42
- "EmbeddingModelConfig",
43
- "load_config",
44
- "get_config",
45
- "reset_config",
46
- ]
47
-
@@ -1,108 +0,0 @@
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())
@@ -1,12 +0,0 @@
1
- """Client implementations for ccw-litellm."""
2
-
3
- from __future__ import annotations
4
-
5
- from .litellm_embedder import LiteLLMEmbedder
6
- from .litellm_llm import LiteLLMClient
7
-
8
- __all__ = [
9
- "LiteLLMClient",
10
- "LiteLLMEmbedder",
11
- ]
12
-
@@ -1,270 +0,0 @@
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
- @property
106
- def max_input_tokens(self) -> int:
107
- """Maximum token limit for embeddings.
108
-
109
- Returns the configured max_input_tokens from model config,
110
- enabling adaptive batch sizing based on actual model capacity.
111
- """
112
- return self._model_config.max_input_tokens
113
-
114
- def _estimate_tokens(self, text: str) -> int:
115
- """Estimate token count for a text using fast heuristic.
116
-
117
- Args:
118
- text: Text to estimate tokens for
119
-
120
- Returns:
121
- Estimated token count (len/4 is a reasonable approximation)
122
- """
123
- return len(text) // 4
124
-
125
- def _create_batches(
126
- self,
127
- texts: list[str],
128
- max_tokens: int = 30000
129
- ) -> list[list[str]]:
130
- """Split texts into batches that fit within token limits.
131
-
132
- Args:
133
- texts: List of texts to batch
134
- max_tokens: Maximum tokens per batch (default: 30000, safe margin for 40960 limit)
135
-
136
- Returns:
137
- List of text batches
138
- """
139
- batches = []
140
- current_batch = []
141
- current_tokens = 0
142
-
143
- for text in texts:
144
- text_tokens = self._estimate_tokens(text)
145
-
146
- # If single text exceeds limit, truncate it
147
- if text_tokens > max_tokens:
148
- logger.warning(f"Text with {text_tokens} estimated tokens exceeds limit, truncating")
149
- # Truncate to fit (rough estimate: 4 chars per token)
150
- max_chars = max_tokens * 4
151
- text = text[:max_chars]
152
- text_tokens = self._estimate_tokens(text)
153
-
154
- # Start new batch if current would exceed limit
155
- if current_tokens + text_tokens > max_tokens and current_batch:
156
- batches.append(current_batch)
157
- current_batch = []
158
- current_tokens = 0
159
-
160
- current_batch.append(text)
161
- current_tokens += text_tokens
162
-
163
- # Add final batch
164
- if current_batch:
165
- batches.append(current_batch)
166
-
167
- return batches
168
-
169
- def embed(
170
- self,
171
- texts: str | Sequence[str],
172
- *,
173
- batch_size: int | None = None,
174
- max_tokens_per_batch: int | None = None,
175
- **kwargs: Any,
176
- ) -> NDArray[np.floating]:
177
- """Embed one or more texts.
178
-
179
- Args:
180
- texts: Single text or sequence of texts
181
- batch_size: Batch size for processing (deprecated, use max_tokens_per_batch)
182
- max_tokens_per_batch: Maximum estimated tokens per API call.
183
- If None, uses 90% of model's max_input_tokens for safety margin.
184
- **kwargs: Additional arguments for litellm.embedding()
185
-
186
- Returns:
187
- A numpy array of shape (n_texts, dimensions).
188
-
189
- Raises:
190
- Exception: If LiteLLM embedding fails
191
- """
192
- # Normalize input to list
193
- if isinstance(texts, str):
194
- text_list = [texts]
195
- else:
196
- text_list = list(texts)
197
-
198
- if not text_list:
199
- # Return empty array with correct shape
200
- return np.empty((0, self.dimensions), dtype=np.float32)
201
-
202
- # Merge kwargs
203
- embedding_kwargs = {**self._litellm_kwargs, **kwargs}
204
-
205
- # For OpenAI-compatible endpoints, ensure encoding_format is set
206
- if self._provider_config.api_base and "encoding_format" not in embedding_kwargs:
207
- embedding_kwargs["encoding_format"] = "float"
208
-
209
- # Determine adaptive max_tokens_per_batch
210
- # Use 90% of model's max_input_tokens as safety margin
211
- if max_tokens_per_batch is None:
212
- max_tokens_per_batch = int(self.max_input_tokens * 0.9)
213
- logger.debug(
214
- f"Using adaptive batch size: {max_tokens_per_batch} tokens "
215
- f"(90% of {self.max_input_tokens})"
216
- )
217
-
218
- # Split into token-aware batches
219
- batches = self._create_batches(text_list, max_tokens_per_batch)
220
-
221
- if len(batches) > 1:
222
- logger.info(f"Split {len(text_list)} texts into {len(batches)} batches for embedding")
223
-
224
- all_embeddings = []
225
-
226
- for batch_idx, batch in enumerate(batches):
227
- try:
228
- # Build call kwargs with explicit api_base
229
- call_kwargs = {**embedding_kwargs}
230
- if self._provider_config.api_base:
231
- call_kwargs["api_base"] = self._provider_config.api_base
232
- if self._provider_config.api_key:
233
- call_kwargs["api_key"] = self._provider_config.api_key
234
-
235
- # Call LiteLLM embedding for this batch
236
- response = litellm.embedding(
237
- model=self._format_model_name(),
238
- input=batch,
239
- **call_kwargs,
240
- )
241
-
242
- # Extract embeddings
243
- batch_embeddings = [item["embedding"] for item in response.data]
244
- all_embeddings.extend(batch_embeddings)
245
-
246
- except Exception as e:
247
- logger.error(f"LiteLLM embedding failed for batch {batch_idx + 1}/{len(batches)}: {e}")
248
- raise
249
-
250
- # Convert to numpy array
251
- result = np.array(all_embeddings, dtype=np.float32)
252
-
253
- # Validate dimensions
254
- if result.shape[1] != self.dimensions:
255
- logger.warning(
256
- f"Expected {self.dimensions} dimensions, got {result.shape[1]}. "
257
- f"Configuration may be incorrect."
258
- )
259
-
260
- return result
261
-
262
- @property
263
- def model_name(self) -> str:
264
- """Get configured model name."""
265
- return self._model_name
266
-
267
- @property
268
- def provider(self) -> str:
269
- """Get configured provider name."""
270
- return self._model_config.provider
@@ -1,198 +0,0 @@
1
- """LiteLLM client implementation for LLM operations."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import logging
7
- import os
8
- from typing import Any, Sequence
9
-
10
- import litellm
11
-
12
- from ..config import LiteLLMConfig, get_config
13
- from ..interfaces.llm import AbstractLLMClient, ChatMessage, LLMResponse
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- class LiteLLMClient(AbstractLLMClient):
19
- """LiteLLM client implementation.
20
-
21
- Supports multiple providers (OpenAI, Anthropic, etc.) through LiteLLM's unified interface.
22
-
23
- Example:
24
- client = LiteLLMClient(model="default")
25
- response = client.chat([
26
- ChatMessage(role="user", content="Hello!")
27
- ])
28
- print(response.content)
29
- """
30
-
31
- def __init__(
32
- self,
33
- model: str = "default",
34
- config: LiteLLMConfig | None = None,
35
- **litellm_kwargs: Any,
36
- ) -> None:
37
- """Initialize LiteLLM client.
38
-
39
- Args:
40
- model: Model name from configuration (default: "default")
41
- config: Configuration instance (default: use global config)
42
- **litellm_kwargs: Additional arguments to pass to litellm.completion()
43
- """
44
- self._config = config or get_config()
45
- self._model_name = model
46
- self._litellm_kwargs = litellm_kwargs
47
-
48
- # Get model configuration
49
- try:
50
- self._model_config = self._config.get_llm_model(model)
51
- except ValueError as e:
52
- logger.error(f"Failed to get model configuration: {e}")
53
- raise
54
-
55
- # Get provider configuration
56
- try:
57
- self._provider_config = self._config.get_provider(self._model_config.provider)
58
- except ValueError as e:
59
- logger.error(f"Failed to get provider configuration: {e}")
60
- raise
61
-
62
- # Set up LiteLLM environment
63
- self._setup_litellm()
64
-
65
- def _setup_litellm(self) -> None:
66
- """Configure LiteLLM with provider settings."""
67
- provider = self._model_config.provider
68
-
69
- # Set API key
70
- if self._provider_config.api_key:
71
- env_var = f"{provider.upper()}_API_KEY"
72
- litellm.api_key = self._provider_config.api_key
73
- # Also set environment-specific keys
74
- if provider == "openai":
75
- litellm.openai_key = self._provider_config.api_key
76
- elif provider == "anthropic":
77
- litellm.anthropic_key = self._provider_config.api_key
78
-
79
- # Set API base
80
- if self._provider_config.api_base:
81
- litellm.api_base = self._provider_config.api_base
82
-
83
- def _format_model_name(self) -> str:
84
- """Format model name for LiteLLM.
85
-
86
- Returns:
87
- Formatted model name (e.g., "gpt-4", "claude-3-opus-20240229")
88
- """
89
- # LiteLLM expects model names in format: "provider/model" or just "model"
90
- # If provider is explicit, use provider/model format
91
- provider = self._model_config.provider
92
- model = self._model_config.model
93
-
94
- # For some providers, LiteLLM expects explicit prefix
95
- if provider in ["anthropic", "azure", "vertex_ai", "bedrock"]:
96
- return f"{provider}/{model}"
97
-
98
- # If there's a custom api_base, use openai/ prefix to force OpenAI-compatible routing
99
- # This prevents LiteLLM from auto-detecting model provider from name
100
- # (e.g., "gemini-2.5-pro" would otherwise trigger Vertex AI auth)
101
- if self._provider_config.api_base:
102
- # Check if it's not the default OpenAI endpoint
103
- default_openai_bases = [
104
- "https://api.openai.com/v1",
105
- "https://api.openai.com",
106
- ]
107
- if self._provider_config.api_base not in default_openai_bases:
108
- return f"openai/{model}"
109
-
110
- return model
111
-
112
- def chat(
113
- self,
114
- messages: Sequence[ChatMessage],
115
- **kwargs: Any,
116
- ) -> LLMResponse:
117
- """Chat completion for a sequence of messages.
118
-
119
- Args:
120
- messages: Sequence of chat messages
121
- **kwargs: Additional arguments for litellm.completion()
122
-
123
- Returns:
124
- LLM response with content and raw response
125
-
126
- Raises:
127
- Exception: If LiteLLM completion fails
128
- """
129
- # Convert messages to LiteLLM format
130
- litellm_messages = [
131
- {"role": msg.role, "content": msg.content} for msg in messages
132
- ]
133
-
134
- # Merge kwargs
135
- completion_kwargs = {**self._litellm_kwargs, **kwargs}
136
-
137
- # Build extra_headers from multiple sources
138
- if "extra_headers" not in completion_kwargs:
139
- completion_kwargs["extra_headers"] = {}
140
-
141
- # 1. Load custom headers from environment variable (set by CCW)
142
- env_headers = os.environ.get("CCW_LITELLM_EXTRA_HEADERS")
143
- if env_headers:
144
- try:
145
- custom_headers = json.loads(env_headers)
146
- completion_kwargs["extra_headers"].update(custom_headers)
147
- except json.JSONDecodeError:
148
- logger.warning(f"Invalid JSON in CCW_LITELLM_EXTRA_HEADERS: {env_headers}")
149
-
150
- # 2. Override User-Agent to avoid being blocked by some API proxies
151
- # that detect and block OpenAI SDK's default User-Agent
152
- # This is a fallback - user can override via custom headers
153
- if "User-Agent" not in completion_kwargs["extra_headers"]:
154
- completion_kwargs["extra_headers"]["User-Agent"] = "python-httpx/0.27"
155
-
156
- try:
157
- # Call LiteLLM
158
- response = litellm.completion(
159
- model=self._format_model_name(),
160
- messages=litellm_messages,
161
- **completion_kwargs,
162
- )
163
-
164
- # Extract content
165
- content = response.choices[0].message.content or ""
166
-
167
- return LLMResponse(content=content, raw=response)
168
-
169
- except Exception as e:
170
- logger.error(f"LiteLLM completion failed: {e}")
171
- raise
172
-
173
- def complete(self, prompt: str, **kwargs: Any) -> LLMResponse:
174
- """Text completion for a prompt.
175
-
176
- Args:
177
- prompt: Input prompt
178
- **kwargs: Additional arguments for litellm.completion()
179
-
180
- Returns:
181
- LLM response with content and raw response
182
-
183
- Raises:
184
- Exception: If LiteLLM completion fails
185
- """
186
- # Convert to chat format (most modern models use chat interface)
187
- messages = [ChatMessage(role="user", content=prompt)]
188
- return self.chat(messages, **kwargs)
189
-
190
- @property
191
- def model_name(self) -> str:
192
- """Get configured model name."""
193
- return self._model_name
194
-
195
- @property
196
- def provider(self) -> str:
197
- """Get configured provider name."""
198
- return self._model_config.provider
@@ -1,22 +0,0 @@
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
-