feat(backend): Migrate old models in existing agents (#9452)

Some existing nodes use models that no longer exist as values on
`LlmModel` enum.

### Changes 🏗️

- Update models for all blocks with `LlmModel` fields that do not exist
in `LlmModel` enum to `gpt-4o`, directly in `AgentNode->constantInput`
db column, on server startup

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] Updates wrong models to `gpt-4o` for all affected `AgentNode`s
  - [x] Doesn't update correct models
  - [x] Doesn't insert model when unnecessary
  - [x] Doesn't break other values in jsonb
This commit is contained in:
Krzysztof Czerwinski
2025-03-25 10:38:11 +01:00
committed by GitHub
parent f0df4c9174
commit b7ca8d9c30
2 changed files with 41 additions and 0 deletions

View File

@@ -19,6 +19,8 @@ from pydantic.fields import Field, computed_field
from backend.blocks.agent import AgentExecutorBlock
from backend.blocks.io import AgentInputBlock, AgentOutputBlock
from backend.blocks.llm import LlmModel
from backend.data.db import prisma as db
from backend.util import type as type_utils
from .block import Block, BlockInput, BlockSchema, BlockType, get_block, get_blocks
@@ -1026,3 +1028,40 @@ async def fix_llm_provider_credentials():
where={"id": node_id},
data={"constantInput": Json(node_preset_input)},
)
async def migrate_llm_models(migrate_to: LlmModel):
"""
Update all LLM models in all AI blocks that don't exist in the enum.
Note: Only updates top level LlmModel SchemaFields of blocks (won't update nested fields).
"""
logger.info("Migrating LLM models")
# Scan all blocks and search for LlmModel fields
llm_model_fields: dict[str, str] = {} # {block_id: field_name}
# Search for all LlmModel fields
for block_type in get_blocks().values():
block = block_type()
from pydantic.fields import FieldInfo
fields: dict[str, FieldInfo] = block.input_schema.model_fields
# Collect top-level LlmModel fields
for field_name, field in fields.items():
if field.annotation == LlmModel:
llm_model_fields[block.id] = field_name
# Update each block
for id, path in llm_model_fields.items():
# Convert enum values to a list of strings for the SQL query
enum_values = [v.value for v in LlmModel.__members__.values()]
query = f"""
UPDATE "AgentNode"
SET "constantInput" = jsonb_set("constantInput", '{{{path}}}', '"{migrate_to.value}"', true)
WHERE "agentBlockId" = '{id}'
AND "constantInput" ? '{path}'
AND "constantInput"->>'{path}' NOT IN ({','.join(f"'{value}'" for value in enum_values)})
"""
await db.execute_raw(query)

View File

@@ -28,6 +28,7 @@ import backend.server.v2.store.model
import backend.server.v2.store.routes
import backend.util.service
import backend.util.settings
from backend.blocks.llm import LlmModel
from backend.data.model import Credentials
from backend.integrations.providers import ProviderName
from backend.server.external.api import external_app
@@ -56,6 +57,7 @@ async def lifespan_context(app: fastapi.FastAPI):
await backend.data.block.initialize_blocks()
await backend.data.user.migrate_and_encrypt_user_integrations()
await backend.data.graph.fix_llm_provider_credentials()
await backend.data.graph.migrate_llm_models(LlmModel.GPT4O)
with launch_darkly_context():
yield
await backend.data.db.disconnect()