Skip to content

Commit 93634a3

Browse files
tomkisPetrBulanek
authored andcommitted
fix: update docs for the forms
Signed-off-by: Tomas Weiss <tomas.weiss2@gmail.com>
1 parent 666efe2 commit 93634a3

File tree

1 file changed

+117
-83
lines changed

1 file changed

+117
-83
lines changed

docs/sdk/forms.mdx

Lines changed: 117 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -5,59 +5,62 @@ description: "Collect structured input from users"
55

66
One of the most powerful features of the Agent Stack is the ability to request structured data from users through interactive forms. Instead of relying on free-form text input, your agent can present users with specific fields, dropdowns, and other form elements to gather precise information.
77

8-
The Agent Stack provides a Form extension that allows you to collect structured data from users in two ways:
8+
The Agent Stack provides a Form extensions that allows you to collect structured data from users in two ways:
99

1010
1. **Initial form rendering** - Present a form as the first interaction before users start a conversation with your agent
1111
2. **Dynamic form requests** - Request forms at any point during a multi-turn conversation when your agent needs specific structured input
1212

13+
## Initial Form Rendering
14+
15+
For initial form rendering, you specify the form structure when injecting the extension and then parse the response using a Pydantic model. The form is presented to users before they start a conversation with your agent.
1316

14-
## Quickstart
17+
### Quickstart
1518

1619
<Steps>
17-
<Step title="Import the Form extension">
18-
Import the necessary components from the Agent Stack SDK form extension.
20+
<Step title="Import the form extension">
21+
Import `FormServiceExtensionServer`, `FormServiceExtensionSpec`, `FormRender`, and field types from the Agent Stack SDK.
1922
</Step>
2023

21-
<Step title="Add form parameter to your agent">
22-
Inject the Form extension into your agent function using the `Annotated` type hint.
24+
<Step title="Define your form data model">
25+
Create a Pydantic model with fields matching your form field IDs.
2326
</Step>
2427

25-
<Step title="Define your form structure">
26-
Create a `FormRender` object with the fields you want to collect from users.
28+
<Step title="Add form parameter to your agent">
29+
Inject the form extension into your agent function using `FormServiceExtensionSpec.demand(initial_form=FormRender(...))`.
2730
</Step>
2831

29-
<Step title="Process form data">
30-
Use either `parse_form_response()` for initial forms or `request_form()` for dynamic forms.
32+
<Step title="Parse form data">
33+
Call `form.parse_initial_form(model=YourModel)` to extract the submitted form data.
3134
</Step>
3235
</Steps>
3336

34-
## Initial Form Rendering
35-
36-
For initial form rendering, you specify the form structure when injecting the extension and then parse the response from the initial message:
37-
3837
```python
39-
import os
4038
from typing import Annotated
4139

4240
from a2a.types import Message
41+
from pydantic import BaseModel
4342
from agentstack_sdk.server import Server
44-
from agentstack_sdk.a2a.extensions.ui.form import (
45-
FormExtensionServer,
46-
FormExtensionSpec,
47-
FormRender,
48-
TextField
43+
from agentstack_sdk.a2a.extensions.common.form import FormRender, TextField
44+
from agentstack_sdk.a2a.extensions.services.form import (
45+
FormServiceExtensionServer,
46+
FormServiceExtensionSpec,
4947
)
5048

5149
server = Server()
5250

51+
52+
class UserInfo(BaseModel):
53+
first_name: str | None
54+
last_name: str | None
55+
56+
5357
@server.agent()
5458
async def initial_form_agent(
55-
message: Message,
59+
_message: Message,
5660
form: Annotated[
57-
FormExtensionServer,
58-
FormExtensionSpec(
59-
params=FormRender(
60-
id="user_info_form",
61+
FormServiceExtensionServer,
62+
FormServiceExtensionSpec.demand(
63+
initial_form=FormRender(
6164
title="Welcome! Please tell us about yourself",
6265
columns=2,
6366
fields=[
@@ -70,48 +73,71 @@ async def initial_form_agent(
7073
):
7174
"""Agent that collects user information through an initial form"""
7275

73-
# Parse the form data from the initial message
74-
form_data = form.parse_form_response(message=message)
75-
76-
# Access the form values
77-
first_name = form_data.values['first_name'].value
78-
last_name = form_data.values['last_name'].value
76+
# Parse the form data using a Pydantic model
77+
user_info = form.parse_initial_form(model=UserInfo)
7978

80-
yield f"Hello {first_name} {last_name}! Nice to meet you."
79+
if user_info is None:
80+
yield "No form data received."
81+
else:
82+
yield f"Hello {user_info.first_name} {user_info.last_name}! Nice to meet you."
8183

82-
def run():
83-
server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000)))
8484

8585
if __name__ == "__main__":
86-
run()
86+
server.run()
8787
```
8888

8989
## Dynamic Form Requests
9090

91-
For dynamic form requests during conversation, you can request forms at any point when your agent needs structured input. This is useful when your agent needs to collect additional information based on the conversation flow:
91+
For dynamic form requests during conversation, you can request forms at any point when your agent needs structured input. This is useful when your agent needs to collect additional information based on the conversation flow.
92+
93+
### Quickstart
94+
95+
<Steps>
96+
<Step title="Import the request form extension">
97+
Import `RequestFormExtensionServer`, `RequestFormExtensionSpec`, `FormRender`, and field types from the Agent Stack SDK.
98+
</Step>
99+
100+
<Step title="Define your form data model">
101+
Create a Pydantic model with fields matching your form field IDs.
102+
</Step>
103+
104+
<Step title="Add request form parameter to your agent">
105+
Inject the request form extension into your agent function using `RequestFormExtensionSpec()`.
106+
</Step>
107+
108+
<Step title="Request form dynamically">
109+
Call `await request_form.request_form(form=FormRender(...), model=YourModel)` when you need to collect structured input.
110+
</Step>
111+
</Steps>
92112

93113
```python
94-
import os
95114
from typing import Annotated
96115

97116
from a2a.types import Message
98117
from a2a.utils.message import get_message_text
118+
from pydantic import BaseModel
99119
from agentstack_sdk.server import Server
100-
from agentstack_sdk.a2a.extensions.ui.form import (
101-
FormExtensionServer,
102-
FormExtensionSpec,
103-
FormRender,
104-
TextField
120+
from agentstack_sdk.a2a.extensions.common.form import FormRender, TextField
121+
from agentstack_sdk.a2a.extensions.ui.request_form import (
122+
RequestFormExtensionServer,
123+
RequestFormExtensionSpec,
105124
)
106125

107126
server = Server()
108127

128+
129+
class ContactInfo(BaseModel):
130+
email: str | None
131+
phone: str | None
132+
company: str | None
133+
134+
109135
@server.agent()
110136
async def dynamic_form_agent(
111137
message: Message,
112-
form: Annotated[
113-
FormExtensionServer,
114-
FormExtensionSpec(params=None)
138+
request_form: Annotated[
139+
RequestFormExtensionServer,
140+
RequestFormExtensionSpec(),
115141
],
116142
):
117143
"""Agent that requests forms dynamically during conversation"""
@@ -121,55 +147,47 @@ async def dynamic_form_agent(
121147
# Check if user wants to provide contact information
122148
if "contact" in user_input.lower() or "reach" in user_input.lower():
123149
# Request contact form dynamically
124-
form_data = await form.request_form(
150+
contact_info = await request_form.request_form(
125151
form=FormRender(
126-
id="contact_form",
127152
title="Please provide your contact information",
128153
columns=2,
129154
fields=[
130155
TextField(id="email", label="Email Address", col_span=2),
131156
TextField(id="phone", label="Phone Number", col_span=1),
132157
TextField(id="company", label="Company", col_span=1),
133158
],
134-
)
159+
),
160+
model=ContactInfo,
135161
)
136162

137-
email = form_data.values['email'].value
138-
phone = form_data.values['phone'].value
139-
company = form_data.values['company'].value
140-
141-
yield f"Thank you! I'll contact you at {email} or {phone} regarding {company}."
163+
if contact_info is None:
164+
yield "No contact information received."
165+
else:
166+
yield f"Thank you! I'll contact you at {contact_info.email} or {contact_info.phone} regarding {contact_info.company}."
142167
else:
143168
yield "Hello! If you'd like me to contact you, just let me know and I'll ask for your details."
144169

145-
def run():
146-
server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", 8000)))
147170

148171
if __name__ == "__main__":
149-
run()
172+
server.run()
150173
```
151174

152175
## How to work with forms
153176

154177
Here's what you need to know to add form capabilities to your agent:
155178

156-
**Import the form extension**: Import `FormExtensionServer`, `FormExtensionSpec`, `FormRender`, and field types from `agentstack_sdk.a2a.extensions.ui.form`.
157-
158-
**Inject the extension**: Add a form parameter to your agent function using the `Annotated` type hint with `FormExtensionServer` and `FormExtensionSpec`.
179+
**Import the form components**:
180+
- For form fields and `FormRender`, import from `agentstack_sdk.a2a.extensions.common.form`
181+
- For initial forms, import `FormServiceExtensionServer` and `FormServiceExtensionSpec` from `agentstack_sdk.a2a.extensions.services.form`
182+
- For dynamic forms, import `RequestFormExtensionServer` and `RequestFormExtensionSpec` from `agentstack_sdk.a2a.extensions.ui.request_form`
159183

160-
**For initial forms**: Specify the form structure in the `FormExtensionSpec` parameters and use `parse_form_response()` to extract data from the initial message.
184+
**Inject the extension**: Add a form parameter to your agent function using the `Annotated` type hint.
161185

162-
**For dynamic forms**: Use an empty `FormExtensionSpec()` and call `await form.request_form()` with your form definition when needed.
186+
**For initial forms**: Use `FormServiceExtensionSpec.demand(initial_form=FormRender(...))` to specify the form structure and call `form.parse_initial_form(model=YourModel)` to extract data.
163187

164-
**Access form data**: Use `form_data.values['field_id'].value` to access the submitted values from your form fields. Different field types return different value types:
165-
166-
- **TextField/DateField**: Returns `str | None`
167-
- **FileField**: Returns `list[FileInfo] | None` where each `FileInfo` has `uri`, `name`, and `mime_type`
168-
- **SingleSelectField**: Returns `str | None` (selected option ID)
169-
- **MultiSelectField**: Returns `list[str] | None` (list of selected option IDs)
170-
- **CheckboxField**: Returns `bool | None`
188+
**For dynamic forms**: Use `RequestFormExtensionSpec()` and call `await request_form.request_form(form=FormRender(...), model=YourModel)` when needed.
171189

172-
As a convenient shortcut, you may use a custom model (Pydantic model, `TypedDict`, `dataclass`, or any class supported by `pydantic.TypeAdapter`) to load form data. For example, we can define a following model:
190+
**Access form data**: The recommended approach is to use a Pydantic model (or `TypedDict`, `dataclass`, or any class supported by `pydantic.TypeAdapter`) to load form data. Define a model with fields matching your form field IDs:
173191

174192
```python
175193
from pydantic import BaseModel
@@ -180,12 +198,27 @@ class ContactInfo(BaseModel):
180198
company: str | None
181199
```
182200

183-
Then, in agent code, pass `model=ContactInfo` to `parse_form_response(...)` or `request_form(...)` to get the form data directly as an instance of `ContactInfo`:
201+
Then, pass `model=ContactInfo` to `parse_initial_form(...)` or `request_form(...)` to get the form data directly as an instance of `ContactInfo`:
184202

185203
```python
186-
contact_info: ContactInfo = form.parse_form_response(message=message, model=ContactInfo)
204+
# For initial forms
205+
contact_info: ContactInfo | None = form.parse_initial_form(model=ContactInfo)
206+
207+
# For dynamic forms
208+
contact_info: ContactInfo | None = await request_form.request_form(
209+
form=FormRender(...),
210+
model=ContactInfo
211+
)
187212
```
188213

214+
If you don't use a model, the methods return `FormResponse` which has a `values` dictionary. You can access values using `form_data.values['field_id'].value`. Different field types return different value types:
215+
216+
- **TextField/DateField**: Returns `str | None`
217+
- **FileField**: Returns `list[FileInfo] | None` where each `FileInfo` has `uri`, `name`, and `mime_type`
218+
- **SingleSelectField**: Returns `str | None` (selected option ID)
219+
- **MultiSelectField**: Returns `list[str] | None` (list of selected option IDs)
220+
- **CheckboxField**: Returns `bool | None`
221+
189222
## Form Field Types
190223

191224
The Agent Stack supports various field types for collecting different kinds of structured data:
@@ -194,23 +227,24 @@ The Agent Stack supports various field types for collecting different kinds of s
194227
Basic text input fields for collecting strings, names, descriptions, etc.
195228

196229
```python
197-
from agentstack_sdk.a2a.extensions.ui.form import TextField
230+
from agentstack_sdk.a2a.extensions.common.form import TextField
198231

199232
TextField(
200233
id="username",
201234
label="Username",
202235
col_span=1,
203236
required=True,
204237
placeholder="Enter your username",
205-
default_value=""
238+
default_value="",
239+
type="text" # Optional, defaults to "text"
206240
)
207241
```
208242

209243
### DateField
210244
Date input fields for collecting dates and timestamps.
211245

212246
```python
213-
from agentstack_sdk.a2a.extensions.ui.form import DateField
247+
from agentstack_sdk.a2a.extensions.common.form import DateField
214248

215249
DateField(
216250
id="birth_date",
@@ -226,7 +260,7 @@ DateField(
226260
File upload fields for collecting files from users.
227261

228262
```python
229-
from agentstack_sdk.a2a.extensions.ui.form import FileField
263+
from agentstack_sdk.a2a.extensions.common.form import FileField
230264

231265
FileField(
232266
id="document",
@@ -241,7 +275,7 @@ FileField(
241275
Single-select dropdown fields for choosing single option from a list.
242276

243277
```python
244-
from agentstack_sdk.a2a.extensions.ui.form import OptionItem, SingleSelectField
278+
from agentstack_sdk.a2a.extensions.common.form import OptionItem, SingleSelectField
245279

246280
SingleSelectField(
247281
id="contact_method",
@@ -262,7 +296,7 @@ SingleSelectField(
262296
Multi-select dropdown fields for choosing multiple options from a list.
263297

264298
```python
265-
from agentstack_sdk.a2a.extensions.ui.form import OptionItem, MultiSelectField
299+
from agentstack_sdk.a2a.extensions.common.form import OptionItem, MultiSelectField
266300

267301
MultiSelectField(
268302
id="interests",
@@ -283,7 +317,7 @@ MultiSelectField(
283317
Single checkbox fields for boolean values.
284318

285319
```python
286-
from agentstack_sdk.a2a.extensions.ui.form import CheckboxField
320+
from agentstack_sdk.a2a.extensions.common.form import CheckboxField
287321

288322
CheckboxField(
289323
id="newsletter",
@@ -300,8 +334,9 @@ CheckboxField(
300334
Control how your form appears using the `FormRender` configuration:
301335

302336
```python
337+
from agentstack_sdk.a2a.extensions.common.form import FormRender
338+
303339
FormRender(
304-
id="my_form",
305340
title="Form Title",
306341
description="Optional description text below the title",
307342
columns=2, # Number of columns in the form grid
@@ -313,11 +348,10 @@ FormRender(
313348
```
314349

315350
**FormRender properties**:
316-
- **`id`**: Unique identifier for the form (required)
317-
- **`title`**: Main heading displayed above the form
351+
- **`title`**: Main heading displayed above the form (optional)
318352
- **`description`**: Optional description text displayed below the title
319-
- **`columns`**: Number of columns in the form grid (1-4)
320-
- **`submit_label`**: Custom text for the submit button (default: "Submit")
353+
- **`columns`**: Number of columns in the form grid (1-4, optional)
354+
- **`submit_label`**: Custom text for the submit button (optional, default: "Submit")
321355
- **`fields`**: List of form field definitions (required)
322356

323357
<Tip>

0 commit comments

Comments
 (0)