mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-03 06:08:15 +00:00
address comments
This commit is contained in:
@@ -354,7 +354,7 @@ Routes:
|
||||
|
||||
### أذونات المسارات
|
||||
|
||||
استخدم `@listen(..., required_permissions=[...])` لحماية المسارات بعد أن يختار الموجّه مساراً صالحاً. يتحقق المفوّض الافتراضي من مجموعة `permissions` في `self.state`؛ وتُعاد الجولات المرفوضة إلى `permission_denied`.
|
||||
استخدم `@listen(..., required_permissions=[...])` لحماية المسارات بعد أن يختار الموجّه مسارًا صالحًا. يتحقق المفوّض الافتراضي من مجموعة `permissions` في `self.state`؛ وتُعاد الجولات المرفوضة إلى `permission_denied`.
|
||||
|
||||
```python
|
||||
from crewai import Flow
|
||||
@@ -402,7 +402,7 @@ class SupportFlow(Flow[AppState]):
|
||||
return reply
|
||||
```
|
||||
|
||||
استخدم `RouterConfig.route_permissions` عندما تحتاج إلى تكوين الأذونات بعيداً عن المعالجات. للتفويض المخصص، تجاوز `can_access_route(required_permissions)` بدلاً من تجاوز `route_turn`.
|
||||
استخدم `RouterConfig.route_permissions` عندما تحتاج إلى تكوين الأذونات بعيدًا عن المعالجات. للتفويض المخصص، تجاوز `can_access_route(required_permissions)` بدلًا من تجاوز `route_turn`.
|
||||
|
||||
### المسارات المدمجة
|
||||
|
||||
|
||||
@@ -659,7 +659,10 @@ class _ConversationalMixin:
|
||||
) -> str | None:
|
||||
router_llm = self._default_router_llm(router_config)
|
||||
if router_llm is None:
|
||||
return router_config.default_intent
|
||||
return self._route_or_permission_denied(
|
||||
router_config.default_intent,
|
||||
router_config,
|
||||
)
|
||||
|
||||
try:
|
||||
llm = self._coerce_llm(router_llm)
|
||||
@@ -670,19 +673,36 @@ class _ConversationalMixin:
|
||||
)
|
||||
intent = self._extract_router_intent(response, router_config.intent_field)
|
||||
except Exception:
|
||||
return router_config.fallback_intent or router_config.default_intent
|
||||
return self._route_or_permission_denied(
|
||||
router_config.fallback_intent or router_config.default_intent,
|
||||
router_config,
|
||||
)
|
||||
|
||||
if intent is None:
|
||||
return router_config.fallback_intent or router_config.default_intent
|
||||
return self._route_or_permission_denied(
|
||||
router_config.fallback_intent or router_config.default_intent,
|
||||
router_config,
|
||||
)
|
||||
|
||||
valid_labels = self._effective_routes(router_config)
|
||||
if valid_labels and intent not in valid_labels:
|
||||
return router_config.fallback_intent or router_config.default_intent
|
||||
return self._route_or_permission_denied(
|
||||
router_config.fallback_intent or router_config.default_intent,
|
||||
router_config,
|
||||
)
|
||||
|
||||
if not self._can_access_router_intent(intent, router_config):
|
||||
return self._route_or_permission_denied(intent, router_config)
|
||||
|
||||
def _route_or_permission_denied(
|
||||
self,
|
||||
route: str | None,
|
||||
router_config: RouterConfig,
|
||||
) -> str | None:
|
||||
if route is None:
|
||||
return None
|
||||
if not self._can_access_router_intent(route, router_config):
|
||||
return router_config.permission_denied_route
|
||||
|
||||
return intent
|
||||
return route
|
||||
|
||||
def _can_access_router_intent(
|
||||
self,
|
||||
|
||||
@@ -64,6 +64,11 @@ def listen(
|
||||
)
|
||||
if not permissions:
|
||||
raise ValueError("required_permissions must not be empty")
|
||||
if any(
|
||||
not isinstance(permission, str) or not permission.strip()
|
||||
for permission in permissions
|
||||
):
|
||||
raise ValueError("required_permissions must contain non-empty strings")
|
||||
wrapper.__route_permissions__ = permissions
|
||||
return wrapper
|
||||
|
||||
|
||||
@@ -171,6 +171,7 @@ class ListenMethod(FlowMethod[P, R]):
|
||||
__trigger_methods__: list[FlowMethodName] | None = None
|
||||
__condition_type__: FlowConditionType | None = None
|
||||
__trigger_condition__: FlowCondition | None = None
|
||||
__route_permissions__: tuple[str, ...] | None = None
|
||||
|
||||
|
||||
class RouterMethod(FlowMethod[P, R]):
|
||||
|
||||
@@ -891,6 +891,7 @@ class TestConversationalFlow:
|
||||
return "denied"
|
||||
|
||||
flow = PermissionFlow()
|
||||
flow._state = PermissionState()
|
||||
result = flow.handle_turn("research CrewAI")
|
||||
|
||||
assert result == "denied"
|
||||
@@ -929,6 +930,7 @@ class TestConversationalFlow:
|
||||
return "denied"
|
||||
|
||||
flow = PermissionFlow()
|
||||
flow._state = PermissionState()
|
||||
result = flow.handle_turn("research CrewAI")
|
||||
|
||||
assert result == "researched"
|
||||
@@ -970,10 +972,65 @@ class TestConversationalFlow:
|
||||
assert result == "admin report"
|
||||
assert flow.state.last_intent == "admin"
|
||||
|
||||
def test_router_infers_permissions_from_listener_metadata(self) -> None:
|
||||
class PermissionState(ConversationState):
|
||||
permissions: set[str] = Field(default_factory=set)
|
||||
def test_router_permissions_gate_protected_default_intent(self) -> None:
|
||||
@ConversationConfig(
|
||||
router=RouterConfig(
|
||||
routes=["admin"],
|
||||
route_permissions={"admin": "admin"},
|
||||
default_intent="admin",
|
||||
),
|
||||
)
|
||||
class PermissionFlow(ConversationalFlow):
|
||||
@listen("admin")
|
||||
def run_admin(self) -> str:
|
||||
self.append_assistant_message("admin report")
|
||||
return "admin report"
|
||||
|
||||
@listen("permission_denied")
|
||||
def deny(self) -> str:
|
||||
self.append_assistant_message("denied")
|
||||
return "denied"
|
||||
|
||||
flow = PermissionFlow()
|
||||
result = flow.handle_turn("show audit")
|
||||
|
||||
assert result == "denied"
|
||||
assert flow.state.last_intent == "permission_denied"
|
||||
|
||||
def test_router_permissions_gate_protected_fallback_intent(self) -> None:
|
||||
class Route(BaseModel):
|
||||
intent: str
|
||||
|
||||
router_llm = MagicMock()
|
||||
router_llm.call.return_value = Route(intent="unknown")
|
||||
|
||||
@ConversationConfig(
|
||||
router=RouterConfig(
|
||||
response_format=Route,
|
||||
llm=router_llm,
|
||||
routes=["admin"],
|
||||
route_permissions={"admin": "admin"},
|
||||
fallback_intent="admin",
|
||||
),
|
||||
)
|
||||
class PermissionFlow(ConversationalFlow):
|
||||
@listen("admin")
|
||||
def run_admin(self) -> str:
|
||||
self.append_assistant_message("admin report")
|
||||
return "admin report"
|
||||
|
||||
@listen("permission_denied")
|
||||
def deny(self) -> str:
|
||||
self.append_assistant_message("denied")
|
||||
return "denied"
|
||||
|
||||
flow = PermissionFlow()
|
||||
result = flow.handle_turn("something unexpected")
|
||||
|
||||
assert result == "denied"
|
||||
assert flow.state.last_intent == "permission_denied"
|
||||
|
||||
def test_router_infers_permissions_from_listener_metadata(self) -> None:
|
||||
router_llm = MagicMock()
|
||||
|
||||
@ConversationConfig(
|
||||
@@ -981,7 +1038,7 @@ class TestConversationalFlow:
|
||||
llm=router_llm,
|
||||
),
|
||||
)
|
||||
class PermissionFlow(Flow[PermissionState]):
|
||||
class PermissionFlow(Flow[ConversationState]):
|
||||
conversational = True
|
||||
|
||||
@listen("research", required_permissions=["web_search"])
|
||||
@@ -1007,6 +1064,14 @@ class TestConversationalFlow:
|
||||
assert result == "denied"
|
||||
assert flow.state.last_intent == "permission_denied"
|
||||
|
||||
def test_listener_required_permissions_reject_empty_values(self) -> None:
|
||||
try:
|
||||
listen("research", required_permissions=["web_search", ""])(lambda: None)
|
||||
except ValueError as exc:
|
||||
assert "non-empty strings" in str(exc)
|
||||
else:
|
||||
raise AssertionError("empty permission names should be rejected")
|
||||
|
||||
def test_conversational_flow_auto_defaults_to_conversation_state(self) -> None:
|
||||
"""``class C(Flow): conversational = True`` resolves state to ConversationState.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user