feat: refactor node spec and add mcp tools (#244)

* refactor: carve out extraction panel

* refactor: create spec versions for node types

* refactor: create a GenericNode and remove custom nodes

* feat: add python and typescript sdk

* add dograh sdk

* fix: fetch draft workflow definition over published one

* fix: fix routes of SDKs to use code gen

* chore: remove doclink dependency to reduce image size

* chore: format files

* chore: bump pipecat

* feat: let mcp fetch archived workflows on demand

* chore: fix tests

* feat: add sdk documentation

* chore: change banner and add badge
This commit is contained in:
Abhishek 2026-04-21 07:56:16 +05:30 committed by GitHub
parent 0a61ef295f
commit 00a1a22b74
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
162 changed files with 14355 additions and 3554 deletions

View file

@ -8,6 +8,7 @@ from loguru import logger
from api.db.models import WorkflowRunModel
from api.services.gen_ai.json_parser import parse_llm_json
from api.services.pipecat.service_factory import create_llm_service_from_provider
from api.services.workflow.dto import QANodeData
from api.services.workflow.qa.conversation import (
build_conversation_structure,
format_transcript,
@ -77,7 +78,7 @@ async def _generate_conversation_summary(
async def run_per_node_qa_analysis(
qa_node_data: dict[str, Any],
qa_data: QANodeData,
workflow_run: WorkflowRunModel,
workflow_run_id: int,
workflow_definition: dict,
@ -106,18 +107,16 @@ async def run_per_node_qa_analysis(
logger.info(
f"Events lack node_id for run {workflow_run_id}, falling back to whole-call QA"
)
return await _run_whole_call_qa_analysis(
qa_node_data, workflow_run, workflow_run_id
)
return await _run_whole_call_qa_analysis(qa_data, workflow_run, workflow_run_id)
system_prompt = qa_node_data.get("qa_system_prompt", "")
system_prompt = qa_data.qa_system_prompt or ""
if not system_prompt:
logger.warning("No system prompt defined for QA Node")
return {"error": "no_system_prompt", "node_results": {}}
# Resolve LLM config
provider, model, api_key, service_kwargs = await resolve_llm_config(
qa_node_data, workflow_run
qa_data, workflow_run
)
if not api_key:
logger.warning(
@ -127,7 +126,7 @@ async def run_per_node_qa_analysis(
# Ensure node summaries
node_summaries = await ensure_node_summaries(
workflow_definition, definition_id, workflow_run, qa_node_data
workflow_definition, definition_id, workflow_run, qa_data
)
# Set up Langfuse tracing
@ -228,7 +227,7 @@ async def run_per_node_qa_analysis(
async def _run_whole_call_qa_analysis(
qa_node_data: dict[str, Any],
qa_data: QANodeData,
workflow_run: WorkflowRunModel,
workflow_run_id: int,
) -> dict[str, Any]:
@ -254,13 +253,13 @@ async def _run_whole_call_qa_analysis(
metrics = compute_call_metrics(rtf_events, call_duration)
# Resolve LLM config
system_prompt = qa_node_data.get("qa_system_prompt", "")
system_prompt = qa_data.qa_system_prompt or ""
if not system_prompt:
logger.warning("No system prompt defined for QA Node")
return {"error": "no_system_prompt", "node_results": {}}
provider, model, api_key, service_kwargs = await resolve_llm_config(
qa_node_data, workflow_run
qa_data, workflow_run
)
if not api_key:

View file

@ -4,10 +4,11 @@ import random
from api.db import db_client
from api.db.models import WorkflowRunModel
from api.services.workflow.dto import QANodeData
async def resolve_llm_config(
qa_node_data: dict, workflow_run: WorkflowRunModel
qa_data: QANodeData, workflow_run: WorkflowRunModel
) -> tuple[str, str, str, dict]:
"""Resolve the LLM provider, model, API key, and extra kwargs for QA analysis.
@ -18,24 +19,23 @@ async def resolve_llm_config(
(provider, model, api_key, service_kwargs) tuple service_kwargs can be
passed directly to create_llm_service_from_provider as keyword arguments.
"""
if not qa_node_data.get("qa_use_workflow_llm", True):
provider = qa_node_data.get("qa_provider", "openai")
if not qa_data.qa_use_workflow_llm:
provider = qa_data.qa_provider or "openai"
kwargs = {}
if provider == "azure":
kwargs["endpoint"] = qa_node_data.get("qa_endpoint", "")
kwargs["endpoint"] = qa_data.qa_endpoint or ""
return (
provider,
qa_node_data.get("qa_model"),
qa_node_data.get("qa_api_key"),
qa_data.qa_model,
qa_data.qa_api_key,
kwargs,
)
# Fall back to user's configured LLM
provider, model, api_key, kwargs = await resolve_user_llm_config(workflow_run)
qa_model = qa_node_data.get("qa_model", "default")
if qa_model and qa_model != "default":
model = qa_model
if qa_data.qa_model and qa_data.qa_model != "default":
model = qa_data.qa_model
return provider, model, api_key, kwargs

View file

@ -7,7 +7,7 @@ from loguru import logger
from api.db import db_client
from api.db.models import WorkflowRunModel
from api.services.pipecat.service_factory import create_llm_service_from_provider
from api.services.workflow.dto import NodeType
from api.services.workflow.dto import NodeType, QANodeData
from api.services.workflow.qa.llm_config import resolve_llm_config
from api.services.workflow.qa.tracing import create_node_summary_trace
from pipecat.processors.aggregators.llm_context import LLMContext
@ -48,7 +48,7 @@ async def ensure_node_summaries(
workflow_definition: dict,
definition_id: int | None,
workflow_run: WorkflowRunModel,
qa_node_data: dict,
qa_data: QANodeData,
) -> dict[str, Any]:
"""Ensure every agentNode/startCall node has a summary in the definition.
@ -69,7 +69,7 @@ async def ensure_node_summaries(
return existing_summaries
provider, model, api_key, service_kwargs = await resolve_llm_config(
qa_node_data, workflow_run
qa_data, workflow_run
)
if not api_key:
logger.warning("No API key for node summary generation, skipping")