feat(docs): python support agent cookbook example#3623
feat(docs): python support agent cookbook example#3623abelanger5 merged 12 commits intohatchet-dev:mainfrom
Conversation
|
@BloggerBust is attempting to deploy a commit to the Hatchet Team on Vercel. A member of the Team first needs to authorize it. |
ec71b43 to
6e320ec
Compare
This adds the new workflow-support-agent page to the cookbooks sidebar with a "Workflow Patterns" separator to match the section added to the cookbooks index in PR hatchet-dev#3623.
6e320ec to
9998a39
Compare
|
Promptless prepared a documentation update related to this change. Triggered by PR #3623 The PR adds the cookbook page but is missing the sidebar navigation update in Review: Add support agent meta entry |
6c74e04 to
98eef2b
Compare
|
Added the Workflow Patterns separator and the support agent entry to |
78bd25d to
2a375ab
Compare
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
mrkaye97
left a comment
There was a problem hiding this comment.
left a couple of small comments on the python side! looks great broadly though, very cool
| text = subject + " " + body | ||
|
|
||
| if any(word in text for word in ["bill", "charge", "payment", "invoice"]): | ||
| category = "billing" |
There was a problem hiding this comment.
maybe we can make this and the priority into enums if we're always returning one of a few categories? doesn't super duper matter, but one of those things that if we're going to be making recommendations via these examples for how to structure things, I'd love to recommend
There was a problem hiding this comment.
Thank you for the feedback Matt! I considered enums here, but I kept these as simple strings to keep the example compact and focused on the workflow pattern, and to avoid adding extra modeling overhead in a small cookbook example.
| @hatchet.task(input_validator=SupportTicketInput) | ||
| async def generate_reply(input: SupportTicketInput, ctx: Context) -> ReplyOutput: | ||
| """Generate an initial support reply using Claude.""" | ||
| api_key = os.environ.get("ANTHROPIC_API_KEY") |
There was a problem hiding this comment.
ideally we'd prefer pydantic-settings for managing config IMO, or at the very least, read this on worker start and crash immediately so we don't fail tasks at runtime
There was a problem hiding this comment.
That makes sense in production, but for this cookbook example I intentionally kept the provider / API key optional and supplied a fixed fallback response when it is not provided. I wanted the example to remain runnable even for users who do not have an Anthropic API key, so they can still see the intended workflow behavior end to end.
| import importlib | ||
|
|
||
| anthropic = importlib.import_module("anthropic") | ||
| client = anthropic.AsyncAnthropic(api_key=api_key) |
There was a problem hiding this comment.
what's the reason for burying this import? so we don't need to add any deps? (if yes totally fine, although maybe we could also set this example up as an extra or an optional dep so we could rework here a bit)
There was a problem hiding this comment.
That is correct. I kept the Anthropic import local so the dependency remains optional for this cookbook example, which lets users run the example even if they do not have an API key or do not want to install the provider dependency just to follow the workflow pattern.
| @hatchet.durable_task(input_validator=SupportTicketInput) | ||
| async def support_agent( | ||
| input: SupportTicketInput, ctx: DurableContext | ||
| ) -> dict[str, Any]: |
There was a problem hiding this comment.
would be great to return a pydantic model here too if we can
There was a problem hiding this comment.
Thanks, I agree that a typed Pydantic return model could be a nice refinement here. I left the workflow return as a simple dict to keep the example smaller and focused on the control-flow pattern.
| wait_result = await ctx.aio_wait_for( | ||
| "await-customer-reply", | ||
| or_( | ||
| SleepCondition(timedelta(seconds=TIMEOUT_SECONDS)), | ||
| UserEventCondition( | ||
| event_key=REPLY_EVENT_KEY, | ||
| scope=input.ticket_id, | ||
| consider_events_since=consider_events_since, | ||
| ), | ||
| ), | ||
| ) | ||
|
|
||
| # The or-group result is {"CREATE": {"<condition_key>": ...}}. | ||
| # Check whether the reply event condition was the one that resolved. | ||
| resolved_key = list(wait_result["CREATE"].keys())[0] | ||
| customer_replied = resolved_key == REPLY_EVENT_KEY |
There was a problem hiding this comment.
so this is an issue on the SDK for sure, and I need to think about how to fix it, but what's the objective here? is it basically to wait for the event with a timeout? the raw aio_wait_for with an or group kind of sucks because it returns this thing that really should be internal, as you've discovered, so I might need to cook up some better way of doing this sort of thing... I wonder if adding a timeout: timedelta parameter to the wait could work, or something like that... I'm not too sure. I think this is okay for now, but ideally we could do this in a way where we don't need to use the internal CREATE match object, although that might be challenging
There was a problem hiding this comment.
Yes, exactly, the objective here is to wait for the customer reply event with a timeout. I agree the raw aio_wait_for(... or_(...)) result is a bit awkward to expose in an example, but this seemed like the most direct way to express the branching with the current SDK. As the SDK improves I would be happy to revisit the example.
abelanger5
left a comment
There was a problem hiding this comment.
💯 this looks great! Left some small comments and going to let @mrkaye97 leave comments on the code. In general I think explaining concepts a bit more and adding internal links to docs would be really helpful, but I like the clear and direct tone and it's easy to follow along.
2a375ab to
406e60a
Compare
- add support_agent durable workflow example - add triage, reply generation, and escalation tasks - use consider_events_since for early scoped reply events - add trigger script for the example - add E2E tests for resolved and timeout paths - register support_agent workflows in the central example worker
Also include how to run the tests themselves.
272590d to
ca1d3d0
Compare
* feat(python): add support_agent example with durable reply handling - add support_agent durable workflow example - add triage, reply generation, and escalation tasks - use consider_events_since for early scoped reply events - add trigger script for the example - add E2E tests for resolved and timeout paths - register support_agent workflows in the central example worker * docs(cookbooks): add support agent workflow cookbook and example snippets * docs(cookbooks): add support agent sidebar entry * docs(cookbooks): minor improvement to support agent intro and fallback wording * docs(cookbooks): document support agent worker registration * docs(cookbooks): document support agent worker registration * docs(lint): Fixed black errors * docs(cookbooks): Improve phrasing and explanations Also include how to run the tests themselves. * docs(cookbooks): replace ASCII diagram with Mermaid * docs(cookbooks): Generalize intro beyond support workflows * docs(cookbooks): add links to support agent cookbook * docs(cookbooks): clarify durable workflow rationale
Description
Adds a new cookbook docs page for building a support agent with Hatchet, along with the supporting Python example and trigger/example files.
This PR introduces a practical end-to-end workflow example that:
It also adds a new Workflow Patterns section to the cookbooks index so this example is separate from the existing webhook-focused cookbook entries.
The Python example uses the snippet markers and was validated locally against a live Hatchet instance. The current implementation supports live Claude interaction when
ANTHROPIC_API_KEYis set, and a fixed fallback reply when it is not.Type of change
What's Changed
workflow-support-agent.mdxexamples/python/support_agent/consider_events_sinceto safely capture early reply events