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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.venv/
.vscode/
__pycache__/
*.pyc
.env
Expand All @@ -16,4 +17,5 @@ coverage.xml
.scannerwork/
opencode.json
.vscode/
agents
agents
opencode.json
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ dependencies = [
"arize-phoenix-otel==0.15.0",
"openinference-instrumentation-langchain==0.1.62",
"arize-phoenix-client==2.3.0",

"tenacity>=8.0.0"
]
[dependency-groups]
dev = [
Expand Down
53 changes: 46 additions & 7 deletions src/application/requests/prompt.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,56 @@
from pydantic import BaseModel, Field
from enum import Enum
from pydantic import BaseModel, Field, field_validator, model_validator


class MessageRole(str, Enum):
system = "system"
user = "user"
assistant = "assistant"


class PromptMessage(BaseModel):
role: MessageRole
content: str = Field(..., min_length=1)


class CreatePromptRequest(BaseModel):
identifier: str = Field(..., min_length=1)
content: list[dict[str, str]]
model_name: str
identifier: str = Field(..., min_length=1, pattern=r"^[a-zA-Z0-9_-]+$")
content: list[PromptMessage] = Field(..., min_length=1)
model_name: str = Field(..., min_length=1)
description: str | None = None
tags: list[str] | None = None
metadata : dict | None = None
metadata: dict | None = None

@model_validator(mode="after")
def validate_content_roles(self) -> "CreatePromptRequest":
roles = [msg.role for msg in self.content]
if MessageRole.user not in roles and MessageRole.system not in roles:
raise ValueError("content must contain at least one 'user' or 'system' message")
return self

@field_validator("content", mode="before")
@classmethod
def validate_message_format(cls, v: list) -> list:
if not v:
raise ValueError("content cannot be empty")
for i, msg in enumerate(v):
if isinstance(msg, dict):
if "role" not in msg:
raise ValueError(f"message[{i}] missing 'role' field")
if "content" not in msg:
raise ValueError(f"message[{i}] missing 'content' field")
return v


class UpdatePromptRequest(BaseModel):
content: list[dict[str, str]] | None = None
content: list[PromptMessage] | None = None
model_name: str | None = None
description: str | None = None
metadata : dict | None = None
metadata: dict | None = None

@field_validator("content", mode="before")
@classmethod
def validate_content_not_empty(cls, v: list | None) -> list | None:
if v is not None and len(v) == 0:
raise ValueError("content cannot be an empty list")
return v
39 changes: 29 additions & 10 deletions src/application/routes/prompt.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging

from fastapi import APIRouter, Depends, HTTPException
from httpx import HTTPStatusError

from src.application.requests.prompt import (
CreatePromptRequest,
Expand All @@ -9,34 +10,51 @@
from src.application.use_cases.create_prompt import CreatePromptUseCase
from src.application.use_cases.get_prompt import GetPromptUseCase
from src.application.use_cases.update_prompt import UpdatePromptUseCase
from src.dependencies import get_prompt_manager # We'll add this
from src.dependencies import get_prompt_manager
from src.domain.ports.prompt_manager import PromptManager

logger = logging.getLogger("composable-agents")

router = APIRouter(prefix="/prompts", tags=["prompts"])


@router.post("/create")
def _handle_http_error(e: Exception, identifier: str | None = None) -> HTTPException:
"""Map exceptions to appropriate HTTP status codes."""
if isinstance(e, ValueError) and "not found" in str(e).lower():
return HTTPException(status_code=404, detail=str(e))
if isinstance(e, HTTPStatusError):
if e.response.status_code == 404:
return HTTPException(status_code=404, detail=f"Prompt not found: {identifier}")
if e.response.status_code == 409:
return HTTPException(status_code=409, detail=f"Prompt already exists: {identifier}")
if e.response.status_code == 400:
return HTTPException(status_code=400, detail=str(e))
if isinstance(e, ValueError):
return HTTPException(status_code=400, detail=str(e))
return HTTPException(status_code=500, detail=str(e))


@router.post("/create", status_code=201)
async def create_prompt(
request: CreatePromptRequest,
prompt_manager: PromptManager = Depends(get_prompt_manager),
):
"""Create a new prompt."""
use_case = CreatePromptUseCase(prompt_manager)
try:
content_dicts = [msg.model_dump() for msg in request.content]
prompt = await use_case.execute(
identifier=request.identifier,
content=request.content,
content=content_dicts,
model_name=request.model_name,
description=request.description,
tags=request.tags,
metadata=request.metadata,
)
return {"status": "success", "prompt": prompt}
except Exception as e:
logger.error(f"Error creating prompt: {e}")
raise HTTPException(status_code=500, detail=str(e))
logger.error(f"Error creating prompt '{request.identifier}': {e}")
raise _handle_http_error(e, request.identifier)


@router.get("/get/{identifier}")
Expand All @@ -56,8 +74,8 @@ async def get_prompt(
)
return {"status": "success", "prompt": prompt}
except Exception as e:
logger.error(f"Error getting prompt: {e}")
raise HTTPException(status_code=404, detail=str(e))
logger.error(f"Error getting prompt '{identifier}': {e}")
raise _handle_http_error(e, identifier)


@router.put("/update/{identifier}")
Expand All @@ -69,14 +87,15 @@ async def update_prompt(
"""Update a prompt."""
use_case = UpdatePromptUseCase(prompt_manager)
try:
content_dicts = [msg.model_dump() for msg in request.content] if request.content else None
prompt = await use_case.execute(
identifier=identifier,
content=request.content,
content=content_dicts,
model_name=request.model_name,
description=request.description,
metadata=request.metadata,
)
return {"status": "success", "prompt": prompt}
except Exception as e:
logger.error(f"Error updating prompt: {e}")
raise HTTPException(status_code=500, detail=str(e))
logger.error(f"Error updating prompt '{identifier}': {e}")
raise _handle_http_error(e, identifier)
Loading
Loading