Skip to content

Commit dbb2bb6

Browse files
committed
Improve resiliency with better retry defaults
Code execution in particular is sensitive to timeouts and retries, and requires smarter defaults. Users can still override the default behavior by providing custom channel options or channel options.HttpHandler.
1 parent 619cd8b commit dbb2bb6

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

src/xAI/GrokClient.cs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
using System.Collections.Concurrent;
2+
using System.Diagnostics;
23
using System.Net.Http.Headers;
34
using Grpc.Net.Client;
5+
using Microsoft.Extensions.Http;
6+
using Polly;
7+
using Polly.Extensions.Http;
48
using xAI.Protocol;
59

610
namespace xAI;
@@ -47,12 +51,59 @@ public sealed class GrokClient(string apiKey, GrokClientOptions options) : IDisp
4751

4852
internal GrpcChannel Channel => channels.GetOrAdd((Endpoint, ApiKey), key =>
4953
{
54+
var inner = Options.ChannelOptions?.HttpHandler;
55+
if (inner == null)
56+
{
57+
// If no custom HttpHandler is provided, we create one with Polly retry
58+
// policies to handle transient errors, including gRPC-specific ones.
59+
var retryPolicy = HttpPolicyExtensions
60+
.HandleTransientHttpError()
61+
.Or<Grpc.Core.RpcException>(ex =>
62+
ex.StatusCode is Grpc.Core.StatusCode.Unavailable or
63+
Grpc.Core.StatusCode.DeadlineExceeded or
64+
Grpc.Core.StatusCode.Internal &&
65+
ex.Status.Detail?.Contains("504") == true ||
66+
ex.Status.Detail?.Contains("INTERNAL_ERROR") == true)
67+
.WaitAndRetryAsync(
68+
retryCount: 3,
69+
sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))
70+
#if DEBUG
71+
, onRetry: (outcome, delay, retryCount, ctx) =>
72+
{
73+
Debug.WriteLine($"[xAI Streaming Retry #{retryCount}] {outcome.Exception?.Message} — waiting {delay.TotalSeconds}s");
74+
}
75+
#endif
76+
);
77+
78+
inner = new PolicyHttpMessageHandler(retryPolicy)
79+
{
80+
InnerHandler = new SocketsHttpHandler
81+
{
82+
PooledConnectionLifetime = TimeSpan.FromMinutes(20),
83+
KeepAlivePingDelay = TimeSpan.FromSeconds(30),
84+
KeepAlivePingTimeout = TimeSpan.FromSeconds(10),
85+
KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always, // crucial for long streams
86+
EnableMultipleHttp2Connections = true,
87+
ConnectTimeout = TimeSpan.FromSeconds(60),
88+
MaxConnectionsPerServer = 10
89+
}
90+
};
91+
}
92+
5093
var handler = new AuthenticationHeaderHandler(ApiKey)
5194
{
52-
InnerHandler = Options.ChannelOptions?.HttpHandler ?? new HttpClientHandler()
95+
InnerHandler = inner
96+
};
97+
98+
// Provide some sensible defaults for gRPC channel options, while allowing users to
99+
// override them via GrokClientOptions.ChannelOptions if needed.
100+
var options = Options.ChannelOptions ?? new GrpcChannelOptions
101+
{
102+
DisposeHttpClient = true,
103+
MaxReceiveMessageSize = 128 * 1024 * 1024, // large enough for tool output
104+
MaxSendMessageSize = 16 * 1024 * 1024,
53105
};
54106

55-
var options = Options.ChannelOptions ?? new GrpcChannelOptions();
56107
options.HttpHandler = handler;
57108

58109
return GrpcChannel.ForAddress(Endpoint, options);

src/xAI/xAI.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
</PropertyGroup>
1515

1616
<ItemGroup>
17+
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="10.0.3" />
1718
<PackageReference Include="NuGetizer" Version="1.4.7" PrivateAssets="all" />
1819
<PackageReference Include="Google.Protobuf" Version="3.34.0" />
1920
<PackageReference Include="Grpc.Net.Client" Version="2.76.0" />

0 commit comments

Comments
 (0)