mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-09 02:24:11 +08:00
feat: Enhance configuration management and embedding capabilities
- Added JSON-based settings management in Config class for embedding and LLM configurations. - Introduced methods to save and load settings from a JSON file. - Updated BaseEmbedder and its subclasses to include max_tokens property for better token management. - Enhanced chunking strategy to support recursive splitting of large symbols with improved overlap handling. - Implemented comprehensive tests for recursive splitting and chunking behavior. - Added CLI tools configuration management for better integration with external tools. - Introduced a new command for compacting session memory into structured text for recovery.
This commit is contained in:
@@ -81,7 +81,7 @@ class LiteLLMEmbedder(AbstractEmbedder):
|
||||
"""Format model name for LiteLLM.
|
||||
|
||||
Returns:
|
||||
Formatted model name (e.g., "text-embedding-3-small")
|
||||
Formatted model name (e.g., "openai/text-embedding-3-small")
|
||||
"""
|
||||
provider = self._model_config.provider
|
||||
model = self._model_config.model
|
||||
@@ -90,6 +90,11 @@ class LiteLLMEmbedder(AbstractEmbedder):
|
||||
if provider in ["azure", "vertex_ai", "bedrock"]:
|
||||
return f"{provider}/{model}"
|
||||
|
||||
# For providers with custom api_base (OpenAI-compatible endpoints),
|
||||
# use openai/ prefix to tell LiteLLM to use OpenAI API format
|
||||
if self._provider_config.api_base and provider not in ["openai", "anthropic"]:
|
||||
return f"openai/{model}"
|
||||
|
||||
return model
|
||||
|
||||
@property
|
||||
@@ -133,6 +138,10 @@ class LiteLLMEmbedder(AbstractEmbedder):
|
||||
embedding_kwargs = {**self._litellm_kwargs, **kwargs}
|
||||
|
||||
try:
|
||||
# For OpenAI-compatible endpoints, ensure encoding_format is set
|
||||
if self._provider_config.api_base and "encoding_format" not in embedding_kwargs:
|
||||
embedding_kwargs["encoding_format"] = "float"
|
||||
|
||||
# Call LiteLLM embedding
|
||||
response = litellm.embedding(
|
||||
model=self._format_model_name(),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
@@ -11,8 +12,12 @@ import yaml
|
||||
|
||||
from .models import LiteLLMConfig
|
||||
|
||||
# Default configuration path
|
||||
DEFAULT_CONFIG_PATH = Path.home() / ".ccw" / "config" / "litellm-config.yaml"
|
||||
# Default configuration paths
|
||||
# JSON format (UI config) takes priority over YAML format
|
||||
DEFAULT_JSON_CONFIG_PATH = Path.home() / ".ccw" / "config" / "litellm-api-config.json"
|
||||
DEFAULT_YAML_CONFIG_PATH = Path.home() / ".ccw" / "config" / "litellm-config.yaml"
|
||||
# Keep backward compatibility
|
||||
DEFAULT_CONFIG_PATH = DEFAULT_YAML_CONFIG_PATH
|
||||
|
||||
# Global configuration singleton
|
||||
_config_instance: LiteLLMConfig | None = None
|
||||
@@ -84,11 +89,147 @@ def _get_default_config() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def load_config(config_path: Path | str | None = None) -> LiteLLMConfig:
|
||||
"""Load LiteLLM configuration from YAML file.
|
||||
def _convert_json_to_internal_format(json_config: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Convert UI JSON config format to internal format.
|
||||
|
||||
The UI stores config in a different structure:
|
||||
- providers: array of {id, name, type, apiKey, apiBase, llmModels[], embeddingModels[]}
|
||||
|
||||
Internal format uses:
|
||||
- providers: dict of {provider_id: {api_key, api_base}}
|
||||
- llm_models: dict of {model_id: {provider, model}}
|
||||
- embedding_models: dict of {model_id: {provider, model, dimensions}}
|
||||
|
||||
Args:
|
||||
config_path: Path to configuration file (default: ~/.ccw/config/litellm-config.yaml)
|
||||
json_config: Configuration in UI JSON format
|
||||
|
||||
Returns:
|
||||
Configuration in internal format
|
||||
"""
|
||||
providers: dict[str, Any] = {}
|
||||
llm_models: dict[str, Any] = {}
|
||||
embedding_models: dict[str, Any] = {}
|
||||
default_provider: str | None = None
|
||||
|
||||
for provider in json_config.get("providers", []):
|
||||
if not provider.get("enabled", True):
|
||||
continue
|
||||
|
||||
provider_id = provider.get("id", "")
|
||||
if not provider_id:
|
||||
continue
|
||||
|
||||
# Set first enabled provider as default
|
||||
if default_provider is None:
|
||||
default_provider = provider_id
|
||||
|
||||
# Convert provider with advanced settings
|
||||
provider_config: dict[str, Any] = {
|
||||
"api_key": provider.get("apiKey", ""),
|
||||
"api_base": provider.get("apiBase"),
|
||||
}
|
||||
|
||||
# Map advanced settings
|
||||
adv = provider.get("advancedSettings", {})
|
||||
if adv.get("timeout"):
|
||||
provider_config["timeout"] = adv["timeout"]
|
||||
if adv.get("maxRetries"):
|
||||
provider_config["max_retries"] = adv["maxRetries"]
|
||||
if adv.get("organization"):
|
||||
provider_config["organization"] = adv["organization"]
|
||||
if adv.get("apiVersion"):
|
||||
provider_config["api_version"] = adv["apiVersion"]
|
||||
if adv.get("customHeaders"):
|
||||
provider_config["custom_headers"] = adv["customHeaders"]
|
||||
|
||||
providers[provider_id] = provider_config
|
||||
|
||||
# Convert LLM models
|
||||
for model in provider.get("llmModels", []):
|
||||
if not model.get("enabled", True):
|
||||
continue
|
||||
model_id = model.get("id", "")
|
||||
if not model_id:
|
||||
continue
|
||||
|
||||
llm_model_config: dict[str, Any] = {
|
||||
"provider": provider_id,
|
||||
"model": model.get("name", ""),
|
||||
}
|
||||
# Add model-specific endpoint settings
|
||||
endpoint = model.get("endpointSettings", {})
|
||||
if endpoint.get("baseUrl"):
|
||||
llm_model_config["api_base"] = endpoint["baseUrl"]
|
||||
if endpoint.get("timeout"):
|
||||
llm_model_config["timeout"] = endpoint["timeout"]
|
||||
if endpoint.get("maxRetries"):
|
||||
llm_model_config["max_retries"] = endpoint["maxRetries"]
|
||||
|
||||
# Add capabilities
|
||||
caps = model.get("capabilities", {})
|
||||
if caps.get("contextWindow"):
|
||||
llm_model_config["context_window"] = caps["contextWindow"]
|
||||
if caps.get("maxOutputTokens"):
|
||||
llm_model_config["max_output_tokens"] = caps["maxOutputTokens"]
|
||||
|
||||
llm_models[model_id] = llm_model_config
|
||||
|
||||
# Convert embedding models
|
||||
for model in provider.get("embeddingModels", []):
|
||||
if not model.get("enabled", True):
|
||||
continue
|
||||
model_id = model.get("id", "")
|
||||
if not model_id:
|
||||
continue
|
||||
|
||||
embedding_model_config: dict[str, Any] = {
|
||||
"provider": provider_id,
|
||||
"model": model.get("name", ""),
|
||||
"dimensions": model.get("capabilities", {}).get("embeddingDimension", 1536),
|
||||
}
|
||||
# Add model-specific endpoint settings
|
||||
endpoint = model.get("endpointSettings", {})
|
||||
if endpoint.get("baseUrl"):
|
||||
embedding_model_config["api_base"] = endpoint["baseUrl"]
|
||||
if endpoint.get("timeout"):
|
||||
embedding_model_config["timeout"] = endpoint["timeout"]
|
||||
|
||||
embedding_models[model_id] = embedding_model_config
|
||||
|
||||
# Ensure we have defaults if no models found
|
||||
if not llm_models:
|
||||
llm_models["default"] = {
|
||||
"provider": default_provider or "openai",
|
||||
"model": "gpt-4",
|
||||
}
|
||||
|
||||
if not embedding_models:
|
||||
embedding_models["default"] = {
|
||||
"provider": default_provider or "openai",
|
||||
"model": "text-embedding-3-small",
|
||||
"dimensions": 1536,
|
||||
}
|
||||
|
||||
return {
|
||||
"version": json_config.get("version", 1),
|
||||
"default_provider": default_provider or "openai",
|
||||
"providers": providers,
|
||||
"llm_models": llm_models,
|
||||
"embedding_models": embedding_models,
|
||||
}
|
||||
|
||||
|
||||
def load_config(config_path: Path | str | None = None) -> LiteLLMConfig:
|
||||
"""Load LiteLLM configuration from JSON or YAML file.
|
||||
|
||||
Priority order:
|
||||
1. Explicit config_path if provided
|
||||
2. JSON config (UI format): ~/.ccw/config/litellm-api-config.json
|
||||
3. YAML config: ~/.ccw/config/litellm-config.yaml
|
||||
4. Default configuration
|
||||
|
||||
Args:
|
||||
config_path: Path to configuration file (optional)
|
||||
|
||||
Returns:
|
||||
Parsed and validated configuration
|
||||
@@ -97,22 +238,47 @@ def load_config(config_path: Path | str | None = None) -> LiteLLMConfig:
|
||||
FileNotFoundError: If config file not found and no default available
|
||||
ValueError: If configuration is invalid
|
||||
"""
|
||||
if config_path is None:
|
||||
config_path = DEFAULT_CONFIG_PATH
|
||||
else:
|
||||
config_path = Path(config_path)
|
||||
raw_config: dict[str, Any] | None = None
|
||||
is_json_format = False
|
||||
|
||||
# Load configuration
|
||||
if config_path.exists():
|
||||
if config_path is not None:
|
||||
config_path = Path(config_path)
|
||||
if config_path.exists():
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
if config_path.suffix == ".json":
|
||||
raw_config = json.load(f)
|
||||
is_json_format = True
|
||||
else:
|
||||
raw_config = yaml.safe_load(f)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to load configuration from {config_path}: {e}") from e
|
||||
|
||||
# Check JSON config first (UI format)
|
||||
if raw_config is None and DEFAULT_JSON_CONFIG_PATH.exists():
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
with open(DEFAULT_JSON_CONFIG_PATH, "r", encoding="utf-8") as f:
|
||||
raw_config = json.load(f)
|
||||
is_json_format = True
|
||||
except Exception:
|
||||
pass # Fall through to YAML
|
||||
|
||||
# Check YAML config
|
||||
if raw_config is None and DEFAULT_YAML_CONFIG_PATH.exists():
|
||||
try:
|
||||
with open(DEFAULT_YAML_CONFIG_PATH, "r", encoding="utf-8") as f:
|
||||
raw_config = yaml.safe_load(f)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to load configuration from {config_path}: {e}") from e
|
||||
else:
|
||||
# Use default configuration
|
||||
except Exception:
|
||||
pass # Fall through to default
|
||||
|
||||
# Use default configuration
|
||||
if raw_config is None:
|
||||
raw_config = _get_default_config()
|
||||
|
||||
# Convert JSON format to internal format if needed
|
||||
if is_json_format:
|
||||
raw_config = _convert_json_to_internal_format(raw_config)
|
||||
|
||||
# Substitute environment variables
|
||||
config_data = _substitute_env_vars(raw_config)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user