mirror of
https://github.com/langgenius/dify.git
synced 2026-06-01 06:28:14 +08:00
The planner had no idea which tools the tenant actually has installed, so ``tool`` nodes were generated with hallucinated provider/tool names that failed at draft sync. Add a tenant-scoped catalogue helper and pipe its output into both planner and builder prompts. - core/workflow/generator/tool_catalogue.py: enumerate hardcoded built-in providers plus plugin providers (via ToolManager.list_builtin_providers which already covers both), pull provider_name + tool_name + label + llm-facing description, cap to 80 entries to keep the prompt bounded, and render as a compact one-tool-per-line block. Per-provider failures are logged and skipped so one bad plugin can't kill generation. - prompts/planner_prompts.py + prompts/builder_prompts.py: new optional catalogue section. Planner is told to pick concrete provider/tool identifiers from the list; builder is told to set provider_id / provider_name / tool_name to entries that actually exist. - runner.py: thread the catalogue text through generate_workflow_graph → _run_planner / _run_builder. New param defaults to "" so unit tests and tool-less tenants still work unchanged. - services/workflow_generator_service.py: build + format the catalogue for the calling tenant. Catalogue build failures (plugin daemon down, etc.) are logged and downgraded to "no catalogue" — never block generation outright. - 5 new unit tests cover the catalogue formatter (empty/short/long descriptions, label dedupe, newline stripping); the existing 7 runner tests keep passing thanks to the default-empty param. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83 lines
3.1 KiB
Python
83 lines
3.1 KiB
Python
"""
|
|
Workflow generator service.
|
|
|
|
Thin facade over ``core.workflow.generator.WorkflowGenerator`` that owns the
|
|
model-manager / model-instance plumbing. Controllers call this; the pure
|
|
domain class never touches the model registry directly.
|
|
|
|
Pattern mirrors ``LLMGenerator.generate_rule_config`` — see
|
|
``core/llm_generator/llm_generator.py`` — but lives in ``services/`` because
|
|
the generator output is consumed at the application layer (sync_draft_workflow,
|
|
createApp) rather than from inside another workflow.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from core.app.app_config.entities import ModelConfig
|
|
from core.model_manager import ModelManager
|
|
from core.workflow.generator import WorkflowGenerator
|
|
from core.workflow.generator.tool_catalogue import build_tool_catalogue, format_tool_catalogue
|
|
from core.workflow.generator.types import WorkflowGenerateResultDict, WorkflowGenerationMode
|
|
from graphon.model_runtime.entities.model_entities import ModelType
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class WorkflowGeneratorService:
|
|
"""
|
|
Coordinates model resolution with the workflow generator domain logic.
|
|
|
|
Single public method (``generate_workflow_graph``) keeps the surface area
|
|
minimal — the cmd+k `/create` flow is the only caller today.
|
|
"""
|
|
|
|
@classmethod
|
|
def generate_workflow_graph(
|
|
cls,
|
|
*,
|
|
tenant_id: str,
|
|
mode: WorkflowGenerationMode,
|
|
instruction: str,
|
|
model_config: ModelConfig,
|
|
ideal_output: str = "",
|
|
) -> WorkflowGenerateResultDict:
|
|
"""
|
|
Resolve a model instance for the tenant and run the generator.
|
|
|
|
Errors from the LLM call (auth, quota, invoke) propagate so the
|
|
controller can map them to existing HTTP error envelopes (same
|
|
envelope as ``/rule-generate``).
|
|
"""
|
|
model_manager = ModelManager.for_tenant(tenant_id=tenant_id)
|
|
model_instance = model_manager.get_model_instance(
|
|
tenant_id=tenant_id,
|
|
model_type=ModelType.LLM,
|
|
provider=model_config.provider,
|
|
model=model_config.name,
|
|
)
|
|
|
|
model_parameters: dict[str, Any] = dict(model_config.completion_params or {})
|
|
|
|
# Build the installed-tool catalogue for this tenant so the planner/
|
|
# builder can pick concrete tools instead of inventing names. A failure
|
|
# here (plugin daemon unreachable, etc.) must not block generation —
|
|
# log and fall back to the no-tool catalogue path.
|
|
try:
|
|
tool_catalogue_text = format_tool_catalogue(build_tool_catalogue(tenant_id))
|
|
except Exception:
|
|
logger.exception("Workflow generator: failed to build tool catalogue for tenant %s", tenant_id)
|
|
tool_catalogue_text = ""
|
|
|
|
return WorkflowGenerator.generate_workflow_graph(
|
|
model_instance=model_instance,
|
|
model_parameters=model_parameters,
|
|
provider=model_config.provider,
|
|
model_name=model_config.name,
|
|
model_mode=str(model_config.mode),
|
|
mode=mode,
|
|
instruction=instruction,
|
|
ideal_output=ideal_output,
|
|
tool_catalogue_text=tool_catalogue_text,
|
|
)
|