mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-06 23:49:24 +00:00
fix(checkpoint): rebuild custom BaseLLM as concrete LLM on restore
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
A custom BaseLLM subclass serializes with the inherited llm_type "base", which the registry maps to the abstract BaseLLM. Restore then crashed on cls(**value). Rebuild a concrete LLM from the saved config when the resolved class is abstract.
This commit is contained in:
@@ -82,6 +82,7 @@ _LLM_TYPE_REGISTRY: dict[str, str] = {
|
||||
def _validate_llm_ref(value: Any) -> Any:
|
||||
if isinstance(value, dict):
|
||||
import importlib
|
||||
import inspect
|
||||
|
||||
llm_type = value.get("llm_type")
|
||||
if not llm_type or llm_type not in _LLM_TYPE_REGISTRY:
|
||||
@@ -92,6 +93,12 @@ def _validate_llm_ref(value: Any) -> Any:
|
||||
dotted = _LLM_TYPE_REGISTRY[llm_type]
|
||||
mod_path, cls_name = dotted.rsplit(".", 1)
|
||||
cls = getattr(importlib.import_module(mod_path), cls_name)
|
||||
if inspect.isabstract(cls):
|
||||
from crewai.llm import LLM
|
||||
|
||||
return LLM(
|
||||
**{k: v for k, v in value.items() if v is not None and k != "llm_type"}
|
||||
)
|
||||
return cls(**value)
|
||||
return value
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
@@ -742,3 +743,26 @@ class TestCheckpointReusedExecutor:
|
||||
|
||||
assert len(result.tasks_output) == 2
|
||||
assert result.tasks_output[1].raw
|
||||
|
||||
|
||||
class TestCustomLLMCheckpointRestore:
|
||||
"""A custom BaseLLM subclass serializes with the inherited llm_type "base".
|
||||
|
||||
Restoring it must not try to instantiate the abstract BaseLLM; it is rebuilt
|
||||
as a concrete LLM from the saved config instead.
|
||||
"""
|
||||
|
||||
def test_restore_does_not_instantiate_abstract_base_llm(self) -> None:
|
||||
agent = Agent(role="r", goal="g", backstory="b", llm=_FinalAnswerLLM())
|
||||
task = Task(description="d", expected_output="e", agent=agent)
|
||||
crew = Crew(agents=[agent], tasks=[task], verbose=False)
|
||||
|
||||
raw = RuntimeState(root=[crew]).model_dump_json()
|
||||
restored = RuntimeState.model_validate_json(
|
||||
raw, context={"from_checkpoint": True}
|
||||
)
|
||||
|
||||
llm = restored.root[0].agents[0].llm
|
||||
assert isinstance(llm, BaseLLM)
|
||||
assert not inspect.isabstract(type(llm))
|
||||
assert llm.model == "stub"
|
||||
|
||||
Reference in New Issue
Block a user