feat: stream challenge submissions and tidy challenge imports

This commit is contained in:
Joey Yakimowich-Payne 2025-10-01 14:33:44 -06:00
commit cb4cde0ed2
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
13 changed files with 309 additions and 93 deletions

View file

@ -44,6 +44,10 @@ from . import (
version,
)
# Import custom challenge controllers
from . import challenges as challenges
from . import red_blue_challenges as red_blue_challenges
# Import app controllers
from .app import (
advanced_prompt_template,
@ -129,10 +133,6 @@ from .workspace import (
workspace,
)
# Import custom challenge controllers
from . import challenges as challenges
from . import red_blue_challenges as red_blue_challenges
api.add_namespace(console_ns)
__all__ = [
@ -149,6 +149,7 @@ __all__ = [
"audio",
"billing",
"bp",
"challenges",
"completion",
"compliance",
"console_ns",
@ -193,6 +194,7 @@ __all__ = [
"rag_pipeline_import",
"rag_pipeline_workflow",
"recommended_app",
"red_blue_challenges",
"saved_message",
"setup",
"site",
@ -208,6 +210,4 @@ __all__ = [
"workflow_run",
"workflow_statistic",
"workspace",
"challenges",
"red_blue_challenges",
]

View file

@ -7,9 +7,8 @@ from controllers.console.wraps import (
account_initialization_required,
setup_required,
)
from libs.login import login_required
from extensions.ext_database import db
from libs.login import current_user
from libs.login import current_user, login_required
from models.challenge import Challenge
@ -72,7 +71,7 @@ class ChallengeListCreateApi(Resource):
c.app_id = args["app_id"]
# Convert empty string to None for UUID field
workflow_id = args.get("workflow_id")
c.workflow_id = workflow_id if workflow_id else None
c.workflow_id = workflow_id or None
c.name = args["name"]
c.description = args.get("description")
c.goal = args.get("goal")

View file

@ -18,21 +18,21 @@ web_ns = Namespace("web", description="Web application API operations", path="/"
from . import (
app,
audio,
challenges,
completion,
conversation,
feature,
files,
forgot_password,
login,
register,
message,
passport,
red_blue_challenges,
register,
remote_files,
saved_message,
site,
workflow,
challenges,
red_blue_challenges,
)
api.add_namespace(web_ns)
@ -42,20 +42,20 @@ __all__ = [
"app",
"audio",
"bp",
"challenges",
"completion",
"conversation",
"feature",
"files",
"forgot_password",
"login",
"register",
"message",
"passport",
"red_blue_challenges",
"register",
"remote_files",
"saved_message",
"site",
"web_ns",
"workflow",
"challenges",
"red_blue_challenges",
]

View file

@ -1,11 +1,10 @@
from __future__ import annotations
from flask_restx import Resource
from sqlalchemy import select
from controllers.web import web_ns
from extensions.ext_database import db
from sqlalchemy import select
from models.challenge import Challenge, ChallengeAttempt
from models.model import App, Site

View file

@ -16,7 +16,7 @@ class WebRegisterApi(Resource):
name = payload.get('name') or 'Player'
password = payload.get('password')
if not email or not password:
return { 'result': 'bad_request' }, 400
return {'result': 'bad_request'}, 400
account = RegisterService.register(
email=email,
name=name,
@ -25,6 +25,6 @@ class WebRegisterApi(Resource):
create_workspace_required=False,
)
db.session.commit()
return { 'result': 'success', 'data': { 'account_id': account.id } }, 201
return {'result': 'success', 'data': {'account_id': account.id}}, 201

View file

@ -4,6 +4,7 @@ from core.workflow.enums import NodeType
from core.workflow.nodes.agent.agent_node import AgentNode
from core.workflow.nodes.answer.answer_node import AnswerNode
from core.workflow.nodes.base.node import Node
from core.workflow.nodes.challenge_evaluator.node import ChallengeEvaluatorNode
from core.workflow.nodes.code import CodeNode
from core.workflow.nodes.datasource.datasource_node import DatasourceNode
from core.workflow.nodes.document_extractor import DocumentExtractorNode
@ -11,6 +12,7 @@ from core.workflow.nodes.end.end_node import EndNode
from core.workflow.nodes.http_request import HttpRequestNode
from core.workflow.nodes.if_else import IfElseNode
from core.workflow.nodes.iteration import IterationNode, IterationStartNode
from core.workflow.nodes.judging_llm.node import JudgingLLMNode
from core.workflow.nodes.knowledge_index import KnowledgeIndexNode
from core.workflow.nodes.knowledge_retrieval import KnowledgeRetrievalNode
from core.workflow.nodes.list_operator import ListOperatorNode
@ -19,14 +21,12 @@ from core.workflow.nodes.loop import LoopEndNode, LoopNode, LoopStartNode
from core.workflow.nodes.parameter_extractor import ParameterExtractorNode
from core.workflow.nodes.question_classifier import QuestionClassifierNode
from core.workflow.nodes.start import StartNode
from core.workflow.nodes.team_challenge.node import TeamChallengeNode
from core.workflow.nodes.template_transform import TemplateTransformNode
from core.workflow.nodes.tool import ToolNode
from core.workflow.nodes.variable_aggregator import VariableAggregatorNode
from core.workflow.nodes.variable_assigner.v1 import VariableAssignerNode as VariableAssignerNodeV1
from core.workflow.nodes.variable_assigner.v2 import VariableAssignerNode as VariableAssignerNodeV2
from core.workflow.nodes.challenge_evaluator.node import ChallengeEvaluatorNode
from core.workflow.nodes.judging_llm.node import JudgingLLMNode
from core.workflow.nodes.team_challenge.node import TeamChallengeNode
LATEST_VERSION = "latest"

View file

@ -9,6 +9,7 @@ from .account import (
TenantStatus,
)
from .api_based_extension import APIBasedExtension, APIBasedExtensionPoint
from .challenge import Challenge, ChallengeAttempt
from .dataset import (
AppDatasetJoin,
Dataset,
@ -68,6 +69,7 @@ from .provider import (
TenantDefaultModel,
TenantPreferredModelProvider,
)
from .red_blue import RedBlueChallenge, TeamPairing, TeamSubmission
from .source import DataSourceApiKeyAuthBinding, DataSourceOauthBinding
from .task import CeleryTask, CeleryTaskSet
from .tools import (
@ -91,8 +93,6 @@ from .workflow import (
WorkflowRun,
WorkflowType,
)
from .challenge import Challenge, ChallengeAttempt
from .red_blue import RedBlueChallenge, TeamSubmission, TeamPairing
__all__ = [
"APIBasedExtension",
@ -113,6 +113,8 @@ __all__ = [
"BuiltinToolProvider",
"CeleryTask",
"CeleryTaskSet",
"Challenge",
"ChallengeAttempt",
"Conversation",
"ConversationVariable",
"CreatorUserRole",
@ -154,10 +156,13 @@ __all__ = [
"ProviderQuotaType",
"ProviderType",
"RecommendedApp",
"RedBlueChallenge",
"SavedMessage",
"Site",
"Tag",
"TagBinding",
"TeamPairing",
"TeamSubmission",
"Tenant",
"TenantAccountJoin",
"TenantAccountRole",
@ -183,9 +188,4 @@ __all__ = [
"WorkflowRunTriggeredFrom",
"WorkflowToolProvider",
"WorkflowType",
"Challenge",
"ChallengeAttempt",
"RedBlueChallenge",
"TeamSubmission",
"TeamPairing",
]

View file

@ -62,7 +62,7 @@ class ChallengeScorerService:
raise ValueError("Scorer must return a dict with 'score' key")
return result
except Exception as e:
logger.error(f"Scorer plugin {scorer_plugin_id} failed: {e}", exc_info=True)
logger.error("Scorer plugin %s failed: %s", scorer_plugin_id, e, exc_info=True)
raise ValueError(f"Scorer plugin execution failed: {e}")
@classmethod
@ -99,11 +99,11 @@ class ChallengeScorerService:
# Cache it
cls._plugin_cache[cache_key] = scorer
logger.info(f"Loaded scorer plugin: {plugin_id} from {entrypoint}")
logger.info("Loaded scorer plugin: %s from %s", plugin_id, entrypoint)
return scorer
except Exception as e:
logger.error(f"Failed to load scorer plugin {plugin_id}:{entrypoint}: {e}", exc_info=True)
logger.error("Failed to load scorer plugin %s:%s: %s", plugin_id, entrypoint, e, exc_info=True)
return None
@classmethod

View file

@ -7,7 +7,7 @@ from typing import Any
from sqlalchemy.orm import Session
from extensions.ext_database import db
from models.challenge import Challenge, ChallengeAttempt
from models.challenge import ChallengeAttempt
class ChallengeService: