Skip to content

Commit 818d7ce

Browse files
committed
Show tool call instead of raw text output for gemini code execution
1 parent b487fe6 commit 818d7ce

5 files changed

Lines changed: 99 additions & 24 deletions

File tree

src/BE/Controllers/Chats/Chats/ChatController.cs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -485,11 +485,9 @@ private static async Task ProcessChatSpan(
485485
Step step = await RunOne(messageToSend);
486486
await WriteStep(step);
487487

488-
if (step.StepContents.Any(x => x.ContentTypeId == (byte)DBMessageContentType.ToolCall))
488+
if (TryGetUnfinishedToolCall(step, out List<StepContentToolCall> unfinishedToolCalls))
489489
{
490-
foreach (StepContentToolCall call in step.StepContents!
491-
.Where(x => x.StepContentToolCall != null)
492-
.Select(x => x.StepContentToolCall!))
490+
foreach (StepContentToolCall call in unfinishedToolCalls)
493491
{
494492
if (!toolNameMap.TryGetValue(call.Name!, out var mapped))
495493
{
@@ -601,6 +599,28 @@ Dictionary<string, string> MergeHeaders(params string?[] headers)
601599
}
602600
}
603601

602+
static bool TryGetUnfinishedToolCall(Step step, out List<StepContentToolCall> toolCall)
603+
{
604+
toolCall = [];
605+
foreach (StepContent content in step.StepContents!)
606+
{
607+
if (content.ContentTypeId == (byte)DBMessageContentType.ToolCall && content.StepContentToolCall != null)
608+
{
609+
string toolCallId = content.StepContentToolCall.ToolCallId!;
610+
bool hasResponse = step.StepContents.Any(x =>
611+
x.ContentTypeId == (byte)DBMessageContentType.ToolCallResponse
612+
&& x.StepContentToolCallResponse != null
613+
&& x.StepContentToolCallResponse.ToolCallId == toolCallId);
614+
if (!hasResponse)
615+
{
616+
toolCall.Add(content.StepContentToolCall);
617+
}
618+
}
619+
}
620+
621+
return toolCall.Count > 0;
622+
}
623+
604624
writer.TryWrite(new EndTurn(chatSpan.SpanId, turn));
605625
writer.Complete();
606626

@@ -651,6 +671,10 @@ async Task<Step> RunOne(List<OpenAIChatMessage> messageToSend)
651671
}
652672
writer.TryWrite(new CallingToolLine(chatSpan.SpanId, toolCall.Id!, toolCall.Name!, toolCall.Arguments));
653673
}
674+
else if (item is ToolCallResponseSegment toolCallResponse)
675+
{
676+
writer.TryWrite(new ToolCompletedLine(chatSpan.SpanId, toolCallResponse.IsSuccess, toolCallResponse.ToolCallId!, toolCallResponse.Response!));
677+
}
654678
else if (item is ImageChatSegment imgSeg)
655679
{
656680
imageFileCache[imgSeg] = new TaskCompletionSource<DB.File>();

src/BE/DB/Extensions/StepContent.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,21 @@ public static StepContent FromTool(string toolCallId, string name, string parame
5656
return new StepContent { StepContentToolCall = new() { Name = name, ToolCallId = toolCallId, Parameters = parameters }, ContentTypeId = (byte)DBMessageContentType.ToolCall };
5757
}
5858

59+
public static StepContent FromToolResponse(string toolCallId, string? response, int durationMs, bool isSuccess)
60+
{
61+
return new StepContent
62+
{
63+
StepContentToolCallResponse = new()
64+
{
65+
ToolCallId = toolCallId,
66+
Response = response!,
67+
DurationMs = durationMs,
68+
IsSuccess = isSuccess
69+
},
70+
ContentTypeId = (byte)DBMessageContentType.ToolCallResponse
71+
};
72+
}
73+
5974
public static StepContent FromError(string error)
6075
{
6176
return new StepContent { StepContentText = new() { Content = error }, ContentTypeId = (byte)DBMessageContentType.Error };
@@ -84,6 +99,7 @@ public static IEnumerable<StepContent> FromFullResponse(InternalChatSegment last
8499
ThinkChatSegment think => FromThink(think.Think),
85100
ImageChatSegment image => FromFile(imageMcCache[image].Task.GetAwaiter().GetResult()),
86101
ToolCallSegment tool => FromTool(tool.Id ?? tool.Index.ToString(), tool.Name!, tool.Arguments),
102+
ToolCallResponseSegment toolResp => FromToolResponse(toolResp.ToolCallId, toolResp.Response, toolResp.DurationMs, toolResp.IsSuccess),
87103
_ => throw new NotSupportedException(),
88104
};
89105
}))

src/BE/Services/Models/ChatServices/GoogleAI/GoogleAI2ChatService.cs

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Chats.BE.Services.Models.Dtos;
44
using Mscc.GenerativeAI;
55
using OpenAI.Chat;
6+
using System.Diagnostics;
67
using System.Runtime.CompilerServices;
78
using System.Text.Json.Nodes;
89
using ChatMessage = OpenAI.Chat.ChatMessage;
@@ -87,6 +88,8 @@ var x when x.IsLowOrMinimal() => 1024,
8788
SafetySettings = _safetySettings,
8889
Tools = tool == null ? null : [tool],
8990
};
91+
Stopwatch codeExecutionSw = new();
92+
string? codeExecutionId = null;
9093
await foreach (GenerateContentResponse response in _generativeModel.GenerateContentStream(gcr, new RequestOptions(null, NetworkTimeout), cancellationToken))
9194
{
9295
if (response.Candidates != null && response.Candidates.Count > 0)
@@ -96,29 +99,27 @@ var x when x.IsLowOrMinimal() => 1024,
9699
{
97100
if (part.ExecutableCode != null)
98101
{
99-
items.Add(ChatSegmentItem.FromText($"""
100-
```{part.ExecutableCode.Language}
101-
{part.ExecutableCode.Code}
102-
```
103-
104-
"""));
105-
//items.Add(ChatSegmentItem.FromToolCall(0, new FunctionCall()
106-
//{
107-
// Name = "code_execution",
108-
// Args = new JsonObject
109-
// {
110-
// ["code"] = part.ExecutableCode.Code,
111-
// },
112-
//}));
102+
codeExecutionId = "ce-" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
103+
items.Add(ChatSegmentItem.FromToolCall(0, new FunctionCall()
104+
{
105+
Id = codeExecutionId,
106+
Name = part.ExecutableCode.Language.ToString(),
107+
Args = new
108+
{
109+
code = part.ExecutableCode.Code,
110+
},
111+
}));
112+
codeExecutionSw = Stopwatch.StartNew();
113113
}
114114
else if (part.CodeExecutionResult != null)
115115
{
116-
items.Add(ChatSegmentItem.FromText($"""
117-
```
118-
{part.CodeExecutionResult.Output}
119-
```
120-
121-
"""));
116+
if (codeExecutionId == null)
117+
{
118+
throw new InvalidOperationException("CodeExecutionResult received without prior ExecutableCode.");
119+
}
120+
items.Add(ChatSegmentItem.FromToolCallResponse(codeExecutionId, part.CodeExecutionResult.Output,
121+
(int)codeExecutionSw.ElapsedMilliseconds,
122+
isSuccess: part.CodeExecutionResult.Outcome == Outcome.OutcomeOk));
122123
}
123124
else if (part.Text != null)
124125
{

src/BE/Services/Models/ChatServices/ToolCallSegment.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Chats.BE.Controllers.OpenAICompatible.Dtos;
2+
using Chats.BE.DB;
23
using Chats.BE.Services.Models.Dtos;
34
using System.Runtime.CompilerServices;
45
using System.Text;
@@ -29,6 +30,28 @@ public sealed record ToolCallSegment : ChatSegmentItem
2930
public required string Arguments { get; init; }
3031
}
3132

33+
public sealed record ToolCallResponseSegment : ChatSegmentItem
34+
{
35+
public required string ToolCallId { get; init; }
36+
37+
public string? Response { get; init; }
38+
39+
public required int DurationMs { get; init; }
40+
41+
public required bool IsSuccess { get; init; }
42+
43+
public StepContentToolCallResponse ToDB()
44+
{
45+
return new StepContentToolCallResponse
46+
{
47+
ToolCallId = ToolCallId!,
48+
Response = Response!,
49+
DurationMs = DurationMs,
50+
IsSuccess = IsSuccess
51+
};
52+
}
53+
}
54+
3255
/// <summary>
3356
/// 组合完毕后的完整函数调用。
3457
/// </summary>

src/BE/Services/Models/Dtos/ChatSegmentItem.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,17 @@ static ToolCallSegment FromToolCall(StreamingChatToolCallUpdate toolCall)
175175
};
176176
}
177177

178+
public static ToolCallResponseSegment FromToolCallResponse(string toolCallId, string? response, int durationMs, bool isSuccess)
179+
{
180+
return new ToolCallResponseSegment()
181+
{
182+
ToolCallId = toolCallId,
183+
Response = response,
184+
DurationMs = durationMs,
185+
IsSuccess = isSuccess,
186+
};
187+
}
188+
178189
static IEnumerable<ChatSegmentItem> FromToolCalls(IReadOnlyList<ChatToolCall> toolCall)
179190
{
180191
return toolCall.Select((x, i) => new ToolCallSegment()

0 commit comments

Comments
 (0)