mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-29 00:35:00 +00:00
fix: Improve model info caching and fallback logic
This commit is contained in:
parent
7ef1b21a3f
commit
b67914d74e
3 changed files with 24 additions and 100 deletions
|
@ -16,7 +16,6 @@ from PIL import Image
|
||||||
from aider import urls
|
from aider import urls
|
||||||
from aider.dump import dump # noqa: F401
|
from aider.dump import dump # noqa: F401
|
||||||
from aider.llm import AIDER_APP_NAME, AIDER_SITE_URL, litellm
|
from aider.llm import AIDER_APP_NAME, AIDER_SITE_URL, litellm
|
||||||
from aider.utils import safe_read_json, safe_write_json
|
|
||||||
|
|
||||||
DEFAULT_MODEL_NAME = "gpt-4o"
|
DEFAULT_MODEL_NAME = "gpt-4o"
|
||||||
ANTHROPIC_BETA_HEADER = "max-tokens-3-5-sonnet-2024-07-15,prompt-caching-2024-07-31"
|
ANTHROPIC_BETA_HEADER = "max-tokens-3-5-sonnet-2024-07-15,prompt-caching-2024-07-31"
|
||||||
|
@ -427,58 +426,44 @@ MODEL_SETTINGS = [
|
||||||
|
|
||||||
|
|
||||||
def get_model_info(model):
|
def get_model_info(model):
|
||||||
if litellm._lazy_module:
|
if not litellm._lazy_module:
|
||||||
# Do it the slow way...
|
cache_dir = Path.home() / ".aider" / "caches"
|
||||||
try:
|
cache_file = cache_dir / "model_prices_and_context_window.json"
|
||||||
return litellm.get_model_info(model)
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
except Exception:
|
|
||||||
return dict()
|
|
||||||
|
|
||||||
cache_dir = Path.home() / ".aider" / "caches"
|
current_time = time.time()
|
||||||
cache_file = cache_dir / "model_prices_and_context_window.json"
|
cache_age = (
|
||||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
current_time - cache_file.stat().st_mtime if cache_file.exists() else float("inf")
|
||||||
|
)
|
||||||
|
|
||||||
current_time = time.time()
|
if cache_age < 60 * 60 * 24:
|
||||||
cache_age = current_time - cache_file.stat().st_mtime if cache_file.exists() else float("inf")
|
try:
|
||||||
|
content = json.loads(cache_file.read_text())
|
||||||
if cache_file.exists() and cache_age < 86400: # 86400 seconds = 1 day
|
info = content.get(model, dict())
|
||||||
content = safe_read_json(cache_file)
|
|
||||||
if content:
|
|
||||||
info = content.get(model)
|
|
||||||
if info:
|
|
||||||
return info
|
return info
|
||||||
|
except Exception as ex:
|
||||||
|
print(str(ex))
|
||||||
|
|
||||||
# If cache doesn't exist or is old, fetch from GitHub
|
|
||||||
try:
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
url = (
|
url = (
|
||||||
"https://raw.githubusercontent.com/BerriAI/litellm/main/"
|
"https://raw.githubusercontent.com/BerriAI/litellm/main/"
|
||||||
"model_prices_and_context_window.json"
|
"model_prices_and_context_window.json"
|
||||||
)
|
)
|
||||||
response = requests.get(url, timeout=5)
|
|
||||||
if response.status_code == 200:
|
|
||||||
content = response.json()
|
|
||||||
safe_write_json(cache_file, content)
|
|
||||||
info = content.get(model)
|
|
||||||
if info:
|
|
||||||
return info
|
|
||||||
except Exception:
|
|
||||||
# If fetching from GitHub fails, fall back to local resource
|
|
||||||
try:
|
try:
|
||||||
with importlib.resources.open_text(
|
response = requests.get(url, timeout=5)
|
||||||
"litellm", "model_prices_and_context_window_backup.json"
|
if response.status_code == 200:
|
||||||
) as f:
|
content = response.json()
|
||||||
content = json.load(f)
|
cache_file.write_text(json.dumps(content, indent=4))
|
||||||
info = content.get(model)
|
info = content.get(model, dict())
|
||||||
if info:
|
return info
|
||||||
return info
|
except Exception as ex:
|
||||||
except Exception:
|
print(str(ex))
|
||||||
pass # If there's any error, fall back to the slow way
|
|
||||||
|
|
||||||
# If all else fails, do it the slow way...
|
# If all else fails, do it the slow way...
|
||||||
try:
|
try:
|
||||||
return litellm.get_model_info(model)
|
info = litellm.get_model_info(model)
|
||||||
|
return info
|
||||||
except Exception:
|
except Exception:
|
||||||
return dict()
|
return dict()
|
||||||
|
|
||||||
|
@ -490,7 +475,6 @@ class Model(ModelSettings):
|
||||||
self.weak_model = None
|
self.weak_model = None
|
||||||
|
|
||||||
self.info = self.get_model_info(model)
|
self.info = self.get_model_info(model)
|
||||||
dump(self.info)
|
|
||||||
|
|
||||||
# Are all needed keys/params available?
|
# Are all needed keys/params available?
|
||||||
res = self.validate_environment()
|
res = self.validate_environment()
|
||||||
|
|
|
@ -92,23 +92,6 @@ def is_image_file(file_name):
|
||||||
return any(file_name.endswith(ext) for ext in IMAGE_EXTENSIONS)
|
return any(file_name.endswith(ext) for ext in IMAGE_EXTENSIONS)
|
||||||
|
|
||||||
|
|
||||||
def safe_read_json(file_path):
|
|
||||||
try:
|
|
||||||
with open(file_path, "r") as f:
|
|
||||||
return json.load(f)
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def safe_write_json(file_path, data):
|
|
||||||
try:
|
|
||||||
with open(file_path, "w") as f:
|
|
||||||
json.dump(data, f)
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def safe_abs_path(res):
|
def safe_abs_path(res):
|
||||||
"Gives an abs path, which safely returns a full (not 8.3) windows path"
|
"Gives an abs path, which safely returns a full (not 8.3) windows path"
|
||||||
res = Path(res).resolve()
|
res = Path(res).resolve()
|
||||||
|
|
|
@ -29,49 +29,6 @@ class TestModels(unittest.TestCase):
|
||||||
model = Model("gpt-4-0613")
|
model = Model("gpt-4-0613")
|
||||||
self.assertEqual(model.info["max_input_tokens"], 8 * 1024)
|
self.assertEqual(model.info["max_input_tokens"], 8 * 1024)
|
||||||
|
|
||||||
@patch("aider.models.litellm._lazy_module", False)
|
|
||||||
@patch("aider.models.Path.home")
|
|
||||||
@patch("aider.models.Path.stat")
|
|
||||||
@patch("aider.models.safe_read_json")
|
|
||||||
@patch("aider.models.safe_write_json")
|
|
||||||
@patch("requests.get")
|
|
||||||
@patch("aider.models.Path.mkdir")
|
|
||||||
def test_get_model_info(
|
|
||||||
self, mock_mkdir, mock_get, mock_write_json, mock_read_json, mock_stat, mock_home
|
|
||||||
):
|
|
||||||
# Setup
|
|
||||||
mock_home.return_value = Path("/mock/home")
|
|
||||||
mock_stat.return_value = unittest.mock.Mock(st_mtime=time.time() - 86400 * 2) # 2 days old
|
|
||||||
mock_mkdir.return_value = None # Ensure mkdir doesn't raise an exception
|
|
||||||
|
|
||||||
# Test case 1: Cache exists and is fresh
|
|
||||||
mock_read_json.return_value = {"test_model": {"info": "cached"}}
|
|
||||||
mock_stat.return_value.st_mtime = time.time() - 3600 # 1 hour old
|
|
||||||
self.assertEqual(get_model_info("test_model"), {"info": "cached"})
|
|
||||||
|
|
||||||
# Test case 2: Cache doesn't exist or is old, GitHub fetch succeeds
|
|
||||||
mock_read_json.return_value = None
|
|
||||||
mock_get.return_value.status_code = 200
|
|
||||||
mock_get.return_value.json.return_value = {"test_model": {"info": "from_github"}}
|
|
||||||
self.assertEqual(get_model_info("test_model"), {"info": "from_github"})
|
|
||||||
|
|
||||||
# Test case 3: Cache doesn't exist, GitHub fetch fails, fallback to local resource
|
|
||||||
mock_get.return_value.status_code = 404
|
|
||||||
with patch("importlib.resources.open_text") as mock_open_text:
|
|
||||||
mock_open_text.return_value.__enter__.return_value.read.return_value = json.dumps(
|
|
||||||
{"test_model": {"info": "local_backup"}}
|
|
||||||
)
|
|
||||||
self.assertEqual(get_model_info("test_model"), {"info": "local_backup"})
|
|
||||||
|
|
||||||
# Test case 4: All previous methods fail, fallback to litellm.get_model_info
|
|
||||||
mock_open_text.side_effect = Exception("Resource not found")
|
|
||||||
with patch("aider.models.litellm.get_model_info") as mock_litellm_get_model_info:
|
|
||||||
mock_litellm_get_model_info.return_value = {"info": "from_litellm"}
|
|
||||||
self.assertEqual(get_model_info("test_model"), {"info": "from_litellm"})
|
|
||||||
|
|
||||||
# Test case 5: Everything fails
|
|
||||||
mock_litellm_get_model_info.side_effect = Exception("LiteLLM failed")
|
|
||||||
self.assertEqual(get_model_info("test_model"), {})
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue