mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-09 17:05:08 +00:00
Compare commits
23 Commits
bugfix/add
...
brandon/st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc2bcc292f | ||
|
|
ea4feb7b2e | ||
|
|
23b9e10323 | ||
|
|
ddb7958da7 | ||
|
|
477cce321f | ||
|
|
7bed63a693 | ||
|
|
2709a9205a | ||
|
|
d19d7b01ec | ||
|
|
a3ad2c1957 | ||
|
|
c3e7a3ec19 | ||
|
|
cba8c9faec | ||
|
|
bcb7fb27d0 | ||
|
|
c310044bec | ||
|
|
5263df24b6 | ||
|
|
dea6ed7ef0 | ||
|
|
d3a0dad323 | ||
|
|
67bf4aea56 | ||
|
|
8c76bad50f | ||
|
|
e27a15023c | ||
|
|
a836f466f4 | ||
|
|
67f0de1f90 | ||
|
|
c642ebf97e | ||
|
|
a21e310d78 |
@@ -43,7 +43,7 @@ Think of an agent as a specialized team member with specific skills, expertise,
|
|||||||
| **Max Retry Limit** _(optional)_ | `max_retry_limit` | `int` | Maximum number of retries when an error occurs. Default is 2. |
|
| **Max Retry Limit** _(optional)_ | `max_retry_limit` | `int` | Maximum number of retries when an error occurs. Default is 2. |
|
||||||
| **Respect Context Window** _(optional)_ | `respect_context_window` | `bool` | Keep messages under context window size by summarizing. Default is True. |
|
| **Respect Context Window** _(optional)_ | `respect_context_window` | `bool` | Keep messages under context window size by summarizing. Default is True. |
|
||||||
| **Code Execution Mode** _(optional)_ | `code_execution_mode` | `Literal["safe", "unsafe"]` | Mode for code execution: 'safe' (using Docker) or 'unsafe' (direct). Default is 'safe'. |
|
| **Code Execution Mode** _(optional)_ | `code_execution_mode` | `Literal["safe", "unsafe"]` | Mode for code execution: 'safe' (using Docker) or 'unsafe' (direct). Default is 'safe'. |
|
||||||
| **Embedder Config** _(optional)_ | `embedder_config` | `Optional[Dict[str, Any]]` | Configuration for the embedder used by the agent. |
|
| **Embedder** _(optional)_ | `embedder` | `Optional[Dict[str, Any]]` | Configuration for the embedder used by the agent. |
|
||||||
| **Knowledge Sources** _(optional)_ | `knowledge_sources` | `Optional[List[BaseKnowledgeSource]]` | Knowledge sources available to the agent. |
|
| **Knowledge Sources** _(optional)_ | `knowledge_sources` | `Optional[List[BaseKnowledgeSource]]` | Knowledge sources available to the agent. |
|
||||||
| **Use System Prompt** _(optional)_ | `use_system_prompt` | `Optional[bool]` | Whether to use system prompt (for o1 model support). Default is True. |
|
| **Use System Prompt** _(optional)_ | `use_system_prompt` | `Optional[bool]` | Whether to use system prompt (for o1 model support). Default is True. |
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ agent = Agent(
|
|||||||
use_system_prompt=True, # Default: True
|
use_system_prompt=True, # Default: True
|
||||||
tools=[SerperDevTool()], # Optional: List of tools
|
tools=[SerperDevTool()], # Optional: List of tools
|
||||||
knowledge_sources=None, # Optional: List of knowledge sources
|
knowledge_sources=None, # Optional: List of knowledge sources
|
||||||
embedder_config=None, # Optional: Custom embedder configuration
|
embedder=None, # Optional: Custom embedder configuration
|
||||||
system_template=None, # Optional: Custom system prompt template
|
system_template=None, # Optional: Custom system prompt template
|
||||||
prompt_template=None, # Optional: Custom prompt template
|
prompt_template=None, # Optional: Custom prompt template
|
||||||
response_template=None, # Optional: Custom response template
|
response_template=None, # Optional: Custom response template
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ The CrewAI CLI provides a set of commands to interact with CrewAI, allowing you
|
|||||||
|
|
||||||
To use the CrewAI CLI, make sure you have CrewAI installed:
|
To use the CrewAI CLI, make sure you have CrewAI installed:
|
||||||
|
|
||||||
```shell
|
```shell Terminal
|
||||||
pip install crewai
|
pip install crewai
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ pip install crewai
|
|||||||
|
|
||||||
The basic structure of a CrewAI CLI command is:
|
The basic structure of a CrewAI CLI command is:
|
||||||
|
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai [COMMAND] [OPTIONS] [ARGUMENTS]
|
crewai [COMMAND] [OPTIONS] [ARGUMENTS]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ crewai [COMMAND] [OPTIONS] [ARGUMENTS]
|
|||||||
|
|
||||||
Create a new crew or flow.
|
Create a new crew or flow.
|
||||||
|
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai create [OPTIONS] TYPE NAME
|
crewai create [OPTIONS] TYPE NAME
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ crewai create [OPTIONS] TYPE NAME
|
|||||||
- `NAME`: Name of the crew or flow
|
- `NAME`: Name of the crew or flow
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai create crew my_new_crew
|
crewai create crew my_new_crew
|
||||||
crewai create flow my_new_flow
|
crewai create flow my_new_flow
|
||||||
```
|
```
|
||||||
@@ -47,14 +47,14 @@ crewai create flow my_new_flow
|
|||||||
|
|
||||||
Show the installed version of CrewAI.
|
Show the installed version of CrewAI.
|
||||||
|
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai version [OPTIONS]
|
crewai version [OPTIONS]
|
||||||
```
|
```
|
||||||
|
|
||||||
- `--tools`: (Optional) Show the installed version of CrewAI tools
|
- `--tools`: (Optional) Show the installed version of CrewAI tools
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai version
|
crewai version
|
||||||
crewai version --tools
|
crewai version --tools
|
||||||
```
|
```
|
||||||
@@ -63,7 +63,7 @@ crewai version --tools
|
|||||||
|
|
||||||
Train the crew for a specified number of iterations.
|
Train the crew for a specified number of iterations.
|
||||||
|
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai train [OPTIONS]
|
crewai train [OPTIONS]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ crewai train [OPTIONS]
|
|||||||
- `-f, --filename TEXT`: Path to a custom file for training (default: "trained_agents_data.pkl")
|
- `-f, --filename TEXT`: Path to a custom file for training (default: "trained_agents_data.pkl")
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai train -n 10 -f my_training_data.pkl
|
crewai train -n 10 -f my_training_data.pkl
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -79,14 +79,14 @@ crewai train -n 10 -f my_training_data.pkl
|
|||||||
|
|
||||||
Replay the crew execution from a specific task.
|
Replay the crew execution from a specific task.
|
||||||
|
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai replay [OPTIONS]
|
crewai replay [OPTIONS]
|
||||||
```
|
```
|
||||||
|
|
||||||
- `-t, --task_id TEXT`: Replay the crew from this task ID, including all subsequent tasks
|
- `-t, --task_id TEXT`: Replay the crew from this task ID, including all subsequent tasks
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai replay -t task_123456
|
crewai replay -t task_123456
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ crewai replay -t task_123456
|
|||||||
|
|
||||||
Retrieve your latest crew.kickoff() task outputs.
|
Retrieve your latest crew.kickoff() task outputs.
|
||||||
|
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai log-tasks-outputs
|
crewai log-tasks-outputs
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ crewai log-tasks-outputs
|
|||||||
|
|
||||||
Reset the crew memories (long, short, entity, latest_crew_kickoff_outputs).
|
Reset the crew memories (long, short, entity, latest_crew_kickoff_outputs).
|
||||||
|
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai reset-memories [OPTIONS]
|
crewai reset-memories [OPTIONS]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ crewai reset-memories [OPTIONS]
|
|||||||
- `-a, --all`: Reset ALL memories
|
- `-a, --all`: Reset ALL memories
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai reset-memories --long --short
|
crewai reset-memories --long --short
|
||||||
crewai reset-memories --all
|
crewai reset-memories --all
|
||||||
```
|
```
|
||||||
@@ -122,7 +122,7 @@ crewai reset-memories --all
|
|||||||
|
|
||||||
Test the crew and evaluate the results.
|
Test the crew and evaluate the results.
|
||||||
|
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai test [OPTIONS]
|
crewai test [OPTIONS]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ crewai test [OPTIONS]
|
|||||||
- `-m, --model TEXT`: LLM Model to run the tests on the Crew (default: "gpt-4o-mini")
|
- `-m, --model TEXT`: LLM Model to run the tests on the Crew (default: "gpt-4o-mini")
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai test -n 5 -m gpt-3.5-turbo
|
crewai test -n 5 -m gpt-3.5-turbo
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ crewai test -n 5 -m gpt-3.5-turbo
|
|||||||
|
|
||||||
Run the crew.
|
Run the crew.
|
||||||
|
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai run
|
crewai run
|
||||||
```
|
```
|
||||||
<Note>
|
<Note>
|
||||||
@@ -153,7 +153,7 @@ Starting in version `0.98.0`, when you run the `crewai chat` command, you start
|
|||||||
|
|
||||||
After receiving the results, you can continue interacting with the assistant for further instructions or questions.
|
After receiving the results, you can continue interacting with the assistant for further instructions or questions.
|
||||||
|
|
||||||
```shell
|
```shell Terminal
|
||||||
crewai chat
|
crewai chat
|
||||||
```
|
```
|
||||||
<Note>
|
<Note>
|
||||||
|
|||||||
@@ -324,6 +324,13 @@ agent = Agent(
|
|||||||
verbose=True,
|
verbose=True,
|
||||||
allow_delegation=False,
|
allow_delegation=False,
|
||||||
llm=gemini_llm,
|
llm=gemini_llm,
|
||||||
|
embedder={
|
||||||
|
"provider": "google",
|
||||||
|
"config": {
|
||||||
|
"model": "models/text-embedding-004",
|
||||||
|
"api_key": GEMINI_API_KEY,
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
task = Task(
|
task = Task(
|
||||||
|
|||||||
@@ -243,6 +243,9 @@ There are three ways to configure LLMs in CrewAI. Choose the method that best fi
|
|||||||
# llm: bedrock/amazon.titan-text-express-v1
|
# llm: bedrock/amazon.titan-text-express-v1
|
||||||
# llm: bedrock/meta.llama2-70b-chat-v1
|
# llm: bedrock/meta.llama2-70b-chat-v1
|
||||||
|
|
||||||
|
# Amazon SageMaker Models - Enterprise-grade
|
||||||
|
# llm: sagemaker/<my-endpoint>
|
||||||
|
|
||||||
# Mistral Models - Open source alternative
|
# Mistral Models - Open source alternative
|
||||||
# llm: mistral/mistral-large-latest
|
# llm: mistral/mistral-large-latest
|
||||||
# llm: mistral/mistral-medium-latest
|
# llm: mistral/mistral-medium-latest
|
||||||
@@ -462,11 +465,22 @@ Learn how to get the most out of your LLM configuration:
|
|||||||
# https://cloud.google.com/vertex-ai/generative-ai/docs/overview
|
# https://cloud.google.com/vertex-ai/generative-ai/docs/overview
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## GET CREDENTIALS
|
||||||
|
file_path = 'path/to/vertex_ai_service_account.json'
|
||||||
|
|
||||||
|
# Load the JSON file
|
||||||
|
with open(file_path, 'r') as file:
|
||||||
|
vertex_credentials = json.load(file)
|
||||||
|
|
||||||
|
# Convert to JSON string
|
||||||
|
vertex_credentials_json = json.dumps(vertex_credentials)
|
||||||
|
|
||||||
Example usage:
|
Example usage:
|
||||||
```python Code
|
```python Code
|
||||||
llm = LLM(
|
llm = LLM(
|
||||||
model="gemini/gemini-1.5-pro-latest",
|
model="gemini/gemini-1.5-pro-latest",
|
||||||
temperature=0.7
|
temperature=0.7,
|
||||||
|
vertex_credentials=vertex_credentials_json
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@@ -506,6 +520,21 @@ Learn how to get the most out of your LLM configuration:
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
|
<Accordion title="Amazon SageMaker">
|
||||||
|
```python Code
|
||||||
|
AWS_ACCESS_KEY_ID=<your-access-key>
|
||||||
|
AWS_SECRET_ACCESS_KEY=<your-secret-key>
|
||||||
|
AWS_DEFAULT_REGION=<your-region>
|
||||||
|
```
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
```python Code
|
||||||
|
llm = LLM(
|
||||||
|
model="sagemaker/<my-endpoint>"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="Mistral">
|
<Accordion title="Mistral">
|
||||||
```python Code
|
```python Code
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ crew = Crew(
|
|||||||
| :------------------------------- | :---------------- | :---------------------------- | :------------------------------------------------------------------------------------------------------------------- |
|
| :------------------------------- | :---------------- | :---------------------------- | :------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **Description** | `description` | `str` | A clear, concise statement of what the task entails. |
|
| **Description** | `description` | `str` | A clear, concise statement of what the task entails. |
|
||||||
| **Expected Output** | `expected_output` | `str` | A detailed description of what the task's completion looks like. |
|
| **Expected Output** | `expected_output` | `str` | A detailed description of what the task's completion looks like. |
|
||||||
| **Name** _(optional)_ | `name` | `Optional[str]` | A name identifier for the task. |
|
| **Name** _(optional)_ | `name` | `Optional[str]` | A name identifier for the task. |
|
||||||
| **Agent** _(optional)_ | `agent` | `Optional[BaseAgent]` | The agent responsible for executing the task. |
|
| **Agent** _(optional)_ | `agent` | `Optional[BaseAgent]` | The agent responsible for executing the task. |
|
||||||
| **Tools** _(optional)_ | `tools` | `List[BaseTool]` | The tools/resources the agent is limited to use for this task. |
|
| **Tools** _(optional)_ | `tools` | `List[BaseTool]` | The tools/resources the agent is limited to use for this task. |
|
||||||
| **Context** _(optional)_ | `context` | `Optional[List["Task"]]` | Other tasks whose outputs will be used as context for this task. |
|
| **Context** _(optional)_ | `context` | `Optional[List["Task"]]` | Other tasks whose outputs will be used as context for this task. |
|
||||||
| **Async Execution** _(optional)_ | `async_execution` | `Optional[bool]` | Whether the task should be executed asynchronously. Defaults to False. |
|
| **Async Execution** _(optional)_ | `async_execution` | `Optional[bool]` | Whether the task should be executed asynchronously. Defaults to False. |
|
||||||
|
| **Human Input** _(optional)_ | `human_input` | `Optional[bool]` | Whether the task should have a human review the final answer of the agent. Defaults to False. |
|
||||||
| **Config** _(optional)_ | `config` | `Optional[Dict[str, Any]]` | Task-specific configuration parameters. |
|
| **Config** _(optional)_ | `config` | `Optional[Dict[str, Any]]` | Task-specific configuration parameters. |
|
||||||
| **Output File** _(optional)_ | `output_file` | `Optional[str]` | File path for storing the task output. |
|
| **Output File** _(optional)_ | `output_file` | `Optional[str]` | File path for storing the task output. |
|
||||||
| **Output JSON** _(optional)_ | `output_json` | `Optional[Type[BaseModel]]` | A Pydantic model to structure the JSON output. |
|
| **Output JSON** _(optional)_ | `output_json` | `Optional[Type[BaseModel]]` | A Pydantic model to structure the JSON output. |
|
||||||
|
|||||||
@@ -15,10 +15,48 @@ icon: wrench
|
|||||||
If you need to update Python, visit [python.org/downloads](https://python.org/downloads)
|
If you need to update Python, visit [python.org/downloads](https://python.org/downloads)
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
|
# Setting Up Your Environment
|
||||||
|
|
||||||
|
Before installing CrewAI, it's recommended to set up a virtual environment. This helps isolate your project dependencies and avoid conflicts.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Create a Virtual Environment">
|
||||||
|
Choose your preferred method to create a virtual environment:
|
||||||
|
|
||||||
|
**Using venv (Python's built-in tool):**
|
||||||
|
```shell Terminal
|
||||||
|
python3 -m venv .venv
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using conda:**
|
||||||
|
```shell Terminal
|
||||||
|
conda create -n crewai-env python=3.12
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Activate the Virtual Environment">
|
||||||
|
Activate your virtual environment based on your platform:
|
||||||
|
|
||||||
|
**On macOS/Linux (venv):**
|
||||||
|
```shell Terminal
|
||||||
|
source .venv/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
**On Windows (venv):**
|
||||||
|
```shell Terminal
|
||||||
|
.venv\Scripts\activate
|
||||||
|
```
|
||||||
|
|
||||||
|
**Using conda (all platforms):**
|
||||||
|
```shell Terminal
|
||||||
|
conda activate crewai-env
|
||||||
|
```
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
# Installing CrewAI
|
# Installing CrewAI
|
||||||
|
|
||||||
CrewAI is a flexible and powerful AI framework that enables you to create and manage AI agents, tools, and tasks efficiently.
|
Now let's get you set up! 🚀
|
||||||
Let's get you set up! 🚀
|
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Install CrewAI">
|
<Step title="Install CrewAI">
|
||||||
@@ -72,9 +110,9 @@ Let's get you set up! 🚀
|
|||||||
|
|
||||||
# Creating a New Project
|
# Creating a New Project
|
||||||
|
|
||||||
<Info>
|
<Tip>
|
||||||
We recommend using the YAML Template scaffolding for a structured approach to defining agents and tasks.
|
We recommend using the YAML Template scaffolding for a structured approach to defining agents and tasks.
|
||||||
</Info>
|
</Tip>
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Generate Project Structure">
|
<Step title="Generate Project Structure">
|
||||||
@@ -104,7 +142,18 @@ Let's get you set up! 🚀
|
|||||||
└── tasks.yaml
|
└── tasks.yaml
|
||||||
```
|
```
|
||||||
</Frame>
|
</Frame>
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Install Additional Tools">
|
||||||
|
You can install additional tools using UV:
|
||||||
|
```shell Terminal
|
||||||
|
uv add <tool-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
UV is our preferred package manager as it's significantly faster than pip and provides better dependency resolution.
|
||||||
|
</Tip>
|
||||||
|
</Step>
|
||||||
|
|
||||||
<Step title="Customize Your Project">
|
<Step title="Customize Your Project">
|
||||||
Your project will contain these essential files:
|
Your project will contain these essential files:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: Composio
|
title: Composio Tool
|
||||||
description: Composio provides 250+ production-ready tools for AI agents with flexible authentication management.
|
description: Composio provides 250+ production-ready tools for AI agents with flexible authentication management.
|
||||||
icon: gear-code
|
icon: gear-code
|
||||||
---
|
---
|
||||||
@@ -75,7 +75,8 @@ filtered_action_enums = toolset.find_actions_by_use_case(
|
|||||||
)
|
)
|
||||||
|
|
||||||
tools = toolset.get_tools(actions=filtered_action_enums)
|
tools = toolset.get_tools(actions=filtered_action_enums)
|
||||||
```<Tip>Set `advanced` to True to get actions for complex use cases</Tip>
|
```
|
||||||
|
<Tip>Set `advanced` to True to get actions for complex use cases</Tip>
|
||||||
|
|
||||||
- Using specific tools:
|
- Using specific tools:
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "crewai"
|
name = "crewai"
|
||||||
version = "0.98.0"
|
version = "0.100.0"
|
||||||
description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
|
description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10,<3.13"
|
requires-python = ">=3.10,<3.13"
|
||||||
@@ -11,8 +11,8 @@ dependencies = [
|
|||||||
# Core Dependencies
|
# Core Dependencies
|
||||||
"pydantic>=2.4.2",
|
"pydantic>=2.4.2",
|
||||||
"openai>=1.13.3",
|
"openai>=1.13.3",
|
||||||
"litellm==1.57.4",
|
"litellm==1.59.8",
|
||||||
"instructor>=1.3.3",
|
"instructor>=1.7.2",
|
||||||
# Text Processing
|
# Text Processing
|
||||||
"pdfplumber>=0.11.4",
|
"pdfplumber>=0.11.4",
|
||||||
"regex>=2024.9.11",
|
"regex>=2024.9.11",
|
||||||
@@ -36,6 +36,7 @@ dependencies = [
|
|||||||
"tomli-w>=1.1.0",
|
"tomli-w>=1.1.0",
|
||||||
"tomli>=2.0.2",
|
"tomli>=2.0.2",
|
||||||
"blinker>=1.9.0",
|
"blinker>=1.9.0",
|
||||||
|
"json5>=0.10.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ warnings.filterwarnings(
|
|||||||
category=UserWarning,
|
category=UserWarning,
|
||||||
module="pydantic.main",
|
module="pydantic.main",
|
||||||
)
|
)
|
||||||
__version__ = "0.98.0"
|
__version__ = "0.100.0"
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Agent",
|
"Agent",
|
||||||
"Crew",
|
"Crew",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import os
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Any, Dict, List, Literal, Optional, Union
|
from typing import Any, Dict, List, Literal, Optional, Union
|
||||||
@@ -8,7 +7,6 @@ from pydantic import Field, InstanceOf, PrivateAttr, model_validator
|
|||||||
from crewai.agents import CacheHandler
|
from crewai.agents import CacheHandler
|
||||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||||
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
||||||
from crewai.cli.constants import ENV_VARS, LITELLM_PARAMS
|
|
||||||
from crewai.knowledge.knowledge import Knowledge
|
from crewai.knowledge.knowledge import Knowledge
|
||||||
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
|
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
|
||||||
from crewai.knowledge.utils.knowledge_utils import extract_knowledge_context
|
from crewai.knowledge.utils.knowledge_utils import extract_knowledge_context
|
||||||
@@ -63,6 +61,7 @@ class Agent(BaseAgent):
|
|||||||
tools: Tools at agents disposal
|
tools: Tools at agents disposal
|
||||||
step_callback: Callback to be executed after each step of the agent execution.
|
step_callback: Callback to be executed after each step of the agent execution.
|
||||||
knowledge_sources: Knowledge sources for the agent.
|
knowledge_sources: Knowledge sources for the agent.
|
||||||
|
embedder: Embedder configuration for the agent.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_times_executed: int = PrivateAttr(default=0)
|
_times_executed: int = PrivateAttr(default=0)
|
||||||
@@ -124,17 +123,10 @@ class Agent(BaseAgent):
|
|||||||
default="safe",
|
default="safe",
|
||||||
description="Mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution).",
|
description="Mode for code execution: 'safe' (using Docker) or 'unsafe' (direct execution).",
|
||||||
)
|
)
|
||||||
embedder_config: Optional[Dict[str, Any]] = Field(
|
embedder: Optional[Dict[str, Any]] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
description="Embedder configuration for the agent.",
|
description="Embedder configuration for the agent.",
|
||||||
)
|
)
|
||||||
knowledge_sources: Optional[List[BaseKnowledgeSource]] = Field(
|
|
||||||
default=None,
|
|
||||||
description="Knowledge sources for the agent.",
|
|
||||||
)
|
|
||||||
_knowledge: Optional[Knowledge] = PrivateAttr(
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def post_init_setup(self):
|
def post_init_setup(self):
|
||||||
@@ -165,10 +157,11 @@ class Agent(BaseAgent):
|
|||||||
if isinstance(self.knowledge_sources, list) and all(
|
if isinstance(self.knowledge_sources, list) and all(
|
||||||
isinstance(k, BaseKnowledgeSource) for k in self.knowledge_sources
|
isinstance(k, BaseKnowledgeSource) for k in self.knowledge_sources
|
||||||
):
|
):
|
||||||
self._knowledge = Knowledge(
|
self.knowledge = Knowledge(
|
||||||
sources=self.knowledge_sources,
|
sources=self.knowledge_sources,
|
||||||
embedder_config=self.embedder_config,
|
embedder=self.embedder,
|
||||||
collection_name=knowledge_agent_name,
|
collection_name=knowledge_agent_name,
|
||||||
|
storage=self.knowledge_storage or None,
|
||||||
)
|
)
|
||||||
except (TypeError, ValueError) as e:
|
except (TypeError, ValueError) as e:
|
||||||
raise ValueError(f"Invalid Knowledge Configuration: {str(e)}")
|
raise ValueError(f"Invalid Knowledge Configuration: {str(e)}")
|
||||||
@@ -227,8 +220,8 @@ class Agent(BaseAgent):
|
|||||||
if memory.strip() != "":
|
if memory.strip() != "":
|
||||||
task_prompt += self.i18n.slice("memory").format(memory=memory)
|
task_prompt += self.i18n.slice("memory").format(memory=memory)
|
||||||
|
|
||||||
if self._knowledge:
|
if self.knowledge:
|
||||||
agent_knowledge_snippets = self._knowledge.query([task.prompt()])
|
agent_knowledge_snippets = self.knowledge.query([task.prompt()])
|
||||||
if agent_knowledge_snippets:
|
if agent_knowledge_snippets:
|
||||||
agent_knowledge_context = extract_knowledge_context(
|
agent_knowledge_context = extract_knowledge_context(
|
||||||
agent_knowledge_snippets
|
agent_knowledge_snippets
|
||||||
@@ -261,6 +254,9 @@ class Agent(BaseAgent):
|
|||||||
}
|
}
|
||||||
)["output"]
|
)["output"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if e.__class__.__module__.startswith("litellm"):
|
||||||
|
# Do not retry on litellm errors
|
||||||
|
raise e
|
||||||
self._times_executed += 1
|
self._times_executed += 1
|
||||||
if self._times_executed > self.max_retry_limit:
|
if self._times_executed > self.max_retry_limit:
|
||||||
raise e
|
raise e
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ from pydantic_core import PydanticCustomError
|
|||||||
from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess
|
from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess
|
||||||
from crewai.agents.cache.cache_handler import CacheHandler
|
from crewai.agents.cache.cache_handler import CacheHandler
|
||||||
from crewai.agents.tools_handler import ToolsHandler
|
from crewai.agents.tools_handler import ToolsHandler
|
||||||
|
from crewai.knowledge.knowledge import Knowledge
|
||||||
|
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
|
||||||
from crewai.tools import BaseTool
|
from crewai.tools import BaseTool
|
||||||
from crewai.tools.base_tool import Tool
|
from crewai.tools.base_tool import Tool
|
||||||
from crewai.utilities import I18N, Logger, RPMController
|
from crewai.utilities import I18N, Logger, RPMController
|
||||||
@@ -48,6 +50,8 @@ class BaseAgent(ABC, BaseModel):
|
|||||||
cache_handler (InstanceOf[CacheHandler]): An instance of the CacheHandler class.
|
cache_handler (InstanceOf[CacheHandler]): An instance of the CacheHandler class.
|
||||||
tools_handler (InstanceOf[ToolsHandler]): An instance of the ToolsHandler class.
|
tools_handler (InstanceOf[ToolsHandler]): An instance of the ToolsHandler class.
|
||||||
max_tokens: Maximum number of tokens for the agent to generate in a response.
|
max_tokens: Maximum number of tokens for the agent to generate in a response.
|
||||||
|
knowledge_sources: Knowledge sources for the agent.
|
||||||
|
knowledge_storage: Custom knowledge storage for the agent.
|
||||||
|
|
||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
@@ -130,6 +134,17 @@ class BaseAgent(ABC, BaseModel):
|
|||||||
max_tokens: Optional[int] = Field(
|
max_tokens: Optional[int] = Field(
|
||||||
default=None, description="Maximum number of tokens for the agent's execution."
|
default=None, description="Maximum number of tokens for the agent's execution."
|
||||||
)
|
)
|
||||||
|
knowledge: Optional[Knowledge] = Field(
|
||||||
|
default=None, description="Knowledge for the agent."
|
||||||
|
)
|
||||||
|
knowledge_sources: Optional[List[BaseKnowledgeSource]] = Field(
|
||||||
|
default=None,
|
||||||
|
description="Knowledge sources for the agent.",
|
||||||
|
)
|
||||||
|
knowledge_storage: Optional[Any] = Field(
|
||||||
|
default=None,
|
||||||
|
description="Custom knowledge storage for the agent.",
|
||||||
|
)
|
||||||
|
|
||||||
@model_validator(mode="before")
|
@model_validator(mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -256,13 +271,44 @@ class BaseAgent(ABC, BaseModel):
|
|||||||
"tools_handler",
|
"tools_handler",
|
||||||
"cache_handler",
|
"cache_handler",
|
||||||
"llm",
|
"llm",
|
||||||
|
"knowledge_sources",
|
||||||
|
"knowledge_storage",
|
||||||
|
"knowledge",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Copy llm and clear callbacks
|
# Copy llm
|
||||||
existing_llm = shallow_copy(self.llm)
|
existing_llm = shallow_copy(self.llm)
|
||||||
|
copied_knowledge = shallow_copy(self.knowledge)
|
||||||
|
copied_knowledge_storage = shallow_copy(self.knowledge_storage)
|
||||||
|
# Properly copy knowledge sources if they exist
|
||||||
|
existing_knowledge_sources = None
|
||||||
|
if self.knowledge_sources:
|
||||||
|
# Create a shared storage instance for all knowledge sources
|
||||||
|
shared_storage = (
|
||||||
|
self.knowledge_sources[0].storage if self.knowledge_sources else None
|
||||||
|
)
|
||||||
|
|
||||||
|
existing_knowledge_sources = []
|
||||||
|
for source in self.knowledge_sources:
|
||||||
|
copied_source = (
|
||||||
|
source.model_copy()
|
||||||
|
if hasattr(source, "model_copy")
|
||||||
|
else shallow_copy(source)
|
||||||
|
)
|
||||||
|
# Ensure all copied sources use the same storage instance
|
||||||
|
copied_source.storage = shared_storage
|
||||||
|
existing_knowledge_sources.append(copied_source)
|
||||||
|
|
||||||
copied_data = self.model_dump(exclude=exclude)
|
copied_data = self.model_dump(exclude=exclude)
|
||||||
copied_data = {k: v for k, v in copied_data.items() if v is not None}
|
copied_data = {k: v for k, v in copied_data.items() if v is not None}
|
||||||
copied_agent = type(self)(**copied_data, llm=existing_llm, tools=self.tools)
|
copied_agent = type(self)(
|
||||||
|
**copied_data,
|
||||||
|
llm=existing_llm,
|
||||||
|
tools=self.tools,
|
||||||
|
knowledge_sources=existing_knowledge_sources,
|
||||||
|
knowledge=copied_knowledge,
|
||||||
|
knowledge_storage=copied_knowledge_storage,
|
||||||
|
)
|
||||||
|
|
||||||
return copied_agent
|
return copied_agent
|
||||||
|
|
||||||
|
|||||||
@@ -95,18 +95,29 @@ class CrewAgentExecutorMixin:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def _ask_human_input(self, final_answer: str) -> str:
|
def _ask_human_input(self, final_answer: str) -> str:
|
||||||
"""Prompt human input for final decision making."""
|
"""Prompt human input with mode-appropriate messaging."""
|
||||||
self._printer.print(
|
self._printer.print(
|
||||||
content=f"\033[1m\033[95m ## Final Result:\033[00m \033[92m{final_answer}\033[00m"
|
content=f"\033[1m\033[95m ## Final Result:\033[00m \033[92m{final_answer}\033[00m"
|
||||||
)
|
)
|
||||||
|
|
||||||
self._printer.print(
|
# Training mode prompt (single iteration)
|
||||||
content=(
|
if self.crew and getattr(self.crew, "_train", False):
|
||||||
|
prompt = (
|
||||||
"\n\n=====\n"
|
"\n\n=====\n"
|
||||||
"## Please provide feedback on the Final Result and the Agent's actions. "
|
"## TRAINING MODE: Provide feedback to improve the agent's performance.\n"
|
||||||
"Respond with 'looks good' or a similar phrase when you're satisfied.\n"
|
"This will be used to train better versions of the agent.\n"
|
||||||
|
"Please provide detailed feedback about the result quality and reasoning process.\n"
|
||||||
"=====\n"
|
"=====\n"
|
||||||
),
|
)
|
||||||
color="bold_yellow",
|
# Regular human-in-the-loop prompt (multiple iterations)
|
||||||
)
|
else:
|
||||||
|
prompt = (
|
||||||
|
"\n\n=====\n"
|
||||||
|
"## HUMAN FEEDBACK: Provide feedback on the Final Result and Agent's actions.\n"
|
||||||
|
"Respond with 'looks good' to accept or provide specific improvement requests.\n"
|
||||||
|
"You can provide multiple rounds of feedback until satisfied.\n"
|
||||||
|
"=====\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._printer.print(content=prompt, color="bold_yellow")
|
||||||
return input()
|
return input()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from crewai.agents.parser import (
|
|||||||
OutputParserException,
|
OutputParserException,
|
||||||
)
|
)
|
||||||
from crewai.agents.tools_handler import ToolsHandler
|
from crewai.agents.tools_handler import ToolsHandler
|
||||||
|
from crewai.llm import LLM
|
||||||
from crewai.tools.base_tool import BaseTool
|
from crewai.tools.base_tool import BaseTool
|
||||||
from crewai.tools.tool_usage import ToolUsage, ToolUsageErrorException
|
from crewai.tools.tool_usage import ToolUsage, ToolUsageErrorException
|
||||||
from crewai.utilities import I18N, Printer
|
from crewai.utilities import I18N, Printer
|
||||||
@@ -54,7 +55,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
callbacks: List[Any] = [],
|
callbacks: List[Any] = [],
|
||||||
):
|
):
|
||||||
self._i18n: I18N = I18N()
|
self._i18n: I18N = I18N()
|
||||||
self.llm = llm
|
self.llm: LLM = llm
|
||||||
self.task = task
|
self.task = task
|
||||||
self.agent = agent
|
self.agent = agent
|
||||||
self.crew = crew
|
self.crew = crew
|
||||||
@@ -80,10 +81,8 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
self.tool_name_to_tool_map: Dict[str, BaseTool] = {
|
self.tool_name_to_tool_map: Dict[str, BaseTool] = {
|
||||||
tool.name: tool for tool in self.tools
|
tool.name: tool for tool in self.tools
|
||||||
}
|
}
|
||||||
if self.llm.stop:
|
self.stop = stop_words
|
||||||
self.llm.stop = list(set(self.llm.stop + self.stop))
|
self.llm.stop = list(set(self.llm.stop + self.stop))
|
||||||
else:
|
|
||||||
self.llm.stop = self.stop
|
|
||||||
|
|
||||||
def invoke(self, inputs: Dict[str, str]) -> Dict[str, Any]:
|
def invoke(self, inputs: Dict[str, str]) -> Dict[str, Any]:
|
||||||
if "system" in self.prompt:
|
if "system" in self.prompt:
|
||||||
@@ -98,7 +97,22 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
self._show_start_logs()
|
self._show_start_logs()
|
||||||
|
|
||||||
self.ask_for_human_input = bool(inputs.get("ask_for_human_input", False))
|
self.ask_for_human_input = bool(inputs.get("ask_for_human_input", False))
|
||||||
formatted_answer = self._invoke_loop()
|
|
||||||
|
try:
|
||||||
|
formatted_answer = self._invoke_loop()
|
||||||
|
except AssertionError:
|
||||||
|
self._printer.print(
|
||||||
|
content="Agent failed to reach a final answer. This is likely a bug - please report it.",
|
||||||
|
color="red",
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
if e.__class__.__module__.startswith("litellm"):
|
||||||
|
# Do not retry on litellm errors
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
self._handle_unknown_error(e)
|
||||||
|
raise e
|
||||||
|
|
||||||
if self.ask_for_human_input:
|
if self.ask_for_human_input:
|
||||||
formatted_answer = self._handle_human_feedback(formatted_answer)
|
formatted_answer = self._handle_human_feedback(formatted_answer)
|
||||||
@@ -107,7 +121,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
self._create_long_term_memory(formatted_answer)
|
self._create_long_term_memory(formatted_answer)
|
||||||
return {"output": formatted_answer.output}
|
return {"output": formatted_answer.output}
|
||||||
|
|
||||||
def _invoke_loop(self):
|
def _invoke_loop(self) -> AgentFinish:
|
||||||
"""
|
"""
|
||||||
Main loop to invoke the agent's thought process until it reaches a conclusion
|
Main loop to invoke the agent's thought process until it reaches a conclusion
|
||||||
or the maximum number of iterations is reached.
|
or the maximum number of iterations is reached.
|
||||||
@@ -124,7 +138,6 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
self._enforce_rpm_limit()
|
self._enforce_rpm_limit()
|
||||||
|
|
||||||
answer = self._get_llm_response()
|
answer = self._get_llm_response()
|
||||||
|
|
||||||
formatted_answer = self._process_llm_response(answer)
|
formatted_answer = self._process_llm_response(answer)
|
||||||
|
|
||||||
if isinstance(formatted_answer, AgentAction):
|
if isinstance(formatted_answer, AgentAction):
|
||||||
@@ -142,13 +155,37 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
formatted_answer = self._handle_output_parser_exception(e)
|
formatted_answer = self._handle_output_parser_exception(e)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if e.__class__.__module__.startswith("litellm"):
|
||||||
|
# Do not retry on litellm errors
|
||||||
|
raise e
|
||||||
if self._is_context_length_exceeded(e):
|
if self._is_context_length_exceeded(e):
|
||||||
self._handle_context_length()
|
self._handle_context_length()
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
|
self._handle_unknown_error(e)
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
self.iterations += 1
|
||||||
|
|
||||||
|
# During the invoke loop, formatted_answer alternates between AgentAction
|
||||||
|
# (when the agent is using tools) and eventually becomes AgentFinish
|
||||||
|
# (when the agent reaches a final answer). This assertion confirms we've
|
||||||
|
# reached a final answer and helps type checking understand this transition.
|
||||||
|
assert isinstance(formatted_answer, AgentFinish)
|
||||||
self._show_logs(formatted_answer)
|
self._show_logs(formatted_answer)
|
||||||
return formatted_answer
|
return formatted_answer
|
||||||
|
|
||||||
|
def _handle_unknown_error(self, exception: Exception) -> None:
|
||||||
|
"""Handle unknown errors by informing the user."""
|
||||||
|
self._printer.print(
|
||||||
|
content="An unknown error occurred. Please check the details below.",
|
||||||
|
color="red",
|
||||||
|
)
|
||||||
|
self._printer.print(
|
||||||
|
content=f"Error details: {exception}",
|
||||||
|
color="red",
|
||||||
|
)
|
||||||
|
|
||||||
def _has_reached_max_iterations(self) -> bool:
|
def _has_reached_max_iterations(self) -> bool:
|
||||||
"""Check if the maximum number of iterations has been reached."""
|
"""Check if the maximum number of iterations has been reached."""
|
||||||
return self.iterations >= self.max_iter
|
return self.iterations >= self.max_iter
|
||||||
@@ -160,10 +197,17 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
|
|
||||||
def _get_llm_response(self) -> str:
|
def _get_llm_response(self) -> str:
|
||||||
"""Call the LLM and return the response, handling any invalid responses."""
|
"""Call the LLM and return the response, handling any invalid responses."""
|
||||||
answer = self.llm.call(
|
try:
|
||||||
self.messages,
|
answer = self.llm.call(
|
||||||
callbacks=self.callbacks,
|
self.messages,
|
||||||
)
|
callbacks=self.callbacks,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self._printer.print(
|
||||||
|
content=f"Error during LLM call: {e}",
|
||||||
|
color="red",
|
||||||
|
)
|
||||||
|
raise e
|
||||||
|
|
||||||
if not answer:
|
if not answer:
|
||||||
self._printer.print(
|
self._printer.print(
|
||||||
@@ -184,7 +228,6 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
if FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE in e.error:
|
if FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE in e.error:
|
||||||
answer = answer.split("Observation:")[0].strip()
|
answer = answer.split("Observation:")[0].strip()
|
||||||
|
|
||||||
self.iterations += 1
|
|
||||||
return self._format_answer(answer)
|
return self._format_answer(answer)
|
||||||
|
|
||||||
def _handle_agent_action(
|
def _handle_agent_action(
|
||||||
@@ -260,8 +303,11 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
self._printer.print(
|
self._printer.print(
|
||||||
content=f"\033[1m\033[95m# Agent:\033[00m \033[1m\033[92m{agent_role}\033[00m"
|
content=f"\033[1m\033[95m# Agent:\033[00m \033[1m\033[92m{agent_role}\033[00m"
|
||||||
)
|
)
|
||||||
|
description = (
|
||||||
|
getattr(self.task, "description") if self.task else "Not Found"
|
||||||
|
)
|
||||||
self._printer.print(
|
self._printer.print(
|
||||||
content=f"\033[95m## Task:\033[00m \033[92m{self.task.description}\033[00m"
|
content=f"\033[95m## Task:\033[00m \033[92m{description}\033[00m"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _show_logs(self, formatted_answer: Union[AgentAction, AgentFinish]):
|
def _show_logs(self, formatted_answer: Union[AgentAction, AgentFinish]):
|
||||||
@@ -386,58 +432,50 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _handle_crew_training_output(
|
def _handle_crew_training_output(
|
||||||
self, result: AgentFinish, human_feedback: str | None = None
|
self, result: AgentFinish, human_feedback: Optional[str] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Function to handle the process of the training data."""
|
"""Handle the process of saving training data."""
|
||||||
agent_id = str(self.agent.id) # type: ignore
|
agent_id = str(self.agent.id) # type: ignore
|
||||||
|
train_iteration = (
|
||||||
|
getattr(self.crew, "_train_iteration", None) if self.crew else None
|
||||||
|
)
|
||||||
|
|
||||||
|
if train_iteration is None or not isinstance(train_iteration, int):
|
||||||
|
self._printer.print(
|
||||||
|
content="Invalid or missing train iteration. Cannot save training data.",
|
||||||
|
color="red",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
# Load training data
|
|
||||||
training_handler = CrewTrainingHandler(TRAINING_DATA_FILE)
|
training_handler = CrewTrainingHandler(TRAINING_DATA_FILE)
|
||||||
training_data = training_handler.load()
|
training_data = training_handler.load() or {}
|
||||||
|
|
||||||
# Check if training data exists, human input is not requested, and self.crew is valid
|
# Initialize or retrieve agent's training data
|
||||||
if training_data and not self.ask_for_human_input:
|
agent_training_data = training_data.get(agent_id, {})
|
||||||
if self.crew is not None and hasattr(self.crew, "_train_iteration"):
|
|
||||||
train_iteration = self.crew._train_iteration
|
|
||||||
if agent_id in training_data and isinstance(train_iteration, int):
|
|
||||||
training_data[agent_id][train_iteration][
|
|
||||||
"improved_output"
|
|
||||||
] = result.output
|
|
||||||
training_handler.save(training_data)
|
|
||||||
else:
|
|
||||||
self._printer.print(
|
|
||||||
content="Invalid train iteration type or agent_id not in training data.",
|
|
||||||
color="red",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._printer.print(
|
|
||||||
content="Crew is None or does not have _train_iteration attribute.",
|
|
||||||
color="red",
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.ask_for_human_input and human_feedback is not None:
|
if human_feedback is not None:
|
||||||
training_data = {
|
# Save initial output and human feedback
|
||||||
|
agent_training_data[train_iteration] = {
|
||||||
"initial_output": result.output,
|
"initial_output": result.output,
|
||||||
"human_feedback": human_feedback,
|
"human_feedback": human_feedback,
|
||||||
"agent": agent_id,
|
|
||||||
"agent_role": self.agent.role, # type: ignore
|
|
||||||
}
|
}
|
||||||
if self.crew is not None and hasattr(self.crew, "_train_iteration"):
|
else:
|
||||||
train_iteration = self.crew._train_iteration
|
# Save improved output
|
||||||
if isinstance(train_iteration, int):
|
if train_iteration in agent_training_data:
|
||||||
CrewTrainingHandler(TRAINING_DATA_FILE).append(
|
agent_training_data[train_iteration]["improved_output"] = result.output
|
||||||
train_iteration, agent_id, training_data
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._printer.print(
|
|
||||||
content="Invalid train iteration type. Expected int.",
|
|
||||||
color="red",
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self._printer.print(
|
self._printer.print(
|
||||||
content="Crew is None or does not have _train_iteration attribute.",
|
content=(
|
||||||
|
f"No existing training data for agent {agent_id} and iteration "
|
||||||
|
f"{train_iteration}. Cannot save improved output."
|
||||||
|
),
|
||||||
color="red",
|
color="red",
|
||||||
)
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Update the training data and save
|
||||||
|
training_data[agent_id] = agent_training_data
|
||||||
|
training_handler.save(training_data)
|
||||||
|
|
||||||
def _format_prompt(self, prompt: str, inputs: Dict[str, str]) -> str:
|
def _format_prompt(self, prompt: str, inputs: Dict[str, str]) -> str:
|
||||||
prompt = prompt.replace("{input}", inputs["input"])
|
prompt = prompt.replace("{input}", inputs["input"])
|
||||||
@@ -453,82 +491,111 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
|||||||
return {"role": role, "content": prompt}
|
return {"role": role, "content": prompt}
|
||||||
|
|
||||||
def _handle_human_feedback(self, formatted_answer: AgentFinish) -> AgentFinish:
|
def _handle_human_feedback(self, formatted_answer: AgentFinish) -> AgentFinish:
|
||||||
"""
|
"""Handle human feedback with different flows for training vs regular use.
|
||||||
Handles the human feedback loop, allowing the user to provide feedback
|
|
||||||
on the agent's output and determining if additional iterations are needed.
|
|
||||||
|
|
||||||
Parameters:
|
Args:
|
||||||
formatted_answer (AgentFinish): The initial output from the agent.
|
formatted_answer: The initial AgentFinish result to get feedback on
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
AgentFinish: The final output after incorporating human feedback.
|
AgentFinish: The final answer after processing feedback
|
||||||
"""
|
"""
|
||||||
|
human_feedback = self._ask_human_input(formatted_answer.output)
|
||||||
|
|
||||||
|
if self._is_training_mode():
|
||||||
|
return self._handle_training_feedback(formatted_answer, human_feedback)
|
||||||
|
|
||||||
|
return self._handle_regular_feedback(formatted_answer, human_feedback)
|
||||||
|
|
||||||
|
def _is_training_mode(self) -> bool:
|
||||||
|
"""Check if crew is in training mode."""
|
||||||
|
return bool(self.crew and self.crew._train)
|
||||||
|
|
||||||
|
def _handle_training_feedback(
|
||||||
|
self, initial_answer: AgentFinish, feedback: str
|
||||||
|
) -> AgentFinish:
|
||||||
|
"""Process feedback for training scenarios with single iteration."""
|
||||||
|
self._printer.print(
|
||||||
|
content="\nProcessing training feedback.\n",
|
||||||
|
color="yellow",
|
||||||
|
)
|
||||||
|
self._handle_crew_training_output(initial_answer, feedback)
|
||||||
|
self.messages.append(
|
||||||
|
self._format_msg(
|
||||||
|
self._i18n.slice("feedback_instructions").format(feedback=feedback)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
improved_answer = self._invoke_loop()
|
||||||
|
self._handle_crew_training_output(improved_answer)
|
||||||
|
self.ask_for_human_input = False
|
||||||
|
return improved_answer
|
||||||
|
|
||||||
|
def _handle_regular_feedback(
|
||||||
|
self, current_answer: AgentFinish, initial_feedback: str
|
||||||
|
) -> AgentFinish:
|
||||||
|
"""Process feedback for regular use with potential multiple iterations."""
|
||||||
|
feedback = initial_feedback
|
||||||
|
answer = current_answer
|
||||||
|
|
||||||
while self.ask_for_human_input:
|
while self.ask_for_human_input:
|
||||||
human_feedback = self._ask_human_input(formatted_answer.output)
|
response = self._get_llm_feedback_response(feedback)
|
||||||
|
|
||||||
if self.crew and self.crew._train:
|
if not self._feedback_requires_changes(response):
|
||||||
self._handle_crew_training_output(formatted_answer, human_feedback)
|
|
||||||
|
|
||||||
# Make an LLM call to verify if additional changes are requested based on human feedback
|
|
||||||
additional_changes_prompt = self._i18n.slice(
|
|
||||||
"human_feedback_classification"
|
|
||||||
).format(feedback=human_feedback)
|
|
||||||
|
|
||||||
retry_count = 0
|
|
||||||
llm_call_successful = False
|
|
||||||
additional_changes_response = None
|
|
||||||
|
|
||||||
while retry_count < MAX_LLM_RETRY and not llm_call_successful:
|
|
||||||
try:
|
|
||||||
additional_changes_response = (
|
|
||||||
self.llm.call(
|
|
||||||
[
|
|
||||||
self._format_msg(
|
|
||||||
additional_changes_prompt, role="system"
|
|
||||||
)
|
|
||||||
],
|
|
||||||
callbacks=self.callbacks,
|
|
||||||
)
|
|
||||||
.strip()
|
|
||||||
.lower()
|
|
||||||
)
|
|
||||||
llm_call_successful = True
|
|
||||||
except Exception as e:
|
|
||||||
retry_count += 1
|
|
||||||
|
|
||||||
self._printer.print(
|
|
||||||
content=f"Error during LLM call to classify human feedback: {e}. Retrying... ({retry_count}/{MAX_LLM_RETRY})",
|
|
||||||
color="red",
|
|
||||||
)
|
|
||||||
|
|
||||||
if not llm_call_successful:
|
|
||||||
self._printer.print(
|
|
||||||
content="Error processing feedback after multiple attempts.",
|
|
||||||
color="red",
|
|
||||||
)
|
|
||||||
self.ask_for_human_input = False
|
self.ask_for_human_input = False
|
||||||
break
|
|
||||||
|
|
||||||
if additional_changes_response == "false":
|
|
||||||
self.ask_for_human_input = False
|
|
||||||
elif additional_changes_response == "true":
|
|
||||||
self.ask_for_human_input = True
|
|
||||||
# Add human feedback to messages
|
|
||||||
self.messages.append(self._format_msg(f"Feedback: {human_feedback}"))
|
|
||||||
# Invoke the loop again with updated messages
|
|
||||||
formatted_answer = self._invoke_loop()
|
|
||||||
|
|
||||||
if self.crew and self.crew._train:
|
|
||||||
self._handle_crew_training_output(formatted_answer)
|
|
||||||
else:
|
else:
|
||||||
# Unexpected response
|
answer = self._process_feedback_iteration(feedback)
|
||||||
self._printer.print(
|
feedback = self._ask_human_input(answer.output)
|
||||||
content=f"Unexpected response from LLM: '{additional_changes_response}'. Assuming no additional changes requested.",
|
|
||||||
color="red",
|
|
||||||
)
|
|
||||||
self.ask_for_human_input = False
|
|
||||||
|
|
||||||
return formatted_answer
|
return answer
|
||||||
|
|
||||||
|
def _get_llm_feedback_response(self, feedback: str) -> Optional[str]:
|
||||||
|
"""Get LLM classification of whether feedback requires changes."""
|
||||||
|
prompt = self._i18n.slice("human_feedback_classification").format(
|
||||||
|
feedback=feedback
|
||||||
|
)
|
||||||
|
message = self._format_msg(prompt, role="system")
|
||||||
|
|
||||||
|
for retry in range(MAX_LLM_RETRY):
|
||||||
|
try:
|
||||||
|
response = self.llm.call([message], callbacks=self.callbacks)
|
||||||
|
return response.strip().lower() if response else None
|
||||||
|
except Exception as error:
|
||||||
|
self._log_feedback_error(retry, error)
|
||||||
|
|
||||||
|
self._log_max_retries_exceeded()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _feedback_requires_changes(self, response: Optional[str]) -> bool:
|
||||||
|
"""Determine if feedback response indicates need for changes."""
|
||||||
|
return response == "true" if response else False
|
||||||
|
|
||||||
|
def _process_feedback_iteration(self, feedback: str) -> AgentFinish:
|
||||||
|
"""Process a single feedback iteration."""
|
||||||
|
self.messages.append(
|
||||||
|
self._format_msg(
|
||||||
|
self._i18n.slice("feedback_instructions").format(feedback=feedback)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return self._invoke_loop()
|
||||||
|
|
||||||
|
def _log_feedback_error(self, retry_count: int, error: Exception) -> None:
|
||||||
|
"""Log feedback processing errors."""
|
||||||
|
self._printer.print(
|
||||||
|
content=(
|
||||||
|
f"Error processing feedback: {error}. "
|
||||||
|
f"Retrying... ({retry_count + 1}/{MAX_LLM_RETRY})"
|
||||||
|
),
|
||||||
|
color="red",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _log_max_retries_exceeded(self) -> None:
|
||||||
|
"""Log when max retries for feedback processing are exceeded."""
|
||||||
|
self._printer.print(
|
||||||
|
content=(
|
||||||
|
f"Failed to process feedback after {MAX_LLM_RETRY} attempts. "
|
||||||
|
"Ending feedback loop."
|
||||||
|
),
|
||||||
|
color="red",
|
||||||
|
)
|
||||||
|
|
||||||
def _handle_max_iterations_exceeded(self, formatted_answer):
|
def _handle_max_iterations_exceeded(self, formatted_answer):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -350,7 +350,10 @@ def chat():
|
|||||||
Start a conversation with the Crew, collecting user-supplied inputs,
|
Start a conversation with the Crew, collecting user-supplied inputs,
|
||||||
and using the Chat LLM to generate responses.
|
and using the Chat LLM to generate responses.
|
||||||
"""
|
"""
|
||||||
click.echo("Starting a conversation with the Crew")
|
click.secho(
|
||||||
|
"\nStarting a conversation with the Crew\n" "Type 'exit' or Ctrl+C to quit.\n",
|
||||||
|
)
|
||||||
|
|
||||||
run_chat()
|
run_chat()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,52 @@
|
|||||||
import json
|
import json
|
||||||
|
import platform
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import tomli
|
import tomli
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
from crewai.cli.utils import read_toml
|
||||||
|
from crewai.cli.version import get_crewai_version
|
||||||
from crewai.crew import Crew
|
from crewai.crew import Crew
|
||||||
from crewai.llm import LLM
|
from crewai.llm import LLM
|
||||||
from crewai.types.crew_chat import ChatInputField, ChatInputs
|
from crewai.types.crew_chat import ChatInputField, ChatInputs
|
||||||
from crewai.utilities.llm_utils import create_llm
|
from crewai.utilities.llm_utils import create_llm
|
||||||
|
|
||||||
|
MIN_REQUIRED_VERSION = "0.98.0"
|
||||||
|
|
||||||
|
|
||||||
|
def check_conversational_crews_version(
|
||||||
|
crewai_version: str, pyproject_data: dict
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the installed crewAI version supports conversational crews.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
crewai_version: The current version of crewAI.
|
||||||
|
pyproject_data: Dictionary containing pyproject.toml data.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if version check passes, False otherwise.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if version.parse(crewai_version) < version.parse(MIN_REQUIRED_VERSION):
|
||||||
|
click.secho(
|
||||||
|
"You are using an older version of crewAI that doesn't support conversational crews. "
|
||||||
|
"Run 'uv upgrade crewai' to get the latest version.",
|
||||||
|
fg="red",
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
except version.InvalidVersion:
|
||||||
|
click.secho("Invalid crewAI version format detected.", fg="red")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def run_chat():
|
def run_chat():
|
||||||
"""
|
"""
|
||||||
@@ -19,20 +54,47 @@ def run_chat():
|
|||||||
Incorporates crew_name, crew_description, and input fields to build a tool schema.
|
Incorporates crew_name, crew_description, and input fields to build a tool schema.
|
||||||
Exits if crew_name or crew_description are missing.
|
Exits if crew_name or crew_description are missing.
|
||||||
"""
|
"""
|
||||||
|
crewai_version = get_crewai_version()
|
||||||
|
pyproject_data = read_toml()
|
||||||
|
|
||||||
|
if not check_conversational_crews_version(crewai_version, pyproject_data):
|
||||||
|
return
|
||||||
|
|
||||||
crew, crew_name = load_crew_and_name()
|
crew, crew_name = load_crew_and_name()
|
||||||
chat_llm = initialize_chat_llm(crew)
|
chat_llm = initialize_chat_llm(crew)
|
||||||
if not chat_llm:
|
if not chat_llm:
|
||||||
return
|
return
|
||||||
|
|
||||||
crew_chat_inputs = generate_crew_chat_inputs(crew, crew_name, chat_llm)
|
# Indicate that the crew is being analyzed
|
||||||
crew_tool_schema = generate_crew_tool_schema(crew_chat_inputs)
|
click.secho(
|
||||||
system_message = build_system_message(crew_chat_inputs)
|
"\nAnalyzing crew and required inputs - this may take 3 to 30 seconds "
|
||||||
|
"depending on the complexity of your crew.",
|
||||||
# Call the LLM to generate the introductory message
|
fg="white",
|
||||||
introductory_message = chat_llm.call(
|
|
||||||
messages=[{"role": "system", "content": system_message}]
|
|
||||||
)
|
)
|
||||||
click.secho(f"\nAssistant: {introductory_message}\n", fg="green")
|
|
||||||
|
# Start loading indicator
|
||||||
|
loading_complete = threading.Event()
|
||||||
|
loading_thread = threading.Thread(target=show_loading, args=(loading_complete,))
|
||||||
|
loading_thread.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
crew_chat_inputs = generate_crew_chat_inputs(crew, crew_name, chat_llm)
|
||||||
|
crew_tool_schema = generate_crew_tool_schema(crew_chat_inputs)
|
||||||
|
system_message = build_system_message(crew_chat_inputs)
|
||||||
|
|
||||||
|
# Call the LLM to generate the introductory message
|
||||||
|
introductory_message = chat_llm.call(
|
||||||
|
messages=[{"role": "system", "content": system_message}]
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
# Stop loading indicator
|
||||||
|
loading_complete.set()
|
||||||
|
loading_thread.join()
|
||||||
|
|
||||||
|
# Indicate that the analysis is complete
|
||||||
|
click.secho("\nFinished analyzing crew.\n", fg="white")
|
||||||
|
|
||||||
|
click.secho(f"Assistant: {introductory_message}\n", fg="green")
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{"role": "system", "content": system_message},
|
{"role": "system", "content": system_message},
|
||||||
@@ -43,15 +105,17 @@ def run_chat():
|
|||||||
crew_chat_inputs.crew_name: create_tool_function(crew, messages),
|
crew_chat_inputs.crew_name: create_tool_function(crew, messages),
|
||||||
}
|
}
|
||||||
|
|
||||||
click.secho(
|
|
||||||
"\nEntering an interactive chat loop with function-calling.\n"
|
|
||||||
"Type 'exit' or Ctrl+C to quit.\n",
|
|
||||||
fg="cyan",
|
|
||||||
)
|
|
||||||
|
|
||||||
chat_loop(chat_llm, messages, crew_tool_schema, available_functions)
|
chat_loop(chat_llm, messages, crew_tool_schema, available_functions)
|
||||||
|
|
||||||
|
|
||||||
|
def show_loading(event: threading.Event):
|
||||||
|
"""Display animated loading dots while processing."""
|
||||||
|
while not event.is_set():
|
||||||
|
print(".", end="", flush=True)
|
||||||
|
time.sleep(1)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
def initialize_chat_llm(crew: Crew) -> Optional[LLM]:
|
def initialize_chat_llm(crew: Crew) -> Optional[LLM]:
|
||||||
"""Initializes the chat LLM and handles exceptions."""
|
"""Initializes the chat LLM and handles exceptions."""
|
||||||
try:
|
try:
|
||||||
@@ -85,7 +149,7 @@ def build_system_message(crew_chat_inputs: ChatInputs) -> str:
|
|||||||
"Please keep your responses concise and friendly. "
|
"Please keep your responses concise and friendly. "
|
||||||
"If a user asks a question outside the crew's scope, provide a brief answer and remind them of the crew's purpose. "
|
"If a user asks a question outside the crew's scope, provide a brief answer and remind them of the crew's purpose. "
|
||||||
"After calling the tool, be prepared to take user feedback and make adjustments as needed. "
|
"After calling the tool, be prepared to take user feedback and make adjustments as needed. "
|
||||||
"If you are ever unsure about a user's request or need clarification, ask the user for more information."
|
"If you are ever unsure about a user's request or need clarification, ask the user for more information. "
|
||||||
"Before doing anything else, introduce yourself with a friendly message like: 'Hey! I'm here to help you with [crew's purpose]. Could you please provide me with [inputs] so we can get started?' "
|
"Before doing anything else, introduce yourself with a friendly message like: 'Hey! I'm here to help you with [crew's purpose]. Could you please provide me with [inputs] so we can get started?' "
|
||||||
"For example: 'Hey! I'm here to help you with uncovering and reporting cutting-edge developments through thorough research and detailed analysis. Could you please provide me with a topic you're interested in? This will help us generate a comprehensive research report and detailed analysis.'"
|
"For example: 'Hey! I'm here to help you with uncovering and reporting cutting-edge developments through thorough research and detailed analysis. Could you please provide me with a topic you're interested in? This will help us generate a comprehensive research report and detailed analysis.'"
|
||||||
f"\nCrew Name: {crew_chat_inputs.crew_name}"
|
f"\nCrew Name: {crew_chat_inputs.crew_name}"
|
||||||
@@ -102,25 +166,33 @@ def create_tool_function(crew: Crew, messages: List[Dict[str, str]]) -> Any:
|
|||||||
return run_crew_tool_with_messages
|
return run_crew_tool_with_messages
|
||||||
|
|
||||||
|
|
||||||
|
def flush_input():
|
||||||
|
"""Flush any pending input from the user."""
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
# Windows platform
|
||||||
|
import msvcrt
|
||||||
|
|
||||||
|
while msvcrt.kbhit():
|
||||||
|
msvcrt.getch()
|
||||||
|
else:
|
||||||
|
# Unix-like platforms (Linux, macOS)
|
||||||
|
import termios
|
||||||
|
|
||||||
|
termios.tcflush(sys.stdin, termios.TCIFLUSH)
|
||||||
|
|
||||||
|
|
||||||
def chat_loop(chat_llm, messages, crew_tool_schema, available_functions):
|
def chat_loop(chat_llm, messages, crew_tool_schema, available_functions):
|
||||||
"""Main chat loop for interacting with the user."""
|
"""Main chat loop for interacting with the user."""
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
user_input = click.prompt("You", type=str)
|
# Flush any pending input before accepting new input
|
||||||
if user_input.strip().lower() in ["exit", "quit"]:
|
flush_input()
|
||||||
click.echo("Exiting chat. Goodbye!")
|
|
||||||
break
|
|
||||||
|
|
||||||
messages.append({"role": "user", "content": user_input})
|
user_input = get_user_input()
|
||||||
final_response = chat_llm.call(
|
handle_user_input(
|
||||||
messages=messages,
|
user_input, chat_llm, messages, crew_tool_schema, available_functions
|
||||||
tools=[crew_tool_schema],
|
|
||||||
available_functions=available_functions,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
messages.append({"role": "assistant", "content": final_response})
|
|
||||||
click.secho(f"\nAssistant: {final_response}\n", fg="green")
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
click.echo("\nExiting chat. Goodbye!")
|
click.echo("\nExiting chat. Goodbye!")
|
||||||
break
|
break
|
||||||
@@ -129,6 +201,55 @@ def chat_loop(chat_llm, messages, crew_tool_schema, available_functions):
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_input() -> str:
|
||||||
|
"""Collect multi-line user input with exit handling."""
|
||||||
|
click.secho(
|
||||||
|
"\nYou (type your message below. Press 'Enter' twice when you're done):",
|
||||||
|
fg="blue",
|
||||||
|
)
|
||||||
|
user_input_lines = []
|
||||||
|
while True:
|
||||||
|
line = input()
|
||||||
|
if line.strip().lower() == "exit":
|
||||||
|
return "exit"
|
||||||
|
if line == "":
|
||||||
|
break
|
||||||
|
user_input_lines.append(line)
|
||||||
|
return "\n".join(user_input_lines)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_user_input(
|
||||||
|
user_input: str,
|
||||||
|
chat_llm: LLM,
|
||||||
|
messages: List[Dict[str, str]],
|
||||||
|
crew_tool_schema: Dict[str, Any],
|
||||||
|
available_functions: Dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
if user_input.strip().lower() == "exit":
|
||||||
|
click.echo("Exiting chat. Goodbye!")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not user_input.strip():
|
||||||
|
click.echo("Empty message. Please provide input or type 'exit' to quit.")
|
||||||
|
return
|
||||||
|
|
||||||
|
messages.append({"role": "user", "content": user_input})
|
||||||
|
|
||||||
|
# Indicate that assistant is processing
|
||||||
|
click.echo()
|
||||||
|
click.secho("Assistant is processing your input. Please wait...", fg="green")
|
||||||
|
|
||||||
|
# Process assistant's response
|
||||||
|
final_response = chat_llm.call(
|
||||||
|
messages=messages,
|
||||||
|
tools=[crew_tool_schema],
|
||||||
|
available_functions=available_functions,
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.append({"role": "assistant", "content": final_response})
|
||||||
|
click.secho(f"\nAssistant: {final_response}\n", fg="green")
|
||||||
|
|
||||||
|
|
||||||
def generate_crew_tool_schema(crew_inputs: ChatInputs) -> dict:
|
def generate_crew_tool_schema(crew_inputs: ChatInputs) -> dict:
|
||||||
"""
|
"""
|
||||||
Dynamically build a Littellm 'function' schema for the given crew.
|
Dynamically build a Littellm 'function' schema for the given crew.
|
||||||
@@ -323,10 +444,10 @@ def generate_input_description_with_ai(input_name: str, crew: Crew, chat_llm) ->
|
|||||||
):
|
):
|
||||||
# Replace placeholders with input names
|
# Replace placeholders with input names
|
||||||
task_description = placeholder_pattern.sub(
|
task_description = placeholder_pattern.sub(
|
||||||
lambda m: m.group(1), task.description
|
lambda m: m.group(1), task.description or ""
|
||||||
)
|
)
|
||||||
expected_output = placeholder_pattern.sub(
|
expected_output = placeholder_pattern.sub(
|
||||||
lambda m: m.group(1), task.expected_output
|
lambda m: m.group(1), task.expected_output or ""
|
||||||
)
|
)
|
||||||
context_texts.append(f"Task Description: {task_description}")
|
context_texts.append(f"Task Description: {task_description}")
|
||||||
context_texts.append(f"Expected Output: {expected_output}")
|
context_texts.append(f"Expected Output: {expected_output}")
|
||||||
@@ -337,10 +458,10 @@ def generate_input_description_with_ai(input_name: str, crew: Crew, chat_llm) ->
|
|||||||
or f"{{{input_name}}}" in agent.backstory
|
or f"{{{input_name}}}" in agent.backstory
|
||||||
):
|
):
|
||||||
# Replace placeholders with input names
|
# Replace placeholders with input names
|
||||||
agent_role = placeholder_pattern.sub(lambda m: m.group(1), agent.role)
|
agent_role = placeholder_pattern.sub(lambda m: m.group(1), agent.role or "")
|
||||||
agent_goal = placeholder_pattern.sub(lambda m: m.group(1), agent.goal)
|
agent_goal = placeholder_pattern.sub(lambda m: m.group(1), agent.goal or "")
|
||||||
agent_backstory = placeholder_pattern.sub(
|
agent_backstory = placeholder_pattern.sub(
|
||||||
lambda m: m.group(1), agent.backstory
|
lambda m: m.group(1), agent.backstory or ""
|
||||||
)
|
)
|
||||||
context_texts.append(f"Agent Role: {agent_role}")
|
context_texts.append(f"Agent Role: {agent_role}")
|
||||||
context_texts.append(f"Agent Goal: {agent_goal}")
|
context_texts.append(f"Agent Goal: {agent_goal}")
|
||||||
@@ -381,18 +502,20 @@ def generate_crew_description_with_ai(crew: Crew, chat_llm) -> str:
|
|||||||
for task in crew.tasks:
|
for task in crew.tasks:
|
||||||
# Replace placeholders with input names
|
# Replace placeholders with input names
|
||||||
task_description = placeholder_pattern.sub(
|
task_description = placeholder_pattern.sub(
|
||||||
lambda m: m.group(1), task.description
|
lambda m: m.group(1), task.description or ""
|
||||||
)
|
)
|
||||||
expected_output = placeholder_pattern.sub(
|
expected_output = placeholder_pattern.sub(
|
||||||
lambda m: m.group(1), task.expected_output
|
lambda m: m.group(1), task.expected_output or ""
|
||||||
)
|
)
|
||||||
context_texts.append(f"Task Description: {task_description}")
|
context_texts.append(f"Task Description: {task_description}")
|
||||||
context_texts.append(f"Expected Output: {expected_output}")
|
context_texts.append(f"Expected Output: {expected_output}")
|
||||||
for agent in crew.agents:
|
for agent in crew.agents:
|
||||||
# Replace placeholders with input names
|
# Replace placeholders with input names
|
||||||
agent_role = placeholder_pattern.sub(lambda m: m.group(1), agent.role)
|
agent_role = placeholder_pattern.sub(lambda m: m.group(1), agent.role or "")
|
||||||
agent_goal = placeholder_pattern.sub(lambda m: m.group(1), agent.goal)
|
agent_goal = placeholder_pattern.sub(lambda m: m.group(1), agent.goal or "")
|
||||||
agent_backstory = placeholder_pattern.sub(lambda m: m.group(1), agent.backstory)
|
agent_backstory = placeholder_pattern.sub(
|
||||||
|
lambda m: m.group(1), agent.backstory or ""
|
||||||
|
)
|
||||||
context_texts.append(f"Agent Role: {agent_role}")
|
context_texts.append(f"Agent Role: {agent_role}")
|
||||||
context_texts.append(f"Agent Goal: {agent_goal}")
|
context_texts.append(f"Agent Goal: {agent_goal}")
|
||||||
context_texts.append(f"Agent Backstory: {agent_backstory}")
|
context_texts.append(f"Agent Backstory: {agent_backstory}")
|
||||||
|
|||||||
1
src/crewai/cli/templates/crew/.gitignore
vendored
1
src/crewai/cli/templates/crew/.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.env
|
.env
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
|||||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||||
requires-python = ">=3.10,<3.13"
|
requires-python = ">=3.10,<3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crewai[tools]>=0.98.0,<1.0.0"
|
"crewai[tools]>=0.100.0,<1.0.0"
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
1
src/crewai/cli/templates/flow/.gitignore
vendored
1
src/crewai/cli/templates/flow/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
.env
|
.env
|
||||||
__pycache__/
|
__pycache__/
|
||||||
lib/
|
lib/
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
|||||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||||
requires-python = ">=3.10,<3.13"
|
requires-python = ">=3.10,<3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crewai[tools]>=0.98.0,<1.0.0",
|
"crewai[tools]>=0.100.0,<1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description = "Power up your crews with {{folder_name}}"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10,<3.13"
|
requires-python = ">=3.10,<3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crewai[tools]>=0.98.0"
|
"crewai[tools]>=0.100.0"
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.crewai]
|
[tool.crewai]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import re
|
|||||||
import uuid
|
import uuid
|
||||||
import warnings
|
import warnings
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
|
from copy import copy as shallow_copy
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
@@ -210,8 +211,9 @@ class Crew(BaseModel):
|
|||||||
default=None,
|
default=None,
|
||||||
description="LLM used to handle chatting with the crew.",
|
description="LLM used to handle chatting with the crew.",
|
||||||
)
|
)
|
||||||
_knowledge: Optional[Knowledge] = PrivateAttr(
|
knowledge: Optional[Knowledge] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
|
description="Knowledge for the crew.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@field_validator("id", mode="before")
|
@field_validator("id", mode="before")
|
||||||
@@ -289,7 +291,7 @@ class Crew(BaseModel):
|
|||||||
if isinstance(self.knowledge_sources, list) and all(
|
if isinstance(self.knowledge_sources, list) and all(
|
||||||
isinstance(k, BaseKnowledgeSource) for k in self.knowledge_sources
|
isinstance(k, BaseKnowledgeSource) for k in self.knowledge_sources
|
||||||
):
|
):
|
||||||
self._knowledge = Knowledge(
|
self.knowledge = Knowledge(
|
||||||
sources=self.knowledge_sources,
|
sources=self.knowledge_sources,
|
||||||
embedder_config=self.embedder,
|
embedder_config=self.embedder,
|
||||||
collection_name="crew",
|
collection_name="crew",
|
||||||
@@ -492,21 +494,26 @@ class Crew(BaseModel):
|
|||||||
train_crew = self.copy()
|
train_crew = self.copy()
|
||||||
train_crew._setup_for_training(filename)
|
train_crew._setup_for_training(filename)
|
||||||
|
|
||||||
for n_iteration in range(n_iterations):
|
try:
|
||||||
train_crew._train_iteration = n_iteration
|
for n_iteration in range(n_iterations):
|
||||||
train_crew.kickoff(inputs=inputs)
|
train_crew._train_iteration = n_iteration
|
||||||
|
train_crew.kickoff(inputs=inputs)
|
||||||
|
|
||||||
training_data = CrewTrainingHandler(TRAINING_DATA_FILE).load()
|
training_data = CrewTrainingHandler(TRAINING_DATA_FILE).load()
|
||||||
|
|
||||||
for agent in train_crew.agents:
|
for agent in train_crew.agents:
|
||||||
if training_data.get(str(agent.id)):
|
if training_data.get(str(agent.id)):
|
||||||
result = TaskEvaluator(agent).evaluate_training_data(
|
result = TaskEvaluator(agent).evaluate_training_data(
|
||||||
training_data=training_data, agent_id=str(agent.id)
|
training_data=training_data, agent_id=str(agent.id)
|
||||||
)
|
)
|
||||||
|
CrewTrainingHandler(filename).save_trained_data(
|
||||||
CrewTrainingHandler(filename).save_trained_data(
|
agent_id=str(agent.role), trained_data=result.model_dump()
|
||||||
agent_id=str(agent.role), trained_data=result.model_dump()
|
)
|
||||||
)
|
except Exception as e:
|
||||||
|
self._logger.log("error", f"Training failed: {e}", color="red")
|
||||||
|
CrewTrainingHandler(TRAINING_DATA_FILE).clear()
|
||||||
|
CrewTrainingHandler(filename).clear()
|
||||||
|
raise
|
||||||
|
|
||||||
def kickoff(
|
def kickoff(
|
||||||
self,
|
self,
|
||||||
@@ -991,8 +998,8 @@ class Crew(BaseModel):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def query_knowledge(self, query: List[str]) -> Union[List[Dict[str, Any]], None]:
|
def query_knowledge(self, query: List[str]) -> Union[List[Dict[str, Any]], None]:
|
||||||
if self._knowledge:
|
if self.knowledge:
|
||||||
return self._knowledge.query(query)
|
return self.knowledge.query(query)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def fetch_inputs(self) -> Set[str]:
|
def fetch_inputs(self) -> Set[str]:
|
||||||
@@ -1036,6 +1043,8 @@ class Crew(BaseModel):
|
|||||||
"_telemetry",
|
"_telemetry",
|
||||||
"agents",
|
"agents",
|
||||||
"tasks",
|
"tasks",
|
||||||
|
"knowledge_sources",
|
||||||
|
"knowledge",
|
||||||
}
|
}
|
||||||
|
|
||||||
cloned_agents = [agent.copy() for agent in self.agents]
|
cloned_agents = [agent.copy() for agent in self.agents]
|
||||||
@@ -1043,6 +1052,9 @@ class Crew(BaseModel):
|
|||||||
task_mapping = {}
|
task_mapping = {}
|
||||||
|
|
||||||
cloned_tasks = []
|
cloned_tasks = []
|
||||||
|
existing_knowledge_sources = shallow_copy(self.knowledge_sources)
|
||||||
|
existing_knowledge = shallow_copy(self.knowledge)
|
||||||
|
|
||||||
for task in self.tasks:
|
for task in self.tasks:
|
||||||
cloned_task = task.copy(cloned_agents, task_mapping)
|
cloned_task = task.copy(cloned_agents, task_mapping)
|
||||||
cloned_tasks.append(cloned_task)
|
cloned_tasks.append(cloned_task)
|
||||||
@@ -1062,7 +1074,13 @@ class Crew(BaseModel):
|
|||||||
copied_data.pop("agents", None)
|
copied_data.pop("agents", None)
|
||||||
copied_data.pop("tasks", None)
|
copied_data.pop("tasks", None)
|
||||||
|
|
||||||
copied_crew = Crew(**copied_data, agents=cloned_agents, tasks=cloned_tasks)
|
copied_crew = Crew(
|
||||||
|
**copied_data,
|
||||||
|
agents=cloned_agents,
|
||||||
|
tasks=cloned_tasks,
|
||||||
|
knowledge_sources=existing_knowledge_sources,
|
||||||
|
knowledge=existing_knowledge,
|
||||||
|
)
|
||||||
|
|
||||||
return copied_crew
|
return copied_crew
|
||||||
|
|
||||||
|
|||||||
@@ -15,20 +15,20 @@ class Knowledge(BaseModel):
|
|||||||
Args:
|
Args:
|
||||||
sources: List[BaseKnowledgeSource] = Field(default_factory=list)
|
sources: List[BaseKnowledgeSource] = Field(default_factory=list)
|
||||||
storage: Optional[KnowledgeStorage] = Field(default=None)
|
storage: Optional[KnowledgeStorage] = Field(default=None)
|
||||||
embedder_config: Optional[Dict[str, Any]] = None
|
embedder: Optional[Dict[str, Any]] = None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sources: List[BaseKnowledgeSource] = Field(default_factory=list)
|
sources: List[BaseKnowledgeSource] = Field(default_factory=list)
|
||||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||||
storage: Optional[KnowledgeStorage] = Field(default=None)
|
storage: Optional[KnowledgeStorage] = Field(default=None)
|
||||||
embedder_config: Optional[Dict[str, Any]] = None
|
embedder: Optional[Dict[str, Any]] = None
|
||||||
collection_name: Optional[str] = None
|
collection_name: Optional[str] = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
collection_name: str,
|
collection_name: str,
|
||||||
sources: List[BaseKnowledgeSource],
|
sources: List[BaseKnowledgeSource],
|
||||||
embedder_config: Optional[Dict[str, Any]] = None,
|
embedder: Optional[Dict[str, Any]] = None,
|
||||||
storage: Optional[KnowledgeStorage] = None,
|
storage: Optional[KnowledgeStorage] = None,
|
||||||
**data,
|
**data,
|
||||||
):
|
):
|
||||||
@@ -37,25 +37,23 @@ class Knowledge(BaseModel):
|
|||||||
self.storage = storage
|
self.storage = storage
|
||||||
else:
|
else:
|
||||||
self.storage = KnowledgeStorage(
|
self.storage = KnowledgeStorage(
|
||||||
embedder_config=embedder_config, collection_name=collection_name
|
embedder=embedder, collection_name=collection_name
|
||||||
)
|
)
|
||||||
self.sources = sources
|
self.sources = sources
|
||||||
self.storage.initialize_knowledge_storage()
|
self.storage.initialize_knowledge_storage()
|
||||||
for source in sources:
|
self._add_sources()
|
||||||
source.storage = self.storage
|
|
||||||
source.add()
|
|
||||||
|
|
||||||
def query(self, query: List[str], limit: int = 3) -> List[Dict[str, Any]]:
|
def query(self, query: List[str], limit: int = 3) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Query across all knowledge sources to find the most relevant information.
|
Query across all knowledge sources to find the most relevant information.
|
||||||
Returns the top_k most relevant chunks.
|
Returns the top_k most relevant chunks.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If storage is not initialized.
|
ValueError: If storage is not initialized.
|
||||||
"""
|
"""
|
||||||
if self.storage is None:
|
if self.storage is None:
|
||||||
raise ValueError("Storage is not initialized.")
|
raise ValueError("Storage is not initialized.")
|
||||||
|
|
||||||
results = self.storage.search(
|
results = self.storage.search(
|
||||||
query,
|
query,
|
||||||
limit,
|
limit,
|
||||||
@@ -63,6 +61,9 @@ class Knowledge(BaseModel):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
def _add_sources(self):
|
def _add_sources(self):
|
||||||
for source in self.sources:
|
try:
|
||||||
source.storage = self.storage
|
for source in self.sources:
|
||||||
source.add()
|
source.storage = self.storage
|
||||||
|
source.add()
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ class BaseFileKnowledgeSource(BaseKnowledgeSource, ABC):
|
|||||||
def validate_file_path(cls, v, info):
|
def validate_file_path(cls, v, info):
|
||||||
"""Validate that at least one of file_path or file_paths is provided."""
|
"""Validate that at least one of file_path or file_paths is provided."""
|
||||||
# Single check if both are None, O(1) instead of nested conditions
|
# Single check if both are None, O(1) instead of nested conditions
|
||||||
if v is None and info.data.get("file_path" if info.field_name == "file_paths" else "file_paths") is None:
|
if (
|
||||||
|
v is None
|
||||||
|
and info.data.get(
|
||||||
|
"file_path" if info.field_name == "file_paths" else "file_paths"
|
||||||
|
)
|
||||||
|
is None
|
||||||
|
):
|
||||||
raise ValueError("Either file_path or file_paths must be provided")
|
raise ValueError("Either file_path or file_paths must be provided")
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|||||||
@@ -48,11 +48,11 @@ class KnowledgeStorage(BaseKnowledgeStorage):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
embedder_config: Optional[Dict[str, Any]] = None,
|
embedder: Optional[Dict[str, Any]] = None,
|
||||||
collection_name: Optional[str] = None,
|
collection_name: Optional[str] = None,
|
||||||
):
|
):
|
||||||
self.collection_name = collection_name
|
self.collection_name = collection_name
|
||||||
self._set_embedder_config(embedder_config)
|
self._set_embedder_config(embedder)
|
||||||
|
|
||||||
def search(
|
def search(
|
||||||
self,
|
self,
|
||||||
@@ -99,7 +99,7 @@ class KnowledgeStorage(BaseKnowledgeStorage):
|
|||||||
)
|
)
|
||||||
if self.app:
|
if self.app:
|
||||||
self.collection = self.app.get_or_create_collection(
|
self.collection = self.app.get_or_create_collection(
|
||||||
name=collection_name, embedding_function=self.embedder_config
|
name=collection_name, embedding_function=self.embedder
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise Exception("Vector Database Client not initialized")
|
raise Exception("Vector Database Client not initialized")
|
||||||
@@ -187,17 +187,15 @@ class KnowledgeStorage(BaseKnowledgeStorage):
|
|||||||
api_key=os.getenv("OPENAI_API_KEY"), model_name="text-embedding-3-small"
|
api_key=os.getenv("OPENAI_API_KEY"), model_name="text-embedding-3-small"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _set_embedder_config(
|
def _set_embedder_config(self, embedder: Optional[Dict[str, Any]] = None) -> None:
|
||||||
self, embedder_config: Optional[Dict[str, Any]] = None
|
|
||||||
) -> None:
|
|
||||||
"""Set the embedding configuration for the knowledge storage.
|
"""Set the embedding configuration for the knowledge storage.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
embedder_config (Optional[Dict[str, Any]]): Configuration dictionary for the embedder.
|
embedder_config (Optional[Dict[str, Any]]): Configuration dictionary for the embedder.
|
||||||
If None or empty, defaults to the default embedding function.
|
If None or empty, defaults to the default embedding function.
|
||||||
"""
|
"""
|
||||||
self.embedder_config = (
|
self.embedder = (
|
||||||
EmbeddingConfigurator().configure_embedder(embedder_config)
|
EmbeddingConfigurator().configure_embedder(embedder)
|
||||||
if embedder_config
|
if embedder
|
||||||
else self._create_default_embedding_function()
|
else self._create_default_embedding_function()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import warnings
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import Any, Dict, List, Optional, Union, cast
|
from typing import Any, Dict, List, Optional, Union, cast
|
||||||
|
|
||||||
|
import instructor
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
from openai.types.chat import ChatCompletionMessageParam
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
warnings.simplefilter("ignore", UserWarning)
|
warnings.simplefilter("ignore", UserWarning)
|
||||||
@@ -133,16 +136,17 @@ class LLM:
|
|||||||
logprobs: Optional[int] = None,
|
logprobs: Optional[int] = None,
|
||||||
top_logprobs: Optional[int] = None,
|
top_logprobs: Optional[int] = None,
|
||||||
base_url: Optional[str] = None,
|
base_url: Optional[str] = None,
|
||||||
|
api_base: Optional[str] = None,
|
||||||
api_version: Optional[str] = None,
|
api_version: Optional[str] = None,
|
||||||
api_key: Optional[str] = None,
|
api_key: Optional[str] = None,
|
||||||
callbacks: List[Any] = [],
|
callbacks: List[Any] = [],
|
||||||
|
**kwargs,
|
||||||
):
|
):
|
||||||
self.model = model
|
self.model = model
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.temperature = temperature
|
self.temperature = temperature
|
||||||
self.top_p = top_p
|
self.top_p = top_p
|
||||||
self.n = n
|
self.n = n
|
||||||
self.stop = stop
|
|
||||||
self.max_completion_tokens = max_completion_tokens
|
self.max_completion_tokens = max_completion_tokens
|
||||||
self.max_tokens = max_tokens
|
self.max_tokens = max_tokens
|
||||||
self.presence_penalty = presence_penalty
|
self.presence_penalty = presence_penalty
|
||||||
@@ -153,77 +157,154 @@ class LLM:
|
|||||||
self.logprobs = logprobs
|
self.logprobs = logprobs
|
||||||
self.top_logprobs = top_logprobs
|
self.top_logprobs = top_logprobs
|
||||||
self.base_url = base_url
|
self.base_url = base_url
|
||||||
|
self.api_base = api_base
|
||||||
self.api_version = api_version
|
self.api_version = api_version
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.callbacks = callbacks
|
self.callbacks = callbacks
|
||||||
self.context_window_size = 0
|
self.context_window_size = 0
|
||||||
|
self.additional_params = kwargs
|
||||||
|
|
||||||
litellm.drop_params = True
|
litellm.drop_params = True
|
||||||
|
|
||||||
|
# Normalize self.stop to always be a List[str]
|
||||||
|
if stop is None:
|
||||||
|
self.stop: List[str] = []
|
||||||
|
elif isinstance(stop, str):
|
||||||
|
self.stop = [stop]
|
||||||
|
else:
|
||||||
|
self.stop = stop
|
||||||
|
|
||||||
self.set_callbacks(callbacks)
|
self.set_callbacks(callbacks)
|
||||||
self.set_env_callbacks()
|
self.set_env_callbacks()
|
||||||
|
|
||||||
def call(
|
def call(
|
||||||
self,
|
self,
|
||||||
messages: List[Dict[str, str]],
|
messages: Union[str, List[Dict[str, str]]],
|
||||||
tools: Optional[List[dict]] = None,
|
tools: Optional[List[dict]] = None,
|
||||||
callbacks: Optional[List[Any]] = None,
|
callbacks: Optional[List[Any]] = None,
|
||||||
available_functions: Optional[Dict[str, Any]] = None,
|
available_functions: Optional[Dict[str, Any]] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
High-level call method that:
|
High-level LLM call method that handles:
|
||||||
1) Calls litellm.completion
|
1. Multiple input formats (string or message list)
|
||||||
2) Checks for function/tool calls
|
2. Structured responses via Instructor integration
|
||||||
3) If a tool call is found:
|
3. Tool/function calling with optional structured output
|
||||||
a) executes the function
|
4. Callback integration
|
||||||
b) returns the result
|
|
||||||
4) If no tool call, returns the text response
|
|
||||||
|
|
||||||
:param messages: The conversation messages
|
Parameters:
|
||||||
:param tools: Optional list of function schemas for function calling
|
messages: Input prompt(s) as either:
|
||||||
:param callbacks: Optional list of callbacks
|
- String (converted to single user message)
|
||||||
:param available_functions: A dictionary mapping function_name -> actual Python function
|
- List of message dicts with 'role' and 'content'
|
||||||
:return: Final text response from the LLM or the tool result
|
|
||||||
|
tools: List of tool schemas for function calling
|
||||||
|
callbacks: List of callback handlers
|
||||||
|
available_functions: Mapping of function names to callables
|
||||||
|
response_format: Pydantic model for structured responses
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Can be:
|
||||||
|
- Plain text response
|
||||||
|
- Structured response (if response_format provided)
|
||||||
|
- Tool function result (raw or structured)
|
||||||
|
|
||||||
|
Behavior:
|
||||||
|
- With response_format and no tools: Direct structured response
|
||||||
|
- With tools: Initial LLM call → Tool execution → Optional secondary structured call
|
||||||
|
- Without tools/response_format: Standard text completion
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
# Basic text completion
|
||||||
|
llm.call("Hello world")
|
||||||
|
|
||||||
|
# Structured response without tools
|
||||||
|
class City(BaseModel):
|
||||||
|
name: str
|
||||||
|
population: int
|
||||||
|
|
||||||
|
response = llm.call(
|
||||||
|
"Name a major US city",
|
||||||
|
response_format=City
|
||||||
|
)
|
||||||
|
print(response.name) # Structured access
|
||||||
|
|
||||||
|
# Tool usage with raw output
|
||||||
|
llm.call(
|
||||||
|
"What's 5 squared?",
|
||||||
|
tools=[math_tools],
|
||||||
|
available_functions={"square": square_number}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Tool usage with structured output
|
||||||
|
response = llm.call(
|
||||||
|
"Analyze this data",
|
||||||
|
tools=[data_tools],
|
||||||
|
available_functions={"analyze": analyze_data},
|
||||||
|
response_format=AnalysisResult
|
||||||
|
)
|
||||||
|
print(response.metrics) # Structured access
|
||||||
"""
|
"""
|
||||||
|
if isinstance(messages, str):
|
||||||
|
messages = [{"role": "user", "content": messages}]
|
||||||
|
|
||||||
with suppress_warnings():
|
with suppress_warnings():
|
||||||
if callbacks and len(callbacks) > 0:
|
if callbacks and len(callbacks) > 0:
|
||||||
self.set_callbacks(callbacks)
|
self.set_callbacks(callbacks)
|
||||||
|
|
||||||
|
# Prepare the parameters for the completion call.
|
||||||
|
params = {
|
||||||
|
"model": self.model,
|
||||||
|
"messages": messages,
|
||||||
|
"timeout": self.timeout,
|
||||||
|
"temperature": self.temperature,
|
||||||
|
"top_p": self.top_p,
|
||||||
|
"n": self.n,
|
||||||
|
"stop": self.stop,
|
||||||
|
"max_tokens": self.max_tokens or self.max_completion_tokens,
|
||||||
|
"presence_penalty": self.presence_penalty,
|
||||||
|
"frequency_penalty": self.frequency_penalty,
|
||||||
|
"logit_bias": self.logit_bias,
|
||||||
|
"seed": self.seed,
|
||||||
|
"logprobs": self.logprobs,
|
||||||
|
"top_logprobs": self.top_logprobs,
|
||||||
|
"api_base": self.api_base,
|
||||||
|
"base_url": self.base_url,
|
||||||
|
"api_version": self.api_version,
|
||||||
|
"api_key": self.api_key,
|
||||||
|
"stream": False,
|
||||||
|
"tools": tools,
|
||||||
|
**self.additional_params,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove any keys with None values.
|
||||||
|
params = {k: v for k, v in params.items() if v is not None}
|
||||||
|
|
||||||
|
# --- Direct structured response if no tools are provided.
|
||||||
|
if self.response_format is not None and (tools is None or len(tools) == 0):
|
||||||
|
print("Direct structured response")
|
||||||
|
try:
|
||||||
|
# Cast messages to required type and remove model param
|
||||||
|
params["messages"] = cast(
|
||||||
|
List[ChatCompletionMessageParam], messages
|
||||||
|
)
|
||||||
|
params.pop("model", None)
|
||||||
|
|
||||||
|
client = instructor.from_litellm(litellm.completion)
|
||||||
|
response = client.chat.completions.create(**params)
|
||||||
|
return response
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"LiteLLM call failed: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# --- Standard flow with potential tool calls.
|
||||||
try:
|
try:
|
||||||
# --- 1) Make the completion call
|
print("NOT DIRECT STRUCTURED RESPONSE")
|
||||||
params = {
|
|
||||||
"model": self.model,
|
|
||||||
"messages": messages,
|
|
||||||
"timeout": self.timeout,
|
|
||||||
"temperature": self.temperature,
|
|
||||||
"top_p": self.top_p,
|
|
||||||
"n": self.n,
|
|
||||||
"stop": self.stop,
|
|
||||||
"max_tokens": self.max_tokens or self.max_completion_tokens,
|
|
||||||
"presence_penalty": self.presence_penalty,
|
|
||||||
"frequency_penalty": self.frequency_penalty,
|
|
||||||
"logit_bias": self.logit_bias,
|
|
||||||
"response_format": self.response_format,
|
|
||||||
"seed": self.seed,
|
|
||||||
"logprobs": self.logprobs,
|
|
||||||
"top_logprobs": self.top_logprobs,
|
|
||||||
"api_base": self.base_url,
|
|
||||||
"api_version": self.api_version,
|
|
||||||
"api_key": self.api_key,
|
|
||||||
"stream": False,
|
|
||||||
"tools": tools, # pass the tool schema
|
|
||||||
}
|
|
||||||
|
|
||||||
params = {k: v for k, v in params.items() if v is not None}
|
|
||||||
|
|
||||||
response = litellm.completion(**params)
|
response = litellm.completion(**params)
|
||||||
response_message = cast(Choices, cast(ModelResponse, response).choices)[
|
response_message = cast(Choices, cast(ModelResponse, response).choices)[
|
||||||
0
|
0
|
||||||
].message
|
].message
|
||||||
text_response = response_message.content or ""
|
text_response = response_message.content or ""
|
||||||
tool_calls = getattr(response_message, "tool_calls", [])
|
tool_calls = getattr(response_message, "tool_calls", [])
|
||||||
|
|
||||||
# Ensure callbacks get the full response object with usage info
|
|
||||||
if callbacks and len(callbacks) > 0:
|
if callbacks and len(callbacks) > 0:
|
||||||
for callback in callbacks:
|
for callback in callbacks:
|
||||||
if hasattr(callback, "log_success_event"):
|
if hasattr(callback, "log_success_event"):
|
||||||
@@ -236,11 +317,11 @@ class LLM:
|
|||||||
end_time=0,
|
end_time=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- 2) If no tool calls, return the text response
|
# If no tool call is requested or available_functions is not provided, return the text response.
|
||||||
if not tool_calls or not available_functions:
|
if not tool_calls or not available_functions:
|
||||||
return text_response
|
return text_response
|
||||||
|
|
||||||
# --- 3) Handle the tool call
|
# --- Handle tool calls.
|
||||||
tool_call = tool_calls[0]
|
tool_call = tool_calls[0]
|
||||||
function_name = tool_call.function.name
|
function_name = tool_call.function.name
|
||||||
|
|
||||||
@@ -253,23 +334,40 @@ class LLM:
|
|||||||
|
|
||||||
fn = available_functions[function_name]
|
fn = available_functions[function_name]
|
||||||
try:
|
try:
|
||||||
# Call the actual tool function
|
|
||||||
result = fn(**function_args)
|
result = fn(**function_args)
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Error executing function '{function_name}': {e}"
|
f"Error executing function '{function_name}': {e}"
|
||||||
)
|
)
|
||||||
return text_response
|
return text_response
|
||||||
|
|
||||||
|
# If a structured response is requested, perform a secondary call using the tool result.
|
||||||
|
if self.response_format is not None:
|
||||||
|
new_params = dict(params)
|
||||||
|
# Cast tool result message to required type
|
||||||
|
new_params["messages"] = cast(
|
||||||
|
List[ChatCompletionMessageParam],
|
||||||
|
[{"role": "user", "content": result}],
|
||||||
|
)
|
||||||
|
new_params.pop("model", None)
|
||||||
|
if "tools" in new_params:
|
||||||
|
del new_params["tools"]
|
||||||
|
try:
|
||||||
|
client = instructor.from_litellm(litellm.completion)
|
||||||
|
final_response = client.chat.completions.create(
|
||||||
|
**new_params, response_model=response_format
|
||||||
|
)
|
||||||
|
return final_response
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"LiteLLM structured call failed: {e}")
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"Tool call requested unknown function '{function_name}'"
|
f"Tool call requested unknown function '{function_name}'"
|
||||||
)
|
)
|
||||||
return text_response
|
return text_response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if not LLMContextLengthExceededException(
|
if not LLMContextLengthExceededException(
|
||||||
str(e)
|
str(e)
|
||||||
|
|||||||
@@ -431,7 +431,9 @@ class Task(BaseModel):
|
|||||||
content = (
|
content = (
|
||||||
json_output
|
json_output
|
||||||
if json_output
|
if json_output
|
||||||
else pydantic_output.model_dump_json() if pydantic_output else result
|
else pydantic_output.model_dump_json()
|
||||||
|
if pydantic_output
|
||||||
|
else result
|
||||||
)
|
)
|
||||||
self._save_file(content)
|
self._save_file(content)
|
||||||
|
|
||||||
@@ -452,7 +454,7 @@ class Task(BaseModel):
|
|||||||
return "\n".join(tasks_slices)
|
return "\n".join(tasks_slices)
|
||||||
|
|
||||||
def interpolate_inputs_and_add_conversation_history(
|
def interpolate_inputs_and_add_conversation_history(
|
||||||
self, inputs: Dict[str, Union[str, int, float]]
|
self, inputs: Dict[str, Union[str, int, float, Dict[str, Any], List[Any]]]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Interpolate inputs into the task description, expected output, and output file path.
|
"""Interpolate inputs into the task description, expected output, and output file path.
|
||||||
Add conversation history if present.
|
Add conversation history if present.
|
||||||
@@ -524,7 +526,9 @@ class Task(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def interpolate_only(
|
def interpolate_only(
|
||||||
self, input_string: Optional[str], inputs: Dict[str, Union[str, int, float]]
|
self,
|
||||||
|
input_string: Optional[str],
|
||||||
|
inputs: Dict[str, Union[str, int, float, Dict[str, Any], List[Any]]],
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Interpolate placeholders (e.g., {key}) in a string while leaving JSON untouched.
|
"""Interpolate placeholders (e.g., {key}) in a string while leaving JSON untouched.
|
||||||
|
|
||||||
@@ -532,17 +536,39 @@ class Task(BaseModel):
|
|||||||
input_string: The string containing template variables to interpolate.
|
input_string: The string containing template variables to interpolate.
|
||||||
Can be None or empty, in which case an empty string is returned.
|
Can be None or empty, in which case an empty string is returned.
|
||||||
inputs: Dictionary mapping template variables to their values.
|
inputs: Dictionary mapping template variables to their values.
|
||||||
Supported value types are strings, integers, and floats.
|
Supported value types are strings, integers, floats, and dicts/lists
|
||||||
If input_string is empty or has no placeholders, inputs can be empty.
|
containing only these types and other nested dicts/lists.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The interpolated string with all template variables replaced with their values.
|
The interpolated string with all template variables replaced with their values.
|
||||||
Empty string if input_string is None or empty.
|
Empty string if input_string is None or empty.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If a required template variable is missing from inputs.
|
ValueError: If a value contains unsupported types
|
||||||
KeyError: If a template variable is not found in the inputs dictionary.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Validation function for recursive type checking
|
||||||
|
def validate_type(value: Any) -> None:
|
||||||
|
if value is None:
|
||||||
|
return
|
||||||
|
if isinstance(value, (str, int, float, bool)):
|
||||||
|
return
|
||||||
|
if isinstance(value, (dict, list)):
|
||||||
|
for item in value.values() if isinstance(value, dict) else value:
|
||||||
|
validate_type(item)
|
||||||
|
return
|
||||||
|
raise ValueError(
|
||||||
|
f"Unsupported type {type(value).__name__} in inputs. "
|
||||||
|
"Only str, int, float, bool, dict, and list are allowed."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate all input values
|
||||||
|
for key, value in inputs.items():
|
||||||
|
try:
|
||||||
|
validate_type(value)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"Invalid value for key '{key}': {str(e)}") from e
|
||||||
|
|
||||||
if input_string is None or not input_string:
|
if input_string is None or not input_string:
|
||||||
return ""
|
return ""
|
||||||
if "{" not in input_string and "}" not in input_string:
|
if "{" not in input_string and "}" not in input_string:
|
||||||
@@ -551,15 +577,7 @@ class Task(BaseModel):
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Inputs dictionary cannot be empty when interpolating variables"
|
"Inputs dictionary cannot be empty when interpolating variables"
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Validate input types
|
|
||||||
for key, value in inputs.items():
|
|
||||||
if not isinstance(value, (str, int, float)):
|
|
||||||
raise ValueError(
|
|
||||||
f"Value for key '{key}' must be a string, integer, or float, got {type(value).__name__}"
|
|
||||||
)
|
|
||||||
|
|
||||||
escaped_string = input_string.replace("{", "{{").replace("}", "}}")
|
escaped_string = input_string.replace("{", "{{").replace("}", "}}")
|
||||||
|
|
||||||
for key in inputs.keys():
|
for key in inputs.keys():
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import ast
|
import ast
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import re
|
|
||||||
import time
|
import time
|
||||||
from difflib import SequenceMatcher
|
from difflib import SequenceMatcher
|
||||||
|
from json import JSONDecodeError
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from typing import Any, Dict, List, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
|
import json5
|
||||||
from json_repair import repair_json
|
from json_repair import repair_json
|
||||||
|
|
||||||
import crewai.utilities.events as events
|
import crewai.utilities.events as events
|
||||||
@@ -407,28 +408,55 @@ class ToolUsage:
|
|||||||
)
|
)
|
||||||
return self._tool_calling(tool_string)
|
return self._tool_calling(tool_string)
|
||||||
|
|
||||||
def _validate_tool_input(self, tool_input: str) -> Dict[str, Any]:
|
def _validate_tool_input(self, tool_input: Optional[str]) -> Dict[str, Any]:
|
||||||
|
if tool_input is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if not isinstance(tool_input, str) or not tool_input.strip():
|
||||||
|
raise Exception(
|
||||||
|
"Tool input must be a valid dictionary in JSON or Python literal format"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Attempt 1: Parse as JSON
|
||||||
try:
|
try:
|
||||||
# Replace Python literals with JSON equivalents
|
|
||||||
replacements = {
|
|
||||||
r"'": '"',
|
|
||||||
r"None": "null",
|
|
||||||
r"True": "true",
|
|
||||||
r"False": "false",
|
|
||||||
}
|
|
||||||
for pattern, replacement in replacements.items():
|
|
||||||
tool_input = re.sub(pattern, replacement, tool_input)
|
|
||||||
|
|
||||||
arguments = json.loads(tool_input)
|
arguments = json.loads(tool_input)
|
||||||
except json.JSONDecodeError:
|
if isinstance(arguments, dict):
|
||||||
# Attempt to repair JSON string
|
return arguments
|
||||||
repaired_input = repair_json(tool_input)
|
except (JSONDecodeError, TypeError):
|
||||||
try:
|
pass # Continue to the next parsing attempt
|
||||||
arguments = json.loads(repaired_input)
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
raise Exception(f"Invalid tool input JSON: {e}")
|
|
||||||
|
|
||||||
return arguments
|
# Attempt 2: Parse as Python literal
|
||||||
|
try:
|
||||||
|
arguments = ast.literal_eval(tool_input)
|
||||||
|
if isinstance(arguments, dict):
|
||||||
|
return arguments
|
||||||
|
except (ValueError, SyntaxError):
|
||||||
|
pass # Continue to the next parsing attempt
|
||||||
|
|
||||||
|
# Attempt 3: Parse as JSON5
|
||||||
|
try:
|
||||||
|
arguments = json5.loads(tool_input)
|
||||||
|
if isinstance(arguments, dict):
|
||||||
|
return arguments
|
||||||
|
except (JSONDecodeError, ValueError, TypeError):
|
||||||
|
pass # Continue to the next parsing attempt
|
||||||
|
|
||||||
|
# Attempt 4: Repair JSON
|
||||||
|
try:
|
||||||
|
repaired_input = repair_json(tool_input)
|
||||||
|
self._printer.print(
|
||||||
|
content=f"Repaired JSON: {repaired_input}", color="blue"
|
||||||
|
)
|
||||||
|
arguments = json.loads(repaired_input)
|
||||||
|
if isinstance(arguments, dict):
|
||||||
|
return arguments
|
||||||
|
except Exception as e:
|
||||||
|
self._printer.print(content=f"Failed to repair JSON: {e}", color="red")
|
||||||
|
|
||||||
|
# If all parsing attempts fail, raise an error
|
||||||
|
raise Exception(
|
||||||
|
"Tool input must be a valid dictionary in JSON or Python literal format"
|
||||||
|
)
|
||||||
|
|
||||||
def on_tool_error(self, tool: Any, tool_calling: ToolCalling, e: Exception) -> None:
|
def on_tool_error(self, tool: Any, tool_calling: ToolCalling, e: Exception) -> None:
|
||||||
event_data = self._prepare_event_data(tool, tool_calling)
|
event_data = self._prepare_event_data(tool, tool_calling)
|
||||||
|
|||||||
@@ -24,7 +24,8 @@
|
|||||||
"manager_request": "Your best answer to your coworker asking you this, accounting for the context shared.",
|
"manager_request": "Your best answer to your coworker asking you this, accounting for the context shared.",
|
||||||
"formatted_task_instructions": "Ensure your final answer contains only the content in the following format: {output_format}\n\nEnsure the final output does not include any code block markers like ```json or ```python.",
|
"formatted_task_instructions": "Ensure your final answer contains only the content in the following format: {output_format}\n\nEnsure the final output does not include any code block markers like ```json or ```python.",
|
||||||
"human_feedback_classification": "Determine if the following feedback indicates that the user is satisfied or if further changes are needed. Respond with 'True' if further changes are needed, or 'False' if the user is satisfied. **Important** Do not include any additional commentary outside of your 'True' or 'False' response.\n\nFeedback: \"{feedback}\"",
|
"human_feedback_classification": "Determine if the following feedback indicates that the user is satisfied or if further changes are needed. Respond with 'True' if further changes are needed, or 'False' if the user is satisfied. **Important** Do not include any additional commentary outside of your 'True' or 'False' response.\n\nFeedback: \"{feedback}\"",
|
||||||
"conversation_history_instruction": "You are a member of a crew collaborating to achieve a common goal. Your task is a specific action that contributes to this larger objective. For additional context, please review the conversation history between you and the user that led to the initiation of this crew. Use any relevant information or feedback from the conversation to inform your task execution and ensure your response aligns with both the immediate task and the crew's overall goals."
|
"conversation_history_instruction": "You are a member of a crew collaborating to achieve a common goal. Your task is a specific action that contributes to this larger objective. For additional context, please review the conversation history between you and the user that led to the initiation of this crew. Use any relevant information or feedback from the conversation to inform your task execution and ensure your response aligns with both the immediate task and the crew's overall goals.",
|
||||||
|
"feedback_instructions": "User feedback: {feedback}\nInstructions: Use this feedback to enhance the next output iteration.\nNote: Do not respond or add commentary."
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"force_final_answer_error": "You can't keep going, here is the best final answer you generated:\n\n {formatted_answer}",
|
"force_final_answer_error": "You can't keep going, here is the best final answer you generated:\n\n {formatted_answer}",
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ class EmbeddingConfigurator:
|
|||||||
raise Exception(
|
raise Exception(
|
||||||
f"Unsupported embedding provider: {provider}, supported providers: {list(self.embedding_functions.keys())}"
|
f"Unsupported embedding provider: {provider}, supported providers: {list(self.embedding_functions.keys())}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.embedding_functions[provider](config, model_name)
|
return self.embedding_functions[provider](config, model_name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -92,13 +92,34 @@ class TaskEvaluator:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
output_training_data = training_data[agent_id]
|
output_training_data = training_data[agent_id]
|
||||||
|
|
||||||
final_aggregated_data = ""
|
final_aggregated_data = ""
|
||||||
for _, data in output_training_data.items():
|
|
||||||
|
for iteration, data in output_training_data.items():
|
||||||
|
improved_output = data.get("improved_output")
|
||||||
|
initial_output = data.get("initial_output")
|
||||||
|
human_feedback = data.get("human_feedback")
|
||||||
|
|
||||||
|
if not all([improved_output, initial_output, human_feedback]):
|
||||||
|
missing_fields = [
|
||||||
|
field
|
||||||
|
for field in ["improved_output", "initial_output", "human_feedback"]
|
||||||
|
if not data.get(field)
|
||||||
|
]
|
||||||
|
error_msg = (
|
||||||
|
f"Critical training data error: Missing fields ({', '.join(missing_fields)}) "
|
||||||
|
f"for agent {agent_id} in iteration {iteration}.\n"
|
||||||
|
"This indicates a broken training process. "
|
||||||
|
"Cannot proceed with evaluation.\n"
|
||||||
|
"Please check your training implementation."
|
||||||
|
)
|
||||||
|
raise ValueError(error_msg)
|
||||||
|
|
||||||
final_aggregated_data += (
|
final_aggregated_data += (
|
||||||
f"Initial Output:\n{data['initial_output']}\n\n"
|
f"Iteration: {iteration}\n"
|
||||||
f"Human Feedback:\n{data['human_feedback']}\n\n"
|
f"Initial Output:\n{initial_output}\n\n"
|
||||||
f"Improved Output:\n{data['improved_output']}\n\n"
|
f"Human Feedback:\n{human_feedback}\n\n"
|
||||||
|
f"Improved Output:\n{improved_output}\n\n"
|
||||||
|
"------------------------------------------------\n\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
evaluation_query = (
|
evaluation_query = (
|
||||||
|
|||||||
@@ -24,12 +24,10 @@ def create_llm(
|
|||||||
|
|
||||||
# 1) If llm_value is already an LLM object, return it directly
|
# 1) If llm_value is already an LLM object, return it directly
|
||||||
if isinstance(llm_value, LLM):
|
if isinstance(llm_value, LLM):
|
||||||
print("LLM value is already an LLM object")
|
|
||||||
return llm_value
|
return llm_value
|
||||||
|
|
||||||
# 2) If llm_value is a string (model name)
|
# 2) If llm_value is a string (model name)
|
||||||
if isinstance(llm_value, str):
|
if isinstance(llm_value, str):
|
||||||
print("LLM value is a string")
|
|
||||||
try:
|
try:
|
||||||
created_llm = LLM(model=llm_value)
|
created_llm = LLM(model=llm_value)
|
||||||
return created_llm
|
return created_llm
|
||||||
@@ -39,12 +37,10 @@ def create_llm(
|
|||||||
|
|
||||||
# 3) If llm_value is None, parse environment variables or use default
|
# 3) If llm_value is None, parse environment variables or use default
|
||||||
if llm_value is None:
|
if llm_value is None:
|
||||||
print("LLM value is None")
|
|
||||||
return _llm_via_environment_or_fallback()
|
return _llm_via_environment_or_fallback()
|
||||||
|
|
||||||
# 4) Otherwise, attempt to extract relevant attributes from an unknown object
|
# 4) Otherwise, attempt to extract relevant attributes from an unknown object
|
||||||
try:
|
try:
|
||||||
print("LLM value is an unknown object")
|
|
||||||
# Extract attributes with explicit types
|
# Extract attributes with explicit types
|
||||||
model = (
|
model = (
|
||||||
getattr(llm_value, "model_name", None)
|
getattr(llm_value, "model_name", None)
|
||||||
@@ -57,6 +53,7 @@ def create_llm(
|
|||||||
timeout: Optional[float] = getattr(llm_value, "timeout", None)
|
timeout: Optional[float] = getattr(llm_value, "timeout", None)
|
||||||
api_key: Optional[str] = getattr(llm_value, "api_key", None)
|
api_key: Optional[str] = getattr(llm_value, "api_key", None)
|
||||||
base_url: Optional[str] = getattr(llm_value, "base_url", None)
|
base_url: Optional[str] = getattr(llm_value, "base_url", None)
|
||||||
|
api_base: Optional[str] = getattr(llm_value, "api_base", None)
|
||||||
|
|
||||||
created_llm = LLM(
|
created_llm = LLM(
|
||||||
model=model,
|
model=model,
|
||||||
@@ -66,6 +63,7 @@ def create_llm(
|
|||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
|
api_base=api_base,
|
||||||
)
|
)
|
||||||
return created_llm
|
return created_llm
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -105,8 +103,18 @@ def _llm_via_environment_or_fallback() -> Optional[LLM]:
|
|||||||
callbacks: List[Any] = []
|
callbacks: List[Any] = []
|
||||||
|
|
||||||
# Optional base URL from env
|
# Optional base URL from env
|
||||||
api_base = os.environ.get("OPENAI_API_BASE") or os.environ.get("OPENAI_BASE_URL")
|
base_url = (
|
||||||
if api_base:
|
os.environ.get("BASE_URL")
|
||||||
|
or os.environ.get("OPENAI_API_BASE")
|
||||||
|
or os.environ.get("OPENAI_BASE_URL")
|
||||||
|
)
|
||||||
|
|
||||||
|
api_base = os.environ.get("API_BASE") or os.environ.get("AZURE_API_BASE")
|
||||||
|
|
||||||
|
# Synchronize base_url and api_base if one is populated and the other is not
|
||||||
|
if base_url and not api_base:
|
||||||
|
api_base = base_url
|
||||||
|
elif api_base and not base_url:
|
||||||
base_url = api_base
|
base_url = api_base
|
||||||
|
|
||||||
# Initialize llm_params dictionary
|
# Initialize llm_params dictionary
|
||||||
@@ -119,6 +127,7 @@ def _llm_via_environment_or_fallback() -> Optional[LLM]:
|
|||||||
"timeout": timeout,
|
"timeout": timeout,
|
||||||
"api_key": api_key,
|
"api_key": api_key,
|
||||||
"base_url": base_url,
|
"base_url": base_url,
|
||||||
|
"api_base": api_base,
|
||||||
"api_version": api_version,
|
"api_version": api_version,
|
||||||
"presence_penalty": presence_penalty,
|
"presence_penalty": presence_penalty,
|
||||||
"frequency_penalty": frequency_penalty,
|
"frequency_penalty": frequency_penalty,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
from crewai.utilities.file_handler import PickleHandler
|
from crewai.utilities.file_handler import PickleHandler
|
||||||
|
|
||||||
|
|
||||||
@@ -29,3 +31,8 @@ class CrewTrainingHandler(PickleHandler):
|
|||||||
data[agent_id] = {train_iteration: new_data}
|
data[agent_id] = {train_iteration: new_data}
|
||||||
|
|
||||||
self.save(data)
|
self.save(data)
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""Clear the training data by removing the file or resetting its contents."""
|
||||||
|
if os.path.exists(self.file_path):
|
||||||
|
self.save({})
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from crewai import Agent, Crew, Task
|
|||||||
from crewai.agents.cache import CacheHandler
|
from crewai.agents.cache import CacheHandler
|
||||||
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
from crewai.agents.crew_agent_executor import CrewAgentExecutor
|
||||||
from crewai.agents.parser import AgentAction, CrewAgentParser, OutputParserException
|
from crewai.agents.parser import AgentAction, CrewAgentParser, OutputParserException
|
||||||
|
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
|
||||||
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
|
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
|
||||||
from crewai.llm import LLM
|
from crewai.llm import LLM
|
||||||
from crewai.tools import tool
|
from crewai.tools import tool
|
||||||
@@ -1600,3 +1601,181 @@ def test_agent_with_knowledge_sources():
|
|||||||
|
|
||||||
# Assert that the agent provides the correct information
|
# Assert that the agent provides the correct information
|
||||||
assert "red" in result.raw.lower()
|
assert "red" in result.raw.lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_agent_with_knowledge_sources_works_with_copy():
|
||||||
|
content = "Brandon's favorite color is red and he likes Mexican food."
|
||||||
|
string_source = StringKnowledgeSource(content=content)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"crewai.knowledge.source.base_knowledge_source.BaseKnowledgeSource",
|
||||||
|
autospec=True,
|
||||||
|
) as MockKnowledgeSource:
|
||||||
|
mock_knowledge_source_instance = MockKnowledgeSource.return_value
|
||||||
|
mock_knowledge_source_instance.__class__ = BaseKnowledgeSource
|
||||||
|
mock_knowledge_source_instance.sources = [string_source]
|
||||||
|
|
||||||
|
agent = Agent(
|
||||||
|
role="Information Agent",
|
||||||
|
goal="Provide information based on knowledge sources",
|
||||||
|
backstory="You have access to specific knowledge sources.",
|
||||||
|
llm=LLM(model="gpt-4o-mini"),
|
||||||
|
knowledge_sources=[string_source],
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"crewai.knowledge.storage.knowledge_storage.KnowledgeStorage"
|
||||||
|
) as MockKnowledgeStorage:
|
||||||
|
mock_knowledge_storage = MockKnowledgeStorage.return_value
|
||||||
|
agent.knowledge_storage = mock_knowledge_storage
|
||||||
|
|
||||||
|
agent_copy = agent.copy()
|
||||||
|
|
||||||
|
assert agent_copy.role == agent.role
|
||||||
|
assert agent_copy.goal == agent.goal
|
||||||
|
assert agent_copy.backstory == agent.backstory
|
||||||
|
assert agent_copy.knowledge_sources is not None
|
||||||
|
assert len(agent_copy.knowledge_sources) == 1
|
||||||
|
assert isinstance(agent_copy.knowledge_sources[0], StringKnowledgeSource)
|
||||||
|
assert agent_copy.knowledge_sources[0].content == content
|
||||||
|
assert isinstance(agent_copy.llm, LLM)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_litellm_auth_error_handling():
|
||||||
|
"""Test that LiteLLM authentication errors are handled correctly and not retried."""
|
||||||
|
from litellm import AuthenticationError as LiteLLMAuthenticationError
|
||||||
|
|
||||||
|
# Create an agent with a mocked LLM and max_retry_limit=0
|
||||||
|
agent = Agent(
|
||||||
|
role="test role",
|
||||||
|
goal="test goal",
|
||||||
|
backstory="test backstory",
|
||||||
|
llm=LLM(model="gpt-4"),
|
||||||
|
max_retry_limit=0, # Disable retries for authentication errors
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a task
|
||||||
|
task = Task(
|
||||||
|
description="Test task",
|
||||||
|
expected_output="Test output",
|
||||||
|
agent=agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mock the LLM call to raise AuthenticationError
|
||||||
|
with (
|
||||||
|
patch.object(LLM, "call") as mock_llm_call,
|
||||||
|
pytest.raises(LiteLLMAuthenticationError, match="Invalid API key"),
|
||||||
|
):
|
||||||
|
mock_llm_call.side_effect = LiteLLMAuthenticationError(
|
||||||
|
message="Invalid API key", llm_provider="openai", model="gpt-4"
|
||||||
|
)
|
||||||
|
agent.execute_task(task)
|
||||||
|
|
||||||
|
# Verify the call was only made once (no retries)
|
||||||
|
mock_llm_call.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_crew_agent_executor_litellm_auth_error():
|
||||||
|
"""Test that CrewAgentExecutor handles LiteLLM authentication errors by raising them."""
|
||||||
|
from litellm.exceptions import AuthenticationError
|
||||||
|
|
||||||
|
from crewai.agents.tools_handler import ToolsHandler
|
||||||
|
from crewai.utilities import Printer
|
||||||
|
|
||||||
|
# Create an agent and executor
|
||||||
|
agent = Agent(
|
||||||
|
role="test role",
|
||||||
|
goal="test goal",
|
||||||
|
backstory="test backstory",
|
||||||
|
llm=LLM(model="gpt-4", api_key="invalid_api_key"),
|
||||||
|
)
|
||||||
|
task = Task(
|
||||||
|
description="Test task",
|
||||||
|
expected_output="Test output",
|
||||||
|
agent=agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create executor with all required parameters
|
||||||
|
executor = CrewAgentExecutor(
|
||||||
|
agent=agent,
|
||||||
|
task=task,
|
||||||
|
llm=agent.llm,
|
||||||
|
crew=None,
|
||||||
|
prompt={"system": "You are a test agent", "user": "Execute the task: {input}"},
|
||||||
|
max_iter=5,
|
||||||
|
tools=[],
|
||||||
|
tools_names="",
|
||||||
|
stop_words=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_handler=ToolsHandler(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mock the LLM call to raise AuthenticationError
|
||||||
|
with (
|
||||||
|
patch.object(LLM, "call") as mock_llm_call,
|
||||||
|
patch.object(Printer, "print") as mock_printer,
|
||||||
|
pytest.raises(AuthenticationError) as exc_info,
|
||||||
|
):
|
||||||
|
mock_llm_call.side_effect = AuthenticationError(
|
||||||
|
message="Invalid API key", llm_provider="openai", model="gpt-4"
|
||||||
|
)
|
||||||
|
executor.invoke(
|
||||||
|
{
|
||||||
|
"input": "test input",
|
||||||
|
"tool_names": "",
|
||||||
|
"tools": "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify error handling messages
|
||||||
|
error_message = f"Error during LLM call: {str(mock_llm_call.side_effect)}"
|
||||||
|
mock_printer.assert_any_call(
|
||||||
|
content=error_message,
|
||||||
|
color="red",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify the call was only made once (no retries)
|
||||||
|
mock_llm_call.assert_called_once()
|
||||||
|
|
||||||
|
# Assert that the exception was raised and has the expected attributes
|
||||||
|
assert exc_info.type is AuthenticationError
|
||||||
|
assert "Invalid API key".lower() in exc_info.value.message.lower()
|
||||||
|
assert exc_info.value.llm_provider == "openai"
|
||||||
|
assert exc_info.value.model == "gpt-4"
|
||||||
|
|
||||||
|
|
||||||
|
def test_litellm_anthropic_error_handling():
|
||||||
|
"""Test that AnthropicError from LiteLLM is handled correctly and not retried."""
|
||||||
|
from litellm.llms.anthropic.common_utils import AnthropicError
|
||||||
|
|
||||||
|
# Create an agent with a mocked LLM that uses an Anthropic model
|
||||||
|
agent = Agent(
|
||||||
|
role="test role",
|
||||||
|
goal="test goal",
|
||||||
|
backstory="test backstory",
|
||||||
|
llm=LLM(model="claude-3.5-sonnet-20240620"),
|
||||||
|
max_retry_limit=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a task
|
||||||
|
task = Task(
|
||||||
|
description="Test task",
|
||||||
|
expected_output="Test output",
|
||||||
|
agent=agent,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mock the LLM call to raise AnthropicError
|
||||||
|
with (
|
||||||
|
patch.object(LLM, "call") as mock_llm_call,
|
||||||
|
pytest.raises(AnthropicError, match="Test Anthropic error"),
|
||||||
|
):
|
||||||
|
mock_llm_call.side_effect = AnthropicError(
|
||||||
|
status_code=500,
|
||||||
|
message="Test Anthropic error",
|
||||||
|
)
|
||||||
|
agent.execute_task(task)
|
||||||
|
|
||||||
|
# Verify the LLM call was only made once (no retries)
|
||||||
|
mock_llm_call.assert_called_once()
|
||||||
|
|||||||
@@ -2,21 +2,21 @@ interactions:
|
|||||||
- request:
|
- request:
|
||||||
body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour
|
body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour
|
||||||
personal goal is: test goal\nYou ONLY have access to the following tools, and
|
personal goal is: test goal\nYou ONLY have access to the following tools, and
|
||||||
should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer(*args:
|
should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer\nTool
|
||||||
Any, **kwargs: Any) -> Any\nTool Description: get_final_answer() - Get the final
|
Arguments: {}\nTool Description: Get the final answer but don''t give it yet,
|
||||||
answer but don''t give it yet, just re-use this tool non-stop. \nTool
|
just re-use this\n tool non-stop.\n\nIMPORTANT: Use the following format
|
||||||
Arguments: {}\n\nUse the following format:\n\nThought: you should always think
|
in your response:\n\n```\nThought: you should always think about what to do\nAction:
|
||||||
about what to do\nAction: the action to take, only one name of [get_final_answer],
|
the action to take, only one name of [get_final_answer], just the name, exactly
|
||||||
just the name, exactly as it''s written.\nAction Input: the input to the action,
|
as it''s written.\nAction Input: the input to the action, just a simple JSON
|
||||||
just a simple python dictionary, enclosed in curly braces, using \" to wrap
|
object, enclosed in curly braces, using \" to wrap keys and values.\nObservation:
|
||||||
keys and values.\nObservation: the result of the action\n\nOnce all necessary
|
the result of the action\n```\n\nOnce all necessary information is gathered,
|
||||||
information is gathered:\n\nThought: I now know the final answer\nFinal Answer:
|
return the following format:\n\n```\nThought: I now know the final answer\nFinal
|
||||||
the final answer to the original input question\n"}, {"role": "user", "content":
|
Answer: the final answer to the original input question\n```"}, {"role": "user",
|
||||||
"\nCurrent Task: Use the get_final_answer tool.\n\nThis is the expect criteria
|
"content": "\nCurrent Task: Use the get_final_answer tool.\n\nThis is the expect
|
||||||
for your final answer: The final answer\nyou MUST return the actual complete
|
criteria for your final answer: The final answer\nyou MUST return the actual
|
||||||
content as the final answer, not a summary.\n\nBegin! This is VERY important
|
complete content as the final answer, not a summary.\n\nBegin! This is VERY
|
||||||
to you, use the tools available and give your best Final Answer, your job depends
|
important to you, use the tools available and give your best Final Answer, your
|
||||||
on it!\n\nThought:"}], "model": "gpt-4o"}'
|
job depends on it!\n\nThought:"}], "model": "gpt-4o", "stop": ["\nObservation:"]}'
|
||||||
headers:
|
headers:
|
||||||
accept:
|
accept:
|
||||||
- application/json
|
- application/json
|
||||||
@@ -25,16 +25,13 @@ interactions:
|
|||||||
connection:
|
connection:
|
||||||
- keep-alive
|
- keep-alive
|
||||||
content-length:
|
content-length:
|
||||||
- '1325'
|
- '1367'
|
||||||
content-type:
|
content-type:
|
||||||
- application/json
|
- application/json
|
||||||
cookie:
|
|
||||||
- _cfuvid=ePJSDFdHag2D8lj21_ijAMWjoA6xfnPNxN4uekvC728-1727226247743-0.0.1.1-604800000;
|
|
||||||
__cf_bm=3giyBOIM0GNudFELtsBWYXwLrpLBTNLsh81wfXgu2tg-1727226247-1.0.1.1-ugUDz0c5EhmfVpyGtcdedlIWeDGuy2q0tXQTKVpv83HZhvxgBcS7SBL1wS4rapPM38yhfEcfwA79ARt3HQEzKA
|
|
||||||
host:
|
host:
|
||||||
- api.openai.com
|
- api.openai.com
|
||||||
user-agent:
|
user-agent:
|
||||||
- OpenAI/Python 1.47.0
|
- OpenAI/Python 1.59.6
|
||||||
x-stainless-arch:
|
x-stainless-arch:
|
||||||
- arm64
|
- arm64
|
||||||
x-stainless-async:
|
x-stainless-async:
|
||||||
@@ -44,30 +41,35 @@ interactions:
|
|||||||
x-stainless-os:
|
x-stainless-os:
|
||||||
- MacOS
|
- MacOS
|
||||||
x-stainless-package-version:
|
x-stainless-package-version:
|
||||||
- 1.47.0
|
- 1.59.6
|
||||||
x-stainless-raw-response:
|
x-stainless-raw-response:
|
||||||
- 'true'
|
- 'true'
|
||||||
|
x-stainless-retry-count:
|
||||||
|
- '0'
|
||||||
x-stainless-runtime:
|
x-stainless-runtime:
|
||||||
- CPython
|
- CPython
|
||||||
x-stainless-runtime-version:
|
x-stainless-runtime-version:
|
||||||
- 3.11.7
|
- 3.12.7
|
||||||
method: POST
|
method: POST
|
||||||
uri: https://api.openai.com/v1/chat/completions
|
uri: https://api.openai.com/v1/chat/completions
|
||||||
response:
|
response:
|
||||||
content: "{\n \"id\": \"chatcmpl-ABAtOWmVjvzQ9X58tKAUcOF4gmXwx\",\n \"object\":
|
content: "{\n \"id\": \"chatcmpl-AsXdf4OZKCZSigmN4k0gyh67NciqP\",\n \"object\":
|
||||||
\"chat.completion\",\n \"created\": 1727226842,\n \"model\": \"gpt-4o-2024-05-13\",\n
|
\"chat.completion\",\n \"created\": 1737562383,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||||
\"assistant\",\n \"content\": \"Thought: I need to use the get_final_answer
|
\"assistant\",\n \"content\": \"```\\nThought: I have to use the available
|
||||||
tool to determine the final answer.\\nAction: get_final_answer\\nAction Input:
|
tool to get the final answer. Let's proceed with executing it.\\nAction: get_final_answer\\nAction
|
||||||
{}\",\n \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\":
|
Input: {}\",\n \"refusal\": null\n },\n \"logprobs\": null,\n
|
||||||
\"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 274,\n \"completion_tokens\":
|
\ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||||
27,\n \"total_tokens\": 301,\n \"completion_tokens_details\": {\n \"reasoning_tokens\":
|
274,\n \"completion_tokens\": 33,\n \"total_tokens\": 307,\n \"prompt_tokens_details\":
|
||||||
0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n"
|
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||||
|
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||||
|
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||||
|
\"default\",\n \"system_fingerprint\": \"fp_50cad350e4\"\n}\n"
|
||||||
headers:
|
headers:
|
||||||
CF-Cache-Status:
|
CF-Cache-Status:
|
||||||
- DYNAMIC
|
- DYNAMIC
|
||||||
CF-RAY:
|
CF-RAY:
|
||||||
- 8c8727b3492f31e6-MIA
|
- 9060d43e3be1d690-IAD
|
||||||
Connection:
|
Connection:
|
||||||
- keep-alive
|
- keep-alive
|
||||||
Content-Encoding:
|
Content-Encoding:
|
||||||
@@ -75,19 +77,27 @@ interactions:
|
|||||||
Content-Type:
|
Content-Type:
|
||||||
- application/json
|
- application/json
|
||||||
Date:
|
Date:
|
||||||
- Wed, 25 Sep 2024 01:14:03 GMT
|
- Wed, 22 Jan 2025 16:13:03 GMT
|
||||||
Server:
|
Server:
|
||||||
- cloudflare
|
- cloudflare
|
||||||
|
Set-Cookie:
|
||||||
|
- __cf_bm=_Jcp7wnO_mXdvOnborCN6j8HwJxJXbszedJC1l7pFUg-1737562383-1.0.1.1-pDSLXlg.nKjG4wsT7mTJPjUvOX1UJITiS4MqKp6yfMWwRSJINsW1qC48SAcjBjakx2H5I1ESVk9JtUpUFDtf4g;
|
||||||
|
path=/; expires=Wed, 22-Jan-25 16:43:03 GMT; domain=.api.openai.com; HttpOnly;
|
||||||
|
Secure; SameSite=None
|
||||||
|
- _cfuvid=x3SYvzL2nq_PTBGtE8R9cl5CkeaaDzZFQIrYfo91S2s-1737562383916-0.0.1.1-604800000;
|
||||||
|
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||||
Transfer-Encoding:
|
Transfer-Encoding:
|
||||||
- chunked
|
- chunked
|
||||||
X-Content-Type-Options:
|
X-Content-Type-Options:
|
||||||
- nosniff
|
- nosniff
|
||||||
access-control-expose-headers:
|
access-control-expose-headers:
|
||||||
- X-Request-ID
|
- X-Request-ID
|
||||||
|
alt-svc:
|
||||||
|
- h3=":443"; ma=86400
|
||||||
openai-organization:
|
openai-organization:
|
||||||
- crewai-iuxna1
|
- crewai-iuxna1
|
||||||
openai-processing-ms:
|
openai-processing-ms:
|
||||||
- '348'
|
- '791'
|
||||||
openai-version:
|
openai-version:
|
||||||
- '2020-10-01'
|
- '2020-10-01'
|
||||||
strict-transport-security:
|
strict-transport-security:
|
||||||
@@ -99,45 +109,59 @@ interactions:
|
|||||||
x-ratelimit-remaining-requests:
|
x-ratelimit-remaining-requests:
|
||||||
- '9999'
|
- '9999'
|
||||||
x-ratelimit-remaining-tokens:
|
x-ratelimit-remaining-tokens:
|
||||||
- '29999682'
|
- '29999680'
|
||||||
x-ratelimit-reset-requests:
|
x-ratelimit-reset-requests:
|
||||||
- 6ms
|
- 6ms
|
||||||
x-ratelimit-reset-tokens:
|
x-ratelimit-reset-tokens:
|
||||||
- 0s
|
- 0s
|
||||||
x-request-id:
|
x-request-id:
|
||||||
- req_be929caac49706f487950548bdcdd46e
|
- req_eeed99acafd3aeb1e3d4a6c8063192b0
|
||||||
http_version: HTTP/1.1
|
http_version: HTTP/1.1
|
||||||
status_code: 200
|
status_code: 200
|
||||||
- request:
|
- request:
|
||||||
body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour
|
body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour
|
||||||
personal goal is: test goal\nYou ONLY have access to the following tools, and
|
personal goal is: test goal\nYou ONLY have access to the following tools, and
|
||||||
should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer(*args:
|
should NEVER make up tools that are not listed here:\n\nTool Name: get_final_answer\nTool
|
||||||
Any, **kwargs: Any) -> Any\nTool Description: get_final_answer() - Get the final
|
Arguments: {}\nTool Description: Get the final answer but don''t give it yet,
|
||||||
answer but don''t give it yet, just re-use this tool non-stop. \nTool
|
just re-use this\n tool non-stop.\n\nIMPORTANT: Use the following format
|
||||||
Arguments: {}\n\nUse the following format:\n\nThought: you should always think
|
in your response:\n\n```\nThought: you should always think about what to do\nAction:
|
||||||
about what to do\nAction: the action to take, only one name of [get_final_answer],
|
the action to take, only one name of [get_final_answer], just the name, exactly
|
||||||
just the name, exactly as it''s written.\nAction Input: the input to the action,
|
as it''s written.\nAction Input: the input to the action, just a simple JSON
|
||||||
just a simple python dictionary, enclosed in curly braces, using \" to wrap
|
object, enclosed in curly braces, using \" to wrap keys and values.\nObservation:
|
||||||
keys and values.\nObservation: the result of the action\n\nOnce all necessary
|
the result of the action\n```\n\nOnce all necessary information is gathered,
|
||||||
information is gathered:\n\nThought: I now know the final answer\nFinal Answer:
|
return the following format:\n\n```\nThought: I now know the final answer\nFinal
|
||||||
the final answer to the original input question\n"}, {"role": "user", "content":
|
Answer: the final answer to the original input question\n```"}, {"role": "user",
|
||||||
"\nCurrent Task: Use the get_final_answer tool.\n\nThis is the expect criteria
|
"content": "\nCurrent Task: Use the get_final_answer tool.\n\nThis is the expect
|
||||||
for your final answer: The final answer\nyou MUST return the actual complete
|
criteria for your final answer: The final answer\nyou MUST return the actual
|
||||||
content as the final answer, not a summary.\n\nBegin! This is VERY important
|
complete content as the final answer, not a summary.\n\nBegin! This is VERY
|
||||||
to you, use the tools available and give your best Final Answer, your job depends
|
important to you, use the tools available and give your best Final Answer, your
|
||||||
on it!\n\nThought:"}, {"role": "user", "content": "Thought: I need to use the
|
job depends on it!\n\nThought:"}, {"role": "assistant", "content": "```\nThought:
|
||||||
get_final_answer tool to determine the final answer.\nAction: get_final_answer\nAction
|
I have to use the available tool to get the final answer. Let''s proceed with
|
||||||
|
executing it.\nAction: get_final_answer\nAction Input: {}\nObservation: I encountered
|
||||||
|
an error: Error on parsing tool.\nMoving on then. I MUST either use a tool (use
|
||||||
|
one at time) OR give my best final answer not both at the same time. When responding,
|
||||||
|
I must use the following format:\n\n```\nThought: you should always think about
|
||||||
|
what to do\nAction: the action to take, should be one of [get_final_answer]\nAction
|
||||||
|
Input: the input to the action, dictionary enclosed in curly braces\nObservation:
|
||||||
|
the result of the action\n```\nThis Thought/Action/Action Input/Result can repeat
|
||||||
|
N times. Once I know the final answer, I must return the following format:\n\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\n```"}, {"role":
|
||||||
|
"assistant", "content": "```\nThought: I have to use the available tool to get
|
||||||
|
the final answer. Let''s proceed with executing it.\nAction: get_final_answer\nAction
|
||||||
Input: {}\nObservation: I encountered an error: Error on parsing tool.\nMoving
|
Input: {}\nObservation: I encountered an error: Error on parsing tool.\nMoving
|
||||||
on then. I MUST either use a tool (use one at time) OR give my best final answer
|
on then. I MUST either use a tool (use one at time) OR give my best final answer
|
||||||
not both at the same time. To Use the following format:\n\nThought: you should
|
not both at the same time. When responding, I must use the following format:\n\n```\nThought:
|
||||||
always think about what to do\nAction: the action to take, should be one of
|
you should always think about what to do\nAction: the action to take, should
|
||||||
[get_final_answer]\nAction Input: the input to the action, dictionary enclosed
|
be one of [get_final_answer]\nAction Input: the input to the action, dictionary
|
||||||
in curly braces\nObservation: the result of the action\n... (this Thought/Action/Action
|
enclosed in curly braces\nObservation: the result of the action\n```\nThis Thought/Action/Action
|
||||||
Input/Result can repeat N times)\nThought: I now can give a great answer\nFinal
|
Input/Result can repeat N times. Once I know the final answer, I must return
|
||||||
|
the following format:\n\n```\nThought: I now can give a great answer\nFinal
|
||||||
Answer: Your final answer must be the great and the most complete as possible,
|
Answer: Your final answer must be the great and the most complete as possible,
|
||||||
it must be outcome described\n\n \nNow it''s time you MUST give your absolute
|
it must be outcome described\n\n```\nNow it''s time you MUST give your absolute
|
||||||
best final answer. You''ll ignore all previous instructions, stop using any
|
best final answer. You''ll ignore all previous instructions, stop using any
|
||||||
tools, and just return your absolute BEST Final answer."}], "model": "gpt-4o"}'
|
tools, and just return your absolute BEST Final answer."}], "model": "gpt-4o",
|
||||||
|
"stop": ["\nObservation:"]}'
|
||||||
headers:
|
headers:
|
||||||
accept:
|
accept:
|
||||||
- application/json
|
- application/json
|
||||||
@@ -146,16 +170,16 @@ interactions:
|
|||||||
connection:
|
connection:
|
||||||
- keep-alive
|
- keep-alive
|
||||||
content-length:
|
content-length:
|
||||||
- '2320'
|
- '3445'
|
||||||
content-type:
|
content-type:
|
||||||
- application/json
|
- application/json
|
||||||
cookie:
|
cookie:
|
||||||
- _cfuvid=ePJSDFdHag2D8lj21_ijAMWjoA6xfnPNxN4uekvC728-1727226247743-0.0.1.1-604800000;
|
- __cf_bm=_Jcp7wnO_mXdvOnborCN6j8HwJxJXbszedJC1l7pFUg-1737562383-1.0.1.1-pDSLXlg.nKjG4wsT7mTJPjUvOX1UJITiS4MqKp6yfMWwRSJINsW1qC48SAcjBjakx2H5I1ESVk9JtUpUFDtf4g;
|
||||||
__cf_bm=3giyBOIM0GNudFELtsBWYXwLrpLBTNLsh81wfXgu2tg-1727226247-1.0.1.1-ugUDz0c5EhmfVpyGtcdedlIWeDGuy2q0tXQTKVpv83HZhvxgBcS7SBL1wS4rapPM38yhfEcfwA79ARt3HQEzKA
|
_cfuvid=x3SYvzL2nq_PTBGtE8R9cl5CkeaaDzZFQIrYfo91S2s-1737562383916-0.0.1.1-604800000
|
||||||
host:
|
host:
|
||||||
- api.openai.com
|
- api.openai.com
|
||||||
user-agent:
|
user-agent:
|
||||||
- OpenAI/Python 1.47.0
|
- OpenAI/Python 1.59.6
|
||||||
x-stainless-arch:
|
x-stainless-arch:
|
||||||
- arm64
|
- arm64
|
||||||
x-stainless-async:
|
x-stainless-async:
|
||||||
@@ -165,29 +189,36 @@ interactions:
|
|||||||
x-stainless-os:
|
x-stainless-os:
|
||||||
- MacOS
|
- MacOS
|
||||||
x-stainless-package-version:
|
x-stainless-package-version:
|
||||||
- 1.47.0
|
- 1.59.6
|
||||||
x-stainless-raw-response:
|
x-stainless-raw-response:
|
||||||
- 'true'
|
- 'true'
|
||||||
|
x-stainless-retry-count:
|
||||||
|
- '0'
|
||||||
x-stainless-runtime:
|
x-stainless-runtime:
|
||||||
- CPython
|
- CPython
|
||||||
x-stainless-runtime-version:
|
x-stainless-runtime-version:
|
||||||
- 3.11.7
|
- 3.12.7
|
||||||
method: POST
|
method: POST
|
||||||
uri: https://api.openai.com/v1/chat/completions
|
uri: https://api.openai.com/v1/chat/completions
|
||||||
response:
|
response:
|
||||||
content: "{\n \"id\": \"chatcmpl-ABAtPaaeRfdNsZ3k06CfAmrEW8IJu\",\n \"object\":
|
content: "{\n \"id\": \"chatcmpl-AsXdg9UrLvAiqWP979E6DszLsQ84k\",\n \"object\":
|
||||||
\"chat.completion\",\n \"created\": 1727226843,\n \"model\": \"gpt-4o-2024-05-13\",\n
|
\"chat.completion\",\n \"created\": 1737562384,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
||||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||||
\"assistant\",\n \"content\": \"Final Answer: The final answer\",\n \"refusal\":
|
\"assistant\",\n \"content\": \"```\\nThought: I now know the final answer\\nFinal
|
||||||
null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n
|
Answer: The final answer must be the great and the most complete as possible,
|
||||||
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 483,\n \"completion_tokens\":
|
it must be outcome described.\\n```\",\n \"refusal\": null\n },\n
|
||||||
6,\n \"total_tokens\": 489,\n \"completion_tokens_details\": {\n \"reasoning_tokens\":
|
\ \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n ],\n
|
||||||
0\n }\n },\n \"system_fingerprint\": \"fp_e375328146\"\n}\n"
|
\ \"usage\": {\n \"prompt_tokens\": 719,\n \"completion_tokens\": 35,\n
|
||||||
|
\ \"total_tokens\": 754,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||||
|
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n
|
||||||
|
\ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||||
|
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||||
|
\"default\",\n \"system_fingerprint\": \"fp_50cad350e4\"\n}\n"
|
||||||
headers:
|
headers:
|
||||||
CF-Cache-Status:
|
CF-Cache-Status:
|
||||||
- DYNAMIC
|
- DYNAMIC
|
||||||
CF-RAY:
|
CF-RAY:
|
||||||
- 8c8727b9da1f31e6-MIA
|
- 9060d4441edad690-IAD
|
||||||
Connection:
|
Connection:
|
||||||
- keep-alive
|
- keep-alive
|
||||||
Content-Encoding:
|
Content-Encoding:
|
||||||
@@ -195,7 +226,7 @@ interactions:
|
|||||||
Content-Type:
|
Content-Type:
|
||||||
- application/json
|
- application/json
|
||||||
Date:
|
Date:
|
||||||
- Wed, 25 Sep 2024 01:14:03 GMT
|
- Wed, 22 Jan 2025 16:13:05 GMT
|
||||||
Server:
|
Server:
|
||||||
- cloudflare
|
- cloudflare
|
||||||
Transfer-Encoding:
|
Transfer-Encoding:
|
||||||
@@ -209,7 +240,7 @@ interactions:
|
|||||||
openai-organization:
|
openai-organization:
|
||||||
- crewai-iuxna1
|
- crewai-iuxna1
|
||||||
openai-processing-ms:
|
openai-processing-ms:
|
||||||
- '188'
|
- '928'
|
||||||
openai-version:
|
openai-version:
|
||||||
- '2020-10-01'
|
- '2020-10-01'
|
||||||
strict-transport-security:
|
strict-transport-security:
|
||||||
@@ -221,13 +252,13 @@ interactions:
|
|||||||
x-ratelimit-remaining-requests:
|
x-ratelimit-remaining-requests:
|
||||||
- '9999'
|
- '9999'
|
||||||
x-ratelimit-remaining-tokens:
|
x-ratelimit-remaining-tokens:
|
||||||
- '29999445'
|
- '29999187'
|
||||||
x-ratelimit-reset-requests:
|
x-ratelimit-reset-requests:
|
||||||
- 6ms
|
- 6ms
|
||||||
x-ratelimit-reset-tokens:
|
x-ratelimit-reset-tokens:
|
||||||
- 1ms
|
- 1ms
|
||||||
x-request-id:
|
x-request-id:
|
||||||
- req_d8e32538689fe064627468bad802d9a8
|
- req_61fc7506e6db326ec572224aec81ef23
|
||||||
http_version: HTTP/1.1
|
http_version: HTTP/1.1
|
||||||
status_code: 200
|
status_code: 200
|
||||||
version: 1
|
version: 1
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
interactions:
|
|
||||||
- request:
|
|
||||||
body: '{"messages": [{"role": "system", "content": "You are Futel Official Infopoint.
|
|
||||||
Futel Football Club info\nYour personal goal is: Answer questions about Futel\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: Test task\n\nThis is the expect criteria for your
|
|
||||||
final answer: Your best answer to your coworker asking you this, accounting
|
|
||||||
for the context shared.\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", "stop": ["\nObservation:"], "stream": false}'
|
|
||||||
headers:
|
|
||||||
accept:
|
|
||||||
- application/json
|
|
||||||
accept-encoding:
|
|
||||||
- gzip, deflate
|
|
||||||
connection:
|
|
||||||
- keep-alive
|
|
||||||
content-length:
|
|
||||||
- '939'
|
|
||||||
content-type:
|
|
||||||
- application/json
|
|
||||||
cookie:
|
|
||||||
- __cf_bm=cwWdOaPJjFMNJaLtJfa8Kjqavswg5bzVRFzBX4gneGw-1736458417-1.0.1.1-bvf2HshgcMtgn7GdxqwySFDAIacGccDFfEXniBFTTDmbGMCiIIwf6t2DiwWnBldmUHixwc5kDO9gYs08g.feBA;
|
|
||||||
_cfuvid=WMw7PSqkYqQOieguBRs0uNkwNU92A.ZKbgDbCAcV3EQ-1736458417825-0.0.1.1-604800000
|
|
||||||
host:
|
|
||||||
- api.openai.com
|
|
||||||
user-agent:
|
|
||||||
- OpenAI/Python 1.52.1
|
|
||||||
x-stainless-arch:
|
|
||||||
- arm64
|
|
||||||
x-stainless-async:
|
|
||||||
- 'false'
|
|
||||||
x-stainless-lang:
|
|
||||||
- python
|
|
||||||
x-stainless-os:
|
|
||||||
- MacOS
|
|
||||||
x-stainless-package-version:
|
|
||||||
- 1.52.1
|
|
||||||
x-stainless-raw-response:
|
|
||||||
- 'true'
|
|
||||||
x-stainless-retry-count:
|
|
||||||
- '0'
|
|
||||||
x-stainless-runtime:
|
|
||||||
- CPython
|
|
||||||
x-stainless-runtime-version:
|
|
||||||
- 3.12.7
|
|
||||||
method: POST
|
|
||||||
uri: https://api.openai.com/v1/chat/completions
|
|
||||||
response:
|
|
||||||
content: "{\n \"id\": \"chatcmpl-AnuRlxiTxduAVoXHHY58Fvfbll5IS\",\n \"object\":
|
|
||||||
\"chat.completion\",\n \"created\": 1736458417,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
|
||||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
|
||||||
\"assistant\",\n \"content\": \"I now can give a great answer \\nFinal
|
|
||||||
Answer: This is a test task, and the context or question from the coworker is
|
|
||||||
not specified. Therefore, my best effort would be to affirm my readiness to
|
|
||||||
answer accurately and in detail any question about Futel Football Club based
|
|
||||||
on the context described. If provided with specific information or questions,
|
|
||||||
I will ensure to respond comprehensively as required by my job directives.\",\n
|
|
||||||
\ \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\":
|
|
||||||
\"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 177,\n \"completion_tokens\":
|
|
||||||
82,\n \"total_tokens\": 259,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
|
||||||
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n
|
|
||||||
\ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
|
||||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\":
|
|
||||||
\"fp_703d4ff298\"\n}\n"
|
|
||||||
headers:
|
|
||||||
CF-Cache-Status:
|
|
||||||
- DYNAMIC
|
|
||||||
CF-RAY:
|
|
||||||
- 8ff78bf7bd6cc002-ATL
|
|
||||||
Connection:
|
|
||||||
- keep-alive
|
|
||||||
Content-Encoding:
|
|
||||||
- gzip
|
|
||||||
Content-Type:
|
|
||||||
- application/json
|
|
||||||
Date:
|
|
||||||
- Thu, 09 Jan 2025 21:33:40 GMT
|
|
||||||
Server:
|
|
||||||
- cloudflare
|
|
||||||
Transfer-Encoding:
|
|
||||||
- chunked
|
|
||||||
X-Content-Type-Options:
|
|
||||||
- nosniff
|
|
||||||
access-control-expose-headers:
|
|
||||||
- X-Request-ID
|
|
||||||
alt-svc:
|
|
||||||
- h3=":443"; ma=86400
|
|
||||||
openai-organization:
|
|
||||||
- crewai-iuxna1
|
|
||||||
openai-processing-ms:
|
|
||||||
- '2263'
|
|
||||||
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:
|
|
||||||
- '29999786'
|
|
||||||
x-ratelimit-reset-requests:
|
|
||||||
- 6ms
|
|
||||||
x-ratelimit-reset-tokens:
|
|
||||||
- 0s
|
|
||||||
x-request-id:
|
|
||||||
- req_7c1a31da73cd103e9f410f908e59187f
|
|
||||||
http_version: HTTP/1.1
|
|
||||||
status_code: 200
|
|
||||||
version: 1
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
interactions:
|
|
||||||
- request:
|
|
||||||
body: '{"messages": [{"role": "system", "content": "You are Futel Official Infopoint.
|
|
||||||
Futel Football Club info\nYour personal goal is: Answer questions about Futel\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: Test task\n\nThis is the expect criteria for your
|
|
||||||
final answer: Your best answer to your coworker asking you this, accounting
|
|
||||||
for the context shared.\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", "stop": ["\nObservation:"], "stream": false}'
|
|
||||||
headers:
|
|
||||||
accept:
|
|
||||||
- application/json
|
|
||||||
accept-encoding:
|
|
||||||
- gzip, deflate
|
|
||||||
connection:
|
|
||||||
- keep-alive
|
|
||||||
content-length:
|
|
||||||
- '939'
|
|
||||||
content-type:
|
|
||||||
- application/json
|
|
||||||
cookie:
|
|
||||||
- __cf_bm=cwWdOaPJjFMNJaLtJfa8Kjqavswg5bzVRFzBX4gneGw-1736458417-1.0.1.1-bvf2HshgcMtgn7GdxqwySFDAIacGccDFfEXniBFTTDmbGMCiIIwf6t2DiwWnBldmUHixwc5kDO9gYs08g.feBA;
|
|
||||||
_cfuvid=WMw7PSqkYqQOieguBRs0uNkwNU92A.ZKbgDbCAcV3EQ-1736458417825-0.0.1.1-604800000
|
|
||||||
host:
|
|
||||||
- api.openai.com
|
|
||||||
user-agent:
|
|
||||||
- OpenAI/Python 1.52.1
|
|
||||||
x-stainless-arch:
|
|
||||||
- arm64
|
|
||||||
x-stainless-async:
|
|
||||||
- 'false'
|
|
||||||
x-stainless-lang:
|
|
||||||
- python
|
|
||||||
x-stainless-os:
|
|
||||||
- MacOS
|
|
||||||
x-stainless-package-version:
|
|
||||||
- 1.52.1
|
|
||||||
x-stainless-raw-response:
|
|
||||||
- 'true'
|
|
||||||
x-stainless-retry-count:
|
|
||||||
- '0'
|
|
||||||
x-stainless-runtime:
|
|
||||||
- CPython
|
|
||||||
x-stainless-runtime-version:
|
|
||||||
- 3.12.7
|
|
||||||
method: POST
|
|
||||||
uri: https://api.openai.com/v1/chat/completions
|
|
||||||
response:
|
|
||||||
content: "{\n \"id\": \"chatcmpl-AnuRrFJZGKw8cIEshvuW1PKwFZFKs\",\n \"object\":
|
|
||||||
\"chat.completion\",\n \"created\": 1736458423,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
|
||||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
|
||||||
\"assistant\",\n \"content\": \"I now can give a great answer \\nFinal
|
|
||||||
Answer: Although you mentioned this being a \\\"Test task\\\" and haven't provided
|
|
||||||
a specific question regarding Futel Football Club, your request appears to involve
|
|
||||||
ensuring accuracy and detail in responses. For a proper answer about Futel,
|
|
||||||
I'd be ready to provide details about the club's history, management, players,
|
|
||||||
match schedules, and recent performance statistics. Remember to ask specific
|
|
||||||
questions to receive a targeted response. If this were a real context where
|
|
||||||
information was shared, I would respond precisely to what's been asked regarding
|
|
||||||
Futel Football Club.\",\n \"refusal\": null\n },\n \"logprobs\":
|
|
||||||
null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
|
||||||
177,\n \"completion_tokens\": 113,\n \"total_tokens\": 290,\n \"prompt_tokens_details\":
|
|
||||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
|
||||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
|
||||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\":
|
|
||||||
\"fp_703d4ff298\"\n}\n"
|
|
||||||
headers:
|
|
||||||
CF-Cache-Status:
|
|
||||||
- DYNAMIC
|
|
||||||
CF-RAY:
|
|
||||||
- 8ff78c1d0ecdc002-ATL
|
|
||||||
Connection:
|
|
||||||
- keep-alive
|
|
||||||
Content-Encoding:
|
|
||||||
- gzip
|
|
||||||
Content-Type:
|
|
||||||
- application/json
|
|
||||||
Date:
|
|
||||||
- Thu, 09 Jan 2025 21:33:47 GMT
|
|
||||||
Server:
|
|
||||||
- cloudflare
|
|
||||||
Transfer-Encoding:
|
|
||||||
- chunked
|
|
||||||
X-Content-Type-Options:
|
|
||||||
- nosniff
|
|
||||||
access-control-expose-headers:
|
|
||||||
- X-Request-ID
|
|
||||||
alt-svc:
|
|
||||||
- h3=":443"; ma=86400
|
|
||||||
openai-organization:
|
|
||||||
- crewai-iuxna1
|
|
||||||
openai-processing-ms:
|
|
||||||
- '3097'
|
|
||||||
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:
|
|
||||||
- '29999786'
|
|
||||||
x-ratelimit-reset-requests:
|
|
||||||
- 6ms
|
|
||||||
x-ratelimit-reset-tokens:
|
|
||||||
- 0s
|
|
||||||
x-request-id:
|
|
||||||
- req_179e1d56e2b17303e40480baffbc7b08
|
|
||||||
http_version: HTTP/1.1
|
|
||||||
status_code: 200
|
|
||||||
version: 1
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
interactions:
|
|
||||||
- request:
|
|
||||||
body: '{"messages": [{"role": "system", "content": "You are Futel Official Infopoint.
|
|
||||||
Futel Football Club info\nYour personal goal is: Answer questions about Futel\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: Test task\n\nThis is the expect criteria for your
|
|
||||||
final answer: Your best answer to your coworker asking you this, accounting
|
|
||||||
for the context shared.\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", "stop": ["\nObservation:"], "stream": false}'
|
|
||||||
headers:
|
|
||||||
accept:
|
|
||||||
- application/json
|
|
||||||
accept-encoding:
|
|
||||||
- gzip, deflate
|
|
||||||
connection:
|
|
||||||
- keep-alive
|
|
||||||
content-length:
|
|
||||||
- '939'
|
|
||||||
content-type:
|
|
||||||
- application/json
|
|
||||||
cookie:
|
|
||||||
- __cf_bm=cwWdOaPJjFMNJaLtJfa8Kjqavswg5bzVRFzBX4gneGw-1736458417-1.0.1.1-bvf2HshgcMtgn7GdxqwySFDAIacGccDFfEXniBFTTDmbGMCiIIwf6t2DiwWnBldmUHixwc5kDO9gYs08g.feBA;
|
|
||||||
_cfuvid=WMw7PSqkYqQOieguBRs0uNkwNU92A.ZKbgDbCAcV3EQ-1736458417825-0.0.1.1-604800000
|
|
||||||
host:
|
|
||||||
- api.openai.com
|
|
||||||
user-agent:
|
|
||||||
- OpenAI/Python 1.52.1
|
|
||||||
x-stainless-arch:
|
|
||||||
- arm64
|
|
||||||
x-stainless-async:
|
|
||||||
- 'false'
|
|
||||||
x-stainless-lang:
|
|
||||||
- python
|
|
||||||
x-stainless-os:
|
|
||||||
- MacOS
|
|
||||||
x-stainless-package-version:
|
|
||||||
- 1.52.1
|
|
||||||
x-stainless-raw-response:
|
|
||||||
- 'true'
|
|
||||||
x-stainless-retry-count:
|
|
||||||
- '0'
|
|
||||||
x-stainless-runtime:
|
|
||||||
- CPython
|
|
||||||
x-stainless-runtime-version:
|
|
||||||
- 3.12.7
|
|
||||||
method: POST
|
|
||||||
uri: https://api.openai.com/v1/chat/completions
|
|
||||||
response:
|
|
||||||
content: "{\n \"id\": \"chatcmpl-AnuRqgg7eiHnDi2DOqdk99fiqOboz\",\n \"object\":
|
|
||||||
\"chat.completion\",\n \"created\": 1736458422,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
|
||||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
|
||||||
\"assistant\",\n \"content\": \"I now can give a great answer \\nFinal
|
|
||||||
Answer: Your best answer to your coworker asking you this, accounting for the
|
|
||||||
context shared. You MUST return the actual complete content as the final answer,
|
|
||||||
not a summary.\",\n \"refusal\": null\n },\n \"logprobs\":
|
|
||||||
null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
|
||||||
177,\n \"completion_tokens\": 44,\n \"total_tokens\": 221,\n \"prompt_tokens_details\":
|
|
||||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
|
||||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
|
||||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\":
|
|
||||||
\"fp_703d4ff298\"\n}\n"
|
|
||||||
headers:
|
|
||||||
CF-Cache-Status:
|
|
||||||
- DYNAMIC
|
|
||||||
CF-RAY:
|
|
||||||
- 8ff78c164ad2c002-ATL
|
|
||||||
Connection:
|
|
||||||
- keep-alive
|
|
||||||
Content-Encoding:
|
|
||||||
- gzip
|
|
||||||
Content-Type:
|
|
||||||
- application/json
|
|
||||||
Date:
|
|
||||||
- Thu, 09 Jan 2025 21:33:43 GMT
|
|
||||||
Server:
|
|
||||||
- cloudflare
|
|
||||||
Transfer-Encoding:
|
|
||||||
- chunked
|
|
||||||
X-Content-Type-Options:
|
|
||||||
- nosniff
|
|
||||||
access-control-expose-headers:
|
|
||||||
- X-Request-ID
|
|
||||||
alt-svc:
|
|
||||||
- h3=":443"; ma=86400
|
|
||||||
openai-organization:
|
|
||||||
- crewai-iuxna1
|
|
||||||
openai-processing-ms:
|
|
||||||
- '899'
|
|
||||||
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:
|
|
||||||
- '29999786'
|
|
||||||
x-ratelimit-reset-requests:
|
|
||||||
- 6ms
|
|
||||||
x-ratelimit-reset-tokens:
|
|
||||||
- 0s
|
|
||||||
x-request-id:
|
|
||||||
- req_9f5226208edb90a27987aaf7e0ca03d3
|
|
||||||
http_version: HTTP/1.1
|
|
||||||
status_code: 200
|
|
||||||
version: 1
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
interactions:
|
|
||||||
- request:
|
|
||||||
body: '{"messages": [{"role": "system", "content": "You are Futel Official Infopoint.
|
|
||||||
Futel Football Club info\nYour personal goal is: Answer questions about Futel\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: Test task\n\nThis is the expect criteria for your
|
|
||||||
final answer: Your best answer to your coworker asking you this, accounting
|
|
||||||
for the context shared.\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", "stop": ["\nObservation:"], "stream": false}'
|
|
||||||
headers:
|
|
||||||
accept:
|
|
||||||
- application/json
|
|
||||||
accept-encoding:
|
|
||||||
- gzip, deflate
|
|
||||||
connection:
|
|
||||||
- keep-alive
|
|
||||||
content-length:
|
|
||||||
- '939'
|
|
||||||
content-type:
|
|
||||||
- application/json
|
|
||||||
host:
|
|
||||||
- api.openai.com
|
|
||||||
user-agent:
|
|
||||||
- OpenAI/Python 1.52.1
|
|
||||||
x-stainless-arch:
|
|
||||||
- arm64
|
|
||||||
x-stainless-async:
|
|
||||||
- 'false'
|
|
||||||
x-stainless-lang:
|
|
||||||
- python
|
|
||||||
x-stainless-os:
|
|
||||||
- MacOS
|
|
||||||
x-stainless-package-version:
|
|
||||||
- 1.52.1
|
|
||||||
x-stainless-raw-response:
|
|
||||||
- 'true'
|
|
||||||
x-stainless-retry-count:
|
|
||||||
- '0'
|
|
||||||
x-stainless-runtime:
|
|
||||||
- CPython
|
|
||||||
x-stainless-runtime-version:
|
|
||||||
- 3.12.7
|
|
||||||
method: POST
|
|
||||||
uri: https://api.openai.com/v1/chat/completions
|
|
||||||
response:
|
|
||||||
content: "{\n \"id\": \"chatcmpl-AnuRjmwH5mrykLxQhFwTqqTiDtuTf\",\n \"object\":
|
|
||||||
\"chat.completion\",\n \"created\": 1736458415,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
|
||||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
|
||||||
\"assistant\",\n \"content\": \"I now can give a great answer \\nFinal
|
|
||||||
Answer: As this is a test task, please note that Futel Football Club is fictional
|
|
||||||
and any specific details about it would not be available. However, if you have
|
|
||||||
specific questions or need information about a particular aspect of Futel or
|
|
||||||
any general football club inquiry, feel free to ask, and I'll do my best to
|
|
||||||
assist you with your query!\",\n \"refusal\": null\n },\n \"logprobs\":
|
|
||||||
null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
|
||||||
177,\n \"completion_tokens\": 79,\n \"total_tokens\": 256,\n \"prompt_tokens_details\":
|
|
||||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
|
||||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
|
||||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\":
|
|
||||||
\"fp_703d4ff298\"\n}\n"
|
|
||||||
headers:
|
|
||||||
CF-Cache-Status:
|
|
||||||
- DYNAMIC
|
|
||||||
CF-RAY:
|
|
||||||
- 8ff78be5eebfc002-ATL
|
|
||||||
Connection:
|
|
||||||
- keep-alive
|
|
||||||
Content-Encoding:
|
|
||||||
- gzip
|
|
||||||
Content-Type:
|
|
||||||
- application/json
|
|
||||||
Date:
|
|
||||||
- Thu, 09 Jan 2025 21:33:37 GMT
|
|
||||||
Server:
|
|
||||||
- cloudflare
|
|
||||||
Set-Cookie:
|
|
||||||
- __cf_bm=cwWdOaPJjFMNJaLtJfa8Kjqavswg5bzVRFzBX4gneGw-1736458417-1.0.1.1-bvf2HshgcMtgn7GdxqwySFDAIacGccDFfEXniBFTTDmbGMCiIIwf6t2DiwWnBldmUHixwc5kDO9gYs08g.feBA;
|
|
||||||
path=/; expires=Thu, 09-Jan-25 22:03:37 GMT; domain=.api.openai.com; HttpOnly;
|
|
||||||
Secure; SameSite=None
|
|
||||||
- _cfuvid=WMw7PSqkYqQOieguBRs0uNkwNU92A.ZKbgDbCAcV3EQ-1736458417825-0.0.1.1-604800000;
|
|
||||||
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
|
||||||
Transfer-Encoding:
|
|
||||||
- chunked
|
|
||||||
X-Content-Type-Options:
|
|
||||||
- nosniff
|
|
||||||
access-control-expose-headers:
|
|
||||||
- X-Request-ID
|
|
||||||
alt-svc:
|
|
||||||
- h3=":443"; ma=86400
|
|
||||||
openai-organization:
|
|
||||||
- crewai-iuxna1
|
|
||||||
openai-processing-ms:
|
|
||||||
- '2730'
|
|
||||||
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:
|
|
||||||
- '29999786'
|
|
||||||
x-ratelimit-reset-requests:
|
|
||||||
- 6ms
|
|
||||||
x-ratelimit-reset-tokens:
|
|
||||||
- 0s
|
|
||||||
x-request-id:
|
|
||||||
- req_014478ba748f860d10ac250ca0ba824a
|
|
||||||
http_version: HTTP/1.1
|
|
||||||
status_code: 200
|
|
||||||
version: 1
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
interactions:
|
|
||||||
- request:
|
|
||||||
body: '{"messages": [{"role": "system", "content": "You are Futel Official Infopoint.
|
|
||||||
Futel Football Club info\nYour personal goal is: Answer questions about Futel\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: Test task\n\nThis is the expect criteria for your
|
|
||||||
final answer: Your best answer to your coworker asking you this, accounting
|
|
||||||
for the context shared.\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", "stop": ["\nObservation:"], "stream": false}'
|
|
||||||
headers:
|
|
||||||
accept:
|
|
||||||
- application/json
|
|
||||||
accept-encoding:
|
|
||||||
- gzip, deflate
|
|
||||||
connection:
|
|
||||||
- keep-alive
|
|
||||||
content-length:
|
|
||||||
- '939'
|
|
||||||
content-type:
|
|
||||||
- application/json
|
|
||||||
cookie:
|
|
||||||
- __cf_bm=cwWdOaPJjFMNJaLtJfa8Kjqavswg5bzVRFzBX4gneGw-1736458417-1.0.1.1-bvf2HshgcMtgn7GdxqwySFDAIacGccDFfEXniBFTTDmbGMCiIIwf6t2DiwWnBldmUHixwc5kDO9gYs08g.feBA;
|
|
||||||
_cfuvid=WMw7PSqkYqQOieguBRs0uNkwNU92A.ZKbgDbCAcV3EQ-1736458417825-0.0.1.1-604800000
|
|
||||||
host:
|
|
||||||
- api.openai.com
|
|
||||||
user-agent:
|
|
||||||
- OpenAI/Python 1.52.1
|
|
||||||
x-stainless-arch:
|
|
||||||
- arm64
|
|
||||||
x-stainless-async:
|
|
||||||
- 'false'
|
|
||||||
x-stainless-lang:
|
|
||||||
- python
|
|
||||||
x-stainless-os:
|
|
||||||
- MacOS
|
|
||||||
x-stainless-package-version:
|
|
||||||
- 1.52.1
|
|
||||||
x-stainless-raw-response:
|
|
||||||
- 'true'
|
|
||||||
x-stainless-retry-count:
|
|
||||||
- '0'
|
|
||||||
x-stainless-runtime:
|
|
||||||
- CPython
|
|
||||||
x-stainless-runtime-version:
|
|
||||||
- 3.12.7
|
|
||||||
method: POST
|
|
||||||
uri: https://api.openai.com/v1/chat/completions
|
|
||||||
response:
|
|
||||||
content: "{\n \"id\": \"chatcmpl-AnuRofLgmzWcDya5LILqYwIJYgFoq\",\n \"object\":
|
|
||||||
\"chat.completion\",\n \"created\": 1736458420,\n \"model\": \"gpt-4o-2024-08-06\",\n
|
|
||||||
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
|
||||||
\"assistant\",\n \"content\": \"I now can give a great answer \\nFinal
|
|
||||||
Answer: As an official Futel Football Club infopoint, my responsibility is to
|
|
||||||
provide detailed and accurate information about the club. This includes answering
|
|
||||||
questions regarding team statistics, player performances, upcoming fixtures,
|
|
||||||
ticketing and fan zone details, club history, and community initiatives. Our
|
|
||||||
focus is to ensure that fans and stakeholders have access to the latest and
|
|
||||||
most precise information about the club's on and off-pitch activities. If there's
|
|
||||||
anything specific you need to know, just let me know, and I'll be more than
|
|
||||||
happy to assist!\",\n \"refusal\": null\n },\n \"logprobs\":
|
|
||||||
null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
|
||||||
177,\n \"completion_tokens\": 115,\n \"total_tokens\": 292,\n \"prompt_tokens_details\":
|
|
||||||
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
|
||||||
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
|
||||||
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"system_fingerprint\":
|
|
||||||
\"fp_703d4ff298\"\n}\n"
|
|
||||||
headers:
|
|
||||||
CF-Cache-Status:
|
|
||||||
- DYNAMIC
|
|
||||||
CF-RAY:
|
|
||||||
- 8ff78c066f37c002-ATL
|
|
||||||
Connection:
|
|
||||||
- keep-alive
|
|
||||||
Content-Encoding:
|
|
||||||
- gzip
|
|
||||||
Content-Type:
|
|
||||||
- application/json
|
|
||||||
Date:
|
|
||||||
- Thu, 09 Jan 2025 21:33:42 GMT
|
|
||||||
Server:
|
|
||||||
- cloudflare
|
|
||||||
Transfer-Encoding:
|
|
||||||
- chunked
|
|
||||||
X-Content-Type-Options:
|
|
||||||
- nosniff
|
|
||||||
access-control-expose-headers:
|
|
||||||
- X-Request-ID
|
|
||||||
alt-svc:
|
|
||||||
- h3=":443"; ma=86400
|
|
||||||
openai-organization:
|
|
||||||
- crewai-iuxna1
|
|
||||||
openai-processing-ms:
|
|
||||||
- '2459'
|
|
||||||
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:
|
|
||||||
- '29999786'
|
|
||||||
x-ratelimit-reset-requests:
|
|
||||||
- 6ms
|
|
||||||
x-ratelimit-reset-tokens:
|
|
||||||
- 0s
|
|
||||||
x-request-id:
|
|
||||||
- req_a146dd27f040f39a576750970cca0f52
|
|
||||||
http_version: HTTP/1.1
|
|
||||||
status_code: 200
|
|
||||||
version: 1
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
102
tests/cassettes/test_llm_call_with_message_list.yaml
Normal file
102
tests/cassettes/test_llm_call_with_message_list.yaml
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
interactions:
|
||||||
|
- request:
|
||||||
|
body: '{"messages": [{"role": "user", "content": "What is the capital of France?"}],
|
||||||
|
"model": "gpt-4o-mini"}'
|
||||||
|
headers:
|
||||||
|
accept:
|
||||||
|
- application/json
|
||||||
|
accept-encoding:
|
||||||
|
- gzip, deflate
|
||||||
|
connection:
|
||||||
|
- keep-alive
|
||||||
|
content-length:
|
||||||
|
- '101'
|
||||||
|
content-type:
|
||||||
|
- application/json
|
||||||
|
cookie:
|
||||||
|
- _cfuvid=8NrWEBP3dDmc8p2.csR.EdsSwS8zFvzWI1kPICaK_fM-1737568015338-0.0.1.1-604800000;
|
||||||
|
__cf_bm=pKr3NwXmTZN9rMSlKvEX40VPKbrxF93QwDNHunL2v8Y-1737568015-1.0.1.1-nR0EA7hYIwWpIBYUI53d9xQrUnl5iML6lgz4AGJW4ZGPBDxFma3PZ2cBhlr_hE7wKa5fV3r32eMu_rNWMXD.eA
|
||||||
|
host:
|
||||||
|
- api.openai.com
|
||||||
|
user-agent:
|
||||||
|
- OpenAI/Python 1.59.6
|
||||||
|
x-stainless-arch:
|
||||||
|
- arm64
|
||||||
|
x-stainless-async:
|
||||||
|
- 'false'
|
||||||
|
x-stainless-lang:
|
||||||
|
- python
|
||||||
|
x-stainless-os:
|
||||||
|
- MacOS
|
||||||
|
x-stainless-package-version:
|
||||||
|
- 1.59.6
|
||||||
|
x-stainless-raw-response:
|
||||||
|
- 'true'
|
||||||
|
x-stainless-retry-count:
|
||||||
|
- '0'
|
||||||
|
x-stainless-runtime:
|
||||||
|
- CPython
|
||||||
|
x-stainless-runtime-version:
|
||||||
|
- 3.12.7
|
||||||
|
method: POST
|
||||||
|
uri: https://api.openai.com/v1/chat/completions
|
||||||
|
response:
|
||||||
|
content: "{\n \"id\": \"chatcmpl-AsZ6WjNfEOrHwwEEdSZZCRBiTpBMS\",\n \"object\":
|
||||||
|
\"chat.completion\",\n \"created\": 1737568016,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n
|
||||||
|
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||||
|
\"assistant\",\n \"content\": \"The capital of France is Paris.\",\n
|
||||||
|
\ \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\":
|
||||||
|
\"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 14,\n \"completion_tokens\":
|
||||||
|
8,\n \"total_tokens\": 22,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||||
|
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n
|
||||||
|
\ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||||
|
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||||
|
\"default\",\n \"system_fingerprint\": \"fp_72ed7ab54c\"\n}\n"
|
||||||
|
headers:
|
||||||
|
CF-Cache-Status:
|
||||||
|
- DYNAMIC
|
||||||
|
CF-RAY:
|
||||||
|
- 90615dc63b805cb1-RDU
|
||||||
|
Connection:
|
||||||
|
- keep-alive
|
||||||
|
Content-Encoding:
|
||||||
|
- gzip
|
||||||
|
Content-Type:
|
||||||
|
- application/json
|
||||||
|
Date:
|
||||||
|
- Wed, 22 Jan 2025 17:46:56 GMT
|
||||||
|
Server:
|
||||||
|
- cloudflare
|
||||||
|
Transfer-Encoding:
|
||||||
|
- chunked
|
||||||
|
X-Content-Type-Options:
|
||||||
|
- nosniff
|
||||||
|
access-control-expose-headers:
|
||||||
|
- X-Request-ID
|
||||||
|
alt-svc:
|
||||||
|
- h3=":443"; ma=86400
|
||||||
|
openai-organization:
|
||||||
|
- crewai-iuxna1
|
||||||
|
openai-processing-ms:
|
||||||
|
- '355'
|
||||||
|
openai-version:
|
||||||
|
- '2020-10-01'
|
||||||
|
strict-transport-security:
|
||||||
|
- max-age=31536000; includeSubDomains; preload
|
||||||
|
x-ratelimit-limit-requests:
|
||||||
|
- '30000'
|
||||||
|
x-ratelimit-limit-tokens:
|
||||||
|
- '150000000'
|
||||||
|
x-ratelimit-remaining-requests:
|
||||||
|
- '29999'
|
||||||
|
x-ratelimit-remaining-tokens:
|
||||||
|
- '149999974'
|
||||||
|
x-ratelimit-reset-requests:
|
||||||
|
- 2ms
|
||||||
|
x-ratelimit-reset-tokens:
|
||||||
|
- 0s
|
||||||
|
x-request-id:
|
||||||
|
- req_cdbed69c9c63658eb552b07f1220df19
|
||||||
|
http_version: HTTP/1.1
|
||||||
|
status_code: 200
|
||||||
|
version: 1
|
||||||
108
tests/cassettes/test_llm_call_with_string_input.yaml
Normal file
108
tests/cassettes/test_llm_call_with_string_input.yaml
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
interactions:
|
||||||
|
- request:
|
||||||
|
body: '{"messages": [{"role": "user", "content": "Return the name of a random
|
||||||
|
city in the world."}], "model": "gpt-4o-mini"}'
|
||||||
|
headers:
|
||||||
|
accept:
|
||||||
|
- application/json
|
||||||
|
accept-encoding:
|
||||||
|
- gzip, deflate
|
||||||
|
connection:
|
||||||
|
- keep-alive
|
||||||
|
content-length:
|
||||||
|
- '117'
|
||||||
|
content-type:
|
||||||
|
- application/json
|
||||||
|
cookie:
|
||||||
|
- _cfuvid=3UeEmz_rnmsoZxrVUv32u35gJOi766GDWNe5_RTjiPk-1736537376739-0.0.1.1-604800000
|
||||||
|
host:
|
||||||
|
- api.openai.com
|
||||||
|
user-agent:
|
||||||
|
- OpenAI/Python 1.59.6
|
||||||
|
x-stainless-arch:
|
||||||
|
- arm64
|
||||||
|
x-stainless-async:
|
||||||
|
- 'false'
|
||||||
|
x-stainless-lang:
|
||||||
|
- python
|
||||||
|
x-stainless-os:
|
||||||
|
- MacOS
|
||||||
|
x-stainless-package-version:
|
||||||
|
- 1.59.6
|
||||||
|
x-stainless-raw-response:
|
||||||
|
- 'true'
|
||||||
|
x-stainless-retry-count:
|
||||||
|
- '0'
|
||||||
|
x-stainless-runtime:
|
||||||
|
- CPython
|
||||||
|
x-stainless-runtime-version:
|
||||||
|
- 3.12.7
|
||||||
|
method: POST
|
||||||
|
uri: https://api.openai.com/v1/chat/completions
|
||||||
|
response:
|
||||||
|
content: "{\n \"id\": \"chatcmpl-AsZ6UtbaNSMpNU9VJKxvn52t5eJTq\",\n \"object\":
|
||||||
|
\"chat.completion\",\n \"created\": 1737568014,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n
|
||||||
|
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||||
|
\"assistant\",\n \"content\": \"How about \\\"Lisbon\\\"? It\u2019s the
|
||||||
|
capital city of Portugal, known for its rich history and vibrant culture.\",\n
|
||||||
|
\ \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\":
|
||||||
|
\"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 18,\n \"completion_tokens\":
|
||||||
|
24,\n \"total_tokens\": 42,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||||
|
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n
|
||||||
|
\ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||||
|
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||||
|
\"default\",\n \"system_fingerprint\": \"fp_72ed7ab54c\"\n}\n"
|
||||||
|
headers:
|
||||||
|
CF-Cache-Status:
|
||||||
|
- DYNAMIC
|
||||||
|
CF-RAY:
|
||||||
|
- 90615dbcaefb5cb1-RDU
|
||||||
|
Connection:
|
||||||
|
- keep-alive
|
||||||
|
Content-Encoding:
|
||||||
|
- gzip
|
||||||
|
Content-Type:
|
||||||
|
- application/json
|
||||||
|
Date:
|
||||||
|
- Wed, 22 Jan 2025 17:46:55 GMT
|
||||||
|
Server:
|
||||||
|
- cloudflare
|
||||||
|
Set-Cookie:
|
||||||
|
- __cf_bm=pKr3NwXmTZN9rMSlKvEX40VPKbrxF93QwDNHunL2v8Y-1737568015-1.0.1.1-nR0EA7hYIwWpIBYUI53d9xQrUnl5iML6lgz4AGJW4ZGPBDxFma3PZ2cBhlr_hE7wKa5fV3r32eMu_rNWMXD.eA;
|
||||||
|
path=/; expires=Wed, 22-Jan-25 18:16:55 GMT; domain=.api.openai.com; HttpOnly;
|
||||||
|
Secure; SameSite=None
|
||||||
|
- _cfuvid=8NrWEBP3dDmc8p2.csR.EdsSwS8zFvzWI1kPICaK_fM-1737568015338-0.0.1.1-604800000;
|
||||||
|
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||||
|
Transfer-Encoding:
|
||||||
|
- chunked
|
||||||
|
X-Content-Type-Options:
|
||||||
|
- nosniff
|
||||||
|
access-control-expose-headers:
|
||||||
|
- X-Request-ID
|
||||||
|
alt-svc:
|
||||||
|
- h3=":443"; ma=86400
|
||||||
|
openai-organization:
|
||||||
|
- crewai-iuxna1
|
||||||
|
openai-processing-ms:
|
||||||
|
- '449'
|
||||||
|
openai-version:
|
||||||
|
- '2020-10-01'
|
||||||
|
strict-transport-security:
|
||||||
|
- max-age=31536000; includeSubDomains; preload
|
||||||
|
x-ratelimit-limit-requests:
|
||||||
|
- '30000'
|
||||||
|
x-ratelimit-limit-tokens:
|
||||||
|
- '150000000'
|
||||||
|
x-ratelimit-remaining-requests:
|
||||||
|
- '29999'
|
||||||
|
x-ratelimit-remaining-tokens:
|
||||||
|
- '149999971'
|
||||||
|
x-ratelimit-reset-requests:
|
||||||
|
- 2ms
|
||||||
|
x-ratelimit-reset-tokens:
|
||||||
|
- 0s
|
||||||
|
x-request-id:
|
||||||
|
- req_898373758d2eae3cd84814050b2588e3
|
||||||
|
http_version: HTTP/1.1
|
||||||
|
status_code: 200
|
||||||
|
version: 1
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
interactions:
|
||||||
|
- request:
|
||||||
|
body: '{"messages": [{"role": "user", "content": "Tell me a joke."}], "model":
|
||||||
|
"gpt-4o-mini"}'
|
||||||
|
headers:
|
||||||
|
accept:
|
||||||
|
- application/json
|
||||||
|
accept-encoding:
|
||||||
|
- gzip, deflate
|
||||||
|
connection:
|
||||||
|
- keep-alive
|
||||||
|
content-length:
|
||||||
|
- '86'
|
||||||
|
content-type:
|
||||||
|
- application/json
|
||||||
|
cookie:
|
||||||
|
- _cfuvid=8NrWEBP3dDmc8p2.csR.EdsSwS8zFvzWI1kPICaK_fM-1737568015338-0.0.1.1-604800000;
|
||||||
|
__cf_bm=pKr3NwXmTZN9rMSlKvEX40VPKbrxF93QwDNHunL2v8Y-1737568015-1.0.1.1-nR0EA7hYIwWpIBYUI53d9xQrUnl5iML6lgz4AGJW4ZGPBDxFma3PZ2cBhlr_hE7wKa5fV3r32eMu_rNWMXD.eA
|
||||||
|
host:
|
||||||
|
- api.openai.com
|
||||||
|
user-agent:
|
||||||
|
- OpenAI/Python 1.59.6
|
||||||
|
x-stainless-arch:
|
||||||
|
- arm64
|
||||||
|
x-stainless-async:
|
||||||
|
- 'false'
|
||||||
|
x-stainless-lang:
|
||||||
|
- python
|
||||||
|
x-stainless-os:
|
||||||
|
- MacOS
|
||||||
|
x-stainless-package-version:
|
||||||
|
- 1.59.6
|
||||||
|
x-stainless-raw-response:
|
||||||
|
- 'true'
|
||||||
|
x-stainless-retry-count:
|
||||||
|
- '0'
|
||||||
|
x-stainless-runtime:
|
||||||
|
- CPython
|
||||||
|
x-stainless-runtime-version:
|
||||||
|
- 3.12.7
|
||||||
|
method: POST
|
||||||
|
uri: https://api.openai.com/v1/chat/completions
|
||||||
|
response:
|
||||||
|
content: "{\n \"id\": \"chatcmpl-AsZ6VyjuUcXYpChXmD8rUSy6nSGq8\",\n \"object\":
|
||||||
|
\"chat.completion\",\n \"created\": 1737568015,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n
|
||||||
|
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||||
|
\"assistant\",\n \"content\": \"Why did the scarecrow win an award? \\n\\nBecause
|
||||||
|
he was outstanding in his field!\",\n \"refusal\": null\n },\n \"logprobs\":
|
||||||
|
null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||||
|
12,\n \"completion_tokens\": 19,\n \"total_tokens\": 31,\n \"prompt_tokens_details\":
|
||||||
|
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||||
|
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||||
|
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||||
|
\"default\",\n \"system_fingerprint\": \"fp_72ed7ab54c\"\n}\n"
|
||||||
|
headers:
|
||||||
|
CF-Cache-Status:
|
||||||
|
- DYNAMIC
|
||||||
|
CF-RAY:
|
||||||
|
- 90615dc03b6c5cb1-RDU
|
||||||
|
Connection:
|
||||||
|
- keep-alive
|
||||||
|
Content-Encoding:
|
||||||
|
- gzip
|
||||||
|
Content-Type:
|
||||||
|
- application/json
|
||||||
|
Date:
|
||||||
|
- Wed, 22 Jan 2025 17:46:56 GMT
|
||||||
|
Server:
|
||||||
|
- cloudflare
|
||||||
|
Transfer-Encoding:
|
||||||
|
- chunked
|
||||||
|
X-Content-Type-Options:
|
||||||
|
- nosniff
|
||||||
|
access-control-expose-headers:
|
||||||
|
- X-Request-ID
|
||||||
|
alt-svc:
|
||||||
|
- h3=":443"; ma=86400
|
||||||
|
openai-organization:
|
||||||
|
- crewai-iuxna1
|
||||||
|
openai-processing-ms:
|
||||||
|
- '825'
|
||||||
|
openai-version:
|
||||||
|
- '2020-10-01'
|
||||||
|
strict-transport-security:
|
||||||
|
- max-age=31536000; includeSubDomains; preload
|
||||||
|
x-ratelimit-limit-requests:
|
||||||
|
- '30000'
|
||||||
|
x-ratelimit-limit-tokens:
|
||||||
|
- '150000000'
|
||||||
|
x-ratelimit-remaining-requests:
|
||||||
|
- '29999'
|
||||||
|
x-ratelimit-remaining-tokens:
|
||||||
|
- '149999979'
|
||||||
|
x-ratelimit-reset-requests:
|
||||||
|
- 2ms
|
||||||
|
x-ratelimit-reset-tokens:
|
||||||
|
- 0s
|
||||||
|
x-request-id:
|
||||||
|
- req_4c1485d44e7461396d4a7316a63ff353
|
||||||
|
http_version: HTTP/1.1
|
||||||
|
status_code: 200
|
||||||
|
version: 1
|
||||||
111
tests/cassettes/test_llm_call_with_tool_and_message_list.yaml
Normal file
111
tests/cassettes/test_llm_call_with_tool_and_message_list.yaml
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
interactions:
|
||||||
|
- request:
|
||||||
|
body: '{"messages": [{"role": "user", "content": "What is the square of 5?"}],
|
||||||
|
"model": "gpt-4o-mini", "tools": [{"type": "function", "function": {"name":
|
||||||
|
"square_number", "description": "Returns the square of a number.", "parameters":
|
||||||
|
{"type": "object", "properties": {"number": {"type": "integer", "description":
|
||||||
|
"The number to square"}}, "required": ["number"]}}}]}'
|
||||||
|
headers:
|
||||||
|
accept:
|
||||||
|
- application/json
|
||||||
|
accept-encoding:
|
||||||
|
- gzip, deflate
|
||||||
|
connection:
|
||||||
|
- keep-alive
|
||||||
|
content-length:
|
||||||
|
- '361'
|
||||||
|
content-type:
|
||||||
|
- application/json
|
||||||
|
host:
|
||||||
|
- api.openai.com
|
||||||
|
user-agent:
|
||||||
|
- OpenAI/Python 1.59.6
|
||||||
|
x-stainless-arch:
|
||||||
|
- arm64
|
||||||
|
x-stainless-async:
|
||||||
|
- 'false'
|
||||||
|
x-stainless-lang:
|
||||||
|
- python
|
||||||
|
x-stainless-os:
|
||||||
|
- MacOS
|
||||||
|
x-stainless-package-version:
|
||||||
|
- 1.59.6
|
||||||
|
x-stainless-raw-response:
|
||||||
|
- 'true'
|
||||||
|
x-stainless-retry-count:
|
||||||
|
- '0'
|
||||||
|
x-stainless-runtime:
|
||||||
|
- CPython
|
||||||
|
x-stainless-runtime-version:
|
||||||
|
- 3.12.7
|
||||||
|
method: POST
|
||||||
|
uri: https://api.openai.com/v1/chat/completions
|
||||||
|
response:
|
||||||
|
content: "{\n \"id\": \"chatcmpl-AsZL5nGOaVpcGnDOesTxBZPHhMoaS\",\n \"object\":
|
||||||
|
\"chat.completion\",\n \"created\": 1737568919,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n
|
||||||
|
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||||
|
\"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n
|
||||||
|
\ \"id\": \"call_i6JVJ1KxX79A4WzFri98E03U\",\n \"type\":
|
||||||
|
\"function\",\n \"function\": {\n \"name\": \"square_number\",\n
|
||||||
|
\ \"arguments\": \"{\\\"number\\\":5}\"\n }\n }\n
|
||||||
|
\ ],\n \"refusal\": null\n },\n \"logprobs\": null,\n
|
||||||
|
\ \"finish_reason\": \"tool_calls\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\":
|
||||||
|
58,\n \"completion_tokens\": 15,\n \"total_tokens\": 73,\n \"prompt_tokens_details\":
|
||||||
|
{\n \"cached_tokens\": 0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\":
|
||||||
|
{\n \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||||
|
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||||
|
\"default\",\n \"system_fingerprint\": \"fp_72ed7ab54c\"\n}\n"
|
||||||
|
headers:
|
||||||
|
CF-Cache-Status:
|
||||||
|
- DYNAMIC
|
||||||
|
CF-RAY:
|
||||||
|
- 906173d229b905f6-IAD
|
||||||
|
Connection:
|
||||||
|
- keep-alive
|
||||||
|
Content-Encoding:
|
||||||
|
- gzip
|
||||||
|
Content-Type:
|
||||||
|
- application/json
|
||||||
|
Date:
|
||||||
|
- Wed, 22 Jan 2025 18:02:00 GMT
|
||||||
|
Server:
|
||||||
|
- cloudflare
|
||||||
|
Set-Cookie:
|
||||||
|
- __cf_bm=BYDpIoqfPZyRxl9xcFxkt4IzTUGe8irWQlZ.aYLt8Xc-1737568920-1.0.1.1-Y_cVFN7TbguWRBorSKZynVY02QUtYbsbHuR2gR1wJ8LHuqOF4xIxtK5iHVCpWWgIyPDol9xOXiqUkU8xRV_vHA;
|
||||||
|
path=/; expires=Wed, 22-Jan-25 18:32:00 GMT; domain=.api.openai.com; HttpOnly;
|
||||||
|
Secure; SameSite=None
|
||||||
|
- _cfuvid=etTqqA9SBOnENmrFAUBIexdW0v2ZeO1x9_Ek_WChlfU-1737568920137-0.0.1.1-604800000;
|
||||||
|
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||||
|
Transfer-Encoding:
|
||||||
|
- chunked
|
||||||
|
X-Content-Type-Options:
|
||||||
|
- nosniff
|
||||||
|
access-control-expose-headers:
|
||||||
|
- X-Request-ID
|
||||||
|
alt-svc:
|
||||||
|
- h3=":443"; ma=86400
|
||||||
|
openai-organization:
|
||||||
|
- crewai-iuxna1
|
||||||
|
openai-processing-ms:
|
||||||
|
- '642'
|
||||||
|
openai-version:
|
||||||
|
- '2020-10-01'
|
||||||
|
strict-transport-security:
|
||||||
|
- max-age=31536000; includeSubDomains; preload
|
||||||
|
x-ratelimit-limit-requests:
|
||||||
|
- '30000'
|
||||||
|
x-ratelimit-limit-tokens:
|
||||||
|
- '150000000'
|
||||||
|
x-ratelimit-remaining-requests:
|
||||||
|
- '29999'
|
||||||
|
x-ratelimit-remaining-tokens:
|
||||||
|
- '149999976'
|
||||||
|
x-ratelimit-reset-requests:
|
||||||
|
- 2ms
|
||||||
|
x-ratelimit-reset-tokens:
|
||||||
|
- 0s
|
||||||
|
x-request-id:
|
||||||
|
- req_388e63f9b8d4edc0dd153001f25388e5
|
||||||
|
http_version: HTTP/1.1
|
||||||
|
status_code: 200
|
||||||
|
version: 1
|
||||||
107
tests/cassettes/test_llm_call_with_tool_and_string_input.yaml
Normal file
107
tests/cassettes/test_llm_call_with_tool_and_string_input.yaml
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
interactions:
|
||||||
|
- request:
|
||||||
|
body: '{"messages": [{"role": "user", "content": "What is the current year?"}],
|
||||||
|
"model": "gpt-4o-mini", "tools": [{"type": "function", "function": {"name":
|
||||||
|
"get_current_year", "description": "Returns the current year as a string.",
|
||||||
|
"parameters": {"type": "object", "properties": {}, "required": []}}}]}'
|
||||||
|
headers:
|
||||||
|
accept:
|
||||||
|
- application/json
|
||||||
|
accept-encoding:
|
||||||
|
- gzip, deflate
|
||||||
|
connection:
|
||||||
|
- keep-alive
|
||||||
|
content-length:
|
||||||
|
- '295'
|
||||||
|
content-type:
|
||||||
|
- application/json
|
||||||
|
cookie:
|
||||||
|
- _cfuvid=8NrWEBP3dDmc8p2.csR.EdsSwS8zFvzWI1kPICaK_fM-1737568015338-0.0.1.1-604800000;
|
||||||
|
__cf_bm=pKr3NwXmTZN9rMSlKvEX40VPKbrxF93QwDNHunL2v8Y-1737568015-1.0.1.1-nR0EA7hYIwWpIBYUI53d9xQrUnl5iML6lgz4AGJW4ZGPBDxFma3PZ2cBhlr_hE7wKa5fV3r32eMu_rNWMXD.eA
|
||||||
|
host:
|
||||||
|
- api.openai.com
|
||||||
|
user-agent:
|
||||||
|
- OpenAI/Python 1.59.6
|
||||||
|
x-stainless-arch:
|
||||||
|
- arm64
|
||||||
|
x-stainless-async:
|
||||||
|
- 'false'
|
||||||
|
x-stainless-lang:
|
||||||
|
- python
|
||||||
|
x-stainless-os:
|
||||||
|
- MacOS
|
||||||
|
x-stainless-package-version:
|
||||||
|
- 1.59.6
|
||||||
|
x-stainless-raw-response:
|
||||||
|
- 'true'
|
||||||
|
x-stainless-retry-count:
|
||||||
|
- '0'
|
||||||
|
x-stainless-runtime:
|
||||||
|
- CPython
|
||||||
|
x-stainless-runtime-version:
|
||||||
|
- 3.12.7
|
||||||
|
method: POST
|
||||||
|
uri: https://api.openai.com/v1/chat/completions
|
||||||
|
response:
|
||||||
|
content: "{\n \"id\": \"chatcmpl-AsZJ8HKXQU9nTB7xbGAkKxqrg9BZ2\",\n \"object\":
|
||||||
|
\"chat.completion\",\n \"created\": 1737568798,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n
|
||||||
|
\ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\":
|
||||||
|
\"assistant\",\n \"content\": null,\n \"tool_calls\": [\n {\n
|
||||||
|
\ \"id\": \"call_mfvEs2jngeFloVZpZOHZVaKY\",\n \"type\":
|
||||||
|
\"function\",\n \"function\": {\n \"name\": \"get_current_year\",\n
|
||||||
|
\ \"arguments\": \"{}\"\n }\n }\n ],\n
|
||||||
|
\ \"refusal\": null\n },\n \"logprobs\": null,\n \"finish_reason\":
|
||||||
|
\"tool_calls\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 46,\n \"completion_tokens\":
|
||||||
|
12,\n \"total_tokens\": 58,\n \"prompt_tokens_details\": {\n \"cached_tokens\":
|
||||||
|
0,\n \"audio_tokens\": 0\n },\n \"completion_tokens_details\": {\n
|
||||||
|
\ \"reasoning_tokens\": 0,\n \"audio_tokens\": 0,\n \"accepted_prediction_tokens\":
|
||||||
|
0,\n \"rejected_prediction_tokens\": 0\n }\n },\n \"service_tier\":
|
||||||
|
\"default\",\n \"system_fingerprint\": \"fp_72ed7ab54c\"\n}\n"
|
||||||
|
headers:
|
||||||
|
CF-Cache-Status:
|
||||||
|
- DYNAMIC
|
||||||
|
CF-RAY:
|
||||||
|
- 906170e038281775-IAD
|
||||||
|
Connection:
|
||||||
|
- keep-alive
|
||||||
|
Content-Encoding:
|
||||||
|
- gzip
|
||||||
|
Content-Type:
|
||||||
|
- application/json
|
||||||
|
Date:
|
||||||
|
- Wed, 22 Jan 2025 17:59:59 GMT
|
||||||
|
Server:
|
||||||
|
- cloudflare
|
||||||
|
Transfer-Encoding:
|
||||||
|
- chunked
|
||||||
|
X-Content-Type-Options:
|
||||||
|
- nosniff
|
||||||
|
access-control-expose-headers:
|
||||||
|
- X-Request-ID
|
||||||
|
alt-svc:
|
||||||
|
- h3=":443"; ma=86400
|
||||||
|
openai-organization:
|
||||||
|
- crewai-iuxna1
|
||||||
|
openai-processing-ms:
|
||||||
|
- '416'
|
||||||
|
openai-version:
|
||||||
|
- '2020-10-01'
|
||||||
|
strict-transport-security:
|
||||||
|
- max-age=31536000; includeSubDomains; preload
|
||||||
|
x-ratelimit-limit-requests:
|
||||||
|
- '30000'
|
||||||
|
x-ratelimit-limit-tokens:
|
||||||
|
- '150000000'
|
||||||
|
x-ratelimit-remaining-requests:
|
||||||
|
- '29999'
|
||||||
|
x-ratelimit-remaining-tokens:
|
||||||
|
- '149999975'
|
||||||
|
x-ratelimit-reset-requests:
|
||||||
|
- 2ms
|
||||||
|
x-ratelimit-reset-tokens:
|
||||||
|
- 0s
|
||||||
|
x-request-id:
|
||||||
|
- req_4039a5e5772d1790a3131f0b1ea06139
|
||||||
|
http_version: HTTP/1.1
|
||||||
|
status_code: 200
|
||||||
|
version: 1
|
||||||
@@ -14,6 +14,7 @@ from crewai.agent import Agent
|
|||||||
from crewai.agents.cache import CacheHandler
|
from crewai.agents.cache import CacheHandler
|
||||||
from crewai.crew import Crew
|
from crewai.crew import Crew
|
||||||
from crewai.crews.crew_output import CrewOutput
|
from crewai.crews.crew_output import CrewOutput
|
||||||
|
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
|
||||||
from crewai.memory.contextual.contextual_memory import ContextualMemory
|
from crewai.memory.contextual.contextual_memory import ContextualMemory
|
||||||
from crewai.process import Process
|
from crewai.process import Process
|
||||||
from crewai.project import crew
|
from crewai.project import crew
|
||||||
@@ -555,12 +556,12 @@ def test_crew_with_delegating_agents_should_not_override_task_tools():
|
|||||||
_, kwargs = mock_execute_sync.call_args
|
_, kwargs = mock_execute_sync.call_args
|
||||||
tools = kwargs["tools"]
|
tools = kwargs["tools"]
|
||||||
|
|
||||||
assert any(
|
assert any(isinstance(tool, TestTool) for tool in tools), (
|
||||||
isinstance(tool, TestTool) for tool in tools
|
"TestTool should be present"
|
||||||
), "TestTool should be present"
|
)
|
||||||
assert any(
|
assert any("delegate" in tool.name.lower() for tool in tools), (
|
||||||
"delegate" in tool.name.lower() for tool in tools
|
"Delegation tool should be present"
|
||||||
), "Delegation tool should be present"
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
@@ -619,12 +620,12 @@ def test_crew_with_delegating_agents_should_not_override_agent_tools():
|
|||||||
_, kwargs = mock_execute_sync.call_args
|
_, kwargs = mock_execute_sync.call_args
|
||||||
tools = kwargs["tools"]
|
tools = kwargs["tools"]
|
||||||
|
|
||||||
assert any(
|
assert any(isinstance(tool, TestTool) for tool in new_ceo.tools), (
|
||||||
isinstance(tool, TestTool) for tool in new_ceo.tools
|
"TestTool should be present"
|
||||||
), "TestTool should be present"
|
)
|
||||||
assert any(
|
assert any("delegate" in tool.name.lower() for tool in tools), (
|
||||||
"delegate" in tool.name.lower() for tool in tools
|
"Delegation tool should be present"
|
||||||
), "Delegation tool should be present"
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
@@ -748,17 +749,17 @@ def test_task_tools_override_agent_tools_with_allow_delegation():
|
|||||||
used_tools = kwargs["tools"]
|
used_tools = kwargs["tools"]
|
||||||
|
|
||||||
# Confirm AnotherTestTool is present but TestTool is not
|
# Confirm AnotherTestTool is present but TestTool is not
|
||||||
assert any(
|
assert any(isinstance(tool, AnotherTestTool) for tool in used_tools), (
|
||||||
isinstance(tool, AnotherTestTool) for tool in used_tools
|
"AnotherTestTool should be present"
|
||||||
), "AnotherTestTool should be present"
|
)
|
||||||
assert not any(
|
assert not any(isinstance(tool, TestTool) for tool in used_tools), (
|
||||||
isinstance(tool, TestTool) for tool in used_tools
|
"TestTool should not be present among used tools"
|
||||||
), "TestTool should not be present among used tools"
|
)
|
||||||
|
|
||||||
# Confirm delegation tool(s) are present
|
# Confirm delegation tool(s) are present
|
||||||
assert any(
|
assert any("delegate" in tool.name.lower() for tool in used_tools), (
|
||||||
"delegate" in tool.name.lower() for tool in used_tools
|
"Delegation tool should be present"
|
||||||
), "Delegation tool should be present"
|
)
|
||||||
|
|
||||||
# Finally, make sure the agent's original tools remain unchanged
|
# Finally, make sure the agent's original tools remain unchanged
|
||||||
assert len(researcher_with_delegation.tools) == 1
|
assert len(researcher_with_delegation.tools) == 1
|
||||||
@@ -1466,7 +1467,6 @@ def test_dont_set_agents_step_callback_if_already_set():
|
|||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
def test_crew_function_calling_llm():
|
def test_crew_function_calling_llm():
|
||||||
|
|
||||||
from crewai import LLM
|
from crewai import LLM
|
||||||
from crewai.tools import tool
|
from crewai.tools import tool
|
||||||
|
|
||||||
@@ -1560,9 +1560,9 @@ def test_code_execution_flag_adds_code_tool_upon_kickoff():
|
|||||||
|
|
||||||
# Verify that exactly one tool was used and it was a CodeInterpreterTool
|
# Verify that exactly one tool was used and it was a CodeInterpreterTool
|
||||||
assert len(used_tools) == 1, "Should have exactly one tool"
|
assert len(used_tools) == 1, "Should have exactly one tool"
|
||||||
assert isinstance(
|
assert isinstance(used_tools[0], CodeInterpreterTool), (
|
||||||
used_tools[0], CodeInterpreterTool
|
"Tool should be CodeInterpreterTool"
|
||||||
), "Tool should be CodeInterpreterTool"
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
@@ -3107,9 +3107,9 @@ def test_fetch_inputs():
|
|||||||
expected_placeholders = {"role_detail", "topic", "field"}
|
expected_placeholders = {"role_detail", "topic", "field"}
|
||||||
actual_placeholders = crew.fetch_inputs()
|
actual_placeholders = crew.fetch_inputs()
|
||||||
|
|
||||||
assert (
|
assert actual_placeholders == expected_placeholders, (
|
||||||
actual_placeholders == expected_placeholders
|
f"Expected {expected_placeholders}, but got {actual_placeholders}"
|
||||||
), f"Expected {expected_placeholders}, but got {actual_placeholders}"
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_task_tools_preserve_code_execution_tools():
|
def test_task_tools_preserve_code_execution_tools():
|
||||||
@@ -3182,20 +3182,20 @@ def test_task_tools_preserve_code_execution_tools():
|
|||||||
used_tools = kwargs["tools"]
|
used_tools = kwargs["tools"]
|
||||||
|
|
||||||
# Verify all expected tools are present
|
# Verify all expected tools are present
|
||||||
assert any(
|
assert any(isinstance(tool, TestTool) for tool in used_tools), (
|
||||||
isinstance(tool, TestTool) for tool in used_tools
|
"Task's TestTool should be present"
|
||||||
), "Task's TestTool should be present"
|
)
|
||||||
assert any(
|
assert any(isinstance(tool, CodeInterpreterTool) for tool in used_tools), (
|
||||||
isinstance(tool, CodeInterpreterTool) for tool in used_tools
|
"CodeInterpreterTool should be present"
|
||||||
), "CodeInterpreterTool should be present"
|
)
|
||||||
assert any(
|
assert any("delegate" in tool.name.lower() for tool in used_tools), (
|
||||||
"delegate" in tool.name.lower() for tool in used_tools
|
"Delegation tool should be present"
|
||||||
), "Delegation tool should be present"
|
)
|
||||||
|
|
||||||
# Verify the total number of tools (TestTool + CodeInterpreter + 2 delegation tools)
|
# Verify the total number of tools (TestTool + CodeInterpreter + 2 delegation tools)
|
||||||
assert (
|
assert len(used_tools) == 4, (
|
||||||
len(used_tools) == 4
|
"Should have TestTool, CodeInterpreter, and 2 delegation tools"
|
||||||
), "Should have TestTool, CodeInterpreter, and 2 delegation tools"
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
@@ -3239,9 +3239,9 @@ def test_multimodal_flag_adds_multimodal_tools():
|
|||||||
used_tools = kwargs["tools"]
|
used_tools = kwargs["tools"]
|
||||||
|
|
||||||
# Check that the multimodal tool was added
|
# Check that the multimodal tool was added
|
||||||
assert any(
|
assert any(isinstance(tool, AddImageTool) for tool in used_tools), (
|
||||||
isinstance(tool, AddImageTool) for tool in used_tools
|
"AddImageTool should be present when agent is multimodal"
|
||||||
), "AddImageTool should be present when agent is multimodal"
|
)
|
||||||
|
|
||||||
# Verify we have exactly one tool (just the AddImageTool)
|
# Verify we have exactly one tool (just the AddImageTool)
|
||||||
assert len(used_tools) == 1, "Should only have the AddImageTool"
|
assert len(used_tools) == 1, "Should only have the AddImageTool"
|
||||||
@@ -3467,9 +3467,9 @@ def test_crew_guardrail_feedback_in_context():
|
|||||||
assert len(execution_contexts) > 1, "Task should have been executed multiple times"
|
assert len(execution_contexts) > 1, "Task should have been executed multiple times"
|
||||||
|
|
||||||
# Verify that the second execution included the guardrail feedback
|
# Verify that the second execution included the guardrail feedback
|
||||||
assert (
|
assert "Output must contain the keyword 'IMPORTANT'" in execution_contexts[1], (
|
||||||
"Output must contain the keyword 'IMPORTANT'" in execution_contexts[1]
|
"Guardrail feedback should be included in retry context"
|
||||||
), "Guardrail feedback should be included in retry context"
|
)
|
||||||
|
|
||||||
# Verify final output meets guardrail requirements
|
# Verify final output meets guardrail requirements
|
||||||
assert "IMPORTANT" in result.raw, "Final output should contain required keyword"
|
assert "IMPORTANT" in result.raw, "Final output should contain required keyword"
|
||||||
@@ -3494,7 +3494,6 @@ def test_before_kickoff_callback():
|
|||||||
|
|
||||||
@before_kickoff
|
@before_kickoff
|
||||||
def modify_inputs(self, inputs):
|
def modify_inputs(self, inputs):
|
||||||
|
|
||||||
self.inputs_modified = True
|
self.inputs_modified = True
|
||||||
inputs["modified"] = True
|
inputs["modified"] = True
|
||||||
return inputs
|
return inputs
|
||||||
@@ -3596,3 +3595,21 @@ def test_before_kickoff_without_inputs():
|
|||||||
# Verify that the inputs were initialized and modified inside the before_kickoff method
|
# Verify that the inputs were initialized and modified inside the before_kickoff method
|
||||||
assert test_crew_instance.received_inputs is not None
|
assert test_crew_instance.received_inputs is not None
|
||||||
assert test_crew_instance.received_inputs.get("modified") is True
|
assert test_crew_instance.received_inputs.get("modified") is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_crew_with_knowledge_sources_works_with_copy():
|
||||||
|
content = "Brandon's favorite color is red and he likes Mexican food."
|
||||||
|
string_source = StringKnowledgeSource(content=content)
|
||||||
|
|
||||||
|
crew = Crew(
|
||||||
|
agents=[researcher, writer],
|
||||||
|
tasks=[Task(description="test", expected_output="test", agent=researcher)],
|
||||||
|
knowledge_sources=[string_source],
|
||||||
|
)
|
||||||
|
|
||||||
|
crew_copy = crew.copy()
|
||||||
|
|
||||||
|
assert crew_copy.knowledge_sources == crew.knowledge_sources
|
||||||
|
assert len(crew_copy.agents) == len(crew.agents)
|
||||||
|
assert len(crew_copy.tasks) == len(crew.tasks)
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess
|
from crewai.agents.agent_builder.utilities.base_token_process import TokenProcess
|
||||||
from crewai.llm import LLM
|
from crewai.llm import LLM
|
||||||
|
from crewai.tools import tool
|
||||||
from crewai.utilities.token_counter_callback import TokenCalcHandler
|
from crewai.utilities.token_counter_callback import TokenCalcHandler
|
||||||
|
|
||||||
|
|
||||||
@@ -37,3 +39,166 @@ def test_llm_callback_replacement():
|
|||||||
assert usage_metrics_1.successful_requests == 1
|
assert usage_metrics_1.successful_requests == 1
|
||||||
assert usage_metrics_2.successful_requests == 1
|
assert usage_metrics_2.successful_requests == 1
|
||||||
assert usage_metrics_1 == calc_handler_1.token_cost_process.get_summary()
|
assert usage_metrics_1 == calc_handler_1.token_cost_process.get_summary()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_llm_call_with_string_input():
|
||||||
|
llm = LLM(model="gpt-4o-mini")
|
||||||
|
|
||||||
|
# Test the call method with a string input
|
||||||
|
result = llm.call("Return the name of a random city in the world.")
|
||||||
|
assert isinstance(result, str)
|
||||||
|
assert len(result.strip()) > 0 # Ensure the response is not empty
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_llm_call_with_string_input_and_callbacks():
|
||||||
|
llm = LLM(model="gpt-4o-mini")
|
||||||
|
calc_handler = TokenCalcHandler(token_cost_process=TokenProcess())
|
||||||
|
|
||||||
|
# Test the call method with a string input and callbacks
|
||||||
|
result = llm.call(
|
||||||
|
"Tell me a joke.",
|
||||||
|
callbacks=[calc_handler],
|
||||||
|
)
|
||||||
|
usage_metrics = calc_handler.token_cost_process.get_summary()
|
||||||
|
|
||||||
|
assert isinstance(result, str)
|
||||||
|
assert len(result.strip()) > 0
|
||||||
|
assert usage_metrics.successful_requests == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_llm_call_with_message_list():
|
||||||
|
llm = LLM(model="gpt-4o-mini")
|
||||||
|
messages = [{"role": "user", "content": "What is the capital of France?"}]
|
||||||
|
|
||||||
|
# Test the call method with a list of messages
|
||||||
|
result = llm.call(messages)
|
||||||
|
assert isinstance(result, str)
|
||||||
|
assert "Paris" in result
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_llm_call_with_tool_and_string_input():
|
||||||
|
llm = LLM(model="gpt-4o-mini")
|
||||||
|
|
||||||
|
def get_current_year() -> str:
|
||||||
|
"""Returns the current year as a string."""
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
return str(datetime.now().year)
|
||||||
|
|
||||||
|
# Create tool schema
|
||||||
|
tool_schema = {
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "get_current_year",
|
||||||
|
"description": "Returns the current year as a string.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {},
|
||||||
|
"required": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Available functions mapping
|
||||||
|
available_functions = {"get_current_year": get_current_year}
|
||||||
|
|
||||||
|
# Test the call method with a string input and tool
|
||||||
|
result = llm.call(
|
||||||
|
"What is the current year?",
|
||||||
|
tools=[tool_schema],
|
||||||
|
available_functions=available_functions,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(result, str)
|
||||||
|
assert result == get_current_year()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_llm_call_with_tool_and_message_list():
|
||||||
|
llm = LLM(model="gpt-4o-mini")
|
||||||
|
|
||||||
|
def square_number(number: int) -> int:
|
||||||
|
"""Returns the square of a number."""
|
||||||
|
return number * number
|
||||||
|
|
||||||
|
# Create tool schema
|
||||||
|
tool_schema = {
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "square_number",
|
||||||
|
"description": "Returns the square of a number.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"number": {"type": "integer", "description": "The number to square"}
|
||||||
|
},
|
||||||
|
"required": ["number"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Available functions mapping
|
||||||
|
available_functions = {"square_number": square_number}
|
||||||
|
|
||||||
|
messages = [{"role": "user", "content": "What is the square of 5?"}]
|
||||||
|
|
||||||
|
# Test the call method with messages and tool
|
||||||
|
result = llm.call(
|
||||||
|
messages,
|
||||||
|
tools=[tool_schema],
|
||||||
|
available_functions=available_functions,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(result, int)
|
||||||
|
assert result == 25
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||||
|
def test_llm_passes_additional_params():
|
||||||
|
llm = LLM(
|
||||||
|
model="gpt-4o-mini",
|
||||||
|
vertex_credentials="test_credentials",
|
||||||
|
vertex_project="test_project",
|
||||||
|
)
|
||||||
|
|
||||||
|
messages = [{"role": "user", "content": "Hello, world!"}]
|
||||||
|
|
||||||
|
with patch("litellm.completion") as mocked_completion:
|
||||||
|
# Create mocks for response structure
|
||||||
|
mock_message = MagicMock()
|
||||||
|
mock_message.content = "Test response"
|
||||||
|
mock_choice = MagicMock()
|
||||||
|
mock_choice.message = mock_message
|
||||||
|
mock_response = MagicMock()
|
||||||
|
mock_response.choices = [mock_choice]
|
||||||
|
mock_response.usage = {
|
||||||
|
"prompt_tokens": 5,
|
||||||
|
"completion_tokens": 5,
|
||||||
|
"total_tokens": 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set up the mocked completion to return the mock response
|
||||||
|
mocked_completion.return_value = mock_response
|
||||||
|
|
||||||
|
result = llm.call(messages)
|
||||||
|
|
||||||
|
# Assert that litellm.completion was called once
|
||||||
|
mocked_completion.assert_called_once()
|
||||||
|
|
||||||
|
# Retrieve the actual arguments with which litellm.completion was called
|
||||||
|
_, kwargs = mocked_completion.call_args
|
||||||
|
|
||||||
|
# Check that the additional_params were passed to litellm.completion
|
||||||
|
assert kwargs["vertex_credentials"] == "test_credentials"
|
||||||
|
assert kwargs["vertex_project"] == "test_project"
|
||||||
|
|
||||||
|
# Also verify that other expected parameters are present
|
||||||
|
assert kwargs["model"] == "gpt-4o-mini"
|
||||||
|
assert kwargs["messages"] == messages
|
||||||
|
|
||||||
|
# Check the result from llm.call
|
||||||
|
assert result == "Test response"
|
||||||
|
|||||||
@@ -779,6 +779,43 @@ def test_interpolate_only():
|
|||||||
assert result == no_placeholders
|
assert result == no_placeholders
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_only_with_dict_inside_expected_output():
|
||||||
|
"""Test the interpolate_only method for various scenarios including JSON structure preservation."""
|
||||||
|
task = Task(
|
||||||
|
description="Unused in this test",
|
||||||
|
expected_output="Unused in this test: {questions}",
|
||||||
|
)
|
||||||
|
|
||||||
|
json_string = '{"questions": {"main_question": "What is the user\'s name?", "secondary_question": "What is the user\'s age?"}}'
|
||||||
|
result = task.interpolate_only(
|
||||||
|
input_string=json_string,
|
||||||
|
inputs={
|
||||||
|
"questions": {
|
||||||
|
"main_question": "What is the user's name?",
|
||||||
|
"secondary_question": "What is the user's age?",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert '"main_question": "What is the user\'s name?"' in result
|
||||||
|
assert '"secondary_question": "What is the user\'s age?"' in result
|
||||||
|
assert result == json_string
|
||||||
|
|
||||||
|
normal_string = "Hello {name}, welcome to {place}!"
|
||||||
|
result = task.interpolate_only(
|
||||||
|
input_string=normal_string, inputs={"name": "John", "place": "CrewAI"}
|
||||||
|
)
|
||||||
|
assert result == "Hello John, welcome to CrewAI!"
|
||||||
|
|
||||||
|
result = task.interpolate_only(input_string="", inputs={"unused": "value"})
|
||||||
|
assert result == ""
|
||||||
|
|
||||||
|
no_placeholders = "Hello, this is a test"
|
||||||
|
result = task.interpolate_only(
|
||||||
|
input_string=no_placeholders, inputs={"unused": "value"}
|
||||||
|
)
|
||||||
|
assert result == no_placeholders
|
||||||
|
|
||||||
|
|
||||||
def test_task_output_str_with_pydantic():
|
def test_task_output_str_with_pydantic():
|
||||||
from crewai.tasks.output_format import OutputFormat
|
from crewai.tasks.output_format import OutputFormat
|
||||||
|
|
||||||
@@ -966,3 +1003,283 @@ def test_task_execution_times():
|
|||||||
assert task.start_time is not None
|
assert task.start_time is not None
|
||||||
assert task.end_time is not None
|
assert task.end_time is not None
|
||||||
assert task.execution_duration == (task.end_time - task.start_time).total_seconds()
|
assert task.execution_duration == (task.end_time - task.start_time).total_seconds()
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_with_list_of_strings():
|
||||||
|
task = Task(
|
||||||
|
description="Test list interpolation",
|
||||||
|
expected_output="List: {items}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test simple list of strings
|
||||||
|
input_str = "Available items: {items}"
|
||||||
|
inputs = {"items": ["apple", "banana", "cherry"]}
|
||||||
|
result = task.interpolate_only(input_str, inputs)
|
||||||
|
assert result == f"Available items: {inputs['items']}"
|
||||||
|
|
||||||
|
# Test empty list
|
||||||
|
empty_list_input = {"items": []}
|
||||||
|
result = task.interpolate_only(input_str, empty_list_input)
|
||||||
|
assert result == "Available items: []"
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_with_list_of_dicts():
|
||||||
|
task = Task(
|
||||||
|
description="Test list of dicts interpolation",
|
||||||
|
expected_output="People: {people}",
|
||||||
|
)
|
||||||
|
|
||||||
|
input_data = {
|
||||||
|
"people": [
|
||||||
|
{"name": "Alice", "age": 30, "skills": ["Python", "AI"]},
|
||||||
|
{"name": "Bob", "age": 25, "skills": ["Java", "Cloud"]},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
result = task.interpolate_only("{people}", input_data)
|
||||||
|
|
||||||
|
parsed_result = eval(result)
|
||||||
|
assert isinstance(parsed_result, list)
|
||||||
|
assert len(parsed_result) == 2
|
||||||
|
assert parsed_result[0]["name"] == "Alice"
|
||||||
|
assert parsed_result[0]["age"] == 30
|
||||||
|
assert parsed_result[0]["skills"] == ["Python", "AI"]
|
||||||
|
assert parsed_result[1]["name"] == "Bob"
|
||||||
|
assert parsed_result[1]["age"] == 25
|
||||||
|
assert parsed_result[1]["skills"] == ["Java", "Cloud"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_with_nested_structures():
|
||||||
|
task = Task(
|
||||||
|
description="Test nested structures",
|
||||||
|
expected_output="Company: {company}",
|
||||||
|
)
|
||||||
|
|
||||||
|
input_data = {
|
||||||
|
"company": {
|
||||||
|
"name": "TechCorp",
|
||||||
|
"departments": [
|
||||||
|
{
|
||||||
|
"name": "Engineering",
|
||||||
|
"employees": 50,
|
||||||
|
"tools": ["Git", "Docker", "Kubernetes"],
|
||||||
|
},
|
||||||
|
{"name": "Sales", "employees": 20, "regions": {"north": 5, "south": 3}},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = task.interpolate_only("{company}", input_data)
|
||||||
|
parsed = eval(result)
|
||||||
|
|
||||||
|
assert parsed["name"] == "TechCorp"
|
||||||
|
assert len(parsed["departments"]) == 2
|
||||||
|
assert parsed["departments"][0]["tools"] == ["Git", "Docker", "Kubernetes"]
|
||||||
|
assert parsed["departments"][1]["regions"]["north"] == 5
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_with_special_characters():
|
||||||
|
task = Task(
|
||||||
|
description="Test special characters in dicts",
|
||||||
|
expected_output="Data: {special_data}",
|
||||||
|
)
|
||||||
|
|
||||||
|
input_data = {
|
||||||
|
"special_data": {
|
||||||
|
"quotes": """This has "double" and 'single' quotes""",
|
||||||
|
"unicode": "文字化けテスト",
|
||||||
|
"symbols": "!@#$%^&*()",
|
||||||
|
"empty": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = task.interpolate_only("{special_data}", input_data)
|
||||||
|
parsed = eval(result)
|
||||||
|
|
||||||
|
assert parsed["quotes"] == """This has "double" and 'single' quotes"""
|
||||||
|
assert parsed["unicode"] == "文字化けテスト"
|
||||||
|
assert parsed["symbols"] == "!@#$%^&*()"
|
||||||
|
assert parsed["empty"] == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_mixed_types():
|
||||||
|
task = Task(
|
||||||
|
description="Test mixed type interpolation",
|
||||||
|
expected_output="Mixed: {data}",
|
||||||
|
)
|
||||||
|
|
||||||
|
input_data = {
|
||||||
|
"data": {
|
||||||
|
"name": "Test Dataset",
|
||||||
|
"samples": 1000,
|
||||||
|
"features": ["age", "income", "location"],
|
||||||
|
"metadata": {
|
||||||
|
"source": "public",
|
||||||
|
"validated": True,
|
||||||
|
"tags": ["demo", "test", "temp"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = task.interpolate_only("{data}", input_data)
|
||||||
|
parsed = eval(result)
|
||||||
|
|
||||||
|
assert parsed["name"] == "Test Dataset"
|
||||||
|
assert parsed["samples"] == 1000
|
||||||
|
assert parsed["metadata"]["tags"] == ["demo", "test", "temp"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_complex_combination():
|
||||||
|
task = Task(
|
||||||
|
description="Test complex combination",
|
||||||
|
expected_output="Report: {report}",
|
||||||
|
)
|
||||||
|
|
||||||
|
input_data = {
|
||||||
|
"report": [
|
||||||
|
{
|
||||||
|
"month": "January",
|
||||||
|
"metrics": {"sales": 15000, "expenses": 8000, "profit": 7000},
|
||||||
|
"top_products": ["Product A", "Product B"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "February",
|
||||||
|
"metrics": {"sales": 18000, "expenses": 8500, "profit": 9500},
|
||||||
|
"top_products": ["Product C", "Product D"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
result = task.interpolate_only("{report}", input_data)
|
||||||
|
parsed = eval(result)
|
||||||
|
|
||||||
|
assert len(parsed) == 2
|
||||||
|
assert parsed[0]["month"] == "January"
|
||||||
|
assert parsed[1]["metrics"]["profit"] == 9500
|
||||||
|
assert "Product D" in parsed[1]["top_products"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_invalid_type_validation():
|
||||||
|
task = Task(
|
||||||
|
description="Test invalid type validation",
|
||||||
|
expected_output="Should never reach here",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test with invalid top-level type
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
task.interpolate_only("{data}", {"data": set()}) # type: ignore we are purposely testing this failure
|
||||||
|
|
||||||
|
assert "Unsupported type set" in str(excinfo.value)
|
||||||
|
|
||||||
|
# Test with invalid nested type
|
||||||
|
invalid_nested = {
|
||||||
|
"profile": {
|
||||||
|
"name": "John",
|
||||||
|
"age": 30,
|
||||||
|
"tags": {"a", "b", "c"}, # Set is invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
task.interpolate_only("{data}", {"data": invalid_nested})
|
||||||
|
assert "Unsupported type set" in str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_custom_object_validation():
|
||||||
|
task = Task(
|
||||||
|
description="Test custom object rejection",
|
||||||
|
expected_output="Should never reach here",
|
||||||
|
)
|
||||||
|
|
||||||
|
class CustomObject:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
|
# Test with custom object at top level
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
task.interpolate_only("{obj}", {"obj": CustomObject(5)}) # type: ignore we are purposely testing this failure
|
||||||
|
assert "Unsupported type CustomObject" in str(excinfo.value)
|
||||||
|
|
||||||
|
# Test with nested custom object in dictionary
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
task.interpolate_only(
|
||||||
|
"{data}", {"data": {"valid": 1, "invalid": CustomObject(5)}}
|
||||||
|
)
|
||||||
|
assert "Unsupported type CustomObject" in str(excinfo.value)
|
||||||
|
|
||||||
|
# Test with nested custom object in list
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
task.interpolate_only("{data}", {"data": [1, "valid", CustomObject(5)]})
|
||||||
|
assert "Unsupported type CustomObject" in str(excinfo.value)
|
||||||
|
|
||||||
|
# Test with deeply nested custom object
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
task.interpolate_only(
|
||||||
|
"{data}", {"data": {"level1": {"level2": [{"level3": CustomObject(5)}]}}}
|
||||||
|
)
|
||||||
|
assert "Unsupported type CustomObject" in str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_valid_complex_types():
|
||||||
|
task = Task(
|
||||||
|
description="Test valid complex types",
|
||||||
|
expected_output="Validation should pass",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Valid complex structure
|
||||||
|
valid_data = {
|
||||||
|
"name": "Valid Dataset",
|
||||||
|
"stats": {
|
||||||
|
"count": 1000,
|
||||||
|
"distribution": [0.2, 0.3, 0.5],
|
||||||
|
"features": ["age", "income"],
|
||||||
|
"nested": {"deep": [1, 2, 3], "deeper": {"a": 1, "b": 2.5}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Should not raise any errors
|
||||||
|
result = task.interpolate_only("{data}", {"data": valid_data})
|
||||||
|
parsed = eval(result)
|
||||||
|
assert parsed["name"] == "Valid Dataset"
|
||||||
|
assert parsed["stats"]["nested"]["deeper"]["b"] == 2.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_edge_cases():
|
||||||
|
task = Task(
|
||||||
|
description="Test edge cases",
|
||||||
|
expected_output="Edge case handling",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test empty dict and list
|
||||||
|
assert task.interpolate_only("{}", {"data": {}}) == "{}"
|
||||||
|
assert task.interpolate_only("[]", {"data": []}) == "[]"
|
||||||
|
|
||||||
|
# Test numeric types
|
||||||
|
assert task.interpolate_only("{num}", {"num": 42}) == "42"
|
||||||
|
assert task.interpolate_only("{num}", {"num": 3.14}) == "3.14"
|
||||||
|
|
||||||
|
# Test boolean values (valid JSON types)
|
||||||
|
assert task.interpolate_only("{flag}", {"flag": True}) == "True"
|
||||||
|
assert task.interpolate_only("{flag}", {"flag": False}) == "False"
|
||||||
|
|
||||||
|
|
||||||
|
def test_interpolate_valid_types():
|
||||||
|
task = Task(
|
||||||
|
description="Test valid types including null and boolean",
|
||||||
|
expected_output="Should pass validation",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test with boolean and null values (valid JSON types)
|
||||||
|
valid_data = {
|
||||||
|
"name": "Test",
|
||||||
|
"active": True,
|
||||||
|
"deleted": False,
|
||||||
|
"optional": None,
|
||||||
|
"nested": {"flag": True, "empty": None},
|
||||||
|
}
|
||||||
|
|
||||||
|
result = task.interpolate_only("{data}", {"data": valid_data})
|
||||||
|
parsed = eval(result)
|
||||||
|
|
||||||
|
assert parsed["active"] is True
|
||||||
|
assert parsed["deleted"] is False
|
||||||
|
assert parsed["optional"] is None
|
||||||
|
assert parsed["nested"]["flag"] is True
|
||||||
|
assert parsed["nested"]["empty"] is None
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from crewai import Agent
|
|
||||||
from crewai.tools.agent_tools.base_agent_tools import BaseAgentTool
|
|
||||||
|
|
||||||
|
|
||||||
class InternalAgentTool(BaseAgentTool):
|
|
||||||
"""Concrete implementation of BaseAgentTool for testing."""
|
|
||||||
|
|
||||||
def _run(self, *args, **kwargs):
|
|
||||||
"""Implement required _run method."""
|
|
||||||
return "Test response"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"role_name,should_match",
|
|
||||||
[
|
|
||||||
("Futel Official Infopoint", True), # exact match
|
|
||||||
(' "Futel Official Infopoint" ', True), # extra quotes and spaces
|
|
||||||
("Futel Official Infopoint\n", True), # trailing newline
|
|
||||||
('"Futel Official Infopoint"', True), # embedded quotes
|
|
||||||
(" FUTEL\nOFFICIAL INFOPOINT ", True), # multiple whitespace and newline
|
|
||||||
],
|
|
||||||
)
|
|
||||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
|
||||||
def test_agent_tool_role_matching(role_name, should_match):
|
|
||||||
"""Test that agent tools can match roles regardless of case, whitespace, and special characters."""
|
|
||||||
# Create test agent
|
|
||||||
test_agent = Agent(
|
|
||||||
role="Futel Official Infopoint",
|
|
||||||
goal="Answer questions about Futel",
|
|
||||||
backstory="Futel Football Club info",
|
|
||||||
allow_delegation=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create test agent tool
|
|
||||||
agent_tool = InternalAgentTool(
|
|
||||||
name="test_tool", description="Test tool", agents=[test_agent]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test role matching
|
|
||||||
result = agent_tool._execute(agent_name=role_name, task="Test task", context=None)
|
|
||||||
|
|
||||||
if should_match:
|
|
||||||
assert (
|
|
||||||
"coworker mentioned not found" not in result.lower()
|
|
||||||
), f"Should find agent with role name: {role_name}"
|
|
||||||
else:
|
|
||||||
assert (
|
|
||||||
"coworker mentioned not found" in result.lower()
|
|
||||||
), f"Should not find agent with role name: {role_name}"
|
|
||||||
@@ -231,3 +231,255 @@ def test_validate_tool_input_with_special_characters():
|
|||||||
|
|
||||||
arguments = tool_usage._validate_tool_input(tool_input)
|
arguments = tool_usage._validate_tool_input(tool_input)
|
||||||
assert arguments == expected_arguments
|
assert arguments == expected_arguments
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_tool_input_none_input():
|
||||||
|
tool_usage = ToolUsage(
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
tools=[],
|
||||||
|
original_tools=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_names="",
|
||||||
|
task=MagicMock(),
|
||||||
|
function_calling_llm=None,
|
||||||
|
agent=MagicMock(),
|
||||||
|
action=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
arguments = tool_usage._validate_tool_input(None)
|
||||||
|
assert arguments == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_tool_input_valid_json():
|
||||||
|
tool_usage = ToolUsage(
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
tools=[],
|
||||||
|
original_tools=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_names="",
|
||||||
|
task=MagicMock(),
|
||||||
|
function_calling_llm=None,
|
||||||
|
agent=MagicMock(),
|
||||||
|
action=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
tool_input = '{"key": "value", "number": 42, "flag": true}'
|
||||||
|
expected_arguments = {"key": "value", "number": 42, "flag": True}
|
||||||
|
|
||||||
|
arguments = tool_usage._validate_tool_input(tool_input)
|
||||||
|
assert arguments == expected_arguments
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_tool_input_python_dict():
|
||||||
|
tool_usage = ToolUsage(
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
tools=[],
|
||||||
|
original_tools=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_names="",
|
||||||
|
task=MagicMock(),
|
||||||
|
function_calling_llm=None,
|
||||||
|
agent=MagicMock(),
|
||||||
|
action=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
tool_input = "{'key': 'value', 'number': 42, 'flag': True}"
|
||||||
|
expected_arguments = {"key": "value", "number": 42, "flag": True}
|
||||||
|
|
||||||
|
arguments = tool_usage._validate_tool_input(tool_input)
|
||||||
|
assert arguments == expected_arguments
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_tool_input_json5_unquoted_keys():
|
||||||
|
tool_usage = ToolUsage(
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
tools=[],
|
||||||
|
original_tools=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_names="",
|
||||||
|
task=MagicMock(),
|
||||||
|
function_calling_llm=None,
|
||||||
|
agent=MagicMock(),
|
||||||
|
action=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
tool_input = "{key: 'value', number: 42, flag: true}"
|
||||||
|
expected_arguments = {"key": "value", "number": 42, "flag": True}
|
||||||
|
|
||||||
|
arguments = tool_usage._validate_tool_input(tool_input)
|
||||||
|
assert arguments == expected_arguments
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_tool_input_with_trailing_commas():
|
||||||
|
tool_usage = ToolUsage(
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
tools=[],
|
||||||
|
original_tools=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_names="",
|
||||||
|
task=MagicMock(),
|
||||||
|
function_calling_llm=None,
|
||||||
|
agent=MagicMock(),
|
||||||
|
action=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
tool_input = '{"key": "value", "number": 42, "flag": true,}'
|
||||||
|
expected_arguments = {"key": "value", "number": 42, "flag": True}
|
||||||
|
|
||||||
|
arguments = tool_usage._validate_tool_input(tool_input)
|
||||||
|
assert arguments == expected_arguments
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_tool_input_invalid_input():
|
||||||
|
tool_usage = ToolUsage(
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
tools=[],
|
||||||
|
original_tools=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_names="",
|
||||||
|
task=MagicMock(),
|
||||||
|
function_calling_llm=None,
|
||||||
|
agent=MagicMock(),
|
||||||
|
action=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
invalid_inputs = [
|
||||||
|
"Just a string",
|
||||||
|
"['list', 'of', 'values']",
|
||||||
|
"12345",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
for invalid_input in invalid_inputs:
|
||||||
|
with pytest.raises(Exception) as e_info:
|
||||||
|
tool_usage._validate_tool_input(invalid_input)
|
||||||
|
assert (
|
||||||
|
"Tool input must be a valid dictionary in JSON or Python literal format"
|
||||||
|
in str(e_info.value)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test for None input separately
|
||||||
|
arguments = tool_usage._validate_tool_input(None)
|
||||||
|
assert arguments == {} # Expecting an empty dictionary
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_tool_input_complex_structure():
|
||||||
|
tool_usage = ToolUsage(
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
tools=[],
|
||||||
|
original_tools=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_names="",
|
||||||
|
task=MagicMock(),
|
||||||
|
function_calling_llm=None,
|
||||||
|
agent=MagicMock(),
|
||||||
|
action=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
tool_input = """
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"name": "Alice",
|
||||||
|
"age": 30
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{"id": 1, "value": "Item1"},
|
||||||
|
{"id": 2, "value": "Item2",}
|
||||||
|
],
|
||||||
|
"active": true,
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
expected_arguments = {
|
||||||
|
"user": {"name": "Alice", "age": 30},
|
||||||
|
"items": [
|
||||||
|
{"id": 1, "value": "Item1"},
|
||||||
|
{"id": 2, "value": "Item2"},
|
||||||
|
],
|
||||||
|
"active": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments = tool_usage._validate_tool_input(tool_input)
|
||||||
|
assert arguments == expected_arguments
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_tool_input_code_content():
|
||||||
|
tool_usage = ToolUsage(
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
tools=[],
|
||||||
|
original_tools=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_names="",
|
||||||
|
task=MagicMock(),
|
||||||
|
function_calling_llm=None,
|
||||||
|
agent=MagicMock(),
|
||||||
|
action=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
tool_input = '{"filename": "script.py", "content": "def hello():\\n print(\'Hello, world!\')"}'
|
||||||
|
expected_arguments = {
|
||||||
|
"filename": "script.py",
|
||||||
|
"content": "def hello():\n print('Hello, world!')",
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments = tool_usage._validate_tool_input(tool_input)
|
||||||
|
assert arguments == expected_arguments
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_tool_input_with_escaped_quotes():
|
||||||
|
tool_usage = ToolUsage(
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
tools=[],
|
||||||
|
original_tools=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_names="",
|
||||||
|
task=MagicMock(),
|
||||||
|
function_calling_llm=None,
|
||||||
|
agent=MagicMock(),
|
||||||
|
action=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
tool_input = '{"text": "He said, \\"Hello, world!\\""}'
|
||||||
|
expected_arguments = {"text": 'He said, "Hello, world!"'}
|
||||||
|
|
||||||
|
arguments = tool_usage._validate_tool_input(tool_input)
|
||||||
|
assert arguments == expected_arguments
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_tool_input_large_json_content():
|
||||||
|
tool_usage = ToolUsage(
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
tools=[],
|
||||||
|
original_tools=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_names="",
|
||||||
|
task=MagicMock(),
|
||||||
|
function_calling_llm=None,
|
||||||
|
agent=MagicMock(),
|
||||||
|
action=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Simulate a large JSON content
|
||||||
|
tool_input = (
|
||||||
|
'{"data": ' + json.dumps([{"id": i, "value": i * 2} for i in range(1000)]) + "}"
|
||||||
|
)
|
||||||
|
expected_arguments = {"data": [{"id": i, "value": i * 2} for i in range(1000)]}
|
||||||
|
|
||||||
|
arguments = tool_usage._validate_tool_input(tool_input)
|
||||||
|
assert arguments == expected_arguments
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_tool_input_none_input():
|
||||||
|
tool_usage = ToolUsage(
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
tools=[],
|
||||||
|
original_tools=[],
|
||||||
|
tools_description="",
|
||||||
|
tools_names="",
|
||||||
|
task=MagicMock(),
|
||||||
|
function_calling_llm=None,
|
||||||
|
agent=MagicMock(),
|
||||||
|
action=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
arguments = tool_usage._validate_tool_input(None)
|
||||||
|
assert arguments == {} # Expecting an empty dictionary
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ def test_evaluate_training_data(converter_mock):
|
|||||||
mock.call(
|
mock.call(
|
||||||
llm=original_agent.llm,
|
llm=original_agent.llm,
|
||||||
text="Assess the quality of the training data based on the llm output, human feedback , and llm "
|
text="Assess the quality of the training data based on the llm output, human feedback , and llm "
|
||||||
"output improved result.\n\nInitial Output:\nInitial output 1\n\nHuman Feedback:\nHuman feedback "
|
"output improved result.\n\nIteration: data1\nInitial Output:\nInitial output 1\n\nHuman Feedback:\nHuman feedback "
|
||||||
"1\n\nImproved Output:\nImproved output 1\n\nInitial Output:\nInitial output 2\n\nHuman "
|
"1\n\nImproved Output:\nImproved output 1\n\n------------------------------------------------\n\nIteration: data2\nInitial Output:\nInitial output 2\n\nHuman "
|
||||||
"Feedback:\nHuman feedback 2\n\nImproved Output:\nImproved output 2\n\nPlease provide:\n- Provide "
|
"Feedback:\nHuman feedback 2\n\nImproved Output:\nImproved output 2\n\n------------------------------------------------\n\nPlease provide:\n- Provide "
|
||||||
"a list of clear, actionable instructions derived from the Human Feedbacks to enhance the Agent's "
|
"a list of clear, actionable instructions derived from the Human Feedbacks to enhance the Agent's "
|
||||||
"performance. Analyze the differences between Initial Outputs and Improved Outputs to generate specific "
|
"performance. Analyze the differences between Initial Outputs and Improved Outputs to generate specific "
|
||||||
"action items for future tasks. Ensure all key and specificpoints from the human feedback are "
|
"action items for future tasks. Ensure all key and specificpoints from the human feedback are "
|
||||||
|
|||||||
106
uv.lock
generated
106
uv.lock
generated
@@ -649,7 +649,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crewai"
|
name = "crewai"
|
||||||
version = "0.98.0"
|
version = "0.100.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "appdirs" },
|
{ name = "appdirs" },
|
||||||
@@ -659,6 +659,7 @@ dependencies = [
|
|||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
{ name = "instructor" },
|
{ name = "instructor" },
|
||||||
{ name = "json-repair" },
|
{ name = "json-repair" },
|
||||||
|
{ name = "json5" },
|
||||||
{ name = "jsonref" },
|
{ name = "jsonref" },
|
||||||
{ name = "litellm" },
|
{ name = "litellm" },
|
||||||
{ name = "openai" },
|
{ name = "openai" },
|
||||||
@@ -735,10 +736,11 @@ requires-dist = [
|
|||||||
{ name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.32.1" },
|
{ name = "crewai-tools", marker = "extra == 'tools'", specifier = ">=0.32.1" },
|
||||||
{ name = "docling", marker = "extra == 'docling'", specifier = ">=2.12.0" },
|
{ name = "docling", marker = "extra == 'docling'", specifier = ">=2.12.0" },
|
||||||
{ name = "fastembed", marker = "extra == 'fastembed'", specifier = ">=0.4.1" },
|
{ name = "fastembed", marker = "extra == 'fastembed'", specifier = ">=0.4.1" },
|
||||||
{ name = "instructor", specifier = ">=1.3.3" },
|
{ name = "instructor", specifier = ">=1.7.2" },
|
||||||
{ name = "json-repair", specifier = ">=0.25.2" },
|
{ name = "json-repair", specifier = ">=0.25.2" },
|
||||||
|
{ name = "json5", specifier = ">=0.10.0" },
|
||||||
{ name = "jsonref", specifier = ">=1.1.0" },
|
{ name = "jsonref", specifier = ">=1.1.0" },
|
||||||
{ name = "litellm", specifier = "==1.57.4" },
|
{ name = "litellm", specifier = "==1.59.8" },
|
||||||
{ name = "mem0ai", marker = "extra == 'mem0'", specifier = ">=0.1.29" },
|
{ name = "mem0ai", marker = "extra == 'mem0'", specifier = ">=0.1.29" },
|
||||||
{ name = "openai", specifier = ">=1.13.3" },
|
{ name = "openai", specifier = ">=1.13.3" },
|
||||||
{ name = "openpyxl", specifier = ">=3.1.5" },
|
{ name = "openpyxl", specifier = ">=3.1.5" },
|
||||||
@@ -1959,7 +1961,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instructor"
|
name = "instructor"
|
||||||
version = "1.6.3"
|
version = "1.7.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiohttp" },
|
{ name = "aiohttp" },
|
||||||
@@ -1969,13 +1971,14 @@ dependencies = [
|
|||||||
{ name = "openai" },
|
{ name = "openai" },
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "pydantic-core" },
|
{ name = "pydantic-core" },
|
||||||
|
{ name = "requests" },
|
||||||
{ name = "rich" },
|
{ name = "rich" },
|
||||||
{ name = "tenacity" },
|
{ name = "tenacity" },
|
||||||
{ name = "typer" },
|
{ name = "typer" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b8/e6/21969fe0de9d278979872240b6af17510af8bd5020f6845891719c1d3eef/instructor-1.6.3.tar.gz", hash = "sha256:399cd90e30b5bc7cbd47acd7399c9c4e84926a96c20c8b5d00c5a04b41ed41ab", size = 56708 }
|
sdist = { url = "https://files.pythonhosted.org/packages/63/ba/692739c76959191aa7e5f0fccda871b36548355f4a09c8733687e64e62b0/instructor-1.7.2.tar.gz", hash = "sha256:6c01b2b159766df24865dc81f7bf8457cbda88a3c0bbc810da3467d19b185ed2", size = 66200177 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/98/c96bf0b1656173d06cd6c3a5adaf3ac429f86d5d696ae8e90e6eb15e89be/instructor-1.6.3-py3-none-any.whl", hash = "sha256:a8f973fea621c0188009b65a3429a526c24aeb249fc24100b605ea496e92d622", size = 69447 },
|
{ url = "https://files.pythonhosted.org/packages/c5/82/fd319382c1a33d7021cf151007b4cbd5daddf09d9ca5fb670e476668f9fc/instructor-1.7.2-py3-none-any.whl", hash = "sha256:cb43d27f6d7631c31762b936b2fcb44d2a3f9d8a020430a0f4d3484604ffb95b", size = 71353 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2026,46 +2029,46 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiter"
|
name = "jiter"
|
||||||
version = "0.5.0"
|
version = "0.8.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/1a/aa64be757afc614484b370a4d9fc1747dc9237b37ce464f7f9d9ca2a3d38/jiter-0.5.0.tar.gz", hash = "sha256:1d916ba875bcab5c5f7d927df998c4cb694d27dceddf3392e58beaf10563368a", size = 158300 }
|
sdist = { url = "https://files.pythonhosted.org/packages/f8/70/90bc7bd3932e651486861df5c8ffea4ca7c77d28e8532ddefe2abc561a53/jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d", size = 163007 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/af/09/f659fc67d6aaa82c56432c4a7cc8365fff763acbf1c8f24121076617f207/jiter-0.5.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b599f4e89b3def9a94091e6ee52e1d7ad7bc33e238ebb9c4c63f211d74822c3f", size = 284126 },
|
{ url = "https://files.pythonhosted.org/packages/f2/f3/8c11e0e87bd5934c414f9b1cfae3cbfd4a938d4669d57cb427e1c4d11a7f/jiter-0.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ca8577f6a413abe29b079bc30f907894d7eb07a865c4df69475e868d73e71c7b", size = 303381 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/2d/5bdaddfefc44f91af0f3340e75ef327950d790c9f86490757ac8b395c074/jiter-0.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a063f71c4b06225543dddadbe09d203dc0c95ba352d8b85f1221173480a71d5", size = 299265 },
|
{ url = "https://files.pythonhosted.org/packages/ea/28/4cd3f0bcbf40e946bc6a62a82c951afc386a25673d3d8d5ee461f1559bbe/jiter-0.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b25bd626bde7fb51534190c7e3cb97cee89ee76b76d7585580e22f34f5e3f393", size = 311718 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/74/bd/964485231deaec8caa6599f3f27c8787a54e9f9373ae80dcfbda2ad79c02/jiter-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc0d5b8b3dd12e91dd184b87273f864b363dfabc90ef29a1092d269f18c7e28", size = 332178 },
|
{ url = "https://files.pythonhosted.org/packages/0d/17/57acab00507e60bd954eaec0837d9d7b119b4117ff49b8a62f2b646f32ed/jiter-0.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c826a221851a8dc028eb6d7d6429ba03184fa3c7e83ae01cd6d3bd1d4bd17d", size = 335465 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cf/4f/6353179174db10254549bbf2eb2c7ea102e59e0460ee374adb12071c274d/jiter-0.5.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c22541f0b672f4d741382a97c65609332a783501551445ab2df137ada01e019e", size = 342533 },
|
{ url = "https://files.pythonhosted.org/packages/74/b9/1a3ddd2bc95ae17c815b021521020f40c60b32137730126bada962ef32b4/jiter-0.8.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d35c864c2dff13dfd79fb070fc4fc6235d7b9b359efe340e1261deb21b9fcb66", size = 355570 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/6f/21576071b8b056ef743129b9dacf9da65e328b58766f3d1ea265e966f000/jiter-0.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63314832e302cc10d8dfbda0333a384bf4bcfce80d65fe99b0f3c0da8945a91a", size = 363469 },
|
{ url = "https://files.pythonhosted.org/packages/78/69/6d29e2296a934199a7d0dde673ecccf98c9c8db44caf0248b3f2b65483cb/jiter-0.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f557c55bc2b7676e74d39d19bcb8775ca295c7a028246175d6a8b431e70835e5", size = 381383 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/73/a1/9ef99a279c72a031dbe8a4085db41e3521ae01ab0058651d6ccc809a5e93/jiter-0.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a25fbd8a5a58061e433d6fae6d5298777c0814a8bcefa1e5ecfff20c594bd749", size = 379078 },
|
{ url = "https://files.pythonhosted.org/packages/22/d7/fbc4c3fb1bf65f9be22a32759b539f88e897aeb13fe84ab0266e4423487a/jiter-0.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:580ccf358539153db147e40751a0b41688a5ceb275e6f3e93d91c9467f42b2e3", size = 390454 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/41/6a/c038077509d67fe876c724bfe9ad15334593851a7def0d84518172bdd44a/jiter-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:503b2c27d87dfff5ab717a8200fbbcf4714516c9d85558048b1fc14d2de7d8dc", size = 318943 },
|
{ url = "https://files.pythonhosted.org/packages/4d/a0/3993cda2e267fe679b45d0bcc2cef0b4504b0aa810659cdae9737d6bace9/jiter-0.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af102d3372e917cffce49b521e4c32c497515119dc7bd8a75665e90a718bbf08", size = 345039 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/0d/d82673814eb38c208b7881581df596e680f8c2c003e2b80c25ca58975ee4/jiter-0.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d1f3d27cce923713933a844872d213d244e09b53ec99b7a7fdf73d543529d6d", size = 357394 },
|
{ url = "https://files.pythonhosted.org/packages/b9/ef/69c18562b4c09ce88fab5df1dcaf643f6b1a8b970b65216e7221169b81c4/jiter-0.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cadcc978f82397d515bb2683fc0d50103acff2a180552654bb92d6045dec2c49", size = 376200 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/9e/cbd8f6612346c38cc42e41e35cda19ce78f5b12e4106d1186e8e95ee839b/jiter-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c95980207b3998f2c3b3098f357994d3fd7661121f30669ca7cb945f09510a87", size = 511080 },
|
{ url = "https://files.pythonhosted.org/packages/4d/17/0b5a8de46a6ab4d836f70934036278b49b8530c292b29dde3483326d4555/jiter-0.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba5bdf56969cad2019d4e8ffd3f879b5fdc792624129741d3d83fc832fef8c7d", size = 511158 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ff/33/135c0c33565b6d5c3010d047710837427dd24c9adbc9ca090f3f92df446e/jiter-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:afa66939d834b0ce063f57d9895e8036ffc41c4bd90e4a99631e5f261d9b518e", size = 492827 },
|
{ url = "https://files.pythonhosted.org/packages/6c/b2/c401a0a2554b36c9e6d6e4876b43790d75139cf3936f0222e675cbc23451/jiter-0.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3b94a33a241bee9e34b8481cdcaa3d5c2116f575e0226e421bed3f7a6ea71cff", size = 503956 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/68/c1/491a8ef682508edbaf2a32e41c1b1e34064078b369b0c2d141170999d1c9/jiter-0.5.0-cp310-none-win32.whl", hash = "sha256:f16ca8f10e62f25fd81d5310e852df6649af17824146ca74647a018424ddeccf", size = 195081 },
|
{ url = "https://files.pythonhosted.org/packages/d4/02/a0291ed7d72c0ac130f172354ee3cf0b2556b69584de391463a8ee534f40/jiter-0.8.2-cp310-cp310-win32.whl", hash = "sha256:6e5337bf454abddd91bd048ce0dca5134056fc99ca0205258766db35d0a2ea43", size = 202846 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/31/20/8cda4faa9571affea6130b150289522a22329778bdfa45a7aab4e7edff95/jiter-0.5.0-cp310-none-win_amd64.whl", hash = "sha256:b2950e4798e82dd9176935ef6a55cf6a448b5c71515a556da3f6b811a7844f1e", size = 190977 },
|
{ url = "https://files.pythonhosted.org/packages/ad/20/8c988831ae4bf437e29f1671e198fc99ba8fe49f2895f23789acad1d1811/jiter-0.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:4a9220497ca0cb1fe94e3f334f65b9b5102a0b8147646118f020d8ce1de70105", size = 204414 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/94/5f/3ac960ed598726aae46edea916e6df4df7ff6fe084bc60774b95cf3154e6/jiter-0.5.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4c8e1ed0ef31ad29cae5ea16b9e41529eb50a7fba70600008e9f8de6376d553", size = 284131 },
|
{ url = "https://files.pythonhosted.org/packages/cb/b0/c1a7caa7f9dc5f1f6cfa08722867790fe2d3645d6e7170ca280e6e52d163/jiter-0.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2dd61c5afc88a4fda7d8b2cf03ae5947c6ac7516d32b7a15bf4b49569a5c076b", size = 303666 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/03/eb/2308fa5f5c14c97c4c7720fef9465f1fa0771826cddb4eec9866bdd88846/jiter-0.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6f16e21276074a12d8421692515b3fd6d2ea9c94fd0734c39a12960a20e85f3", size = 299310 },
|
{ url = "https://files.pythonhosted.org/packages/f5/97/0468bc9eeae43079aaa5feb9267964e496bf13133d469cfdc135498f8dd0/jiter-0.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a6c710d657c8d1d2adbbb5c0b0c6bfcec28fd35bd6b5f016395f9ac43e878a15", size = 311934 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3c/f6/dba34ca10b44715fa5302b8e8d2113f72eb00a9297ddf3fa0ae4fd22d1d1/jiter-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5280e68e7740c8c128d3ae5ab63335ce6d1fb6603d3b809637b11713487af9e6", size = 332282 },
|
{ url = "https://files.pythonhosted.org/packages/e5/69/64058e18263d9a5f1e10f90c436853616d5f047d997c37c7b2df11b085ec/jiter-0.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9584de0cd306072635fe4b89742bf26feae858a0683b399ad0c2509011b9dc0", size = 335506 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/69/f7/64e0a7439790ec47f7681adb3871c9d9c45fff771102490bbee5e92c00b7/jiter-0.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:583c57fc30cc1fec360e66323aadd7fc3edeec01289bfafc35d3b9dcb29495e4", size = 342370 },
|
{ url = "https://files.pythonhosted.org/packages/9d/14/b747f9a77b8c0542141d77ca1e2a7523e854754af2c339ac89a8b66527d6/jiter-0.8.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a90a923338531b7970abb063cfc087eebae6ef8ec8139762007188f6bc69a9f", size = 355849 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/55/31/1efbfff2ae8e4d919144c53db19b828049ad0622a670be3bbea94a86282c/jiter-0.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26351cc14507bdf466b5f99aba3df3143a59da75799bf64a53a3ad3155ecded9", size = 363591 },
|
{ url = "https://files.pythonhosted.org/packages/53/e2/98a08161db7cc9d0e39bc385415890928ff09709034982f48eccfca40733/jiter-0.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21974d246ed0181558087cd9f76e84e8321091ebfb3a93d4c341479a736f099", size = 381700 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/30/c3/7ab2ca2276426a7398c6dfb651e38dbc81954c79a3bfbc36c514d8599499/jiter-0.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829df14d656b3fb87e50ae8b48253a8851c707da9f30d45aacab2aa2ba2d614", size = 378551 },
|
{ url = "https://files.pythonhosted.org/packages/7a/38/1674672954d35bce3b1c9af99d5849f9256ac8f5b672e020ac7821581206/jiter-0.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32475a42b2ea7b344069dc1e81445cfc00b9d0e3ca837f0523072432332e9f74", size = 389710 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/47/e7/5d88031cd743c62199b125181a591b1671df3ff2f6e102df85c58d8f7d31/jiter-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42a4bdcf7307b86cb863b2fb9bb55029b422d8f86276a50487982d99eed7c6e", size = 319152 },
|
{ url = "https://files.pythonhosted.org/packages/f8/9b/92f9da9a9e107d019bcf883cd9125fa1690079f323f5a9d5c6986eeec3c0/jiter-0.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9931fd36ee513c26b5bf08c940b0ac875de175341cbdd4fa3be109f0492586", size = 345553 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4c/2d/09ea58e1adca9f0359f3d41ef44a1a18e59518d7c43a21f4ece9e72e28c0/jiter-0.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04d461ad0aebf696f8da13c99bc1b3e06f66ecf6cfd56254cc402f6385231c06", size = 357377 },
|
{ url = "https://files.pythonhosted.org/packages/44/a6/6d030003394e9659cd0d7136bbeabd82e869849ceccddc34d40abbbbb269/jiter-0.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0820f4a3a59ddced7fce696d86a096d5cc48d32a4183483a17671a61edfddc", size = 376388 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7d/2f/83ff1058cb56fc3ff73e0d3c6440703ddc9cdb7f759b00cfbde8228fc435/jiter-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6375923c5f19888c9226582a124b77b622f8fd0018b843c45eeb19d9701c403", size = 511091 },
|
{ url = "https://files.pythonhosted.org/packages/ad/8d/87b09e648e4aca5f9af89e3ab3cfb93db2d1e633b2f2931ede8dabd9b19a/jiter-0.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ffc86ae5e3e6a93765d49d1ab47b6075a9c978a2b3b80f0f32628f39caa0c88", size = 511226 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ae/c9/4f85f97c9894382ab457382337aea0012711baaa17f2ed55c0ff25f3668a/jiter-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cec323a853c24fd0472517113768c92ae0be8f8c384ef4441d3632da8baa646", size = 492948 },
|
{ url = "https://files.pythonhosted.org/packages/77/95/8008ebe4cdc82eac1c97864a8042ca7e383ed67e0ec17bfd03797045c727/jiter-0.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5127dc1abd809431172bc3fbe8168d6b90556a30bb10acd5ded41c3cfd6f43b6", size = 504134 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4d/f2/2e987e0eb465e064c5f52c2f29c8d955452e3b316746e326269263bfb1b7/jiter-0.5.0-cp311-none-win32.whl", hash = "sha256:aa1db0967130b5cab63dfe4d6ff547c88b2a394c3410db64744d491df7f069bb", size = 195183 },
|
{ url = "https://files.pythonhosted.org/packages/26/0d/3056a74de13e8b2562e4d526de6dac2f65d91ace63a8234deb9284a1d24d/jiter-0.8.2-cp311-cp311-win32.whl", hash = "sha256:66227a2c7b575720c1871c8800d3a0122bb8ee94edb43a5685aa9aceb2782d44", size = 203103 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ab/59/05d1c3203c349b37c4dd28b02b9b4e5915a7bcbd9319173b4548a67d2e93/jiter-0.5.0-cp311-none-win_amd64.whl", hash = "sha256:aa9d2b85b2ed7dc7697597dcfaac66e63c1b3028652f751c81c65a9f220899ae", size = 191032 },
|
{ url = "https://files.pythonhosted.org/packages/4e/1e/7f96b798f356e531ffc0f53dd2f37185fac60fae4d6c612bbbd4639b90aa/jiter-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:cde031d8413842a1e7501e9129b8e676e62a657f8ec8166e18a70d94d4682855", size = 206717 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/aa/bd/c3950e2c478161e131bed8cb67c36aed418190e2a961a1c981e69954e54b/jiter-0.5.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9f664e7351604f91dcdd557603c57fc0d551bc65cc0a732fdacbf73ad335049a", size = 283511 },
|
{ url = "https://files.pythonhosted.org/packages/a1/17/c8747af8ea4e045f57d6cfd6fc180752cab9bc3de0e8a0c9ca4e8af333b1/jiter-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e6ec2be506e7d6f9527dae9ff4b7f54e68ea44a0ef6b098256ddf895218a2f8f", size = 302027 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/1c/8ce58d8c37a589eeaaa5d07d131fd31043886f5e77ab50c00a66d869a361/jiter-0.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:044f2f1148b5248ad2c8c3afb43430dccf676c5a5834d2f5089a4e6c5bbd64df", size = 296974 },
|
{ url = "https://files.pythonhosted.org/packages/3c/c1/6da849640cd35a41e91085723b76acc818d4b7d92b0b6e5111736ce1dd10/jiter-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76e324da7b5da060287c54f2fabd3db5f76468006c811831f051942bf68c9d44", size = 310326 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4d/b8/6faeff9eed8952bed93a77ea1cffae7b946795b88eafd1a60e87a67b09e0/jiter-0.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:702e3520384c88b6e270c55c772d4bd6d7b150608dcc94dea87ceba1b6391248", size = 331897 },
|
{ url = "https://files.pythonhosted.org/packages/06/99/a2bf660d8ccffee9ad7ed46b4f860d2108a148d0ea36043fd16f4dc37e94/jiter-0.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:180a8aea058f7535d1c84183c0362c710f4750bef66630c05f40c93c2b152a0f", size = 334242 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4f/54/1d9a2209b46d39ce6f0cef3ad87c462f9c50312ab84585e6bd5541292b35/jiter-0.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:528d742dcde73fad9d63e8242c036ab4a84389a56e04efd854062b660f559544", size = 342962 },
|
{ url = "https://files.pythonhosted.org/packages/a7/5f/cea1c17864828731f11427b9d1ab7f24764dbd9aaf4648a7f851164d2718/jiter-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025337859077b41548bdcbabe38698bcd93cfe10b06ff66617a48ff92c9aec60", size = 356654 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/de/90360be7fc54b2b4c2dfe79eb4ed1f659fce9c96682e6a0be4bbe71371f7/jiter-0.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf80e5fe6ab582c82f0c3331df27a7e1565e2dcf06265afd5173d809cdbf9ba", size = 363844 },
|
{ url = "https://files.pythonhosted.org/packages/e9/13/62774b7e5e7f5d5043efe1d0f94ead66e6d0f894ae010adb56b3f788de71/jiter-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecff0dc14f409599bbcafa7e470c00b80f17abc14d1405d38ab02e4b42e55b57", size = 379967 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ba/ad/ef32b173191b7a53ea8a6757b80723cba321f8469834825e8c71c96bde17/jiter-0.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:44dfc9ddfb9b51a5626568ef4e55ada462b7328996294fe4d36de02fce42721f", size = 378709 },
|
{ url = "https://files.pythonhosted.org/packages/ec/fb/096b34c553bb0bd3f2289d5013dcad6074948b8d55212aa13a10d44c5326/jiter-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd9fee7d0775ebaba131f7ca2e2d83839a62ad65e8e02fe2bd8fc975cedeb9e", size = 389252 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/07/de/353ce53743c0defbbbd652e89c106a97dbbac4eb42c95920b74b5056b93a/jiter-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c451f7922992751a936b96c5f5b9bb9312243d9b754c34b33d0cb72c84669f4e", size = 319038 },
|
{ url = "https://files.pythonhosted.org/packages/17/61/beea645c0bf398ced8b199e377b61eb999d8e46e053bb285c91c3d3eaab0/jiter-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14601dcac4889e0a1c75ccf6a0e4baf70dbc75041e51bcf8d0e9274519df6887", size = 345490 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3f/92/42d47310bf9530b9dece9e2d7c6d51cf419af5586ededaf5e66622d160e2/jiter-0.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:308fce789a2f093dca1ff91ac391f11a9f99c35369117ad5a5c6c4903e1b3e3a", size = 357763 },
|
{ url = "https://files.pythonhosted.org/packages/d5/df/834aa17ad5dcc3cf0118821da0a0cf1589ea7db9832589278553640366bc/jiter-0.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92249669925bc1c54fcd2ec73f70f2c1d6a817928480ee1c65af5f6b81cdf12d", size = 376991 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/bd/8c/2bb76a9a84474d48fdd133d3445db8a4413da4e87c23879d917e000a9d87/jiter-0.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7f5ad4a7c6b0d90776fdefa294f662e8a86871e601309643de30bf94bb93a64e", size = 511031 },
|
{ url = "https://files.pythonhosted.org/packages/67/80/87d140399d382fb4ea5b3d56e7ecaa4efdca17cd7411ff904c1517855314/jiter-0.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e725edd0929fa79f8349ab4ec7f81c714df51dc4e991539a578e5018fa4a7152", size = 510822 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/33/4f/9f23d79c0795e0a8e56e7988e8785c2dcda27e0ed37977256d50c77c6a19/jiter-0.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ea189db75f8eca08807d02ae27929e890c7d47599ce3d0a6a5d41f2419ecf338", size = 493042 },
|
{ url = "https://files.pythonhosted.org/packages/5c/37/3394bb47bac1ad2cb0465601f86828a0518d07828a650722e55268cdb7e6/jiter-0.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf55846c7b7a680eebaf9c3c48d630e1bf51bdf76c68a5f654b8524335b0ad29", size = 503730 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/67/8a4f975aa834b8aecdb6b131422390173928fd47f42f269dcc32034ab432/jiter-0.5.0-cp312-none-win32.whl", hash = "sha256:e3bbe3910c724b877846186c25fe3c802e105a2c1fc2b57d6688b9f8772026e4", size = 195405 },
|
{ url = "https://files.pythonhosted.org/packages/f9/e2/253fc1fa59103bb4e3aa0665d6ceb1818df1cd7bf3eb492c4dad229b1cd4/jiter-0.8.2-cp312-cp312-win32.whl", hash = "sha256:7efe4853ecd3d6110301665a5178b9856be7e2a9485f49d91aa4d737ad2ae49e", size = 203375 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/15/81/296b1e25c43db67848728cdab34ac3eb5c5cbb4955ceb3f51ae60d4a5e3d/jiter-0.5.0-cp312-none-win_amd64.whl", hash = "sha256:a586832f70c3f1481732919215f36d41c59ca080fa27a65cf23d9490e75b2ef5", size = 189720 },
|
{ url = "https://files.pythonhosted.org/packages/41/69/6d4bbe66b3b3b4507e47aa1dd5d075919ad242b4b1115b3f80eecd443687/jiter-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:83c0efd80b29695058d0fd2fa8a556490dbce9804eac3e281f373bbc99045f6c", size = 204740 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2077,6 +2080,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/23/38/34cb843cee4c5c27aa5c822e90e99bf96feb3dfa705713b5b6e601d17f5c/json_repair-0.30.0-py3-none-any.whl", hash = "sha256:bda4a5552dc12085c6363ff5acfcdb0c9cafc629989a2112081b7e205828228d", size = 17641 },
|
{ url = "https://files.pythonhosted.org/packages/23/38/34cb843cee4c5c27aa5c822e90e99bf96feb3dfa705713b5b6e601d17f5c/json_repair-0.30.0-py3-none-any.whl", hash = "sha256:bda4a5552dc12085c6363ff5acfcdb0c9cafc629989a2112081b7e205828228d", size = 17641 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "json5"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/85/3d/bbe62f3d0c05a689c711cff57b2e3ac3d3e526380adb7c781989f075115c/json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559", size = 48202 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/42/797895b952b682c3dafe23b1834507ee7f02f4d6299b65aaa61425763278/json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa", size = 34049 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jsonlines"
|
name = "jsonlines"
|
||||||
version = "3.1.0"
|
version = "3.1.0"
|
||||||
@@ -2363,7 +2375,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litellm"
|
name = "litellm"
|
||||||
version = "1.57.4"
|
version = "1.59.8"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiohttp" },
|
{ name = "aiohttp" },
|
||||||
@@ -2378,9 +2390,9 @@ dependencies = [
|
|||||||
{ name = "tiktoken" },
|
{ name = "tiktoken" },
|
||||||
{ name = "tokenizers" },
|
{ name = "tokenizers" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/1a/9a/115bde058901b087e7fec1bed4be47baf8d5c78aff7dd2ffebcb922003ff/litellm-1.57.4.tar.gz", hash = "sha256:747a870ddee9c71f9560fc68ad02485bc1008fcad7d7a43e87867a59b8ed0669", size = 6304427 }
|
sdist = { url = "https://files.pythonhosted.org/packages/86/b0/c8ec06bd1c87a92d6d824008982b3c82b450d7bd3be850a53913f1ac4907/litellm-1.59.8.tar.gz", hash = "sha256:9d645cc4460f6a9813061f07086648c4c3d22febc8e1f21c663f2b7750d90512", size = 6428607 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/9f/72/35c8509cb2a37343c213b794420405cbef2e1fdf8626ee981fcbba3d7c5c/litellm-1.57.4-py3-none-any.whl", hash = "sha256:afe48924d8a36db801018970a101622fce33d117fe9c54441c0095c491511abb", size = 6592126 },
|
{ url = "https://files.pythonhosted.org/packages/b9/38/889da058f566ef9ea321aafa25e423249492cf2a508dfdc0e5acfcf04526/litellm-1.59.8-py3-none-any.whl", hash = "sha256:2473914bd2343485a185dfe7eedb12ee5fda32da3c9d9a8b73f6966b9b20cf39", size = 6716233 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user