mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-04-15 23:42:37 +00:00
docs: update changelog and version for v1.14.2a4
This commit is contained in:
@@ -4,6 +4,30 @@ description: "تحديثات المنتج والتحسينات وإصلاحات
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="15 أبريل 2026">
|
||||
## v1.14.2a4
|
||||
|
||||
[عرض الإصدار على GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.14.2a4)
|
||||
|
||||
## ما الذي تغير
|
||||
|
||||
### الميزات
|
||||
- إضافة تلميحات استئناف إلى إصدار أدوات المطورين عند الفشل
|
||||
|
||||
### إصلاحات الأخطاء
|
||||
- إصلاح توجيه وضع الصرامة إلى واجهة برمجة تطبيقات Bedrock Converse
|
||||
- إصلاح إصدار pytest إلى 9.0.3 لثغرة الأمان GHSA-6w46-j5rx-g56g
|
||||
- رفع الحد الأدنى لـ OpenAI إلى >=2.0.0
|
||||
|
||||
### الوثائق
|
||||
- تحديث سجل التغييرات والإصدار لـ v1.14.2a3
|
||||
|
||||
## المساهمون
|
||||
|
||||
@greysonlalonde
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="13 أبريل 2026">
|
||||
## v1.14.2a3
|
||||
|
||||
|
||||
@@ -4,6 +4,30 @@ description: "Product updates, improvements, and bug fixes for CrewAI"
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="Apr 15, 2026">
|
||||
## v1.14.2a4
|
||||
|
||||
[View release on GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.14.2a4)
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Features
|
||||
- Add resume hints to devtools release on failure
|
||||
|
||||
### Bug Fixes
|
||||
- Fix strict mode forwarding to Bedrock Converse API
|
||||
- Fix pytest version to 9.0.3 for security vulnerability GHSA-6w46-j5rx-g56g
|
||||
- Bump OpenAI lower bound to >=2.0.0
|
||||
|
||||
### Documentation
|
||||
- Update changelog and version for v1.14.2a3
|
||||
|
||||
## Contributors
|
||||
|
||||
@greysonlalonde
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="Apr 13, 2026">
|
||||
## v1.14.2a3
|
||||
|
||||
|
||||
@@ -4,6 +4,30 @@ description: "CrewAI의 제품 업데이트, 개선 사항 및 버그 수정"
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="2026년 4월 15일">
|
||||
## v1.14.2a4
|
||||
|
||||
[GitHub 릴리스 보기](https://github.com/crewAIInc/crewAI/releases/tag/1.14.2a4)
|
||||
|
||||
## 변경 사항
|
||||
|
||||
### 기능
|
||||
- 실패 시 devtools 릴리스에 이력서 힌트 추가
|
||||
|
||||
### 버그 수정
|
||||
- Bedrock Converse API로의 엄격 모드 포워딩 수정
|
||||
- 보안 취약점 GHSA-6w46-j5rx-g56g에 대해 pytest 버전을 9.0.3으로 수정
|
||||
- OpenAI 하한을 >=2.0.0으로 상향 조정
|
||||
|
||||
### 문서
|
||||
- v1.14.2a3에 대한 변경 로그 및 버전 업데이트
|
||||
|
||||
## 기여자
|
||||
|
||||
@greysonlalonde
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026년 4월 13일">
|
||||
## v1.14.2a3
|
||||
|
||||
|
||||
@@ -4,6 +4,30 @@ description: "Atualizações de produto, melhorias e correções do CrewAI"
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="15 abr 2026">
|
||||
## v1.14.2a4
|
||||
|
||||
[Ver release no GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.14.2a4)
|
||||
|
||||
## O que Mudou
|
||||
|
||||
### Recursos
|
||||
- Adicionar dicas de retomar ao release do devtools em caso de falha
|
||||
|
||||
### Correções de Bugs
|
||||
- Corrigir o encaminhamento do modo estrito para a API Bedrock Converse
|
||||
- Corrigir a versão do pytest para 9.0.3 devido à vulnerabilidade de segurança GHSA-6w46-j5rx-g56g
|
||||
- Aumentar o limite inferior do OpenAI para >=2.0.0
|
||||
|
||||
### Documentação
|
||||
- Atualizar o changelog e a versão para v1.14.2a3
|
||||
|
||||
## Contribuidores
|
||||
|
||||
@greysonlalonde
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="13 abr 2026">
|
||||
## v1.14.2a3
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ google-genai = [
|
||||
]
|
||||
azure-ai-inference = [
|
||||
"azure-ai-inference~=1.0.0b9",
|
||||
"azure-identity>=1.17.0,<2",
|
||||
]
|
||||
anthropic = [
|
||||
"anthropic~=0.73.0",
|
||||
|
||||
@@ -35,6 +35,7 @@ try:
|
||||
)
|
||||
from azure.core.credentials import (
|
||||
AzureKeyCredential,
|
||||
TokenCredential,
|
||||
)
|
||||
from azure.core.exceptions import (
|
||||
HttpResponseError,
|
||||
@@ -88,6 +89,8 @@ class AzureCompletion(BaseLLM):
|
||||
response_format: type[BaseModel] | None = None
|
||||
is_openai_model: bool = False
|
||||
is_azure_openai_endpoint: bool = False
|
||||
azure_tenant_id: str | None = None
|
||||
azure_client_id: str | None = None
|
||||
|
||||
_client: Any = PrivateAttr(default=None)
|
||||
_async_client: Any = PrivateAttr(default=None)
|
||||
@@ -115,6 +118,8 @@ class AzureCompletion(BaseLLM):
|
||||
data["api_version"] = (
|
||||
data.get("api_version") or os.getenv("AZURE_API_VERSION") or "2024-06-01"
|
||||
)
|
||||
data["azure_tenant_id"] = data.get("azure_tenant_id") or os.getenv("AZURE_TENANT_ID")
|
||||
data["azure_client_id"] = data.get("azure_client_id") or os.getenv("AZURE_CLIENT_ID")
|
||||
|
||||
# Credentials and endpoint are validated lazily in `_init_clients`
|
||||
# so the LLM can be constructed before deployment env vars are set.
|
||||
@@ -183,24 +188,89 @@ class AzureCompletion(BaseLLM):
|
||||
AzureCompletion._is_azure_openai_endpoint(self.endpoint)
|
||||
)
|
||||
|
||||
if not self.api_key:
|
||||
raise ValueError(
|
||||
"Azure API key is required. Set AZURE_API_KEY environment "
|
||||
"variable or pass api_key parameter."
|
||||
)
|
||||
# Re-read identity env vars for deferred builds
|
||||
if not self.azure_tenant_id:
|
||||
self.azure_tenant_id = os.getenv("AZURE_TENANT_ID")
|
||||
if not self.azure_client_id:
|
||||
self.azure_client_id = os.getenv("AZURE_CLIENT_ID")
|
||||
|
||||
if not self.endpoint:
|
||||
raise ValueError(
|
||||
"Azure endpoint is required. Set AZURE_ENDPOINT environment "
|
||||
"variable or pass endpoint parameter."
|
||||
)
|
||||
|
||||
credential = self._resolve_credential()
|
||||
|
||||
client_kwargs: dict[str, Any] = {
|
||||
"endpoint": self.endpoint,
|
||||
"credential": AzureKeyCredential(self.api_key),
|
||||
"credential": credential,
|
||||
}
|
||||
if self.api_version:
|
||||
client_kwargs["api_version"] = self.api_version
|
||||
return client_kwargs
|
||||
|
||||
def _resolve_credential(self) -> AzureKeyCredential | TokenCredential:
|
||||
"""Resolve the Azure credential using a priority chain.
|
||||
|
||||
1. OIDC federation (WorkloadIdentityCredential) — auto-discovered
|
||||
from AZURE_FEDERATED_TOKEN_FILE + AZURE_TENANT_ID + AZURE_CLIENT_ID
|
||||
2. Client secret (ClientSecretCredential) — explicit SP credentials
|
||||
3. Default chain (DefaultAzureCredential) — Managed Identity et al.
|
||||
4. API key fallback (AzureKeyCredential) — existing path
|
||||
"""
|
||||
federated_token_file = os.getenv("AZURE_FEDERATED_TOKEN_FILE")
|
||||
client_secret = os.getenv("AZURE_CLIENT_SECRET")
|
||||
|
||||
# Path 1: OIDC Workload Identity Federation
|
||||
if federated_token_file and self.azure_tenant_id and self.azure_client_id:
|
||||
try:
|
||||
from azure.identity import WorkloadIdentityCredential
|
||||
|
||||
return WorkloadIdentityCredential(
|
||||
tenant_id=self.azure_tenant_id,
|
||||
client_id=self.azure_client_id,
|
||||
token_file_path=federated_token_file,
|
||||
)
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"azure-identity is required for workload identity federation. "
|
||||
'Install with: uv add "crewai[azure-ai-inference]"'
|
||||
) from None
|
||||
|
||||
# Path 2: Client Secret (Service Principal)
|
||||
if client_secret and self.azure_tenant_id and self.azure_client_id:
|
||||
try:
|
||||
from azure.identity import ClientSecretCredential
|
||||
|
||||
return ClientSecretCredential(
|
||||
tenant_id=self.azure_tenant_id,
|
||||
client_id=self.azure_client_id,
|
||||
client_secret=client_secret,
|
||||
)
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"azure-identity is required for service principal authentication. "
|
||||
'Install with: uv add "crewai[azure-ai-inference]"'
|
||||
) from None
|
||||
|
||||
# Path 3: DefaultAzureCredential (Managed Identity, Azure CLI, etc.)
|
||||
# Only attempt if azure-identity is installed and no API key is available
|
||||
if not self.api_key:
|
||||
try:
|
||||
from azure.identity import DefaultAzureCredential
|
||||
|
||||
return DefaultAzureCredential()
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"Azure API key is required when azure-identity is not installed. "
|
||||
"Set AZURE_API_KEY environment variable, pass api_key parameter, "
|
||||
'or install azure-identity: uv add "crewai[azure-ai-inference]"'
|
||||
) from None
|
||||
|
||||
# Path 4: API Key (existing path)
|
||||
return AzureKeyCredential(self.api_key)
|
||||
|
||||
def _get_sync_client(self) -> Any:
|
||||
if self._client is None:
|
||||
self._client = self._build_sync_client()
|
||||
|
||||
257
lib/crewai/tests/llms/azure/test_azure_credentials.py
Normal file
257
lib/crewai/tests/llms/azure/test_azure_credentials.py
Normal file
@@ -0,0 +1,257 @@
|
||||
"""Tests for Azure credential resolution chain in AzureCompletion.
|
||||
|
||||
Covers the four credential paths:
|
||||
1. WorkloadIdentityCredential (OIDC federation)
|
||||
2. ClientSecretCredential (Service Principal)
|
||||
3. DefaultAzureCredential (Managed Identity / CLI fallback)
|
||||
4. AzureKeyCredential (API key - existing path)
|
||||
"""
|
||||
|
||||
import os
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
ENDPOINT = "https://test.openai.azure.com"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _clear_azure_env(monkeypatch):
|
||||
"""Remove all Azure env vars to start clean."""
|
||||
for key in [
|
||||
"AZURE_API_KEY", "AZURE_ENDPOINT", "AZURE_OPENAI_ENDPOINT",
|
||||
"AZURE_API_BASE", "AZURE_API_VERSION", "AZURE_TENANT_ID",
|
||||
"AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "AZURE_FEDERATED_TOKEN_FILE",
|
||||
]:
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("_clear_azure_env")
|
||||
class TestCredentialResolution:
|
||||
"""Tests for AzureCompletion._resolve_credential."""
|
||||
|
||||
def test_api_key_credential_when_api_key_set(self):
|
||||
"""Path 4: API key produces AzureKeyCredential."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from azure.core.credentials import AzureKeyCredential
|
||||
|
||||
completion = AzureCompletion(
|
||||
model="gpt-4",
|
||||
api_key="test-key",
|
||||
endpoint=ENDPOINT,
|
||||
)
|
||||
cred = completion._resolve_credential()
|
||||
assert isinstance(cred, AzureKeyCredential)
|
||||
|
||||
def test_api_key_from_env(self, monkeypatch):
|
||||
"""Path 4: api_key picked up from AZURE_API_KEY env var."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
from azure.core.credentials import AzureKeyCredential
|
||||
|
||||
monkeypatch.setenv("AZURE_API_KEY", "env-key")
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
completion = AzureCompletion(model="gpt-4")
|
||||
cred = completion._resolve_credential()
|
||||
assert isinstance(cred, AzureKeyCredential)
|
||||
|
||||
def test_workload_identity_credential(self, monkeypatch, tmp_path):
|
||||
"""Path 1: OIDC federation via WorkloadIdentityCredential."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
token_file = tmp_path / "token.txt"
|
||||
token_file.write_text("eyJhbGciOiJSUzI1NiJ9.test")
|
||||
|
||||
monkeypatch.setenv("AZURE_FEDERATED_TOKEN_FILE", str(token_file))
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
mock_wi_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.WorkloadIdentityCredential",
|
||||
return_value=mock_wi_cred,
|
||||
) as mock_cls:
|
||||
completion = AzureCompletion(
|
||||
model="gpt-4",
|
||||
azure_tenant_id="tenant-123",
|
||||
azure_client_id="client-456",
|
||||
)
|
||||
cred = completion._resolve_credential()
|
||||
assert cred is mock_wi_cred
|
||||
mock_cls.assert_called_once_with(
|
||||
tenant_id="tenant-123",
|
||||
client_id="client-456",
|
||||
token_file_path=str(token_file),
|
||||
)
|
||||
|
||||
def test_workload_identity_from_env_vars(self, monkeypatch, tmp_path):
|
||||
"""Path 1: All WI fields discovered from environment."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
token_file = tmp_path / "token.txt"
|
||||
token_file.write_text("eyJhbGciOiJSUzI1NiJ9.test")
|
||||
|
||||
monkeypatch.setenv("AZURE_FEDERATED_TOKEN_FILE", str(token_file))
|
||||
monkeypatch.setenv("AZURE_TENANT_ID", "env-tenant")
|
||||
monkeypatch.setenv("AZURE_CLIENT_ID", "env-client")
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
mock_wi_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.WorkloadIdentityCredential",
|
||||
return_value=mock_wi_cred,
|
||||
) as mock_cls:
|
||||
completion = AzureCompletion(model="gpt-4")
|
||||
cred = completion._resolve_credential()
|
||||
assert cred is mock_wi_cred
|
||||
mock_cls.assert_called_once_with(
|
||||
tenant_id="env-tenant",
|
||||
client_id="env-client",
|
||||
token_file_path=str(token_file),
|
||||
)
|
||||
|
||||
def test_client_secret_credential(self, monkeypatch):
|
||||
"""Path 2: Service Principal with client secret."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
monkeypatch.setenv("AZURE_CLIENT_SECRET", "sp-secret")
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
mock_cs_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.ClientSecretCredential",
|
||||
return_value=mock_cs_cred,
|
||||
) as mock_cls:
|
||||
completion = AzureCompletion(
|
||||
model="gpt-4",
|
||||
azure_tenant_id="tenant-123",
|
||||
azure_client_id="client-456",
|
||||
)
|
||||
cred = completion._resolve_credential()
|
||||
assert cred is mock_cs_cred
|
||||
mock_cls.assert_called_once_with(
|
||||
tenant_id="tenant-123",
|
||||
client_id="client-456",
|
||||
client_secret="sp-secret",
|
||||
)
|
||||
|
||||
def test_default_azure_credential_when_no_api_key(self, monkeypatch):
|
||||
"""Path 3: DefaultAzureCredential when no api_key and no SP/WI vars."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
mock_default_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.DefaultAzureCredential",
|
||||
return_value=mock_default_cred,
|
||||
):
|
||||
completion = AzureCompletion(model="gpt-4")
|
||||
cred = completion._resolve_credential()
|
||||
assert cred is mock_default_cred
|
||||
|
||||
def test_workload_identity_takes_priority_over_api_key(self, monkeypatch, tmp_path):
|
||||
"""WI credential should take priority even when api_key is also set."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
token_file = tmp_path / "token.txt"
|
||||
token_file.write_text("eyJhbGciOiJSUzI1NiJ9.test")
|
||||
|
||||
monkeypatch.setenv("AZURE_FEDERATED_TOKEN_FILE", str(token_file))
|
||||
monkeypatch.setenv("AZURE_API_KEY", "should-not-use-this")
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
mock_wi_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.WorkloadIdentityCredential",
|
||||
return_value=mock_wi_cred,
|
||||
):
|
||||
completion = AzureCompletion(
|
||||
model="gpt-4",
|
||||
azure_tenant_id="tenant-123",
|
||||
azure_client_id="client-456",
|
||||
)
|
||||
cred = completion._resolve_credential()
|
||||
assert cred is mock_wi_cred
|
||||
|
||||
def test_client_secret_takes_priority_over_api_key(self, monkeypatch):
|
||||
"""SP credential should take priority over API key."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
monkeypatch.setenv("AZURE_CLIENT_SECRET", "sp-secret")
|
||||
monkeypatch.setenv("AZURE_API_KEY", "should-not-use-this")
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
mock_cs_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.ClientSecretCredential",
|
||||
return_value=mock_cs_cred,
|
||||
):
|
||||
completion = AzureCompletion(
|
||||
model="gpt-4",
|
||||
azure_tenant_id="tenant-123",
|
||||
azure_client_id="client-456",
|
||||
)
|
||||
cred = completion._resolve_credential()
|
||||
assert cred is mock_cs_cred
|
||||
|
||||
def test_raises_when_no_api_key_and_no_azure_identity(self, monkeypatch):
|
||||
"""ValueError when no api_key and azure-identity not installed."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
with patch.dict("sys.modules", {"azure.identity": None}):
|
||||
completion = AzureCompletion(model="gpt-4")
|
||||
with pytest.raises(ValueError, match="Azure API key is required"):
|
||||
completion._resolve_credential()
|
||||
|
||||
def test_endpoint_still_required(self, monkeypatch, tmp_path):
|
||||
"""Endpoint is always required regardless of credential type."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
token_file = tmp_path / "token.txt"
|
||||
token_file.write_text("test-jwt")
|
||||
|
||||
monkeypatch.setenv("AZURE_FEDERATED_TOKEN_FILE", str(token_file))
|
||||
monkeypatch.setenv("AZURE_TENANT_ID", "tenant-123")
|
||||
monkeypatch.setenv("AZURE_CLIENT_ID", "client-456")
|
||||
|
||||
completion = AzureCompletion(model="gpt-4")
|
||||
with pytest.raises(ValueError, match="Azure endpoint is required"):
|
||||
completion._make_client_kwargs()
|
||||
|
||||
def test_deferred_build_picks_up_wi_env_vars(self, monkeypatch, tmp_path):
|
||||
"""Env vars set after construction are picked up on deferred build."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
# Construct with endpoint only — no credentials yet
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
completion = AzureCompletion(model="gpt-4")
|
||||
|
||||
# Now set WI env vars (simulating WI manager setting them before crew run)
|
||||
token_file = tmp_path / "token.txt"
|
||||
token_file.write_text("eyJhbGciOiJSUzI1NiJ9.deferred")
|
||||
monkeypatch.setenv("AZURE_FEDERATED_TOKEN_FILE", str(token_file))
|
||||
monkeypatch.setenv("AZURE_TENANT_ID", "deferred-tenant")
|
||||
monkeypatch.setenv("AZURE_CLIENT_ID", "deferred-client")
|
||||
|
||||
mock_wi_cred = MagicMock()
|
||||
with patch(
|
||||
"azure.identity.WorkloadIdentityCredential",
|
||||
return_value=mock_wi_cred,
|
||||
):
|
||||
kwargs = completion._make_client_kwargs()
|
||||
assert kwargs["credential"] is mock_wi_cred
|
||||
|
||||
def test_make_client_kwargs_includes_api_version(self, monkeypatch):
|
||||
"""api_version is included in client kwargs."""
|
||||
from crewai.llms.providers.azure.completion import AzureCompletion
|
||||
|
||||
monkeypatch.setenv("AZURE_API_KEY", "test-key")
|
||||
monkeypatch.setenv("AZURE_ENDPOINT", ENDPOINT)
|
||||
|
||||
completion = AzureCompletion(model="gpt-4", api_version="2025-01-01")
|
||||
kwargs = completion._make_client_kwargs()
|
||||
assert kwargs["api_version"] == "2025-01-01"
|
||||
assert kwargs["endpoint"] == ENDPOINT
|
||||
Reference in New Issue
Block a user