Skip to content

Commit

Permalink
Merge branch 'main' into fix-invalid-json-responses
Browse files Browse the repository at this point in the history
  • Loading branch information
dirkbrnd authored Feb 21, 2025
2 parents 603dbd2 + e8caa38 commit 7938748
Show file tree
Hide file tree
Showing 21 changed files with 319 additions and 111 deletions.
25 changes: 22 additions & 3 deletions libs/agno/agno/agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,8 @@ def run(
import time

time.sleep(delay)

# If we get here, all retries failed
if last_exception is not None:
logger.error(
f"Failed after {num_attempts} attempts. Last error using {last_exception.model_name}({last_exception.model_id})"
Expand Down Expand Up @@ -1257,7 +1259,13 @@ async def arun(
time.sleep(delay)

# If we get here, all retries failed
raise Exception(f"Failed after {num_attempts} attempts. Last error: {str(last_exception)}")
if last_exception is not None:
logger.error(
f"Failed after {num_attempts} attempts. Last error using {last_exception.model_name}({last_exception.model_id})"
)
raise last_exception
else:
raise Exception(f"Failed after {num_attempts} attempts.")

def create_run_response(
self,
Expand Down Expand Up @@ -1353,7 +1361,7 @@ def add_tools_to_model(self, model: Model) -> None:
if tool.name not in self._functions_for_model:
tool._agent = self
tool.process_entrypoint(strict=strict)
if strict:
if strict and tool.strict is None:
tool.strict = True
self._functions_for_model[tool.name] = tool
self._tools_for_model.append({"type": "function", "function": tool.to_dict()})
Expand Down Expand Up @@ -2363,7 +2371,18 @@ def _transfer_task_to_agent(
if member_agent.name is None:
member_agent.name = agent_name

transfer_function = Function.from_callable(_transfer_task_to_agent)
strict = (
True
if (
member_agent.response_model is not None
and member_agent.structured_outputs
and member_agent.model is not None
and member_agent.model.supports_structured_outputs
)
else False
)
transfer_function = Function.from_callable(_transfer_task_to_agent, strict=strict)
transfer_function.strict = strict
transfer_function.name = f"transfer_task_to_{agent_name}"
transfer_function.description = dedent(f"""\
Use this function to transfer a task to {agent_name}
Expand Down
25 changes: 22 additions & 3 deletions libs/agno/agno/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,29 @@ def __init__(
)


class ModelProviderError(Exception):
class AgnoError(Exception):
"""Exception raised when an internal error occurs."""

def __init__(self, message: str, status_code: int = 500):
super().__init__(message)
self.status_code = status_code


class ModelProviderError(AgnoError):
"""Exception raised when a model provider returns an error."""

def __init__(self, exc, model_name: str, model_id: str):
super().__init__(exc)
def __init__(
self, message: str, status_code: int = 502, model_name: Optional[str] = None, model_id: Optional[str] = None
):
super().__init__(message, status_code)
self.model_name = model_name
self.model_id = model_id


class ModelRateLimitError(ModelProviderError):
"""Exception raised when a model provider returns a rate limit error."""

def __init__(
self, message: str, status_code: int = 429, model_name: Optional[str] = None, model_id: Optional[str] = None
):
super().__init__(message, status_code, model_name, model_id)
42 changes: 25 additions & 17 deletions libs/agno/agno/models/anthropic/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from os import getenv
from typing import Any, Dict, List, Optional, Tuple, Union

from agno.exceptions import ModelProviderError
from agno.exceptions import ModelProviderError, ModelRateLimitError
from agno.media import Image
from agno.models.base import Model
from agno.models.message import Message
Expand Down Expand Up @@ -319,16 +319,18 @@ def invoke(self, messages: List[Message]) -> AnthropicMessage:
)
except APIConnectionError as e:
logger.error(f"Connection error while calling Claude API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
except RateLimitError as e:
logger.warning(f"Rate limit exceeded: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelRateLimitError(message=e.message, model_name=self.name, model_id=self.id) from e
except APIStatusError as e:
logger.error(f"Claude API error (status {e.status_code}): {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(
message=e.message, status_code=e.status_code, model_name=self.name, model_id=self.id
) from e
except Exception as e:
logger.error(f"Unexpected error calling Claude API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

def invoke_stream(self, messages: List[Message]) -> Any:
"""
Expand All @@ -355,16 +357,18 @@ def invoke_stream(self, messages: List[Message]) -> Any:
)
except APIConnectionError as e:
logger.error(f"Connection error while calling Claude API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
except RateLimitError as e:
logger.warning(f"Rate limit exceeded: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelRateLimitError(message=e.message, model_name=self.name, model_id=self.id) from e
except APIStatusError as e:
logger.error(f"Claude API error (status {e.status_code}): {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(
message=e.message, status_code=e.status_code, model_name=self.name, model_id=self.id
) from e
except Exception as e:
logger.error(f"Unexpected error calling Claude API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

async def ainvoke(self, messages: List[Message]) -> AnthropicMessage:
"""
Expand Down Expand Up @@ -392,16 +396,18 @@ async def ainvoke(self, messages: List[Message]) -> AnthropicMessage:
)
except APIConnectionError as e:
logger.error(f"Connection error while calling Claude API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
except RateLimitError as e:
logger.warning(f"Rate limit exceeded: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelRateLimitError(message=e.message, model_name=self.name, model_id=self.id) from e
except APIStatusError as e:
logger.error(f"Claude API error (status {e.status_code}): {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(
message=e.message, status_code=e.status_code, model_name=self.name, model_id=self.id
) from e
except Exception as e:
logger.error(f"Unexpected error calling Claude API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

async def ainvoke_stream(self, messages: List[Message]) -> AsyncIterator[Any]:
"""
Expand All @@ -425,16 +431,18 @@ async def ainvoke_stream(self, messages: List[Message]) -> AsyncIterator[Any]:
yield chunk
except APIConnectionError as e:
logger.error(f"Connection error while calling Claude API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=e.message, model_name=self.name, model_id=self.id) from e
except RateLimitError as e:
logger.warning(f"Rate limit exceeded: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelRateLimitError(message=e.message, model_name=self.name, model_id=self.id) from e
except APIStatusError as e:
logger.error(f"Claude API error (status {e.status_code}): {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(
message=e.message, status_code=e.status_code, model_name=self.name, model_id=self.id
) from e
except Exception as e:
logger.error(f"Unexpected error calling Claude API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

# Overwrite the default from the base model
def format_function_call_results(
Expand Down
17 changes: 8 additions & 9 deletions libs/agno/agno/models/aws/bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from os import getenv
from typing import Any, Dict, Iterator, List, Optional, Tuple

from agno.exceptions import ModelProviderError
from agno.exceptions import AgnoError, ModelProviderError
from agno.models.base import MessageData, Model
from agno.models.message import Message
from agno.models.response import ModelResponse
Expand Down Expand Up @@ -68,10 +68,9 @@ def get_client(self) -> AwsClient:
self.aws_region = self.aws_region or getenv("AWS_REGION")

if not self.aws_access_key_id or not self.aws_secret_access_key:
raise ModelProviderError(
"AWS credentials not found. Please set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.",
model_name=self.name,
model_id=self.id,
raise AgnoError(
message="AWS credentials not found. Please set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.",
status_code=400,
)

self.client = AwsClient(
Expand Down Expand Up @@ -238,10 +237,10 @@ def invoke(self, messages: List[Message]) -> Dict[str, Any]:
return self.get_client().converse(modelId=self.id, messages=formatted_messages, **body)
except ClientError as e:
logger.error(f"Unexpected error calling Bedrock API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e.response), model_name=self.name, model_id=self.id) from e
except Exception as e:
logger.error(f"Unexpected error calling Bedrock API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

def invoke_stream(self, messages: List[Message]) -> Iterator[Dict[str, Any]]:
"""
Expand Down Expand Up @@ -271,10 +270,10 @@ def invoke_stream(self, messages: List[Message]) -> Iterator[Dict[str, Any]]:
return self.get_client().converse_stream(modelId=self.id, messages=formatted_messages, **body)["stream"]
except ClientError as e:
logger.error(f"Unexpected error calling Bedrock API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e.response), model_name=self.name, model_id=self.id) from e
except Exception as e:
logger.error(f"Unexpected error calling Bedrock API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

# Overwrite the default from the base model
def format_function_call_results(
Expand Down
28 changes: 18 additions & 10 deletions libs/agno/agno/models/azure/ai_foundry.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,12 @@ def invoke(self, messages: List[Message]) -> Any:
)
except HttpResponseError as e:
logger.error(f"Azure AI API error: {e}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(
message=e.response.reason, status_code=e.response.status_code, model_name=self.name, model_id=self.id
) from e
except Exception as e:
logger.error(f"Error from Azure AI API: {e}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

async def ainvoke(self, messages: List[Message]) -> Any:
"""
Expand All @@ -239,10 +241,12 @@ async def ainvoke(self, messages: List[Message]) -> Any:
)
except HttpResponseError as e:
logger.error(f"Azure AI API error: {e}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(
message=e.response.reason, status_code=e.response.status_code, model_name=self.name, model_id=self.id
) from e
except Exception as e:
logger.error(f"Error from Azure AI API: {e}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

def invoke_stream(self, messages: List[Message]) -> Iterator[Any]:
"""
Expand All @@ -260,10 +264,12 @@ def invoke_stream(self, messages: List[Message]) -> Iterator[Any]:
)
except HttpResponseError as e:
logger.error(f"Azure AI API error: {e}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(
message=e.response.reason, status_code=e.response.status_code, model_name=self.name, model_id=self.id
) from e
except Exception as e:
logger.error(f"Error from Azure AI API: {e}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

async def ainvoke_stream(self, messages: List[Message]) -> AsyncIterator[Any]:
"""
Expand All @@ -287,10 +293,12 @@ async def ainvoke_stream(self, messages: List[Message]) -> AsyncIterator[Any]:

except HttpResponseError as e:
logger.error(f"Azure AI API error: {e}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(
message=e.response.reason, status_code=e.response.status_code, model_name=self.name, model_id=self.id
) from e
except Exception as e:
logger.error(f"Error from Azure AI API: {e}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

def parse_provider_response(self, response: ChatCompletions) -> ModelResponse:
"""
Expand Down Expand Up @@ -340,7 +348,7 @@ def parse_provider_response(self, response: ChatCompletions) -> ModelResponse:

except Exception as e:
logger.error(f"Error parsing Azure AI response: {e}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

return model_response

Expand Down Expand Up @@ -410,6 +418,6 @@ def parse_provider_response_delta(self, response_delta: StreamingChatCompletions

except Exception as e:
logger.error(f"Error parsing Azure AI response delta: {e}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

return model_response
2 changes: 0 additions & 2 deletions libs/agno/agno/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,6 @@ async def aresponse_stream(self, messages: List[Message]) -> AsyncIterator[Model

# Handle tool calls if present
if assistant_message.tool_calls is not None:
yield ModelResponse(content="\n\n")

# Prepare function calls
function_calls_to_run: List[FunctionCall] = self.get_function_calls_to_run(assistant_message, messages)
function_call_results: List[Message] = []
Expand Down
8 changes: 4 additions & 4 deletions libs/agno/agno/models/cohere/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def invoke(self, messages: List[Message]) -> ChatResponse:
return self.get_client().chat(model=self.id, messages=self._format_messages(messages), **request_kwargs) # type: ignore
except Exception as e:
logger.error(f"Unexpected error calling Cohere API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

def invoke_stream(self, messages: List[Message]) -> Iterator[StreamedChatResponseV2]:
"""
Expand All @@ -166,7 +166,7 @@ def invoke_stream(self, messages: List[Message]) -> Iterator[StreamedChatRespons
)
except Exception as e:
logger.error(f"Unexpected error calling Cohere API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

async def ainvoke(self, messages: List[Message]) -> ChatResponse:
"""
Expand All @@ -188,7 +188,7 @@ async def ainvoke(self, messages: List[Message]) -> ChatResponse:
)
except Exception as e:
logger.error(f"Unexpected error calling Cohere API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

async def ainvoke_stream(self, messages: List[Message]) -> AsyncIterator[StreamedChatResponseV2]:
"""
Expand All @@ -211,7 +211,7 @@ async def ainvoke_stream(self, messages: List[Message]) -> AsyncIterator[Streame
yield response
except Exception as e:
logger.error(f"Unexpected error calling Cohere API: {str(e)}")
raise ModelProviderError(e, self.name, self.id) from e
raise ModelProviderError(message=str(e), model_name=self.name, model_id=self.id) from e

def parse_provider_response(self, response: ChatResponse) -> ModelResponse:
"""
Expand Down
Loading

0 comments on commit 7938748

Please sign in to comment.