mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-10 00:28:31 +00:00
Merge branch 'main' into gl/fix/output-parser-exception-retry-logic
# Conflicts: # lib/crewai/src/crewai/llms/providers/anthropic/completion.py
This commit is contained in:
@@ -1200,6 +1200,52 @@ Learn how to get the most out of your LLM configuration:
|
||||
)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Transport Interceptors">
|
||||
CrewAI provides message interceptors for several providers, allowing you to hook into request/response cycles at the transport layer.
|
||||
|
||||
**Supported Providers:**
|
||||
- ✅ OpenAI
|
||||
- ✅ Anthropic
|
||||
|
||||
**Basic Usage:**
|
||||
```python
|
||||
import httpx
|
||||
from crewai import LLM
|
||||
from crewai.llms.hooks import BaseInterceptor
|
||||
|
||||
class CustomInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Custom interceptor to modify requests and responses."""
|
||||
|
||||
def on_outbound(self, request: httpx.Request) -> httpx.Request:
|
||||
"""Print request before sending to the LLM provider."""
|
||||
print(request)
|
||||
return request
|
||||
|
||||
def on_inbound(self, response: httpx.Response) -> httpx.Response:
|
||||
"""Process response after receiving from the LLM provider."""
|
||||
print(f"Status: {response.status_code}")
|
||||
print(f"Response time: {response.elapsed}")
|
||||
return response
|
||||
|
||||
# Use the interceptor with an LLM
|
||||
llm = LLM(
|
||||
model="openai/gpt-4o",
|
||||
interceptor=CustomInterceptor()
|
||||
)
|
||||
```
|
||||
|
||||
**Important Notes:**
|
||||
- Both methods must return the received object or type of object.
|
||||
- Modifying received objects may result in unexpected behavior or application crashes.
|
||||
- Not all providers support interceptors - check the supported providers list above
|
||||
|
||||
<Info>
|
||||
Interceptors operate at the transport layer. This is particularly useful for:
|
||||
- Message transformation and filtering
|
||||
- Debugging API interactions
|
||||
</Info>
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
@@ -20,6 +20,7 @@ from typing import (
|
||||
)
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import httpx
|
||||
from pydantic import BaseModel, Field
|
||||
from typing_extensions import Self
|
||||
|
||||
@@ -53,6 +54,7 @@ if TYPE_CHECKING:
|
||||
from litellm.utils import supports_response_schema
|
||||
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.task import Task
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
from crewai.utilities.types import LLMMessage
|
||||
@@ -334,6 +336,8 @@ class LLM(BaseLLM):
|
||||
return cast(
|
||||
Self, native_class(model=model_string, provider=provider, **kwargs)
|
||||
)
|
||||
except NotImplementedError:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise ImportError(f"Error importing native provider: {e}") from e
|
||||
|
||||
@@ -403,6 +407,7 @@ class LLM(BaseLLM):
|
||||
callbacks: list[Any] | None = None,
|
||||
reasoning_effort: Literal["none", "low", "medium", "high"] | None = None,
|
||||
stream: bool = False,
|
||||
interceptor: BaseInterceptor[httpx.Request, httpx.Response] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize LLM instance.
|
||||
@@ -442,6 +447,7 @@ class LLM(BaseLLM):
|
||||
self.additional_params = kwargs
|
||||
self.is_anthropic = self._is_anthropic_model(model)
|
||||
self.stream = stream
|
||||
self.interceptor = interceptor
|
||||
|
||||
litellm.drop_params = True
|
||||
|
||||
|
||||
6
lib/crewai/src/crewai/llms/hooks/__init__.py
Normal file
6
lib/crewai/src/crewai/llms/hooks/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""Interceptor contracts for crewai"""
|
||||
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
__all__ = ["BaseInterceptor"]
|
||||
82
lib/crewai/src/crewai/llms/hooks/base.py
Normal file
82
lib/crewai/src/crewai/llms/hooks/base.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""Base classes for LLM transport interceptors.
|
||||
|
||||
This module provides abstract base classes for intercepting and modifying
|
||||
outbound and inbound messages at the transport level.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
|
||||
|
||||
class BaseInterceptor(ABC, Generic[T, U]):
|
||||
"""Abstract base class for intercepting transport-level messages.
|
||||
|
||||
Provides hooks to intercept and modify outbound and inbound messages
|
||||
at the transport layer.
|
||||
|
||||
Type parameters:
|
||||
T: Outbound message type (e.g., httpx.Request)
|
||||
U: Inbound message type (e.g., httpx.Response)
|
||||
|
||||
Example:
|
||||
>>> class CustomInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
... def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
... message.headers["X-Custom-Header"] = "value"
|
||||
... return message
|
||||
...
|
||||
... def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
... print(f"Status: {message.status_code}")
|
||||
... return message
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def on_outbound(self, message: T) -> T:
|
||||
"""Intercept outbound message before sending.
|
||||
|
||||
Args:
|
||||
message: Outbound message object.
|
||||
|
||||
Returns:
|
||||
Modified message object.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def on_inbound(self, message: U) -> U:
|
||||
"""Intercept inbound message after receiving.
|
||||
|
||||
Args:
|
||||
message: Inbound message object.
|
||||
|
||||
Returns:
|
||||
Modified message object.
|
||||
"""
|
||||
...
|
||||
|
||||
async def aon_outbound(self, message: T) -> T:
|
||||
"""Async version of on_outbound.
|
||||
|
||||
Args:
|
||||
message: Outbound message object.
|
||||
|
||||
Returns:
|
||||
Modified message object.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def aon_inbound(self, message: U) -> U:
|
||||
"""Async version of on_inbound.
|
||||
|
||||
Args:
|
||||
message: Inbound message object.
|
||||
|
||||
Returns:
|
||||
Modified message object.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
87
lib/crewai/src/crewai/llms/hooks/transport.py
Normal file
87
lib/crewai/src/crewai/llms/hooks/transport.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""HTTP transport implementations for LLM request/response interception.
|
||||
|
||||
This module provides internal transport classes that integrate with BaseInterceptor
|
||||
to enable request/response modification at the transport level.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
class HTTPTransport(httpx.HTTPTransport):
|
||||
"""HTTP transport that uses an interceptor for request/response modification.
|
||||
|
||||
This transport is used internally when a user provides a BaseInterceptor.
|
||||
Users should not instantiate this class directly - instead, pass an interceptor
|
||||
to the LLM client and this transport will be created automatically.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
interceptor: BaseInterceptor[httpx.Request, httpx.Response],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize transport with interceptor.
|
||||
|
||||
Args:
|
||||
interceptor: HTTP interceptor for modifying raw request/response objects.
|
||||
**kwargs: Additional arguments passed to httpx.HTTPTransport.
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.interceptor = interceptor
|
||||
|
||||
def handle_request(self, request: httpx.Request) -> httpx.Response:
|
||||
"""Handle request with interception.
|
||||
|
||||
Args:
|
||||
request: The HTTP request to handle.
|
||||
|
||||
Returns:
|
||||
The HTTP response.
|
||||
"""
|
||||
request = self.interceptor.on_outbound(request)
|
||||
response = super().handle_request(request)
|
||||
return self.interceptor.on_inbound(response)
|
||||
|
||||
|
||||
class AsyncHTTPransport(httpx.AsyncHTTPTransport):
|
||||
"""Async HTTP transport that uses an interceptor for request/response modification.
|
||||
|
||||
This transport is used internally when a user provides a BaseInterceptor.
|
||||
Users should not instantiate this class directly - instead, pass an interceptor
|
||||
to the LLM client and this transport will be created automatically.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
interceptor: BaseInterceptor[httpx.Request, httpx.Response],
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize async transport with interceptor.
|
||||
|
||||
Args:
|
||||
interceptor: HTTP interceptor for modifying raw request/response objects.
|
||||
**kwargs: Additional arguments passed to httpx.AsyncHTTPTransport.
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.interceptor = interceptor
|
||||
|
||||
async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
|
||||
"""Handle async request with interception.
|
||||
|
||||
Args:
|
||||
request: The HTTP request to handle.
|
||||
|
||||
Returns:
|
||||
The HTTP response.
|
||||
"""
|
||||
request = await self.interceptor.aon_outbound(request)
|
||||
response = await super().handle_async_request(request)
|
||||
return await self.interceptor.aon_inbound(response)
|
||||
@@ -2,12 +2,13 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, cast
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from crewai.events.types.llm_events import LLMCallType
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llms.hooks.transport import HTTPTransport
|
||||
from crewai.utilities.agent_utils import is_context_length_exceeded
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededError,
|
||||
@@ -15,10 +16,14 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
from crewai.utilities.types import LLMMessage
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
|
||||
try:
|
||||
from anthropic import Anthropic
|
||||
from anthropic.types import Message
|
||||
from anthropic.types.tool_use_block import ToolUseBlock
|
||||
import httpx
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
'Anthropic native provider not available, to install: uv add "crewai[anthropic]"'
|
||||
@@ -45,6 +50,7 @@ class AnthropicCompletion(BaseLLM):
|
||||
stop_sequences: list[str] | None = None,
|
||||
stream: bool = False,
|
||||
client_params: dict[str, Any] | None = None,
|
||||
interceptor: BaseInterceptor[httpx.Request, httpx.Response] | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Initialize Anthropic chat completion client.
|
||||
@@ -61,6 +67,7 @@ class AnthropicCompletion(BaseLLM):
|
||||
stop_sequences: Stop sequences (Anthropic uses stop_sequences, not stop)
|
||||
stream: Enable streaming responses
|
||||
client_params: Additional parameters for the Anthropic client
|
||||
interceptor: HTTP interceptor for modifying requests/responses at transport level.
|
||||
**kwargs: Additional parameters
|
||||
"""
|
||||
super().__init__(
|
||||
@@ -68,6 +75,7 @@ class AnthropicCompletion(BaseLLM):
|
||||
)
|
||||
|
||||
# Client params
|
||||
self.interceptor = interceptor
|
||||
self.client_params = client_params
|
||||
self.base_url = base_url
|
||||
self.timeout = timeout
|
||||
@@ -100,6 +108,11 @@ class AnthropicCompletion(BaseLLM):
|
||||
"max_retries": self.max_retries,
|
||||
}
|
||||
|
||||
if self.interceptor:
|
||||
transport = HTTPTransport(interceptor=self.interceptor)
|
||||
http_client = httpx.Client(transport=transport)
|
||||
client_params["http_client"] = http_client # type: ignore[assignment]
|
||||
|
||||
if self.client_params:
|
||||
client_params.update(self.client_params)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -13,23 +13,25 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
)
|
||||
from crewai.utilities.types import LLMMessage
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
|
||||
|
||||
try:
|
||||
from azure.ai.inference import ( # type: ignore[import-not-found]
|
||||
from azure.ai.inference import (
|
||||
ChatCompletionsClient,
|
||||
)
|
||||
from azure.ai.inference.models import ( # type: ignore[import-not-found]
|
||||
from azure.ai.inference.models import (
|
||||
ChatCompletions,
|
||||
ChatCompletionsToolCall,
|
||||
StreamingChatCompletionsUpdate,
|
||||
)
|
||||
from azure.core.credentials import ( # type: ignore[import-not-found]
|
||||
from azure.core.credentials import (
|
||||
AzureKeyCredential,
|
||||
)
|
||||
from azure.core.exceptions import ( # type: ignore[import-not-found]
|
||||
from azure.core.exceptions import (
|
||||
HttpResponseError,
|
||||
)
|
||||
|
||||
@@ -64,7 +66,8 @@ class AzureCompletion(BaseLLM):
|
||||
max_tokens: int | None = None,
|
||||
stop: list[str] | None = None,
|
||||
stream: bool = False,
|
||||
**kwargs,
|
||||
interceptor: BaseInterceptor[Any, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Initialize Azure AI Inference chat completion client.
|
||||
|
||||
@@ -82,8 +85,15 @@ class AzureCompletion(BaseLLM):
|
||||
max_tokens: Maximum tokens in response
|
||||
stop: Stop sequences
|
||||
stream: Enable streaming responses
|
||||
interceptor: HTTP interceptor (not yet supported for Azure).
|
||||
**kwargs: Additional parameters
|
||||
"""
|
||||
if interceptor is not None:
|
||||
raise NotImplementedError(
|
||||
"HTTP interceptors are not yet supported for Azure AI Inference provider. "
|
||||
"Interceptors are currently supported for OpenAI and Anthropic providers only."
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
model=model, temperature=temperature, stop=stop or [], **kwargs
|
||||
)
|
||||
@@ -121,7 +131,7 @@ class AzureCompletion(BaseLLM):
|
||||
if self.api_version:
|
||||
client_kwargs["api_version"] = self.api_version
|
||||
|
||||
self.client = ChatCompletionsClient(**client_kwargs)
|
||||
self.client = ChatCompletionsClient(**client_kwargs) # type: ignore[arg-type]
|
||||
|
||||
self.top_p = top_p
|
||||
self.frequency_penalty = frequency_penalty
|
||||
@@ -249,7 +259,7 @@ class AzureCompletion(BaseLLM):
|
||||
def _prepare_completion_params(
|
||||
self,
|
||||
messages: list[LLMMessage],
|
||||
tools: list[dict] | None = None,
|
||||
tools: list[dict[str, Any]] | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Prepare parameters for Azure AI Inference chat completion.
|
||||
@@ -302,7 +312,9 @@ class AzureCompletion(BaseLLM):
|
||||
|
||||
return params
|
||||
|
||||
def _convert_tools_for_interference(self, tools: list[dict]) -> list[dict]:
|
||||
def _convert_tools_for_interference(
|
||||
self, tools: list[dict[str, Any]]
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Convert CrewAI tool format to Azure OpenAI function calling format."""
|
||||
|
||||
from crewai.llms.providers.utils.common import safe_tool_conversion
|
||||
|
||||
@@ -30,6 +30,8 @@ if TYPE_CHECKING:
|
||||
ToolTypeDef,
|
||||
)
|
||||
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
try:
|
||||
from boto3.session import Session
|
||||
@@ -157,8 +159,9 @@ class BedrockCompletion(BaseLLM):
|
||||
guardrail_config: dict[str, Any] | None = None,
|
||||
additional_model_request_fields: dict[str, Any] | None = None,
|
||||
additional_model_response_field_paths: list[str] | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
interceptor: BaseInterceptor[Any, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize AWS Bedrock completion client.
|
||||
|
||||
Args:
|
||||
@@ -176,8 +179,15 @@ class BedrockCompletion(BaseLLM):
|
||||
guardrail_config: Guardrail configuration for content filtering
|
||||
additional_model_request_fields: Model-specific request parameters
|
||||
additional_model_response_field_paths: Custom response field paths
|
||||
interceptor: HTTP interceptor (not yet supported for Bedrock).
|
||||
**kwargs: Additional parameters
|
||||
"""
|
||||
if interceptor is not None:
|
||||
raise NotImplementedError(
|
||||
"HTTP interceptors are not yet supported for AWS Bedrock provider. "
|
||||
"Interceptors are currently supported for OpenAI and Anthropic providers only."
|
||||
)
|
||||
|
||||
# Extract provider from kwargs to avoid duplicate argument
|
||||
kwargs.pop("provider", None)
|
||||
|
||||
@@ -247,7 +257,7 @@ class BedrockCompletion(BaseLLM):
|
||||
try:
|
||||
# Emit call started event
|
||||
self._emit_call_started_event(
|
||||
messages=messages, # type: ignore[arg-type]
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
callbacks=callbacks,
|
||||
available_functions=available_functions,
|
||||
@@ -740,7 +750,9 @@ class BedrockCompletion(BaseLLM):
|
||||
return converse_messages, system_message
|
||||
|
||||
@staticmethod
|
||||
def _format_tools_for_converse(tools: list[dict]) -> list[ConverseToolTypeDef]:
|
||||
def _format_tools_for_converse(
|
||||
tools: list[dict[str, Any]],
|
||||
) -> list[ConverseToolTypeDef]:
|
||||
"""Convert CrewAI tools to Converse API format following AWS specification."""
|
||||
from crewai.llms.providers.utils.common import safe_tool_conversion
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, cast
|
||||
@@ -7,6 +6,7 @@ from pydantic import BaseModel
|
||||
|
||||
from crewai.events.types.llm_events import LLMCallType
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.utilities.agent_utils import is_context_length_exceeded
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededError,
|
||||
@@ -45,7 +45,8 @@ class GeminiCompletion(BaseLLM):
|
||||
stream: bool = False,
|
||||
safety_settings: dict[str, Any] | None = None,
|
||||
client_params: dict[str, Any] | None = None,
|
||||
**kwargs,
|
||||
interceptor: BaseInterceptor[Any, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Initialize Google Gemini chat completion client.
|
||||
|
||||
@@ -63,8 +64,15 @@ class GeminiCompletion(BaseLLM):
|
||||
safety_settings: Safety filter settings
|
||||
client_params: Additional parameters to pass to the Google Gen AI Client constructor.
|
||||
Supports parameters like http_options, credentials, debug_config, etc.
|
||||
interceptor: HTTP interceptor (not yet supported for Gemini).
|
||||
**kwargs: Additional parameters
|
||||
"""
|
||||
if interceptor is not None:
|
||||
raise NotImplementedError(
|
||||
"HTTP interceptors are not yet supported for Google Gemini provider. "
|
||||
"Interceptors are currently supported for OpenAI and Anthropic providers only."
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
model=model, temperature=temperature, stop=stop_sequences or [], **kwargs
|
||||
)
|
||||
@@ -96,7 +104,7 @@ class GeminiCompletion(BaseLLM):
|
||||
self.is_gemini_1_5 = "gemini-1.5" in model.lower()
|
||||
self.supports_tools = self.is_gemini_1_5 or self.is_gemini_2
|
||||
|
||||
def _initialize_client(self, use_vertexai: bool = False) -> genai.Client:
|
||||
def _initialize_client(self, use_vertexai: bool = False) -> genai.Client: # type: ignore[no-any-unimported]
|
||||
"""Initialize the Google Gen AI client with proper parameter handling.
|
||||
|
||||
Args:
|
||||
@@ -171,7 +179,7 @@ class GeminiCompletion(BaseLLM):
|
||||
def call(
|
||||
self,
|
||||
messages: str | list[LLMMessage],
|
||||
tools: list[dict] | None = None,
|
||||
tools: list[dict[str, Any]] | None = None,
|
||||
callbacks: list[Any] | None = None,
|
||||
available_functions: dict[str, Any] | None = None,
|
||||
from_task: Any | None = None,
|
||||
@@ -193,7 +201,7 @@ class GeminiCompletion(BaseLLM):
|
||||
"""
|
||||
try:
|
||||
self._emit_call_started_event(
|
||||
messages=messages, # type: ignore[arg-type]
|
||||
messages=messages,
|
||||
tools=tools,
|
||||
callbacks=callbacks,
|
||||
available_functions=available_functions,
|
||||
@@ -203,7 +211,7 @@ class GeminiCompletion(BaseLLM):
|
||||
self.tools = tools
|
||||
|
||||
formatted_content, system_instruction = self._format_messages_for_gemini(
|
||||
messages # type: ignore[arg-type]
|
||||
messages
|
||||
)
|
||||
|
||||
config = self._prepare_generation_config(
|
||||
@@ -245,10 +253,10 @@ class GeminiCompletion(BaseLLM):
|
||||
)
|
||||
raise
|
||||
|
||||
def _prepare_generation_config(
|
||||
def _prepare_generation_config( # type: ignore[no-any-unimported]
|
||||
self,
|
||||
system_instruction: str | None = None,
|
||||
tools: list[dict] | None = None,
|
||||
tools: list[dict[str, Any]] | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
) -> types.GenerateContentConfig:
|
||||
"""Prepare generation config for Google Gemini API.
|
||||
@@ -297,7 +305,9 @@ class GeminiCompletion(BaseLLM):
|
||||
|
||||
return types.GenerateContentConfig(**config_params)
|
||||
|
||||
def _convert_tools_for_interference(self, tools: list[dict]) -> list[types.Tool]:
|
||||
def _convert_tools_for_interference( # type: ignore[no-any-unimported]
|
||||
self, tools: list[dict[str, Any]]
|
||||
) -> list[types.Tool]:
|
||||
"""Convert CrewAI tool format to Gemini function declaration format."""
|
||||
gemini_tools = []
|
||||
|
||||
@@ -320,7 +330,7 @@ class GeminiCompletion(BaseLLM):
|
||||
|
||||
return gemini_tools
|
||||
|
||||
def _format_messages_for_gemini(
|
||||
def _format_messages_for_gemini( # type: ignore[no-any-unimported]
|
||||
self, messages: str | list[LLMMessage]
|
||||
) -> tuple[list[types.Content], str | None]:
|
||||
"""Format messages for Gemini API.
|
||||
@@ -364,7 +374,7 @@ class GeminiCompletion(BaseLLM):
|
||||
|
||||
return contents, system_instruction
|
||||
|
||||
def _handle_completion(
|
||||
def _handle_completion( # type: ignore[no-any-unimported]
|
||||
self,
|
||||
contents: list[types.Content],
|
||||
system_instruction: str | None,
|
||||
@@ -431,7 +441,7 @@ class GeminiCompletion(BaseLLM):
|
||||
|
||||
return content
|
||||
|
||||
def _handle_streaming_completion(
|
||||
def _handle_streaming_completion( # type: ignore[no-any-unimported]
|
||||
self,
|
||||
contents: list[types.Content],
|
||||
config: types.GenerateContentConfig,
|
||||
@@ -560,8 +570,9 @@ class GeminiCompletion(BaseLLM):
|
||||
}
|
||||
return {"total_tokens": 0}
|
||||
|
||||
def _convert_contents_to_dict(
|
||||
self, contents: list[types.Content]
|
||||
def _convert_contents_to_dict( # type: ignore[no-any-unimported]
|
||||
self,
|
||||
contents: list[types.Content],
|
||||
) -> list[dict[str, str]]:
|
||||
"""Convert contents to dict format."""
|
||||
return [
|
||||
|
||||
@@ -6,6 +6,7 @@ import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import httpx
|
||||
from openai import APIConnectionError, NotFoundError, OpenAI
|
||||
from openai.types.chat import ChatCompletion, ChatCompletionChunk
|
||||
from openai.types.chat.chat_completion import Choice
|
||||
@@ -14,6 +15,7 @@ from pydantic import BaseModel
|
||||
|
||||
from crewai.events.types.llm_events import LLMCallType
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.llms.hooks.transport import HTTPTransport
|
||||
from crewai.utilities.agent_utils import is_context_length_exceeded
|
||||
from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededError,
|
||||
@@ -23,6 +25,7 @@ from crewai.utilities.types import LLMMessage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from crewai.agent.core import Agent
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.task import Task
|
||||
from crewai.tools.base_tool import BaseTool
|
||||
|
||||
@@ -59,6 +62,7 @@ class OpenAICompletion(BaseLLM):
|
||||
top_logprobs: int | None = None,
|
||||
reasoning_effort: str | None = None,
|
||||
provider: str | None = None,
|
||||
interceptor: BaseInterceptor[httpx.Request, httpx.Response] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initialize OpenAI chat completion client."""
|
||||
@@ -66,6 +70,7 @@ class OpenAICompletion(BaseLLM):
|
||||
if provider is None:
|
||||
provider = kwargs.pop("provider", "openai")
|
||||
|
||||
self.interceptor = interceptor
|
||||
# Client configuration attributes
|
||||
self.organization = organization
|
||||
self.project = project
|
||||
@@ -88,6 +93,11 @@ class OpenAICompletion(BaseLLM):
|
||||
)
|
||||
|
||||
client_config = self._get_client_params()
|
||||
if self.interceptor:
|
||||
transport = HTTPTransport(interceptor=self.interceptor)
|
||||
http_client = httpx.Client(transport=transport)
|
||||
client_config["http_client"] = http_client
|
||||
|
||||
self.client = OpenAI(**client_config)
|
||||
|
||||
# Completion parameters
|
||||
|
||||
@@ -1,21 +1,75 @@
|
||||
"""Utility functions for the crewai project module."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from functools import lru_cache
|
||||
from typing import ParamSpec, TypeVar, cast
|
||||
from functools import wraps
|
||||
from typing import Any, ParamSpec, TypeVar, cast
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from crewai.agents.cache.cache_handler import CacheHandler
|
||||
|
||||
|
||||
P = ParamSpec("P")
|
||||
R = TypeVar("R")
|
||||
cache = CacheHandler()
|
||||
|
||||
|
||||
def _make_hashable(arg: Any) -> Any:
|
||||
"""Convert argument to hashable form for caching.
|
||||
|
||||
Args:
|
||||
arg: The argument to convert.
|
||||
|
||||
Returns:
|
||||
Hashable representation of the argument.
|
||||
"""
|
||||
if isinstance(arg, BaseModel):
|
||||
return arg.model_dump_json()
|
||||
if isinstance(arg, dict):
|
||||
return tuple(sorted((k, _make_hashable(v)) for k, v in arg.items()))
|
||||
if isinstance(arg, list):
|
||||
return tuple(_make_hashable(item) for item in arg)
|
||||
if hasattr(arg, "__dict__"):
|
||||
return ("__instance__", id(arg))
|
||||
return arg
|
||||
|
||||
|
||||
def memoize(meth: Callable[P, R]) -> Callable[P, R]:
|
||||
"""Memoize a method by caching its results based on arguments.
|
||||
|
||||
Handles Pydantic BaseModel instances by converting them to JSON strings
|
||||
before hashing for cache lookup.
|
||||
|
||||
Args:
|
||||
meth: The method to memoize.
|
||||
|
||||
Returns:
|
||||
A memoized version of the method that caches results.
|
||||
"""
|
||||
return cast(Callable[P, R], lru_cache(typed=True)(meth))
|
||||
|
||||
@wraps(meth)
|
||||
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
"""Wrapper that converts arguments to hashable form before caching.
|
||||
|
||||
Args:
|
||||
*args: Positional arguments to the memoized method.
|
||||
**kwargs: Keyword arguments to the memoized method.
|
||||
|
||||
Returns:
|
||||
The result of the memoized method call.
|
||||
"""
|
||||
hashable_args = tuple(_make_hashable(arg) for arg in args)
|
||||
hashable_kwargs = tuple(
|
||||
sorted((k, _make_hashable(v)) for k, v in kwargs.items())
|
||||
)
|
||||
cache_key = str((hashable_args, hashable_kwargs))
|
||||
|
||||
cached_result: R | None = cache.read(tool=meth.__name__, input=cache_key)
|
||||
if cached_result is not None:
|
||||
return cached_result
|
||||
|
||||
result = meth(*args, **kwargs)
|
||||
cache.add(tool=meth.__name__, input=cache_key, output=result)
|
||||
return result
|
||||
|
||||
return cast(Callable[P, R], wrapper)
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"max_tokens":4096,"messages":[{"role":"user","content":"Reply with just
|
||||
the word: SUCCESS"}],"model":"claude-3-5-haiku-20241022","stream":false}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
anthropic-version:
|
||||
- '2023-06-01'
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '145'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.anthropic.com
|
||||
user-agent:
|
||||
- Anthropic/Python 0.71.0
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 0.71.0
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.10
|
||||
x-stainless-timeout:
|
||||
- NOT_GIVEN
|
||||
method: POST
|
||||
uri: https://api.anthropic.com/v1/messages
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAAwAAAP//dJDLasMwEEX/5a5lsF1nUe1KCARCu6gpFEoRgzTEamzJ1qNNCf734tDQ
|
||||
F10N3HNmBu4JgzfcQ0L3lA0XV8Wq6MgeclGXdVOVdQ0BayAxxL0qq9rcXm/vb2g37ehtc+xeHu+m
|
||||
4xYC6X3kxeIYac8QCL5fAorRxkQuQUB7l9glyKfTxU98XMh5SLQP6/WmbTE/C8TkRxWYoneQYGdU
|
||||
ysHhE0SeMjvNkC73vUA+f5UnWDfmpJI/sIuQVSOgSXesdGBK1jv1UygvPDCZ/9hld7nPY8cDB+rV
|
||||
avjrf9Gq+01nAZ/T96gRiBxerWaVLAdILE0ZCgbz/AEAAP//AwA4VVIcmwEAAA==
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 9997ac4cbfb443fa-EWR
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 04 Nov 2025 22:50:55 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Robots-Tag:
|
||||
- none
|
||||
anthropic-organization-id:
|
||||
- 87faf353-e074-4658-b885-bfac7aa5a7b5
|
||||
anthropic-ratelimit-input-tokens-limit:
|
||||
- '400000'
|
||||
anthropic-ratelimit-input-tokens-remaining:
|
||||
- '400000'
|
||||
anthropic-ratelimit-input-tokens-reset:
|
||||
- '2025-11-04T22:50:55Z'
|
||||
anthropic-ratelimit-output-tokens-limit:
|
||||
- '80000'
|
||||
anthropic-ratelimit-output-tokens-remaining:
|
||||
- '80000'
|
||||
anthropic-ratelimit-output-tokens-reset:
|
||||
- '2025-11-04T22:50:55Z'
|
||||
anthropic-ratelimit-requests-limit:
|
||||
- '4000'
|
||||
anthropic-ratelimit-requests-remaining:
|
||||
- '3999'
|
||||
anthropic-ratelimit-requests-reset:
|
||||
- '2025-11-04T22:50:54Z'
|
||||
anthropic-ratelimit-tokens-limit:
|
||||
- '480000'
|
||||
anthropic-ratelimit-tokens-remaining:
|
||||
- '480000'
|
||||
anthropic-ratelimit-tokens-reset:
|
||||
- '2025-11-04T22:50:55Z'
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
request-id:
|
||||
- req_011CUofgQ1jTrjQ2sveXU1cC
|
||||
retry-after:
|
||||
- '5'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
x-envoy-upstream-service-time:
|
||||
- '461'
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,108 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"max_tokens":4096,"messages":[{"role":"user","content":"Say ''Hello World''
|
||||
and nothing else"}],"model":"claude-3-5-haiku-20241022","stream":false}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
anthropic-version:
|
||||
- '2023-06-01'
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '146'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.anthropic.com
|
||||
user-agent:
|
||||
- Anthropic/Python 0.71.0
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 0.71.0
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.10
|
||||
x-stainless-timeout:
|
||||
- NOT_GIVEN
|
||||
method: POST
|
||||
uri: https://api.anthropic.com/v1/messages
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAAwAAAP//dJBNS8QwEIb/irznFNqu3UPOHvYoeGhFJIRk2IZNk5pMRCn979LF4hee
|
||||
Bt7nmRl4F0zRkoeE8bpYqg5VV43aXUrV1u1tU7ctBJyFxJTPqm7u3OP9sTFD7uJwLkN/6seHQwcB
|
||||
fp9psyhnfSYIpOi3QOfsMuvAEDAxMAWGfFp2n+ltI9chcSLv400fk7dYnwUyx1kl0jkGSFCwiksK
|
||||
+ASZXgoFQ5CheC9Qrp/lAhfmworjhUKGbI4CRpuRlEmk2cWgfgr1zhNp+x/bd7f7NI80UdJeddNf
|
||||
/4s242+6CsTC36NOIFN6dYYUO0qQ2NqyOlms6wcAAAD//wMArYPuQZ8BAAA=
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 9997ac4268d972a4-EWR
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 04 Nov 2025 22:50:53 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Response-Tracked:
|
||||
- 'true'
|
||||
X-Robots-Tag:
|
||||
- none
|
||||
anthropic-organization-id:
|
||||
- 87faf353-e074-4658-b885-bfac7aa5a7b5
|
||||
anthropic-ratelimit-input-tokens-limit:
|
||||
- '400000'
|
||||
anthropic-ratelimit-input-tokens-remaining:
|
||||
- '400000'
|
||||
anthropic-ratelimit-input-tokens-reset:
|
||||
- '2025-11-04T22:50:53Z'
|
||||
anthropic-ratelimit-output-tokens-limit:
|
||||
- '80000'
|
||||
anthropic-ratelimit-output-tokens-remaining:
|
||||
- '80000'
|
||||
anthropic-ratelimit-output-tokens-reset:
|
||||
- '2025-11-04T22:50:53Z'
|
||||
anthropic-ratelimit-requests-limit:
|
||||
- '4000'
|
||||
anthropic-ratelimit-requests-remaining:
|
||||
- '3999'
|
||||
anthropic-ratelimit-requests-reset:
|
||||
- '2025-11-04T22:50:53Z'
|
||||
anthropic-ratelimit-tokens-limit:
|
||||
- '480000'
|
||||
anthropic-ratelimit-tokens-remaining:
|
||||
- '480000'
|
||||
anthropic-ratelimit-tokens-reset:
|
||||
- '2025-11-04T22:50:53Z'
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
request-id:
|
||||
- req_011CUofgGvqpCtoqCmKcwXdK
|
||||
retry-after:
|
||||
- '7'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
x-envoy-upstream-service-time:
|
||||
- '441'
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,107 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"max_tokens":4096,"messages":[{"role":"user","content":"Count from 1 to
|
||||
3"}],"model":"claude-3-5-haiku-20241022","stream":false}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
anthropic-version:
|
||||
- '2023-06-01'
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '129'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.anthropic.com
|
||||
user-agent:
|
||||
- Anthropic/Python 0.71.0
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 0.71.0
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.10
|
||||
x-stainless-timeout:
|
||||
- NOT_GIVEN
|
||||
method: POST
|
||||
uri: https://api.anthropic.com/v1/messages
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA3SQy2rDMBBFf8XcTTcy2E5DQbuuUvrIJt3VRQh7EovYI1calbTB/14cGvqiq4F7
|
||||
zgzDPWLwLfXQaHqbWsoX+TLvrNunvCqqy7KoKii4FhpD3JmiXD2s7++u+HG1kcP77cGuN9tyuIaC
|
||||
vI00WxSj3REUgu/nwMboolgWKDSehVign45nX+gwk9PQuKFAFzFrfGJxvMu2wQ9ZmYnPFrrmmsua
|
||||
q5oXmJ4VovjRBLLRMzSIWyMpMD5BpJdE3BA0p75XSKev9BGOxyRG/J44QpdLhcY2HZkmkBXn2fwU
|
||||
ijMPZNv/2Hl3vk9jRwMF25vl8Nf/omX3m04KPsn3qCoUIoVX15ARRwEac5WtDS2m6QMAAP//AwC8
|
||||
QSj4vAEAAA==
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 9997ac45ea33de93-EWR
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 04 Nov 2025 22:50:54 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Robots-Tag:
|
||||
- none
|
||||
anthropic-organization-id:
|
||||
- 87faf353-e074-4658-b885-bfac7aa5a7b5
|
||||
anthropic-ratelimit-input-tokens-limit:
|
||||
- '400000'
|
||||
anthropic-ratelimit-input-tokens-remaining:
|
||||
- '400000'
|
||||
anthropic-ratelimit-input-tokens-reset:
|
||||
- '2025-11-04T22:50:54Z'
|
||||
anthropic-ratelimit-output-tokens-limit:
|
||||
- '80000'
|
||||
anthropic-ratelimit-output-tokens-remaining:
|
||||
- '80000'
|
||||
anthropic-ratelimit-output-tokens-reset:
|
||||
- '2025-11-04T22:50:54Z'
|
||||
anthropic-ratelimit-requests-limit:
|
||||
- '4000'
|
||||
anthropic-ratelimit-requests-remaining:
|
||||
- '3999'
|
||||
anthropic-ratelimit-requests-reset:
|
||||
- '2025-11-04T22:50:53Z'
|
||||
anthropic-ratelimit-tokens-limit:
|
||||
- '480000'
|
||||
anthropic-ratelimit-tokens-remaining:
|
||||
- '480000'
|
||||
anthropic-ratelimit-tokens-reset:
|
||||
- '2025-11-04T22:50:54Z'
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
request-id:
|
||||
- req_011CUofgKLSz1aDmL9qbduuR
|
||||
retry-after:
|
||||
- '6'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
x-envoy-upstream-service-time:
|
||||
- '801'
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,116 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages":[{"role":"user","content":"Reply with just the word: SUCCESS"}],"model":"gpt-4o-mini"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '98'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.10
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAAwAAAP//jJJNT+MwEIbv+RXWnJtVW/oBvbEVPexyq2AFCEXGnqReHI9lTxAr1P++
|
||||
clKa8CVxyWGeecfvO5mXTAgwGlYC1E6yqr3N1ze/Z8+Xm+X1+cU1P23qXydSnz5ubvXP8o+FUVLQ
|
||||
w19U/Kr6oaj2FtmQ67AKKBnT1MlyMZ2eLU6WkxbUpNEmWeU5n1FeG2fy6Xg6y8fLfHJ6UO/IKIyw
|
||||
EneZEEK8tN/k02l8hpUYj14rNcYoK4TVsUkICGRTBWSMJrJ0DKMeKnKMrrW+vVqvL7bbIQ1YNlEm
|
||||
h66xdgCkc8QyJWx93R/I/ujEUuUDPcR3UiiNM3FXBJSRXHo1Mnlo6T4T4r5N3LwJAT5Q7blgesT2
|
||||
ucmsGwf9ngfwwJhY2kF5PvpkWKGRpbFxsDBQUu1Q98p+u7LRhgYgG0T+6OWz2V1s46rvjO+BUugZ
|
||||
deEDaqPe5u3bAqYj/KrtuOLWMEQMT0ZhwQZD+g0aS9nY7jQg/ouMdVEaV2HwwXT3UfpivhjLcoHz
|
||||
+Rlk++w/AAAA//8DAGpm+y8tAwAA
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 9997a55e8e85d954-EWR
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 04 Nov 2025 22:46:11 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=Ljd6Yw.qJgdFyASoXMTCHgeOXz.kPJVf9verbOyhWzg-1762296371-1.0.1.1-HutBZMolyfao56ckVJOnqKZgW8SSm0S_xA1DF2HIE4eYlqsLEi3OtkeTKNc536CxqhcmuTINB23o_A6nID5TAGpXCeNYBEgLJKiggQamQ9w;
|
||||
path=/; expires=Tue, 04-Nov-25 23:16:11 GMT; domain=.api.openai.com; HttpOnly;
|
||||
Secure; SameSite=None
|
||||
- _cfuvid=Tz6VwwwbLcFpqp9Poc_3sUeqc33hmGkTq8YCekrTAns-1762296371669-0.0.1.1-604800000;
|
||||
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '194'
|
||||
openai-project:
|
||||
- proj_xitITlrFeen7zjNSzML82h9x
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '222'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-project-tokens:
|
||||
- '150000000'
|
||||
x-ratelimit-limit-requests:
|
||||
- '30000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '150000000'
|
||||
x-ratelimit-remaining-project-tokens:
|
||||
- '149999987'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '29999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '149999987'
|
||||
x-ratelimit-reset-project-tokens:
|
||||
- 0s
|
||||
x-ratelimit-reset-requests:
|
||||
- 2ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
x-request-id:
|
||||
- req_5f6372db5713441eb3ba1cc481aeb0fe
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,119 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages":[{"role":"user","content":"Say ''Hello World'' and nothing
|
||||
else"}],"model":"gpt-4o-mini"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '99'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.10
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAAwAAAP//jJJNT+MwEIbv+RXWnJtVGvoBvXLhS0Jc2EUIRcaepN51PJY9QaxQ/zty
|
||||
UpoUWGkvPviZd/y+43nLhACjYSNAbSWr1tv8/OF68Xr/wtKfXMaLhb6xv+71Dd1dGV3cwiwp6Pk3
|
||||
Kv5Q/VDUeotsyA1YBZSMqet8vSrLs9XJet6DljTaJGs85wvKW+NMXhblIi/W+fx0r96SURhhIx4z
|
||||
IYR468/k02l8hY0oZh83LcYoG4TNoUgICGTTDcgYTWTpGGYjVOQYXW/9Aq0l8ZOC1dOKgHUXZXLp
|
||||
OmsnQDpHLFPK3tvTnuwObiw1PtBz/CSF2jgTt1VAGcmllyOTh57uMiGe+tTdURDwgVrPFdMf7J+b
|
||||
L4d2MM56hOWeMbG0E8169k2zSiNLY+NkaKCk2qIeleOEZacNTUA2ifzVy3e9h9jGNf/TfgRKoWfU
|
||||
lQ+ojTrOO5YFTIv4r7LDiHvDEDG8GIUVGwzpGzTWsrPDekD8GxnbqjauweCDGXak9tVyVch6hcvl
|
||||
GWS77B0AAP//AwC41MWDMQMAAA==
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 9997a563da0042a3-EWR
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 04 Nov 2025 22:46:12 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=71X9mE9Hmg7j990_h7K01BESKxBp2D4QYv9j1PmSm6I-1762296372-1.0.1.1-V7pmEV0YDa.OeJ8Pht15YJt2XRqusPvH52QlHRhBCRAoGIkSmqMCG.rYS44HRNCR3Kf2D4UeRaNaUMgws1tL74cvebKOa_aGVjBw_O2okGc;
|
||||
path=/; expires=Tue, 04-Nov-25 23:16:12 GMT; domain=.api.openai.com; HttpOnly;
|
||||
Secure; SameSite=None
|
||||
- _cfuvid=.9w.Y6a8QsaD_7IAK4u3JaHCreibv0u6ujLC7HVF2nY-1762296372265-0.0.1.1-604800000;
|
||||
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
X-Response-Tracked:
|
||||
- 'true'
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '332'
|
||||
openai-project:
|
||||
- proj_xitITlrFeen7zjNSzML82h9x
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '349'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-project-tokens:
|
||||
- '150000000'
|
||||
x-ratelimit-limit-requests:
|
||||
- '30000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '150000000'
|
||||
x-ratelimit-remaining-project-tokens:
|
||||
- '149999987'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '29999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '149999990'
|
||||
x-ratelimit-reset-project-tokens:
|
||||
- 0s
|
||||
x-ratelimit-reset-requests:
|
||||
- 2ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
x-request-id:
|
||||
- req_21f5fd685fdf43e7b06e4ccf5f796b96
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,116 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages":[{"role":"user","content":"Count from 1 to 3"}],"model":"gpt-4o-mini"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '82'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.10
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAAwAAAP//jJJBb9swDIXv/hUCz3YRu4nT5FpgC9DrLttQGIpM29pkUZDobEGR/z7I
|
||||
TmO364BdfODHR71H8yURAnQNewGqk6x6Z7LHr0/rc85lR8fdIXw7DWd3ePr869Pj6fClgzQq6PgD
|
||||
Fb+q7hT1ziBrshNWHiVjnJpvy6LYlffbYgQ91WiirHWcrSnrtdVZsSrW2Wqb5Q9XdUdaYYC9+J4I
|
||||
IcTL+I0+bY2/YS9W6WulxxBki7C/NQkBnkysgAxBB5aWIZ2hIstoR+t5KopU3N8tscdmCDJatIMx
|
||||
CyCtJZYx4mjs+UouNyuGWufpGN5JodFWh67yKAPZ+GxgcjDSSyLE8xh5eJMCnKfeccX0E8fn8vU0
|
||||
DuZFz/DhyphYmrlcFOkHw6oaWWoTFhsDJVWH9ayc1yuHWtMCJIvIf3v5aPYUW9v2f8bPQCl0jHXl
|
||||
PNZavc07t3mMV/ivttuKR8MQ0J+0woo1+vgbamzkYKbbgHAOjH3VaNuid15PB9K4alOuZFPiZrOD
|
||||
5JL8AQAA//8DAIyvq4AuAwAA
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 9997a567bce9c35e-EWR
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 04 Nov 2025 22:46:13 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=WbtKMfrbJkHmW8iHwTlAt1O0TT9hmE7i6Jc4CuzPFkk-1762296373-1.0.1.1-H4_jBpfR_9YQFFm2iDhVCcmwtOAfFhVkN6HaUsD3H8frMqxJjj7oiLathDv89L6e412o.pMtaQVL5e5XfVEv0diMAwtUsWsbzbTwF3rgkug;
|
||||
path=/; expires=Tue, 04-Nov-25 23:16:13 GMT; domain=.api.openai.com; HttpOnly;
|
||||
Secure; SameSite=None
|
||||
- _cfuvid=rxHDwk1CRF6MkO5Jc7ikrkXBxkrhhmf.yJD6Z94mvUI-1762296373153-0.0.1.1-604800000;
|
||||
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '552'
|
||||
openai-project:
|
||||
- proj_xitITlrFeen7zjNSzML82h9x
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '601'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-project-tokens:
|
||||
- '150000000'
|
||||
x-ratelimit-limit-requests:
|
||||
- '30000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '150000000'
|
||||
x-ratelimit-remaining-project-tokens:
|
||||
- '149999992'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '29999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '149999992'
|
||||
x-ratelimit-reset-project-tokens:
|
||||
- 0s
|
||||
x-ratelimit-reset-requests:
|
||||
- 2ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
x-request-id:
|
||||
- req_f67a12044f894859b6b867e583e42e24
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -261,11 +261,9 @@ interactions:
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=REDACTED;
|
||||
path=/; expires=Wed, 05-Nov-25 13:58:46 GMT; domain=.api.openai.com; HttpOnly;
|
||||
Secure; SameSite=None
|
||||
- _cfuvid=REDACTED;
|
||||
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- __cf_bm=REDACTED; path=/; expires=Wed, 05-Nov-25 13:58:46 GMT; domain=.api.openai.com;
|
||||
HttpOnly; Secure; SameSite=None
|
||||
- _cfuvid=REDACTED; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
@@ -327,8 +325,7 @@ interactions:
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=REDACTED;
|
||||
_cfuvid=REDACTED
|
||||
- __cf_bm=REDACTED; _cfuvid=REDACTED
|
||||
host:
|
||||
- api.openai.com
|
||||
user-REDACTED:
|
||||
@@ -613,11 +610,9 @@ interactions:
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=REDACTED;
|
||||
path=/; expires=Wed, 05-Nov-25 13:58:52 GMT; domain=.api.openai.com; HttpOnly;
|
||||
Secure; SameSite=None
|
||||
- _cfuvid=REDACTED;
|
||||
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
- __cf_bm=REDACTED; path=/; expires=Wed, 05-Nov-25 13:58:52 GMT; domain=.api.openai.com;
|
||||
HttpOnly; Secure; SameSite=None
|
||||
- _cfuvid=REDACTED; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
Transfer-Encoding:
|
||||
@@ -695,8 +690,7 @@ interactions:
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=REDACTED;
|
||||
_cfuvid=REDACTED
|
||||
- __cf_bm=REDACTED; _cfuvid=REDACTED
|
||||
host:
|
||||
- api.openai.com
|
||||
user-REDACTED:
|
||||
@@ -946,8 +940,7 @@ interactions:
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=REDACTED;
|
||||
_cfuvid=REDACTED
|
||||
- __cf_bm=REDACTED; _cfuvid=REDACTED
|
||||
host:
|
||||
- api.openai.com
|
||||
user-REDACTED:
|
||||
@@ -1103,8 +1096,7 @@ interactions:
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=REDACTED;
|
||||
_cfuvid=REDACTED
|
||||
- __cf_bm=REDACTED; _cfuvid=REDACTED
|
||||
host:
|
||||
- api.openai.com
|
||||
user-REDACTED:
|
||||
@@ -1259,8 +1251,7 @@ interactions:
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=REDACTED;
|
||||
_cfuvid=REDACTED
|
||||
- __cf_bm=REDACTED; _cfuvid=REDACTED
|
||||
host:
|
||||
- api.openai.com
|
||||
user-REDACTED:
|
||||
@@ -1361,4 +1352,435 @@ interactions:
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"input":["Counting and Understanding Numbers(Topic): A math topic focusing
|
||||
on basic counting and number recognition suitable for 6-year-old children."],"model":"text-embedding-3-small","encoding_format":"base64"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '213'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- _cfuvid=REDACTED
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.9
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/embeddings
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
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
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 999df9fdfd6f5e4b-EWR
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 05 Nov 2025 17:12:32 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=Ipe.2Za34YNu_dh7H.OA5xwdSbGX5iazBRXn_3I5FHc-1762362752-1.0.1.1-HWtQ_T5RQayTNBrBXdCJpZOdvqkhREn3bsNJMdoniV3zlGFPv8HtTax9hHDHeuowqN.YAeUKu.YvNvCArHl4W_jOQB.peQWkEbB3vndJiZU;
|
||||
path=/; expires=Wed, 05-Nov-25 17:42:32 GMT; domain=.api.openai.com; HttpOnly;
|
||||
Secure; SameSite=None
|
||||
- _cfuvid=BseARaiBSIUng8tc7xw8RRLnBuo9liZGd5GtXIyeLX8-1762362752370-0.0.1.1-604800000;
|
||||
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-allow-origin:
|
||||
- '*'
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-model:
|
||||
- text-embedding-3-small
|
||||
openai-organization:
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
openai-processing-ms:
|
||||
- '393'
|
||||
openai-project:
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
via:
|
||||
- envoy-router-7dbb48b87-t6hdx
|
||||
x-envoy-upstream-service-time:
|
||||
- '653'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '3000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '1000000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '2999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '999966'
|
||||
x-ratelimit-reset-requests:
|
||||
- 20ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 2ms
|
||||
x-request-id:
|
||||
- req_8c16fc27349f4a508ea9a3dbb63b3f04
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"input":["Angle(Teaching Approach): Utilizes everyday objects to connect
|
||||
abstract numbers to tangible items to enhance understanding and engagement."],"model":"text-embedding-3-small","encoding_format":"base64"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, zstd
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '212'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- _cfuvid=BseARaiBSIUng8tc7xw8RRLnBuo9liZGd5GtXIyeLX8-1762362752370-0.0.1.1-604800000;
|
||||
__cf_bm=Ipe.2Za34YNu_dh7H.OA5xwdSbGX5iazBRXn_3I5FHc-1762362752-1.0.1.1-HWtQ_T5RQayTNBrBXdCJpZOdvqkhREn3bsNJMdoniV3zlGFPv8HtTax9hHDHeuowqN.YAeUKu.YvNvCArHl4W_jOQB.peQWkEbB3vndJiZU
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.109.1
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.109.1
|
||||
x-stainless-read-timeout:
|
||||
- '600'
|
||||
x-stainless-retry-count:
|
||||
- '0'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.9
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/embeddings
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
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
|
||||
BQAA//8DAFjeumvgIAAA
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 999dfa02dc345e4b-EWR
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 05 Nov 2025 17:12:32 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-allow-origin:
|
||||
- '*'
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-model:
|
||||
- text-embedding-3-small
|
||||
openai-organization:
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
openai-processing-ms:
|
||||
- '94'
|
||||
openai-project:
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
via:
|
||||
- envoy-router-7dbb48b87-ct5vk
|
||||
x-envoy-upstream-service-time:
|
||||
- '124'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-requests:
|
||||
- '3000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '1000000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '2999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '999966'
|
||||
x-ratelimit-reset-requests:
|
||||
- 20ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 2ms
|
||||
x-request-id:
|
||||
- req_2dcf36b5ee6549779e7f5891115cbd6c
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,539 +1,17 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages": [{"role": "system", "content": "You are Scorer. You''re an
|
||||
expert scorer, specialized in scoring titles.\nYour personal goal is: Score
|
||||
the title\nTo give my best complete final answer to the task use the exact following
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo
|
||||
give my best complete final answer to the task respond using the exact following
|
||||
format:\n\nThought: I now can give a great answer\nFinal Answer: Your final
|
||||
answer must be the great and the most complete as possible, it must be outcome
|
||||
described.\n\nI MUST use these formats, my job depends on it!"}, {"role": "user",
|
||||
"content": "\nCurrent Task: Give me an integer score between 1-5 for the following
|
||||
title: ''The impact of AI in the future of work''\n\nThis is the expect criteria
|
||||
for your final answer: The score of the title.\nyou MUST return the actual complete
|
||||
content as the final answer, not a summary.\n\nBegin! This is VERY important
|
||||
to you, use the tools available and give your best Final Answer, your job depends
|
||||
on it!\n\nThought:"}], "model": "gpt-4o"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '915'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=9.8sBYBkvBR8R1K_bVF7xgU..80XKlEIg3N2OBbTSCU-1727214102-1.0.1.1-.qiTLXbPamYUMSuyNsOEB9jhGu.jOifujOrx9E2JZvStbIZ9RTIiE44xKKNfLPxQkOi6qAT3h6htK8lPDGV_5g;
|
||||
_cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.47.0
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.47.0
|
||||
x-stainless-raw-response:
|
||||
- 'true'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.11.7
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
content: "{\n \"id\": \"chatcmpl-AB7gMdbh6Ncs7ekM3mpk0rfbH9oHy\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1727214502,\n \"model\": \"gpt-4o-2024-05-13\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": \"Thought: I now can give a great answer\\nFinal
|
||||
Answer: 4\",\n \"refusal\": null\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
186,\n \"completion_tokens\": 15,\n \"total_tokens\": 201,\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0\n }\n },\n \"system_fingerprint\": \"fp_52a7f40b0b\"\n}\n"
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8c85fa6eecc81cf3-GRU
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 24 Sep 2024 21:48:22 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '231'
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
x-ratelimit-limit-requests:
|
||||
- '10000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '30000000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '9999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '29999781'
|
||||
x-ratelimit-reset-requests:
|
||||
- 6ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
x-request-id:
|
||||
- req_04be4057cf9dce611e16f95ffa36a88a
|
||||
http_version: HTTP/1.1
|
||||
status_code: 200
|
||||
- request:
|
||||
body: '{"messages": [{"role": "user", "content": "4"}, {"role": "system", "content":
|
||||
"I''m gonna convert this raw text into valid JSON.\n\nThe json should have the
|
||||
following structure, with the following keys:\n{\n score: int\n}"}], "model":
|
||||
"gpt-4o", "tool_choice": {"type": "function", "function": {"name": "ScoreOutput"}},
|
||||
"tools": [{"type": "function", "function": {"name": "ScoreOutput", "description":
|
||||
"Correctly extracted `ScoreOutput` with all the required parameters with correct
|
||||
types", "parameters": {"properties": {"score": {"title": "Score", "type": "integer"}},
|
||||
"required": ["score"], "type": "object"}}}]}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '615'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=9.8sBYBkvBR8R1K_bVF7xgU..80XKlEIg3N2OBbTSCU-1727214102-1.0.1.1-.qiTLXbPamYUMSuyNsOEB9jhGu.jOifujOrx9E2JZvStbIZ9RTIiE44xKKNfLPxQkOi6qAT3h6htK8lPDGV_5g;
|
||||
_cfuvid=lbRdAddVWV6W3f5Dm9SaOPWDUOxqtZBSPr_fTW26nEA-1727213194587-0.0.1.1-604800000
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.47.0
|
||||
x-stainless-arch:
|
||||
- arm64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- MacOS
|
||||
x-stainless-package-version:
|
||||
- 1.47.0
|
||||
x-stainless-raw-response:
|
||||
- 'true'
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.11.7
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
content: "{\n \"id\": \"chatcmpl-AB7gNjFjFYTE9aEM06OLFECfe74NF\",\n \"object\":
|
||||
\"chat.completion\",\n \"created\": 1727214503,\n \"model\": \"gpt-4o-2024-05-13\",\n
|
||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||
\"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n
|
||||
\ \"id\": \"call_BriklYUCRXEHjLYyyPiFo1w7\",\n \"type\":
|
||||
\"function\",\n \"function\": {\n \"name\": \"ScoreOutput\",\n
|
||||
\ \"arguments\": \"{\\\"score\\\":4}\"\n }\n }\n
|
||||
\ ],\n \"refusal\": null\n },\n \"logprobs\": null,\n
|
||||
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||
100,\n \"completion_tokens\": 5,\n \"total_tokens\": 105,\n \"completion_tokens_details\":
|
||||
{\n \"reasoning_tokens\": 0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n"
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8c85fa72fadb1cf3-GRU
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Tue, 24 Sep 2024 21:48:23 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Content-Type-Options:
|
||||
- nosniff
|
||||
access-control-expose-headers:
|
||||
- X-Request-ID
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '221'
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
x-ratelimit-limit-requests:
|
||||
- '10000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '30000000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '9999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '29999947'
|
||||
x-ratelimit-reset-requests:
|
||||
- 6ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
x-request-id:
|
||||
- req_d75a37a0ce046c6a74a19fb24a97be79
|
||||
http_version: HTTP/1.1
|
||||
status_code: 200
|
||||
- request:
|
||||
body: '{"trace_id": "b4e722b9-c407-4653-ba06-1786963c9c4a", "execution_type":
|
||||
"crew", "user_identifier": null, "execution_context": {"crew_fingerprint": null,
|
||||
"crew_name": "crew", "flow_name": null, "crewai_version": "0.201.1", "privacy_level":
|
||||
"standard"}, "execution_metadata": {"expected_duration_estimate": 300, "agent_count":
|
||||
0, "task_count": 0, "flow_method_count": 0, "execution_started_at": "2025-10-08T18:15:00.412875+00:00"}}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate, zstd
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '428'
|
||||
Content-Type:
|
||||
- application/json
|
||||
User-Agent:
|
||||
- CrewAI-CLI/0.201.1
|
||||
X-Crewai-Organization-Id:
|
||||
- d3a3d10c-35db-423f-a7a4-c026030ba64d
|
||||
X-Crewai-Version:
|
||||
- 0.201.1
|
||||
method: POST
|
||||
uri: http://localhost:3000/crewai_plus/api/v1/tracing/batches
|
||||
response:
|
||||
body:
|
||||
string: '{"id":"1a36dd2f-483b-4934-a9b6-f7b95cee2824","trace_id":"b4e722b9-c407-4653-ba06-1786963c9c4a","execution_type":"crew","crew_name":"crew","flow_name":null,"status":"running","duration_ms":null,"crewai_version":"0.201.1","privacy_level":"standard","total_events":0,"execution_context":{"crew_fingerprint":null,"crew_name":"crew","flow_name":null,"crewai_version":"0.201.1","privacy_level":"standard"},"created_at":"2025-10-08T18:15:00.934Z","updated_at":"2025-10-08T18:15:00.934Z"}'
|
||||
headers:
|
||||
Content-Length:
|
||||
- '480'
|
||||
cache-control:
|
||||
- no-store
|
||||
content-security-policy:
|
||||
- 'default-src ''self'' *.crewai.com crewai.com; script-src ''self'' ''unsafe-inline''
|
||||
*.crewai.com crewai.com https://cdn.jsdelivr.net/npm/apexcharts https://www.gstatic.com
|
||||
https://run.pstmn.io https://apis.google.com https://apis.google.com/js/api.js
|
||||
https://accounts.google.com https://accounts.google.com/gsi/client https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css.map
|
||||
https://*.google.com https://docs.google.com https://slides.google.com https://js.hs-scripts.com
|
||||
https://js.sentry-cdn.com https://browser.sentry-cdn.com https://www.googletagmanager.com
|
||||
https://js-na1.hs-scripts.com https://share.descript.com/; style-src ''self''
|
||||
''unsafe-inline'' *.crewai.com crewai.com https://cdn.jsdelivr.net/npm/apexcharts;
|
||||
img-src ''self'' data: *.crewai.com crewai.com https://zeus.tools.crewai.com
|
||||
https://dashboard.tools.crewai.com https://cdn.jsdelivr.net; font-src ''self''
|
||||
data: *.crewai.com crewai.com; connect-src ''self'' *.crewai.com crewai.com
|
||||
https://zeus.tools.crewai.com https://connect.useparagon.com/ https://zeus.useparagon.com/*
|
||||
https://*.useparagon.com/* https://run.pstmn.io https://connect.tools.crewai.com/
|
||||
https://*.sentry.io https://www.google-analytics.com ws://localhost:3036 wss://localhost:3036;
|
||||
frame-src ''self'' *.crewai.com crewai.com https://connect.useparagon.com/
|
||||
https://zeus.tools.crewai.com https://zeus.useparagon.com/* https://connect.tools.crewai.com/
|
||||
https://docs.google.com https://drive.google.com https://slides.google.com
|
||||
https://accounts.google.com https://*.google.com https://www.youtube.com https://share.descript.com'
|
||||
content-type:
|
||||
- application/json; charset=utf-8
|
||||
etag:
|
||||
- W/"31db72e28a68dfa1c4f3568b388bc2f0"
|
||||
expires:
|
||||
- '0'
|
||||
permissions-policy:
|
||||
- camera=(), microphone=(self), geolocation=()
|
||||
pragma:
|
||||
- no-cache
|
||||
referrer-policy:
|
||||
- strict-origin-when-cross-origin
|
||||
server-timing:
|
||||
- cache_read.active_support;dur=0.18, sql.active_record;dur=73.01, cache_generate.active_support;dur=16.53,
|
||||
cache_write.active_support;dur=0.22, cache_read_multi.active_support;dur=0.33,
|
||||
start_processing.action_controller;dur=0.01, instantiation.active_record;dur=1.29,
|
||||
feature_operation.flipper;dur=0.50, start_transaction.active_record;dur=0.01,
|
||||
transaction.active_record;dur=21.52, process_action.action_controller;dur=459.22
|
||||
vary:
|
||||
- Accept
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
x-frame-options:
|
||||
- SAMEORIGIN
|
||||
x-permitted-cross-domain-policies:
|
||||
- none
|
||||
x-request-id:
|
||||
- ebc8e3ab-5979-48b7-8816-667a1fd98ce2
|
||||
x-runtime:
|
||||
- '0.524429'
|
||||
x-xss-protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 201
|
||||
message: Created
|
||||
- request:
|
||||
body: '{"events": [{"event_id": "4a27c1d9-f908-42e4-b4dc-7091db74915b", "timestamp":
|
||||
"2025-10-08T18:15:00.950855+00:00", "type": "crew_kickoff_started", "event_data":
|
||||
{"timestamp": "2025-10-08T18:15:00.412055+00:00", "type": "crew_kickoff_started",
|
||||
"source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
|
||||
"crew_name": "crew", "crew": null, "inputs": null}}, {"event_id": "eaa85c90-02d2-444c-bf52-1178d68bae6d",
|
||||
"timestamp": "2025-10-08T18:15:00.952277+00:00", "type": "task_started", "event_data":
|
||||
{"task_description": "Give me an integer score between 1-5 for the following
|
||||
title: ''The impact of AI in the future of work''", "expected_output": "The
|
||||
score of the title.", "task_name": "Give me an integer score between 1-5 for
|
||||
the following title: ''The impact of AI in the future of work''", "context":
|
||||
"", "agent_role": "Scorer", "task_id": "3dca2ae4-e374-42e6-a6de-ecae1e8ac310"}},
|
||||
{"event_id": "8f1cce5b-7a60-4b53-aac1-05a9d7c3335e", "timestamp": "2025-10-08T18:15:00.952865+00:00",
|
||||
"type": "agent_execution_started", "event_data": {"agent_role": "Scorer", "agent_goal":
|
||||
"Score the title", "agent_backstory": "You''re an expert scorer, specialized
|
||||
in scoring titles."}}, {"event_id": "754a8fb5-bb3a-4204-839e-7b622eb3d6dd",
|
||||
"timestamp": "2025-10-08T18:15:00.953005+00:00", "type": "llm_call_started",
|
||||
"event_data": {"timestamp": "2025-10-08T18:15:00.952957+00:00", "type": "llm_call_started",
|
||||
"source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
|
||||
"task_name": "Give me an integer score between 1-5 for the following title:
|
||||
''The impact of AI in the future of work''", "task_id": "3dca2ae4-e374-42e6-a6de-ecae1e8ac310",
|
||||
"agent_id": "2ba6f80d-a1da-409f-bd89-13a286b7dfb7", "agent_role": "Scorer",
|
||||
"from_task": null, "from_agent": null, "model": "gpt-4o-mini", "messages": [{"role":
|
||||
"system", "content": "You are Scorer. You''re an expert scorer, specialized
|
||||
in scoring titles.\nYour personal goal is: Score the title\nTo give my best
|
||||
complete final answer to the task respond using the exact following format:\n\nThought:
|
||||
I now can give a great answer\nFinal Answer: Your final answer must be the great
|
||||
and the most complete as possible, it must be outcome described.\n\nI MUST use
|
||||
these formats, my job depends on it!"}, {"role": "user", "content": "\nCurrent
|
||||
described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\nEnsure your final answer contains only
|
||||
the content in the following format: {\n \"score\": int\n}\n\nEnsure the final
|
||||
output does not include any code block markers like ```json or ```python.\n\nBegin!
|
||||
This is VERY important to you, use the tools available and give your best Final
|
||||
Answer, your job depends on it!\n\nThought:"}], "tools": null, "callbacks":
|
||||
["<crewai.utilities.token_counter_callback.TokenCalcHandler object at 0x300f52060>"],
|
||||
"available_functions": null}}, {"event_id": "1a15dcc3-8827-4803-ac61-ab70d5be90f3",
|
||||
"timestamp": "2025-10-08T18:15:01.085142+00:00", "type": "llm_call_completed",
|
||||
"event_data": {"timestamp": "2025-10-08T18:15:01.084844+00:00", "type": "llm_call_completed",
|
||||
"source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
|
||||
"task_name": "Give me an integer score between 1-5 for the following title:
|
||||
''The impact of AI in the future of work''", "task_id": "3dca2ae4-e374-42e6-a6de-ecae1e8ac310",
|
||||
"agent_id": "2ba6f80d-a1da-409f-bd89-13a286b7dfb7", "agent_role": "Scorer",
|
||||
"from_task": null, "from_agent": null, "messages": [{"role": "system", "content":
|
||||
"You are Scorer. You''re an expert scorer, specialized in scoring titles.\nYour
|
||||
personal goal is: Score the title\nTo give my best complete final answer to
|
||||
the task respond using the exact following format:\n\nThought: I now can give
|
||||
a great answer\nFinal Answer: Your final answer must be the great and the most
|
||||
complete as possible, it must be outcome described.\n\nI MUST use these formats,
|
||||
my job depends on it!"}, {"role": "user", "content": "\nCurrent Task: Give me
|
||||
an integer score between 1-5 for the following title: ''The impact of AI in
|
||||
the future of work''\n\nThis is the expected criteria for your final answer:
|
||||
The score of the title.\nyou MUST return the actual complete content as the
|
||||
final answer, not a summary.\nEnsure your final answer contains only the content
|
||||
in the following format: {\n \"score\": int\n}\n\nEnsure the final output does
|
||||
not include any code block markers like ```json or ```python.\n\nBegin! This
|
||||
is VERY important to you, use the tools available and give your best Final Answer,
|
||||
your job depends on it!\n\nThought:"}], "response": "Thought: I now can give
|
||||
a great answer\nFinal Answer: 4", "call_type": "<LLMCallType.LLM_CALL: ''llm_call''>",
|
||||
"model": "gpt-4o-mini"}}, {"event_id": "c6742bd6-5a92-41e8-94f6-49ba267d785f",
|
||||
"timestamp": "2025-10-08T18:15:01.085480+00:00", "type": "agent_execution_completed",
|
||||
"event_data": {"agent_role": "Scorer", "agent_goal": "Score the title", "agent_backstory":
|
||||
"You''re an expert scorer, specialized in scoring titles."}}, {"event_id": "486c254c-57b6-477b-aadc-d5be745613fb",
|
||||
"timestamp": "2025-10-08T18:15:01.085639+00:00", "type": "task_failed", "event_data":
|
||||
{"serialization_error": "Circular reference detected (id repeated)", "object_type":
|
||||
"TaskFailedEvent"}}, {"event_id": "b2ce4ceb-74f6-4379-b65e-8d6dc371f956", "timestamp":
|
||||
"2025-10-08T18:15:01.086242+00:00", "type": "crew_kickoff_failed", "event_data":
|
||||
{"timestamp": "2025-10-08T18:15:01.086226+00:00", "type": "crew_kickoff_failed",
|
||||
"source_fingerprint": null, "source_type": null, "fingerprint_metadata": null,
|
||||
"crew_name": "crew", "crew": null, "error": "Failed to convert text into a Pydantic
|
||||
model due to error: ''NoneType'' object has no attribute ''supports_function_calling''"}}],
|
||||
"batch_metadata": {"events_count": 8, "batch_sequence": 1, "is_final_batch":
|
||||
false}}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate, zstd
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '5982'
|
||||
Content-Type:
|
||||
- application/json
|
||||
User-Agent:
|
||||
- CrewAI-CLI/0.201.1
|
||||
X-Crewai-Organization-Id:
|
||||
- d3a3d10c-35db-423f-a7a4-c026030ba64d
|
||||
X-Crewai-Version:
|
||||
- 0.201.1
|
||||
method: POST
|
||||
uri: http://localhost:3000/crewai_plus/api/v1/tracing/batches/b4e722b9-c407-4653-ba06-1786963c9c4a/events
|
||||
response:
|
||||
body:
|
||||
string: '{"events_created":8,"trace_batch_id":"1a36dd2f-483b-4934-a9b6-f7b95cee2824"}'
|
||||
headers:
|
||||
Content-Length:
|
||||
- '76'
|
||||
cache-control:
|
||||
- no-store
|
||||
content-security-policy:
|
||||
- 'default-src ''self'' *.crewai.com crewai.com; script-src ''self'' ''unsafe-inline''
|
||||
*.crewai.com crewai.com https://cdn.jsdelivr.net/npm/apexcharts https://www.gstatic.com
|
||||
https://run.pstmn.io https://apis.google.com https://apis.google.com/js/api.js
|
||||
https://accounts.google.com https://accounts.google.com/gsi/client https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css.map
|
||||
https://*.google.com https://docs.google.com https://slides.google.com https://js.hs-scripts.com
|
||||
https://js.sentry-cdn.com https://browser.sentry-cdn.com https://www.googletagmanager.com
|
||||
https://js-na1.hs-scripts.com https://share.descript.com/; style-src ''self''
|
||||
''unsafe-inline'' *.crewai.com crewai.com https://cdn.jsdelivr.net/npm/apexcharts;
|
||||
img-src ''self'' data: *.crewai.com crewai.com https://zeus.tools.crewai.com
|
||||
https://dashboard.tools.crewai.com https://cdn.jsdelivr.net; font-src ''self''
|
||||
data: *.crewai.com crewai.com; connect-src ''self'' *.crewai.com crewai.com
|
||||
https://zeus.tools.crewai.com https://connect.useparagon.com/ https://zeus.useparagon.com/*
|
||||
https://*.useparagon.com/* https://run.pstmn.io https://connect.tools.crewai.com/
|
||||
https://*.sentry.io https://www.google-analytics.com ws://localhost:3036 wss://localhost:3036;
|
||||
frame-src ''self'' *.crewai.com crewai.com https://connect.useparagon.com/
|
||||
https://zeus.tools.crewai.com https://zeus.useparagon.com/* https://connect.tools.crewai.com/
|
||||
https://docs.google.com https://drive.google.com https://slides.google.com
|
||||
https://accounts.google.com https://*.google.com https://www.youtube.com https://share.descript.com'
|
||||
content-type:
|
||||
- application/json; charset=utf-8
|
||||
etag:
|
||||
- W/"ec084df3e365d72581f5734016786212"
|
||||
expires:
|
||||
- '0'
|
||||
permissions-policy:
|
||||
- camera=(), microphone=(self), geolocation=()
|
||||
pragma:
|
||||
- no-cache
|
||||
referrer-policy:
|
||||
- strict-origin-when-cross-origin
|
||||
server-timing:
|
||||
- cache_read.active_support;dur=0.05, sql.active_record;dur=60.65, cache_generate.active_support;dur=2.12,
|
||||
cache_write.active_support;dur=0.12, cache_read_multi.active_support;dur=0.09,
|
||||
start_processing.action_controller;dur=0.00, instantiation.active_record;dur=0.51,
|
||||
start_transaction.active_record;dur=0.00, transaction.active_record;dur=115.06,
|
||||
process_action.action_controller;dur=475.82
|
||||
vary:
|
||||
- Accept
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
x-frame-options:
|
||||
- SAMEORIGIN
|
||||
x-permitted-cross-domain-policies:
|
||||
- none
|
||||
x-request-id:
|
||||
- 96f38d13-8f41-4b6f-b41a-cc526f821efd
|
||||
x-runtime:
|
||||
- '0.520997'
|
||||
x-xss-protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"status": "completed", "duration_ms": 1218, "final_event_count": 8}'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate, zstd
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '68'
|
||||
Content-Type:
|
||||
- application/json
|
||||
User-Agent:
|
||||
- CrewAI-CLI/0.201.1
|
||||
X-Crewai-Organization-Id:
|
||||
- d3a3d10c-35db-423f-a7a4-c026030ba64d
|
||||
X-Crewai-Version:
|
||||
- 0.201.1
|
||||
method: PATCH
|
||||
uri: http://localhost:3000/crewai_plus/api/v1/tracing/batches/b4e722b9-c407-4653-ba06-1786963c9c4a/finalize
|
||||
response:
|
||||
body:
|
||||
string: '{"id":"1a36dd2f-483b-4934-a9b6-f7b95cee2824","trace_id":"b4e722b9-c407-4653-ba06-1786963c9c4a","execution_type":"crew","crew_name":"crew","flow_name":null,"status":"completed","duration_ms":1218,"crewai_version":"0.201.1","privacy_level":"standard","total_events":8,"execution_context":{"crew_name":"crew","flow_name":null,"privacy_level":"standard","crewai_version":"0.201.1","crew_fingerprint":null},"created_at":"2025-10-08T18:15:00.934Z","updated_at":"2025-10-08T18:15:02.539Z"}'
|
||||
headers:
|
||||
Content-Length:
|
||||
- '482'
|
||||
cache-control:
|
||||
- no-store
|
||||
content-security-policy:
|
||||
- 'default-src ''self'' *.crewai.com crewai.com; script-src ''self'' ''unsafe-inline''
|
||||
*.crewai.com crewai.com https://cdn.jsdelivr.net/npm/apexcharts https://www.gstatic.com
|
||||
https://run.pstmn.io https://apis.google.com https://apis.google.com/js/api.js
|
||||
https://accounts.google.com https://accounts.google.com/gsi/client https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css.map
|
||||
https://*.google.com https://docs.google.com https://slides.google.com https://js.hs-scripts.com
|
||||
https://js.sentry-cdn.com https://browser.sentry-cdn.com https://www.googletagmanager.com
|
||||
https://js-na1.hs-scripts.com https://share.descript.com/; style-src ''self''
|
||||
''unsafe-inline'' *.crewai.com crewai.com https://cdn.jsdelivr.net/npm/apexcharts;
|
||||
img-src ''self'' data: *.crewai.com crewai.com https://zeus.tools.crewai.com
|
||||
https://dashboard.tools.crewai.com https://cdn.jsdelivr.net; font-src ''self''
|
||||
data: *.crewai.com crewai.com; connect-src ''self'' *.crewai.com crewai.com
|
||||
https://zeus.tools.crewai.com https://connect.useparagon.com/ https://zeus.useparagon.com/*
|
||||
https://*.useparagon.com/* https://run.pstmn.io https://connect.tools.crewai.com/
|
||||
https://*.sentry.io https://www.google-analytics.com ws://localhost:3036 wss://localhost:3036;
|
||||
frame-src ''self'' *.crewai.com crewai.com https://connect.useparagon.com/
|
||||
https://zeus.tools.crewai.com https://zeus.useparagon.com/* https://connect.tools.crewai.com/
|
||||
https://docs.google.com https://drive.google.com https://slides.google.com
|
||||
https://accounts.google.com https://*.google.com https://www.youtube.com https://share.descript.com'
|
||||
content-type:
|
||||
- application/json; charset=utf-8
|
||||
etag:
|
||||
- W/"f69bd753b6206f7d8f00bfae64391d7a"
|
||||
expires:
|
||||
- '0'
|
||||
permissions-policy:
|
||||
- camera=(), microphone=(self), geolocation=()
|
||||
pragma:
|
||||
- no-cache
|
||||
referrer-policy:
|
||||
- strict-origin-when-cross-origin
|
||||
server-timing:
|
||||
- cache_read.active_support;dur=0.13, sql.active_record;dur=20.82, cache_generate.active_support;dur=2.02,
|
||||
cache_write.active_support;dur=0.18, cache_read_multi.active_support;dur=0.08,
|
||||
start_processing.action_controller;dur=0.00, instantiation.active_record;dur=1.08,
|
||||
unpermitted_parameters.action_controller;dur=0.00, start_transaction.active_record;dur=0.00,
|
||||
transaction.active_record;dur=2.90, process_action.action_controller;dur=844.67
|
||||
vary:
|
||||
- Accept
|
||||
x-content-type-options:
|
||||
- nosniff
|
||||
x-frame-options:
|
||||
- SAMEORIGIN
|
||||
x-permitted-cross-domain-policies:
|
||||
- none
|
||||
x-request-id:
|
||||
- 80da6a7c-c07d-4a00-b5d9-fb85448ef76a
|
||||
x-runtime:
|
||||
- '0.904849'
|
||||
x-xss-protection:
|
||||
- 1; mode=block
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"Please convert the following text
|
||||
into valid JSON.\n\nOutput ONLY the valid JSON and nothing else.\n\nThe JSON
|
||||
must follow this schema exactly:\n```json\n{\n score: int\n}\n```"},{"role":"user","content":"4"}],"model":"gpt-4.1-mini"}'
|
||||
as the final answer, not a summary.\n\nBegin! This is VERY important to you,
|
||||
use the tools available and give your best Final Answer, your job depends on
|
||||
it!\n\nThought:"}],"model":"gpt-4.1-mini"}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
@@ -542,7 +20,7 @@ interactions:
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '277'
|
||||
- '923'
|
||||
content-type:
|
||||
- application/json
|
||||
host:
|
||||
@@ -566,23 +44,23 @@ interactions:
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.10
|
||||
- 3.12.9
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAAwAAAP//jJIxb9swEIV3/QriZiuwFNlxtBadig6ZGrQKJJo8SbQpkiWpwoHh/16Q
|
||||
ci0lTYEuGu679/TueOeEEBAcSgKsp54NRqafvj2ffh4PX56e2n37+vW7Gj8/n/qjE/64OcAqKPT+
|
||||
gMz/Ud0xPRiJXmg1YWaRegyu2cM22z3crx/XEQyaowyyzvi0uMvSQSiR5ut8k66LNCuu8l4Lhg5K
|
||||
8iMhhJBz/IagiuMJShLNYmVA52iHUN6aCAGrZagAdU44T5WH1QyZVh5VzN40zcFpValzpQKrwDFt
|
||||
sYKSFJW6VKppmqXUYjs6GvKrUcoFoEppT8P8MfTLlVxuMaXujNV7904KrVDC9bVF6rQKkZzXBiK9
|
||||
JIS8xHWMbyYEY/VgfO31EePvisfJDuZXmGF2f4Veeyrn+jZffeBWc/RUSLdYJzDKeuSzct49HbnQ
|
||||
C5AsZv47zEfe09xCdf9jPwPG0HjktbHIBXs78NxmMdzov9puO46BwaH9JRjWXqAN78CxpaOcDgfc
|
||||
q/M41K1QHVpjxXQ9rakLlu82Wbvb5pBckt8AAAD//wMA5Zmg4EwDAAA=
|
||||
H4sIAAAAAAAAA4xSTU/cMBC951eMfN6gTch+KLeCQIDaC+XAtkWR15kkUxzbsh22Ldr/jpwsm1Co
|
||||
1EukzJv3/N7MPEcAjEqWAxMN96I1Mj7fbM+vrs+qG3fbXdHtt+zz/X3z9cufzd3mYsdmgaG3P1H4
|
||||
V9aJ0K2R6EmrARYWucegmqyW6ekyTVanPdDqEmWg1cbH2UkSt6QoTufpIp5ncZId6I0mgY7l8D0C
|
||||
AHjuv8GoKvEXy2E+e6206ByvkeXHJgBmtQwVxp0j57nybDaCQiuPqvd+1+iubnwO16D0DgRXUNMT
|
||||
Aoc6BACu3A7tD3VJikv41P/lkE3VLFad4yGS6qScAFwp7XkYSZ/j4YDsj86lro3VW/cXlVWkyDWF
|
||||
Re60Ci6d14b16D4CeOgn1L0JzYzVrfGF14/YP5esV4MeGzczQRcH0GvP5VhP5+nsA72iRM9JusmM
|
||||
meCiwXKkjgvhXUl6AkST1O/dfKQ9JCdV/4/8CAiBxmNZGIslibeJxzaL4XD/1Xaccm+YObRPJLDw
|
||||
hDZsosSKd3K4JuZ+O49tUZGq0RpLw0lVpshEul4k1XqZsmgfvQAAAP//AwDqWlfDYQMAAA==
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 996f475b7e3fedda-MXP
|
||||
- 999debdd89d62223-EWR
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
@@ -590,14 +68,14 @@ interactions:
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 31 Oct 2025 01:11:31 GMT
|
||||
- Wed, 05 Nov 2025 17:02:53 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=9OwoBJAn84Nsq0RZdCIu06cNB6RLqor4C1.Q58nU28U-1761873091-1.0.1.1-p82_h8Vnxe0NfH5Iv6MFt.SderZj.v9VnCx_ro6ti2MGhlJOLFsPd6XhBxPsnmuV7Vt_4_uqAbE57E5f1Epl1cmGBT.0844N3CLnTwZFWQI;
|
||||
path=/; expires=Fri, 31-Oct-25 01:41:31 GMT; domain=.api.openai.com; HttpOnly;
|
||||
- __cf_bm=tfabkCaSRhhrfazOeTzwx40DBXVo2PjV9viPz7rNRSA-1762362173-1.0.1.1-448VqZWH1ZRKLLTCSb9Qezp9.0dIiDqeZ0CVT8Q.dmfbePuniUR.EDrrXv4lBIWqNgZ_yF1ety_tftCaKIm0Uq4ydaAKdcHVUoOMUjhtvY4;
|
||||
path=/; expires=Wed, 05-Nov-25 17:32:53 GMT; domain=.api.openai.com; HttpOnly;
|
||||
Secure; SameSite=None
|
||||
- _cfuvid=E4.xW3I8m58fngo4vkTKo8hmBumar1HkV.yU8KKjlZg-1761873091967-0.0.1.1-604800000;
|
||||
- _cfuvid=l9MNhSJeUsRd3C2.MkHdNv0aEoH19qe6RpeKp4Gf.do-1762362173542-0.0.1.1-604800000;
|
||||
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Strict-Transport-Security:
|
||||
- max-age=31536000; includeSubDomains; preload
|
||||
@@ -612,44 +90,48 @@ interactions:
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
openai-processing-ms:
|
||||
- '1770'
|
||||
- '408'
|
||||
openai-project:
|
||||
- proj_xitITlrFeen7zjNSzML82h9x
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '1998'
|
||||
- '421'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-project-tokens:
|
||||
- '150000000'
|
||||
x-ratelimit-limit-requests:
|
||||
- '30000'
|
||||
- '500'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '150000000'
|
||||
x-ratelimit-remaining-project-tokens:
|
||||
- '149999955'
|
||||
- '200000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '29999'
|
||||
- '499'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '149999952'
|
||||
x-ratelimit-reset-project-tokens:
|
||||
- 0s
|
||||
- '199794'
|
||||
x-ratelimit-reset-requests:
|
||||
- 2ms
|
||||
- 120ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
- 61ms
|
||||
x-request-id:
|
||||
- req_ba7a12cb40744f648d17844196f9c2c6
|
||||
- req_f1d89dcf14e44e1590e646ecef55c6ff
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: '{"messages":[{"role":"system","content":"Please convert the following text
|
||||
into valid JSON.\n\nOutput ONLY the valid JSON and nothing else.\n\nThe JSON
|
||||
must follow this schema exactly:\n```json\n{\n score: int\n}\n```"},{"role":"user","content":"4"}],"model":"gpt-4.1-mini","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
body: '{"messages":[{"role":"system","content":"You are Scorer. You''re an expert
|
||||
scorer, specialized in scoring titles.\nYour personal goal is: Score the title\nTo
|
||||
give my best complete final answer to the task respond using the exact following
|
||||
format:\n\nThought: I now can give a great answer\nFinal Answer: Your final
|
||||
answer must be the great and the most complete as possible, it must be outcome
|
||||
described.\n\nI MUST use these formats, my job depends on it!"},{"role":"user","content":"\nCurrent
|
||||
Task: Give me an integer score between 1-5 for the following title: ''The impact
|
||||
of AI in the future of work''\n\nThis is the expected criteria for your final
|
||||
answer: The score of the title.\nyou MUST return the actual complete content
|
||||
as the final answer, not a summary.\n\nBegin! This is VERY important to you,
|
||||
use the tools available and give your best Final Answer, your job depends on
|
||||
it!\n\nThought:"},{"role":"assistant","content":"Thought: I now can give a great
|
||||
answer\nFinal Answer: 4"}],"model":"gpt-4.1-mini","response_format":{"type":"json_schema","json_schema":{"schema":{"properties":{"score":{"title":"Score","type":"integer"}},"required":["score"],"title":"ScoreOutput","type":"object","additionalProperties":false},"name":"ScoreOutput","strict":true}},"stream":false}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
@@ -658,12 +140,12 @@ interactions:
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '541'
|
||||
- '1276'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- __cf_bm=9OwoBJAn84Nsq0RZdCIu06cNB6RLqor4C1.Q58nU28U-1761873091-1.0.1.1-p82_h8Vnxe0NfH5Iv6MFt.SderZj.v9VnCx_ro6ti2MGhlJOLFsPd6XhBxPsnmuV7Vt_4_uqAbE57E5f1Epl1cmGBT.0844N3CLnTwZFWQI;
|
||||
_cfuvid=E4.xW3I8m58fngo4vkTKo8hmBumar1HkV.yU8KKjlZg-1761873091967-0.0.1.1-604800000
|
||||
- __cf_bm=tfabkCaSRhhrfazOeTzwx40DBXVo2PjV9viPz7rNRSA-1762362173-1.0.1.1-448VqZWH1ZRKLLTCSb9Qezp9.0dIiDqeZ0CVT8Q.dmfbePuniUR.EDrrXv4lBIWqNgZ_yF1ety_tftCaKIm0Uq4ydaAKdcHVUoOMUjhtvY4;
|
||||
_cfuvid=l9MNhSJeUsRd3C2.MkHdNv0aEoH19qe6RpeKp4Gf.do-1762362173542-0.0.1.1-604800000
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
@@ -687,23 +169,23 @@ interactions:
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.12.10
|
||||
- 3.12.9
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAAwAAAP//jFLBbqMwFLzzFdY7hypQklKuVS899NpK2wo59gPcGtuyH92uovz7ypAE
|
||||
0t1KvXB482Y8M7x9whgoCRUD0XESvdPp3dPzZ/gty77xO7fZPj4MMs/ldYf3Lt/CKjLs7g0FnVhX
|
||||
wvZOIylrJlh45IRRNbvZZuXN9fo2H4HeStSR1jpKi6ss7ZVRab7ON+m6SLPiSO+sEhigYr8Sxhjb
|
||||
j99o1Ej8hIqtV6dJjyHwFqE6LzEG3uo4AR6CCsQNwWoGhTWEZvS+f4EgrMcXqIrDcsdjMwQejZpB
|
||||
6wXAjbHEY9DR3esROZz9aNs6b3fhCxUaZVToao88WBPfDmQdjOghYex1zD1cRAHnbe+oJvuO43Pl
|
||||
ZpKDue4ZPGFkiet5fHus6lKslkhc6bCoDQQXHcqZOXfMB6nsAkgWkf/18j/tKbYy7U/kZ0AIdISy
|
||||
dh6lEpd55zWP8Ra/WztXPBqGgP5DCaxJoY+/QWLDBz0dCIQ/gbCvG2Va9M6r6UoaVxciLzdZU25z
|
||||
SA7JXwAAAP//AwAXjqY4NAMAAA==
|
||||
H4sIAAAAAAAAAwAAAP//jFKxbtswFNz1FcSbrcBSJDvR6g5FliJj2wQCTT5JbCmSIJ9aF4b/vaBk
|
||||
W3KSAl048N4d747vmDAGSkLFQHScRO90uvu6330+dNm37fr3ITx+eUYaxKen8rk77ApYRYbd/0BB
|
||||
F9adsL3TSMqaCRYeOWFUzbab/H6TZ9v7EeitRB1praO0uMvSXhmV5uu8TNdFmp3VRWeVwAAV+54w
|
||||
xthxPKNRI/EAFVuvLjc9hsBbhOo6xBh4q+MN8BBUIG4IVjMorCE0o/fjCwRhPb5AVZyWMx6bIfBo
|
||||
1AxaLwBujCUeg47uXs/I6epH29Z5uw9vqNAoo0JXe+TBmvh2IOtgRE8JY69j7uEmCjhve0c12Z84
|
||||
PpcX5aQHc98zesHIEtcLUnku61aulkhc6bAoDgQXHcqZOrfMB6nsAkgWod+b+Uh7Cq5M+z/yMyAE
|
||||
OkJZO49SidvA85jHuI3/GruWPBqGgP6XEliTQh8/QmLDBz2tCIQ/gbCvG2Va9M6raU8aVxcifyiz
|
||||
5mGTQ3JK/gIAAP//AwCqSfwtNgMAAA==
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 996f47692b63edda-MXP
|
||||
- 999debe15a932223-EWR
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
@@ -711,7 +193,7 @@ interactions:
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 31 Oct 2025 01:11:33 GMT
|
||||
- Wed, 05 Nov 2025 17:02:54 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Strict-Transport-Security:
|
||||
@@ -727,37 +209,31 @@ interactions:
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
- user-hortuttj2f3qtmxyik2zxf4q
|
||||
openai-processing-ms:
|
||||
- '929'
|
||||
- '283'
|
||||
openai-project:
|
||||
- proj_xitITlrFeen7zjNSzML82h9x
|
||||
- proj_fL4UBWR1CMpAAdgzaSKqsVvA
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
x-envoy-upstream-service-time:
|
||||
- '991'
|
||||
- '445'
|
||||
x-openai-proxy-wasm:
|
||||
- v0.1
|
||||
x-ratelimit-limit-project-tokens:
|
||||
- '150000000'
|
||||
x-ratelimit-limit-requests:
|
||||
- '30000'
|
||||
- '500'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '150000000'
|
||||
x-ratelimit-remaining-project-tokens:
|
||||
- '149999955'
|
||||
- '200000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '29999'
|
||||
- '499'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '149999955'
|
||||
x-ratelimit-reset-project-tokens:
|
||||
- 0s
|
||||
- '199779'
|
||||
x-ratelimit-reset-requests:
|
||||
- 2ms
|
||||
- 120ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 0s
|
||||
- 66ms
|
||||
x-request-id:
|
||||
- req_892607f68e764ba3846c431954608c36
|
||||
- req_85126ec40c9942809be5f2cf8a5927dc
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
|
||||
@@ -13,9 +13,7 @@ load_result = load_dotenv(override=True)
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_test_environment():
|
||||
"""Set up test environment with a temporary directory for SQLite storage."""
|
||||
import shutil
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as temp_dir:
|
||||
# Create the directory with proper permissions
|
||||
storage_dir = Path(temp_dir) / "crewai_test_storage"
|
||||
storage_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -42,9 +40,7 @@ def setup_test_environment():
|
||||
yield
|
||||
|
||||
os.environ.pop("CREWAI_TESTING", None)
|
||||
# Force cleanup of storage directory before tempfile context exits
|
||||
if storage_dir.exists():
|
||||
shutil.rmtree(storage_dir, ignore_errors=True)
|
||||
# TemporaryDirectory handles cleanup automatically with ignore_cleanup_errors=True
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
|
||||
@@ -62,18 +62,23 @@ class TestAgentEvaluator:
|
||||
agents=mock_crew.agents, evaluators=[GoalAlignmentEvaluator()]
|
||||
)
|
||||
|
||||
task_completed_event = threading.Event()
|
||||
task_completed_condition = threading.Condition()
|
||||
task_completed = False
|
||||
|
||||
@crewai_event_bus.on(TaskCompletedEvent)
|
||||
async def on_task_completed(source, event):
|
||||
# TaskCompletedEvent fires AFTER evaluation results are stored
|
||||
task_completed_event.set()
|
||||
nonlocal task_completed
|
||||
with task_completed_condition:
|
||||
task_completed = True
|
||||
task_completed_condition.notify()
|
||||
|
||||
mock_crew.kickoff()
|
||||
|
||||
assert task_completed_event.wait(timeout=5), (
|
||||
"Timeout waiting for task completion"
|
||||
)
|
||||
with task_completed_condition:
|
||||
assert task_completed_condition.wait_for(
|
||||
lambda: task_completed, timeout=5
|
||||
), "Timeout waiting for task completion"
|
||||
|
||||
results = agent_evaluator.get_evaluation_results()
|
||||
|
||||
|
||||
1
lib/crewai/tests/llms/hooks/__init__.py
Normal file
1
lib/crewai/tests/llms/hooks/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Tests for LLM interceptor hooks functionality."""
|
||||
311
lib/crewai/tests/llms/hooks/test_anthropic_interceptor.py
Normal file
311
lib/crewai/tests/llms/hooks/test_anthropic_interceptor.py
Normal file
@@ -0,0 +1,311 @@
|
||||
"""Tests for Anthropic provider with interceptor integration."""
|
||||
|
||||
import os
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_anthropic_api_key(monkeypatch):
|
||||
"""Set dummy Anthropic API key for tests that don't make real API calls."""
|
||||
if "ANTHROPIC_API_KEY" not in os.environ:
|
||||
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-test-key-dummy")
|
||||
|
||||
|
||||
class AnthropicTestInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Test interceptor for Anthropic provider."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize tracking and modification state."""
|
||||
self.outbound_calls: list[httpx.Request] = []
|
||||
self.inbound_calls: list[httpx.Response] = []
|
||||
self.custom_header_value = "anthropic-test-value"
|
||||
|
||||
def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Track and modify outbound Anthropic requests.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
Modified request with custom headers.
|
||||
"""
|
||||
self.outbound_calls.append(message)
|
||||
message.headers["X-Anthropic-Interceptor"] = self.custom_header_value
|
||||
message.headers["X-Request-ID"] = "test-request-456"
|
||||
return message
|
||||
|
||||
def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Track inbound Anthropic responses.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
The response with tracking header.
|
||||
"""
|
||||
self.inbound_calls.append(message)
|
||||
message.headers["X-Response-Tracked"] = "true"
|
||||
return message
|
||||
|
||||
|
||||
class TestAnthropicInterceptorIntegration:
|
||||
"""Test suite for Anthropic provider with interceptor."""
|
||||
|
||||
def test_anthropic_llm_accepts_interceptor(self) -> None:
|
||||
"""Test that Anthropic LLM accepts interceptor parameter."""
|
||||
interceptor = AnthropicTestInterceptor()
|
||||
|
||||
llm = LLM(model="anthropic/claude-3-5-sonnet-20241022", interceptor=interceptor)
|
||||
|
||||
assert llm.interceptor is interceptor
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization", "x-api-key"])
|
||||
def test_anthropic_call_with_interceptor_tracks_requests(self) -> None:
|
||||
"""Test that interceptor tracks Anthropic API requests."""
|
||||
interceptor = AnthropicTestInterceptor()
|
||||
llm = LLM(model="anthropic/claude-3-5-haiku-20241022", interceptor=interceptor)
|
||||
|
||||
# Make a simple completion call
|
||||
result = llm.call(
|
||||
messages=[{"role": "user", "content": "Say 'Hello World' and nothing else"}]
|
||||
)
|
||||
|
||||
# Verify custom headers were added
|
||||
for request in interceptor.outbound_calls:
|
||||
assert "X-Anthropic-Interceptor" in request.headers
|
||||
assert request.headers["X-Anthropic-Interceptor"] == "anthropic-test-value"
|
||||
assert "X-Request-ID" in request.headers
|
||||
assert request.headers["X-Request-ID"] == "test-request-456"
|
||||
|
||||
# Verify response was tracked
|
||||
for response in interceptor.inbound_calls:
|
||||
assert "X-Response-Tracked" in response.headers
|
||||
assert response.headers["X-Response-Tracked"] == "true"
|
||||
|
||||
# Verify result is valid
|
||||
assert result is not None
|
||||
assert isinstance(result, str)
|
||||
assert len(result) > 0
|
||||
|
||||
def test_anthropic_without_interceptor_works(self) -> None:
|
||||
"""Test that Anthropic LLM works without interceptor."""
|
||||
llm = LLM(model="anthropic/claude-3-5-sonnet-20241022")
|
||||
|
||||
assert llm.interceptor is None
|
||||
|
||||
def test_multiple_anthropic_llms_different_interceptors(self) -> None:
|
||||
"""Test that multiple Anthropic LLMs can have different interceptors."""
|
||||
interceptor1 = AnthropicTestInterceptor()
|
||||
interceptor1.custom_header_value = "claude-opus-value"
|
||||
|
||||
interceptor2 = AnthropicTestInterceptor()
|
||||
interceptor2.custom_header_value = "claude-sonnet-value"
|
||||
|
||||
llm1 = LLM(model="anthropic/claude-3-opus-20240229", interceptor=interceptor1)
|
||||
llm2 = LLM(model="anthropic/claude-3-5-sonnet-20241022", interceptor=interceptor2)
|
||||
|
||||
assert llm1.interceptor is interceptor1
|
||||
assert llm2.interceptor is interceptor2
|
||||
assert llm1.interceptor.custom_header_value == "claude-opus-value"
|
||||
assert llm2.interceptor.custom_header_value == "claude-sonnet-value"
|
||||
|
||||
|
||||
class AnthropicLoggingInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Interceptor that logs Anthropic request/response details."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize logging lists."""
|
||||
self.request_urls: list[str] = []
|
||||
self.request_methods: list[str] = []
|
||||
self.response_status_codes: list[int] = []
|
||||
self.anthropic_version_headers: list[str] = []
|
||||
|
||||
def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Log outbound request details.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
The request unchanged.
|
||||
"""
|
||||
self.request_urls.append(str(message.url))
|
||||
self.request_methods.append(message.method)
|
||||
if "anthropic-version" in message.headers:
|
||||
self.anthropic_version_headers.append(message.headers["anthropic-version"])
|
||||
return message
|
||||
|
||||
def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Log inbound response details.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
The response unchanged.
|
||||
"""
|
||||
self.response_status_codes.append(message.status_code)
|
||||
return message
|
||||
|
||||
|
||||
class TestAnthropicLoggingInterceptor:
|
||||
"""Test suite for logging interceptor with Anthropic."""
|
||||
|
||||
def test_logging_interceptor_instantiation(self) -> None:
|
||||
"""Test that logging interceptor can be created with Anthropic LLM."""
|
||||
interceptor = AnthropicLoggingInterceptor()
|
||||
llm = LLM(model="anthropic/claude-3-5-sonnet-20241022", interceptor=interceptor)
|
||||
|
||||
assert llm.interceptor is interceptor
|
||||
assert isinstance(llm.interceptor, AnthropicLoggingInterceptor)
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization", "x-api-key"])
|
||||
def test_logging_interceptor_tracks_details(self) -> None:
|
||||
"""Test that logging interceptor tracks request/response details."""
|
||||
interceptor = AnthropicLoggingInterceptor()
|
||||
llm = LLM(model="anthropic/claude-3-5-haiku-20241022", interceptor=interceptor)
|
||||
|
||||
# Make a completion call
|
||||
result = llm.call(messages=[{"role": "user", "content": "Count from 1 to 3"}])
|
||||
|
||||
# Verify URL points to Anthropic API
|
||||
for url in interceptor.request_urls:
|
||||
assert "anthropic" in url.lower() or "api" in url.lower()
|
||||
|
||||
# Verify methods are POST (messages endpoint uses POST)
|
||||
for method in interceptor.request_methods:
|
||||
assert method == "POST"
|
||||
|
||||
# Verify successful status codes
|
||||
for status_code in interceptor.response_status_codes:
|
||||
assert 200 <= status_code < 300
|
||||
|
||||
|
||||
# Verify result is valid
|
||||
assert result is not None
|
||||
|
||||
|
||||
class AnthropicHeaderInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Interceptor that adds Anthropic-specific headers."""
|
||||
|
||||
def __init__(self, workspace_id: str, user_id: str) -> None:
|
||||
"""Initialize with Anthropic-specific metadata.
|
||||
|
||||
Args:
|
||||
workspace_id: The workspace ID to inject.
|
||||
user_id: The user ID to inject.
|
||||
"""
|
||||
self.workspace_id = workspace_id
|
||||
self.user_id = user_id
|
||||
|
||||
def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Add custom metadata headers to request.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
Request with metadata headers.
|
||||
"""
|
||||
message.headers["X-Workspace-ID"] = self.workspace_id
|
||||
message.headers["X-User-ID"] = self.user_id
|
||||
message.headers["X-Custom-Client"] = "crewai-interceptor"
|
||||
return message
|
||||
|
||||
def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Pass through inbound response.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
The response unchanged.
|
||||
"""
|
||||
return message
|
||||
|
||||
|
||||
class TestAnthropicHeaderInterceptor:
|
||||
"""Test suite for header interceptor with Anthropic."""
|
||||
|
||||
def test_header_interceptor_with_anthropic(self) -> None:
|
||||
"""Test that header interceptor can be used with Anthropic LLM."""
|
||||
interceptor = AnthropicHeaderInterceptor(
|
||||
workspace_id="ws-789", user_id="user-012"
|
||||
)
|
||||
llm = LLM(model="anthropic/claude-3-5-sonnet-20241022", interceptor=interceptor)
|
||||
|
||||
assert llm.interceptor is interceptor
|
||||
assert llm.interceptor.workspace_id == "ws-789"
|
||||
assert llm.interceptor.user_id == "user-012"
|
||||
|
||||
def test_header_interceptor_adds_headers(self) -> None:
|
||||
"""Test that header interceptor adds custom headers to requests."""
|
||||
interceptor = AnthropicHeaderInterceptor(workspace_id="ws-123", user_id="u-456")
|
||||
request = httpx.Request("POST", "https://api.anthropic.com/v1/messages")
|
||||
|
||||
modified_request = interceptor.on_outbound(request)
|
||||
|
||||
assert "X-Workspace-ID" in modified_request.headers
|
||||
assert modified_request.headers["X-Workspace-ID"] == "ws-123"
|
||||
assert "X-User-ID" in modified_request.headers
|
||||
assert modified_request.headers["X-User-ID"] == "u-456"
|
||||
assert "X-Custom-Client" in modified_request.headers
|
||||
assert modified_request.headers["X-Custom-Client"] == "crewai-interceptor"
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization", "x-api-key"])
|
||||
def test_header_interceptor_with_real_call(self) -> None:
|
||||
"""Test that header interceptor works with real Anthropic API call."""
|
||||
interceptor = AnthropicHeaderInterceptor(workspace_id="ws-999", user_id="u-888")
|
||||
llm = LLM(model="anthropic/claude-3-5-haiku-20241022", interceptor=interceptor)
|
||||
|
||||
# Make a simple call
|
||||
result = llm.call(
|
||||
messages=[{"role": "user", "content": "Reply with just the word: SUCCESS"}]
|
||||
)
|
||||
|
||||
# Verify the call succeeded
|
||||
assert result is not None
|
||||
assert len(result) > 0
|
||||
|
||||
# Verify the interceptor was configured
|
||||
assert llm.interceptor is interceptor
|
||||
|
||||
|
||||
class TestMixedProviderInterceptors:
|
||||
"""Test suite for using interceptors with different providers."""
|
||||
|
||||
def test_openai_and_anthropic_different_interceptors(self) -> None:
|
||||
"""Test that OpenAI and Anthropic LLMs can have different interceptors."""
|
||||
openai_interceptor = AnthropicTestInterceptor()
|
||||
openai_interceptor.custom_header_value = "openai-specific"
|
||||
|
||||
anthropic_interceptor = AnthropicTestInterceptor()
|
||||
anthropic_interceptor.custom_header_value = "anthropic-specific"
|
||||
|
||||
openai_llm = LLM(model="gpt-4", interceptor=openai_interceptor)
|
||||
anthropic_llm = LLM(
|
||||
model="anthropic/claude-3-5-sonnet-20241022", interceptor=anthropic_interceptor
|
||||
)
|
||||
|
||||
assert openai_llm.interceptor is openai_interceptor
|
||||
assert anthropic_llm.interceptor is anthropic_interceptor
|
||||
assert openai_llm.interceptor.custom_header_value == "openai-specific"
|
||||
assert anthropic_llm.interceptor.custom_header_value == "anthropic-specific"
|
||||
|
||||
def test_same_interceptor_different_providers(self) -> None:
|
||||
"""Test that same interceptor instance can be used with multiple providers."""
|
||||
shared_interceptor = AnthropicTestInterceptor()
|
||||
|
||||
openai_llm = LLM(model="gpt-4", interceptor=shared_interceptor)
|
||||
anthropic_llm = LLM(
|
||||
model="anthropic/claude-3-5-sonnet-20241022", interceptor=shared_interceptor
|
||||
)
|
||||
|
||||
assert openai_llm.interceptor is shared_interceptor
|
||||
assert anthropic_llm.interceptor is shared_interceptor
|
||||
assert openai_llm.interceptor is anthropic_llm.interceptor
|
||||
287
lib/crewai/tests/llms/hooks/test_base_interceptor.py
Normal file
287
lib/crewai/tests/llms/hooks/test_base_interceptor.py
Normal file
@@ -0,0 +1,287 @@
|
||||
"""Tests for base interceptor functionality."""
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
class SimpleInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Simple test interceptor implementation."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize tracking lists."""
|
||||
self.outbound_calls: list[httpx.Request] = []
|
||||
self.inbound_calls: list[httpx.Response] = []
|
||||
|
||||
def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Track outbound calls.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
The request unchanged.
|
||||
"""
|
||||
self.outbound_calls.append(message)
|
||||
return message
|
||||
|
||||
def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Track inbound calls.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
The response unchanged.
|
||||
"""
|
||||
self.inbound_calls.append(message)
|
||||
return message
|
||||
|
||||
|
||||
class ModifyingInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Interceptor that modifies requests and responses."""
|
||||
|
||||
def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Add custom header to outbound request.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
Modified request with custom header.
|
||||
"""
|
||||
message.headers["X-Custom-Header"] = "test-value"
|
||||
message.headers["X-Intercepted"] = "true"
|
||||
return message
|
||||
|
||||
def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Add custom header to inbound response.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
Modified response with custom header.
|
||||
"""
|
||||
message.headers["X-Response-Intercepted"] = "true"
|
||||
return message
|
||||
|
||||
|
||||
class AsyncInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Interceptor with async support."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize tracking lists."""
|
||||
self.async_outbound_calls: list[httpx.Request] = []
|
||||
self.async_inbound_calls: list[httpx.Response] = []
|
||||
|
||||
def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Handle sync outbound.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
The request unchanged.
|
||||
"""
|
||||
return message
|
||||
|
||||
def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Handle sync inbound.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
The response unchanged.
|
||||
"""
|
||||
return message
|
||||
|
||||
async def aon_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Handle async outbound.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
Modified request with async header.
|
||||
"""
|
||||
self.async_outbound_calls.append(message)
|
||||
message.headers["X-Async-Outbound"] = "true"
|
||||
return message
|
||||
|
||||
async def aon_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Handle async inbound.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
Modified response with async header.
|
||||
"""
|
||||
self.async_inbound_calls.append(message)
|
||||
message.headers["X-Async-Inbound"] = "true"
|
||||
return message
|
||||
|
||||
|
||||
class TestBaseInterceptor:
|
||||
"""Test suite for BaseInterceptor class."""
|
||||
|
||||
def test_interceptor_instantiation(self) -> None:
|
||||
"""Test that interceptor can be instantiated."""
|
||||
interceptor = SimpleInterceptor()
|
||||
assert interceptor is not None
|
||||
assert isinstance(interceptor, BaseInterceptor)
|
||||
|
||||
def test_on_outbound_called(self) -> None:
|
||||
"""Test that on_outbound is called and tracks requests."""
|
||||
interceptor = SimpleInterceptor()
|
||||
request = httpx.Request("GET", "https://api.example.com/test")
|
||||
|
||||
result = interceptor.on_outbound(request)
|
||||
|
||||
assert len(interceptor.outbound_calls) == 1
|
||||
assert interceptor.outbound_calls[0] is request
|
||||
assert result is request
|
||||
|
||||
def test_on_inbound_called(self) -> None:
|
||||
"""Test that on_inbound is called and tracks responses."""
|
||||
interceptor = SimpleInterceptor()
|
||||
response = httpx.Response(200, json={"status": "ok"})
|
||||
|
||||
result = interceptor.on_inbound(response)
|
||||
|
||||
assert len(interceptor.inbound_calls) == 1
|
||||
assert interceptor.inbound_calls[0] is response
|
||||
assert result is response
|
||||
|
||||
def test_multiple_outbound_calls(self) -> None:
|
||||
"""Test that interceptor tracks multiple outbound calls."""
|
||||
interceptor = SimpleInterceptor()
|
||||
requests = [
|
||||
httpx.Request("GET", "https://api.example.com/1"),
|
||||
httpx.Request("POST", "https://api.example.com/2"),
|
||||
httpx.Request("PUT", "https://api.example.com/3"),
|
||||
]
|
||||
|
||||
for req in requests:
|
||||
interceptor.on_outbound(req)
|
||||
|
||||
assert len(interceptor.outbound_calls) == 3
|
||||
assert interceptor.outbound_calls == requests
|
||||
|
||||
def test_multiple_inbound_calls(self) -> None:
|
||||
"""Test that interceptor tracks multiple inbound calls."""
|
||||
interceptor = SimpleInterceptor()
|
||||
responses = [
|
||||
httpx.Response(200, json={"id": 1}),
|
||||
httpx.Response(201, json={"id": 2}),
|
||||
httpx.Response(404, json={"error": "not found"}),
|
||||
]
|
||||
|
||||
for resp in responses:
|
||||
interceptor.on_inbound(resp)
|
||||
|
||||
assert len(interceptor.inbound_calls) == 3
|
||||
assert interceptor.inbound_calls == responses
|
||||
|
||||
|
||||
class TestModifyingInterceptor:
|
||||
"""Test suite for interceptor that modifies messages."""
|
||||
|
||||
def test_outbound_header_modification(self) -> None:
|
||||
"""Test that interceptor can add headers to outbound requests."""
|
||||
interceptor = ModifyingInterceptor()
|
||||
request = httpx.Request("GET", "https://api.example.com/test")
|
||||
|
||||
result = interceptor.on_outbound(request)
|
||||
|
||||
assert result is request
|
||||
assert "X-Custom-Header" in result.headers
|
||||
assert result.headers["X-Custom-Header"] == "test-value"
|
||||
assert "X-Intercepted" in result.headers
|
||||
assert result.headers["X-Intercepted"] == "true"
|
||||
|
||||
def test_inbound_header_modification(self) -> None:
|
||||
"""Test that interceptor can add headers to inbound responses."""
|
||||
interceptor = ModifyingInterceptor()
|
||||
response = httpx.Response(200, json={"status": "ok"})
|
||||
|
||||
result = interceptor.on_inbound(response)
|
||||
|
||||
assert result is response
|
||||
assert "X-Response-Intercepted" in result.headers
|
||||
assert result.headers["X-Response-Intercepted"] == "true"
|
||||
|
||||
def test_preserves_existing_headers(self) -> None:
|
||||
"""Test that interceptor preserves existing headers."""
|
||||
interceptor = ModifyingInterceptor()
|
||||
request = httpx.Request(
|
||||
"GET",
|
||||
"https://api.example.com/test",
|
||||
headers={"Authorization": "Bearer token123", "Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
result = interceptor.on_outbound(request)
|
||||
|
||||
assert result.headers["Authorization"] == "Bearer token123"
|
||||
assert result.headers["Content-Type"] == "application/json"
|
||||
assert result.headers["X-Custom-Header"] == "test-value"
|
||||
|
||||
|
||||
class TestAsyncInterceptor:
|
||||
"""Test suite for async interceptor functionality."""
|
||||
|
||||
def test_sync_methods_work(self) -> None:
|
||||
"""Test that sync methods still work on async interceptor."""
|
||||
interceptor = AsyncInterceptor()
|
||||
request = httpx.Request("GET", "https://api.example.com/test")
|
||||
response = httpx.Response(200)
|
||||
|
||||
req_result = interceptor.on_outbound(request)
|
||||
resp_result = interceptor.on_inbound(response)
|
||||
|
||||
assert req_result is request
|
||||
assert resp_result is response
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_outbound(self) -> None:
|
||||
"""Test async outbound hook."""
|
||||
interceptor = AsyncInterceptor()
|
||||
request = httpx.Request("GET", "https://api.example.com/test")
|
||||
|
||||
result = await interceptor.aon_outbound(request)
|
||||
|
||||
assert result is request
|
||||
assert len(interceptor.async_outbound_calls) == 1
|
||||
assert interceptor.async_outbound_calls[0] is request
|
||||
assert "X-Async-Outbound" in result.headers
|
||||
assert result.headers["X-Async-Outbound"] == "true"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_inbound(self) -> None:
|
||||
"""Test async inbound hook."""
|
||||
interceptor = AsyncInterceptor()
|
||||
response = httpx.Response(200, json={"status": "ok"})
|
||||
|
||||
result = await interceptor.aon_inbound(response)
|
||||
|
||||
assert result is response
|
||||
assert len(interceptor.async_inbound_calls) == 1
|
||||
assert interceptor.async_inbound_calls[0] is response
|
||||
assert "X-Async-Inbound" in result.headers
|
||||
assert result.headers["X-Async-Inbound"] == "true"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_default_async_not_implemented(self) -> None:
|
||||
"""Test that default async methods raise NotImplementedError."""
|
||||
interceptor = SimpleInterceptor()
|
||||
request = httpx.Request("GET", "https://api.example.com/test")
|
||||
response = httpx.Response(200)
|
||||
|
||||
with pytest.raises(NotImplementedError):
|
||||
await interceptor.aon_outbound(request)
|
||||
|
||||
with pytest.raises(NotImplementedError):
|
||||
await interceptor.aon_inbound(response)
|
||||
262
lib/crewai/tests/llms/hooks/test_openai_interceptor.py
Normal file
262
lib/crewai/tests/llms/hooks/test_openai_interceptor.py
Normal file
@@ -0,0 +1,262 @@
|
||||
"""Tests for OpenAI provider with interceptor integration."""
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
class OpenAITestInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Test interceptor for OpenAI provider."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize tracking and modification state."""
|
||||
self.outbound_calls: list[httpx.Request] = []
|
||||
self.inbound_calls: list[httpx.Response] = []
|
||||
self.custom_header_value = "openai-test-value"
|
||||
|
||||
def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Track and modify outbound OpenAI requests.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
Modified request with custom headers.
|
||||
"""
|
||||
self.outbound_calls.append(message)
|
||||
message.headers["X-OpenAI-Interceptor"] = self.custom_header_value
|
||||
message.headers["X-Request-ID"] = "test-request-123"
|
||||
return message
|
||||
|
||||
def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Track inbound OpenAI responses.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
The response with tracking header.
|
||||
"""
|
||||
self.inbound_calls.append(message)
|
||||
message.headers["X-Response-Tracked"] = "true"
|
||||
return message
|
||||
|
||||
|
||||
class TestOpenAIInterceptorIntegration:
|
||||
"""Test suite for OpenAI provider with interceptor."""
|
||||
|
||||
def test_openai_llm_accepts_interceptor(self) -> None:
|
||||
"""Test that OpenAI LLM accepts interceptor parameter."""
|
||||
interceptor = OpenAITestInterceptor()
|
||||
|
||||
llm = LLM(model="gpt-4", interceptor=interceptor)
|
||||
|
||||
assert llm.interceptor is interceptor
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_openai_call_with_interceptor_tracks_requests(self) -> None:
|
||||
"""Test that interceptor tracks OpenAI API requests."""
|
||||
interceptor = OpenAITestInterceptor()
|
||||
llm = LLM(model="gpt-4o-mini", interceptor=interceptor)
|
||||
|
||||
# Make a simple completion call
|
||||
result = llm.call(
|
||||
messages=[{"role": "user", "content": "Say 'Hello World' and nothing else"}]
|
||||
)
|
||||
|
||||
# Verify custom headers were added
|
||||
for request in interceptor.outbound_calls:
|
||||
assert "X-OpenAI-Interceptor" in request.headers
|
||||
assert request.headers["X-OpenAI-Interceptor"] == "openai-test-value"
|
||||
assert "X-Request-ID" in request.headers
|
||||
assert request.headers["X-Request-ID"] == "test-request-123"
|
||||
|
||||
# Verify response was tracked
|
||||
for response in interceptor.inbound_calls:
|
||||
assert "X-Response-Tracked" in response.headers
|
||||
assert response.headers["X-Response-Tracked"] == "true"
|
||||
|
||||
# Verify result is valid
|
||||
assert result is not None
|
||||
assert isinstance(result, str)
|
||||
assert len(result) > 0
|
||||
|
||||
def test_openai_without_interceptor_works(self) -> None:
|
||||
"""Test that OpenAI LLM works without interceptor."""
|
||||
llm = LLM(model="gpt-4")
|
||||
|
||||
assert llm.interceptor is None
|
||||
|
||||
def test_multiple_openai_llms_different_interceptors(self) -> None:
|
||||
"""Test that multiple OpenAI LLMs can have different interceptors."""
|
||||
interceptor1 = OpenAITestInterceptor()
|
||||
interceptor1.custom_header_value = "llm1-value"
|
||||
|
||||
interceptor2 = OpenAITestInterceptor()
|
||||
interceptor2.custom_header_value = "llm2-value"
|
||||
|
||||
llm1 = LLM(model="gpt-4", interceptor=interceptor1)
|
||||
llm2 = LLM(model="gpt-3.5-turbo", interceptor=interceptor2)
|
||||
|
||||
assert llm1.interceptor is interceptor1
|
||||
assert llm2.interceptor is interceptor2
|
||||
assert llm1.interceptor.custom_header_value == "llm1-value"
|
||||
assert llm2.interceptor.custom_header_value == "llm2-value"
|
||||
|
||||
|
||||
class LoggingInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Interceptor that logs request/response details for testing."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize logging lists."""
|
||||
self.request_urls: list[str] = []
|
||||
self.request_methods: list[str] = []
|
||||
self.response_status_codes: list[int] = []
|
||||
|
||||
def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Log outbound request details.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
The request unchanged.
|
||||
"""
|
||||
self.request_urls.append(str(message.url))
|
||||
self.request_methods.append(message.method)
|
||||
return message
|
||||
|
||||
def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Log inbound response details.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
The response unchanged.
|
||||
"""
|
||||
self.response_status_codes.append(message.status_code)
|
||||
return message
|
||||
|
||||
|
||||
class TestOpenAILoggingInterceptor:
|
||||
"""Test suite for logging interceptor with OpenAI."""
|
||||
|
||||
def test_logging_interceptor_instantiation(self) -> None:
|
||||
"""Test that logging interceptor can be created with OpenAI LLM."""
|
||||
interceptor = LoggingInterceptor()
|
||||
llm = LLM(model="gpt-4", interceptor=interceptor)
|
||||
|
||||
assert llm.interceptor is interceptor
|
||||
assert isinstance(llm.interceptor, LoggingInterceptor)
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_logging_interceptor_tracks_details(self) -> None:
|
||||
"""Test that logging interceptor tracks request/response details."""
|
||||
interceptor = LoggingInterceptor()
|
||||
llm = LLM(model="gpt-4o-mini", interceptor=interceptor)
|
||||
|
||||
# Make a completion call
|
||||
result = llm.call(
|
||||
messages=[{"role": "user", "content": "Count from 1 to 3"}]
|
||||
)
|
||||
|
||||
# Verify URL points to OpenAI API
|
||||
for url in interceptor.request_urls:
|
||||
assert "openai" in url.lower() or "api" in url.lower()
|
||||
|
||||
# Verify methods are POST (chat completions use POST)
|
||||
for method in interceptor.request_methods:
|
||||
assert method == "POST"
|
||||
|
||||
# Verify successful status codes
|
||||
for status_code in interceptor.response_status_codes:
|
||||
assert 200 <= status_code < 300
|
||||
|
||||
# Verify result is valid
|
||||
assert result is not None
|
||||
|
||||
|
||||
class AuthInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Interceptor that adds authentication headers."""
|
||||
|
||||
def __init__(self, api_key: str, org_id: str) -> None:
|
||||
"""Initialize with auth credentials.
|
||||
|
||||
Args:
|
||||
api_key: The API key to inject.
|
||||
org_id: The organization ID to inject.
|
||||
"""
|
||||
self.api_key = api_key
|
||||
self.org_id = org_id
|
||||
|
||||
def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Add authentication headers to request.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
Request with auth headers.
|
||||
"""
|
||||
message.headers["X-Custom-API-Key"] = self.api_key
|
||||
message.headers["X-Organization-ID"] = self.org_id
|
||||
return message
|
||||
|
||||
def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Pass through inbound response.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
The response unchanged.
|
||||
"""
|
||||
return message
|
||||
|
||||
|
||||
class TestOpenAIAuthInterceptor:
|
||||
"""Test suite for authentication interceptor with OpenAI."""
|
||||
|
||||
def test_auth_interceptor_with_openai(self) -> None:
|
||||
"""Test that auth interceptor can be used with OpenAI LLM."""
|
||||
interceptor = AuthInterceptor(api_key="custom-key-123", org_id="org-456")
|
||||
llm = LLM(model="gpt-4", interceptor=interceptor)
|
||||
|
||||
assert llm.interceptor is interceptor
|
||||
assert llm.interceptor.api_key == "custom-key-123"
|
||||
assert llm.interceptor.org_id == "org-456"
|
||||
|
||||
def test_auth_interceptor_adds_headers(self) -> None:
|
||||
"""Test that auth interceptor adds custom headers to requests."""
|
||||
interceptor = AuthInterceptor(api_key="test-key", org_id="test-org")
|
||||
request = httpx.Request("POST", "https://api.openai.com/v1/chat/completions")
|
||||
|
||||
modified_request = interceptor.on_outbound(request)
|
||||
|
||||
assert "X-Custom-API-Key" in modified_request.headers
|
||||
assert modified_request.headers["X-Custom-API-Key"] == "test-key"
|
||||
assert "X-Organization-ID" in modified_request.headers
|
||||
assert modified_request.headers["X-Organization-ID"] == "test-org"
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_auth_interceptor_with_real_call(self) -> None:
|
||||
"""Test that auth interceptor works with real OpenAI API call."""
|
||||
interceptor = AuthInterceptor(api_key="custom-123", org_id="org-789")
|
||||
llm = LLM(model="gpt-4o-mini", interceptor=interceptor)
|
||||
|
||||
# Make a simple call
|
||||
result = llm.call(
|
||||
messages=[{"role": "user", "content": "Reply with just the word: SUCCESS"}]
|
||||
)
|
||||
|
||||
# Verify the call succeeded
|
||||
assert result is not None
|
||||
assert len(result) > 0
|
||||
|
||||
# Verify headers were added to outbound requests
|
||||
# (We can't directly inspect the request sent to OpenAI in this test,
|
||||
# but we verify the interceptor was configured and the call succeeded)
|
||||
assert llm.interceptor is interceptor
|
||||
248
lib/crewai/tests/llms/hooks/test_transport.py
Normal file
248
lib/crewai/tests/llms/hooks/test_transport.py
Normal file
@@ -0,0 +1,248 @@
|
||||
"""Tests for transport layer with interceptor integration."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
from crewai.llms.hooks.transport import AsyncHTTPransport, HTTPTransport
|
||||
|
||||
|
||||
class TrackingInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Test interceptor that tracks all calls."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize tracking lists."""
|
||||
self.outbound_calls: list[httpx.Request] = []
|
||||
self.inbound_calls: list[httpx.Response] = []
|
||||
self.async_outbound_calls: list[httpx.Request] = []
|
||||
self.async_inbound_calls: list[httpx.Response] = []
|
||||
|
||||
def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Track outbound calls and add header.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
Modified request with tracking header.
|
||||
"""
|
||||
self.outbound_calls.append(message)
|
||||
message.headers["X-Intercepted-Sync"] = "true"
|
||||
return message
|
||||
|
||||
def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Track inbound calls.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
The response with tracking header.
|
||||
"""
|
||||
self.inbound_calls.append(message)
|
||||
message.headers["X-Response-Intercepted-Sync"] = "true"
|
||||
return message
|
||||
|
||||
async def aon_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Track async outbound calls and add header.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
Modified request with tracking header.
|
||||
"""
|
||||
self.async_outbound_calls.append(message)
|
||||
message.headers["X-Intercepted-Async"] = "true"
|
||||
return message
|
||||
|
||||
async def aon_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Track async inbound calls.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
The response with tracking header.
|
||||
"""
|
||||
self.async_inbound_calls.append(message)
|
||||
message.headers["X-Response-Intercepted-Async"] = "true"
|
||||
return message
|
||||
|
||||
|
||||
class TestHTTPTransport:
|
||||
"""Test suite for sync HTTPTransport with interceptor."""
|
||||
|
||||
def test_transport_instantiation(self) -> None:
|
||||
"""Test that transport can be instantiated with interceptor."""
|
||||
interceptor = TrackingInterceptor()
|
||||
transport = HTTPTransport(interceptor=interceptor)
|
||||
|
||||
assert transport.interceptor is interceptor
|
||||
|
||||
def test_transport_requires_interceptor(self) -> None:
|
||||
"""Test that transport requires interceptor parameter."""
|
||||
# HTTPTransport requires an interceptor parameter
|
||||
with pytest.raises(TypeError):
|
||||
HTTPTransport()
|
||||
|
||||
def test_interceptor_called_on_request(self) -> None:
|
||||
"""Test that interceptor hooks are called during request handling."""
|
||||
interceptor = TrackingInterceptor()
|
||||
transport = HTTPTransport(interceptor=interceptor)
|
||||
|
||||
# Create a mock parent transport that returns a response
|
||||
mock_response = httpx.Response(200, json={"success": True})
|
||||
mock_parent_handle = Mock(return_value=mock_response)
|
||||
|
||||
# Monkey-patch the parent's handle_request
|
||||
original_handle = httpx.HTTPTransport.handle_request
|
||||
httpx.HTTPTransport.handle_request = mock_parent_handle
|
||||
|
||||
try:
|
||||
request = httpx.Request("GET", "https://api.example.com/test")
|
||||
response = transport.handle_request(request)
|
||||
|
||||
# Verify interceptor was called
|
||||
assert len(interceptor.outbound_calls) == 1
|
||||
assert len(interceptor.inbound_calls) == 1
|
||||
assert interceptor.outbound_calls[0] is request
|
||||
assert interceptor.inbound_calls[0] is response
|
||||
|
||||
# Verify headers were added
|
||||
assert "X-Intercepted-Sync" in request.headers
|
||||
assert request.headers["X-Intercepted-Sync"] == "true"
|
||||
assert "X-Response-Intercepted-Sync" in response.headers
|
||||
assert response.headers["X-Response-Intercepted-Sync"] == "true"
|
||||
finally:
|
||||
# Restore original method
|
||||
httpx.HTTPTransport.handle_request = original_handle
|
||||
|
||||
|
||||
|
||||
class TestAsyncHTTPTransport:
|
||||
"""Test suite for async AsyncHTTPransport with interceptor."""
|
||||
|
||||
def test_async_transport_instantiation(self) -> None:
|
||||
"""Test that async transport can be instantiated with interceptor."""
|
||||
interceptor = TrackingInterceptor()
|
||||
transport = AsyncHTTPransport(interceptor=interceptor)
|
||||
|
||||
assert transport.interceptor is interceptor
|
||||
|
||||
def test_async_transport_requires_interceptor(self) -> None:
|
||||
"""Test that async transport requires interceptor parameter."""
|
||||
# AsyncHTTPransport requires an interceptor parameter
|
||||
with pytest.raises(TypeError):
|
||||
AsyncHTTPransport()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_interceptor_called_on_request(self) -> None:
|
||||
"""Test that async interceptor hooks are called during request handling."""
|
||||
interceptor = TrackingInterceptor()
|
||||
transport = AsyncHTTPransport(interceptor=interceptor)
|
||||
|
||||
# Create a mock parent transport that returns a response
|
||||
mock_response = httpx.Response(200, json={"success": True})
|
||||
|
||||
async def mock_handle(*args, **kwargs):
|
||||
return mock_response
|
||||
|
||||
mock_parent_handle = Mock(side_effect=mock_handle)
|
||||
|
||||
# Monkey-patch the parent's handle_async_request
|
||||
original_handle = httpx.AsyncHTTPTransport.handle_async_request
|
||||
httpx.AsyncHTTPTransport.handle_async_request = mock_parent_handle
|
||||
|
||||
try:
|
||||
request = httpx.Request("GET", "https://api.example.com/test")
|
||||
response = await transport.handle_async_request(request)
|
||||
|
||||
# Verify async interceptor was called
|
||||
assert len(interceptor.async_outbound_calls) == 1
|
||||
assert len(interceptor.async_inbound_calls) == 1
|
||||
assert interceptor.async_outbound_calls[0] is request
|
||||
assert interceptor.async_inbound_calls[0] is response
|
||||
|
||||
# Verify sync interceptor was NOT called
|
||||
assert len(interceptor.outbound_calls) == 0
|
||||
assert len(interceptor.inbound_calls) == 0
|
||||
|
||||
# Verify async headers were added
|
||||
assert "X-Intercepted-Async" in request.headers
|
||||
assert request.headers["X-Intercepted-Async"] == "true"
|
||||
assert "X-Response-Intercepted-Async" in response.headers
|
||||
assert response.headers["X-Response-Intercepted-Async"] == "true"
|
||||
finally:
|
||||
# Restore original method
|
||||
httpx.AsyncHTTPTransport.handle_async_request = original_handle
|
||||
|
||||
|
||||
|
||||
class TestTransportIntegration:
|
||||
"""Test suite for transport integration scenarios."""
|
||||
|
||||
def test_multiple_requests_same_interceptor(self) -> None:
|
||||
"""Test that multiple requests through same interceptor are tracked."""
|
||||
interceptor = TrackingInterceptor()
|
||||
transport = HTTPTransport(interceptor=interceptor)
|
||||
|
||||
mock_response = httpx.Response(200)
|
||||
mock_parent_handle = Mock(return_value=mock_response)
|
||||
|
||||
original_handle = httpx.HTTPTransport.handle_request
|
||||
httpx.HTTPTransport.handle_request = mock_parent_handle
|
||||
|
||||
try:
|
||||
# Make multiple requests
|
||||
requests = [
|
||||
httpx.Request("GET", "https://api.example.com/1"),
|
||||
httpx.Request("POST", "https://api.example.com/2"),
|
||||
httpx.Request("PUT", "https://api.example.com/3"),
|
||||
]
|
||||
|
||||
for req in requests:
|
||||
transport.handle_request(req)
|
||||
|
||||
# Verify all requests were intercepted
|
||||
assert len(interceptor.outbound_calls) == 3
|
||||
assert len(interceptor.inbound_calls) == 3
|
||||
assert interceptor.outbound_calls == requests
|
||||
finally:
|
||||
httpx.HTTPTransport.handle_request = original_handle
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_async_requests_same_interceptor(self) -> None:
|
||||
"""Test that multiple async requests through same interceptor are tracked."""
|
||||
interceptor = TrackingInterceptor()
|
||||
transport = AsyncHTTPransport(interceptor=interceptor)
|
||||
|
||||
mock_response = httpx.Response(200)
|
||||
|
||||
async def mock_handle(*args, **kwargs):
|
||||
return mock_response
|
||||
|
||||
mock_parent_handle = Mock(side_effect=mock_handle)
|
||||
|
||||
original_handle = httpx.AsyncHTTPTransport.handle_async_request
|
||||
httpx.AsyncHTTPTransport.handle_async_request = mock_parent_handle
|
||||
|
||||
try:
|
||||
# Make multiple async requests
|
||||
requests = [
|
||||
httpx.Request("GET", "https://api.example.com/1"),
|
||||
httpx.Request("POST", "https://api.example.com/2"),
|
||||
httpx.Request("DELETE", "https://api.example.com/3"),
|
||||
]
|
||||
|
||||
for req in requests:
|
||||
await transport.handle_async_request(req)
|
||||
|
||||
# Verify all requests were intercepted
|
||||
assert len(interceptor.async_outbound_calls) == 3
|
||||
assert len(interceptor.async_inbound_calls) == 3
|
||||
assert interceptor.async_outbound_calls == requests
|
||||
finally:
|
||||
httpx.AsyncHTTPTransport.handle_async_request = original_handle
|
||||
319
lib/crewai/tests/llms/hooks/test_unsupported_providers.py
Normal file
319
lib/crewai/tests/llms/hooks/test_unsupported_providers.py
Normal file
@@ -0,0 +1,319 @@
|
||||
"""Tests for interceptor behavior with unsupported providers."""
|
||||
|
||||
import os
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.hooks.base import BaseInterceptor
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_provider_api_keys(monkeypatch):
|
||||
"""Set dummy API keys for providers that require them."""
|
||||
if "OPENAI_API_KEY" not in os.environ:
|
||||
monkeypatch.setenv("OPENAI_API_KEY", "sk-test-key-dummy")
|
||||
if "ANTHROPIC_API_KEY" not in os.environ:
|
||||
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-test-key-dummy")
|
||||
if "GOOGLE_API_KEY" not in os.environ:
|
||||
monkeypatch.setenv("GOOGLE_API_KEY", "test-google-key-dummy")
|
||||
|
||||
|
||||
class DummyInterceptor(BaseInterceptor[httpx.Request, httpx.Response]):
|
||||
"""Simple dummy interceptor for testing."""
|
||||
|
||||
def on_outbound(self, message: httpx.Request) -> httpx.Request:
|
||||
"""Pass through outbound request.
|
||||
|
||||
Args:
|
||||
message: The outbound request.
|
||||
|
||||
Returns:
|
||||
The request unchanged.
|
||||
"""
|
||||
message.headers["X-Dummy"] = "true"
|
||||
return message
|
||||
|
||||
def on_inbound(self, message: httpx.Response) -> httpx.Response:
|
||||
"""Pass through inbound response.
|
||||
|
||||
Args:
|
||||
message: The inbound response.
|
||||
|
||||
Returns:
|
||||
The response unchanged.
|
||||
"""
|
||||
return message
|
||||
|
||||
|
||||
class TestAzureProviderInterceptor:
|
||||
"""Test suite for Azure provider with interceptor (unsupported)."""
|
||||
|
||||
def test_azure_llm_accepts_interceptor_parameter(self) -> None:
|
||||
"""Test that Azure LLM raises NotImplementedError with interceptor."""
|
||||
interceptor = DummyInterceptor()
|
||||
|
||||
# Azure provider should raise NotImplementedError
|
||||
with pytest.raises(NotImplementedError) as exc_info:
|
||||
LLM(
|
||||
model="azure/gpt-4",
|
||||
interceptor=interceptor,
|
||||
api_key="test-key",
|
||||
endpoint="https://test.openai.azure.com/openai/deployments/gpt-4",
|
||||
)
|
||||
|
||||
assert "interceptor" in str(exc_info.value).lower()
|
||||
|
||||
def test_azure_raises_not_implemented_on_initialization(self) -> None:
|
||||
"""Test that Azure raises NotImplementedError when interceptor is used."""
|
||||
interceptor = DummyInterceptor()
|
||||
|
||||
with pytest.raises(NotImplementedError) as exc_info:
|
||||
LLM(
|
||||
model="azure/gpt-4",
|
||||
interceptor=interceptor,
|
||||
api_key="test-key",
|
||||
endpoint="https://test.openai.azure.com/openai/deployments/gpt-4",
|
||||
)
|
||||
|
||||
error_msg = str(exc_info.value).lower()
|
||||
assert "interceptor" in error_msg
|
||||
assert "azure" in error_msg
|
||||
|
||||
def test_azure_without_interceptor_works(self) -> None:
|
||||
"""Test that Azure LLM works without interceptor."""
|
||||
llm = LLM(
|
||||
model="azure/gpt-4",
|
||||
api_key="test-key",
|
||||
endpoint="https://test.openai.azure.com/openai/deployments/gpt-4",
|
||||
)
|
||||
|
||||
# Azure provider doesn't have interceptor attribute
|
||||
assert not hasattr(llm, 'interceptor') or llm.interceptor is None
|
||||
|
||||
|
||||
class TestBedrockProviderInterceptor:
|
||||
"""Test suite for Bedrock provider with interceptor (unsupported)."""
|
||||
|
||||
def test_bedrock_llm_accepts_interceptor_parameter(self) -> None:
|
||||
"""Test that Bedrock LLM raises NotImplementedError with interceptor."""
|
||||
interceptor = DummyInterceptor()
|
||||
|
||||
# Bedrock provider should raise NotImplementedError
|
||||
with pytest.raises(NotImplementedError) as exc_info:
|
||||
LLM(
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
interceptor=interceptor,
|
||||
aws_access_key_id="test-access-key",
|
||||
aws_secret_access_key="test-secret-key",
|
||||
aws_region_name="us-east-1",
|
||||
)
|
||||
|
||||
error_msg = str(exc_info.value).lower()
|
||||
assert "interceptor" in error_msg
|
||||
assert "bedrock" in error_msg
|
||||
|
||||
def test_bedrock_raises_not_implemented_on_initialization(self) -> None:
|
||||
"""Test that Bedrock raises NotImplementedError when interceptor is used."""
|
||||
interceptor = DummyInterceptor()
|
||||
|
||||
with pytest.raises(NotImplementedError) as exc_info:
|
||||
LLM(
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
interceptor=interceptor,
|
||||
aws_access_key_id="test-access-key",
|
||||
aws_secret_access_key="test-secret-key",
|
||||
aws_region_name="us-east-1",
|
||||
)
|
||||
|
||||
error_msg = str(exc_info.value).lower()
|
||||
assert "interceptor" in error_msg
|
||||
assert "bedrock" in error_msg
|
||||
|
||||
def test_bedrock_without_interceptor_works(self) -> None:
|
||||
"""Test that Bedrock LLM works without interceptor."""
|
||||
llm = LLM(
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
aws_access_key_id="test-access-key",
|
||||
aws_secret_access_key="test-secret-key",
|
||||
aws_region_name="us-east-1",
|
||||
)
|
||||
|
||||
# Bedrock provider doesn't have interceptor attribute
|
||||
assert not hasattr(llm, 'interceptor') or llm.interceptor is None
|
||||
|
||||
|
||||
class TestGeminiProviderInterceptor:
|
||||
"""Test suite for Gemini provider with interceptor (unsupported)."""
|
||||
|
||||
def test_gemini_llm_accepts_interceptor_parameter(self) -> None:
|
||||
"""Test that Gemini LLM raises NotImplementedError with interceptor."""
|
||||
interceptor = DummyInterceptor()
|
||||
|
||||
# Gemini provider should raise NotImplementedError
|
||||
with pytest.raises(NotImplementedError) as exc_info:
|
||||
LLM(
|
||||
model="gemini/gemini-pro",
|
||||
interceptor=interceptor,
|
||||
api_key="test-gemini-key",
|
||||
)
|
||||
|
||||
error_msg = str(exc_info.value).lower()
|
||||
assert "interceptor" in error_msg
|
||||
assert "gemini" in error_msg
|
||||
|
||||
def test_gemini_raises_not_implemented_on_initialization(self) -> None:
|
||||
"""Test that Gemini raises NotImplementedError when interceptor is used."""
|
||||
interceptor = DummyInterceptor()
|
||||
|
||||
with pytest.raises(NotImplementedError) as exc_info:
|
||||
LLM(
|
||||
model="gemini/gemini-pro",
|
||||
interceptor=interceptor,
|
||||
api_key="test-gemini-key",
|
||||
)
|
||||
|
||||
error_msg = str(exc_info.value).lower()
|
||||
assert "interceptor" in error_msg
|
||||
assert "gemini" in error_msg
|
||||
|
||||
def test_gemini_without_interceptor_works(self) -> None:
|
||||
"""Test that Gemini LLM works without interceptor."""
|
||||
llm = LLM(
|
||||
model="gemini/gemini-pro",
|
||||
api_key="test-gemini-key",
|
||||
)
|
||||
|
||||
# Gemini provider doesn't have interceptor attribute
|
||||
assert not hasattr(llm, 'interceptor') or llm.interceptor is None
|
||||
|
||||
|
||||
class TestUnsupportedProviderMessages:
|
||||
"""Test suite for error messages from unsupported providers."""
|
||||
|
||||
def test_azure_error_message_is_clear(self) -> None:
|
||||
"""Test that Azure error message clearly states lack of support."""
|
||||
interceptor = DummyInterceptor()
|
||||
|
||||
with pytest.raises(NotImplementedError) as exc_info:
|
||||
LLM(
|
||||
model="azure/gpt-4",
|
||||
interceptor=interceptor,
|
||||
api_key="test-key",
|
||||
endpoint="https://test.openai.azure.com/openai/deployments/gpt-4",
|
||||
)
|
||||
|
||||
error_message = str(exc_info.value).lower()
|
||||
assert "azure" in error_message
|
||||
assert "interceptor" in error_message
|
||||
|
||||
def test_bedrock_error_message_is_clear(self) -> None:
|
||||
"""Test that Bedrock error message clearly states lack of support."""
|
||||
interceptor = DummyInterceptor()
|
||||
|
||||
with pytest.raises(NotImplementedError) as exc_info:
|
||||
LLM(
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
interceptor=interceptor,
|
||||
aws_access_key_id="test-access-key",
|
||||
aws_secret_access_key="test-secret-key",
|
||||
aws_region_name="us-east-1",
|
||||
)
|
||||
|
||||
error_message = str(exc_info.value).lower()
|
||||
assert "bedrock" in error_message
|
||||
assert "interceptor" in error_message
|
||||
|
||||
def test_gemini_error_message_is_clear(self) -> None:
|
||||
"""Test that Gemini error message clearly states lack of support."""
|
||||
interceptor = DummyInterceptor()
|
||||
|
||||
with pytest.raises(NotImplementedError) as exc_info:
|
||||
LLM(
|
||||
model="gemini/gemini-pro",
|
||||
interceptor=interceptor,
|
||||
api_key="test-gemini-key",
|
||||
)
|
||||
|
||||
error_message = str(exc_info.value).lower()
|
||||
assert "gemini" in error_message
|
||||
assert "interceptor" in error_message
|
||||
|
||||
|
||||
class TestProviderSupportMatrix:
|
||||
"""Test suite to document which providers support interceptors."""
|
||||
|
||||
def test_supported_providers_accept_interceptor(self) -> None:
|
||||
"""Test that supported providers accept and use interceptors."""
|
||||
interceptor = DummyInterceptor()
|
||||
|
||||
# OpenAI - SUPPORTED
|
||||
openai_llm = LLM(model="gpt-4", interceptor=interceptor)
|
||||
assert openai_llm.interceptor is interceptor
|
||||
|
||||
# Anthropic - SUPPORTED
|
||||
anthropic_llm = LLM(model="anthropic/claude-3-opus-20240229", interceptor=interceptor)
|
||||
assert anthropic_llm.interceptor is interceptor
|
||||
|
||||
def test_unsupported_providers_raise_error(self) -> None:
|
||||
"""Test that unsupported providers raise NotImplementedError."""
|
||||
interceptor = DummyInterceptor()
|
||||
|
||||
# Azure - NOT SUPPORTED
|
||||
with pytest.raises(NotImplementedError):
|
||||
LLM(
|
||||
model="azure/gpt-4",
|
||||
interceptor=interceptor,
|
||||
api_key="test",
|
||||
endpoint="https://test.openai.azure.com/openai/deployments/gpt-4",
|
||||
)
|
||||
|
||||
# Bedrock - NOT SUPPORTED
|
||||
with pytest.raises(NotImplementedError):
|
||||
LLM(
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
interceptor=interceptor,
|
||||
aws_access_key_id="test",
|
||||
aws_secret_access_key="test",
|
||||
aws_region_name="us-east-1",
|
||||
)
|
||||
|
||||
# Gemini - NOT SUPPORTED
|
||||
with pytest.raises(NotImplementedError):
|
||||
LLM(
|
||||
model="gemini/gemini-pro",
|
||||
interceptor=interceptor,
|
||||
api_key="test",
|
||||
)
|
||||
|
||||
def test_all_providers_work_without_interceptor(self) -> None:
|
||||
"""Test that all providers work normally without interceptor."""
|
||||
# OpenAI
|
||||
openai_llm = LLM(model="gpt-4")
|
||||
assert openai_llm.interceptor is None
|
||||
|
||||
# Anthropic
|
||||
anthropic_llm = LLM(model="anthropic/claude-3-opus-20240229")
|
||||
assert anthropic_llm.interceptor is None
|
||||
|
||||
# Azure - doesn't have interceptor attribute
|
||||
azure_llm = LLM(
|
||||
model="azure/gpt-4",
|
||||
api_key="test",
|
||||
endpoint="https://test.openai.azure.com/openai/deployments/gpt-4",
|
||||
)
|
||||
assert not hasattr(azure_llm, 'interceptor') or azure_llm.interceptor is None
|
||||
|
||||
# Bedrock - doesn't have interceptor attribute
|
||||
bedrock_llm = LLM(
|
||||
model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
aws_access_key_id="test",
|
||||
aws_secret_access_key="test",
|
||||
aws_region_name="us-east-1",
|
||||
)
|
||||
assert not hasattr(bedrock_llm, 'interceptor') or bedrock_llm.interceptor is None
|
||||
|
||||
# Gemini - doesn't have interceptor attribute
|
||||
gemini_llm = LLM(model="gemini/gemini-pro", api_key="test")
|
||||
assert not hasattr(gemini_llm, 'interceptor') or gemini_llm.interceptor is None
|
||||
94
lib/crewai/tests/project/test_callback_with_taskoutput.py
Normal file
94
lib/crewai/tests/project/test_callback_with_taskoutput.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""Test callback decorator with TaskOutput arguments."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from crewai import Agent, Crew, Task
|
||||
from crewai.project import CrewBase, callback, task
|
||||
from crewai.tasks.output_format import OutputFormat
|
||||
from crewai.tasks.task_output import TaskOutput
|
||||
|
||||
|
||||
def test_callback_decorator_with_taskoutput() -> None:
|
||||
"""Test that @callback decorator works with TaskOutput arguments."""
|
||||
|
||||
@CrewBase
|
||||
class TestCrew:
|
||||
"""Test crew with callback."""
|
||||
|
||||
callback_called = False
|
||||
callback_output = None
|
||||
|
||||
@callback
|
||||
def task_callback(self, output: TaskOutput) -> None:
|
||||
"""Test callback that receives TaskOutput."""
|
||||
self.callback_called = True
|
||||
self.callback_output = output
|
||||
|
||||
@task
|
||||
def test_task(self) -> Task:
|
||||
"""Test task with callback."""
|
||||
return Task(
|
||||
description="Test task",
|
||||
expected_output="Test output",
|
||||
callback=self.task_callback,
|
||||
)
|
||||
|
||||
test_crew = TestCrew()
|
||||
task_instance = test_crew.test_task()
|
||||
|
||||
test_output = TaskOutput(
|
||||
description="Test task",
|
||||
agent="Test Agent",
|
||||
raw="test result",
|
||||
output_format=OutputFormat.RAW,
|
||||
)
|
||||
|
||||
task_instance.callback(test_output)
|
||||
|
||||
assert test_crew.callback_called
|
||||
assert test_crew.callback_output == test_output
|
||||
|
||||
|
||||
def test_callback_decorator_with_taskoutput_integration() -> None:
|
||||
"""Integration test for callback with actual task execution."""
|
||||
|
||||
@CrewBase
|
||||
class TestCrew:
|
||||
"""Test crew with callback integration."""
|
||||
|
||||
callback_called = False
|
||||
received_output: TaskOutput | None = None
|
||||
|
||||
@callback
|
||||
def task_callback(self, output: TaskOutput) -> None:
|
||||
"""Callback executed after task completion."""
|
||||
self.callback_called = True
|
||||
self.received_output = output
|
||||
|
||||
@task
|
||||
def test_task(self) -> Task:
|
||||
"""Test task."""
|
||||
return Task(
|
||||
description="Test task",
|
||||
expected_output="Test output",
|
||||
callback=self.task_callback,
|
||||
)
|
||||
|
||||
test_crew = TestCrew()
|
||||
|
||||
agent = Agent(
|
||||
role="Test Agent",
|
||||
goal="Test goal",
|
||||
backstory="Test backstory",
|
||||
)
|
||||
|
||||
task_instance = test_crew.test_task()
|
||||
task_instance.agent = agent
|
||||
|
||||
with patch.object(Agent, "execute_task") as mock_execute:
|
||||
mock_execute.return_value = "test result"
|
||||
task_instance.execute_sync()
|
||||
|
||||
assert test_crew.callback_called
|
||||
assert test_crew.received_output is not None
|
||||
assert test_crew.received_output.raw == "test result"
|
||||
Reference in New Issue
Block a user