mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-20 13:28:13 +00:00
Merge branch 'main' into Add-Opik-to-docs
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
import uuid
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from typing import Any, Dict, List, Union
|
from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ def export_state(flow: Flow) -> dict[str, Serializable]:
|
|||||||
|
|
||||||
|
|
||||||
def to_serializable(
|
def to_serializable(
|
||||||
obj: Any, max_depth: int = 5, _current_depth: int = 0
|
obj: Any, exclude: set[str] | None = None, max_depth: int = 5, _current_depth: int = 0
|
||||||
) -> Serializable:
|
) -> Serializable:
|
||||||
"""Converts a Python object into a JSON-compatible representation.
|
"""Converts a Python object into a JSON-compatible representation.
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ def to_serializable(
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj (Any): Object to transform.
|
obj (Any): Object to transform.
|
||||||
|
exclude (set[str], optional): Set of keys to exclude from the result.
|
||||||
max_depth (int, optional): Maximum recursion depth. Defaults to 5.
|
max_depth (int, optional): Maximum recursion depth. Defaults to 5.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -50,21 +52,39 @@ def to_serializable(
|
|||||||
if _current_depth >= max_depth:
|
if _current_depth >= max_depth:
|
||||||
return repr(obj)
|
return repr(obj)
|
||||||
|
|
||||||
|
if exclude is None:
|
||||||
|
exclude = set()
|
||||||
|
|
||||||
if isinstance(obj, (str, int, float, bool, type(None))):
|
if isinstance(obj, (str, int, float, bool, type(None))):
|
||||||
return obj
|
return obj
|
||||||
|
elif isinstance(obj, uuid.UUID):
|
||||||
|
return str(obj)
|
||||||
elif isinstance(obj, (date, datetime)):
|
elif isinstance(obj, (date, datetime)):
|
||||||
return obj.isoformat()
|
return obj.isoformat()
|
||||||
elif isinstance(obj, (list, tuple, set)):
|
elif isinstance(obj, (list, tuple, set)):
|
||||||
return [to_serializable(item, max_depth, _current_depth + 1) for item in obj]
|
return [
|
||||||
|
to_serializable(
|
||||||
|
item, max_depth=max_depth, _current_depth=_current_depth + 1
|
||||||
|
)
|
||||||
|
for item in obj
|
||||||
|
]
|
||||||
elif isinstance(obj, dict):
|
elif isinstance(obj, dict):
|
||||||
return {
|
return {
|
||||||
_to_serializable_key(key): to_serializable(
|
_to_serializable_key(key): to_serializable(
|
||||||
value, max_depth, _current_depth + 1
|
obj=value,
|
||||||
|
exclude=exclude,
|
||||||
|
max_depth=max_depth,
|
||||||
|
_current_depth=_current_depth + 1,
|
||||||
)
|
)
|
||||||
for key, value in obj.items()
|
for key, value in obj.items()
|
||||||
|
if key not in exclude
|
||||||
}
|
}
|
||||||
elif isinstance(obj, BaseModel):
|
elif isinstance(obj, BaseModel):
|
||||||
return to_serializable(obj.model_dump(), max_depth, _current_depth + 1)
|
return to_serializable(
|
||||||
|
obj=obj.model_dump(exclude=exclude),
|
||||||
|
max_depth=max_depth,
|
||||||
|
_current_depth=_current_depth + 1,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return repr(obj)
|
return repr(obj)
|
||||||
|
|
||||||
|
|||||||
@@ -572,7 +572,15 @@ class Task(BaseModel):
|
|||||||
def copy(
|
def copy(
|
||||||
self, agents: List["BaseAgent"], task_mapping: Dict[str, "Task"]
|
self, agents: List["BaseAgent"], task_mapping: Dict[str, "Task"]
|
||||||
) -> "Task":
|
) -> "Task":
|
||||||
"""Create a deep copy of the Task."""
|
"""Creates a deep copy of the Task while preserving its original class type.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
agents: List of agents available for the task.
|
||||||
|
task_mapping: Dictionary mapping task IDs to Task instances.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A copy of the task with the same class type as the original.
|
||||||
|
"""
|
||||||
exclude = {
|
exclude = {
|
||||||
"id",
|
"id",
|
||||||
"agent",
|
"agent",
|
||||||
@@ -595,7 +603,7 @@ class Task(BaseModel):
|
|||||||
cloned_agent = get_agent_by_role(self.agent.role) if self.agent else None
|
cloned_agent = get_agent_by_role(self.agent.role) if self.agent else None
|
||||||
cloned_tools = copy(self.tools) if self.tools else []
|
cloned_tools = copy(self.tools) if self.tools else []
|
||||||
|
|
||||||
copied_task = Task(
|
copied_task = self.__class__(
|
||||||
**copied_data,
|
**copied_data,
|
||||||
context=cloned_context,
|
context=cloned_context,
|
||||||
agent=cloned_agent,
|
agent=cloned_agent,
|
||||||
|
|||||||
@@ -710,4 +710,117 @@ interactions:
|
|||||||
- req_4ceac9bc8ae57f631959b91d2ab63c4d
|
- req_4ceac9bc8ae57f631959b91d2ab63c4d
|
||||||
http_version: HTTP/1.1
|
http_version: HTTP/1.1
|
||||||
status_code: 200
|
status_code: 200
|
||||||
|
- request:
|
||||||
|
body: '{"messages": [{"role": "system", "content": "You are Test Agent. Test agent
|
||||||
|
backstory\nYour personal goal is: Test agent goal\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 description\n\nThis is the expected criteria for your final
|
||||||
|
answer: Test expected output\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:"]}'
|
||||||
|
headers:
|
||||||
|
accept:
|
||||||
|
- application/json
|
||||||
|
accept-encoding:
|
||||||
|
- gzip, deflate
|
||||||
|
connection:
|
||||||
|
- keep-alive
|
||||||
|
content-length:
|
||||||
|
- '840'
|
||||||
|
content-type:
|
||||||
|
- application/json
|
||||||
|
host:
|
||||||
|
- api.openai.com
|
||||||
|
user-agent:
|
||||||
|
- OpenAI/Python 1.61.0
|
||||||
|
x-stainless-arch:
|
||||||
|
- x64
|
||||||
|
x-stainless-async:
|
||||||
|
- 'false'
|
||||||
|
x-stainless-lang:
|
||||||
|
- python
|
||||||
|
x-stainless-os:
|
||||||
|
- MacOS
|
||||||
|
x-stainless-package-version:
|
||||||
|
- 1.61.0
|
||||||
|
x-stainless-raw-response:
|
||||||
|
- 'true'
|
||||||
|
x-stainless-retry-count:
|
||||||
|
- '0'
|
||||||
|
x-stainless-runtime:
|
||||||
|
- CPython
|
||||||
|
x-stainless-runtime-version:
|
||||||
|
- 3.12.9
|
||||||
|
method: POST
|
||||||
|
uri: https://api.openai.com/v1/chat/completions
|
||||||
|
response:
|
||||||
|
content: "{\n \"id\": \"chatcmpl-BExKOliqPgvHyozZaBu5oN50CHtsa\",\n \"object\":
|
||||||
|
\"chat.completion\",\n \"created\": 1742904348,\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: Test expected output\",\n \"refusal\": null,\n \"annotations\":
|
||||||
|
[]\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n
|
||||||
|
\ }\n ],\n \"usage\": {\n \"prompt_tokens\": 158,\n \"completion_tokens\":
|
||||||
|
15,\n \"total_tokens\": 173,\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_90d33c15d4\"\n}\n"
|
||||||
|
headers:
|
||||||
|
CF-RAY:
|
||||||
|
- 925e4749af02f227-GRU
|
||||||
|
Connection:
|
||||||
|
- keep-alive
|
||||||
|
Content-Encoding:
|
||||||
|
- gzip
|
||||||
|
Content-Type:
|
||||||
|
- application/json
|
||||||
|
Date:
|
||||||
|
- Tue, 25 Mar 2025 12:05:48 GMT
|
||||||
|
Server:
|
||||||
|
- cloudflare
|
||||||
|
Set-Cookie:
|
||||||
|
- __cf_bm=VHa7Z7dJYptxXpaMxgldvK6HqIM.m74xpi.80N_EBDc-1742904348-1.0.1.1-VthD2riCSnAprFYhOZxfIrTjT33tybJHpHWB25Q_Hx4vuACCyF00tix6e6eorDReGcW3jb5cUzbGqYi47TrMsS4LYjxBv5eCo7cU9OuFajs;
|
||||||
|
path=/; expires=Tue, 25-Mar-25 12:35:48 GMT; domain=.api.openai.com; HttpOnly;
|
||||||
|
Secure; SameSite=None
|
||||||
|
- _cfuvid=Is8fSaH3lU8yHyT3fI7cRZiDqIYSI6sPpzfzvEV8HMc-1742904348760-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
|
||||||
|
cf-cache-status:
|
||||||
|
- DYNAMIC
|
||||||
|
openai-organization:
|
||||||
|
- crewai-iuxna1
|
||||||
|
openai-processing-ms:
|
||||||
|
- '377'
|
||||||
|
openai-version:
|
||||||
|
- '2020-10-01'
|
||||||
|
strict-transport-security:
|
||||||
|
- max-age=31536000; includeSubDomains; preload
|
||||||
|
x-ratelimit-limit-requests:
|
||||||
|
- '50000'
|
||||||
|
x-ratelimit-limit-tokens:
|
||||||
|
- '150000000'
|
||||||
|
x-ratelimit-remaining-requests:
|
||||||
|
- '49999'
|
||||||
|
x-ratelimit-remaining-tokens:
|
||||||
|
- '149999822'
|
||||||
|
x-ratelimit-reset-requests:
|
||||||
|
- 1ms
|
||||||
|
x-ratelimit-reset-tokens:
|
||||||
|
- 0s
|
||||||
|
x-request-id:
|
||||||
|
- req_fd6b93e3b1a30868482c72306e7f63c2
|
||||||
|
http_version: HTTP/1.1
|
||||||
|
status_code: 200
|
||||||
version: 1
|
version: 1
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import pytest
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from crewai.flow import Flow
|
from crewai.flow import Flow
|
||||||
from crewai.flow.state_utils import export_state, to_string
|
from crewai.flow.state_utils import export_state, to_serializable, to_string
|
||||||
|
|
||||||
|
|
||||||
class Address(BaseModel):
|
class Address(BaseModel):
|
||||||
@@ -148,3 +148,23 @@ def test_depth_limit(mock_flow):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_exclude_keys():
|
||||||
|
result = to_serializable({"key1": "value1", "key2": "value2"}, exclude={"key1"})
|
||||||
|
assert result == {"key2": "value2"}
|
||||||
|
|
||||||
|
model = Person(
|
||||||
|
name="John Doe",
|
||||||
|
age=30,
|
||||||
|
address=Address(street="123 Main St", city="Tech City", country="Pythonia"),
|
||||||
|
birthday=date(1994, 1, 1),
|
||||||
|
skills=["Python", "Testing"],
|
||||||
|
)
|
||||||
|
result = to_serializable(model, exclude={"address"})
|
||||||
|
assert result == {
|
||||||
|
"name": "John Doe",
|
||||||
|
"age": 30,
|
||||||
|
"birthday": "1994-01-01",
|
||||||
|
"skills": ["Python", "Testing"],
|
||||||
|
}
|
||||||
|
|||||||
@@ -787,6 +787,25 @@ def test_conditional_task_definition_based_on_dict():
|
|||||||
assert task.agent is None
|
assert task.agent is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_conditional_task_copy_preserves_type():
|
||||||
|
task_config = {
|
||||||
|
"description": "Give me an integer score between 1-5 for the following title: 'The impact of AI in the future of work', check examples to based your evaluation.",
|
||||||
|
"expected_output": "The score of the title.",
|
||||||
|
}
|
||||||
|
original_task = Task(**task_config)
|
||||||
|
copied_task = original_task.copy(agents=[], task_mapping={})
|
||||||
|
assert isinstance(copied_task, Task)
|
||||||
|
|
||||||
|
original_conditional_config = {
|
||||||
|
"description": "Give me an integer score between 1-5 for the following title: 'The impact of AI in the future of work'. Check examples to base your evaluation on.",
|
||||||
|
"expected_output": "The score of the title.",
|
||||||
|
"condition": lambda x: True,
|
||||||
|
}
|
||||||
|
original_conditional_task = ConditionalTask(**original_conditional_config)
|
||||||
|
copied_conditional_task = original_conditional_task.copy(agents=[], task_mapping={})
|
||||||
|
assert isinstance(copied_conditional_task, ConditionalTask)
|
||||||
|
|
||||||
|
|
||||||
def test_interpolate_inputs():
|
def test_interpolate_inputs():
|
||||||
task = Task(
|
task = Task(
|
||||||
description="Give me a list of 5 interesting ideas about {topic} to explore for an article, what makes them unique and interesting.",
|
description="Give me a list of 5 interesting ideas about {topic} to explore for an article, what makes them unique and interesting.",
|
||||||
|
|||||||
Reference in New Issue
Block a user