Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle tool calls in playground #758

Merged
merged 1 commit into from
Jan 20, 2025
Merged

Handle tool calls in playground #758

merged 1 commit into from
Jan 20, 2025

Conversation

andresgutgon
Copy link
Contributor

@andresgutgon andresgutgon commented Jan 2, 2025

What?

We have a task to allow users to give a response on one or more tool calls that the AI can request in our playground. The thing is that the work necessary to make this work is also essential for making tool calling work on our SDK and Gateway. So this PR is a spike to have a picture of what are the moving parts necessary to make this happen.

image

Related issue

#598

Possible affected clients

These are the places I think these changes affect

  1. Web app playground
  2. Running prompts in batch (from playground)
  3. Users using our SDK clients
  4. Users using our API

TODO

  • Serialize chain AST and scope Done here
  • Store in a cache the serialized chain so it can be resumed
  • Generate a unique identifier of a SDK run so users can reference it when they call the tool. Can be this the documentLogUuid?
  • Present users with a UI in the playground to introduce tool call response
  • Send user's tool call response to gateway and resume previous playground session from cache.
  • Do the resume conversation logic inside addMessage if a cache chain exists for that message and there are tool calls.
  • Removed cached in redis chain when conversation is successfully resumed and completed.
  • Shared prompts tool calling story 💀 Don't allow tool calling responses in shared prompts UI cc @cesr
  • In the playground, send multiple tool calls at the same time when using chat form and some of the tools don't have a response.
  • INSTALL new promptl version
  • QA ⚠️

Tool calls when running prompt in Batch in Playground 🧠 🧠 🧠 🤯

How we can do this?

In SDK

Talked with @csansoon this is how it would look to have tool calls in SDK

const response = sdk.run(
  'weather_experet', 
  { location: "Barcelona" }, 
  { 
    tools: [
      // Same name as in schema
      get_weather: (location: string) => {
         // Call some API or DB and get weather in "loc"
         return { temperature: 23 }
      }
    ]
  }
)

Considerations

  • AI can respond with multiple tool calls at the same time. So maybe we need to handle it at the SDK level as a Promise.all and wait for all the calls to be finished before continuing.
  • What happens if the work to be done in one or more of the tool calls is heavy and needs to be put in a queue in the user's system? Do you think we should pass the reference to the cached partial response (serialized chain) in the tool calls?

@@ -28,6 +32,7 @@ export async function runDocumentAction({
parameters,
}: RunDocumentActionProps) {
const { workspace, user } = await getCurrentUserOrError()
let tools = []
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Requests to SDK in our playground are stateless. We need to implement a cache to remember where the user stopped because there were one o more tool call requested by the AI

(type === ChainEventTypes.StepComplete ||
type === ChainEventTypes.Complete) &&
data.response.streamType === 'text'
) {
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 think this is the combination of AI response that produce tool calls

  1. Step completed of type text
  2. Chain Completed of type text

Am I missing something?

@@ -305,6 +313,8 @@ async function runStep({
} else {
streamConsumer.stepCompleted(response)

// TODO: Here instead of running the state if the step has
// toolCalls we serialize the chain up until this point
Copy link
Contributor Author

Choose a reason for hiding this comment

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

When the AI is cached or when is fresh in both cases the step response can be stepCompleted or chainCompleted and we're doing the same.

Now we also want to handle tool calling and chain serialization so I think we need to DRY this in to a new unified method that handle all the cases

apps/web/next.config.mjs Outdated Show resolved Hide resolved
@andresgutgon andresgutgon changed the title Handle tool calls in SDK and playground Handle tool calls in playground Jan 3, 2025
apps/web/package.json Outdated Show resolved Hide resolved
@geclos
Copy link
Collaborator

geclos commented Jan 7, 2025

This API does not follow the pattern of other methods in the SDK:

const response = sdk.run(
  'weather_experet', 
  { location: "Barcelona" }, 
  { 
    tools: [
      // Same name as in schema
      get_weather: (location: string) => {
         // Call some API or DB and get weather in "loc"
         return { temperature: 23 }
      }
    ]
  }
)

Keep in mind sdk format follows this format: required arguments as positional + last options argument. This means that all required arguments are positional and then any optional arguments go in a last options arguments that is an object. In this case I imagine this would look something like:

const response = sdk.run(
  'weather_experet', 
  { 
   parameters: { location: 'barcelona'},
    tools: [
      get_weather: (location: string) => {
         return { temperature: 23 }
      }
    ]
  }
)

@geclos
Copy link
Collaborator

geclos commented Jan 7, 2025

When stopping the chain because there are tool calls. Emit a new event or emit chainCompleted? ask @geclos and @csansoon for opinions. Alex voted for a new, event

Isn't this the same event than when a step is completed? Since a tool call request wouldbe the response from a step and providing the response would start a new step in the chain.

@andresgutgon
Copy link
Contributor Author

Isn't this the same event than when a step is completed?

If we emit stepCompleted the stream is not stopped. I'm talking in our playground. For that I send chainCompleted to stop execution in the UI.

This works but maybe is not a good idea

@andresgutgon
Copy link
Contributor Author

This API does not follow the pattern of other methods in the SDK:

So we have to use positional arguments?

In OpenAI playground they show arguments as an object
image

@geclos
Copy link
Collaborator

geclos commented Jan 7, 2025

This API does not follow the pattern of other methods in the SDK:

So we have to use positional arguments?

In OpenAI playground they show arguments as an object
image

Let's maintain consistency across methods in our SDK, parameters is an optional parameter

documentLogUuid: string
}) {
const key = generateCacheKey({ documentLogUuid, workspace })
const serialized = chain.serialize()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

chain.serialize() is a new method introduced in that PR

// and add more messages to the same stream session.
//
// For doing this we need a new gateway endpoint to send the tool calls to the server and run the chain
// also the SDK method to be called here
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'll remove this comment. For now I opted for a different action. Let's see how it feels.

@andresgutgon andresgutgon force-pushed the spike/tool-calls branch 4 times, most recently from c6b81db to 5f11b4e Compare January 14, 2025 09:54
const text = data.text
const object = type === 'object' ? data.object : undefined
const toolCalls = type === 'text' ? (data.toolCalls ?? []) : []
let content: MessageContent[] = []
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: you can just avoid the content variable and push directly to the message.content

@andresgutgon andresgutgon force-pushed the spike/tool-calls branch 3 times, most recently from 5f622ed to 1afb6cd Compare January 16, 2025 10:37
const { completed, messages, config } = await (chain as PromptlChain).step(
prevText,
prevContent as PromptlMessage[] | undefined | string,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We now can add to promptl an array of messages as the previous response.

typeof toolCallResponse.result === 'string'
? JSON.parse(toolCallResponse.result)
: toolCallResponse.result,
isError: toolCallResponse.isError || false,
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 like the fact that we're building response message with the types of old compiler. But I don't want to change this now.

We should do a PR killing all compiler types and start using prompt ASAP

@andresgutgon andresgutgon removed the 🚧 wip Work in progress label Jan 17, 2025
Comment on lines +22 to +24
// FIXME: Kill compiler please. I made this module compatible with
// both old compiler and promptl. But because everything is typed with
// old compiler prompts in promptl version are also formatted as old compiler
Copy link
Contributor

Choose a reason for hiding this comment

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

I second this.

Comment on lines 75 to 89
<Text.H6>
You have{' '}
{totalToolRequests <= 1 ? (
<>
<Badge variant='muted'>{totalToolRequests}</Badge> tool to be
responded
</>
) : (
<>
<Badge variant='muted'>{currentToolRequest}</Badge> of{' '}
<Badge variant='muted'>{totalToolRequests}</Badge> tools to be
responded
</>
)}
</Text.H6>
Copy link
Contributor

Choose a reason for hiding this comment

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

Now that this is the first thing you read on the Tool Response input, since it is the top text, I'd move the (?) Tooltip here instead, to explain what do "tools to be responded" mean.

Comment on lines -37 to +38
const [isEditorMounted, setIsEditorMounted] = useState(false) // to avoid race conditions
// to avoid race conditions
const [isEditorMounted, setIsEditorMounted] = useState(false)
Copy link
Contributor

Choose a reason for hiding this comment

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

xd

csansoon
csansoon previously approved these changes Jan 20, 2025
Copy link
Contributor

@csansoon csansoon left a comment

Choose a reason for hiding this comment

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

lgtm!

We have a task to allow users give a response on one or more tool calls
that the AI can request in our playground. The thing is that the work
necessary to make this work is also necessary for making tool call work
on our SDK so this PR is a spike to have a picture of what are the
moving parts necessary to make this happen
@andresgutgon andresgutgon merged commit 4b30a94 into main Jan 20, 2025
4 checks passed
@andresgutgon andresgutgon deleted the spike/tool-calls branch January 20, 2025 09:44
@github-actions github-actions bot locked and limited conversation to collaborators Jan 20, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants