Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .netconfig
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,9 @@
url = https://github.com/andrewlock/NetEscapades.Configuration/blob/master/src/NetEscapades.Configuration.Yaml/YamlConfigurationStreamParser.cs
weak
sha = a1ec2c6746d96b4f6f140509aa68dcff09271146
etag = 9e5c6908edc34eb661d647671f79153d8f3a54ebdc848c8765c78d2715f2f657
etag = 9e5c6908edc34eb661d647671f79153d8f3a54ebdc848c8765c78d2715f2f657
[file "src/Tests/Extensions/CallHelpers.cs"]
url = https://github.com/grpc/grpc-dotnet/blob/master/examples/Tester/Tests/Client/Helpers/CallHelpers.cs
sha = a04684ab2306e5a17bad26d3da69636b326cce14
etag = 7faacded709d2bede93356fd58b93af84884949f3bab098b8b8d121a03696449
weak
1 change: 1 addition & 0 deletions src/Extensions.Grok/Extensions.Grok.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<ItemGroup>
<Compile Include="..\Extensions\Extensions\ChatOptionsExtensions.cs" Link="Extensions\ChatOptionsExtensions.cs" />
<None Include="..\..\osmfeula.txt" Link="osmfeula.txt" PackagePath="OSMFEULA.txt" />
<InternalsVisibleTo Include="Tests"/>
</ItemGroup>

</Project>
20 changes: 17 additions & 3 deletions src/Extensions.Grok/GrokChatClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,19 @@ class GrokChatClient : IChatClient
readonly GrokClientOptions clientOptions;

internal GrokChatClient(GrpcChannel channel, GrokClientOptions clientOptions, string defaultModelId)
: this(new ChatClient(channel), clientOptions, defaultModelId)
{ }

/// <summary>
/// Test constructor.
/// </summary>
internal GrokChatClient(ChatClient client, string defaultModelId)
: this(client, new(), defaultModelId)
{ }

GrokChatClient(ChatClient client, GrokClientOptions clientOptions, string defaultModelId)
{
client = new ChatClient(channel);
this.client = client;
this.clientOptions = clientOptions;
this.defaultModelId = defaultModelId;
metadata = new ChatClientMetadata("xai", clientOptions.Endpoint, defaultModelId);
Expand Down Expand Up @@ -97,7 +108,7 @@ public async Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messag
{
ResponseId = response.Id,
ModelId = response.Model,
CreatedAt = response.Created.ToDateTimeOffset(),
CreatedAt = response.Created?.ToDateTimeOffset(),
FinishReason = lastOutput != null ? MapFinishReason(lastOutput.FinishReason) : null,
Usage = MapToUsage(response.Usage),
};
Expand Down Expand Up @@ -210,13 +221,16 @@ static CitationAnnotation MapCitation(string citation)

GetCompletionsRequest MapToRequest(IEnumerable<ChatMessage> messages, ChatOptions? options)
{
var request = new GetCompletionsRequest
var request = options?.RawRepresentationFactory?.Invoke(this) as GetCompletionsRequest ?? new GetCompletionsRequest()
{
// By default always include citations in the final output if available
Include = { IncludeOption.InlineCitations },
Model = options?.ModelId ?? defaultModelId,
};

if (string.IsNullOrEmpty(request.Model))
request.Model = options?.ModelId ?? defaultModelId;

if ((options?.EndUserId ?? clientOptions.EndUserId) is { } user) request.User = user;
if (options?.MaxOutputTokens is { } maxTokens) request.MaxTokens = maxTokens;
if (options?.Temperature is { } temperature) request.Temperature = temperature;
Expand Down
46 changes: 46 additions & 0 deletions src/Tests/Extensions/CallHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#region Copyright notice and license

// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#endregion

using Grpc.Core;

namespace Tests.Client.Helpers
{
static class CallHelpers
{
public static AsyncUnaryCall<TResponse> CreateAsyncUnaryCall<TResponse>(TResponse response)
{
return new AsyncUnaryCall<TResponse>(
Task.FromResult(response),
Task.FromResult(new Metadata()),
() => Status.DefaultSuccess,
() => new Metadata(),
() => { });
}

public static AsyncUnaryCall<TResponse> CreateAsyncUnaryCall<TResponse>(StatusCode statusCode)
{
var status = new Status(statusCode, string.Empty);
return new AsyncUnaryCall<TResponse>(
Task.FromException<TResponse>(new RpcException(status)),
Task.FromResult(new Metadata()),
() => status,
() => new Metadata(),
() => { });
}
}
}
38 changes: 37 additions & 1 deletion src/Tests/GrokTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
using Devlooped.Extensions.AI.Grok;
using Devlooped.Grok;
using Microsoft.Extensions.AI;
using OpenAI.Realtime;
using Moq;
using Tests.Client.Helpers;
using static ConfigurationExtensions;
using OpenAIClientOptions = OpenAI.OpenAIClientOptions;

Expand Down Expand Up @@ -466,5 +467,40 @@ public async Task GrokStreamsUpdatesFromAllTools()
Assert.True(typed.Price > 100);
}

[Fact]
public async Task GrokCustomFactoryInvokedFromOptions()
{
var invoked = false;
var client = new Mock<Devlooped.Grok.Chat.ChatClient>(MockBehavior.Strict);
client.Setup(x => x.GetCompletionAsync(It.IsAny<GetCompletionsRequest>(), null, null, CancellationToken.None))
.Returns(CallHelpers.CreateAsyncUnaryCall(new GetChatCompletionResponse
{
Outputs =
{
new CompletionOutput
{
Message = new CompletionMessage
{
Content = "Hey Cazzulino!"
}
}
}
}));

var grok = new GrokChatClient(client.Object, "grok-4-1-fast");
var response = await grok.GetResponseAsync("Hi, my internet alias is kzu. Lookup my real full name online.",
new GrokChatOptions
{
RawRepresentationFactory = (client) =>
{
invoked = true;
return new GetCompletionsRequest();
}
});

Assert.True(invoked);
Assert.Equal("Hey Cazzulino!", response.Text);
}

record Response(DateOnly Today, string Release, decimal Price);
}
Loading