Add useWorkflow hook for consuming llama-deploy workflows #19

Closed
opened 2026-02-16 02:17:43 -05:00 by yindo · 1 comment
Owner

Originally created by @marcusschiesser on GitHub (Jun 10, 2025).

Originally assigned to: @thucpn on GitHub.

Spec

/** The Hook! */
function useWorkflow<
  I extends WorkflowEvent = AnyWorkflowEvent,
  O extends WorkflowEvent = AnyWorkflowEvent
>(params: WorkflowHookParams<O>): WorkflowHookHandler<I, O> {
  // ???
}

/** This is the input argument to the hook */
interface WorkflowHookParams<O extends WorkflowEvent = AnyWorkflowEvent> {
  /** the name of the registered workflow on the llama_deploy server */
  workflow: string;
  /** An already instantiated workflow, for resumption */
  taskId?: string;
  /** 
    * callback triggered on receipt of the StopEvent, signalling completion.
    * this event will also be the last event in the list
    */
  onStopEvent?: (event: O) => void;
  /**
    * callback triggered when the workflow has a fatal error.
    * Not sure if llama_deploy communicates errors yet?
    */
  onError?: (tbd: any) => void;
  // an optional base url for the workflow API
  baseUrl?: string;
}


/** This is the return value from the hook */
interface WorkflowHookHandler<
  I extends WorkflowEvent = AnyWorkflowEvent,
  O extends WorkflowEvent = AnyWorkflowEvent,
> {
  /** The task ID, once the workflow session is started */
  taskId?: string
  /** List of all events fired so far. Includes input events sent by the client
    * streamed and result events from the workflow. 
    * Concatenated when new events.
    * Oldest event is at the start (time ascending order)
    */
  events: (I | O)[];
  /** all events before this index were backfilled from a workflow resumption. */
  backfillIndex: number;
  /** 
    * true after the workflow is currently completed. The workflow can be started
    * again after completing by sending a start event. This will return back to
    * false when restarted.
    */
  isComplete: boolean;
  /** true if the workflow had a fatal error */
  isError: boolean;
  /** Manually trigger an event, promise resolves after the event is acked by the server */
  sendEvent(event: I): Promise<void>;
}

/** A generic interface for WorkflowEvents. define sub instances to get more specific fields */
interface WorkflowEvent {
  name: string; // the python class name of the event, for switching on
} 

// Default workflow event, so you can get or send events without specifying a sub-type
interface AnyWorkflowEvent extends WorkflowEvent {
  [key: string]: any // any json value
}

Example

/** A component that starts with a file, the assistant thinks about it and gives a summary. Then normal chat after that */
function AnnotatedChatTriggeredByFileUpload(props: ) {
   
   const taskId = useMemo(() => /* check local storage for existing chat */)
   const handler = useWorkflow({
     workflow: "ChatWithFile",
     taskId,
   })
   const [file, setFile] = useState();
   
   useEffect(() => {
     if (handler.taskId && file) {
        // send a file whenever a one one is set.
        handler.sendEvent({name: "AnalyzeFile", file: file })
     }
     
   }, [handler.taskId, taskId, file])
   
   if (!handler.taskId) return <Loading />
   if (!file && !handler.events.length)
        return <FilePicker onFile={setFile} ]>
   
   return (<>
     <ShowEvents events={handler.events} />
     {handler.complete && <ChatInput onSubmit={(text) => handler.sendEvent({name: "Chat", content: text })} />}
   </>)
  
}

function ShowEvents({events: AnyWorkflowEvent[]}) {
   
   return events.map(x => <ShowEvent event={x} />)
}

function ShowEvent({event: AnyWorkflowEvent}) {
  if (event.name === "AnalyzeFile") {
    return <FileIcon name={event.file.name} />
  }
  if (event.name === "Thinking") {
     return <ThinkingBubble message={event} />
  }
  if (event.name === "FileReport") {
     return <FileReport data={event.data} />
  }
  // ...
  return null;

}
Originally created by @marcusschiesser on GitHub (Jun 10, 2025). Originally assigned to: @thucpn on GitHub. ## Spec ```ts /** The Hook! */ function useWorkflow< I extends WorkflowEvent = AnyWorkflowEvent, O extends WorkflowEvent = AnyWorkflowEvent >(params: WorkflowHookParams<O>): WorkflowHookHandler<I, O> { // ??? } /** This is the input argument to the hook */ interface WorkflowHookParams<O extends WorkflowEvent = AnyWorkflowEvent> { /** the name of the registered workflow on the llama_deploy server */ workflow: string; /** An already instantiated workflow, for resumption */ taskId?: string; /** * callback triggered on receipt of the StopEvent, signalling completion. * this event will also be the last event in the list */ onStopEvent?: (event: O) => void; /** * callback triggered when the workflow has a fatal error. * Not sure if llama_deploy communicates errors yet? */ onError?: (tbd: any) => void; // an optional base url for the workflow API baseUrl?: string; } /** This is the return value from the hook */ interface WorkflowHookHandler< I extends WorkflowEvent = AnyWorkflowEvent, O extends WorkflowEvent = AnyWorkflowEvent, > { /** The task ID, once the workflow session is started */ taskId?: string /** List of all events fired so far. Includes input events sent by the client * streamed and result events from the workflow. * Concatenated when new events. * Oldest event is at the start (time ascending order) */ events: (I | O)[]; /** all events before this index were backfilled from a workflow resumption. */ backfillIndex: number; /** * true after the workflow is currently completed. The workflow can be started * again after completing by sending a start event. This will return back to * false when restarted. */ isComplete: boolean; /** true if the workflow had a fatal error */ isError: boolean; /** Manually trigger an event, promise resolves after the event is acked by the server */ sendEvent(event: I): Promise<void>; } /** A generic interface for WorkflowEvents. define sub instances to get more specific fields */ interface WorkflowEvent { name: string; // the python class name of the event, for switching on } // Default workflow event, so you can get or send events without specifying a sub-type interface AnyWorkflowEvent extends WorkflowEvent { [key: string]: any // any json value } ``` ## Example ``` /** A component that starts with a file, the assistant thinks about it and gives a summary. Then normal chat after that */ function AnnotatedChatTriggeredByFileUpload(props: ) { const taskId = useMemo(() => /* check local storage for existing chat */) const handler = useWorkflow({ workflow: "ChatWithFile", taskId, }) const [file, setFile] = useState(); useEffect(() => { if (handler.taskId && file) { // send a file whenever a one one is set. handler.sendEvent({name: "AnalyzeFile", file: file }) } }, [handler.taskId, taskId, file]) if (!handler.taskId) return <Loading /> if (!file && !handler.events.length) return <FilePicker onFile={setFile} ]> return (<> <ShowEvents events={handler.events} /> {handler.complete && <ChatInput onSubmit={(text) => handler.sendEvent({name: "Chat", content: text })} />} </>) } function ShowEvents({events: AnyWorkflowEvent[]}) { return events.map(x => <ShowEvent event={x} />) } function ShowEvent({event: AnyWorkflowEvent}) { if (event.name === "AnalyzeFile") { return <FileIcon name={event.file.name} /> } if (event.name === "Thinking") { return <ThinkingBubble message={event} /> } if (event.name === "FileReport") { return <FileReport data={event.data} /> } // ... return null; } ```
yindo closed this issue 2026-02-16 02:17:43 -05:00
Author
Owner

@marcusschiesser commented on GitHub (Jun 10, 2025):

updated llama-deploy backend: https://github.com/run-llama/llama_deploy/pull/536 - run it locally and use baseUrl in useWorkflow to point to the local development

@marcusschiesser commented on GitHub (Jun 10, 2025): updated llama-deploy backend: https://github.com/run-llama/llama_deploy/pull/536 - run it locally and use `baseUrl` in `useWorkflow` to point to the local development
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: run-llama/chat-ui#19