Skip to content
Closed
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
2 changes: 2 additions & 0 deletions backend/app/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
utils,
onboarding,
credentials,
evaluation,
)
from app.core.config import settings

Expand All @@ -22,6 +23,7 @@
api_router.include_router(collections.router)
api_router.include_router(credentials.router)
api_router.include_router(documents.router)
api_router.include_router(evaluation.router)
api_router.include_router(login.router)
api_router.include_router(onboarding.router)
api_router.include_router(organization.router)
Expand Down
141 changes: 141 additions & 0 deletions backend/app/api/routes/evaluation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import csv
import io
from typing import List, Dict, Any
from fastapi import APIRouter, UploadFile, File, Depends, HTTPException
from sqlmodel import Session
from langfuse import Langfuse

from app.api.deps import get_current_user_org, get_db
from app.core import logging
from app.models import UserOrganization, UserProjectOrg
from app.utils import APIResponse
from app.crud.credentials import get_provider_credential

logger = logging.getLogger(__name__)
router = APIRouter(tags=["evaluation"])


@router.post("/evaluation/upload-dataset")
async def upload_dataset(
dataset_name: str,
file: UploadFile = File(...),
_session: Session = Depends(get_db),
_current_user: UserOrganization = Depends(get_current_user_org),
):
"""
Upload a CSV dataset for evaluation.
The CSV file should have two columns: input and expected_output.
Only the first 30 rows will be processed.
"""
if not file.filename.endswith(".csv"):
return APIResponse.failure_response(error="Only CSV files are supported")

Check warning on line 31 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L30-L31

Added lines #L30 - L31 were not covered by tests

# Get Langfuse credentials
langfuse_credentials = get_provider_credential(

Check warning on line 34 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L34

Added line #L34 was not covered by tests
session=_session,
org_id=_current_user.organization_id,
provider="langfuse",
project_id=_current_user.project_id,
)
if not langfuse_credentials:
return APIResponse.failure_response(

Check warning on line 41 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L40-L41

Added lines #L40 - L41 were not covered by tests
error="Langfuse credentials not configured for this organization."
)

# Initialize Langfuse client
langfuse = Langfuse(

Check warning on line 46 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L46

Added line #L46 was not covered by tests
public_key=langfuse_credentials["public_key"],
secret_key=langfuse_credentials["secret_key"],
host=langfuse_credentials["host"],
)
# langfuse = Langfuse(
# public_key="pk-lf-00d2d47a-86f0-4d8f-9d8b-ef24fc722731",
# secret_key="sk-lf-5a7caba5-9293-409d-b1ef-e4b3fef990b7",
# host="https://cloud.langfuse.com",
# )

try:

Check warning on line 57 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L57

Added line #L57 was not covered by tests
# Read and validate CSV file
contents = await file.read()
logger.info(f"Read {len(contents)} bytes from file")

Check warning on line 60 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L59-L60

Added lines #L59 - L60 were not covered by tests

# Decode contents and create CSV reader
csv_content = contents.decode("utf-8")
logger.info(f"CSV content preview: {csv_content[:200]}...")

Check warning on line 64 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L63-L64

Added lines #L63 - L64 were not covered by tests

csv_file = io.StringIO(csv_content)
reader = csv.DictReader(csv_file)

Check warning on line 67 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L66-L67

Added lines #L66 - L67 were not covered by tests

# Validate headers
if not reader.fieldnames:
return APIResponse.failure_response(

Check warning on line 71 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L70-L71

Added lines #L70 - L71 were not covered by tests
error="CSV file is empty or has no headers"
)

logger.info(f"CSV headers found: {reader.fieldnames}")

Check warning on line 75 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L75

Added line #L75 was not covered by tests

if not all(

Check warning on line 77 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L77

Added line #L77 was not covered by tests
header in reader.fieldnames for header in ["input", "expected_output"]
):
return APIResponse.failure_response(

Check warning on line 80 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L80

Added line #L80 was not covered by tests
error="CSV must contain 'input' and 'expected_output' columns"
)

# Create dataset
try:
dataset = langfuse.create_dataset(name=dataset_name)
logger.info(f"Created dataset with ID: {dataset.id}")
except Exception as e:
logger.error(f"Error creating dataset: {str(e)}")
return APIResponse.failure_response(

Check warning on line 90 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L85-L90

Added lines #L85 - L90 were not covered by tests
error=f"Failed to create dataset: {str(e)}"
)

# Process rows (limited to 30)
rows_processed = 0
rows_data = [] # Store rows for logging

Check warning on line 96 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L95-L96

Added lines #L95 - L96 were not covered by tests

for row in reader:
if rows_processed >= 30:
break

Check warning on line 100 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L98-L100

Added lines #L98 - L100 were not covered by tests

try:

Check warning on line 102 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L102

Added line #L102 was not covered by tests
# Log the row data
logger.info(f"Processing row {rows_processed + 1}: {row}")

Check warning on line 104 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L104

Added line #L104 was not covered by tests

# Create dataset item
item = langfuse.create_dataset_item(

Check warning on line 107 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L107

Added line #L107 was not covered by tests
dataset_name=dataset_name,
input=row["input"],
expected_output=row["expected_output"],
)
logger.info(f"Created dataset item with ID: {item.id}")

Check warning on line 112 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L112

Added line #L112 was not covered by tests

rows_processed += 1
rows_data.append(row)
except Exception as e:
logger.error(f"Error processing row {rows_processed + 1}: {str(e)}")
continue

Check warning on line 118 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L114-L118

Added lines #L114 - L118 were not covered by tests

if rows_processed == 0:
return APIResponse.failure_response(

Check warning on line 121 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L120-L121

Added lines #L120 - L121 were not covered by tests
error="No rows were successfully processed"
)

# Log summary
logger.info(f"Successfully processed {rows_processed} rows")
logger.info(f"Processed data: {rows_data}")

Check warning on line 127 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L126-L127

Added lines #L126 - L127 were not covered by tests

return APIResponse.success_response(

Check warning on line 129 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L129

Added line #L129 was not covered by tests
data={
"message": f"Successfully uploaded {rows_processed} rows to dataset '{dataset_name}'",
"rows_processed": rows_processed,
"dataset_id": dataset.id if hasattr(dataset, "id") else None,
}
)

except Exception as e:
logger.error(f"Error uploading dataset: {str(e)}")
return APIResponse.failure_response(error=str(e))

Check warning on line 139 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L137-L139

Added lines #L137 - L139 were not covered by tests
finally:
await file.close()

Check warning on line 141 in backend/app/api/routes/evaluation.py

View check run for this annotation

Codecov / codecov/patch

backend/app/api/routes/evaluation.py#L141

Added line #L141 was not covered by tests