diff --git a/api/services/agent/dependency_service.py b/api/services/agent/dependency_service.py index 9af0b0ff6ac..b523cb3d039 100644 --- a/api/services/agent/dependency_service.py +++ b/api/services/agent/dependency_service.py @@ -1,9 +1,46 @@ """Plugin dependency discovery shared by portable Agent formats.""" -from models.agent_config_entities import AgentSoulConfig +from models.agent_config_entities import ( + AgentKnowledgeMetadataFilteringConfig, + AgentKnowledgeRetrievalConfig, + AgentSoulConfig, +) from services.plugin.dependencies_analysis import DependenciesAnalysisService +def _extract_knowledge_retrieval_dependencies(retrieval: AgentKnowledgeRetrievalConfig) -> list[str]: + if retrieval.mode == "single": + if retrieval.model is None: + return [] + return [DependenciesAnalysisService.analyze_model_provider_dependency(retrieval.model.provider)] + + if not retrieval.reranking_enable: + return [] + + if retrieval.reranking_mode == "reranking_model": + if retrieval.reranking_model is None: + return [] + return [DependenciesAnalysisService.analyze_model_provider_dependency(retrieval.reranking_model.provider)] + + if retrieval.reranking_mode == "weighted_score" and retrieval.weights is not None: + vector_setting = retrieval.weights.vector_setting + embedding_provider = vector_setting.get("embedding_provider_name") if vector_setting is not None else None + if embedding_provider: + return [DependenciesAnalysisService.analyze_model_provider_dependency(embedding_provider)] + + return [] + + +def _extract_metadata_filtering_dependencies( + metadata_filtering: AgentKnowledgeMetadataFilteringConfig, +) -> list[str]: + if metadata_filtering.mode != "automatic" or metadata_filtering.metadata_model_config is None: + return [] + return [ + DependenciesAnalysisService.analyze_model_provider_dependency(metadata_filtering.metadata_model_config.provider) + ] + + def extract_agent_soul_dependencies(soul: AgentSoulConfig) -> list[str]: dependencies: list[str] = [] if soul.model is not None: @@ -20,13 +57,8 @@ def extract_agent_soul_dependencies(soul: AgentSoulConfig) -> list[str]: if provider_id: dependencies.append(DependenciesAnalysisService.analyze_tool_dependency(provider_id)) for knowledge_set in soul.knowledge.sets: - retrieval = knowledge_set.retrieval - if retrieval.model is not None: - dependencies.append(DependenciesAnalysisService.analyze_model_provider_dependency(retrieval.model.provider)) - if retrieval.reranking_model is not None: - dependencies.append( - DependenciesAnalysisService.analyze_model_provider_dependency(retrieval.reranking_model.provider) - ) + dependencies.extend(_extract_knowledge_retrieval_dependencies(knowledge_set.retrieval)) + dependencies.extend(_extract_metadata_filtering_dependencies(knowledge_set.metadata_filtering)) return dependencies diff --git a/api/tests/unit_tests/services/agent/test_agent_dsl_service.py b/api/tests/unit_tests/services/agent/test_agent_dsl_service.py index b9c5664fa0f..f14b2b9fc62 100644 --- a/api/tests/unit_tests/services/agent/test_agent_dsl_service.py +++ b/api/tests/unit_tests/services/agent/test_agent_dsl_service.py @@ -573,9 +573,49 @@ def test_extract_package_dependencies_covers_models_features_tools_and_knowledge "retrieval": { "mode": "single", "model": {"provider": "provider/retrieval", "name": "embed", "mode": "embedding"}, + }, + "metadata_filtering": { + "mode": "automatic", + "model_config": { + "provider": "provider/metadata", + "name": "metadata-model", + "mode": "chat", + }, + }, + }, + { + "id": "set-2", + "name": "Reranked Set", + "datasets": [{"id": "dataset-2", "name": "Reranked Docs"}], + "query": {"mode": "user_query", "value": "query"}, + "retrieval": { + "mode": "multiple", + "top_k": 5, + "reranking_enable": True, + "reranking_mode": "reranking_model", "reranking_model": {"provider": "provider/rerank", "model": "rerank"}, }, - } + }, + { + "id": "set-3", + "name": "Weighted Set", + "datasets": [{"id": "dataset-3", "name": "Weighted Docs"}], + "query": {"mode": "user_query", "value": "query"}, + "retrieval": { + "mode": "multiple", + "top_k": 5, + "reranking_enable": True, + "reranking_mode": "weighted_score", + "weights": { + "vector_setting": { + "embedding_provider_name": "provider/embedding", + "embedding_model_name": "embedding-model", + "vector_weight": 0.7, + }, + "keyword_setting": {"keyword_weight": 0.3}, + }, + }, + }, ] }, } @@ -591,10 +631,68 @@ def test_extract_package_dependencies_covers_models_features_tools_and_knowledge "tool:provider/tool", "tool:plugin-id/fallback-provider", "model:provider/retrieval", + "model:provider/metadata", "model:provider/rerank", + "model:provider/embedding", ] +def test_extract_package_dependencies_ignores_inactive_knowledge_models( + monkeypatch: pytest.MonkeyPatch, unbound_session: Session +) -> None: + model_dependency = Mock(side_effect=lambda provider: f"model:{provider}") + monkeypatch.setattr( + "services.agent.dependency_service.DependenciesAnalysisService.analyze_model_provider_dependency", + model_dependency, + ) + soul = AgentSoulConfig.model_validate( + { + "knowledge": { + "sets": [ + { + "id": "set-1", + "name": "Set", + "datasets": [{"id": "dataset-1", "name": "Docs"}], + "query": {"mode": "user_query", "value": "query"}, + "retrieval": { + "mode": "multiple", + "top_k": 5, + "reranking_enable": False, + "reranking_mode": "weighted_score", + "reranking_model": {"provider": "provider/inactive-rerank", "model": "rerank"}, + "weights": { + "vector_setting": { + "embedding_provider_name": "provider/inactive-embedding", + } + }, + "model": { + "provider": "provider/inactive-retrieval", + "name": "retrieval-model", + "mode": "chat", + }, + }, + "metadata_filtering": { + "mode": "disabled", + "model_config": { + "provider": "provider/inactive-metadata", + "name": "metadata-model", + "mode": "chat", + }, + }, + } + ] + } + } + ) + + dependencies = AgentDslService(unbound_session).extract_package_dependencies( + {"agent_1": make_portable_agent_package(_agent(), soul)} + ) + + assert dependencies == [] + model_dependency.assert_not_called() + + def test_create_imported_inline_agent_uses_import_provenance(unbound_session: Session) -> None: service = AgentDslService(unbound_session) soul = AgentSoulConfig(config_note="inline")