From c1eb423f89923db138319a67c086524496717306 Mon Sep 17 00:00:00 2001 From: Zak Lee Date: Tue, 22 Oct 2024 17:46:51 -0700 Subject: [PATCH 1/2] prettify errors --- computer-use-demo/computer_use_demo/loop.py | 42 ++++++++++---- .../computer_use_demo/streamlit.py | 55 +++++++++++++------ 2 files changed, 69 insertions(+), 28 deletions(-) diff --git a/computer-use-demo/computer_use_demo/loop.py b/computer-use-demo/computer_use_demo/loop.py index d3255454..2feffa81 100644 --- a/computer-use-demo/computer_use_demo/loop.py +++ b/computer-use-demo/computer_use_demo/loop.py @@ -8,7 +8,15 @@ from enum import StrEnum from typing import Any, cast -from anthropic import Anthropic, AnthropicBedrock, AnthropicVertex, APIResponse +import httpx +from anthropic import ( + Anthropic, + AnthropicBedrock, + AnthropicVertex, + APIError, + APIResponseValidationError, + APIStatusError, +) from anthropic.types import ( ToolResultBlockParam, ) @@ -16,7 +24,6 @@ BetaContentBlock, BetaContentBlockParam, BetaImageBlockParam, - BetaMessage, BetaMessageParam, BetaTextBlockParam, BetaToolResultBlockParam, @@ -70,7 +77,9 @@ async def sampling_loop( messages: list[BetaMessageParam], output_callback: Callable[[BetaContentBlock], None], tool_output_callback: Callable[[ToolResult, str], None], - api_response_callback: Callable[[APIResponse[BetaMessage]], None], + api_response_callback: Callable[ + [httpx.Request, httpx.Response | object | None, Exception | None], None + ], api_key: str, only_n_most_recent_images: int | None = None, max_tokens: int = 4096, @@ -102,16 +111,25 @@ async def sampling_loop( # we use raw_response to provide debug information to streamlit. Your # implementation may be able call the SDK directly with: # `response = client.messages.create(...)` instead. - raw_response = client.beta.messages.with_raw_response.create( - max_tokens=max_tokens, - messages=messages, - model=model, - system=system, - tools=tool_collection.to_params(), - betas=[BETA_FLAG], - ) + try: + raw_response = client.beta.messages.with_raw_response.create( + max_tokens=max_tokens, + messages=messages, + model=model, + system=system, + tools=tool_collection.to_params(), + betas=[BETA_FLAG], + ) + except (APIStatusError, APIResponseValidationError) as e: + api_response_callback(e.request, e.response, e) + return messages + except APIError as e: + api_response_callback(e.request, e.body, e) + return messages - api_response_callback(cast(APIResponse[BetaMessage], raw_response)) + api_response_callback( + raw_response.http_response.request, raw_response.http_response, None + ) response = raw_response.parse() diff --git a/computer-use-demo/computer_use_demo/streamlit.py b/computer-use-demo/computer_use_demo/streamlit.py index 6750029c..8071adec 100644 --- a/computer-use-demo/computer_use_demo/streamlit.py +++ b/computer-use-demo/computer_use_demo/streamlit.py @@ -6,18 +6,19 @@ import base64 import os import subprocess -from datetime import datetime +from datetime import datetime, timedelta from enum import StrEnum from functools import partial from pathlib import PosixPath from typing import cast +import httpx import streamlit as st -from anthropic import APIResponse +from anthropic import RateLimitError from anthropic.types import ( TextBlock, ) -from anthropic.types.beta import BetaMessage, BetaTextBlock, BetaToolUseBlock +from anthropic.types.beta import BetaTextBlock, BetaToolUseBlock from anthropic.types.tool_use_block import ToolUseBlock from streamlit.delta_generator import DeltaGenerator @@ -186,8 +187,8 @@ def _reset_api_provider(): ) # render past http exchanges - for identity, response in st.session_state.responses.items(): - _render_api_response(response, identity, http_logs) + for identity, (request, response) in st.session_state.responses.items(): + _render_api_response(request, response, identity, http_logs) # render past chats if new_message: @@ -278,16 +279,20 @@ def save_to_storage(filename: str, data: str) -> None: def _api_response_callback( - response: APIResponse[BetaMessage], + request: httpx.Request, + response: httpx.Response | object | None, + error: Exception | None, tab: DeltaGenerator, - response_state: dict[str, APIResponse[BetaMessage]], + response_state: dict[str, tuple[httpx.Request, httpx.Response | object | None]], ): """ Handle an API response by storing it to state and rendering it. """ response_id = datetime.now().isoformat() - response_state[response_id] = response - _render_api_response(response, response_id, tab) + response_state[response_id] = (request, response) + if error: + _render_error(error) + _render_api_response(request, response, response_id, tab) def _tool_output_callback( @@ -299,20 +304,38 @@ def _tool_output_callback( def _render_api_response( - response: APIResponse[BetaMessage], response_id: str, tab: DeltaGenerator + request: httpx.Request, + response: httpx.Response | object | None, + response_id: str, + tab: DeltaGenerator, ): """Render an API response to a streamlit tab""" with tab: with st.expander(f"Request/Response ({response_id})"): newline = "\n\n" st.markdown( - f"`{response.http_request.method} {response.http_request.url}`{newline}{newline.join(f'`{k}: {v}`' for k, v in response.http_request.headers.items())}" - ) - st.json(response.http_request.read().decode()) - st.markdown( - f"`{response.http_response.status_code}`{newline}{newline.join(f'`{k}: {v}`' for k, v in response.headers.items())}" + f"`{request.method} {request.url}`{newline}{newline.join(f'`{k}: {v}`' for k, v in request.headers.items())}" ) - st.json(response.http_response.text) + st.json(request.read().decode()) + st.markdown("---") + if isinstance(response, httpx.Response): + st.markdown( + f"`{response.status_code}`{newline}{newline.join(f'`{k}: {v}`' for k, v in response.headers.items())}" + ) + st.json(response.text) + else: + st.write(response) + + +def _render_error(error: Exception): + if isinstance(error, RateLimitError): + body = "You have been rate limited." + if retry_after := error.response.headers.get("retry-after"): + body += f" **Retry after {str(timedelta(seconds=int(retry_after)))} (HH:MM:SS).**" + body += f"\n\n{error.message}" + else: + body = str(error) + st.error(f"**{error.__class__.__name__}**\n\n{body}", icon=":material/error:") def _render_message( From 26a16f7d5f3a6d27b4d32821de40eb1b7690c4d0 Mon Sep 17 00:00:00 2001 From: Zak Lee Date: Wed, 23 Oct 2024 14:43:43 -0700 Subject: [PATCH 2/2] add traceback; fix hide css --- computer-use-demo/computer_use_demo/streamlit.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/computer-use-demo/computer_use_demo/streamlit.py b/computer-use-demo/computer_use_demo/streamlit.py index 8071adec..7db927e0 100644 --- a/computer-use-demo/computer_use_demo/streamlit.py +++ b/computer-use-demo/computer_use_demo/streamlit.py @@ -6,6 +6,7 @@ import base64 import os import subprocess +import traceback from datetime import datetime, timedelta from enum import StrEnum from functools import partial @@ -39,7 +40,7 @@ display: none; } /* Hide the streamlit deploy button */ - .stDeployButton { + .stAppDeployButton { visibility: hidden; } @@ -331,10 +332,14 @@ def _render_error(error: Exception): if isinstance(error, RateLimitError): body = "You have been rate limited." if retry_after := error.response.headers.get("retry-after"): - body += f" **Retry after {str(timedelta(seconds=int(retry_after)))} (HH:MM:SS).**" + body += f" **Retry after {str(timedelta(seconds=int(retry_after)))} (HH:MM:SS).** See our API [documentation](https://docs.anthropic.com/en/api/rate-limits) for more details." body += f"\n\n{error.message}" else: body = str(error) + body += "\n\n**Traceback:**" + lines = "\n".join(traceback.format_exception(error)) + body += f"\n\n```{lines}```" + save_to_storage(f"error_{datetime.now().timestamp()}.md", body) st.error(f"**{error.__class__.__name__}**\n\n{body}", icon=":material/error:")