Skip to content

Conversation

dosier
Copy link

@dosier dosier commented Sep 5, 2025

Motivation and Context

Before this change it was not possible to use tool-calling when interacting with a LLM through the streaming interface.
This is useful to have as many front-end integrated agents may want to incrementally update the UI but still have tool calling support.

Relevant issues:

Breaking Changes

For now I have created backwards compatibility, introducing a new function that is implemented in PromptExecutor and LLMClient:

    public fun executeStreamingWithTools(
        prompt: Prompt,
        model: LLModel,
        tools: List<ToolDescriptor> = emptyList()
    ): Flow<StreamFrame>

And change executeStreaming default declaration to the following for backwards compatability:

    public fun executeStreaming(prompt: Prompt, model: LLModel): Flow<String> =
        executeStreamingWithTools(prompt, model, emptyList())
            .filterIsInstance<StreamFrame.Append>()
            .map { append -> append.text }

Also I introduced a function in AIAgentLLMWriteSession:

    public fun requestLLMStreamingWithTools(definition: StructuredDataDefinition? = null): Flow<StreamFrame> {
        if (definition != null) {
            val prompt = prompt(prompt, clock) {
                user {
                    definition.definition(this)
                }
            }
            this.prompt = prompt
        }
        return executor.executeStreamingWithTools(prompt, model, tools)
    }

Also in AIAgentNodes

@AIAgentBuilderDslMarker
public fun <T> AIAgentSubgraphBuilderBase<*, *>.nodeLLMRequestStreamingWithTools(
    name: String? = null,
    structureDefinition: StructuredDataDefinition? = null,
    transformStreamData: suspend (Flow<StreamFrame>) -> Flow<T>
): AIAgentNodeDelegate<String, Flow<T>> =
    node(name) { message ->
        llm.writeSession {
            updatePrompt {
                user(message)
            }

            val stream = requestLLMStreamingWithTools(structureDefinition)

            transformStreamData(stream)
        }
    }

However, it may be preferable to force users to use the new function for a cleaner api design, if so they would have to update their usage of PromptExecutor.executeStreaming, LLMClient.executeStreaming, AIAgentLLMWriteSession.requestLLMStreaming, and nodeLLMRequestStreamingWithTools.


Type of the changes

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Tests improvement
  • Refactoring

Checklist

  • The pull request has a description of the proposed change
  • I read the Contributing Guidelines before opening the pull request
  • The pull request uses develop as the base branch
  • Tests for the changes have been added
  • All new and existing tests passed
Additional steps for pull requests adding a new feature
  • An issue describing the proposed change exists
  • The pull request includes a link to the issue
  • The change was discussed and approved in the issue
  • Docs have been added / updated

@dosier dosier changed the title Refactor streaming api to accept tool calls Refactor streaming api to support tool calls Sep 5, 2025
@dosier
Copy link
Author

dosier commented Sep 5, 2025

One issue is that not all llm api clients/models support tool use in streaming, we could introduce some complex generic contracts to only make it use-able to only those models that support it, or let it up to the user to figure out (current design).

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.

1 participant