Skip to content

feat(ai): add tool calling support to Stream()#1445

Merged
hwbrzzl merged 2 commits into
masterfrom
bowen/#917-3
Apr 19, 2026
Merged

feat(ai): add tool calling support to Stream()#1445
hwbrzzl merged 2 commits into
masterfrom
bowen/#917-3

Conversation

@hwbrzzl

@hwbrzzl hwbrzzl commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Stream() now runs a full agentic tool-call loop (up to MaxToolCallIterations), mirroring the existing Prompt() tool-calling behavior
  • Tool invocations are emitted as StreamEventTypeToolCall events so callers can observe them in real time
  • The OpenAI streaming provider accumulates tool-call argument fragments across chunks and returns them via Response.ToolCalls()

Why

Before this change, Stream() stripped tools from the agent prompt and returned plain text only. Callers who needed tool-calling had to fall back to Prompt() and lose the streaming UX.

Now Stream() passes the agent's tools to the provider, accumulates tool-call deltas across streaming chunks, executes the tools, and re-prompts in a loop — exactly like Prompt() does — while forwarding text deltas to the caller in real time. Intermediate done events from inner iterations are suppressed; only the final done is forwarded.

// app/ai/agents/search_agent.go
type SearchAgent struct{}

func (r *SearchAgent) Instructions() string {
    return "You are a helpful assistant that can search the web."
}

func (r *SearchAgent) Tools() []ai.Tool {
    return []ai.Tool{facades.AI().Tool("web_search")}
}

// app/http/controllers/chat_controller.go
func (r *ChatController) Stream(ctx http.Context) http.Response {
    conversation := facades.AI().Agent(&agents.SearchAgent{}).Conversation()
    stream, err := conversation.Stream(ctx.Request().Input("message"))
    if err != nil {
        return ctx.Response().Json(500, http.Json{"error": err.Error()})
    }
    return stream.HTTPResponse(ctx)
}

@hwbrzzl
hwbrzzl requested a review from a team as a code owner April 18, 2026 01:09
Copilot AI review requested due to automatic review settings April 18, 2026 01:09
@codecov

codecov Bot commented Apr 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.38462% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.15%. Comparing base (29468d1) to head (7a51e18).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
ai/conversation.go 89.10% 8 Missing and 3 partials ⚠️
ai/openai/provider.go 91.48% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1445      +/-   ##
==========================================
+ Coverage   69.06%   69.15%   +0.08%     
==========================================
  Files         362      362              
  Lines       28191    28315     +124     
==========================================
+ Hits        19470    19581     +111     
- Misses       7861     7870       +9     
- Partials      860      864       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds end-to-end tool-calling support to Conversation.Stream(), aligning it with the existing Prompt() tool-call loop and exposing tool invocations to streaming consumers.

Changes:

  • Add tool_call stream events and include tool-call data in StreamEvent.
  • Implement a tool-call/re-prompt loop inside Conversation.Stream() and emit tool-call events.
  • Enhance the OpenAI streaming provider to pass tools through and accumulate tool-call argument fragments across chunks into Response.ToolCalls().

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
contracts/ai/stream.go Adds StreamEventTypeToolCall and StreamEvent.ToolCalls to support streaming tool-call notifications.
ai/streamable_response.go Extends default SSE JSON payload to include tool_calls for tool-call events.
ai/openai/provider.go Passes tools into streaming requests and accumulates tool-call deltas into Response.ToolCalls().
ai/conversation.go Implements a streaming tool-call loop (execute tools + re-stream until final text).
ai/conversation_test.go Updates stream behavior expectations and adds coverage for the streaming tool-call loop.
ai/console/stubs.go Updates console agent stub to satisfy the Tools() method.
AGENTS.md Adds planning rules to contributor/agent guidance.

Comment thread ai/conversation.go
Comment thread ai/conversation.go Outdated
Comment thread ai/conversation.go Outdated
Comment thread ai/conversation_test.go
Comment thread ai/openai/provider.go
Comment thread ai/streamable_response.go
Comment thread ai/conversation.go
r.mu.Unlock()
}

initialCtx, cancelInitial := context.WithCancel(r.ctx)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

initialCtx is only canceled on the early error path or when streamCtx.Done() fires. On the normal success path this child context is left alive after the first stream finishes, which leaks cancellation resources across repeated Stream() calls. It would be safer to cancel it as soon as the initial stream is fully consumed, and to add a test that exercises successful completion/cleanup.

Comment thread ai/conversation.go
@hwbrzzl
hwbrzzl merged commit 06f6946 into master Apr 19, 2026
19 checks passed
@hwbrzzl
hwbrzzl deleted the bowen/#917-3 branch April 19, 2026 12:28
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.

2 participants