Issue with submitting tool outputs in assistants stream

I get the following error:

Error: 400 1 validation error for Request
body -> tool_outputs
  extra fields not permitted (type=value_error.extra)

when submitting tool outputs in a stream. As far as I can tell my code is identical to the api docs:

 const { content, assistantId, fnRegistry } = req
        const { client } = this

        const threadId = req.threadId ?? (await client.beta.threads.create()).id

        const lastRuns = await client.beta.threads.runs.list(threadId, { limit: 1 })
        const lastRun = lastRuns.data[0]
        if (lastRun && !FINISHED_STATUSES.includes(lastRun.status)) {
            throw new Error("This thread already has an active run, please wait")
        }

        await client.beta.threads.messages.create(threadId, {
            role: "user",
            content
        })

        const resultTextStream = []
        const args = {}
        return new Promise(resolve => {
            let runId = ""
            client.beta.threads.runs
                .createAndStream(threadId, {
                    assistant_id: assistantId
                })

                .on("run", run => {
                    runId = run.id
                })
                .on("runStepDone", () => resolve({ threadId }))
                .on("textCreated", text => console.log(`\nassistant > ${text.value}\n\n`))
                .on("textDelta", textDelta => {
                    resultTextStream.push(textDelta.value)
                    console.log(resultTextStream.join(""))
                })
                .on("toolCallCreated", toolCall => console.log(`\nassistant > ${toolCall.type}\n\n`))
                .on("toolCallDone", async (toolCall: FunctionToolCall) => {
                    const argP = JSON.parse(args[toolCall.id].join(""))
                    const { result } = await callFn(toolCall.function.name, argP, fnRegistry)
                    console.log(result)
                    const stream = await client.beta.threads.runs.submitToolOutputs(threadId, runId, {
                        tool_outputs: [
                            {
                                tool_call_id: toolCall.id,
                                output: "Done"
                            }
                        ]
                    })

                    for await (const event of stream) {
                        console.log(event)
                    }
                })

                .on("toolCallDelta", (toolCallDelta, snapshot) => {
                    if (toolCallDelta.type === "function") {
                        args[snapshot.id] = [...(args[snapshot.id] ?? []), toolCallDelta.function.arguments]
                    }
                    if (toolCallDelta.type === "code_interpreter") {
                        if (toolCallDelta.code_interpreter.input) {
                            console.log(toolCallDelta.code_interpreter.input)
                        }
                        if (toolCallDelta.code_interpreter.outputs) {
                            console.log("\noutput >\n")
                            toolCallDelta.code_interpreter.outputs.forEach(output => {
                                if (output.type === "logs") {
                                    console.log(`\n${output.logs}\n`)
                                }
                            })
                        }
                    }
                })
        })
    }

Any idea what this could mean?

1 Like

Welcome to the forum.

Maybe this is related?

If not, let us know, and we can dig a bit deeper.

Thanks for the response. However that doesn’t appear to be the case, as my format is the latter. The error is also slightly different…

This format was working on the previous version prior to streaming being released.

It would be nice to see a full example of a streaming assistant run, with function call (or ideally multiple calls). It’s pretty hard to piece together from the docs.

Is there one somewhere that anyone can point to?

I figured out the issue, the run_id was an empty string. The error message was throwing me, so recommend that is tidied up a bit :slight_smile:

FYI this is how I retrieve the run_id, not the most elegant so if someone has a better solution:

        let runId
        return new Promise(resolve => {
            client.beta.threads.runs
                .createAndStream(threadId, {
                    assistant_id: assistantId
                })
                .on("runStepCreated", run => {
                    runId = run.run_id
                })
                .on("runStepDone", () => {
                    console.log("runStepDone")
                    resolve({ threadId })
                })
                .on("textCreated", text => console.log(`\nassistant > ${text.value}\n\n`))
                .on("textDelta", textDelta => {
                    resultTextStream.push(textDelta.value)
                    console.log(resultTextStream.join(""))
                })

                .on("toolCallCreated", toolCall => console.log(`\nassistant > ${toolCall.type}\n\n`))
                .on("toolCallDone", async (toolCall: FunctionToolCall) => {
                    const argP = JSON.parse(args[toolCall.id].join(""))
                    const { result } = await callFn(toolCall.function.name, argP, fnRegistry)

                    const stream = await client.beta.threads.runs.submitToolOutputs(threadId, runId, {
                        stream: true,
                        tool_outputs: [
                            {
                                tool_call_id: toolCall.id,
                                output: JSON.stringify(result)
                            }
                        ]
                    })

                    for await (const event of stream) {
                        console.log(event)
                    }
                })

                .on("toolCallDelta", (toolCallDelta, snapshot) => {
                    if (toolCallDelta.type === "function") {
                        args[snapshot.id] = [...(args[snapshot.id] ?? []), toolCallDelta.function.arguments]
                    }
                })
        })
3 Likes

Thanks Paul, this is super helpful. Were you able to get this to work when there are multiple function/tool calls for the same thread? For the ‘toolCallDelta’ event, the snapshot object seems to be empty for anything other than the first tool

I believe multiple tool output calls are handled in the example i posted here: