mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-05-01 07:13:00 +00:00
docs: add Conversational Flows (self.ask) section to flows docs
Add comprehensive documentation for the self.ask() feature covering: - Basic usage and API - Multiple asks in a single method - Timeout support with retry pattern - Bidirectional metadata support - Custom InputProvider protocol (Slack example) - Auto-checkpoint behavior with persistence - Comparison table: self.ask() vs @human_feedback
This commit is contained in:
@@ -572,6 +572,176 @@ The `third_method` and `fourth_method` listen to the output of the `second_metho
|
|||||||
|
|
||||||
When you run this Flow, the output will change based on the random boolean value generated by the `start_method`.
|
When you run this Flow, the output will change based on the random boolean value generated by the `start_method`.
|
||||||
|
|
||||||
|
### Conversational Flows (User Input)
|
||||||
|
|
||||||
|
The `self.ask()` method pauses flow execution to request input from a user inline, then returns their response as a string. This enables conversational, interactive flows where the AI can gather information, ask clarifying questions, or request approvals during execution.
|
||||||
|
|
||||||
|
#### Basic Usage
|
||||||
|
|
||||||
|
```python Code
|
||||||
|
from crewai.flow.flow import Flow, start, listen
|
||||||
|
|
||||||
|
class GreetingFlow(Flow):
|
||||||
|
@start()
|
||||||
|
def greet(self):
|
||||||
|
name = self.ask("What's your name?")
|
||||||
|
self.state["name"] = name
|
||||||
|
|
||||||
|
@listen(greet)
|
||||||
|
def welcome(self):
|
||||||
|
print(f"Welcome, {self.state['name']}!")
|
||||||
|
|
||||||
|
flow = GreetingFlow()
|
||||||
|
flow.kickoff()
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, `self.ask()` uses a `ConsoleProvider` that prompts via Python's built-in `input()`.
|
||||||
|
|
||||||
|
#### Multiple Asks in One Method
|
||||||
|
|
||||||
|
You can call `self.ask()` multiple times within a single method to gather several inputs:
|
||||||
|
|
||||||
|
```python Code
|
||||||
|
from crewai.flow.flow import Flow, start
|
||||||
|
|
||||||
|
class OnboardingFlow(Flow):
|
||||||
|
@start()
|
||||||
|
def collect_info(self):
|
||||||
|
name = self.ask("What's your name?")
|
||||||
|
role = self.ask("What's your role?")
|
||||||
|
team = self.ask("Which team are you joining?")
|
||||||
|
self.state["profile"] = {"name": name, "role": role, "team": team}
|
||||||
|
print(f"Welcome {name}, {role} on {team}!")
|
||||||
|
|
||||||
|
flow = OnboardingFlow()
|
||||||
|
flow.kickoff()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Timeout Support
|
||||||
|
|
||||||
|
Pass `timeout=` (in seconds) to avoid blocking indefinitely. If the user doesn't respond in time, `self.ask()` returns `None`:
|
||||||
|
|
||||||
|
```python Code
|
||||||
|
from crewai.flow.flow import Flow, start
|
||||||
|
|
||||||
|
class ApprovalFlow(Flow):
|
||||||
|
@start()
|
||||||
|
def request_approval(self):
|
||||||
|
response = self.ask("Approve deployment? (yes/no)", timeout=120)
|
||||||
|
|
||||||
|
if response is None:
|
||||||
|
print("No response received — timed out.")
|
||||||
|
self.state["approved"] = False
|
||||||
|
return
|
||||||
|
|
||||||
|
self.state["approved"] = response.strip().lower() == "yes"
|
||||||
|
```
|
||||||
|
|
||||||
|
Use a `while` loop to retry on timeout:
|
||||||
|
|
||||||
|
```python Code
|
||||||
|
from crewai.flow.flow import Flow, start
|
||||||
|
|
||||||
|
class RetryFlow(Flow):
|
||||||
|
@start()
|
||||||
|
def ask_with_retry(self):
|
||||||
|
answer = None
|
||||||
|
while answer is None:
|
||||||
|
answer = self.ask("Please confirm (yes/no):", timeout=60)
|
||||||
|
if answer is None:
|
||||||
|
print("Timed out, asking again...")
|
||||||
|
self.state["confirmed"] = answer.strip().lower() == "yes"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Metadata Support
|
||||||
|
|
||||||
|
The `metadata` parameter enables bidirectional context passing between the flow and the input provider. Send context to the provider, and receive structured context back:
|
||||||
|
|
||||||
|
```python Code
|
||||||
|
from crewai.flow.flow import Flow, start
|
||||||
|
|
||||||
|
class ContextualFlow(Flow):
|
||||||
|
@start()
|
||||||
|
def gather_feedback(self):
|
||||||
|
response = self.ask(
|
||||||
|
"Rate this output (1-5):",
|
||||||
|
metadata={
|
||||||
|
"step": "quality_review",
|
||||||
|
"output_id": "abc-123",
|
||||||
|
"options": ["1", "2", "3", "4", "5"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.state["rating"] = int(response) if response else None
|
||||||
|
```
|
||||||
|
|
||||||
|
When a custom provider returns an `InputResponse`, it can include its own metadata (e.g., user identity, timestamp, channel info) that your flow can process.
|
||||||
|
|
||||||
|
#### Custom InputProvider
|
||||||
|
|
||||||
|
For production use cases (Slack bots, web UIs, webhooks), implement the `InputProvider` protocol:
|
||||||
|
|
||||||
|
```python Code
|
||||||
|
from crewai.flow.flow import Flow, start
|
||||||
|
from crewai.flow.input_provider import InputProvider, InputResponse
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class SlackInputProvider(InputProvider):
|
||||||
|
def __init__(self, channel_id: str, bot_token: str):
|
||||||
|
self.channel_id = channel_id
|
||||||
|
self.bot_token = bot_token
|
||||||
|
|
||||||
|
def request_input(self, message, flow, metadata=None):
|
||||||
|
# Post the question to Slack
|
||||||
|
requests.post(
|
||||||
|
"https://slack.com/api/chat.postMessage",
|
||||||
|
headers={"Authorization": f"Bearer {self.bot_token}"},
|
||||||
|
json={"channel": self.channel_id, "text": message},
|
||||||
|
)
|
||||||
|
# Wait for and return the user's reply (simplified)
|
||||||
|
reply = self.poll_for_reply()
|
||||||
|
return InputResponse(
|
||||||
|
value=reply["text"],
|
||||||
|
metadata={"user": reply["user"], "ts": reply["ts"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
def poll_for_reply(self):
|
||||||
|
# Your implementation to wait for a Slack reply
|
||||||
|
...
|
||||||
|
|
||||||
|
# Use the custom provider
|
||||||
|
flow = Flow(input_provider=SlackInputProvider(
|
||||||
|
channel_id="C01ABC123",
|
||||||
|
bot_token="xoxb-...",
|
||||||
|
))
|
||||||
|
flow.kickoff()
|
||||||
|
```
|
||||||
|
|
||||||
|
The `request_input` method can return:
|
||||||
|
- A **string** — used directly as the user's response
|
||||||
|
- An **`InputResponse`** — includes `value` (the response string) and optional `metadata`
|
||||||
|
- **`None`** — treated as a timeout / no response
|
||||||
|
|
||||||
|
#### Auto-Checkpoint Behavior
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
When [persistence](/concepts/flows-persistence) is configured, the flow state is automatically saved **before** each `self.ask()` call. If the process restarts while waiting for input, the flow can resume from the checkpoint without losing progress.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
#### `self.ask()` vs `@human_feedback`
|
||||||
|
|
||||||
|
| | `self.ask()` | `@human_feedback` |
|
||||||
|
|---|---|---|
|
||||||
|
| **Purpose** | Inline user input during execution | Approval gates and review feedback |
|
||||||
|
| **Returns** | `str \| None` | `HumanFeedbackResult` with structured fields |
|
||||||
|
| **Timeout** | Built-in `timeout=` parameter | Not built-in |
|
||||||
|
| **Provider** | Pluggable `InputProvider` protocol | Console-based |
|
||||||
|
| **Use when** | Gathering data, clarifications, confirmations | Review/approval workflows with structured feedback |
|
||||||
|
| **Decorator** | None — call `self.ask()` anywhere | `@human_feedback` on the method |
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Both features coexist — you can use `self.ask()` and `@human_feedback` in the same flow. Use `self.ask()` for inline data gathering and `@human_feedback` for structured review gates.
|
||||||
|
</Note>
|
||||||
|
|
||||||
### Human in the Loop (human feedback)
|
### Human in the Loop (human feedback)
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
|
|||||||
Reference in New Issue
Block a user