mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-10 11:12:13 +02:00
fix: fix org scoped access for resources (#517)
* fix: fix org scoped access for resources * Fix auth and config validation regressions * fix: track org config validation timestamp * fix: backfill org model configuration v2 from legacy user rows Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: align config tests with org-level v2 resolution Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: helm example values tweaks Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
041c31a613
commit
fb4038a969
59 changed files with 3531 additions and 517 deletions
|
|
@ -288,6 +288,7 @@ class CampaignCallDispatcher:
|
|||
initial_context=initial_context,
|
||||
campaign_id=campaign.id,
|
||||
queued_run_id=queued_run.id, # Link to queued run for retry tracking
|
||||
organization_id=campaign.organization_id,
|
||||
)
|
||||
await call_concurrency.bind_workflow_run(concurrency_slot, workflow_run.id)
|
||||
slot_bound = True
|
||||
|
|
@ -325,6 +326,7 @@ class CampaignCallDispatcher:
|
|||
|
||||
quota_result = await authorize_workflow_run_start(
|
||||
workflow_id=campaign.workflow_id,
|
||||
organization_id=campaign.organization_id,
|
||||
workflow_run_id=workflow_run.id,
|
||||
)
|
||||
if not quota_result.has_quota:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import copy
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from typing import Literal
|
||||
|
||||
from loguru import logger
|
||||
|
|
@ -11,7 +12,11 @@ from sqlalchemy.orm import selectinload
|
|||
|
||||
from api.constants import MPS_API_URL
|
||||
from api.db import db_client
|
||||
from api.db.models import WorkflowDefinitionModel, WorkflowModel
|
||||
from api.db.models import (
|
||||
OrganizationConfigurationModel,
|
||||
WorkflowDefinitionModel,
|
||||
WorkflowModel,
|
||||
)
|
||||
from api.enums import OrganizationConfigurationKey
|
||||
from api.schemas.ai_model_configuration import (
|
||||
DOGRAH_DEFAULT_LANGUAGE,
|
||||
|
|
@ -55,35 +60,36 @@ class WorkflowAIModelConfigurationMigrationResult:
|
|||
|
||||
async def get_resolved_ai_model_configuration(
|
||||
*,
|
||||
user_id: int | None,
|
||||
organization_id: int | None,
|
||||
) -> ResolvedAIModelConfiguration:
|
||||
organization_configuration = await get_organization_ai_model_configuration_v2(
|
||||
organization_id
|
||||
"""Resolve the effective model configuration for an organization."""
|
||||
organization_configuration_row = (
|
||||
await _get_organization_ai_model_configuration_v2_row(organization_id)
|
||||
)
|
||||
organization_configuration = _parse_organization_ai_model_configuration_v2(
|
||||
organization_configuration_row,
|
||||
organization_id,
|
||||
)
|
||||
if organization_configuration is not None:
|
||||
effective = compile_ai_model_configuration_v2(organization_configuration)
|
||||
if organization_configuration_row is not None:
|
||||
effective.last_validated_at = (
|
||||
organization_configuration_row.last_validated_at
|
||||
)
|
||||
return ResolvedAIModelConfiguration(
|
||||
effective=compile_ai_model_configuration_v2(organization_configuration),
|
||||
effective=effective,
|
||||
source="organization_v2",
|
||||
organization_configuration=organization_configuration,
|
||||
)
|
||||
|
||||
if user_id is None:
|
||||
return ResolvedAIModelConfiguration(
|
||||
effective=EffectiveAIModelConfiguration(),
|
||||
source="empty",
|
||||
)
|
||||
|
||||
legacy = await db_client.get_user_configurations(user_id)
|
||||
return ResolvedAIModelConfiguration(
|
||||
effective=legacy,
|
||||
source="legacy_user_v1" if _has_model_services(legacy) else "empty",
|
||||
effective=EffectiveAIModelConfiguration(),
|
||||
source="empty",
|
||||
)
|
||||
|
||||
|
||||
async def get_effective_ai_model_configuration_for_workflow(
|
||||
*,
|
||||
user_id: int | None,
|
||||
organization_id: int | None,
|
||||
workflow_configurations: dict | None,
|
||||
) -> EffectiveAIModelConfiguration:
|
||||
|
|
@ -97,7 +103,6 @@ async def get_effective_ai_model_configuration_for_workflow(
|
|||
)
|
||||
|
||||
resolved_config = await get_resolved_ai_model_configuration(
|
||||
user_id=user_id,
|
||||
organization_id=organization_id,
|
||||
)
|
||||
return resolve_effective_config(
|
||||
|
|
@ -109,12 +114,34 @@ async def get_effective_ai_model_configuration_for_workflow(
|
|||
async def get_organization_ai_model_configuration_v2(
|
||||
organization_id: int | None,
|
||||
) -> OrganizationAIModelConfigurationV2 | None:
|
||||
if organization_id is None:
|
||||
return None
|
||||
row = await db_client.get_configuration(
|
||||
row = await _get_organization_ai_model_configuration_v2_row(organization_id)
|
||||
return _parse_organization_ai_model_configuration_v2(row, organization_id)
|
||||
|
||||
|
||||
async def update_organization_ai_model_configuration_last_validated_at(
|
||||
organization_id: int,
|
||||
) -> None:
|
||||
await db_client.mark_configuration_validated(
|
||||
organization_id,
|
||||
OrganizationConfigurationKey.MODEL_CONFIGURATION_V2.value,
|
||||
)
|
||||
|
||||
|
||||
async def _get_organization_ai_model_configuration_v2_row(
|
||||
organization_id: int | None,
|
||||
) -> OrganizationConfigurationModel | None:
|
||||
if organization_id is None:
|
||||
return None
|
||||
return await db_client.get_configuration(
|
||||
organization_id,
|
||||
OrganizationConfigurationKey.MODEL_CONFIGURATION_V2.value,
|
||||
)
|
||||
|
||||
|
||||
def _parse_organization_ai_model_configuration_v2(
|
||||
row: OrganizationConfigurationModel | None,
|
||||
organization_id: int | None,
|
||||
) -> OrganizationAIModelConfigurationV2 | None:
|
||||
if row is None or not row.value:
|
||||
return None
|
||||
try:
|
||||
|
|
@ -135,6 +162,7 @@ async def upsert_organization_ai_model_configuration_v2(
|
|||
organization_id,
|
||||
OrganizationConfigurationKey.MODEL_CONFIGURATION_V2.value,
|
||||
configuration.model_dump(mode="json", exclude_none=True),
|
||||
last_validated_at=datetime.now(UTC),
|
||||
)
|
||||
return configuration
|
||||
|
||||
|
|
@ -145,19 +173,12 @@ async def migrate_workflow_model_configurations_to_v2(
|
|||
fallback_user_config: EffectiveAIModelConfiguration,
|
||||
) -> WorkflowAIModelConfigurationMigrationResult:
|
||||
workflows = await _list_workflows_for_model_configuration_migration(organization_id)
|
||||
owner_configs: dict[int, EffectiveAIModelConfiguration] = {}
|
||||
workflow_updates: list[tuple[int, dict]] = []
|
||||
definition_updates: list[tuple[int, dict]] = []
|
||||
migrated_workflow_ids: set[int] = set()
|
||||
|
||||
for workflow in workflows:
|
||||
base_config = fallback_user_config
|
||||
if workflow.user_id is not None:
|
||||
if workflow.user_id not in owner_configs:
|
||||
owner_configs[
|
||||
workflow.user_id
|
||||
] = await db_client.get_user_configurations(workflow.user_id)
|
||||
base_config = owner_configs[workflow.user_id]
|
||||
|
||||
workflow_configs, workflow_changed = (
|
||||
migrate_workflow_configuration_model_override_to_v2(
|
||||
|
|
@ -420,19 +441,6 @@ def _mask_secret_value(value):
|
|||
return mask_key(value)
|
||||
|
||||
|
||||
def _has_model_services(configuration: EffectiveAIModelConfiguration) -> bool:
|
||||
return any(
|
||||
service is not None
|
||||
for service in (
|
||||
configuration.llm,
|
||||
configuration.tts,
|
||||
configuration.stt,
|
||||
configuration.embeddings,
|
||||
configuration.realtime,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _convert_any_dograh_legacy_configuration(
|
||||
configuration: EffectiveAIModelConfiguration,
|
||||
dograh_key: str,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ async def get_organization_context(user: UserModel) -> OrganizationContextRespon
|
|||
)
|
||||
|
||||
resolved = await get_resolved_ai_model_configuration(
|
||||
user_id=user.id,
|
||||
organization_id=organization_id,
|
||||
)
|
||||
managed_service_version = resolved.effective.managed_service_version
|
||||
|
|
|
|||
|
|
@ -233,7 +233,9 @@ class InMemoryLogsBuffer:
|
|||
if self._user_speech_start_timestamp:
|
||||
payload_with_timestamps["timestamp"] = self._user_speech_start_timestamp
|
||||
if self._user_speech_end_timestamp:
|
||||
payload_with_timestamps["end_timestamp"] = self._user_speech_end_timestamp
|
||||
payload_with_timestamps["end_timestamp"] = (
|
||||
self._user_speech_end_timestamp
|
||||
)
|
||||
elif event_type == RealtimeFeedbackType.BOT_TEXT.value:
|
||||
bot_interval_is_active = self._bot_speech_end_timestamp is None
|
||||
if bot_interval_is_active and self._bot_speech_start_timestamp:
|
||||
|
|
|
|||
|
|
@ -53,10 +53,10 @@ from pipecat.frames.frames import (
|
|||
TranscriptionFrame,
|
||||
TTSSpeakFrame,
|
||||
TTSTextFrame,
|
||||
UserStartedSpeakingFrame,
|
||||
UserStoppedSpeakingFrame,
|
||||
UserMuteStartedFrame,
|
||||
UserMuteStoppedFrame,
|
||||
UserStartedSpeakingFrame,
|
||||
UserStoppedSpeakingFrame,
|
||||
VADUserStartedSpeakingFrame,
|
||||
VADUserStoppedSpeakingFrame,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -329,7 +329,7 @@ async def _run_pipeline_telephony_impl(
|
|||
"telephony_configuration_id"
|
||||
)
|
||||
|
||||
# Resolve effective user config here so the transport can tune its
|
||||
# Resolve effective org config here so the transport can tune its
|
||||
# bot-stopped-speaking fallback based on is_realtime; pass the resolved
|
||||
# values into _run_pipeline so it doesn't fetch them again.
|
||||
from api.services.configuration.ai_model_configuration import (
|
||||
|
|
@ -340,7 +340,6 @@ async def _run_pipeline_telephony_impl(
|
|||
(workflow_run.definition.workflow_configurations or {}) if workflow_run else {}
|
||||
)
|
||||
user_config = await get_effective_ai_model_configuration_for_workflow(
|
||||
user_id=user_id,
|
||||
organization_id=workflow.organization_id if workflow else None,
|
||||
workflow_configurations=run_configs,
|
||||
)
|
||||
|
|
@ -385,6 +384,7 @@ async def run_pipeline_smallwebrtc(
|
|||
user_id: int,
|
||||
call_context_vars: dict = {},
|
||||
user_provider_id: str | None = None,
|
||||
organization_id: int | None = None,
|
||||
) -> None:
|
||||
"""Run pipeline for WebRTC connections."""
|
||||
# Register before any async setup so deploy drains see calls that are still
|
||||
|
|
@ -398,6 +398,7 @@ async def run_pipeline_smallwebrtc(
|
|||
user_id,
|
||||
call_context_vars=call_context_vars,
|
||||
user_provider_id=user_provider_id,
|
||||
organization_id=organization_id,
|
||||
)
|
||||
finally:
|
||||
try:
|
||||
|
|
@ -413,6 +414,7 @@ async def _run_pipeline_smallwebrtc_impl(
|
|||
user_id: int,
|
||||
call_context_vars: dict = {},
|
||||
user_provider_id: str | None = None,
|
||||
organization_id: int | None = None,
|
||||
) -> None:
|
||||
"""Run pipeline for WebRTC connections"""
|
||||
logger.debug(
|
||||
|
|
@ -420,8 +422,14 @@ async def _run_pipeline_smallwebrtc_impl(
|
|||
)
|
||||
set_current_run_id(workflow_run_id)
|
||||
|
||||
# Get workflow to extract all pipeline configurations
|
||||
workflow = await db_client.get_workflow(workflow_id, user_id)
|
||||
workflow_scope = (
|
||||
{"organization_id": organization_id}
|
||||
if organization_id is not None
|
||||
else {"user_id": user_id}
|
||||
)
|
||||
|
||||
# Get workflow to extract all pipeline configurations.
|
||||
workflow = await db_client.get_workflow(workflow_id, **workflow_scope)
|
||||
|
||||
# Set org context early so tasks created by the transport inherit it
|
||||
if workflow:
|
||||
|
|
@ -437,19 +445,26 @@ async def _run_pipeline_smallwebrtc_impl(
|
|||
# Create audio configuration for WebRTC
|
||||
audio_config = create_audio_config(WorkflowRunMode.SMALLWEBRTC.value)
|
||||
|
||||
# Resolve workflow_run + effective user_config here so the transport can
|
||||
# Resolve workflow_run + effective org config here so the transport can
|
||||
# tune its bot-stopped-speaking fallback based on is_realtime. _run_pipeline
|
||||
# reuses these via kwargs so we don't fetch twice.
|
||||
from api.services.configuration.ai_model_configuration import (
|
||||
get_effective_ai_model_configuration_for_workflow,
|
||||
)
|
||||
|
||||
workflow_run = await db_client.get_workflow_run(workflow_run_id, user_id)
|
||||
workflow_run = await db_client.get_workflow_run(workflow_run_id, **workflow_scope)
|
||||
if not workflow_run:
|
||||
raise HTTPException(status_code=404, detail="Workflow run not found")
|
||||
if workflow_run.workflow_id != workflow_id:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="workflow_run_workflow_mismatch",
|
||||
)
|
||||
|
||||
run_configs = (
|
||||
(workflow_run.definition.workflow_configurations or {}) if workflow_run else {}
|
||||
)
|
||||
user_config = await get_effective_ai_model_configuration_for_workflow(
|
||||
user_id=user_id,
|
||||
organization_id=workflow.organization_id if workflow else None,
|
||||
workflow_configurations=run_configs,
|
||||
)
|
||||
|
|
@ -472,6 +487,7 @@ async def _run_pipeline_smallwebrtc_impl(
|
|||
user_provider_id=user_provider_id,
|
||||
workflow_run=workflow_run,
|
||||
resolved_user_config=user_config,
|
||||
organization_id=organization_id,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -485,6 +501,7 @@ async def _run_pipeline(
|
|||
user_provider_id: str | None = None,
|
||||
workflow_run=None,
|
||||
resolved_user_config=None,
|
||||
organization_id: int | None = None,
|
||||
) -> None:
|
||||
"""Run the pipeline with active-call drain accounting."""
|
||||
register_worker_active_call(workflow_run_id)
|
||||
|
|
@ -499,6 +516,7 @@ async def _run_pipeline(
|
|||
user_provider_id=user_provider_id,
|
||||
workflow_run=workflow_run,
|
||||
resolved_user_config=resolved_user_config,
|
||||
organization_id=organization_id,
|
||||
)
|
||||
finally:
|
||||
try:
|
||||
|
|
@ -517,6 +535,7 @@ async def _run_pipeline_impl(
|
|||
user_provider_id: str | None = None,
|
||||
workflow_run=None,
|
||||
resolved_user_config=None,
|
||||
organization_id: int | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Run the pipeline with the given transport and configuration
|
||||
|
|
@ -527,11 +546,26 @@ async def _run_pipeline_impl(
|
|||
workflow_run_id: The ID of the workflow run
|
||||
user_id: The ID of the user
|
||||
workflow_run: Pre-fetched workflow run row. Fetched here if None.
|
||||
resolved_user_config: User configuration with model_overrides already
|
||||
applied. Fetched and resolved here if None.
|
||||
resolved_user_config: Organization model configuration with workflow
|
||||
model_overrides already applied. Fetched and resolved here if None.
|
||||
"""
|
||||
workflow_scope = (
|
||||
{"organization_id": organization_id}
|
||||
if organization_id is not None
|
||||
else {"user_id": user_id}
|
||||
)
|
||||
|
||||
if workflow_run is None:
|
||||
workflow_run = await db_client.get_workflow_run(workflow_run_id, user_id)
|
||||
workflow_run = await db_client.get_workflow_run(
|
||||
workflow_run_id, **workflow_scope
|
||||
)
|
||||
if not workflow_run:
|
||||
raise HTTPException(status_code=404, detail="Workflow run not found")
|
||||
if workflow_run.workflow_id != workflow_id:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="workflow_run_workflow_mismatch",
|
||||
)
|
||||
|
||||
# If the workflow run is already completed, we don't need to run it again
|
||||
if workflow_run.is_completed:
|
||||
|
|
@ -544,7 +578,7 @@ async def _run_pipeline_impl(
|
|||
merged_call_context_vars = {**merged_call_context_vars, **call_context_vars}
|
||||
|
||||
# Get workflow for metadata (name, organization_id, call_disposition_codes)
|
||||
workflow = await db_client.get_workflow(workflow_id, user_id)
|
||||
workflow = await db_client.get_workflow(workflow_id, **workflow_scope)
|
||||
if not workflow:
|
||||
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||
|
||||
|
|
@ -576,7 +610,7 @@ async def _run_pipeline_impl(
|
|||
term.strip() for term in dictionary.split(",") if term.strip()
|
||||
]
|
||||
|
||||
# Resolve model overrides from the version onto global user config (skip
|
||||
# Resolve model overrides from the version onto global org config (skip
|
||||
# when the caller already resolved it).
|
||||
if resolved_user_config is None:
|
||||
from api.services.configuration.ai_model_configuration import (
|
||||
|
|
@ -584,7 +618,6 @@ async def _run_pipeline_impl(
|
|||
)
|
||||
|
||||
user_config = await get_effective_ai_model_configuration_for_workflow(
|
||||
user_id=user_id,
|
||||
organization_id=workflow.organization_id,
|
||||
workflow_configurations=run_configs,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -337,6 +337,7 @@ async def _authorize_oss_managed_v2_correlation(
|
|||
async def authorize_workflow_run_start(
|
||||
*,
|
||||
workflow_id: int,
|
||||
organization_id: int,
|
||||
workflow_run_id: int | None = None,
|
||||
actor_user: UserModel | None = None,
|
||||
) -> QuotaCheckResult:
|
||||
|
|
@ -344,30 +345,75 @@ async def authorize_workflow_run_start(
|
|||
|
||||
The workflow organization is the billing subject for hosted deployments.
|
||||
OSS deployments are billed per service key instead. The workflow owner is
|
||||
used only to resolve the effective model configuration.
|
||||
used only as billing metadata.
|
||||
"""
|
||||
if organization_id is None:
|
||||
logger.warning(
|
||||
"Workflow start authorization denied: missing organization scope for workflow {}",
|
||||
workflow_id,
|
||||
)
|
||||
return QuotaCheckResult(
|
||||
has_quota=False,
|
||||
error_code="workflow_not_found",
|
||||
error_message="Workflow not found",
|
||||
)
|
||||
|
||||
try:
|
||||
workflow = await db_client.get_workflow_by_id(workflow_id)
|
||||
if not workflow:
|
||||
workflow = await db_client.get_workflow(
|
||||
workflow_id,
|
||||
organization_id=organization_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Workflow start authorization denied: failed to load workflow {} for org {}: {}",
|
||||
workflow_id,
|
||||
organization_id,
|
||||
e,
|
||||
)
|
||||
return QuotaCheckResult(
|
||||
has_quota=False,
|
||||
error_code="workflow_not_found",
|
||||
error_message="Workflow not found",
|
||||
)
|
||||
|
||||
if not workflow:
|
||||
logger.warning(
|
||||
"Workflow start authorization denied: workflow {} not found for org {}",
|
||||
workflow_id,
|
||||
organization_id,
|
||||
)
|
||||
return QuotaCheckResult(
|
||||
has_quota=False,
|
||||
error_code="workflow_not_found",
|
||||
error_message="Workflow not found",
|
||||
)
|
||||
|
||||
try:
|
||||
actor_id = getattr(actor_user, "id", None) if actor_user is not None else None
|
||||
if actor_user is not None and actor_id is None:
|
||||
logger.warning(
|
||||
"Workflow start authorization denied: actor is missing id for workflow {} org {}",
|
||||
workflow_id,
|
||||
organization_id,
|
||||
)
|
||||
return QuotaCheckResult(
|
||||
has_quota=False,
|
||||
error_code="workflow_not_found",
|
||||
error_message="Workflow not found",
|
||||
)
|
||||
|
||||
actor_id = getattr(actor_user, "id", None)
|
||||
if actor_id is not None and workflow.organization_id is not None:
|
||||
if actor_id is not None:
|
||||
try:
|
||||
is_member = await db_client.is_user_member_of_organization(
|
||||
user_id=actor_id,
|
||||
organization_id=workflow.organization_id,
|
||||
organization_id=organization_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Workflow start authorization denied: failed to validate actor {} membership for workflow {} org {}: {}",
|
||||
actor_id,
|
||||
workflow_id,
|
||||
workflow.organization_id,
|
||||
organization_id,
|
||||
e,
|
||||
)
|
||||
return QuotaCheckResult(
|
||||
|
|
@ -380,7 +426,7 @@ async def authorize_workflow_run_start(
|
|||
"Workflow start authorization denied: actor {} is not a member of workflow {} org {}",
|
||||
actor_id,
|
||||
workflow_id,
|
||||
workflow.organization_id,
|
||||
organization_id,
|
||||
)
|
||||
return QuotaCheckResult(
|
||||
has_quota=False,
|
||||
|
|
@ -397,15 +443,14 @@ async def authorize_workflow_run_start(
|
|||
)
|
||||
|
||||
user_config = await get_effective_ai_model_configuration_for_workflow(
|
||||
user_id=workflow_owner.id,
|
||||
organization_id=workflow.organization_id,
|
||||
organization_id=organization_id,
|
||||
workflow_configurations=workflow.workflow_configurations,
|
||||
)
|
||||
|
||||
if DEPLOYMENT_MODE != "oss":
|
||||
return await _authorize_hosted_workflow_run_start(
|
||||
workflow_owner=workflow_owner,
|
||||
organization_id=workflow.organization_id,
|
||||
organization_id=organization_id,
|
||||
workflow_id=workflow.id,
|
||||
workflow_run_id=workflow_run_id,
|
||||
user_config=user_config,
|
||||
|
|
|
|||
|
|
@ -625,6 +625,7 @@ class ARIConnection:
|
|||
# store the MPS correlation id before the pipeline starts.
|
||||
quota_result = await authorize_workflow_run_start(
|
||||
workflow_id=inbound_workflow_id,
|
||||
organization_id=self.organization_id,
|
||||
workflow_run_id=workflow_run.id,
|
||||
)
|
||||
if not quota_result.has_quota:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ async def resolve_llm_config(
|
|||
"""Resolve the LLM provider, model, API key, and extra kwargs for QA analysis.
|
||||
|
||||
If the QA node has its own LLM configuration (qa_use_workflow_llm=False),
|
||||
use those settings directly. Otherwise, fall back to the user's configured LLM.
|
||||
use those settings directly. Otherwise, fall back to the workflow/org LLM.
|
||||
|
||||
Returns:
|
||||
(provider, model, api_key, service_kwargs) tuple — service_kwargs can be
|
||||
|
|
@ -30,7 +30,7 @@ async def resolve_llm_config(
|
|||
kwargs,
|
||||
)
|
||||
|
||||
# Fall back to user's configured LLM
|
||||
# Fall back to the workflow/org configured LLM
|
||||
provider, model, api_key, kwargs = await resolve_user_llm_config(workflow_run)
|
||||
|
||||
if qa_data.qa_model and qa_data.qa_model != "default":
|
||||
|
|
@ -42,17 +42,13 @@ async def resolve_llm_config(
|
|||
async def resolve_user_llm_config(
|
||||
workflow_run: WorkflowRunModel,
|
||||
) -> tuple[str, str, str, dict]:
|
||||
"""Resolve the user's configured LLM (from EffectiveAIModelConfiguration).
|
||||
"""Resolve the workflow/org configured LLM.
|
||||
|
||||
Returns:
|
||||
(provider, model, api_key, service_kwargs) tuple
|
||||
"""
|
||||
user_id = None
|
||||
if workflow_run.workflow and workflow_run.workflow.user:
|
||||
user_id = workflow_run.workflow.user.id
|
||||
|
||||
llm_config: dict = {}
|
||||
if user_id:
|
||||
if workflow_run.workflow:
|
||||
from api.services.configuration.ai_model_configuration import (
|
||||
get_effective_ai_model_configuration_for_workflow,
|
||||
)
|
||||
|
|
@ -68,7 +64,6 @@ async def resolve_user_llm_config(
|
|||
)
|
||||
|
||||
user_configuration = await get_effective_ai_model_configuration_for_workflow(
|
||||
user_id=user_id,
|
||||
organization_id=workflow_run.workflow.organization_id
|
||||
if workflow_run.workflow
|
||||
else None,
|
||||
|
|
|
|||
|
|
@ -458,7 +458,6 @@ async def execute_text_chat_pending_turn(
|
|||
)
|
||||
|
||||
user_config = await get_effective_ai_model_configuration_for_workflow(
|
||||
user_id=workflow_run.workflow.user.id,
|
||||
organization_id=workflow.organization_id,
|
||||
workflow_configurations=run_configs,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue