Skip to content

Conversation

@mrderyk
Copy link
Contributor

@mrderyk mrderyk commented Mar 20, 2025

This PR enables adding an agentic configuration to react-chatbot. Requests to agentic web services occur on every message by the user, and behavior specified in the configuration allows the chatbot to react to the web service response in ways desired by the developer.

The Agentic Web Service

The agentic web service should send back a response in the form of:

{
  event: string;
  message?: {
    // An optional response that the chatbot should give the user.
    // If not present, the react-chatbot will default to using the chat API
    content?: string;

    // Optional text that the chatbot should append to the end of its response to the user.
    // Useful for cases where we want to combine an answer from the chat API and a 
    // special message from the agentic web service
    post?: string;
  }
}

The chatbot can then parse at least the event property to determine follow-up behavior.

The Chatbot Response Handler

Based on the web service response, the chatbot can take action in 2 ways, all specified in a callback handler:

  1. Send a predefined response back to the user.
  2. Optionally, give the user a number of "user actions" they can take.

User Actions

If defined in the agentic callback handler, the chatbot may then make actions available for the user to select. These come in 3 forms, and may be a combination of all of them:

  1. The action allows the user to send a pre-defined message back to the chatbot (e.g. a "Confirm" action sends a "I agree" message to the chatbot)
  2. The user gets sent to a URL
  3. A callback is executed that performs some logic, e.g. sending a transcript of the chat history.

Configuration

This is all done by adding an agenticConfiguration to either the component or useChat hook:

const agenticConfiguration = {
  url: 'path/to/agentic/web/service',
  onAgenticResponse: (response: AgenticResponse) => {
    switch(response.event) {
      case "event-1":
        return {
          message: {
            content: "This is how the chatbot should respond to the user's query if the service returned 'event-1'",
        };
      case "event-2":
        return {
          message: {
            post: "The chatbot would append this message to the response from the chat API.",
          },
          userActionOptions: [
            // This is one action a user can take in response to this agentic event- in this case, navigating to a URL
            {
              label: "Navigate to a URL",
              url: "path/to/some/resource"
            },
            // This is another action a user can take in response to this agentic event- in this case, executing some logic
            {
              label: "Execute some logic",
              onSelect: () => console.log("on select");
            },
            // If we want the action to send a message to the chatbot, we can do that too.
            {
              label: "Say hello!",
              message: "Hello!",
            },
          ]
        }
      default:
        return;
    } 
  }
};

// via component:
<ReactChatbot
  {...legacyProps}
  agenticConfiguration={agenticConfiguration}
/>,

// via useChat:
const useChatReturnVal = useChat({
  ...legacyProperties,
  agenticConfiguration
});

@github-actions
Copy link

github-actions bot commented Mar 20, 2025

PR Preview Action v1.6.0
Preview removed because the pull request was closed.
2025-03-28 16:51 UTC

@mrderyk mrderyk force-pushed the feat/agentic_config branch 5 times, most recently from ab0d5e6 to e3e3a48 Compare March 21, 2025 19:40
@mrderyk mrderyk changed the title [WIP] feat: Accept agenticConfiguration prop to accommodate agentic behaviors. feat: Accept agenticConfiguration prop to accommodate agentic behaviors. Mar 21, 2025
@mrderyk mrderyk marked this pull request as ready for review March 21, 2025 19:43
@mrderyk mrderyk marked this pull request as draft March 21, 2025 23:02
@mrderyk mrderyk changed the title feat: Accept agenticConfiguration prop to accommodate agentic behaviors. [WIP] feat: Accept agenticConfiguration prop to accommodate agentic behaviors. Mar 21, 2025
@mrderyk mrderyk force-pushed the feat/agentic_config branch 2 times, most recently from db9679b to d0be897 Compare March 24, 2025 23:29
@mrderyk mrderyk force-pushed the feat/agentic_config branch from d0be897 to d0a2bb6 Compare March 25, 2025 00:06
@mrderyk mrderyk changed the title [WIP] feat: Accept agenticConfiguration prop to accommodate agentic behaviors. feat: Accept agenticConfiguration prop to accommodate agentic behaviors. Mar 25, 2025
@mrderyk mrderyk marked this pull request as ready for review March 25, 2025 17:12
Copy link

@MohammedMaaz MohammedMaaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, just some minor comments from my side.

const { options } = messageHistoryItem;

return (
<Fragment key={index}>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for using Fragment here, since there's only 1 child?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't recall, TBH. Could have been just to cleanly wrap it and have the key there instead of mixed in with the div that has a bunch of classes.

// @ts-ignore
(ref.current as ReactChatbotWebComponent).setAgenticConfiguration(props.agenticConfiguration);
}
}, [props]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we be more precise here and only include desired props?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure we can. But I'd like to keep the pattern we have when working with WebComponents for now so as not to add complexity to this PR.

Comment on lines +148 to +149
(globalThis as any).fetch ||= jest.fn();
const global = globalThis as typeof globalThis & { fetch: any };

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the following might be a more simpler equivalent of this, while also avoiding the use of any. Correct me if I'm wrong though.

Suggested change
(globalThis as any).fetch ||= jest.fn();
const global = globalThis as typeof globalThis & { fetch: any };
const global = { ...globalThis, fetch: globalThis.fetch ?? jest.fn() }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So unfortunately, we'll need to keep the original way of writing it.
In the context of jest, globalThis is essentially the global namespace (sort of like window in the browser context). As such, on line 148 we're mocking fetch in that global namespace.

With the change you've suggested, we're defining a new object called global and that's where we are providing the mock of fetch. So the problem there is that the global namespace doesn't get the fetch mock- only this new object which we've defined called global.

Furthermore, the only reason I originally defined global on line 149 was to be able to avoid casting it to that type below- it looked really messy to have to type:

const mockFetch = jest.spyOn(globalThis as typeof globalThis & { fetch: any }, "fetch").mockResolvedValue({
  status: 200,
  json: jest.fn().mockResolvedValue({ event: "mock-event" })
});

@mrderyk mrderyk requested a review from MohammedMaaz March 26, 2025 23:42
@mrderyk mrderyk force-pushed the feat/agentic_config branch from 53c78cd to c31ad26 Compare March 27, 2025 17:20
@mrderyk mrderyk merged commit d9a170d into main Mar 28, 2025
4 checks passed
@mrderyk mrderyk deleted the feat/agentic_config branch March 28, 2025 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants