From 4d3e094a407098b5635017c18e2e4d26266d6863 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 11:52:55 +0000 Subject: [PATCH] Fix SSL certificate verification issue in provider data fetching Co-Authored-By: Joe Moura --- src/crewai/cli/cli.py | 5 +- src/crewai/cli/create_crew.py | 6 +- src/crewai/cli/provider.py | 22 +- tests/cassettes/test_agent_human_input.yaml | 520 ++++++++++++++++++++ tests/cli/create_test.py | 40 ++ tests/cli/provider_test.py | 85 ++++ 6 files changed, 667 insertions(+), 11 deletions(-) create mode 100644 tests/cassettes/test_agent_human_input.yaml create mode 100644 tests/cli/create_test.py create mode 100644 tests/cli/provider_test.py diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index b2d59adbe..4ea0ce0a1 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -37,10 +37,11 @@ def crewai(): @click.argument("name") @click.option("--provider", type=str, help="The provider to use for the crew") @click.option("--skip_provider", is_flag=True, help="Skip provider validation") -def create(type, name, provider, skip_provider=False): +@click.option("--skip_ssl_verify", is_flag=True, help="Skip SSL certificate verification (not secure)") +def create(type, name, provider, skip_provider=False, skip_ssl_verify=False): """Create a new crew, or flow.""" if type == "crew": - create_crew(name, provider, skip_provider) + create_crew(name, provider, skip_provider, skip_ssl_verify=skip_ssl_verify) elif type == "flow": create_flow(name) else: diff --git a/src/crewai/cli/create_crew.py b/src/crewai/cli/create_crew.py index c658b0de1..1c8faa44d 100644 --- a/src/crewai/cli/create_crew.py +++ b/src/crewai/cli/create_crew.py @@ -89,12 +89,12 @@ def copy_template_files(folder_path, name, class_name, parent_folder): copy_template(src_file, dst_file, name, class_name, folder_path.name) -def create_crew(name, provider=None, skip_provider=False, parent_folder=None): +def create_crew(name, provider=None, skip_provider=False, parent_folder=None, skip_ssl_verify=False): folder_path, folder_name, class_name = create_folder_structure(name, parent_folder) env_vars = load_env_vars(folder_path) if not skip_provider: if not provider: - provider_models = get_provider_data() + provider_models = get_provider_data(skip_ssl_verify) if not provider_models: return @@ -114,7 +114,7 @@ def create_crew(name, provider=None, skip_provider=False, parent_folder=None): click.secho("Keeping existing provider configuration.", fg="yellow") return - provider_models = get_provider_data() + provider_models = get_provider_data(skip_ssl_verify) if not provider_models: return diff --git a/src/crewai/cli/provider.py b/src/crewai/cli/provider.py index 529ca5e26..09e50d97f 100644 --- a/src/crewai/cli/provider.py +++ b/src/crewai/cli/provider.py @@ -106,13 +106,14 @@ def select_model(provider, provider_models): return selected_model -def load_provider_data(cache_file, cache_expiry): +def load_provider_data(cache_file, cache_expiry, skip_ssl_verify=False): """ Loads provider data from a cache file if it exists and is not expired. If the cache is expired or corrupted, it fetches the data from the web. Args: - cache_file (Path): The path to the cache file. - cache_expiry (int): The cache expiry time in seconds. + - skip_ssl_verify (bool): Whether to skip SSL certificate verification. Returns: - dict or None: The loaded provider data or None if the operation fails. @@ -133,7 +134,7 @@ def load_provider_data(cache_file, cache_expiry): "Cache expired or not found. Fetching provider data from the web...", fg="cyan", ) - return fetch_provider_data(cache_file) + return fetch_provider_data(cache_file, skip_ssl_verify) def read_cache_file(cache_file): @@ -153,18 +154,24 @@ def read_cache_file(cache_file): return None -def fetch_provider_data(cache_file): +def fetch_provider_data(cache_file, skip_ssl_verify=False): """ Fetches provider data from a specified URL and caches it to a file. Args: - cache_file (Path): The path to the cache file. + - skip_ssl_verify (bool): Whether to skip SSL certificate verification. Returns: - dict or None: The fetched provider data or None if the operation fails. """ try: - response = requests.get(JSON_URL, stream=True, timeout=60) + if skip_ssl_verify: + click.secho( + "Warning: SSL certificate verification is disabled. This is not secure!", + fg="yellow", + ) + response = requests.get(JSON_URL, stream=True, timeout=60, verify=not skip_ssl_verify) response.raise_for_status() data = download_data(response) with open(cache_file, "w") as f: @@ -201,10 +208,13 @@ def download_data(response): return json.loads(data_content.decode("utf-8")) -def get_provider_data(): +def get_provider_data(skip_ssl_verify=False): """ Retrieves provider data from a cache file, filters out models based on provider criteria, and returns a dictionary of providers mapped to their models. + Args: + - skip_ssl_verify (bool): Whether to skip SSL certificate verification. + Returns: - dict or None: A dictionary of providers mapped to their models or None if the operation fails. """ @@ -213,7 +223,7 @@ def get_provider_data(): cache_file = cache_dir / "provider_cache.json" cache_expiry = 24 * 3600 - data = load_provider_data(cache_file, cache_expiry) + data = load_provider_data(cache_file, cache_expiry, skip_ssl_verify) if not data: return None diff --git a/tests/cassettes/test_agent_human_input.yaml b/tests/cassettes/test_agent_human_input.yaml new file mode 100644 index 000000000..8c5fd3a80 --- /dev/null +++ b/tests/cassettes/test_agent_human_input.yaml @@ -0,0 +1,520 @@ +interactions: +- request: + body: !!binary | + CqcXCiQKIgoMc2VydmljZS5uYW1lEhIKEGNyZXdBSS10ZWxlbWV0cnkS/hYKEgoQY3Jld2FpLnRl + bGVtZXRyeRJ5ChBuJJtOdNaB05mOW/p3915eEgj2tkAd3rZcASoQVG9vbCBVc2FnZSBFcnJvcjAB + OYa7/URvKBUYQUpcFEVvKBUYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYuMEoPCgNsbG0SCAoG + Z3B0LTRvegIYAYUBAAEAABLJBwoQifhX01E5i+5laGdALAlZBBIIBuGM1aN+OPgqDENyZXcgQ3Jl + YXRlZDABORVGruBvKBUYQaipwOBvKBUYShoKDmNyZXdhaV92ZXJzaW9uEggKBjAuODYuMEoaCg5w + eXRob25fdmVyc2lvbhIICgYzLjEyLjdKLgoIY3Jld19rZXkSIgogN2U2NjA4OTg5ODU5YTY3ZWVj + ODhlZWY3ZmNlODUyMjVKMQoHY3Jld19pZBImCiRiOThiNWEwMC01YTI1LTQxMDctYjQwNS1hYmYz + MjBhOGYzYThKHAoMY3Jld19wcm9jZXNzEgwKCnNlcXVlbnRpYWxKEQoLY3Jld19tZW1vcnkSAhAA + ShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tzEgIYAUobChVjcmV3X251bWJlcl9vZl9hZ2VudHMSAhgB + SuQCCgtjcmV3X2FnZW50cxLUAgrRAlt7ImtleSI6ICIyMmFjZDYxMWU0NGVmNWZhYzA1YjUzM2Q3 + NWU4ODkzYiIsICJpZCI6ICJkNWIyMzM1YS0yMmIyLTQyZWEtYmYwNS03OTc3NmU3MmYzOTIiLCAi + cm9sZSI6ICJEYXRhIFNjaWVudGlzdCIsICJ2ZXJib3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAy + MCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25fY2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJn + cHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJsZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4 + ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9saW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFsi + Z2V0IGdyZWV0aW5ncyJdfV1KkgIKCmNyZXdfdGFza3MSgwIKgAJbeyJrZXkiOiAiYTI3N2IzNGIy + YzE0NmYwYzU2YzVlMTM1NmU4ZjhhNTciLCAiaWQiOiAiMjJiZWMyMzEtY2QyMS00YzU4LTgyN2Ut + MDU4MWE4ZjBjMTExIiwgImFzeW5jX2V4ZWN1dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6 + IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJEYXRhIFNjaWVudGlzdCIsICJhZ2VudF9rZXkiOiAiMjJh + Y2Q2MTFlNDRlZjVmYWMwNWI1MzNkNzVlODg5M2IiLCAidG9vbHNfbmFtZXMiOiBbImdldCBncmVl + dGluZ3MiXX1degIYAYUBAAEAABKOAgoQ5WYoxRtTyPjge4BduhL0rRIIv2U6rvWALfwqDFRhc2sg + Q3JlYXRlZDABOX068uBvKBUYQZkv8+BvKBUYSi4KCGNyZXdfa2V5EiIKIDdlNjYwODk4OTg1OWE2 + N2VlYzg4ZWVmN2ZjZTg1MjI1SjEKB2NyZXdfaWQSJgokYjk4YjVhMDAtNWEyNS00MTA3LWI0MDUt + YWJmMzIwYThmM2E4Si4KCHRhc2tfa2V5EiIKIGEyNzdiMzRiMmMxNDZmMGM1NmM1ZTEzNTZlOGY4 + YTU3SjEKB3Rhc2tfaWQSJgokMjJiZWMyMzEtY2QyMS00YzU4LTgyN2UtMDU4MWE4ZjBjMTExegIY + AYUBAAEAABKQAQoQXyeDtJDFnyp2Fjk9YEGTpxIIaNE7gbhPNYcqClRvb2wgVXNhZ2UwATkaXTvj + bygVGEGvx0rjbygVGEoaCg5jcmV3YWlfdmVyc2lvbhIICgYwLjg2LjBKHAoJdG9vbF9uYW1lEg8K + DUdldCBHcmVldGluZ3NKDgoIYXR0ZW1wdHMSAhgBegIYAYUBAAEAABLVBwoQMWfznt0qwauEzl7T + UOQxRBII9q+pUS5EdLAqDENyZXcgQ3JlYXRlZDABORONPORvKBUYQSAoS+RvKBUYShoKDmNyZXdh + aV92ZXJzaW9uEggKBjAuODYuMEoaCg5weXRob25fdmVyc2lvbhIICgYzLjEyLjdKLgoIY3Jld19r + ZXkSIgogYzMwNzYwMDkzMjY3NjE0NDRkNTdjNzFkMWRhM2YyN2NKMQoHY3Jld19pZBImCiQ3OTQw + MTkyNS1iOGU5LTQ3MDgtODUzMC00NDhhZmEzYmY4YjBKHAoMY3Jld19wcm9jZXNzEgwKCnNlcXVl + bnRpYWxKEQoLY3Jld19tZW1vcnkSAhAAShoKFGNyZXdfbnVtYmVyX29mX3Rhc2tzEgIYAUobChVj + cmV3X251bWJlcl9vZl9hZ2VudHMSAhgBSuoCCgtjcmV3X2FnZW50cxLaAgrXAlt7ImtleSI6ICI5 + OGYzYjFkNDdjZTk2OWNmMDU3NzI3Yjc4NDE0MjVjZCIsICJpZCI6ICI5OTJkZjYyZi1kY2FiLTQy + OTUtOTIwNi05MDBkNDExNGIxZTkiLCAicm9sZSI6ICJGcmllbmRseSBOZWlnaGJvciIsICJ2ZXJi + b3NlPyI6IGZhbHNlLCAibWF4X2l0ZXIiOiAyMCwgIm1heF9ycG0iOiBudWxsLCAiZnVuY3Rpb25f + Y2FsbGluZ19sbG0iOiAiIiwgImxsbSI6ICJncHQtNG8tbWluaSIsICJkZWxlZ2F0aW9uX2VuYWJs + ZWQ/IjogZmFsc2UsICJhbGxvd19jb2RlX2V4ZWN1dGlvbj8iOiBmYWxzZSwgIm1heF9yZXRyeV9s + aW1pdCI6IDIsICJ0b29sc19uYW1lcyI6IFsiZGVjaWRlIGdyZWV0aW5ncyJdfV1KmAIKCmNyZXdf + dGFza3MSiQIKhgJbeyJrZXkiOiAiODBkN2JjZDQ5MDk5MjkwMDgzODMyZjBlOTgzMzgwZGYiLCAi + aWQiOiAiMmZmNjE5N2UtYmEyNy00YjczLWI0YTctNGZhMDQ4ZTYyYjQ3IiwgImFzeW5jX2V4ZWN1 + dGlvbj8iOiBmYWxzZSwgImh1bWFuX2lucHV0PyI6IGZhbHNlLCAiYWdlbnRfcm9sZSI6ICJGcmll + bmRseSBOZWlnaGJvciIsICJhZ2VudF9rZXkiOiAiOThmM2IxZDQ3Y2U5NjljZjA1NzcyN2I3ODQx + NDI1Y2QiLCAidG9vbHNfbmFtZXMiOiBbImRlY2lkZSBncmVldGluZ3MiXX1degIYAYUBAAEAABKO + AgoQnjTp5boK7/+DQxztYIpqihIIgGnMUkBtzHEqDFRhc2sgQ3JlYXRlZDABOcpYcuRvKBUYQalE + c+RvKBUYSi4KCGNyZXdfa2V5EiIKIGMzMDc2MDA5MzI2NzYxNDQ0ZDU3YzcxZDFkYTNmMjdjSjEK + B2NyZXdfaWQSJgokNzk0MDE5MjUtYjhlOS00NzA4LTg1MzAtNDQ4YWZhM2JmOGIwSi4KCHRhc2tf + a2V5EiIKIDgwZDdiY2Q0OTA5OTI5MDA4MzgzMmYwZTk4MzM4MGRmSjEKB3Rhc2tfaWQSJgokMmZm + NjE5N2UtYmEyNy00YjczLWI0YTctNGZhMDQ4ZTYyYjQ3egIYAYUBAAEAABKTAQoQ26H9pLUgswDN + p9XhJwwL6BIIx3bw7mAvPYwqClRvb2wgVXNhZ2UwATmy7NPlbygVGEEvb+HlbygVGEoaCg5jcmV3 + YWlfdmVyc2lvbhIICgYwLjg2LjBKHwoJdG9vbF9uYW1lEhIKEERlY2lkZSBHcmVldGluZ3NKDgoI + YXR0ZW1wdHMSAhgBegIYAYUBAAEAAA== + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2986' + Content-Type: + - application/x-protobuf + User-Agent: + - OTel-OTLP-Exporter-Python/1.27.0 + method: POST + uri: https://telemetry.crewai.com:4319/v1/traces + response: + body: + string: "\n\0" + headers: + Content-Length: + - '2' + Content-Type: + - application/x-protobuf + Date: + - Fri, 27 Dec 2024 22:14:53 GMT + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour + personal goal is: test goal\nTo give my best complete final answer to the task + use 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: Say the word: Hi\n\nThis + is the expect criteria for your final answer: The word: Hi\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-mini", "stop": ["\nObservation:"], + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '824' + content-type: + - application/json + cookie: + - _cfuvid=ePJSDFdHag2D8lj21_ijAMWjoA6xfnPNxN4uekvC728-1727226247743-0.0.1.1-604800000 + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - Linux + 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-AjCtZLLrWi8ZASpP9bz6HaCV7xBIn\",\n \"object\": + \"chat.completion\",\n \"created\": 1735337693,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"I now can give a great answer \\nFinal + Answer: Hi\",\n \"refusal\": null\n },\n \"logprobs\": null,\n + \ \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 158,\n \"completion_tokens\": 12,\n \"total_tokens\": 170,\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_0aa8d3e20b\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f8caa83deca756b-SEA + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 27 Dec 2024 22:14:53 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=wJkq_yLkzE3OdxE0aMJz.G0kce969.9JxRmZ0ratl4c-1735337693-1.0.1.1-OKpUoRrSPFGvWv5Hp5ET1PNZ7iZNHPKEAuakpcQUxxPSeisUIIR3qIOZ31MGmYugqB5.wkvidgbxOAagqJvmnw; + path=/; expires=Fri, 27-Dec-24 22:44:53 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=A_ASCLNAVfQoyucWOAIhecWtEpNotYoZr0bAFihgNxs-1735337693273-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: + - '404' + 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: + - '149999816' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_6ac84634bff9193743c4b0911c09b4a6 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"role": "system", "content": "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: \"Don''t say hi, say Hello + instead!\""}], "model": "gpt-4o-mini", "stop": ["\nObservation:"], "stream": + false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '461' + content-type: + - application/json + cookie: + - _cfuvid=A_ASCLNAVfQoyucWOAIhecWtEpNotYoZr0bAFihgNxs-1735337693273-0.0.1.1-604800000; + __cf_bm=wJkq_yLkzE3OdxE0aMJz.G0kce969.9JxRmZ0ratl4c-1735337693-1.0.1.1-OKpUoRrSPFGvWv5Hp5ET1PNZ7iZNHPKEAuakpcQUxxPSeisUIIR3qIOZ31MGmYugqB5.wkvidgbxOAagqJvmnw + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - Linux + 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-AjCtZNlWdrrPZhq0MJDqd16sMuQEJ\",\n \"object\": + \"chat.completion\",\n \"created\": 1735337693,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"True\",\n \"refusal\": null\n + \ },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n + \ ],\n \"usage\": {\n \"prompt_tokens\": 78,\n \"completion_tokens\": + 1,\n \"total_tokens\": 79,\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_0aa8d3e20b\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f8caa87094f756b-SEA + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 27 Dec 2024 22:14:53 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: + - '156' + 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: + - '149999898' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_ec74bef2a9ef7b2144c03fd7f7bbeab0 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"role": "system", "content": "You are test role. test backstory\nYour + personal goal is: test goal\nTo give my best complete final answer to the task + use 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: Say the word: Hi\n\nThis + is the expect criteria for your final answer: The word: Hi\nyou MUST return + the actual complete content as the final answer, not a summary.\n\nBegin! This + is VERY important to you, use the tools available and give your best Final Answer, + your job depends on it!\n\nThought:"}, {"role": "assistant", "content": "I now + can give a great answer \nFinal Answer: Hi"}, {"role": "user", "content": "Feedback: + Don''t say hi, say Hello instead!"}], "model": "gpt-4o-mini", "stop": ["\nObservation:"], + "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '986' + content-type: + - application/json + cookie: + - _cfuvid=A_ASCLNAVfQoyucWOAIhecWtEpNotYoZr0bAFihgNxs-1735337693273-0.0.1.1-604800000; + __cf_bm=wJkq_yLkzE3OdxE0aMJz.G0kce969.9JxRmZ0ratl4c-1735337693-1.0.1.1-OKpUoRrSPFGvWv5Hp5ET1PNZ7iZNHPKEAuakpcQUxxPSeisUIIR3qIOZ31MGmYugqB5.wkvidgbxOAagqJvmnw + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - Linux + 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-AjCtZGv4f3h7GDdhyOy9G0sB1lRgC\",\n \"object\": + \"chat.completion\",\n \"created\": 1735337693,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Thought: I understand the feedback and + will adjust my response accordingly. \\nFinal Answer: Hello\",\n \"refusal\": + null\n },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n + \ }\n ],\n \"usage\": {\n \"prompt_tokens\": 188,\n \"completion_tokens\": + 18,\n \"total_tokens\": 206,\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_0aa8d3e20b\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f8caa88cac4756b-SEA + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 27 Dec 2024 22:14:54 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: + - '358' + 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: + - '149999793' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_ae1ab6b206d28ded6fee3c83ed0c2ab7 + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"messages": [{"role": "system", "content": "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: \"looks good\""}], "model": + "gpt-4o-mini", "stop": ["\nObservation:"], "stream": false}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '439' + content-type: + - application/json + cookie: + - _cfuvid=A_ASCLNAVfQoyucWOAIhecWtEpNotYoZr0bAFihgNxs-1735337693273-0.0.1.1-604800000; + __cf_bm=wJkq_yLkzE3OdxE0aMJz.G0kce969.9JxRmZ0ratl4c-1735337693-1.0.1.1-OKpUoRrSPFGvWv5Hp5ET1PNZ7iZNHPKEAuakpcQUxxPSeisUIIR3qIOZ31MGmYugqB5.wkvidgbxOAagqJvmnw + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.52.1 + x-stainless-arch: + - x64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - Linux + 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-AjCtaiHL4TY8Dssk0j2miqmjrzquy\",\n \"object\": + \"chat.completion\",\n \"created\": 1735337694,\n \"model\": \"gpt-4o-mini-2024-07-18\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"False\",\n \"refusal\": null\n + \ },\n \"logprobs\": null,\n \"finish_reason\": \"stop\"\n }\n + \ ],\n \"usage\": {\n \"prompt_tokens\": 73,\n \"completion_tokens\": + 1,\n \"total_tokens\": 74,\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_0aa8d3e20b\"\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8f8caa8bdd26756b-SEA + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Fri, 27 Dec 2024 22:14:54 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: + - '184' + 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: + - '149999902' + x-ratelimit-reset-requests: + - 2ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_652891f79c1104a7a8436275d78a69f1 + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/cli/create_test.py b/tests/cli/create_test.py new file mode 100644 index 000000000..1f0066ca6 --- /dev/null +++ b/tests/cli/create_test.py @@ -0,0 +1,40 @@ +from unittest import mock + +import pytest +from click.testing import CliRunner + +from crewai.cli.cli import create + + +@pytest.fixture +def runner(): + return CliRunner() + + +class TestCreateCommand: + @mock.patch("crewai.cli.cli.create_crew") + def test_create_crew_with_ssl_verify_default(self, mock_create_crew, runner): + """Test that create crew command passes skip_ssl_verify=False by default.""" + result = runner.invoke(create, ["crew", "test_crew"]) + + assert result.exit_code == 0 + mock_create_crew.assert_called_once() + assert mock_create_crew.call_args[1]["skip_ssl_verify"] is False + + @mock.patch("crewai.cli.cli.create_crew") + def test_create_crew_with_skip_ssl_verify(self, mock_create_crew, runner): + """Test that create crew command passes skip_ssl_verify=True when flag is used.""" + result = runner.invoke(create, ["crew", "test_crew", "--skip_ssl_verify"]) + + assert result.exit_code == 0 + mock_create_crew.assert_called_once() + assert mock_create_crew.call_args[1]["skip_ssl_verify"] is True + + @mock.patch("crewai.cli.cli.create_flow") + def test_create_flow_ignores_skip_ssl_verify(self, mock_create_flow, runner): + """Test that create flow command ignores the skip_ssl_verify flag.""" + result = runner.invoke(create, ["flow", "test_flow", "--skip_ssl_verify"]) + + assert result.exit_code == 0 + mock_create_flow.assert_called_once() + assert mock_create_flow.call_args == mock.call("test_flow") diff --git a/tests/cli/provider_test.py b/tests/cli/provider_test.py new file mode 100644 index 000000000..796815208 --- /dev/null +++ b/tests/cli/provider_test.py @@ -0,0 +1,85 @@ +import json +import tempfile +from pathlib import Path +from unittest import mock + +import pytest +import requests +from click.testing import CliRunner + +from crewai.cli.provider import ( + fetch_provider_data, + get_provider_data, + load_provider_data, +) + + +@pytest.fixture +def mock_response(): + """Mock a successful response from requests.get.""" + mock_resp = mock.Mock() + mock_resp.headers = {"content-length": "100"} + mock_resp.iter_content.return_value = [b'{"model1": {"litellm_provider": "openai"}}'] + return mock_resp + + +@pytest.fixture +def mock_cache_file(): + """Create a temporary file to use as a cache file.""" + with tempfile.NamedTemporaryFile() as tmp: + yield Path(tmp.name) + + +class TestProviderFunctions: + @mock.patch("crewai.cli.provider.requests.get") + def test_fetch_provider_data_with_ssl_verify(self, mock_get, mock_response, mock_cache_file): + """Test that fetch_provider_data calls requests.get with verify=True by default.""" + mock_get.return_value = mock_response + + fetch_provider_data(mock_cache_file) + + mock_get.assert_called_once() + assert mock_get.call_args[1]["verify"] is True + + @mock.patch("crewai.cli.provider.requests.get") + def test_fetch_provider_data_without_ssl_verify(self, mock_get, mock_response, mock_cache_file): + """Test that fetch_provider_data calls requests.get with verify=False when skip_ssl_verify=True.""" + mock_get.return_value = mock_response + + fetch_provider_data(mock_cache_file, skip_ssl_verify=True) + + mock_get.assert_called_once() + assert mock_get.call_args[1]["verify"] is False + + @mock.patch("crewai.cli.provider.requests.get") + def test_fetch_provider_data_handles_request_exception(self, mock_get, mock_cache_file): + """Test that fetch_provider_data handles RequestException properly.""" + mock_get.side_effect = requests.RequestException("Test error") + + result = fetch_provider_data(mock_cache_file) + + assert result is None + mock_get.assert_called_once() + + @mock.patch("crewai.cli.provider.fetch_provider_data") + @mock.patch("crewai.cli.provider.read_cache_file") + def test_load_provider_data_with_ssl_verify( + self, mock_read_cache, mock_fetch, mock_cache_file + ): + """Test that load_provider_data passes skip_ssl_verify to fetch_provider_data.""" + mock_read_cache.return_value = None + mock_fetch.return_value = {"model1": {"litellm_provider": "openai"}} + + load_provider_data(mock_cache_file, 3600, skip_ssl_verify=True) + + mock_fetch.assert_called_once_with(mock_cache_file, True) + + @mock.patch("crewai.cli.provider.load_provider_data") + def test_get_provider_data_with_ssl_verify(self, mock_load, tmp_path): + """Test that get_provider_data passes skip_ssl_verify to load_provider_data.""" + mock_load.return_value = {"model1": {"litellm_provider": "openai"}} + + get_provider_data(skip_ssl_verify=True) + + mock_load.assert_called_once() + assert mock_load.call_args[0][2] is True # skip_ssl_verify parameter