From 1afcb039cba2a6c09e5228c71813b71c0d510042 Mon Sep 17 00:00:00 2001 From: wangzhouquan Date: Fri, 21 Nov 2025 19:27:57 +0800 Subject: [PATCH 1/3] fix(ai): fix race condition in concurrent prompt rendering When multiple goroutines concurrently called Execute() or Render() on the same Prompt instance, they would mutate shared Message objects, causing race conditions and incorrect rendering results. The bug was in renderMessages() function which directly modified the Content field of Message objects returned from MessagesFn. This caused concurrent renders to interfere with each other. Fixed by creating new Message copies during rendering instead of mutating the originals. This ensures each concurrent render operation gets its own independent Message objects. --- go/ai/prompt.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/go/ai/prompt.go b/go/ai/prompt.go index 4f0db5b522..4d474935be 100644 --- a/go/ai/prompt.go +++ b/go/ai/prompt.go @@ -427,6 +427,8 @@ func renderMessages(ctx context.Context, opts promptOptions, messages []*Message return nil, err } + // Create new message copies to avoid mutating shared messages during concurrent execution + renderedMsgs := make([]*Message, 0, len(msgs)) for _, msg := range msgs { msgParts := []*Part{} for _, part := range msg.Content { @@ -436,12 +438,20 @@ func renderMessages(ctx context.Context, opts promptOptions, messages []*Message return nil, err } msgParts = append(msgParts, parts...) + } else { + // Preserve non-text parts as-is + msgParts = append(msgParts, part) } } - msg.Content = msgParts + // Create a new message with rendered content instead of mutating the original + renderedMsg := &Message{ + Role: msg.Role, + Content: msgParts, + } + renderedMsgs = append(renderedMsgs, renderedMsg) } - return append(messages, msgs...), nil + return append(messages, renderedMsgs...), nil } // renderPrompt renders a prompt template using dotprompt functionalities From 5447853fa2605b50b9c6881a23cfae9f975508f1 Mon Sep 17 00:00:00 2001 From: wangzq <1437987627@qq.com> Date: Tue, 25 Nov 2025 09:51:05 +0800 Subject: [PATCH 2/3] Update go/ai/prompt.go Co-authored-by: Alex Pascal --- go/ai/prompt.go | 1 + 1 file changed, 1 insertion(+) diff --git a/go/ai/prompt.go b/go/ai/prompt.go index 4d474935be..ec3feaf7a9 100644 --- a/go/ai/prompt.go +++ b/go/ai/prompt.go @@ -447,6 +447,7 @@ func renderMessages(ctx context.Context, opts promptOptions, messages []*Message renderedMsg := &Message{ Role: msg.Role, Content: msgParts, + Metadata: msg.Metadata, } renderedMsgs = append(renderedMsgs, renderedMsg) } From ba862bf3cba6149f5cea8431a036f9a7d53afa44 Mon Sep 17 00:00:00 2001 From: wangzhouquan Date: Wed, 26 Nov 2025 09:50:52 +0800 Subject: [PATCH 3/3] format code --- go/ai/prompt.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go/ai/prompt.go b/go/ai/prompt.go index ec3feaf7a9..d47dcc4ab5 100644 --- a/go/ai/prompt.go +++ b/go/ai/prompt.go @@ -445,8 +445,8 @@ func renderMessages(ctx context.Context, opts promptOptions, messages []*Message } // Create a new message with rendered content instead of mutating the original renderedMsg := &Message{ - Role: msg.Role, - Content: msgParts, + Role: msg.Role, + Content: msgParts, Metadata: msg.Metadata, } renderedMsgs = append(renderedMsgs, renderedMsg)