Skip to content

Commit 096231a

Browse files
committed
Add missing tool_call_id field for responses
Implements xai-org/xai-proto#15 while our fix at xai-org/xai-proto#19 isn't merged.
1 parent ef4f595 commit 096231a

File tree

3 files changed

+170
-25
lines changed

3 files changed

+170
-25
lines changed

src/GrokClient/chat.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,9 @@ message Message {
486486

487487
// The encrypted content.
488488
string encrypted_content = 6;
489+
490+
// The ID associating this tool response with a prior invocation (for role = ROLE_TOOL).
491+
string tool_call_id = 7;
489492
}
490493

491494
enum MessageRole {

src/Tests/ModelsTests.cs

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/Tests/SanityChecks.cs

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
using Grpc.Net.ClientFactory;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Xunit.Abstractions;
4+
using System.Text.Json;
5+
6+
namespace Devlooped.Grok;
7+
8+
public class SanityChecks(ITestOutputHelper output)
9+
{
10+
[SecretsFact("XAI_API_KEY")]
11+
public async Task ListModelsAsync()
12+
{
13+
var services = new ServiceCollection()
14+
.AddGrokClient(Environment.GetEnvironmentVariable("XAI_API_KEY")!)
15+
.BuildServiceProvider();
16+
17+
var client = services.GetRequiredService<Models.ModelsClient>();
18+
19+
var models = await client.ListLanguageModelsAsync();
20+
21+
Assert.NotNull(models);
22+
23+
foreach (var model in models)
24+
output.WriteLine(model.Name);
25+
}
26+
27+
[SecretsFact("XAI_API_KEY")]
28+
public async Task ExecuteLocalFunctionWithWebSearch()
29+
{
30+
var services = new ServiceCollection()
31+
.AddGrokClient(Environment.GetEnvironmentVariable("XAI_API_KEY")!)
32+
.BuildServiceProvider();
33+
34+
var client = services.GetRequiredService<Chat.ChatClient>();
35+
36+
// Define a local function to get the current date
37+
var getDateFunction = new Function
38+
{
39+
Name = "get_date",
40+
Description = "Get the current date in YYYY-MM-DD format",
41+
Parameters = JsonSerializer.Serialize(new
42+
{
43+
type = "object",
44+
properties = new { },
45+
required = Array.Empty<string>()
46+
})
47+
};
48+
49+
// Create the request with both a local function and web_search tool
50+
var request = new GetCompletionsRequest
51+
{
52+
Model = "grok-4-1-fast",
53+
Messages =
54+
{
55+
new Message
56+
{
57+
Role = MessageRole.RoleSystem,
58+
Content = { new Content { Text = "You use the get_date function to get the current date, and Nasdaq and Yahoo finance for stocks news." } }
59+
},
60+
new Message
61+
{
62+
Role = MessageRole.RoleUser,
63+
Content = { new Content { Text = "What's Tesla stock price today?" } }
64+
}
65+
},
66+
Tools =
67+
{
68+
new Tool { Function = getDateFunction },
69+
new Tool { WebSearch = new WebSearch() }
70+
}
71+
};
72+
73+
var response = await client.GetCompletionAsync(request);
74+
75+
Assert.NotNull(response);
76+
output.WriteLine($"Response ID: {response.Id}");
77+
output.WriteLine($"Model: {response.Model}");
78+
79+
// Check if we have outputs
80+
Assert.NotEmpty(response.Outputs);
81+
var firstOutput = response.Outputs[0];
82+
var invokedGetDate = false;
83+
84+
output.WriteLine($"Finish Reason: {firstOutput.FinishReason}");
85+
86+
// The model should call tools
87+
if (firstOutput.Message.ToolCalls.Count > 0)
88+
{
89+
output.WriteLine($"\nTool Calls ({firstOutput.Message.ToolCalls.Count}):");
90+
91+
foreach (var toolCall in firstOutput.Message.ToolCalls)
92+
{
93+
output.WriteLine($" - ID: {toolCall.Id}");
94+
output.WriteLine($" Type: {toolCall.Type}");
95+
output.WriteLine($" Status: {toolCall.Status}");
96+
97+
if (toolCall.Function != null)
98+
{
99+
output.WriteLine($" Function: {toolCall.Function.Name}");
100+
output.WriteLine($" Arguments: {toolCall.Function.Arguments}");
101+
102+
// If it's our local get_date function, we need to execute it
103+
if (toolCall.Function.Name == "get_date" && toolCall.Type == ToolCallType.ClientSideTool)
104+
{
105+
output.WriteLine($" -> Local function call detected, would execute on client side");
106+
107+
// Execute the function
108+
var currentDate = DateTime.Now.ToString("yyyy-MM-dd");
109+
output.WriteLine($" -> Result: {currentDate}");
110+
111+
var call = new Message
112+
{
113+
Role = MessageRole.RoleAssistant,
114+
};
115+
call.ToolCalls.AddRange(firstOutput.Message.ToolCalls);
116+
117+
// Continue the conversation with the function result
118+
var followUpRequest = new GetCompletionsRequest
119+
{
120+
Model = request.Model,
121+
Messages =
122+
{
123+
request.Messages[0], // Original user message
124+
call,
125+
//new Message
126+
//{
127+
// Role = MessageRole.RoleAssistant,
128+
// ToolCalls = { toolCall }
129+
//},
130+
new Message
131+
{
132+
Role = MessageRole.RoleTool,
133+
ToolCallId = toolCall.Id,
134+
Content = { new Content { Text = currentDate } }
135+
}
136+
},
137+
Tools = { request.Tools[0], request.Tools[1] }
138+
};
139+
140+
var followUpResponse = await client.GetCompletionAsync(followUpRequest);
141+
invokedGetDate = true;
142+
143+
// There should be no more tool calls after we return the client-side one.
144+
Assert.Empty(followUpResponse.Outputs.SelectMany(x => x.Message.ToolCalls));
145+
}
146+
}
147+
}
148+
}
149+
150+
if (!string.IsNullOrEmpty(firstOutput.Message.Content))
151+
{
152+
output.WriteLine($"\nContent: {firstOutput.Message.Content}");
153+
}
154+
155+
// Check for citations
156+
if (response.Citations.Count > 0)
157+
{
158+
output.WriteLine($"\nCitations ({response.Citations.Count}):");
159+
foreach (var citation in response.Citations)
160+
{
161+
output.WriteLine($" - {citation}");
162+
}
163+
}
164+
165+
Assert.True(invokedGetDate);
166+
}
167+
}

0 commit comments

Comments
 (0)