Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions apps/evaluations/evaluators.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from apps.service_providers.llm_service.main import LlmService
from apps.service_providers.llm_service.prompt_context import SafeAccessWrapper
from apps.service_providers.llm_service.retry import RATE_LIMIT_EXCEPTIONS
from apps.service_providers.models import LlmProviderModel
from apps.service_providers.models import LlmProvider, LlmProviderModel
from apps.utils.python_execution import RestrictedPythonExecutionMixin, get_code_error_message


Expand Down Expand Up @@ -41,7 +41,6 @@ class LLMResponseMixin(BaseModel):
)

def get_llm_service(self) -> LlmService:
from apps.service_providers.models import LlmProvider # noqa: PLC0415

try:
provider = LlmProvider.objects.get(id=self.llm_provider_id)
Expand Down
6 changes: 4 additions & 2 deletions apps/evaluations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from apps.chat.models import ChatMessage, ChatMessageType
from apps.evaluations.utils import make_evaluation_messages_from_sessions
from apps.experiments.filters import ChatMessageFilter
from apps.experiments.models import ExperimentSession
from apps.teams.models import BaseTeamModel, Team
from apps.teams.utils import get_slug_for_team
Expand Down Expand Up @@ -115,7 +116,6 @@ def __str__(self):
def create_from_sessions(
cls, team: Team, external_session_ids, filtered_session_ids=None, filter_params=None, timezone=None
) -> list[EvaluationMessage]:
from apps.experiments.filters import ChatMessageFilter # noqa: PLC0415

base_queryset = (
ChatMessage.objects.filter(
Expand Down Expand Up @@ -295,7 +295,9 @@ def run(self, run_type=EvaluationRunType.FULL) -> EvaluationRun:
type=run_type,
)

from apps.evaluations.tasks import run_evaluation_task # noqa: PLC0415
from apps.evaluations.tasks import (
run_evaluation_task, # noqa: PLC0415 - circular: evaluations.tasks imports evaluations.models
)

run_evaluation_task.delay(run.id)
return run
Expand Down
2 changes: 0 additions & 2 deletions apps/evaluations/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ class EvaluationConfigTable(tables.Table):

def render_evaluators(self, value, record):
"""Render the evaluators column with icons and labels in an unordered list."""
from apps.evaluations.utils import get_evaluator_type_display # noqa: PLC0415

if not value.exists():
return "—"

Expand Down
8 changes: 3 additions & 5 deletions apps/evaluations/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
from celery import chord, shared_task
from celery.utils.log import get_task_logger
from celery_progress.backend import ProgressRecorder
from django.http import QueryDict
from django.utils import timezone
from taskbadger.celery import Task as TaskbadgerTask

from apps.channels.models import ChannelPlatform, ExperimentChannel
from apps.channels.tasks import handle_evaluation_message
from apps.chat.models import Chat, ChatMessage, ChatMessageType
from apps.evaluations.aggregation import compute_aggregates_for_run
from apps.evaluations.const import PREVIEW_SAMPLE_SIZE
from apps.evaluations.exceptions import HistoryParseException
from apps.evaluations.models import (
Expand All @@ -33,6 +35,7 @@
from apps.experiments.models import Experiment, ExperimentSession, Participant
from apps.files.models import File
from apps.teams.utils import current_team
from apps.web.dynamic_filters.datastructures import FilterParams

EVAL_SESSIONS_TTL_DAYS = 30

Expand Down Expand Up @@ -175,7 +178,6 @@ def mark_evaluation_complete(results, evaluation_run_id):
results: List of results from the group tasks (unused but required by chord)
evaluation_run_id: ID of the evaluation run to mark complete
"""
from apps.evaluations.aggregation import compute_aggregates_for_run # noqa: PLC0415

try:
evaluation_run = EvaluationRun.objects.get(id=evaluation_run_id)
Expand Down Expand Up @@ -658,7 +660,6 @@ def upload_evaluation_run_results_task(self, evaluation_run_id, csv_data, team_i


def _upload_evaluation_run_results(progress_recorder, evaluation_run_id, csv_data, team_id, column_mappings=None):
from apps.evaluations.aggregation import compute_aggregates_for_run # noqa: PLC0415

if not csv_data:
return {"success": False, "error": "CSV file is empty"}
Expand Down Expand Up @@ -802,9 +803,6 @@ def create_dataset_from_sessions_task(
filter_query: Serialized filter parameters as query string (or None)
timezone: Timezone for filtering
"""
from django.http import QueryDict # noqa: PLC0415

from apps.web.dynamic_filters.datastructures import FilterParams # noqa: PLC0415

progress_recorder = ProgressRecorder(self)
dataset = None
Expand Down
2 changes: 1 addition & 1 deletion apps/evaluations/tests/test_dataset_csv_download.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import csv
import io
import json

import pytest
from django.urls import reverse
Expand Down Expand Up @@ -240,7 +241,6 @@ def test_download_dataset_csv_with_participant_data_and_session_state(client, te
assert row5["id"] == str(message5.id)
assert row5["input_content"] == "Complex data"
# Nested structures should be serialized as JSON strings
import json # noqa: PLC0415

assert json.loads(row5["context.nested"]) == {"foo": {"bar": [1, 2, "3"]}}
assert json.loads(row5["participant_data.preferences"]) == {
Expand Down
4 changes: 2 additions & 2 deletions apps/evaluations/tests/test_evaluation_run_csv_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import pytest

from apps.evaluations.aggregation import compute_aggregates_for_run
from apps.evaluations.models import EvaluationRunAggregate
from apps.evaluations.tasks import _upload_evaluation_run_results, process_evaluation_results_csv_rows
from apps.evaluations.views.evaluation_config_views import generate_evaluation_results_column_suggestions
from apps.utils.factories.evaluations import (
Expand Down Expand Up @@ -237,8 +239,6 @@ def test_process_csv_no_update_when_value_unchanged(evaluation_setup):
@pytest.mark.django_db()
def test_upload_task_recomputes_aggregates(evaluation_setup):
"""Test that uploading CSV results triggers aggregate recalculation"""
from apps.evaluations.aggregation import compute_aggregates_for_run # noqa: PLC0415
from apps.evaluations.models import EvaluationRunAggregate # noqa: PLC0415

# Compute initial aggregates
compute_aggregates_for_run(evaluation_setup["run"])
Expand Down
11 changes: 8 additions & 3 deletions apps/evaluations/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def get_evaluator_type_info() -> dict[str, dict[str, str | None]]:
Returns:
Dict mapping evaluator class names to their schema info (label, icon)
"""
from apps.evaluations import evaluators # noqa: PLC0415
from apps.evaluations import evaluators # noqa: PLC0415 - circular: evaluators imports evaluations.utils

evaluator_classes = [
cls
Expand Down Expand Up @@ -80,7 +80,9 @@ def get_evaluators_with_schema(team) -> list[dict]:
Returns:
List of dicts containing evaluator info with schema data
"""
from apps.evaluations.models import Evaluator # noqa: PLC0415
from apps.evaluations.models import (
Evaluator, # noqa: PLC0415 - circular: evaluations.models imports evaluations.utils
)

evaluator_type_info = get_evaluator_type_info()

Expand Down Expand Up @@ -212,7 +214,10 @@ def _clean_field_name(field_name):


def make_evaluation_messages_from_sessions(message_ids_per_session: dict[str, list[str]]) -> list["EvaluationMessage"]:
from apps.evaluations.models import EvaluationMessage, EvaluationMessageContent # noqa: PLC0415
from apps.evaluations.models import ( # noqa: PLC0415 - circular: evaluations.models imports evaluations.utils
EvaluationMessage,
EvaluationMessageContent,
)

def _add_additional_context(msg, existing_context):
if comments := list(msg.comments.all()):
Expand Down
2 changes: 1 addition & 1 deletion apps/evaluations/views/evaluation_config_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from functools import cached_property
from io import StringIO

from django.conf import settings
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
Expand Down Expand Up @@ -293,7 +294,6 @@ def get_table_class(self):
Inspect the first row's keys and build a Table subclass
with one Column per field.
"""
from django.conf import settings # noqa: PLC0415

data = self.get_table_data()
if not data:
Expand Down
6 changes: 2 additions & 4 deletions apps/evaluations/views/evaluator_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from django.views.generic import CreateView, DeleteView, TemplateView, UpdateView
from django_tables2 import SingleTableView

from apps.custom_actions.schema_utils import resolve_references
from apps.evaluations import evaluators
from apps.evaluations.forms import EvaluatorForm
from apps.evaluations.models import Evaluator
from apps.evaluations.tables import EvaluatorTable
Expand Down Expand Up @@ -130,8 +132,6 @@ def delete(self, request, *args, **kwargs):

def _evaluator_schemas():
"""Returns schemas for all available evaluator classes."""
from apps.evaluations import evaluators # noqa: PLC0415

schemas = []

evaluator_classes = [
Expand All @@ -148,8 +148,6 @@ def _evaluator_schemas():

def _get_evaluator_schema(evaluator_class):
"""Get schema for a single evaluator class."""
from apps.custom_actions.schema_utils import resolve_references # noqa: PLC0415

schema = resolve_references(evaluator_class.model_json_schema())
schema.pop("$defs", None)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from apps.assistants.models import OpenAiAssistant
from apps.assistants.sync import push_assistant_to_openai
from apps.teams.models import Team
from apps.teams.utils import current_team


Expand All @@ -14,7 +15,6 @@ def add_arguments(self, parser):
parser.add_argument("--assistant", help="A specific assistant id", required=False)

def handle(self, team, assistant, **options):
from apps.teams.models import Team # noqa: PLC0415

assistants = []
if assistant:
Expand Down
55 changes: 39 additions & 16 deletions apps/experiments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from field_audit import audit_fields
from field_audit.models import AuditAction, AuditingManager

from apps.annotations.models import CustomTaggedItem
from apps.chat.models import Chat, ChatMessage, ChatMessageType
from apps.experiments import model_audit_fields
from apps.experiments.versioning import VersionDetails, VersionField, VersionsMixin, VersionsObjectManagerMixin, differs
Expand Down Expand Up @@ -933,7 +934,9 @@ def archive(self):
self.pipeline.archive()

def delete_experiment_channels(self):
from apps.channels.models import ExperimentChannel # noqa: PLC0415
from apps.channels.models import (
ExperimentChannel, # noqa: PLC0415 - circular: channels.models imports experiments.models
)

for channel in ExperimentChannel.objects.filter(experiment_id=self.id):
channel.soft_delete()
Expand Down Expand Up @@ -1071,9 +1074,13 @@ def get_assistant(self):
- If no assistant node is found or if the pipeline is not set, it returns the default assistant associated with
the instance.
"""
from apps.assistants.models import OpenAiAssistant # noqa: PLC0415
from apps.pipelines.models import Node # noqa: PLC0415
from apps.pipelines.nodes.nodes import AssistantNode # noqa: PLC0415
from apps.assistants.models import (
OpenAiAssistant, # noqa: PLC0415 - circular: assistants.models imports experiments.models
)
from apps.pipelines.models import Node # noqa: PLC0415 - circular: pipelines.models imports experiments.models
from apps.pipelines.nodes.nodes import (
AssistantNode, # noqa: PLC0415 - circular: pipelines.nodes imports experiments.models
)

if self.pipeline:
node_name = AssistantNode.__name__
Expand Down Expand Up @@ -1146,7 +1153,9 @@ def __str__(self):
return self.identifier

def get_platform_display(self):
from apps.channels.models import ChannelPlatform # noqa: PLC0415
from apps.channels.models import (
ChannelPlatform, # noqa: PLC0415 - circular: channels.models imports experiments.models
)

try:
return ChannelPlatform(self.platform).label
Expand Down Expand Up @@ -1220,7 +1229,9 @@ def get_schedules_for_experiment(
as_dict: If True, the data will be returned as an array of dictionaries, otherwise an an array of strings
timezone: The timezone to use for the dates. Defaults to the active timezone.
"""
from apps.events.models import ScheduledMessage # noqa: PLC0415
from apps.events.models import (
ScheduledMessage, # noqa: PLC0415 - circular: events.models imports experiments.models
)

messages = (
ScheduledMessage.objects.filter(
Expand Down Expand Up @@ -1351,8 +1362,6 @@ def get_queryset(self):
return ExperimentSessionQuerySet(self.model, using=self._db)

def get_table_queryset(self, team, experiment_id=None):
from apps.annotations.models import CustomTaggedItem # noqa: PLC0415

queryset = self.get_queryset().filter(team=team)
if experiment_id:
queryset = queryset.filter(experiment__id=experiment_id)
Expand Down Expand Up @@ -1462,7 +1471,9 @@ def is_stale(self) -> bool:
"""A Channel Session is considered stale if the experiment that the channel points to differs from the
one that the experiment session points to. This will happen when the user repurposes the channel to point
to another experiment."""
from apps.channels.models import ChannelPlatform # noqa: PLC0415
from apps.channels.models import (
ChannelPlatform, # noqa: PLC0415 - circular: channels.models imports experiments.models
)

if self.experiment_channel.platform in ChannelPlatform.team_global_platforms():
return False
Expand Down Expand Up @@ -1496,8 +1507,12 @@ def end(self, commit: bool = True, trigger_type=None):
Raises:
ValueError: If trigger_type is specified but commit is not.
"""
from apps.events.models import StaticTriggerType # noqa: PLC0415
from apps.events.tasks import enqueue_static_triggers # noqa: PLC0415
from apps.events.models import (
StaticTriggerType, # noqa: PLC0415 - circular: events.models imports experiments.models
)
from apps.events.tasks import (
enqueue_static_triggers, # noqa: PLC0415 - circular: events.tasks imports experiments.models
)

if trigger_type and not commit:
raise ValueError("Commit must be True when trigger_type is specified")
Expand Down Expand Up @@ -1574,8 +1589,10 @@ def _bot_prompt_for_user(
"""Sends the `instruction_prompt` along with the chat history to the LLM to formulate an appropriate prompt
message. The response from the bot will be saved to the chat history.
"""
from apps.chat.bots import EventBot # noqa: PLC0415
from apps.service_providers.llm_service.history_managers import ExperimentHistoryManager # noqa: PLC0415
from apps.chat.bots import EventBot # noqa: PLC0415 - circular: chat.bots imports experiments.models
from apps.service_providers.llm_service.history_managers import (
ExperimentHistoryManager, # noqa: PLC0415 - circular: history_managers imports experiments.models
)

experiment = use_experiment or self.experiment
history_manager = ExperimentHistoryManager(session=self, experiment=experiment, trace_service=trace_service)
Expand All @@ -1586,7 +1603,7 @@ def try_send_message(self, message: str):
"""Tries to send a message to this user session as the bot. Note that `message` will be send to the user
directly. This is not an instruction to the bot.
"""
from apps.chat.channels import ChannelBase # noqa: PLC0415
from apps.chat.channels import ChannelBase # noqa: PLC0415 - circular: chat.channels imports experiments.models

channel = ChannelBase.from_experiment_session(self)
channel.send_message_to_user(message)
Expand Down Expand Up @@ -1617,8 +1634,14 @@ def get_experiment_version_number(self) -> int:

def requires_participant_data(self) -> bool:
"""Determines if participant data is required for this session"""
from apps.assistants.models import OpenAiAssistant # noqa: PLC0415
from apps.pipelines.nodes.nodes import AssistantNode, LLMResponseWithPrompt, RouterNode # noqa: PLC0415
from apps.assistants.models import (
OpenAiAssistant, # noqa: PLC0415 - circular: assistants.models imports experiments.models
)
from apps.pipelines.nodes.nodes import ( # noqa: PLC0415 - circular: pipelines.nodes imports experiments.models
AssistantNode,
LLMResponseWithPrompt,
RouterNode,
)

if self.experiment.pipeline:
assistant_ids = self.experiment.pipeline.get_node_param_values(AssistantNode, param_name="assistant_id")
Expand Down
6 changes: 4 additions & 2 deletions apps/experiments/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,10 @@ def get_prompt_builder_response_task(team_id: int, user_id, data_dict: dict) ->


def _convert_prompt_builder_history(messages_history):
# lazy import to avoid import on startup
from langchain_core.messages import AIMessage, HumanMessage # noqa: PLC0415
from langchain_core.messages import ( # noqa: PLC0415 - lazy: avoid langchain import on startup
AIMessage,
HumanMessage,
)

history = []
for message in messages_history:
Expand Down
Loading
Loading