@@ -121,6 +121,8 @@ WorkflowAI supports a long list of models. The source of truth for models we sup
121121You can set the model explicitly in the agent decorator:
122122
123123``` python
124+ from workflowai import Model
125+
124126@workflowai.agent (model = Model.GPT_4O_LATEST )
125127def say_hello (input : Input) -> Output:
126128 ...
@@ -151,16 +153,31 @@ def say_hello(input: Input) -> AsyncIterator[Run[Output]]:
151153 ...
152154```
153155
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.
155160
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.
157163
158164``` python
159- # Return the full run object, useful if you want to extract metadata like cost or duration
160165@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+ ```
163175
176+ ### Streaming
177+
178+ You can configure the agent function to stream by changing the type annotation to an AsyncIterator.
179+
180+ ``` python
164181# Stream the output, the output is filled as it is generated
165182@workflowai.agent ()
166183def say_hello (input : Input) -> AsyncIterator[Output]:
@@ -172,6 +189,38 @@ def say_hello(input: Input) -> AsyncIterator[Run[Output]]:
172189 ...
173190```
174191
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+
175224### Tools
176225
177226Tools 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
222271 """ Return the current time in the given timezone in iso format"""
223272 return datetime.now(ZoneInfo(timezone)).isoformat()
224273
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+
225281@agent (
226282 id = " answer-question" ,
227- tools = [get_current_time],
283+ tools = [get_current_time, fetch_webpage ],
228284 version = VersionProperties(model = Model.GPT_4O_LATEST ),
229285)
230286async def answer_question (_ : AnswerQuestionInput) -> Run[AnswerQuestionOutput]: ...
@@ -261,6 +317,29 @@ except WorkflowAIError as e:
261317 print (e.message)
262318```
263319
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+
264343### Definining input and output types
265344
266345There are some important subtleties when defining input and output types.
@@ -368,3 +447,32 @@ async for run in say_hello(Input(name="John")):
368447 print (run.output.greeting1) # will be empty if the model has not generated it yet
369448
370449```
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