Skip to content

Commit 116894b

Browse files
authored
Merge pull request #32 from ZatomicAI/moonshot-ai
Moonshot AI
2 parents 13cc6aa + 82465dd commit 116894b

17 files changed

+503
-3
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Zatomic.AI.Providers
22

3-
C# .NET library that provides chat functionality for the following AI providers: [AI21 Labs](https://docs.ai21.com/reference/jamba-1-6-api-ref), [Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html), [Anthropic](https://docs.anthropic.com/en/api/messages), [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference), [Azure Serverless](https://learn.microsoft.com/en-us/rest/api/aifoundry/model-inference/get-chat-completions/get-chat-completions), [Cohere](https://docs.cohere.com/v2/reference/chat), [Deep Infra](https://deepinfra.com/docs/openai_api), [Fireworks AI](https://docs.fireworks.ai/api-reference/post-chatcompletions), [Google Gemini](https://ai.google.dev/api/generate-content), [Groq](https://console.groq.com/docs/api-reference#chat-create), [Hugging Face](https://huggingface.co/docs/inference-providers/en/tasks/chat-completion), [Hyperbolic](https://docs.hyperbolic.xyz/reference/create_chat_completion_v1_chat_completions_post), [Lambda](https://docs.lambda.ai/public-cloud/lambda-inference-api/), [Meta](https://llama.developer.meta.com/docs/api/chat/), [Mistral](https://docs.mistral.ai/api/), [OpenAI](https://platform.openai.com/docs/api-reference/chat), [Perplexity](https://docs.perplexity.ai/api-reference/chat-completions-post), [Together AI](https://docs.together.ai/reference/chat-completions-1), and [xAI](https://docs.x.ai/docs/api-reference#messages-anthropic-compatible).
3+
C# .NET library that provides chat functionality for the following AI providers: [AI21 Labs](https://docs.ai21.com/reference/jamba-1-6-api-ref), [Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html), [Anthropic](https://docs.anthropic.com/en/api/messages), [Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference), [Azure Serverless](https://learn.microsoft.com/en-us/rest/api/aifoundry/model-inference/get-chat-completions/get-chat-completions), [Cohere](https://docs.cohere.com/v2/reference/chat), [Deep Infra](https://deepinfra.com/docs/openai_api), [Fireworks AI](https://docs.fireworks.ai/api-reference/post-chatcompletions), [Google Gemini](https://ai.google.dev/api/generate-content), [Groq](https://console.groq.com/docs/api-reference#chat-create), [Hugging Face](https://huggingface.co/docs/inference-providers/en/tasks/chat-completion), [Hyperbolic](https://docs.hyperbolic.xyz/reference/create_chat_completion_v1_chat_completions_post), [Lambda](https://docs.lambda.ai/public-cloud/lambda-inference-api/), [Meta](https://llama.developer.meta.com/docs/api/chat/), [Mistral](https://docs.mistral.ai/api/), [Moonshot AI](https://platform.moonshot.ai/docs/api/chat), [OpenAI](https://platform.openai.com/docs/api-reference/chat), [Perplexity](https://docs.perplexity.ai/api-reference/chat-completions-post), [Together AI](https://docs.together.ai/reference/chat-completions-1), and [xAI](https://docs.x.ai/docs/api-reference#messages-anthropic-compatible).
44

55
The library calls the chat completions REST APIs and inference endpoints for each of the above AI providers. Everything is strongly-typed with the library handling all JSON serialization/deserialization for all requests and responses. Both non-stream and streaming functionality is supported using `async` methods for improved performance, and each client utilizes exponential retries to handle `429 Too Many Requests` responses.
66

@@ -111,6 +111,10 @@ The format of the `AppSettigns.Development.json` file is as follows:
111111
"ApiKey": "",
112112
"Model": ""
113113
},
114+
"MoonshotAI": {
115+
"ApiKey": "",
116+
"Model": ""
117+
},
114118
"OpenAI": {
115119
"ApiKey": "",
116120
"Model": ""
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.Threading.Tasks;
2+
using NUnit.Framework;
3+
using Zatomic.AI.Providers.MoonshotAI;
4+
5+
namespace Zatomic.AI.Providers.Samples
6+
{
7+
[TestFixture, Explicit]
8+
public class MoonshotAISamples : BaseSample
9+
{
10+
private readonly string _apiKey;
11+
private readonly string _model;
12+
13+
public MoonshotAISamples()
14+
{
15+
_apiKey = Configuration["MoonshotAI:ApiKey"];
16+
_model = Configuration["MoonshotAI:Model"];
17+
}
18+
19+
[Test]
20+
public async Task Chat()
21+
{
22+
var client = new MoonshotAIChatClient(_apiKey);
23+
var request = new MoonshotAIChatRequest(_model);
24+
request.AddSystemMessage(SystemPrompt);
25+
request.AddUserMessage(UserPrompt);
26+
27+
var response = await client.ChatAsync(request);
28+
WriteOutput(response.Choices[0].Message.Content);
29+
WriteOutput(response.Usage.PromptTokens, response.Usage.CompletionTokens, response.Usage.TotalTokens, response.Duration.Value);
30+
}
31+
32+
[Test]
33+
public async Task ChatStream()
34+
{
35+
var client = new MoonshotAIChatClient(_apiKey);
36+
var request = new MoonshotAIChatRequest(_model);
37+
request.AddSystemMessage(SystemPrompt);
38+
request.AddUserMessage(UserPrompt);
39+
40+
int inputTokens = 0;
41+
int outputTokens = 0;
42+
int totalTokens = 0;
43+
decimal duration = 0;
44+
45+
await foreach (var result in client.ChatStreamAsync(request))
46+
{
47+
WriteOutput(result.Chunk);
48+
49+
if (result.InputTokens.HasValue) inputTokens = result.InputTokens.Value;
50+
if (result.OutputTokens.HasValue) outputTokens = result.OutputTokens.Value;
51+
if (result.TotalTokens.HasValue) totalTokens = result.TotalTokens.Value;
52+
if (result.Duration.HasValue) duration = result.Duration.Value;
53+
}
54+
55+
WriteOutput(inputTokens, outputTokens, totalTokens, duration);
56+
}
57+
}
58+
}

src/Zatomic.AI.Providers/Exceptions/AIExceptionUtility.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Zatomic.AI.Providers.Lambda;
1616
using Zatomic.AI.Providers.Meta;
1717
using Zatomic.AI.Providers.Mistral;
18+
using Zatomic.AI.Providers.MoonshotAI;
1819
using Zatomic.AI.Providers.OpenAI;
1920
using Zatomic.AI.Providers.Perplexity;
2021
using Zatomic.AI.Providers.TogetherAI;
@@ -99,6 +100,11 @@ public static AIException BuildMistralAIException(Exception ex, MistralChatReque
99100
return BuildAIException(ex, "Mistral", request.Model, request, responseJson);
100101
}
101102

103+
public static AIException BuildMoonshotAIAIException(Exception ex, MoonshotAIChatRequest request, string responseJson = null)
104+
{
105+
return BuildAIException(ex, "Moonshot AI", request.Model, request, responseJson);
106+
}
107+
102108
public static AIException BuildOpenAIAIException(Exception ex, OpenAIChatRequest request, string responseJson = null)
103109
{
104110
return BuildAIException(ex, "OpenAI", request.Model, request, responseJson);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Zatomic.AI.Providers.MoonshotAI
4+
{
5+
public class MoonshotAIChatChoice
6+
{
7+
[JsonProperty("delta")]
8+
public MoonshotAIChatDelta Delta { get; set; }
9+
10+
[JsonProperty("finish_reason")]
11+
public string FinishReason { get; set; }
12+
13+
[JsonProperty("index")]
14+
public int Index { get; set; }
15+
16+
[JsonProperty("message")]
17+
public MoonshotAIChatOutputMessage Message { get; set; }
18+
19+
[JsonProperty("usage")]
20+
public MoonshotAIChatUsage Usage { get; set; }
21+
}
22+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.IO;
5+
using System.Net.Http;
6+
using System.Net.Http.Headers;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using Zatomic.AI.Providers.Exceptions;
10+
using Zatomic.AI.Providers.Extensions;
11+
12+
namespace Zatomic.AI.Providers.MoonshotAI
13+
{
14+
public class MoonshotAIChatClient : BaseClient
15+
{
16+
public string ApiKey { get; set; }
17+
public string ApiUrl { get; } = "https://api.moonshot.ai/v1/chat/completions";
18+
19+
public MoonshotAIChatClient()
20+
{
21+
}
22+
23+
public MoonshotAIChatClient(string apiKey) : this()
24+
{
25+
ApiKey = apiKey;
26+
}
27+
28+
public async Task<MoonshotAIChatResponse> ChatAsync(MoonshotAIChatRequest request)
29+
{
30+
MoonshotAIChatResponse response = null;
31+
32+
using (var httpClient = new HttpClient())
33+
{
34+
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
35+
36+
var requestJson = request.Serialize();
37+
var content = new StringContent(requestJson, Encoding.UTF8, "application/json");
38+
39+
string responseJson = null;
40+
41+
try
42+
{
43+
var stopwatch = Stopwatch.StartNew();
44+
45+
var postResponse = await DoWithRetryAsync(() => httpClient.PostAsync(ApiUrl, content));
46+
responseJson = await postResponse.Content.ReadAsStringAsync();
47+
postResponse.EnsureSuccessStatusCode();
48+
49+
stopwatch.Stop();
50+
51+
response = responseJson.Deserialize<MoonshotAIChatResponse>();
52+
response.Duration = stopwatch.ToDurationInSeconds(2);
53+
}
54+
catch (Exception ex)
55+
{
56+
var aiEx = AIExceptionUtility.BuildMoonshotAIAIException(ex, request, responseJson);
57+
throw aiEx;
58+
}
59+
}
60+
61+
return response;
62+
}
63+
64+
public async IAsyncEnumerable<AIStreamResponse> ChatStreamAsync(MoonshotAIChatRequest request)
65+
{
66+
request.Stream = true;
67+
68+
using (var httpClient = new HttpClient())
69+
{
70+
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ApiKey);
71+
72+
var requestJson = request.Serialize();
73+
var postRequest = new HttpRequestMessage(HttpMethod.Post, ApiUrl)
74+
{
75+
Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
76+
};
77+
78+
HttpResponseMessage postResponse = null;
79+
80+
try
81+
{
82+
// This is wrapped in a try-catch in case an error occurs at the start
83+
postResponse = await DoWithRetryAsync(() => httpClient.SendAsync(postRequest, HttpCompletionOption.ResponseHeadersRead));
84+
postResponse.EnsureSuccessStatusCode();
85+
}
86+
catch (Exception ex)
87+
{
88+
var aiEx = AIExceptionUtility.BuildMoonshotAIAIException(ex, request);
89+
throw aiEx;
90+
}
91+
92+
var streamComplete = false;
93+
var stopwatch = Stopwatch.StartNew();
94+
95+
using (var stream = await postResponse.Content.ReadAsStreamAsync())
96+
using (var reader = new StreamReader(stream))
97+
{
98+
while (!reader.EndOfStream && !streamComplete)
99+
{
100+
string line;
101+
102+
try
103+
{
104+
// This is wrapped in a try-catch in case an error occurs mid-stream
105+
line = await reader.ReadLineAsync();
106+
}
107+
catch (Exception ex)
108+
{
109+
var aiEx = AIExceptionUtility.BuildMoonshotAIAIException(ex, request);
110+
throw aiEx;
111+
}
112+
113+
// Event messages start with "data: ", so that's why we substring the line at 6
114+
if (!line.IsNullOrEmpty() && line.StartsWith("data: "))
115+
{
116+
var rsp = line.Substring(6).Deserialize<MoonshotAIChatResponse>();
117+
var streamResponse = new AIStreamResponse { Chunk = rsp.Choices[0].Delta.Content };
118+
119+
if (!rsp.Choices[0].FinishReason.IsNullOrEmpty())
120+
{
121+
streamComplete = true;
122+
stopwatch.Stop();
123+
streamResponse.Duration = stopwatch.ToDurationInSeconds(2);
124+
125+
if (rsp.Choices[0].Usage != null)
126+
{
127+
streamResponse.InputTokens = rsp.Choices[0].Usage.PromptTokens;
128+
streamResponse.OutputTokens = rsp.Choices[0].Usage.CompletionTokens;
129+
streamResponse.TotalTokens = rsp.Choices[0].Usage.TotalTokens;
130+
}
131+
}
132+
133+
yield return streamResponse;
134+
}
135+
}
136+
}
137+
}
138+
}
139+
}
140+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Zatomic.AI.Providers.MoonshotAI
4+
{
5+
public class MoonshotAIChatContent
6+
{
7+
[JsonProperty("image_url", NullValueHandling = NullValueHandling.Ignore)]
8+
public MoonshotAIChatImageUrl ImageUrl { get; set; }
9+
10+
[JsonProperty("text", NullValueHandling = NullValueHandling.Ignore)]
11+
public string Text { get; set; }
12+
13+
[JsonProperty("type")]
14+
public string Type { get; set; }
15+
}
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Zatomic.AI.Providers.MoonshotAI
4+
{
5+
public class MoonshotAIChatDelta
6+
{
7+
[JsonProperty("content")]
8+
public string Content { get; set; }
9+
}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Zatomic.AI.Providers.MoonshotAI
4+
{
5+
public class MoonshotAIChatImageUrl
6+
{
7+
[JsonProperty("url")]
8+
public string Url { get; set; }
9+
}
10+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Collections.Generic;
2+
using Newtonsoft.Json;
3+
4+
namespace Zatomic.AI.Providers.MoonshotAI
5+
{
6+
public class MoonshotAIChatInputMessage
7+
{
8+
[JsonProperty("content")]
9+
public List<MoonshotAIChatContent> Content { get; set; }
10+
11+
[JsonProperty("role")]
12+
public string Role { get; set; }
13+
14+
public MoonshotAIChatInputMessage()
15+
{
16+
Content = new List<MoonshotAIChatContent>();
17+
}
18+
}
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Newtonsoft.Json;
2+
3+
namespace Zatomic.AI.Providers.MoonshotAI
4+
{
5+
public class MoonshotAIChatOutputMessage
6+
{
7+
[JsonProperty("content")]
8+
public string Content { get; set; }
9+
10+
[JsonProperty("role")]
11+
public string Role { get; set; }
12+
}
13+
}

0 commit comments

Comments
 (0)