@@ -121,6 +121,8 @@ WorkflowAI supports a long list of models. The source of truth for models we sup
121
121
You can set the model explicitly in the agent decorator:
122
122
123
123
``` python
124
+ from workflowai import Model
125
+
124
126
@workflowai.agent (model = Model.GPT_4O_LATEST )
125
127
def say_hello (input : Input) -> Output:
126
128
...
@@ -151,16 +153,31 @@ def say_hello(input: Input) -> AsyncIterator[Run[Output]]:
151
153
...
152
154
```
153
155
154
- ### Streaming and advanced usage
156
+ ### The Run object
157
+
158
+ Although having an agent only return the run output covers most use cases, some use cases require having more
159
+ information about the run.
155
160
156
- You can configure the agent function to stream or return the full run object, simply by changing the type annotation.
161
+ By changing the type annotation of the agent function to ` Run[Output] ` , the generated function will return
162
+ the full run object.
157
163
158
164
``` python
159
- # Return the full run object, useful if you want to extract metadata like cost or duration
160
165
@workflowai.agent ()
161
- async def say_hello (input : Input) -> Run[Output]:
162
- ...
166
+ async def say_hello (input : Input) -> Run[Output]: ...
167
+
168
+
169
+ run = await say_hello(Input(name = " John" ))
170
+ print (run.output) # the output, as before
171
+ print (run.model) # the model used for the run
172
+ print (run.cost_usd) # the cost of the run in USD
173
+ print (run.duration_seconds) # the duration of the inference in seconds
174
+ ```
163
175
176
+ ### Streaming
177
+
178
+ You can configure the agent function to stream by changing the type annotation to an AsyncIterator.
179
+
180
+ ``` python
164
181
# Stream the output, the output is filled as it is generated
165
182
@workflowai.agent ()
166
183
def say_hello (input : Input) -> AsyncIterator[Output]:
@@ -172,6 +189,38 @@ def say_hello(input: Input) -> AsyncIterator[Run[Output]]:
172
189
...
173
190
```
174
191
192
+ ### Replying to a run
193
+
194
+ Some use cases require the ability to have a back and forth between the client and the LLM. For example:
195
+
196
+ - tools [ see below] ( #tools ) use the reply ability internally
197
+ - chatbots
198
+ - correcting the LLM output
199
+
200
+ In WorkflowAI, this is done by replying to a run. A reply can contain:
201
+
202
+ - a user response
203
+ - tool results
204
+
205
+ <!-- TODO: find a better example for reply -->
206
+
207
+ ``` python
208
+ # Returning the full run object is required to use the reply feature
209
+ @workflowai.agent ()
210
+ async def say_hello (input : Input) -> Run[Output]:
211
+ ...
212
+
213
+ run = await say_hello(Input(name = " John" ))
214
+ run = await run.reply(user_response = " Now say hello to his brother James" )
215
+ ```
216
+
217
+ The output of a reply to a run has the same type as the original run, which makes it easy to iterate towards the
218
+ construction of a final output.
219
+
220
+ > To allow run iterations, it is very important to have outputs that are tolerant to missing fields, aka that
221
+ > have default values for most of their fields. Otherwise the agent will throw a WorkflowAIError on missing fields
222
+ > and the run chain will be broken.
223
+
175
224
### Tools
176
225
177
226
Tools allow enhancing an agent's capabilities by allowing it to call external functions.
@@ -222,9 +271,16 @@ def get_current_time(timezone: Annotated[str, "The timezone to get the current t
222
271
""" Return the current time in the given timezone in iso format"""
223
272
return datetime.now(ZoneInfo(timezone)).isoformat()
224
273
274
+ # Tools can also be async
275
+ async def fetch_webpage (url : str ) -> str :
276
+ """ Fetch the content of a webpage"""
277
+ async with httpx.AsyncClient() as client:
278
+ response = await client.get(url)
279
+ return response.text
280
+
225
281
@agent (
226
282
id = " answer-question" ,
227
- tools = [get_current_time],
283
+ tools = [get_current_time, fetch_webpage ],
228
284
version = VersionProperties(model = Model.GPT_4O_LATEST ),
229
285
)
230
286
async def answer_question (_ : AnswerQuestionInput) -> Run[AnswerQuestionOutput]: ...
@@ -261,6 +317,29 @@ except WorkflowAIError as e:
261
317
print (e.message)
262
318
```
263
319
320
+ #### Recoverable errors
321
+
322
+ Sometimes, the LLM outputs an object that is partially valid, good examples are:
323
+
324
+ - the model context window was exceeded during the generation
325
+ - the model decided that a tool call result was a failure
326
+
327
+ In this case, an agent that returns an output only will always raise an ` InvalidGenerationError ` which
328
+ subclasses ` WorkflowAIError ` .
329
+
330
+ However, an agent that returns a full run object will try to recover from the error by using the partial output.
331
+
332
+ ``` python
333
+
334
+ run = await agent(input = Input(name = " John" ))
335
+
336
+ # The run will have an error
337
+ assert run.error is not None
338
+
339
+ # The run will have a partial output
340
+ assert run.output is not None
341
+ ```
342
+
264
343
### Definining input and output types
265
344
266
345
There are some important subtleties when defining input and output types.
@@ -368,3 +447,32 @@ async for run in say_hello(Input(name="John")):
368
447
print (run.output.greeting1) # will be empty if the model has not generated it yet
369
448
370
449
```
450
+
451
+ #### Field properties
452
+
453
+ Pydantic allows a variety of other validation criteria for fields: minimum, maximum, pattern, etc.
454
+ This additional criteria are included the JSON Schema that is sent to WorkflowAI, and are sent to the model.
455
+
456
+ ``` python
457
+ class Input (BaseModel ):
458
+ name: str = Field(min_length = 3 , max_length = 10 )
459
+ age: int = Field(ge = 18 , le = 100 )
460
+ email: str = Field(pattern = r " ^ [a-zA-Z0-9_.+- ]+ @[a-zA-Z0-9- ]+ \. [a-zA-Z0-9-. ]+ $ " )
461
+ ```
462
+
463
+ These arguments can be used to stir the model in the right direction. The caveat is have a
464
+ validation that is too strict can lead to invalid generations. In case of an invalid generation:
465
+
466
+ - WorkflowAI retries the inference once by providing the model with the invalid output and the validation error
467
+ - if the model still fails to generate a valid output, the run will fail with an ` InvalidGenerationError ` .
468
+ the partial output is available in the ` partial_output ` attribute of the ` InvalidGenerationError `
469
+
470
+ ``` python
471
+
472
+ @agent ()
473
+ def my_agent (_ : Input) -> :...
474
+ ```
475
+
476
+ ## Contributing
477
+
478
+ See the [ CONTRIBUTING.md] ( ./CONTRIBUTING.md ) file for more details.
0 commit comments