Skip to content

Commit

Permalink
🐍 Add basic error wrapping [Part. 1] (#628)
Browse files Browse the repository at this point in the history
  • Loading branch information
asim-shrestha authored May 30, 2023
1 parent 3ec7329 commit 35d3a40
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 25 deletions.
10 changes: 4 additions & 6 deletions next/__tests__/message-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,17 @@ describe("sendErrorMessage", () => {
response: {
status: 409,
data: {
detail: {
error: "invalid_request",
detail: "You have exceeded the maximum number of requests allowed for your API key.",
code: 429,
},
error: "OpenAIError",
detail: "You have exceeded the maximum number of requests allowed for your API key.",
code: 429,
},
},
};

instance.sendErrorMessage(axiosError);
expect(renderMessage).toHaveBeenCalledWith({
type: "error",
value: axiosError.response.data.detail.detail,
value: axiosError.response.data.detail,
});
});

Expand Down
4 changes: 2 additions & 2 deletions next/src/components/utils/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const getMessageContainerStyle = (message: Message) => {
if (!isTask(message)) {
switch (message.type) {
case "error":
return "border-red-500";
return "border-yellow-400";
default:
return "border-white/10 hover:border-white/40";
}
Expand Down Expand Up @@ -58,7 +58,7 @@ export const getTaskStatusIcon = (
case MESSAGE_TYPE_THINKING:
return <FaBrain className="mt-[0.1em] text-pink-400" />;
case MESSAGE_TYPE_ERROR:
return <FaExclamationTriangle className="text-red-500" />;
return <FaExclamationTriangle className="text-yellow-400" />;
}

if (getTaskStatus(message) === TASK_STATUS_STARTED) {
Expand Down
3 changes: 1 addition & 2 deletions next/src/services/agent/message-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ class MessageService {
else if (axios.isAxiosError(e)) {
switch (e.response?.status) {
case 409:
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const data = (e.response?.data?.detail as object) || {};
const data = (e.response?.data as object) || {};
message = isPlatformError(data)
? data.detail
: "An Unknown Error Occurred, Please Try Again!";
Expand Down
12 changes: 2 additions & 10 deletions next/src/types/errors.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import { z } from "zod";

const platformErrorSchema = z.object({
error: z.enum([
"invalid_request",
"invalid_api_key",
"engine_not_found",
"permission_denied",
"server_error",
"timeout",
"too_many_requests",
]),
error: z.enum(["OpenAIError"]),
detail: z.string(),
code: z.number(),
code: z.number().optional(),
});

export type PlatformError = z.infer<typeof platformErrorSchema>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

from reworkd_platform.web.api.agent.agent_service.agent_service import AgentService
from reworkd_platform.web.api.agent.analysis import Analysis, get_default_analysis
from reworkd_platform.web.api.agent.helpers import (
call_model_with_handling,
parse_with_handling,
)
from reworkd_platform.web.api.agent.model_settings import ModelSettings, create_model
from reworkd_platform.web.api.agent.prompts import (
start_goal_prompt,
Expand All @@ -25,12 +29,14 @@ def __init__(self, model_settings: ModelSettings):
self._language = model_settings.language or "English"

async def start_goal_agent(self, *, goal: str) -> List[str]:
llm = create_model(self.model_settings)
chain = LLMChain(llm=llm, prompt=start_goal_prompt)
completion = await call_model_with_handling(
self.model_settings,
start_goal_prompt,
{"goal": goal, "language": self._language},
)

completion = await chain.arun({"goal": goal, "language": self._language})
task_output_parser = TaskOutputParser(completed_tasks=[])
return task_output_parser.parse(completion)
return parse_with_handling(task_output_parser, completion)

async def analyze_task_agent(
self, *, goal: str, task: str, tool_names: List[str]
Expand Down
30 changes: 30 additions & 0 deletions platform/reworkd_platform/web/api/agent/helpers.py
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
from typing import TypeVar

from langchain import LLMChain
from langchain.schema import OutputParserException, BaseOutputParser

from reworkd_platform.web.api.agent.model_settings import create_model, ModelSettings
from reworkd_platform.web.api.errors import OpenAIError

T = TypeVar("T")


def parse_with_handling(parser: BaseOutputParser[T], completion: str) -> T:
try:
return parser.parse(completion)
except OutputParserException as e:
raise OpenAIError(
e, "There was an issue parsing the response from the AI model."
)


async def call_model_with_handling(
model_settings: ModelSettings, prompt: str, args: dict
) -> str:
try:
model = create_model(model_settings)
chain = LLMChain(llm=model, prompt=prompt)
return await chain.arun(args)
except Exception as e:
raise OpenAIError(
e, "There was an issue getting a response from the AI model."
)
17 changes: 17 additions & 0 deletions platform/reworkd_platform/web/api/error_handling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from fastapi import Request
from fastapi.responses import JSONResponse

from reworkd_platform.web.api.errors import PlatformaticError


async def platformatic_exception_handler(
_: Request,
platform_exception: PlatformaticError,
):
return JSONResponse(
status_code=409,
content={
"error": platform_exception.__class__.__name__,
"detail": platform_exception.detail,
},
)
27 changes: 27 additions & 0 deletions platform/reworkd_platform/web/api/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Parent exception class for all expected backend exceptions
# Will be caught and handled by the platform_exception_handler
# Shoutout https://platformatic.dev/
class PlatformaticError(Exception):
detail: str

def __init__(self, base_exception: Exception, detail: str = ""):
super().__init__(base_exception)
self.detail = detail


class OpenAIError(PlatformaticError):
def __init__(self, exception: Exception, detail: str = ""):
super().__init__(exception, detail)

# (Replicate) ModelError: NSFW content detected. Try running it again, or try a different prompt.
# ReplicateError: You've hit your monthly spend limit. You can change or remove your limit at https://replicate.com/account/billing#limits.


# UnicodeEncodeError: 'utf-8' codec can't encode character '\ud800' in position 445: surrogates not allowed

# InvalidRequestError: Engine not found
# TimeoutError -> https://reworkd.sentry.io/issues/4206041431/?project=4505228628525056&query=is%3Aunresolved&referrer=issue-stream&stream_index=17
# ServiceUnavailableError: The server is overloaded or not ready yet.
# ClientPayloadError: Response payload is not completed -> https://reworkd.sentry.io/issues/4209422945/?project=4505228628525056&query=is%3Aunresolved&referrer=issue-stream&stream_index=21
# InvalidRequestError: This model's maximum context length is 4097 tokens. However, your messages resulted in 5238 tokens. Please reduce the length of the messages.
# InvalidRequestError: This model's maximum context length is 8192 tokens. However, you requested 8451 tokens (451 in the messages, 8000 in the completion). Please reduce the length of the messages or completion.
6 changes: 5 additions & 1 deletion platform/reworkd_platform/web/application.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import logging
from importlib import metadata

import sentry_sdk
Expand All @@ -9,8 +8,11 @@
from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration

import logging
from reworkd_platform.logging import configure_logging
from reworkd_platform.settings import settings
from reworkd_platform.web.api.error_handling import platformatic_exception_handler
from reworkd_platform.web.api.errors import PlatformaticError
from reworkd_platform.web.api.router import api_router
from reworkd_platform.web.lifetime import (
register_shutdown_event,
Expand Down Expand Up @@ -72,4 +74,6 @@ def get_app() -> FastAPI:
# Main router for the API.
app.include_router(router=api_router, prefix="/api")

app.exception_handler(PlatformaticError)(platformatic_exception_handler)

return app

1 comment on commit 35d3a40

@vercel
Copy link

@vercel vercel bot commented on 35d3a40 May 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

docs – ./docs

docs-git-main-reworkd.vercel.app
docs.reworkd.ai
docs-reworkd.vercel.app

Please sign in to comment.