formatting
formatting
This commit is contained in:
parent
1febc07527
commit
d0400c0907
25 changed files with 200 additions and 289 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
"python.defaultInterpreterPath": "${workspaceFolder}/django/aiservice/.venv/bin/python",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ RATE_LIMIT_MAX = int(os.getenv("RATE_LIMIT_MAX", "40"))
|
|||
# - Consider using asyncio.PriorityQueue or celery with priority tasks
|
||||
# - Reference: authapp/models.py:Subscriptions.plan_type, CFAPIKeys.tier
|
||||
|
||||
|
||||
@async_only_middleware
|
||||
class RateLimitMiddleware:
|
||||
def __init__(self, get_response) -> None:
|
||||
|
|
|
|||
|
|
@ -14,19 +14,9 @@ Main components:
|
|||
"""
|
||||
|
||||
# Models
|
||||
from .models import (
|
||||
LLMCall,
|
||||
OptimizationError,
|
||||
)
|
||||
|
||||
# Logging utilities
|
||||
from .logger import (
|
||||
StructuredFormatter,
|
||||
get_observability_logger,
|
||||
log_with_context,
|
||||
LogContext,
|
||||
EventType,
|
||||
)
|
||||
from .logger import EventType, LogContext, StructuredFormatter, get_observability_logger, log_with_context
|
||||
from .models import LLMCall, OptimizationError
|
||||
|
||||
__all__ = [
|
||||
# Models
|
||||
|
|
@ -38,4 +28,4 @@ __all__ = [
|
|||
"log_with_context",
|
||||
"LogContext",
|
||||
"EventType",
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -4,13 +4,10 @@ Simple recorders for manually tracking LLM calls, steps, and errors.
|
|||
No decorators - just call the methods directly where needed.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from asgiref.sync import sync_to_async
|
||||
from django.utils import timezone as django_timezone
|
||||
|
||||
from .models import LLMCall, OptimizationError
|
||||
|
||||
|
|
@ -67,6 +64,7 @@ class LLMCallRecorder:
|
|||
|
||||
Returns:
|
||||
llm_call_id: UUID of the created record
|
||||
|
||||
"""
|
||||
llm_call_id = str(uuid.uuid4())
|
||||
|
||||
|
|
@ -150,12 +148,7 @@ class LLMCallRecorder:
|
|||
|
||||
@sync_to_async
|
||||
def record_llm_call_failure(
|
||||
self,
|
||||
llm_call_id: str,
|
||||
error_type: str,
|
||||
error_message: str,
|
||||
latency_ms: int | None = None,
|
||||
retry_count: int = 0,
|
||||
self, llm_call_id: str, error_type: str, error_message: str, latency_ms: int | None = None, retry_count: int = 0
|
||||
) -> None:
|
||||
"""Update LLM call record with failure information."""
|
||||
LLMCall.objects.filter(id=llm_call_id).update(
|
||||
|
|
@ -200,6 +193,7 @@ class ErrorRecorder:
|
|||
|
||||
Returns:
|
||||
error_id: UUID of the created error record
|
||||
|
||||
"""
|
||||
error_id = str(uuid.uuid4())
|
||||
|
||||
|
|
@ -239,6 +233,7 @@ async def track_llm_call_simple(
|
|||
|
||||
Returns:
|
||||
llm_call_id: UUID of the created record
|
||||
|
||||
"""
|
||||
recorder = LLMCallRecorder()
|
||||
|
||||
|
|
@ -266,4 +261,4 @@ async def track_llm_call_simple(
|
|||
candidates_valid=candidates_valid,
|
||||
)
|
||||
|
||||
return llm_call_id
|
||||
return llm_call_id
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
"""
|
||||
Observability decorators for automatic LLM call tracking.
|
||||
"""Observability decorators for automatic LLM call tracking.
|
||||
|
||||
Usage:
|
||||
@observe_llm_call(call_type="optimization")
|
||||
|
|
@ -18,22 +17,20 @@ The decorator automatically:
|
|||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, Optional
|
||||
from typing import Any
|
||||
|
||||
from aiservice.models.aimodels import calculate_llm_cost
|
||||
from aiservice.observability.database import LLMCallRecorder, ErrorRecorder
|
||||
from aiservice.observability.database import ErrorRecorder, LLMCallRecorder
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def observe_llm_call(
|
||||
call_type: str,
|
||||
extract_trace_id: Optional[Callable] = None,
|
||||
extract_model_name: Optional[Callable] = None,
|
||||
call_type: str, extract_trace_id: Callable | None = None, extract_model_name: Callable | None = None
|
||||
):
|
||||
"""
|
||||
Decorator to automatically observe LLM calls.
|
||||
"""Decorator to automatically observe LLM calls.
|
||||
|
||||
Args:
|
||||
call_type: Type of LLM call ('optimization', 'test_generation', etc.)
|
||||
|
|
@ -51,6 +48,7 @@ def observe_llm_call(
|
|||
):
|
||||
# Just the LLM call - observability is automatic
|
||||
return await llm_client.chat.completions.create(...)
|
||||
|
||||
"""
|
||||
|
||||
def decorator(func: Callable) -> Callable:
|
||||
|
|
@ -154,12 +152,12 @@ async def _record_start_background(
|
|||
system_prompt: str,
|
||||
user_prompt: str,
|
||||
messages: list,
|
||||
temperature: Optional[float],
|
||||
temperature: float | None,
|
||||
n_candidates: int,
|
||||
user_id: Optional[str],
|
||||
python_version: Optional[str],
|
||||
context: Optional[dict],
|
||||
) -> Optional[str]:
|
||||
user_id: str | None,
|
||||
python_version: str | None,
|
||||
context: dict | None,
|
||||
) -> str | None:
|
||||
"""Record LLM call start in background (non-blocking)."""
|
||||
try:
|
||||
# If messages not provided, construct from prompts
|
||||
|
|
@ -190,12 +188,7 @@ async def _record_start_background(
|
|||
|
||||
|
||||
def _record_completion_background(
|
||||
llm_recorder: LLMCallRecorder,
|
||||
llm_call_id: str,
|
||||
status: str,
|
||||
result: Any,
|
||||
latency_ms: int,
|
||||
model_name: str,
|
||||
llm_recorder: LLMCallRecorder, llm_call_id: str, status: str, result: Any, latency_ms: int, model_name: str
|
||||
):
|
||||
"""Record LLM call completion in background (non-blocking)."""
|
||||
|
||||
|
|
@ -256,7 +249,7 @@ def _record_error_background(
|
|||
error_recorder: ErrorRecorder,
|
||||
llm_recorder: LLMCallRecorder,
|
||||
trace_id: str,
|
||||
llm_call_id: Optional[str],
|
||||
llm_call_id: str | None,
|
||||
error: Exception,
|
||||
latency_ms: int,
|
||||
model_name: str,
|
||||
|
|
@ -274,10 +267,7 @@ def _record_error_background(
|
|||
severity="error",
|
||||
error_message=str(error),
|
||||
error_code=type(error).__name__,
|
||||
context={
|
||||
"model": model_name,
|
||||
"n_candidates": n_candidates,
|
||||
},
|
||||
context={"model": model_name, "n_candidates": n_candidates},
|
||||
)
|
||||
|
||||
# Update LLM call status to failed
|
||||
|
|
@ -296,7 +286,7 @@ def _record_error_background(
|
|||
asyncio.create_task(_record())
|
||||
|
||||
|
||||
def _extract_trace_id(args: tuple, kwargs: dict, custom_extractor: Optional[Callable]) -> str:
|
||||
def _extract_trace_id(args: tuple, kwargs: dict, custom_extractor: Callable | None) -> str:
|
||||
"""Extract trace_id from function arguments."""
|
||||
if custom_extractor:
|
||||
return custom_extractor(args, kwargs)
|
||||
|
|
@ -317,7 +307,7 @@ def _extract_trace_id(args: tuple, kwargs: dict, custom_extractor: Optional[Call
|
|||
return "unknown"
|
||||
|
||||
|
||||
def _extract_model_name(args: tuple, kwargs: dict, custom_extractor: Optional[Callable]) -> str:
|
||||
def _extract_model_name(args: tuple, kwargs: dict, custom_extractor: Callable | None) -> str:
|
||||
"""Extract model name from function arguments."""
|
||||
if custom_extractor:
|
||||
return custom_extractor(args, kwargs)
|
||||
|
|
@ -336,4 +326,4 @@ def _extract_model_name(args: tuple, kwargs: dict, custom_extractor: Optional[Ca
|
|||
return model
|
||||
|
||||
logger.warning("Observability: Could not extract model_name from function arguments")
|
||||
return "unknown"
|
||||
return "unknown"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ that can be easily ingested into log analysis systems.
|
|||
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ class StructuredFormatter(logging.Formatter):
|
|||
def format(self, record: logging.LogRecord) -> str:
|
||||
"""Format log record as JSON."""
|
||||
log_data: dict[str, Any] = {
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"timestamp": datetime.now(UTC).isoformat(),
|
||||
"log_level": record.levelname,
|
||||
"logger_name": record.name,
|
||||
"message": record.getMessage(),
|
||||
|
|
@ -81,6 +81,7 @@ def get_observability_logger(name: str, component: str = "aiservice") -> logging
|
|||
"metadata": {"model": "gpt-4", "n_candidates": 5}
|
||||
}
|
||||
)
|
||||
|
||||
"""
|
||||
logger_name = f"{component}.{name}"
|
||||
logger = logging.getLogger(logger_name)
|
||||
|
|
@ -121,6 +122,7 @@ def log_with_context(
|
|||
event_type='llm.call_success',
|
||||
metadata={'model': 'gpt-4', 'latency_ms': 1250}
|
||||
)
|
||||
|
||||
"""
|
||||
extra: dict[str, Any] = {}
|
||||
|
||||
|
|
@ -151,13 +153,7 @@ class LogContext:
|
|||
# All logs will include trace_id and user_id
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
logger: logging.Logger,
|
||||
trace_id: str | None = None,
|
||||
user_id: str | None = None,
|
||||
**kwargs: Any
|
||||
):
|
||||
def __init__(self, logger: logging.Logger, trace_id: str | None = None, user_id: str | None = None, **kwargs: Any):
|
||||
"""Initialize log context.
|
||||
|
||||
Args:
|
||||
|
|
@ -165,6 +161,7 @@ class LogContext:
|
|||
trace_id: Optimization trace ID
|
||||
user_id: User ID
|
||||
**kwargs: Additional context fields to include in all logs
|
||||
|
||||
"""
|
||||
self.logger = logger
|
||||
self.context: dict[str, Any] = {}
|
||||
|
|
@ -182,14 +179,9 @@ class LogContext:
|
|||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Exit context."""
|
||||
# Context is automatically released
|
||||
pass
|
||||
|
||||
def log(
|
||||
self,
|
||||
level: str,
|
||||
message: str,
|
||||
event_type: str | None = None,
|
||||
metadata: dict[str, Any] | None = None
|
||||
self, level: str, message: str, event_type: str | None = None, metadata: dict[str, Any] | None = None
|
||||
) -> None:
|
||||
"""Log message with context."""
|
||||
extra = dict(self.context)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
These models map to Prisma-managed tables for LLM tracking, step tracing,
|
||||
and error monitoring in the optimization pipeline.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
|
|
@ -83,7 +85,9 @@ class OptimizationError(models.Model):
|
|||
llm_call_id = models.UUIDField(null=True, blank=True)
|
||||
|
||||
# Error classification
|
||||
error_type = models.CharField(max_length=50) # 'validation', 'llm_api', 'llm_parsing', 'test_failure', 'compilation'
|
||||
error_type = models.CharField(
|
||||
max_length=50
|
||||
) # 'validation', 'llm_api', 'llm_parsing', 'test_failure', 'compilation'
|
||||
error_category = models.CharField(max_length=50) # 'user_error', 'system_error', 'llm_error', 'infrastructure'
|
||||
severity = models.CharField(max_length=20) # 'critical', 'error', 'warning', 'info'
|
||||
|
||||
|
|
|
|||
|
|
@ -58,9 +58,11 @@ async def code_repair( # noqa: D417
|
|||
- original code
|
||||
- optimized code
|
||||
- behaviour test diffs
|
||||
|
||||
Returns
|
||||
-------
|
||||
CodeRepairIntermediateResponseItemschema or CodeRepairErrorResponseSchema
|
||||
|
||||
"""
|
||||
system_prompt = ctx.get_system_prompt()
|
||||
user_prompt = ctx.get_user_prompt()
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ from enum import Enum
|
|||
|
||||
import libcst as cst
|
||||
import sentry_sdk
|
||||
from aiservice.env_specific import debug_log_sensitive_data
|
||||
from ninja import Field, Schema
|
||||
from pydantic import ValidationError
|
||||
|
||||
from aiservice.env_specific import debug_log_sensitive_data
|
||||
from optimizer.context_utils.constants import REPLACE_IN_FILE_TAGS_REGEX
|
||||
from optimizer.context_utils.context_helpers import group_code, is_markdown_structure_changed, split_markdown_code
|
||||
from optimizer.diff_patches_utils.patches_v2 import apply_patches, group_diff_patches_by_path
|
||||
from optimizer.models import CodeAndExplanation
|
||||
from pydantic import ValidationError
|
||||
from testgen.instrumentation.edit_generated_test import parse_module_to_cst
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,26 +6,28 @@ import time
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
import sentry_sdk
|
||||
from aiservice.analytics.posthog import ph
|
||||
from aiservice.common_utils import validate_trace_id
|
||||
from aiservice.env_specific import create_llm_client, debug_log_sensitive_data, llm_clients
|
||||
from aiservice.models.aimodels import EXPLANATIONS_MODEL, LLM, calculate_llm_cost
|
||||
from aiservice.observability.database import LLMCallRecorder, ErrorRecorder
|
||||
from log_features.log_event import update_optimization_cost
|
||||
from log_features.log_features import log_features
|
||||
from ninja import NinjaAPI, Schema
|
||||
from openai import OpenAIError
|
||||
from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam
|
||||
from packaging import version
|
||||
|
||||
from aiservice.analytics.posthog import ph
|
||||
from aiservice.common_utils import validate_trace_id
|
||||
from aiservice.env_specific import debug_log_sensitive_data, llm_clients
|
||||
from aiservice.models.aimodels import EXPLANATIONS_MODEL, LLM, calculate_llm_cost
|
||||
from aiservice.observability.database import ErrorRecorder, LLMCallRecorder
|
||||
from log_features.log_event import update_optimization_cost
|
||||
from log_features.log_features import log_features
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiservice.models.aimodels import LLM
|
||||
from openai.types.chat import (
|
||||
ChatCompletionAssistantMessageParam,
|
||||
ChatCompletionFunctionMessageParam,
|
||||
ChatCompletionToolMessageParam,
|
||||
)
|
||||
|
||||
from aiservice.models.aimodels import LLM
|
||||
|
||||
explanations_api = NinjaAPI(urls_namespace="explanations")
|
||||
explain_regex_pattern = re.compile(r"<explain>(.*)<\/explain>", re.DOTALL | re.IGNORECASE)
|
||||
|
||||
|
|
@ -235,10 +237,7 @@ async def explain_optimizations( # noqa: D417
|
|||
temperature=None,
|
||||
n_candidates=1,
|
||||
user_id=user_id,
|
||||
context={
|
||||
"optimization_id": data.optimization_id,
|
||||
"speedup": data.speedup,
|
||||
},
|
||||
context={"optimization_id": data.optimization_id, "speedup": data.speedup},
|
||||
)
|
||||
except Exception as obs_error:
|
||||
logging.warning(f"Observability recording failed (explain start): {obs_error}")
|
||||
|
|
@ -265,10 +264,7 @@ async def explain_optimizations( # noqa: D417
|
|||
severity="error",
|
||||
error_message=str(e),
|
||||
error_code=type(e).__name__,
|
||||
context={
|
||||
"model": explanations_model.name,
|
||||
"optimization_id": data.optimization_id,
|
||||
},
|
||||
context={"model": explanations_model.name, "optimization_id": data.optimization_id},
|
||||
)
|
||||
if llm_call_id:
|
||||
await llm_recorder.record_llm_call_completion(
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ from enum import Enum
|
|||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
import sentry_sdk
|
||||
from ninja import NinjaAPI, Schema
|
||||
from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam
|
||||
from packaging import version
|
||||
|
||||
from aiservice.env_specific import debug_log_sensitive_data, llm_clients
|
||||
from aiservice.models.aimodels import OPTIMIZATION_REVIEW_MODEL, calculate_llm_cost
|
||||
from aiservice.observability.decorators import observe_llm_call
|
||||
from log_features.log_event import update_optimization_cost, update_optimization_features_review
|
||||
from ninja import NinjaAPI, Schema
|
||||
from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam
|
||||
from packaging import version
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiservice.models.aimodels import LLM
|
||||
|
|
@ -144,14 +145,9 @@ Output as a json markdown block with the key named as 'rating' and value being o
|
|||
|
||||
@observe_llm_call("optimization_review")
|
||||
async def call_optimization_review_llm(
|
||||
trace_id: str,
|
||||
model: LLM,
|
||||
messages: list[dict[str, str]],
|
||||
user_id: str | None = None,
|
||||
context: dict | None = None,
|
||||
trace_id: str, model: LLM, messages: list[dict[str, str]], user_id: str | None = None, context: dict | None = None
|
||||
):
|
||||
"""
|
||||
Call LLM for optimization review with automatic observability.
|
||||
"""Call LLM for optimization review with automatic observability.
|
||||
|
||||
This function is decorated with @observe_llm_call which automatically:
|
||||
- Records call start (non-blocking)
|
||||
|
|
@ -165,9 +161,7 @@ async def call_optimization_review_llm(
|
|||
if llm_client is None:
|
||||
raise ValueError(f"LLM client for model type '{model.model_type}' is not available")
|
||||
|
||||
return await llm_client.with_options(max_retries=2).chat.completions.create(
|
||||
model=model.name, messages=messages
|
||||
)
|
||||
return await llm_client.with_options(max_retries=2).chat.completions.create(model=model.name, messages=messages)
|
||||
|
||||
|
||||
async def get_optimization_review(
|
||||
|
|
@ -187,9 +181,7 @@ async def get_optimization_review(
|
|||
model=optimization_review_model,
|
||||
messages=messages,
|
||||
user_id=request.user,
|
||||
context={
|
||||
"speedup": data.speedup,
|
||||
},
|
||||
context={"speedup": data.speedup},
|
||||
)
|
||||
|
||||
# Calculate and update cost
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ from dataclasses import dataclass
|
|||
from typing import Never
|
||||
|
||||
import libcst as cst
|
||||
from aiservice.env_specific import debug_log_sensitive_data
|
||||
from pydantic import ValidationError
|
||||
from testgen.instrumentation.edit_generated_test import parse_module_to_cst
|
||||
|
||||
from aiservice.env_specific import debug_log_sensitive_data
|
||||
from optimizer.context_utils.constants import (
|
||||
MULTI_REPLACE_IN_FILE_TAGS_REGEX,
|
||||
REPLACE_IN_FILE_REGEX,
|
||||
|
|
@ -19,6 +18,7 @@ from optimizer.context_utils.context_helpers import (
|
|||
)
|
||||
from optimizer.diff_patches_utils.patches_v2 import apply_patches
|
||||
from optimizer.models import CodeAndExplanation
|
||||
from testgen.instrumentation.edit_generated_test import parse_module_to_cst
|
||||
|
||||
|
||||
@dataclass()
|
||||
|
|
|
|||
|
|
@ -48,17 +48,13 @@ def parse_diff(diff: str) -> list[SearchReplaceBlock]:
|
|||
replace_end = idx
|
||||
|
||||
if idx >= n:
|
||||
raise ValueError(
|
||||
"Invalid diff format: Missing '>>>>>>> REPLACE' marker"
|
||||
)
|
||||
raise ValueError("Invalid diff format: Missing '>>>>>>> REPLACE' marker")
|
||||
|
||||
search_content = "".join(lines[search_start:search_end]).rstrip()
|
||||
replace_content = "".join(lines[replace_start:replace_end]).rstrip()
|
||||
|
||||
try:
|
||||
block = SearchReplaceBlock.from_block(
|
||||
search=search_content, replace=replace_content
|
||||
)
|
||||
block = SearchReplaceBlock.from_block(search=search_content, replace=replace_content)
|
||||
blocks.append(block)
|
||||
except ValidationError as ve:
|
||||
raise ValueError(f"Invalid block format: {ve}")
|
||||
|
|
@ -111,7 +107,5 @@ def apply_patches(diff_str: str, content: str) -> str:
|
|||
start_char_idx = content.find(block.search)
|
||||
if start_char_idx != -1:
|
||||
end_char_idx = start_char_idx + len(block.search)
|
||||
content = (
|
||||
f"{content[:start_char_idx]}{block.replace}{content[end_char_idx:]}"
|
||||
)
|
||||
content = f"{content[:start_char_idx]}{block.replace}{content[end_char_idx:]}"
|
||||
return content
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import libcst as cst
|
|||
import sentry_sdk
|
||||
from ninja import NinjaAPI
|
||||
from ninja.errors import HttpError
|
||||
from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam
|
||||
from pydantic import ValidationError
|
||||
|
||||
from aiservice.analytics.posthog import ph
|
||||
|
|
@ -32,11 +31,6 @@ from optimizer.models import OptimizedCandidateSource, OptimizeSchema # noqa: T
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from django.http import HttpRequest
|
||||
from openai.types.chat import (
|
||||
ChatCompletionAssistantMessageParam,
|
||||
ChatCompletionFunctionMessageParam,
|
||||
ChatCompletionToolMessageParam,
|
||||
)
|
||||
|
||||
from aiservice.models.aimodels import LLM
|
||||
|
||||
|
|
@ -129,8 +123,7 @@ async def call_optimization_llm(
|
|||
user_id: str | None = None,
|
||||
python_version: str | None = None,
|
||||
):
|
||||
"""
|
||||
Call LLM for code optimization with automatic observability.
|
||||
"""Call LLM for code optimization with automatic observability.
|
||||
|
||||
This function is decorated with @observe_llm_call which automatically:
|
||||
- Records call start (non-blocking)
|
||||
|
|
@ -146,10 +139,7 @@ async def call_optimization_llm(
|
|||
|
||||
return await llm_client.with_options(max_retries=3).chat.completions.create(
|
||||
model=model.name,
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_prompt},
|
||||
],
|
||||
messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}],
|
||||
n=n,
|
||||
)
|
||||
|
||||
|
|
@ -386,7 +376,10 @@ async def optimize(
|
|||
},
|
||||
explanations_post={cei.optimization_id: cei.explanation for cei in optimization_response_items},
|
||||
experiment_metadata=data.experiment_metadata if data.experiment_metadata else None,
|
||||
optimizations_origin={cei.optimization_id: {"source": OptimizedCandidateSource.OPTIMIZE, "parent": None} for cei in optimization_response_items},
|
||||
optimizations_origin={
|
||||
cei.optimization_id: {"source": OptimizedCandidateSource.OPTIMIZE, "parent": None}
|
||||
for cei in optimization_response_items
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -52,8 +52,7 @@ async def call_line_profiler_llm(
|
|||
python_version: str | None = None,
|
||||
context: dict | None = None,
|
||||
):
|
||||
"""
|
||||
Call LLM for line profiler optimization with automatic observability.
|
||||
"""Call LLM for line profiler optimization with automatic observability.
|
||||
|
||||
This function is decorated with @observe_llm_call which automatically:
|
||||
- Records call start (non-blocking)
|
||||
|
|
@ -130,9 +129,7 @@ async def optimize_python_code_line_profiler( # noqa: D417
|
|||
n=n,
|
||||
user_id=user_id,
|
||||
python_version=python_version_str,
|
||||
context={
|
||||
"lsp_mode": lsp_mode,
|
||||
},
|
||||
context={"lsp_mode": lsp_mode},
|
||||
)
|
||||
await update_optimization_cost(trace_id=trace_id, cost=calculate_llm_cost(output, optimize_model))
|
||||
except Exception as e:
|
||||
|
|
@ -231,8 +228,10 @@ async def optimize(request, data: OptimizeSchemaLP) -> tuple[int, OptimizeRespon
|
|||
},
|
||||
explanations_post={cei.optimization_id: cei.explanation for cei in optimization_response_items},
|
||||
experiment_metadata=data.experiment_metadata if data.experiment_metadata else None,
|
||||
optimizations_origin={cei.optimization_id: {"source": OptimizedCandidateSource.OPTIMIZE_LP, "parent": None} for cei in optimization_response_items},
|
||||
|
||||
optimizations_origin={
|
||||
cei.optimization_id: {"source": OptimizedCandidateSource.OPTIMIZE_LP, "parent": None}
|
||||
for cei in optimization_response_items
|
||||
},
|
||||
)
|
||||
|
||||
response = OptimizeResponseSchema(optimizations=optimization_response_items)
|
||||
|
|
|
|||
|
|
@ -274,7 +274,6 @@ def _strip_comments_from_code(code: str) -> str:
|
|||
The same code with all comments removed, preserving string content
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
lines = code.splitlines(keepends=True)
|
||||
tokens = tokenize.generate_tokens(io.StringIO(code).readline)
|
||||
|
|
@ -437,91 +436,100 @@ def clean_extraneous_comments(original_module: cst.Module, optimized_module: cst
|
|||
elif is_comment_only and not is_near_change:
|
||||
# Comment line not near a code change - skip it
|
||||
pass
|
||||
# Code line - check if it changed or not
|
||||
# If changed, use optimized version. If unchanged, use original version.
|
||||
elif opt_idx in code_changed_lines:
|
||||
# Code changed - just use the optimized line as-is, no restoration
|
||||
result_lines.append(opt_line)
|
||||
else:
|
||||
# Code line - check if it changed or not
|
||||
# If changed, use optimized version. If unchanged, use original version.
|
||||
if opt_idx in code_changed_lines:
|
||||
# Code changed - just use the optimized line as-is, no restoration
|
||||
result_lines.append(opt_line)
|
||||
else:
|
||||
# Code didn't change - find and use original (including any preceding comments)
|
||||
found_orig = None
|
||||
orig_line_idx = None
|
||||
for orig_idx_search in range(orig_idx, len(orig_lines)):
|
||||
orig_code = orig_code_only[orig_idx_search] if orig_idx_search < len(orig_code_only) else ""
|
||||
if orig_code == opt_code:
|
||||
found_orig = orig_lines[orig_idx_search]
|
||||
orig_line_idx = orig_idx_search
|
||||
orig_idx = orig_idx_search + 1
|
||||
break
|
||||
# Code didn't change - find and use original (including any preceding comments)
|
||||
found_orig = None
|
||||
orig_line_idx = None
|
||||
for orig_idx_search in range(orig_idx, len(orig_lines)):
|
||||
orig_code = orig_code_only[orig_idx_search] if orig_idx_search < len(orig_code_only) else ""
|
||||
if orig_code == opt_code:
|
||||
found_orig = orig_lines[orig_idx_search]
|
||||
orig_line_idx = orig_idx_search
|
||||
orig_idx = orig_idx_search + 1
|
||||
break
|
||||
|
||||
if found_orig:
|
||||
# Check if there are comment-only/blank lines in the original that come before this line
|
||||
# BUT ONLY restore them if this code line is UNCHANGED (not in code_changed_lines)
|
||||
# Don't restore original comments before CHANGED code lines
|
||||
if orig_line_idx is not None and orig_line_idx > 0 and opt_idx not in code_changed_lines:
|
||||
# Look backwards for ALL consecutive comment-only or blank lines
|
||||
# Collect them all, then decide which ones to restore
|
||||
preceding_lines = []
|
||||
check_idx = orig_line_idx - 1
|
||||
if found_orig:
|
||||
# Check if there are comment-only/blank lines in the original that come before this line
|
||||
# BUT ONLY restore them if this code line is UNCHANGED (not in code_changed_lines)
|
||||
# Don't restore original comments before CHANGED code lines
|
||||
if orig_line_idx is not None and orig_line_idx > 0 and opt_idx not in code_changed_lines:
|
||||
# Look backwards for ALL consecutive comment-only or blank lines
|
||||
# Collect them all, then decide which ones to restore
|
||||
preceding_lines = []
|
||||
check_idx = orig_line_idx - 1
|
||||
|
||||
while check_idx >= 0:
|
||||
check_code = orig_code_only[check_idx] if check_idx < len(orig_code_only) else ""
|
||||
if not check_code.strip():
|
||||
# This is a comment-only or blank line in the original
|
||||
# Add it if not already restored and if it was removed from optimized
|
||||
if check_idx not in orig_to_opt_mapping and check_idx not in restored_orig_indices:
|
||||
preceding_lines.insert(0, orig_lines[check_idx])
|
||||
restored_orig_indices.add(check_idx)
|
||||
check_idx -= 1
|
||||
else:
|
||||
# Hit a line with actual code - stop looking backwards
|
||||
break
|
||||
while check_idx >= 0:
|
||||
check_code = orig_code_only[check_idx] if check_idx < len(orig_code_only) else ""
|
||||
if not check_code.strip():
|
||||
# This is a comment-only or blank line in the original
|
||||
# Add it if not already restored and if it was removed from optimized
|
||||
if (
|
||||
check_idx not in orig_to_opt_mapping
|
||||
and check_idx not in restored_orig_indices
|
||||
):
|
||||
preceding_lines.insert(0, orig_lines[check_idx])
|
||||
restored_orig_indices.add(check_idx)
|
||||
check_idx -= 1
|
||||
else:
|
||||
# Hit a line with actual code - stop looking backwards
|
||||
break
|
||||
|
||||
# Add the restored comments/blank lines before the actual line
|
||||
result_lines.extend(preceding_lines)
|
||||
# Add the restored comments/blank lines before the actual line
|
||||
result_lines.extend(preceding_lines)
|
||||
|
||||
# Use the original line (preserves original comments or lack thereof)
|
||||
result_lines.append(found_orig)
|
||||
restored_orig_indices.add(orig_line_idx)
|
||||
# Use the original line (preserves original comments or lack thereof)
|
||||
result_lines.append(found_orig)
|
||||
restored_orig_indices.add(orig_line_idx)
|
||||
|
||||
# Also check for trailing blank/comment lines after this line that were removed
|
||||
# BUT: only restore them if this code line is UNCHANGED and they don't come before a changed line
|
||||
# (in that case, the new comment from optimized should be kept)
|
||||
if orig_line_idx is not None and orig_line_idx < len(orig_lines) - 1 and opt_idx not in code_changed_lines:
|
||||
trailing_lines = []
|
||||
check_idx = orig_line_idx + 1
|
||||
found_changed_line = False
|
||||
# Also check for trailing blank/comment lines after this line that were removed
|
||||
# BUT: only restore them if this code line is UNCHANGED and they don't come before a changed line
|
||||
# (in that case, the new comment from optimized should be kept)
|
||||
if (
|
||||
orig_line_idx is not None
|
||||
and orig_line_idx < len(orig_lines) - 1
|
||||
and opt_idx not in code_changed_lines
|
||||
):
|
||||
trailing_lines = []
|
||||
check_idx = orig_line_idx + 1
|
||||
found_changed_line = False
|
||||
|
||||
while check_idx < len(orig_lines):
|
||||
check_code = orig_code_only[check_idx] if check_idx < len(orig_code_only) else ""
|
||||
if not check_code.strip():
|
||||
# This is a comment-only or blank line in the original
|
||||
# Check if it was removed (not in the optimized version)
|
||||
if check_idx not in orig_to_opt_mapping and check_idx not in restored_orig_indices:
|
||||
trailing_lines.append(orig_lines[check_idx])
|
||||
restored_orig_indices.add(check_idx)
|
||||
check_idx += 1
|
||||
else:
|
||||
# Hit a line with actual code
|
||||
# Check if this code line was changed in the optimized version
|
||||
if check_idx in orig_to_opt_mapping:
|
||||
next_opt_idx = orig_to_opt_mapping[check_idx]
|
||||
if next_opt_idx in code_changed_lines:
|
||||
found_changed_line = True
|
||||
else:
|
||||
# This original line is not in the mapping, which means
|
||||
# it was either deleted or modified. In either case,
|
||||
# this is a changed line.
|
||||
while check_idx < len(orig_lines):
|
||||
check_code = orig_code_only[check_idx] if check_idx < len(orig_code_only) else ""
|
||||
if not check_code.strip():
|
||||
# This is a comment-only or blank line in the original
|
||||
# Check if it was removed (not in the optimized version)
|
||||
if (
|
||||
check_idx not in orig_to_opt_mapping
|
||||
and check_idx not in restored_orig_indices
|
||||
):
|
||||
trailing_lines.append(orig_lines[check_idx])
|
||||
restored_orig_indices.add(check_idx)
|
||||
check_idx += 1
|
||||
else:
|
||||
# Hit a line with actual code
|
||||
# Check if this code line was changed in the optimized version
|
||||
if check_idx in orig_to_opt_mapping:
|
||||
next_opt_idx = orig_to_opt_mapping[check_idx]
|
||||
if next_opt_idx in code_changed_lines:
|
||||
found_changed_line = True
|
||||
break
|
||||
else:
|
||||
# This original line is not in the mapping, which means
|
||||
# it was either deleted or modified. In either case,
|
||||
# this is a changed line.
|
||||
found_changed_line = True
|
||||
break
|
||||
|
||||
# Only add trailing comments if they're NOT immediately before a changed line
|
||||
if not found_changed_line:
|
||||
result_lines.extend(trailing_lines)
|
||||
else:
|
||||
# Keep it (shouldn't happen but be safe)
|
||||
result_lines.append(opt_line)
|
||||
# Only add trailing comments if they're NOT immediately before a changed line
|
||||
if not found_changed_line:
|
||||
result_lines.extend(trailing_lines)
|
||||
else:
|
||||
# Keep it (shouldn't happen but be safe)
|
||||
result_lines.append(opt_line)
|
||||
|
||||
# Parse the cleaned code back into a CST module
|
||||
cleaned_code = "".join(result_lines)
|
||||
|
|
|
|||
|
|
@ -189,14 +189,9 @@ Here is the function_references
|
|||
|
||||
@observe_llm_call("refinement")
|
||||
async def call_refinement_llm(
|
||||
trace_id: str,
|
||||
model: LLM,
|
||||
messages: list[dict[str, str]],
|
||||
user_id: str | None = None,
|
||||
context: dict | None = None,
|
||||
trace_id: str, model: LLM, messages: list[dict[str, str]], user_id: str | None = None, context: dict | None = None
|
||||
):
|
||||
"""
|
||||
Call LLM for refinement with automatic observability.
|
||||
"""Call LLM for refinement with automatic observability.
|
||||
|
||||
This function is decorated with @observe_llm_call which automatically:
|
||||
- Records call start (non-blocking)
|
||||
|
|
@ -270,10 +265,7 @@ async def refinement( # noqa: D417
|
|||
model=optimize_model,
|
||||
messages=messages,
|
||||
user_id=user_id,
|
||||
context={
|
||||
"optimization_id": optimization_id,
|
||||
"speedup": ctx.data.speedup,
|
||||
},
|
||||
context={"optimization_id": optimization_id, "speedup": ctx.data.speedup},
|
||||
)
|
||||
llm_cost = calculate_llm_cost(output, optimize_model)
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import re
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
import sentry_sdk
|
||||
from ninja import NinjaAPI, Schema
|
||||
from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam
|
||||
|
||||
from aiservice.analytics.posthog import ph
|
||||
from aiservice.common_utils import validate_trace_id
|
||||
from aiservice.env_specific import debug_log_sensitive_data, llm_clients
|
||||
|
|
@ -11,17 +14,16 @@ from aiservice.models.aimodels import LLM, RANKING_MODEL, calculate_llm_cost
|
|||
from aiservice.observability.decorators import observe_llm_call
|
||||
from log_features.log_event import update_optimization_cost
|
||||
from log_features.log_features import log_features
|
||||
from ninja import NinjaAPI, Schema
|
||||
from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiservice.models.aimodels import LLM
|
||||
from openai.types.chat import (
|
||||
ChatCompletionAssistantMessageParam,
|
||||
ChatCompletionFunctionMessageParam,
|
||||
ChatCompletionToolMessageParam,
|
||||
)
|
||||
|
||||
from aiservice.models.aimodels import LLM
|
||||
|
||||
# from google import genai
|
||||
# from pydantic import BaseModel
|
||||
#
|
||||
|
|
@ -75,14 +77,9 @@ Here are the function references
|
|||
|
||||
@observe_llm_call("ranking")
|
||||
async def call_ranker_llm(
|
||||
trace_id: str,
|
||||
model: LLM,
|
||||
messages: list[dict[str, str]],
|
||||
user_id: str | None = None,
|
||||
context: dict | None = None,
|
||||
trace_id: str, model: LLM, messages: list[dict[str, str]], user_id: str | None = None, context: dict | None = None
|
||||
):
|
||||
"""
|
||||
Call LLM for ranking with automatic observability.
|
||||
"""Call LLM for ranking with automatic observability.
|
||||
|
||||
This function is decorated with @observe_llm_call which automatically:
|
||||
- Records call start (non-blocking)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
from libcst import CSTTransformer, ImportAlias, ImportFrom, MetadataWrapper, Name, parse_expression, parse_module
|
||||
from functools import lru_cache
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from libcst import (
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ from typing import TYPE_CHECKING, SupportsIndex
|
|||
|
||||
import sentry_sdk
|
||||
import stamina
|
||||
from ninja import NinjaAPI
|
||||
from ninja.errors import HttpError
|
||||
from openai import OpenAIError
|
||||
|
||||
from aiservice.analytics.posthog import ph
|
||||
from aiservice.common_utils import parse_python_version, safe_isort, should_hack_for_demo, validate_trace_id
|
||||
from aiservice.env_specific import IS_PRODUCTION, debug_log_sensitive_data, llm_clients
|
||||
|
|
@ -17,10 +21,6 @@ from aiservice.models.aimodels import EXECUTE_MODEL, calculate_llm_cost
|
|||
from aiservice.observability.decorators import observe_llm_call
|
||||
from log_features.log_event import update_optimization_cost
|
||||
from log_features.log_features import log_features
|
||||
from ninja import NinjaAPI
|
||||
from ninja.errors import HttpError
|
||||
from openai import OpenAIError
|
||||
|
||||
from testgen.instrumentation.edit_generated_test import parse_module_to_cst, replace_definition_with_import
|
||||
from testgen.instrumentation.instrument_new_tests import instrument_test_source
|
||||
from testgen.models import (
|
||||
|
|
@ -199,8 +199,7 @@ async def call_testgen_llm(
|
|||
user_id: str | None = None,
|
||||
python_version: str | None = None,
|
||||
):
|
||||
"""
|
||||
Call LLM for test generation with automatic observability.
|
||||
"""Call LLM for test generation with automatic observability.
|
||||
|
||||
This function is decorated with @observe_llm_call which automatically:
|
||||
- Records call start (non-blocking)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
|
||||
|
||||
from code_repair.code_repair_context import CodeRepairContext, CodeRepairContextData
|
||||
from optimizer.diff_patches_utils.patches_v2 import apply_patches
|
||||
|
||||
|
||||
def test_code_repair_single_file():
|
||||
|
||||
original_code = """```python:demo.py
|
||||
import math
|
||||
from typing import List, Tuple, Optional
|
||||
|
|
@ -93,7 +90,6 @@ def calculate_portfolio_metrics(
|
|||
```
|
||||
"""
|
||||
|
||||
|
||||
llm_response = """Looking at the test failure and comparing the original and modified code, I can see several issues in the modified implementation:
|
||||
|
||||
1. **Hardcoded volatility**: The volatility is hardcoded to `2` instead of being calculated
|
||||
|
|
@ -211,7 +207,7 @@ The key changes I made:
|
|||
6. **Fixed dictionary formatting**: Changed from double quotes to single quotes to match original formatting
|
||||
|
||||
These changes align the modified code with the original implementation's behavior, ensuring that the test for zero volatility passes (when cash investment has 0% return, the volatility should indeed be 0.0)."""
|
||||
ctx = CodeRepairContext(CodeRepairContextData(original_code, optimized_code, ""), "" , "")
|
||||
ctx = CodeRepairContext(CodeRepairContextData(original_code, optimized_code, ""), "", "")
|
||||
diff_patches = ctx.extract_diff_patches_from_llm_res(llm_response)
|
||||
refined_optimization = ctx.apply_patches_to_optimized_code(diff_patches)
|
||||
|
||||
|
|
@ -386,4 +382,4 @@ def calculate_portfolio_metrics(
|
|||
}
|
||||
"""
|
||||
new_code = apply_patches(patch, code)
|
||||
print(new_code)
|
||||
print(new_code)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ documentation to unchanged code.
|
|||
import ast
|
||||
|
||||
import libcst as cst
|
||||
|
||||
from optimizer.postprocess import clean_extraneous_comments
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1 @@
|
|||
"""Workflow generation module for GitHub Actions."""
|
||||
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ from django.apps import AppConfig
|
|||
class WorkflowGenConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "workflow_gen"
|
||||
|
||||
|
|
|
|||
|
|
@ -8,14 +8,11 @@ from typing import TYPE_CHECKING, Any
|
|||
|
||||
import sentry_sdk
|
||||
from ninja import NinjaAPI, Schema
|
||||
from openai.types.chat import (
|
||||
ChatCompletionSystemMessageParam,
|
||||
ChatCompletionUserMessageParam,
|
||||
)
|
||||
from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam
|
||||
|
||||
from aiservice.analytics.posthog import ph
|
||||
from aiservice.env_specific import debug_log_sensitive_data, llm_clients
|
||||
from aiservice.models.aimodels import EXECUTE_MODEL, calculate_llm_cost
|
||||
from aiservice.models.aimodels import EXECUTE_MODEL
|
||||
from aiservice.observability.decorators import observe_llm_call
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -81,8 +78,7 @@ async def call_workflow_gen_llm(
|
|||
user_id: str | None = None,
|
||||
context: dict | None = None,
|
||||
):
|
||||
"""
|
||||
Call LLM for workflow generation with automatic observability.
|
||||
"""Call LLM for workflow generation with automatic observability.
|
||||
|
||||
This function is decorated with @observe_llm_call which automatically:
|
||||
- Records call start (non-blocking)
|
||||
|
|
@ -110,32 +106,22 @@ async def generate_workflow_steps_llm(
|
|||
) -> str | None:
|
||||
"""Generate workflow steps using LLM."""
|
||||
# Format repo files for prompt
|
||||
files_text = "\n".join(
|
||||
[f"**{path}:**\n{content[:8000]}\n" for path, content in repo_files.items()]
|
||||
)
|
||||
files_text = "\n".join([f"**{path}:**\n{content[:8000]}\n" for path, content in repo_files.items()])
|
||||
|
||||
# Format directory structure
|
||||
structure_text = json.dumps(directory_structure, indent=2)
|
||||
|
||||
# Format codeflash config
|
||||
config_text = (
|
||||
json.dumps(codeflash_config, indent=2) if codeflash_config else "Not available"
|
||||
)
|
||||
config_text = json.dumps(codeflash_config, indent=2) if codeflash_config else "Not available"
|
||||
|
||||
user_prompt = USER_PROMPT.format(
|
||||
repo_files=files_text,
|
||||
directory_structure=structure_text,
|
||||
codeflash_config=config_text,
|
||||
repo_files=files_text, directory_structure=structure_text, codeflash_config=config_text
|
||||
)
|
||||
|
||||
system_message = ChatCompletionSystemMessageParam(
|
||||
role="system", content=SYSTEM_PROMPT
|
||||
)
|
||||
system_message = ChatCompletionSystemMessageParam(role="system", content=SYSTEM_PROMPT)
|
||||
user_message = ChatCompletionUserMessageParam(role="user", content=user_prompt)
|
||||
|
||||
debug_log_sensitive_data(
|
||||
f"Generating workflow steps with prompt length: {len(user_prompt)}"
|
||||
)
|
||||
debug_log_sensitive_data(f"Generating workflow steps with prompt length: {len(user_prompt)}")
|
||||
|
||||
try:
|
||||
# Call LLM with automatic observability (decorator handles everything)
|
||||
|
|
@ -146,9 +132,7 @@ async def generate_workflow_steps_llm(
|
|||
temperature=0,
|
||||
n=1,
|
||||
user_id=user_id,
|
||||
context={
|
||||
"num_files": len(repo_files),
|
||||
},
|
||||
context={"num_files": len(repo_files)},
|
||||
)
|
||||
|
||||
if not response.choices or not response.choices[0].message.content:
|
||||
|
|
@ -160,14 +144,10 @@ async def generate_workflow_steps_llm(
|
|||
# Extract YAML steps
|
||||
steps_yaml = _extract_yaml_steps(response_text)
|
||||
if steps_yaml:
|
||||
logger.info(
|
||||
f"Successfully generated workflow steps ({len(steps_yaml)} chars)"
|
||||
)
|
||||
logger.info(f"Successfully generated workflow steps ({len(steps_yaml)} chars)")
|
||||
return steps_yaml
|
||||
|
||||
logger.warning(
|
||||
f"Could not extract valid YAML steps from LLM response: {response_text[:200]}"
|
||||
)
|
||||
logger.warning(f"Could not extract valid YAML steps from LLM response: {response_text[:200]}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -193,11 +173,7 @@ class WorkflowGenErrorResponseSchema(Schema):
|
|||
|
||||
@workflow_gen_api.post(
|
||||
"/",
|
||||
response={
|
||||
200: WorkflowGenResponseSchema,
|
||||
400: WorkflowGenErrorResponseSchema,
|
||||
500: WorkflowGenErrorResponseSchema,
|
||||
},
|
||||
response={200: WorkflowGenResponseSchema, 400: WorkflowGenErrorResponseSchema, 500: WorkflowGenErrorResponseSchema},
|
||||
)
|
||||
async def generate_workflow_steps(
|
||||
request: HttpRequest, data: WorkflowGenInputSchema
|
||||
|
|
@ -227,16 +203,10 @@ async def generate_workflow_steps(
|
|||
error="Failed to generate workflow steps. Please try again or use the static template."
|
||||
)
|
||||
|
||||
ph(
|
||||
request.user,
|
||||
"aiservice-workflow-gen-success",
|
||||
properties={"steps_length": len(workflow_steps)},
|
||||
)
|
||||
ph(request.user, "aiservice-workflow-gen-success", properties={"steps_length": len(workflow_steps)})
|
||||
return 200, WorkflowGenResponseSchema(workflow_steps=workflow_steps)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in generate_workflow_steps endpoint: {e}")
|
||||
sentry_sdk.capture_exception(e)
|
||||
return 500, WorkflowGenErrorResponseSchema(
|
||||
error="Internal server error while generating workflow steps"
|
||||
)
|
||||
return 500, WorkflowGenErrorResponseSchema(error="Internal server error while generating workflow steps")
|
||||
|
|
|
|||
Loading…
Reference in a new issue