Skip to content
Merged
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: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,10 @@ class MyAuthBackend(BaseAuth):
# Your authentication logic
token = credential.credentials
user = verify_token(token)

if not user:
raise HTTPException(401, "Invalid token")

return {
"user_id": user.id,
"username": user.username,
Expand Down
15 changes: 15 additions & 0 deletions TODOD.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,18 @@ So we can create a new api called fix_broken_graph

# TODO: 2. And setup api to register frontend tools

Unique Features to highlight:
3. Single command to run with api, production ready api (Fastapi), async first api design,
uvicorn based with proper logger, health checks, swagger/redocs docs ready,
prometheus metrics ready, using best practices. Using env for control
5. Single command to generate docker image, can be deployable
anywhere, no vendor locked-in, no platform cost, deploy where you
want
7. Easy to connect Authentication with the platform settings, JWT by default,
Can be extend with any auth provider, just provide the class path we will setup.
8. Control Over generated ID, UUID are 128 bit, but if you can control and use
smaller ids, you can save a lot of space in DBs and indexes.
9. All the class like state, message, tool calls are Pydantic models, easily
json serializable, easy to debug, log and store.
10. Sentry Integration ready, just provide the DSN in the settings, all the exceptions
will be sent to sentry with proper context.
26 changes: 18 additions & 8 deletions agentflow_cli/src/app/core/config/sentry_config.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
from typing import TYPE_CHECKING

from fastapi import Depends

from agentflow_cli.src.app.core import Settings, get_settings, logger


if TYPE_CHECKING: # pragma: no cover - only for type hints
import sentry_sdk # noqa: F401
from sentry_sdk.integrations.fastapi import FastApiIntegration # noqa: F401
from sentry_sdk.integrations.starlette import StarletteIntegration # noqa: F401


def init_sentry(settings: Settings = Depends(get_settings)) -> None:
"""Initialize Sentry for error tracking and performance monitoring.

The initialization is best-effort: if ``sentry_sdk`` isn't installed or any
unexpected error occurs, the application continues to run and a warning is
logged instead of failing hard.
"""
environment = settings.MODE.upper() if settings.MODE else ""

if not settings.SENTRY_DSN:
logger.warning(
"Sentry is not configured. Sentry DSN is not set or running in local environment."
)
return

allowed_environments = ["PRODUCTION", "STAGING", "DEVELOPMENT"]
if environment not in allowed_environments:
logger.warning(
f"Sentry is not configured for this environment: {environment}. "
"Allowed environments are: {allowed_environments}"
)
return

logger.info(f"Sentry is configured for environment: {environment}")

try:
import sentry_sdk
from sentry_sdk.integrations.fastapi import FastApiIntegration
Expand Down
6 changes: 3 additions & 3 deletions agentflow_cli/src/app/core/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ class Settings(BaseSettings):
#################################
###### Paths ####################
#################################
ROOT_PATH: str = ""
DOCS_PATH: str = ""
REDOCS_PATH: str = ""
ROOT_PATH: str = "/"
DOCS_PATH: str = "/docs"
REDOCS_PATH: str = "/redocs"

#################################
###### REDIS Config ##########
Expand Down
4 changes: 4 additions & 0 deletions agentflow_cli/src/app/core/config/setup_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from starlette.middleware.cors import CORSMiddleware
from starlette.requests import Request

from .sentry_config import init_sentry
from .settings import get_settings, logger


Expand Down Expand Up @@ -92,3 +93,6 @@ def setup_middleware(app: FastAPI):
# Note: If you need streaming responses, you should not use GZipMiddleware.
app.add_middleware(GZipMiddleware, minimum_size=1000)
logger.debug("Middleware set up")

# Initialize Sentry
init_sentry(settings)
136 changes: 130 additions & 6 deletions agentflow_cli/src/app/core/exceptions/handle_errors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# Handle all exceptions of agentflow here
from agentflow.exceptions import (
GraphError,
GraphRecursionError,
MetricsError,
NodeError,
ResourceNotFoundError,
SchemaVersionError,
SerializationError,
StorageError,
TransientStorageError,
)
from agentflow.utils.validators import ValidationError
from fastapi import FastAPI
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException
Expand All @@ -7,16 +20,13 @@
from agentflow_cli.src.app.utils import error_response
from agentflow_cli.src.app.utils.schemas import ErrorSchemas

from .resources_exceptions import ResourceNotFoundError
from .resources_exceptions import ResourceNotFoundError as APIResourceNotFoundError
from .user_exception import (
UserAccountError,
UserPermissionError,
)


# Handle all exceptions of agentflow here


def init_errors_handler(app: FastAPI):
"""
Initialize error handlers for the FastAPI application.
Expand Down Expand Up @@ -88,12 +98,126 @@ async def user_write_exception_handler(request: Request, exc: UserPermissionErro
status_code=exc.status_code,
)

@app.exception_handler(ResourceNotFoundError)
async def resource_not_found_exception_handler(request: Request, exc: ResourceNotFoundError):
@app.exception_handler(APIResourceNotFoundError)
async def resource_not_found_exception_handler(request: Request, exc: APIResourceNotFoundError):
logger.error(f"ResourceNotFoundError: url: {request.base_url}", exc_info=exc)
return error_response(
request,
error_code=exc.error_code,
message=exc.message,
status_code=exc.status_code,
)

## Need to handle agentflow specific exceptions here
@app.exception_handler(ValidationError)
async def agentflow_validation_exception_handler(request: Request, exc: ValidationError):
logger.error(f"AgentFlow ValidationError: url: {request.base_url}", exc_info=exc)
return error_response(
request,
error_code="AGENTFLOW_VALIDATION_ERROR",
message=str(exc),
status_code=422,
)

@app.exception_handler(GraphError)
async def graph_error_exception_handler(request: Request, exc: GraphError):
logger.error(f"GraphError: url: {request.base_url}", exc_info=exc)
return error_response(
request,
error_code=getattr(exc, "error_code", "GRAPH_000"),
message=getattr(exc, "message", str(exc)),
details=getattr(exc, "context", None),
status_code=500,
)

@app.exception_handler(NodeError)
async def node_error_exception_handler(request: Request, exc: NodeError):
logger.error(f"NodeError: url: {request.base_url}", exc_info=exc)
return error_response(
request,
error_code=getattr(exc, "error_code", "NODE_000"),
message=getattr(exc, "message", str(exc)),
details=getattr(exc, "context", None),
status_code=500,
)

@app.exception_handler(GraphRecursionError)
async def graph_recursion_error_exception_handler(request: Request, exc: GraphRecursionError):
logger.error(f"GraphRecursionError: url: {request.base_url}", exc_info=exc)
return error_response(
request,
error_code=getattr(exc, "error_code", "GRAPH_RECURSION_000"),
message=getattr(exc, "message", str(exc)),
details=getattr(exc, "context", None),
status_code=500,
)

@app.exception_handler(MetricsError)
async def metrics_error_exception_handler(request: Request, exc: MetricsError):
logger.error(f"MetricsError: url: {request.base_url}", exc_info=exc)
return error_response(
request,
error_code=getattr(exc, "error_code", "METRICS_000"),
message=getattr(exc, "message", str(exc)),
details=getattr(exc, "context", None),
status_code=500,
)

@app.exception_handler(SchemaVersionError)
async def schema_version_error_exception_handler(request: Request, exc: SchemaVersionError):
logger.error(f"SchemaVersionError: url: {request.base_url}", exc_info=exc)
return error_response(
request,
error_code=getattr(exc, "error_code", "SCHEMA_VERSION_000"),
message=getattr(exc, "message", str(exc)),
details=getattr(exc, "context", None),
status_code=422,
)

@app.exception_handler(SerializationError)
async def serialization_error_exception_handler(request: Request, exc: SerializationError):
logger.error(f"SerializationError: url: {request.base_url}", exc_info=exc)
return error_response(
request,
error_code=getattr(exc, "error_code", "SERIALIZATION_000"),
message=getattr(exc, "message", str(exc)),
details=getattr(exc, "context", None),
status_code=500,
)

@app.exception_handler(StorageError)
async def storage_error_exception_handler(request: Request, exc: StorageError):
logger.error(f"StorageError: url: {request.base_url}", exc_info=exc)
return error_response(
request,
error_code=getattr(exc, "error_code", "STORAGE_000"),
message=getattr(exc, "message", str(exc)),
details=getattr(exc, "context", None),
status_code=500,
)

@app.exception_handler(TransientStorageError)
async def transient_storage_error_exception_handler(
request: Request, exc: TransientStorageError
):
logger.error(f"TransientStorageError: url: {request.base_url}", exc_info=exc)
return error_response(
request,
error_code=getattr(exc, "error_code", "TRANSIENT_STORAGE_000"),
message=getattr(exc, "message", str(exc)),
details=getattr(exc, "context", None),
status_code=503,
)

@app.exception_handler(ResourceNotFoundError)
async def resource_not_found_storage_exception_handler(
request: Request, exc: ResourceNotFoundError
):
logger.error(f"ResourceNotFoundError: url: {request.base_url}", exc_info=exc)
return error_response(
request,
error_code=getattr(exc, "error_code", "RESOURCE_NOT_FOUND_000"),
message=getattr(exc, "message", str(exc)),
details=getattr(exc, "context", None),
status_code=404,
)
5 changes: 5 additions & 0 deletions agentflow_cli/src/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from injectq import InjectQ
from injectq.integrations.fastapi import setup_fastapi

# # Prometheus Instrumentator import
# from prometheus_fastapi_instrumentator import Instrumentator
# from tortoise import Tortoise
from agentflow_cli.src.app.core import (
get_settings,
Expand Down Expand Up @@ -81,3 +83,6 @@ async def lifespan(app: FastAPI):

# init routes
init_routes(app)

# instrumentator = Instrumentator().instrument(app) # Instrument first
# instrumentator.expose(app) # Then expose
Loading
Loading