SDK C#
Instale o SDK oficial em C# e use chat, streaming, busca na web, agentes e busca semântica no HiNow a partir do .NET.
Atualizado em 09 de ago. de 2026
O SDK oficial em C# para a API do HiNow. Compila para .NET 8 e para .NET Standard 2.1, então roda também em projetos mais antigos e no Unity.
A API fala o protocolo da OpenAI, e o SDK segue o mesmo formato. Se você já integrou com a OpenAI, o desenho das chamadas é o que você conhece.
dotnet add package hinow-ai<PackageReference Include="hinow-ai" Version="2.0.0" />Install-Package hinow-aiGuarde a chave em HINOW_API_KEY e o SDK a encontra sozinho. Passar a chave no construtor também funciona, mas evite deixar o valor no código.
export HINOW_API_KEY="hi_sua_chave_aqui"using Hinow;
// With no arguments, the client reads the key from HINOW_API_KEY.
using var client = new HinowClient();
var answer = await client.Chat.Completions.CreateAsync(new ChatCompletionRequest
{
Model = "hinow/himax",
Messages =
{
new Message("system", "You answer in English."),
new Message("user", "What is an API? Answer in one paragraph."),
},
});
// Message.Text gives you the text. Message.Content holds the raw value.
Console.WriteLine(answer.Choices[0].Message.Text);
var uso = answer.Usage!;
Console.WriteLine($"\ninput: {uso.PromptTokens} · output: {uso.CompletionTokens}");O prefixo hinow/ faz parte do nome do modelo
Mandar himax em vez de hinow/himax devolve 404 model_not_found, e a mensagem não deixa claro que faltou o prefixo. Vale para todos: hinow/himax, hinow/hinova, hinow/higenesis.
Leia a resposta por Message.Text
Message.Content guarda o valor bruto, que numa resposta desserializada é um JsonElement. Text entrega a string já pronta e junta as partes quando a resposta vem em pedaços.
Passe o seu próprio HttpClient e o SDK não o descarta — quem controla o ciclo de vida continua sendo a IHttpClientFactory.
builder.Services.AddHttpClient<HinowClient>((http, _) =>
new HinowClient(apiKey: null, baseUrl: null, timeout: null, httpClient: http));CreateStreamAsync devolve um IAsyncEnumerable: você percorre com await foreach e cada volta traz um pedaço novo. Numa tela de conversa isso muda a percepção de velocidade mais do que trocar de modelo.
using Hinow;
using var client = new HinowClient();
var stream = client.Chat.Completions.CreateStreamAsync(new ChatCompletionRequest
{
Model = "hinow/hinova",
Messages = { new Message("user", "Write three lines about the sea at dawn.") },
});
await foreach (var chunk in stream)
{
// Note the Delta: each chunk carries the new fragment, not the whole answer.
Console.Write(chunk.Choices.Count > 0 ? chunk.Choices[0].Delta.Content : "");
}
Console.WriteLine();É Delta, não Message
Na resposta completa o texto vem em Message.Text. No streaming, cada pedaço traz apenas o trecho novo, em Delta.Content — juntar tudo é com você.
Responde na hora, sem job para acompanhar, a US$ 0,005 por chamada. Os nove tipos ficam em SearchType, então o compilador não deixa você errar o nome.
using Hinow;
using var client = new HinowClient();
// Answers on the spot: there is no job to follow.
var search = await client.Tools.SearchAsync(
"best beaches in northeast Brazil",
SearchType.Search,
country: "br",
lang: "pt-br");
foreach (var item in search.Results)
{
Console.WriteLine($"{item.Position}. {item.Title}");
Console.WriteLine($" {item.Url}");
}
Console.WriteLine($"\n{search.Results.Count} results · US$ {search.Cost}");| Tipo | O que vem preenchido em cada resultado |
|---|---|
SearchType.Search, Scholar, Patents | Position, Title, Url, Snippet |
SearchType.News | os acima mais Source, Date, ImageUrl |
SearchType.Images | Link, ImageUrl, ThumbnailUrl, Width, Height |
SearchType.Videos | Channel, Duration, Date, ThumbnailUrl |
SearchType.Places | Address, Category, Phone, Website, Rating, coordenadas |
SearchType.Shopping | Price, Delivery, Rating, Source |
SearchType.Autocomplete | Suggestions; Results fica vazio |
Varre um ou mais sites atrás de e-mails, telefones e perfis sociais, e cada achado vem com a página exata onde apareceu. Como a varredura leva tempo, esta roda como job.
using Hinow;
using var client = new HinowClient();
// The crawl takes a while, so it runs as a job. This method waits for you.
var job = await client.Tools.WebsiteContactsAndWaitAsync(
new[] { "https://www.teclia.com" },
maxDepth: 2,
maxLinksPerPage: 10);
Console.WriteLine($"status: {job.Status} · US$ {job.Cost}"
+ (job.Cached == true ? " (from cache)" : ""));
Console.WriteLine();
// Each item carries the type, the value and the exact page it was found on.
foreach (var item in job.Result!.Items)
{
if (item.Type == "social")
{
Console.WriteLine($"{item.Platform,-10}{item.Url}");
}
else
{
Console.WriteLine($"{item.Type,-10}{item.Value}");
Console.WriteLine($"{"",-10}em {item.SourceUrl}");
}
}Repetir uma execução idêntica não cobra de novo
O job volta com Cached = true quando o resultado veio do cache. Se quiser controlar o ciclo você mesmo, use WebsiteContactsAsync e acompanhe com Tools.Jobs.RetrieveAsync(job.JobId) — repare que o campo é job_id, não id.
Valide o que vier antes de usar
A varredura devolve o que encontrou no HTML da página, sem julgar. Endereços truncados ou repetidos aparecem — trate a lista como matéria-prima e valide antes de gravar no seu banco.
Assistentes, threads e runs no mesmo formato da API da OpenAI. Você declara as funções, o modelo decide quando chamá-las: o run para, você executa e devolve o resultado.
using System.Text.Json;
using Hinow;
using var client = new HinowClient();
// Your real function: a dictionary here, just for the example.
var orders = new Dictionary<string, object>
{
["A-1001"] = new { status = "delivered", delivered = "2026-08-02" },
["A-1002"] = new { status = "in transit", estimated = "2026-08-12" },
};
// 1. The assistant: model, instructions and the functions it may call.
var assistant = await client.Beta.Assistants.CreateAsync(new AssistantRequest
{
Model = "hinow/himax",
Name = "Support",
Instructions = "You answer questions about orders. Check the tool before stating any status.",
Tools = new List<Tool>
{
Tool.CreateFunction(
"get_order",
"Look up an order by its code.",
new Dictionary<string, object>
{
["type"] = "object",
["properties"] = new Dictionary<string, object>
{
["code"] = new Dictionary<string, object>
{
["type"] = "string",
["description"] = "Order code, e.g. A-1001",
},
},
["required"] = new[] { "code" },
}),
},
});
// 2. The conversation and the question.
var thread = await client.Beta.Threads.CreateAsync();
await client.Beta.Threads.Messages.CreateAsync(thread.Id, "Has order A-1002 arrived?");
// 3. Run it and wait.
var run = await client.Beta.Threads.Runs.CreateAndPollAsync(thread.Id, assistant.Id);
// 4. While the model asks for functions, run them and hand the result back.
while (run.Status == "requires_action")
{
var outputs = new List<ToolOutput>();
foreach (var call in run.RequiredAction!.SubmitToolOutputs!.ToolCalls)
{
var callArgs = JsonSerializer.Deserialize<Dictionary<string, string>>(call.Function.Arguments)!;
var code = callArgs["code"];
Console.WriteLine($"-> the model called {call.Function.Name}({code})");
var order = orders.TryGetValue(code, out var achado)
? achado
: new { error = "order not found" };
outputs.Add(new ToolOutput(call.Id, JsonSerializer.Serialize(order)));
}
await client.Beta.Threads.Runs.SubmitToolOutputsAsync(thread.Id, run.Id, outputs);
run = await client.Beta.Threads.Runs.PollAsync(thread.Id, run.Id);
}
// 5. The final answer is the last message on the thread.
var messages = await client.Beta.Threads.Messages.ListAsync(thread.Id, limit: 1, order: "desc");
Console.WriteLine($"\n{messages.Data[0].Text}");
await client.Beta.Assistants.DeleteAsync(assistant.Id);requires_action não é erro
É o run devolvendo o controle para você executar uma função. Por isso o PollAsync retorna nesse estado em vez de continuar girando: leia o RequiredAction, chame SubmitToolOutputsAsync e volte a acompanhar.
Suba arquivos, junte numa base e busque por significado. A indexação é assíncrona — buscar antes de terminar devolve zero resultado, sem erro, e é para isso que existe o PollAsync.
using Hinow;
using var client = new HinowClient();
// 1. Upload the document.
var file = await client.Files.CreateAsync("returns-policy.txt");
Console.WriteLine($"file: {file.Id} ({file.Bytes} bytes)");
// 2. Create the store and attach the file to it.
var base_ = await client.VectorStores.CreateAsync(name: "Support base");
await client.VectorStores.Files.CreateAsync(base_.Id, file.Id);
Console.WriteLine($"store: {base_.Id}");
// 3. Indexing is asynchronous. Searching before it finishes returns nothing
// at all, with no error, so PollAsync waits for it.
var indexado = await client.VectorStores.Files.PollAsync(base_.Id, file.Id);
Console.WriteLine($"indexing: {indexado.Status}");
// 4. Search by meaning, not by exact word.
var hits = await client.Rag.SearchAsync(
"How many days do I have to return an item?",
ragId: base_.Id,
topK: 3);
Console.WriteLine();
foreach (var achado in hits.Results)
{
Console.WriteLine($"{achado.Score:F2} {achado.Source}");
var excerpt = achado.Text.Replace("\n", " ");
Console.WriteLine($" {excerpt[..Math.Min(110, excerpt.Length)]}…");
}O filtro por base chama-se ragId
A API ignora vector_store_id nesse endpoint: a busca simplesmente varre todos os documentos da conta em vez da base que você queria. É o tipo de detalhe que faz parecer que a busca está devolvendo lixo.
Cada falha vira uma classe própria, então dá para tratar por tipo em vez de comparar string de mensagem. Todas herdam de HinowException, e as que vieram da API carregam StatusCode, ErrorType e o corpo da resposta.
using Hinow;
using var client = new HinowClient();
try
{
await client.Chat.Completions.CreateAsync(new ChatCompletionRequest
{
Model = "himax", // the hinow/ prefix is missing
Messages = { new Message("user", "Olá") },
});
}
catch (HinowAuthenticationException)
{
Console.WriteLine("Invalid or expired key. Check HINOW_API_KEY.");
}
catch (HinowNotFoundException e)
{
Console.WriteLine($"Not found: {e.Message}");
}
catch (HinowInvalidRequestException e)
{
Console.WriteLine($"Invalid request: {e.Message}");
}
catch (HinowRateLimitException)
{
Console.WriteLine("Rate limited. The SDK already retried; wait a moment.");
}
catch (HinowConnectionException e)
{
// Nothing reached the API, so nothing was charged.
Console.WriteLine($"Sem answer da API: {e.Message}");
}
catch (HinowException e)
{
// Safety net: any other error the API reported.
Console.WriteLine($"Error {e.StatusCode}: {e.Message}");
}| Classe | Quando acontece |
|---|---|
HinowAuthenticationException | 401 — chave ausente, inválida ou revogada |
HinowPermissionException | 403 — a chave não tem acesso a esse recurso |
HinowNotFoundException | 404 — modelo, arquivo ou assistente inexistente |
HinowInvalidRequestException | 400 ou 422 — falta um campo ou um valor está fora da faixa |
HinowRateLimitException | 429 — limite atingido ou saldo esgotado |
HinowServerException | 5xx — a falha é do lado da API |
HinowConnectionException | a requisição não chegou; nada foi cobrado |
using Hinow;
using var client = new HinowClient();
// Account credit, in US dollars.
var balance = await client.GetBalanceAsync();
Console.WriteLine($"balance: US$ {balance.Amount:F2}\n");
// One specific model. The id is namespaced: hinow/himax, not himax.
var model = await client.Models.RetrieveAsync("hinow/himax");
Console.WriteLine($"{model.Name} ({model.Id})");
Console.WriteLine($"categories: {string.Join(", ", model.Category)}");| Membro | Para quê |
|---|---|
Chat.Completions | Conversa, streaming, chamada de funções, modo JSON |
Embeddings | Vetores para busca semântica |
Images · Audio · Video | Geração |
Models | Catálogo e recursos de cada modelo |
Tools | Busca na web e contatos de site |
Files | Upload de documentos |
VectorStores | Bases de conhecimento pesquisáveis |
Rag | Busca semântica nos seus documentos |
Beta.Assistants · Beta.Threads | Agentes executados no servidor |
GetBalanceAsync() | Saldo da conta |
using var client = new HinowClient(
apiKey: Environment.GetEnvironmentVariable("HINOW_API_KEY"), // ou use a variável
baseUrl: "https://api.hinow.ai", // ou HINOW_BASE_URL
timeout: TimeSpan.FromSeconds(120),
httpClient: null,
maxRetries: 2); // repete 429 e 5xxAté a 1.0.1, o SDK empacotava Temperature, MaxTokens, TopP e RepetitionPenalty dentro de um objeto parameters antes de enviar, com os números convertidos em texto. A API aceita esse formato e ignora, então essas opções nunca surtiam efeito: pedir MaxTokens = 10 devolvia a resposta inteira. A partir da 2.0 tudo vai no nível raiz, como a API espera.
Outras três mudanças que afetam código existente:
Message.GetContentAsString()devolvianullem toda resposta desserializada, porque o valor chega comoJsonElement. Agora funciona, eMessage.Texté o nome curto.- Os erros passaram a ser tipados, mantendo
HinowExceptioncomo classe base para que oscatchexistentes continuem valendo. - Imagem, vídeo e áudio devolvem no formato da OpenAI:
resposta.Data[0].Urlno lugar deresposta.Data.Urls[0].
Escolhendo entre HiMax, HiNova e HiGenesis
O que cada modelo faz bem, quanto custa e como escrever o prompt para cada um.

