Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc4bc249b6 | |||
| 8d1c839093 | |||
| c5bb663bc7 | |||
| fc61bc42ad | |||
| 9b7488183d | |||
| 78f7c745a7 |
132
.github/workflows/watch-openapi.yml
vendored
Normal file
132
.github/workflows/watch-openapi.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
name: Watch Mistral OpenAPI Spec
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Runs daily at 06:00 UTC
|
||||
- cron: "0 6 * * *"
|
||||
workflow_dispatch: # manual trigger for testing
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
check-spec:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Fetch and compare spec
|
||||
id: check
|
||||
run: |
|
||||
if ! curl --fail -sL https://raw.githubusercontent.com/mistralai/platform-docs-public/main/openapi.yaml -o new-spec.yaml; then
|
||||
echo "::error::Failed to download OpenAPI spec"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NEW_HASH=$(sha256sum new-spec.yaml | cut -d' ' -f1)
|
||||
|
||||
if [ -f .openapi-hash ]; then
|
||||
OLD_HASH=$(cat .openapi-hash)
|
||||
else
|
||||
OLD_HASH="none"
|
||||
fi
|
||||
|
||||
echo "old=$OLD_HASH" >> "$GITHUB_OUTPUT"
|
||||
echo "new=$NEW_HASH" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ "$NEW_HASH" != "$OLD_HASH" ]; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Diff against the in-tree baseline (.openapi-spec.yaml).
|
||||
# Maintainer commits a fresh baseline when addressing the issue.
|
||||
if [ -f .openapi-spec.yaml ]; then
|
||||
diff -u .openapi-spec.yaml new-spec.yaml > /tmp/spec-diff.txt || true
|
||||
else
|
||||
echo "No baseline .openapi-spec.yaml in repo - commit one to enable diffs." > /tmp/spec-diff.txt
|
||||
fi
|
||||
else
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Open or refresh tracking issue
|
||||
if: steps.check.outputs.changed == 'true'
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
OLD_HASH: ${{ steps.check.outputs.old }}
|
||||
NEW_HASH: ${{ steps.check.outputs.new }}
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
let diff = fs.readFileSync('/tmp/spec-diff.txt', 'utf8');
|
||||
|
||||
// Truncate if too long for issue body (GitHub issue body limit is 65536 chars)
|
||||
if (diff.length > 50000) {
|
||||
diff = diff.substring(0, 50000) + '\n\n... (truncated - view full spec for details)';
|
||||
}
|
||||
|
||||
const oldHash = process.env.OLD_HASH;
|
||||
const newHash = process.env.NEW_HASH;
|
||||
|
||||
const body =
|
||||
`The upstream OpenAPI spec has changed and the SDK is out of sync.\n\n` +
|
||||
`**Stored hash (.openapi-hash):** \`${oldHash}\`\n` +
|
||||
`**Current upstream hash:** \`${newHash}\`\n` +
|
||||
`_Last refreshed: ${new Date().toISOString()}_\n\n` +
|
||||
`[View upstream spec](https://github.com/mistralai/platform-docs-public/blob/main/openapi.yaml)\n\n` +
|
||||
`### Diff vs in-tree baseline\n\`\`\`diff\n${diff}\n\`\`\`\n\n` +
|
||||
`### TODO\n` +
|
||||
`- [ ] Review spec changes\n` +
|
||||
`- [ ] Update SDK types if needed\n` +
|
||||
`- [ ] Run tests\n` +
|
||||
`- [ ] Bump \`.openapi-hash\` and refresh \`.openapi-spec.yaml\` in same commit\n` +
|
||||
`- [ ] Close this issue\n\n` +
|
||||
`<sub>This issue body is rewritten by the watcher each run, so the diff and hashes always reflect the latest upstream state.</sub>`;
|
||||
|
||||
const issues = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: 'openapi-update',
|
||||
state: 'open'
|
||||
});
|
||||
|
||||
if (issues.data.length === 0) {
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: 'Mistral OpenAPI spec has changed',
|
||||
body,
|
||||
labels: ['openapi-update']
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh the existing open issue so the diff/hashes never go stale.
|
||||
const issue = issues.data[0];
|
||||
|
||||
// Detect whether upstream moved *again* since the last refresh by
|
||||
// parsing the previous "Current upstream hash" out of the issue body.
|
||||
const marker = /\*\*Current upstream hash:\*\* `([a-f0-9]{64})`/;
|
||||
const prev = (issue.body || '').match(marker);
|
||||
const movedAgain = prev && prev[1] !== newHash;
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body
|
||||
});
|
||||
|
||||
if (movedAgain) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body:
|
||||
`Upstream spec moved again since this issue was last refreshed.\n\n` +
|
||||
`Previous upstream hash: \`${prev[1]}\`\n` +
|
||||
`New upstream hash: \`${newHash}\`\n\n` +
|
||||
`Issue body has been updated to the latest diff.`
|
||||
});
|
||||
}
|
||||
1
.openapi-hash
Normal file
1
.openapi-hash
Normal file
@@ -0,0 +1 @@
|
||||
ff8a7389b7a4e61145561361537aae37c49f7e2dcf7c4f79f41e80a30b484cc3
|
||||
19660
.openapi-spec.yaml
Normal file
19660
.openapi-spec.yaml
Normal file
File diff suppressed because it is too large
Load Diff
69
CHANGELOG.md
69
CHANGELOG.md
@@ -1,3 +1,72 @@
|
||||
## v1.4.0 — 2026-04-28
|
||||
|
||||
Spec/SDK alignment pass after upstream OpenAPI moved to v1.0.0 and
|
||||
Python SDK shipped v2.3.0..v2.4.3. RAG ingestion-pipeline beta surface
|
||||
(Python v2.4.3) intentionally deferred until the dust settles upstream.
|
||||
|
||||
### Added
|
||||
|
||||
- **`Client.GetWorkflowWorkerInfo`** — restores the
|
||||
`GET /v1/workflows/workers/whoami` endpoint that was removed in v1.3.0.
|
||||
The endpoint is still in the spec and is needed by callers running
|
||||
custom workers that connect their own scheduler.
|
||||
(`workflow.WorkerInfo` type.)
|
||||
- **Observability fields API** — three GETs missing since the
|
||||
observability surface was first added:
|
||||
- `Client.GetChatCompletionFields` (`/v1/observability/chat-completion-fields`)
|
||||
- `Client.GetChatCompletionFieldOptions` (`…/{field}/options?operator=…`)
|
||||
- `Client.GetChatCompletionFieldOptionsCounts` (`…/{field}/options-counts`)
|
||||
- new types: `observability.BaseFieldDefinition`, `FieldGroup`,
|
||||
`ChatCompletionFields`, `ChatCompletionFieldOptions`,
|
||||
`FieldOptionCountsRequest`, `FieldOptionCounts`, `FieldOptionCountItem`,
|
||||
plus `FieldType` and `FieldOperator` typed enums.
|
||||
- **Workflow payload encoding constants** — `workflow.EncodedPayloadOption`
|
||||
with `EncodedPayloadOffloaded`, `EncodedPayloadEncrypted`,
|
||||
`EncodedPayloadEncryptedPartial`. Wire-compatible refinement of the
|
||||
pre-existing `[]string` field on `NetworkEncodedInput`.
|
||||
(Mirrors Python SDK v2.4.0.)
|
||||
- **Workflow ↔ connector integration** (Python SDK v2.4.2):
|
||||
- `workflow.ConnectorSlot`, `ConnectorBindings`, `ConnectorExtensions`,
|
||||
`WorkflowExtensions` types.
|
||||
- `workflow.BuildConnectorExtensions(slots …)` helper that produces the
|
||||
nested map expected at `ExecutionRequest.Extensions["mistralai"]`.
|
||||
- `workflow.ConnectorAuthTaskState` + `ConnectorAuthStatus` constants
|
||||
for parsing payloads emitted by the `connector-auth` custom task event.
|
||||
- New `Extensions map[string]any` field on `workflow.ExecutionRequest`.
|
||||
- **HITL (human-in-the-loop) confirmation constants** — typed values
|
||||
alongside the pre-existing `conversation.ToolCallConfirmation` and
|
||||
`tool_confirmations` field:
|
||||
- `conversation.Confirmation` with `ConfirmationAllow` / `ConfirmationDeny`
|
||||
for the reply side.
|
||||
- `ConfirmationStatusPending` / `ConfirmationStatusAllowed` /
|
||||
`ConfirmationStatusDenied` for `FunctionCallEvent.ConfirmationStatus`
|
||||
and `FunctionCallEntry.ConfirmationStatus` (already present as
|
||||
untyped strings).
|
||||
|
||||
### Changed
|
||||
|
||||
- `workflow.NetworkEncodedInput.EncodingOptions` is now
|
||||
`[]EncodedPayloadOption` (string-typed alias). JSON wire format
|
||||
unchanged; existing call sites that passed `[]string{"offloaded"}`
|
||||
need to switch to `[]workflow.EncodedPayloadOption{workflow.EncodedPayloadOffloaded}`
|
||||
or the typed constants directly.
|
||||
- Tracking upstream Mistral OpenAPI spec **v1.0.0** (was v0.1.104).
|
||||
Only spec delta in this window was the removal of OCR confidence-score
|
||||
fields (`OCRPageObject.confidence_scores`,
|
||||
`OCRRequest.confidence_scores_granularity`,
|
||||
`OCRTableObject.word_confidence_scores`, plus the `OCRConfidenceScore`
|
||||
and `OCRPageConfidenceScores` schemas), none of which this SDK exposed.
|
||||
|
||||
### Fixed (CI)
|
||||
|
||||
- `watch-openapi.yml` no longer attempts to commit `.openapi-hash` /
|
||||
`.openapi-spec.yaml` to `main`. The push was being silently reverted
|
||||
by an upstream mirror, leaving the tracking issue stale across
|
||||
multiple upstream releases. The watcher now refreshes the open
|
||||
tracking issue's body on each run so the diff and hashes always
|
||||
reflect the current upstream state, and posts a comment when the
|
||||
spec moves again while the issue is still open.
|
||||
|
||||
## v1.3.0 — 2026-04-03
|
||||
|
||||
Upstream sync with Python SDK v2.3.0. Updates workflow registration model
|
||||
|
||||
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project
|
||||
|
||||
Idiomatic Go SDK for the Mistral AI API. Module path: `github.com/VikingOwl91/mistral-go-sdk`. Requires Go 1.26+. Zero external dependencies (stdlib only). Tracks the upstream [Mistral Python SDK](https://github.com/mistralai/client-python) as reference for API surface and type definitions.
|
||||
Idiomatic Go SDK for the Mistral AI API. Module path: `github.com/VikingOwl91/mistral-go-sdk`. Requires Go 1.26+. Zero external dependencies (stdlib only). Tracks the [official Mistral OpenAPI spec](https://github.com/mistralai/platform-docs-public/blob/main/openapi.yaml) as primary reference for API surface and type definitions, with the [Mistral Python SDK](https://github.com/mistralai/client-python) as secondary reference for implementation patterns. A daily GitHub Action monitors the OpenAPI spec for changes.
|
||||
|
||||
## Repository layout
|
||||
|
||||
|
||||
273
README.md
273
README.md
@@ -1,25 +1,32 @@
|
||||
# mistral-go-sdk
|
||||
|
||||
The most complete Go client for the [Mistral AI API](https://docs.mistral.ai/).
|
||||
Go SDK for Mistral's agentic stack — Workflows, Conversations, Connectors — with full coverage of the rest of the Mistral AI API.
|
||||
|
||||
<!-- Badges -->
|
||||
[](https://pkg.go.dev/github.com/VikingOwl91/mistral-go-sdk)
|
||||

|
||||

|
||||
|
||||
## Why This SDK?
|
||||
## What this is for
|
||||
|
||||
**Zero dependencies.** The entire SDK — including tests — uses only the Go standard library. No `go.sum`, no transitive dependency tree to audit, no version conflicts, no supply chain risk.
|
||||
Mistral's `/v1/chat/completions` endpoint is OpenAI-wire-compatible, so if all you need is plain chat or tool calling, pointing any OpenAI Go client at `https://api.mistral.ai/v1` already works. **This SDK exists for the rest of Mistral's surface** — the parts no other Go client covers:
|
||||
|
||||
**Full API coverage.** 165 methods across every Mistral endpoint — including Workflows, Connectors, Audio Speech/Voices, Conversations, Agents CRUD, Libraries, OCR, Observability, Fine-tuning, and Batch Jobs. No other Go SDK covers Workflows, Conversations, Connectors, or Observability.
|
||||
- **Workflows** — durable, long-running executions with signals, queries, updates, and full event streaming.
|
||||
- **Conversations API** — server-stored multi-turn state with streaming events, agent handoffs, and human-in-the-loop tool confirmations.
|
||||
- **Connectors / MCP** — manage MCP connectors and bind them to workflow executions.
|
||||
- **Agents CRUD** — create, version, and alias agents (not just `agents/completions`).
|
||||
- **Libraries / Documents** — RAG document stores with reprocess, status, and signed-URL retrieval.
|
||||
- **OCR, Audio (transcription + TTS + voices), Fine-tuning, Batch, Observability, Moderation, Classification.**
|
||||
|
||||
**Typed streaming.** A generic pull-based `Stream[T]` iterator — no channels, no goroutines, no leaks. Just `Next()` / `Current()` / `Err()` / `Close()`.
|
||||
If your use case is "Go program calls Mistral and gets a response back," any OpenAI-compatible client is the easier path. If you need agentic workflows or stateful conversations from Go, this is the only option.
|
||||
|
||||
**Forward-compatible.** Unknown types (`UnknownEntry`, `UnknownEvent`, `UnknownMessage`, `UnknownChunk`, `UnknownAgentTool`, workflow `UnknownEvent`) capture raw JSON instead of returning errors. When Mistral ships a new message role or event type, your code keeps running — it doesn't panic.
|
||||
## Why this SDK
|
||||
|
||||
**Hand-written, not generated.** Idiomatic Go with sealed interfaces, discriminated unions, and functional options — not a Speakeasy/OpenAPI auto-gen dump with `any` everywhere.
|
||||
|
||||
**Test-driven.** 284 tests with race detection clean. Every endpoint tested against mock servers; integration tests against the real API.
|
||||
- **Zero dependencies.** Stdlib-only. No `go.sum`, no transitive tree, no supply chain.
|
||||
- **Hand-written, not generated.** Idiomatic Go with sealed interfaces, discriminated unions, and functional options — not a Speakeasy/OpenAPI dump with `any` everywhere.
|
||||
- **Forward-compatible types.** `UnknownEntry`, `UnknownEvent`, `UnknownMessage`, `UnknownChunk`, `UnknownAgentTool` capture raw JSON instead of returning errors — when Mistral ships a new event type, your code keeps running.
|
||||
- **Typed streaming.** Generic pull-based `Stream[T]` iterator — no channels, no goroutines, no leaks. Just `Next()` / `Current()` / `Err()` / `Close()`.
|
||||
- **Test-driven.** 297 tests, race-clean. Every endpoint has a mock-server unit test; integration tests run against the real API behind a build tag.
|
||||
|
||||
## Install
|
||||
|
||||
@@ -29,44 +36,58 @@ go get github.com/VikingOwl91/mistral-go-sdk
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Chat Completion
|
||||
### Execute a workflow with connector bindings
|
||||
|
||||
The most differentiated thing this SDK does. Run a registered workflow, hand it the MCP connectors it needs, and block until completion:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
mistral "github.com/VikingOwl91/mistral-go-sdk"
|
||||
"github.com/VikingOwl91/mistral-go-sdk/chat"
|
||||
"github.com/VikingOwl91/mistral-go-sdk/workflow"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := mistral.NewClient("sk-your-api-key")
|
||||
|
||||
resp, err := client.ChatComplete(context.Background(), &chat.CompletionRequest{
|
||||
Model: "mistral-small-latest",
|
||||
Messages: []chat.Message{
|
||||
&chat.UserMessage{Content: chat.TextContent("What is the capital of France?")},
|
||||
creds := "work-account"
|
||||
resp, err := client.ExecuteWorkflowAndWait(context.Background(), "my-workflow", &workflow.ExecutionRequest{
|
||||
Input: map[string]any{
|
||||
"topic": "Q3 earnings",
|
||||
},
|
||||
Extensions: workflow.BuildConnectorExtensions(
|
||||
workflow.ConnectorSlot{ConnectorName: "gmail"},
|
||||
workflow.ConnectorSlot{ConnectorName: "notion", CredentialsName: &creds},
|
||||
),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(resp.Choices[0].Message.Content)
|
||||
|
||||
fmt.Printf("execution %s finished with status %s\n", resp.ExecutionID, resp.Status)
|
||||
if resp.Status == workflow.ExecutionCompleted {
|
||||
result, _ := json.MarshalIndent(resp.Result, "", " ")
|
||||
fmt.Println(string(result))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Streaming
|
||||
### Stream a conversation with tool-call confirmation
|
||||
|
||||
Conversations keep server-side state across turns. Stream events, accept or deny pending tool calls, and continue:
|
||||
|
||||
```go
|
||||
stream, err := client.ChatCompleteStream(ctx, &chat.CompletionRequest{
|
||||
Model: "mistral-small-latest",
|
||||
Messages: []chat.Message{
|
||||
&chat.UserMessage{Content: chat.TextContent("Tell me a joke.")},
|
||||
},
|
||||
import "github.com/VikingOwl91/mistral-go-sdk/conversation"
|
||||
|
||||
stream, err := client.StartConversationStream(ctx, &conversation.StartRequest{
|
||||
AgentID: "ag-your-agent-id",
|
||||
Inputs: conversation.TextInputs("Summarize today's incident review and email it to the on-call channel."),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -74,9 +95,20 @@ if err != nil {
|
||||
defer stream.Close()
|
||||
|
||||
for stream.Next() {
|
||||
chunk := stream.Current()
|
||||
if len(chunk.Choices) > 0 {
|
||||
fmt.Print(chunk.Choices[0].Delta.Content)
|
||||
event := stream.Current()
|
||||
switch e := event.(type) {
|
||||
case *conversation.FunctionCallEvent:
|
||||
if e.ConfirmationStatus != nil && *e.ConfirmationStatus == conversation.ConfirmationStatusPending {
|
||||
// Hand the user a confirmation prompt; reply on the next AppendConversation.
|
||||
confirmations := []conversation.ToolCallConfirmation{
|
||||
{ToolCallID: e.ToolCallID, Confirmation: string(conversation.ConfirmationAllow)},
|
||||
}
|
||||
_, _ = client.AppendConversation(ctx, /* conversationID */ "conv-...", &conversation.AppendRequest{
|
||||
ToolConfirmations: confirmations,
|
||||
})
|
||||
}
|
||||
case *conversation.MessageOutputEntry:
|
||||
// assistant tokens
|
||||
}
|
||||
}
|
||||
if err := stream.Err(); err != nil {
|
||||
@@ -84,114 +116,24 @@ if err := stream.Err(); err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
### Tool Calling
|
||||
### Chat completion (when that's all you need)
|
||||
|
||||
```go
|
||||
import "github.com/VikingOwl91/mistral-go-sdk/chat"
|
||||
|
||||
resp, err := client.ChatComplete(ctx, &chat.CompletionRequest{
|
||||
Model: "mistral-small-latest",
|
||||
Messages: []chat.Message{
|
||||
&chat.UserMessage{Content: chat.TextContent("What's the weather in Paris?")},
|
||||
&chat.UserMessage{Content: chat.TextContent("What is the capital of France?")},
|
||||
},
|
||||
Tools: []chat.Tool{{
|
||||
Type: "function",
|
||||
Function: chat.Function{
|
||||
Name: "get_weather",
|
||||
Description: "Get weather for a city",
|
||||
Parameters: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"city": map[string]any{"type": "string"},
|
||||
},
|
||||
"required": []string{"city"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
```
|
||||
|
||||
### Conversations
|
||||
|
||||
```go
|
||||
import "github.com/VikingOwl91/mistral-go-sdk/conversation"
|
||||
|
||||
resp, err := client.StartConversation(ctx, &conversation.StartRequest{
|
||||
AgentID: "ag-your-agent-id",
|
||||
Inputs: conversation.TextInputs("Hello, agent!"),
|
||||
})
|
||||
|
||||
// Stream events
|
||||
stream, err := client.AppendConversationStream(ctx, resp.ConversationID, &conversation.AppendRequest{
|
||||
Inputs: conversation.TextInputs("Follow-up question"),
|
||||
})
|
||||
defer stream.Close()
|
||||
for stream.Next() {
|
||||
event := stream.Current()
|
||||
// handle typed events
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(resp.Choices[0].Message.Content)
|
||||
```
|
||||
|
||||
## API Coverage
|
||||
|
||||
165 public methods on `Client`, grouped by domain:
|
||||
|
||||
| Domain | Methods |
|
||||
|--------|---------|
|
||||
| **Chat** | `ChatComplete`, `ChatCompleteStream` |
|
||||
| **FIM** | `FIMComplete`, `FIMCompleteStream` |
|
||||
| **Agents (completions)** | `AgentsComplete`, `AgentsCompleteStream` |
|
||||
| **Agents (CRUD)** | `CreateAgent`, `ListAgents`, `GetAgent`, `UpdateAgent`, `DeleteAgent`, `UpdateAgentVersion`, `ListAgentVersions`, `GetAgentVersion`, `SetAgentAlias`, `ListAgentAliases`, `DeleteAgentAlias` |
|
||||
| **Connectors** | `CreateConnector`, `ListConnectors`, `GetConnector`, `UpdateConnector`, `DeleteConnector`, `GetConnectorAuthURL`, `ListConnectorTools`, `CallConnectorTool` |
|
||||
| **Conversations** | `StartConversation`, `StartConversationStream`, `AppendConversation`, `AppendConversationStream`, `RestartConversation`, `RestartConversationStream`, `GetConversation`, `ListConversations`, `DeleteConversation`, `GetConversationHistory`, `GetConversationMessages` |
|
||||
| **Models** | `ListModels`, `GetModel`, `DeleteModel` |
|
||||
| **Files** | `UploadFile`, `ListFiles`, `GetFile`, `DeleteFile`, `GetFileContent`, `GetFileURL` |
|
||||
| **Embeddings** | `CreateEmbeddings` |
|
||||
| **Fine-tuning** | `CreateFineTuningJob`, `ListFineTuningJobs`, `GetFineTuningJob`, `CancelFineTuningJob`, `StartFineTuningJob`, `UpdateFineTunedModel`, `ArchiveFineTunedModel`, `UnarchiveFineTunedModel` |
|
||||
| **Batch** | `CreateBatchJob`, `ListBatchJobs`, `GetBatchJob`, `CancelBatchJob`, `DeleteBatchJob` |
|
||||
| **OCR** | `OCR` |
|
||||
| **Audio (transcription)** | `Transcribe`, `TranscribeStream` |
|
||||
| **Audio (speech)** | `Speech`, `SpeechStream` |
|
||||
| **Audio (voices)** | `ListVoices`, `CreateVoice`, `GetVoice`, `UpdateVoice`, `DeleteVoice`, `GetVoiceSampleAudio` |
|
||||
| **Libraries** | `CreateLibrary`, `ListLibraries`, `GetLibrary`, `UpdateLibrary`, `DeleteLibrary`, `UploadDocument`, `ListDocuments`, `GetDocument`, `UpdateDocument`, `DeleteDocument`, `GetDocumentTextContent`, `GetDocumentStatus`, `GetDocumentSignedURL`, `GetDocumentExtractedTextSignedURL`, `ReprocessDocument`, `ListLibrarySharing`, `ShareLibrary`, `UnshareLibrary` |
|
||||
| **Moderation** | `Moderate`, `ModerateChat` |
|
||||
| **Classification** | `Classify`, `ClassifyChat` |
|
||||
| **Observability (campaigns)** | `CreateCampaign`, `ListCampaigns`, `GetCampaign`, `DeleteCampaign`, `GetCampaignStatus`, `ListCampaignEvents` |
|
||||
| **Observability (events)** | `SearchChatCompletionEvents`, `SearchChatCompletionEventIDs`, `GetChatCompletionEvent`, `GetSimilarChatCompletionEvents`, `JudgeChatCompletionEvent` |
|
||||
| **Observability (judges)** | `CreateJudge`, `ListJudges`, `GetJudge`, `UpdateJudge`, `DeleteJudge`, `JudgeConversation` |
|
||||
| **Observability (datasets)** | `CreateDataset`, `ListDatasets`, `GetDataset`, `UpdateDataset`, `DeleteDataset`, `ExportDatasetToJSONL`, `ListDatasetRecords`, `CreateDatasetRecord`, `GetDatasetRecord`, `UpdateDatasetRecordPayload`, `UpdateDatasetRecordProperties`, `DeleteDatasetRecord`, `BulkDeleteDatasetRecords`, `JudgeDatasetRecord`, `ImportDatasetFromCampaign`, `ImportDatasetFromExplorer`, `ImportDatasetFromFile`, `ImportDatasetFromPlayground`, `ImportDatasetFromDataset`, `ListDatasetTasks`, `GetDatasetTask` |
|
||||
| **Workflows (CRUD)** | `ListWorkflows`, `GetWorkflow`, `UpdateWorkflow`, `ArchiveWorkflow`, `UnarchiveWorkflow`, `ExecuteWorkflow`, `ExecuteWorkflowAndWait` |
|
||||
| **Workflows (registrations)** | `ListWorkflowRegistrations`, `GetWorkflowRegistration`, `ExecuteWorkflowRegistration` |
|
||||
| **Workflows (executions)** | `GetWorkflowExecution`, `GetWorkflowExecutionHistory`, `StreamWorkflowExecution`, `SignalWorkflowExecution`, `QueryWorkflowExecution`, `UpdateWorkflowExecution`, `TerminateWorkflowExecution`, `CancelWorkflowExecution`, `ResetWorkflowExecution`, `BatchCancelWorkflowExecutions`, `BatchTerminateWorkflowExecutions` |
|
||||
| **Workflows (trace)** | `GetWorkflowExecutionTraceOTel`, `GetWorkflowExecutionTraceSummary`, `GetWorkflowExecutionTraceEvents` |
|
||||
| **Workflows (events)** | `StreamWorkflowEvents`, `ListWorkflowEvents` |
|
||||
| **Workflows (deployments)** | `ListWorkflowDeployments`, `GetWorkflowDeployment` |
|
||||
| **Workflows (metrics)** | `GetWorkflowMetrics` |
|
||||
| **Workflows (runs)** | `ListWorkflowRuns`, `GetWorkflowRun`, `GetWorkflowRunHistory` |
|
||||
| **Workflows (schedules)** | `ListWorkflowSchedules`, `ScheduleWorkflow`, `UnscheduleWorkflow` |
|
||||
|
||||
## Comparison
|
||||
|
||||
There is no official Go SDK from Mistral AI (only Python and TypeScript). The main community options:
|
||||
|
||||
| Feature | mistral-go-sdk | [Gage-Technologies](https://github.com/Gage-Technologies/mistral-go) | [robertjkeck2](https://github.com/robertjkeck2/mistral-go) | [AuxData-ai](https://github.com/AuxData-ai/mistral-go) |
|
||||
|---------|:-:|:-:|:-:|:-:|
|
||||
| Chat / Streaming | Yes | Yes | Yes | Yes |
|
||||
| FIM | Yes | Yes | No | Yes |
|
||||
| Embeddings | Yes | Yes | Yes | Yes |
|
||||
| Tool calling | Yes | No | No | No |
|
||||
| Agents (completions + CRUD) | Yes | No | No | No |
|
||||
| Connectors (MCP) | Yes | No | No | No |
|
||||
| Conversations API | Yes | No | No | No |
|
||||
| Libraries / Documents | Yes | No | No | No |
|
||||
| Fine-tuning / Batch | Yes | No | No | No |
|
||||
| OCR | Yes | No | No | Yes |
|
||||
| Audio (transcription + TTS + voices) | Yes | No | No | No |
|
||||
| Workflows API | Yes | No | No | No |
|
||||
| Observability (beta) | Yes | No | No | No |
|
||||
| Moderation / Classification | Yes | No | No | No |
|
||||
| Vision (multimodal) | Yes | No | No | Yes |
|
||||
| Zero dependencies | Yes | test-only (testify) | test-only (testify) | test-only (testify) |
|
||||
| Forward-compatible types | Yes | No | No | No |
|
||||
| Last updated | 2026 | Jun 2024 | Jan 2024 | ~2025 (fork of Gage) |
|
||||
If this is your whole use case, the OpenAI-wire-compatible endpoint is also a valid path — point any `openai-go` client at `https://api.mistral.ai/v1` and it works. The rest of this SDK is for what the OpenAI shape doesn't cover.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -233,18 +175,85 @@ if err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
## API Coverage
|
||||
|
||||
169 public methods on `Client`, grouped by domain:
|
||||
|
||||
| Domain | Methods |
|
||||
|--------|---------|
|
||||
| **Chat** | `ChatComplete`, `ChatCompleteStream` |
|
||||
| **FIM** | `FIMComplete`, `FIMCompleteStream` |
|
||||
| **Agents (completions)** | `AgentsComplete`, `AgentsCompleteStream` |
|
||||
| **Agents (CRUD)** | `CreateAgent`, `ListAgents`, `GetAgent`, `UpdateAgent`, `DeleteAgent`, `UpdateAgentVersion`, `ListAgentVersions`, `GetAgentVersion`, `SetAgentAlias`, `ListAgentAliases`, `DeleteAgentAlias` |
|
||||
| **Connectors** | `CreateConnector`, `ListConnectors`, `GetConnector`, `UpdateConnector`, `DeleteConnector`, `GetConnectorAuthURL`, `ListConnectorTools`, `CallConnectorTool` |
|
||||
| **Conversations** | `StartConversation`, `StartConversationStream`, `AppendConversation`, `AppendConversationStream`, `RestartConversation`, `RestartConversationStream`, `GetConversation`, `ListConversations`, `DeleteConversation`, `GetConversationHistory`, `GetConversationMessages` |
|
||||
| **Models** | `ListModels`, `GetModel`, `DeleteModel` |
|
||||
| **Files** | `UploadFile`, `ListFiles`, `GetFile`, `DeleteFile`, `GetFileContent`, `GetFileURL` |
|
||||
| **Embeddings** | `CreateEmbeddings` |
|
||||
| **Fine-tuning** | `CreateFineTuningJob`, `ListFineTuningJobs`, `GetFineTuningJob`, `CancelFineTuningJob`, `StartFineTuningJob`, `UpdateFineTunedModel`, `ArchiveFineTunedModel`, `UnarchiveFineTunedModel` |
|
||||
| **Batch** | `CreateBatchJob`, `ListBatchJobs`, `GetBatchJob`, `CancelBatchJob`, `DeleteBatchJob` |
|
||||
| **OCR** | `OCR` |
|
||||
| **Audio (transcription)** | `Transcribe`, `TranscribeStream` |
|
||||
| **Audio (speech)** | `Speech`, `SpeechStream` |
|
||||
| **Audio (voices)** | `ListVoices`, `CreateVoice`, `GetVoice`, `UpdateVoice`, `DeleteVoice`, `GetVoiceSampleAudio` |
|
||||
| **Libraries** | `CreateLibrary`, `ListLibraries`, `GetLibrary`, `UpdateLibrary`, `DeleteLibrary`, `UploadDocument`, `ListDocuments`, `GetDocument`, `UpdateDocument`, `DeleteDocument`, `GetDocumentTextContent`, `GetDocumentStatus`, `GetDocumentSignedURL`, `GetDocumentExtractedTextSignedURL`, `ReprocessDocument`, `ListLibrarySharing`, `ShareLibrary`, `UnshareLibrary` |
|
||||
| **Moderation** | `Moderate`, `ModerateChat` |
|
||||
| **Classification** | `Classify`, `ClassifyChat` |
|
||||
| **Observability (campaigns)** | `CreateCampaign`, `ListCampaigns`, `GetCampaign`, `DeleteCampaign`, `GetCampaignStatus`, `ListCampaignEvents` |
|
||||
| **Observability (events)** | `SearchChatCompletionEvents`, `SearchChatCompletionEventIDs`, `GetChatCompletionEvent`, `GetSimilarChatCompletionEvents`, `JudgeChatCompletionEvent` |
|
||||
| **Observability (fields)** | `GetChatCompletionFields`, `GetChatCompletionFieldOptions`, `GetChatCompletionFieldOptionsCounts` |
|
||||
| **Observability (judges)** | `CreateJudge`, `ListJudges`, `GetJudge`, `UpdateJudge`, `DeleteJudge`, `JudgeConversation` |
|
||||
| **Observability (datasets)** | `CreateDataset`, `ListDatasets`, `GetDataset`, `UpdateDataset`, `DeleteDataset`, `ExportDatasetToJSONL`, `ListDatasetRecords`, `CreateDatasetRecord`, `GetDatasetRecord`, `UpdateDatasetRecordPayload`, `UpdateDatasetRecordProperties`, `DeleteDatasetRecord`, `BulkDeleteDatasetRecords`, `JudgeDatasetRecord`, `ImportDatasetFromCampaign`, `ImportDatasetFromExplorer`, `ImportDatasetFromFile`, `ImportDatasetFromPlayground`, `ImportDatasetFromDataset`, `ListDatasetTasks`, `GetDatasetTask` |
|
||||
| **Workflows (CRUD)** | `ListWorkflows`, `GetWorkflow`, `UpdateWorkflow`, `ArchiveWorkflow`, `UnarchiveWorkflow`, `ExecuteWorkflow`, `ExecuteWorkflowAndWait` |
|
||||
| **Workflows (registrations)** | `ListWorkflowRegistrations`, `GetWorkflowRegistration`, `ExecuteWorkflowRegistration` |
|
||||
| **Workflows (executions)** | `GetWorkflowExecution`, `GetWorkflowExecutionHistory`, `StreamWorkflowExecution`, `SignalWorkflowExecution`, `QueryWorkflowExecution`, `UpdateWorkflowExecution`, `TerminateWorkflowExecution`, `CancelWorkflowExecution`, `ResetWorkflowExecution`, `BatchCancelWorkflowExecutions`, `BatchTerminateWorkflowExecutions` |
|
||||
| **Workflows (trace)** | `GetWorkflowExecutionTraceOTel`, `GetWorkflowExecutionTraceSummary`, `GetWorkflowExecutionTraceEvents` |
|
||||
| **Workflows (events)** | `StreamWorkflowEvents`, `ListWorkflowEvents` |
|
||||
| **Workflows (deployments)** | `ListWorkflowDeployments`, `GetWorkflowDeployment` |
|
||||
| **Workflows (metrics)** | `GetWorkflowMetrics` |
|
||||
| **Workflows (runs)** | `ListWorkflowRuns`, `GetWorkflowRun`, `GetWorkflowRunHistory` |
|
||||
| **Workflows (schedules)** | `ListWorkflowSchedules`, `ScheduleWorkflow`, `UnscheduleWorkflow` |
|
||||
| **Workflows (workers)** | `GetWorkflowWorkerInfo` |
|
||||
|
||||
## Comparison
|
||||
|
||||
There is no official Go SDK from Mistral AI (only Python and TypeScript). The main community options:
|
||||
|
||||
| Feature | mistral-go-sdk | [Gage-Technologies](https://github.com/Gage-Technologies/mistral-go) | [robertjkeck2](https://github.com/robertjkeck2/mistral-go) | [AuxData-ai](https://github.com/AuxData-ai/mistral-go) |
|
||||
|---------|:-:|:-:|:-:|:-:|
|
||||
| Chat / Streaming | Yes | Yes | Yes | Yes |
|
||||
| FIM | Yes | Yes | No | Yes |
|
||||
| Embeddings | Yes | Yes | Yes | Yes |
|
||||
| Tool calling | Yes | No | No | No |
|
||||
| Agents (completions + CRUD) | Yes | No | No | No |
|
||||
| Connectors (MCP) | Yes | No | No | No |
|
||||
| Conversations API | Yes | No | No | No |
|
||||
| Libraries / Documents | Yes | No | No | No |
|
||||
| Fine-tuning / Batch | Yes | No | No | No |
|
||||
| OCR | Yes | No | No | Yes |
|
||||
| Audio (transcription + TTS + voices) | Yes | No | No | No |
|
||||
| Workflows API | Yes | No | No | No |
|
||||
| Observability (beta) | Yes | No | No | No |
|
||||
| Moderation / Classification | Yes | No | No | No |
|
||||
| Vision (multimodal) | Yes | No | No | Yes |
|
||||
| Zero dependencies | Yes | test-only (testify) | test-only (testify) | test-only (testify) |
|
||||
| Forward-compatible types | Yes | No | No | No |
|
||||
| Last updated | 2026 | Jun 2024 | Jan 2024 | ~2025 (fork of Gage) |
|
||||
|
||||
## Upstream Reference
|
||||
|
||||
This SDK tracks the [official Mistral Python SDK](https://github.com/mistralai/client-python)
|
||||
as its upstream reference for API surface and type definitions.
|
||||
This SDK tracks the [official Mistral OpenAPI spec](https://github.com/mistralai/platform-docs-public/blob/main/openapi.yaml) as its primary reference for API surface and type definitions. A daily GitHub Action monitors the spec for changes and refreshes a tracking issue when updates are detected.
|
||||
|
||||
| SDK Version | Upstream Python SDK |
|
||||
|-------------|---------------------|
|
||||
| v1.3.0 | v2.3.0 |
|
||||
| v1.2.1 | v2.2.0 |
|
||||
| v1.2.0 | v2.2.0 |
|
||||
| v1.1.0 | v2.1.3 |
|
||||
| v1.0.0 | v2.0.4 |
|
||||
The [Mistral Python SDK](https://github.com/mistralai/client-python) is used as a secondary reference for implementation patterns.
|
||||
|
||||
| SDK Version | Upstream Python SDK | Upstream OpenAPI |
|
||||
|-------------|---------------------|------------------|
|
||||
| v1.4.0 | v2.4.3 (excl. RAG ingestion-pipeline beta) | v1.0.0 |
|
||||
| v1.3.0 | v2.3.0 | v0.1.104 |
|
||||
| v1.2.1 | v2.2.0 | — |
|
||||
| v1.2.0 | v2.2.0 | — |
|
||||
| v1.1.0 | v2.1.3 | — |
|
||||
| v1.0.0 | v2.0.4 | — |
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -44,10 +44,30 @@ type CompletionArgs struct {
|
||||
ToolChoice *chat.ToolChoiceMode `json:"tool_choice,omitempty"`
|
||||
}
|
||||
|
||||
// Confirmation is a client decision on a pending tool call.
|
||||
type Confirmation string
|
||||
|
||||
const (
|
||||
ConfirmationAllow Confirmation = "allow"
|
||||
ConfirmationDeny Confirmation = "deny"
|
||||
)
|
||||
|
||||
// ConfirmationStatus values appear on FunctionCallEvent.ConfirmationStatus
|
||||
// and FunctionCallEntry.ConfirmationStatus, reporting where in the
|
||||
// human-in-the-loop flow a tool call currently sits.
|
||||
const (
|
||||
ConfirmationStatusPending = "pending"
|
||||
ConfirmationStatusAllowed = "allowed"
|
||||
ConfirmationStatusDenied = "denied"
|
||||
)
|
||||
|
||||
// ToolCallConfirmation confirms or denies a pending tool call.
|
||||
//
|
||||
// Send a slice of these on AppendRequest.ToolConfirmations after receiving
|
||||
// a function call event whose ConfirmationStatus is "pending".
|
||||
type ToolCallConfirmation struct {
|
||||
ToolCallID string `json:"tool_call_id"`
|
||||
Confirmation string `json:"confirmation"` // "allow" or "deny"
|
||||
Confirmation string `json:"confirmation"` // use ConfirmationAllow / ConfirmationDeny
|
||||
}
|
||||
|
||||
// Inputs represents conversation inputs (text string or entry array).
|
||||
|
||||
84
observability/field.go
Normal file
84
observability/field.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package observability
|
||||
|
||||
// FieldType identifies the data type of a chat-completion-event field.
|
||||
type FieldType string
|
||||
|
||||
const (
|
||||
FieldTypeEnum FieldType = "ENUM"
|
||||
FieldTypeText FieldType = "TEXT"
|
||||
FieldTypeInt FieldType = "INT"
|
||||
FieldTypeFloat FieldType = "FLOAT"
|
||||
FieldTypeBool FieldType = "BOOL"
|
||||
FieldTypeTimestamp FieldType = "TIMESTAMP"
|
||||
FieldTypeArray FieldType = "ARRAY"
|
||||
)
|
||||
|
||||
// FieldOperator is a filter operator supported on observability fields.
|
||||
type FieldOperator string
|
||||
|
||||
const (
|
||||
FieldOperatorLT FieldOperator = "lt"
|
||||
FieldOperatorLTE FieldOperator = "lte"
|
||||
FieldOperatorGT FieldOperator = "gt"
|
||||
FieldOperatorGTE FieldOperator = "gte"
|
||||
FieldOperatorStartsWith FieldOperator = "startswith"
|
||||
FieldOperatorIStartsWith FieldOperator = "istartswith"
|
||||
FieldOperatorEndsWith FieldOperator = "endswith"
|
||||
FieldOperatorIEndsWith FieldOperator = "iendswith"
|
||||
FieldOperatorContains FieldOperator = "contains"
|
||||
FieldOperatorIContains FieldOperator = "icontains"
|
||||
FieldOperatorMatches FieldOperator = "matches"
|
||||
FieldOperatorNotContains FieldOperator = "notcontains"
|
||||
FieldOperatorINotContain FieldOperator = "inotcontains"
|
||||
FieldOperatorEq FieldOperator = "eq"
|
||||
FieldOperatorNeq FieldOperator = "neq"
|
||||
FieldOperatorIsNull FieldOperator = "isnull"
|
||||
FieldOperatorIncludes FieldOperator = "includes"
|
||||
FieldOperatorExcludes FieldOperator = "excludes"
|
||||
FieldOperatorLenEq FieldOperator = "len_eq"
|
||||
)
|
||||
|
||||
// BaseFieldDefinition describes a searchable chat-completion-event field.
|
||||
type BaseFieldDefinition struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Type FieldType `json:"type"`
|
||||
Group *string `json:"group,omitempty"`
|
||||
SupportedOperators []FieldOperator `json:"supported_operators"`
|
||||
}
|
||||
|
||||
// FieldGroup groups related field definitions for UI display.
|
||||
type FieldGroup struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
// ChatCompletionFields is the response of GET /v1/observability/chat-completion-fields.
|
||||
type ChatCompletionFields struct {
|
||||
FieldDefinitions []BaseFieldDefinition `json:"field_definitions"`
|
||||
FieldGroups []FieldGroup `json:"field_groups"`
|
||||
}
|
||||
|
||||
// ChatCompletionFieldOptions is the response of
|
||||
// GET /v1/observability/chat-completion-fields/{field_name}/options.
|
||||
//
|
||||
// Each option may be a string, bool, or null — preserved as raw any.
|
||||
type ChatCompletionFieldOptions struct {
|
||||
Options []any `json:"options"`
|
||||
}
|
||||
|
||||
// FieldOptionCountsRequest is the body of POST options-counts.
|
||||
type FieldOptionCountsRequest struct {
|
||||
FilterParams *FilterPayload `json:"filter_params,omitempty"`
|
||||
}
|
||||
|
||||
// FieldOptionCountItem pairs a field value with how many events have it.
|
||||
type FieldOptionCountItem struct {
|
||||
Value string `json:"value"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
// FieldOptionCounts is the response of POST options-counts.
|
||||
type FieldOptionCounts struct {
|
||||
Counts []FieldOptionCountItem `json:"counts"`
|
||||
}
|
||||
46
observability_fields.go
Normal file
46
observability_fields.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package mistral
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/VikingOwl91/mistral-go-sdk/observability"
|
||||
)
|
||||
|
||||
// GetChatCompletionFields returns the searchable field definitions and groups
|
||||
// for chat-completion observability events.
|
||||
func (c *Client) GetChatCompletionFields(ctx context.Context) (*observability.ChatCompletionFields, error) {
|
||||
var resp observability.ChatCompletionFields
|
||||
if err := c.doJSON(ctx, "GET", "/v1/observability/chat-completion-fields", nil, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetChatCompletionFieldOptions returns the distinct values seen for the given
|
||||
// field, filtered by the requested operator.
|
||||
func (c *Client) GetChatCompletionFieldOptions(ctx context.Context, fieldName string, operator observability.FieldOperator) (*observability.ChatCompletionFieldOptions, error) {
|
||||
q := url.Values{}
|
||||
q.Set("operator", string(operator))
|
||||
path := fmt.Sprintf("/v1/observability/chat-completion-fields/%s/options?%s", url.PathEscape(fieldName), q.Encode())
|
||||
var resp observability.ChatCompletionFieldOptions
|
||||
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetChatCompletionFieldOptionsCounts returns per-value event counts for the
|
||||
// given field, optionally filtered by the supplied filter payload.
|
||||
func (c *Client) GetChatCompletionFieldOptionsCounts(ctx context.Context, fieldName string, req *observability.FieldOptionCountsRequest) (*observability.FieldOptionCounts, error) {
|
||||
if req == nil {
|
||||
req = &observability.FieldOptionCountsRequest{}
|
||||
}
|
||||
path := fmt.Sprintf("/v1/observability/chat-completion-fields/%s/options-counts", url.PathEscape(fieldName))
|
||||
var resp observability.FieldOptionCounts
|
||||
if err := c.doJSON(ctx, "POST", path, req, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
100
observability_fields_test.go
Normal file
100
observability_fields_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package mistral
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/VikingOwl91/mistral-go-sdk/observability"
|
||||
)
|
||||
|
||||
func TestGetChatCompletionFields_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/v1/observability/chat-completion-fields" {
|
||||
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"field_definitions": []map[string]any{
|
||||
{
|
||||
"name": "model",
|
||||
"label": "Model",
|
||||
"type": "ENUM",
|
||||
"supported_operators": []string{"eq", "neq", "includes"},
|
||||
},
|
||||
},
|
||||
"field_groups": []map[string]any{
|
||||
{"name": "request", "label": "Request"},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("key", WithBaseURL(server.URL))
|
||||
resp, err := client.GetChatCompletionFields(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resp.FieldDefinitions) != 1 {
|
||||
t.Fatalf("got %d field definitions", len(resp.FieldDefinitions))
|
||||
}
|
||||
def := resp.FieldDefinitions[0]
|
||||
if def.Name != "model" || def.Type != observability.FieldTypeEnum {
|
||||
t.Errorf("unexpected field def: %+v", def)
|
||||
}
|
||||
if len(def.SupportedOperators) != 3 {
|
||||
t.Errorf("got %d operators", len(def.SupportedOperators))
|
||||
}
|
||||
if len(resp.FieldGroups) != 1 || resp.FieldGroups[0].Name != "request" {
|
||||
t.Errorf("unexpected groups: %+v", resp.FieldGroups)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChatCompletionFieldOptions_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/v1/observability/chat-completion-fields/model/options" {
|
||||
t.Errorf("got path %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("operator"); got != "eq" {
|
||||
t.Errorf("got operator=%q want eq", got)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"options": []any{"mistral-small-latest", "mistral-large-latest", nil, true},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("key", WithBaseURL(server.URL))
|
||||
resp, err := client.GetChatCompletionFieldOptions(context.Background(), "model", observability.FieldOperatorEq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resp.Options) != 4 {
|
||||
t.Fatalf("got %d options", len(resp.Options))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChatCompletionFieldOptionsCounts_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" || r.URL.Path != "/v1/observability/chat-completion-fields/model/options-counts" {
|
||||
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"counts": []map[string]any{
|
||||
{"value": "mistral-small-latest", "count": 42},
|
||||
{"value": "mistral-large-latest", "count": 17},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("key", WithBaseURL(server.URL))
|
||||
resp, err := client.GetChatCompletionFieldOptionsCounts(context.Background(), "model", &observability.FieldOptionCountsRequest{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resp.Counts) != 2 || resp.Counts[0].Count != 42 {
|
||||
t.Errorf("unexpected counts: %+v", resp.Counts)
|
||||
}
|
||||
}
|
||||
61
workflow/connectors.go
Normal file
61
workflow/connectors.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package workflow
|
||||
|
||||
// ConnectorSlot declares a connector dependency for a workflow execution.
|
||||
//
|
||||
// Pass a slice of slots to BuildConnectorExtensions to produce the
|
||||
// nested map expected on ExecutionRequest.Extensions.
|
||||
type ConnectorSlot struct {
|
||||
ConnectorName string `json:"connector_name"`
|
||||
CredentialsName *string `json:"credentials_name,omitempty"`
|
||||
}
|
||||
|
||||
// ConnectorBindings is the bindings list inside ConnectorExtensions.
|
||||
type ConnectorBindings struct {
|
||||
Bindings []ConnectorSlot `json:"bindings"`
|
||||
}
|
||||
|
||||
// ConnectorExtensions is the value of the "mistralai" key in workflow extensions.
|
||||
type ConnectorExtensions struct {
|
||||
Connectors ConnectorBindings `json:"connectors"`
|
||||
}
|
||||
|
||||
// WorkflowExtensions is the top-level shape of the extensions field
|
||||
// expected by the workflow execute endpoint when binding connectors.
|
||||
type WorkflowExtensions struct {
|
||||
Mistralai ConnectorExtensions `json:"mistralai"`
|
||||
}
|
||||
|
||||
// BuildConnectorExtensions returns the value to set on
|
||||
// ExecutionRequest.Extensions for the given connector slots.
|
||||
//
|
||||
// The result is a map[string]any so callers can merge in additional
|
||||
// extension keys without colliding with the connector wire shape.
|
||||
func BuildConnectorExtensions(slots ...ConnectorSlot) map[string]any {
|
||||
return map[string]any{
|
||||
"mistralai": ConnectorExtensions{
|
||||
Connectors: ConnectorBindings{Bindings: slots},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectorAuthStatus is the state of an OAuth flow emitted by a
|
||||
// connector-auth custom task event.
|
||||
type ConnectorAuthStatus string
|
||||
|
||||
const (
|
||||
ConnectorAuthWaitingForAuth ConnectorAuthStatus = "waiting_for_auth"
|
||||
ConnectorAuthConnected ConnectorAuthStatus = "connected"
|
||||
ConnectorAuthAccessDenied ConnectorAuthStatus = "access_denied"
|
||||
ConnectorAuthTimedOut ConnectorAuthStatus = "timed_out"
|
||||
ConnectorAuthError ConnectorAuthStatus = "error"
|
||||
)
|
||||
|
||||
// ConnectorAuthTaskState is the payload of a custom task event of type
|
||||
// "connector-auth", emitted while a workflow waits for OAuth completion.
|
||||
type ConnectorAuthTaskState struct {
|
||||
ConnectorName string `json:"connector_name"`
|
||||
ConnectorID string `json:"connector_id"`
|
||||
Status ConnectorAuthStatus `json:"status"`
|
||||
AuthURL *string `json:"auth_url,omitempty"`
|
||||
Message *string `json:"message,omitempty"`
|
||||
}
|
||||
@@ -25,6 +25,9 @@ type ExecutionRequest struct {
|
||||
TimeoutSeconds *float64 `json:"timeout_seconds,omitempty"`
|
||||
CustomTracingAttributes map[string]string `json:"custom_tracing_attributes,omitempty"`
|
||||
DeploymentName *string `json:"deployment_name,omitempty"`
|
||||
// Extensions carries plugin-specific data such as connector bindings.
|
||||
// Use BuildConnectorExtensions to construct the standard connector shape.
|
||||
Extensions map[string]any `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// ExecutionResponse is the response from a workflow execution.
|
||||
@@ -40,11 +43,20 @@ type ExecutionResponse struct {
|
||||
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
|
||||
}
|
||||
|
||||
// EncodedPayloadOption identifies how a workflow payload was encoded.
|
||||
type EncodedPayloadOption string
|
||||
|
||||
const (
|
||||
EncodedPayloadOffloaded EncodedPayloadOption = "offloaded"
|
||||
EncodedPayloadEncrypted EncodedPayloadOption = "encrypted"
|
||||
EncodedPayloadEncryptedPartial EncodedPayloadOption = "encrypted-partial"
|
||||
)
|
||||
|
||||
// NetworkEncodedInput holds a base64-encoded payload for workflow input.
|
||||
type NetworkEncodedInput struct {
|
||||
B64Payload string `json:"b64payload"`
|
||||
EncodingOptions []string `json:"encoding_options,omitempty"`
|
||||
Empty bool `json:"empty,omitempty"`
|
||||
B64Payload string `json:"b64payload"`
|
||||
EncodingOptions []EncodedPayloadOption `json:"encoding_options,omitempty"`
|
||||
Empty bool `json:"empty,omitempty"`
|
||||
}
|
||||
|
||||
// SignalInvocationBody is the request body for signaling a workflow execution.
|
||||
|
||||
12
workflow/worker.go
Normal file
12
workflow/worker.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package workflow
|
||||
|
||||
// WorkerInfo describes the worker scheduler the SDK is connected to.
|
||||
//
|
||||
// Returned by GET /v1/workflows/workers/whoami. Useful when running custom
|
||||
// workers that need to know which scheduler / namespace to connect to.
|
||||
// For managed deployments, prefer Registration.DeploymentID.
|
||||
type WorkerInfo struct {
|
||||
SchedulerURL string `json:"scheduler_url"`
|
||||
Namespace string `json:"namespace"`
|
||||
TLS bool `json:"tls"`
|
||||
}
|
||||
106
workflows_extensions_test.go
Normal file
106
workflows_extensions_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package mistral
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/VikingOwl91/mistral-go-sdk/conversation"
|
||||
"github.com/VikingOwl91/mistral-go-sdk/workflow"
|
||||
)
|
||||
|
||||
func TestNetworkEncodedInput_EncodingOptions(t *testing.T) {
|
||||
in := workflow.NetworkEncodedInput{
|
||||
B64Payload: "eyJrIjoidiJ9",
|
||||
EncodingOptions: []workflow.EncodedPayloadOption{workflow.EncodedPayloadOffloaded, workflow.EncodedPayloadEncrypted},
|
||||
}
|
||||
b, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got map[string]any
|
||||
if err := json.Unmarshal(b, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
opts, ok := got["encoding_options"].([]any)
|
||||
if !ok || len(opts) != 2 {
|
||||
t.Fatalf("unexpected encoding_options: %v", got["encoding_options"])
|
||||
}
|
||||
if opts[0] != "offloaded" || opts[1] != "encrypted" {
|
||||
t.Errorf("got %v, want [offloaded encrypted]", opts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildConnectorExtensions_WireShape(t *testing.T) {
|
||||
creds := "work-account"
|
||||
ext := workflow.BuildConnectorExtensions(
|
||||
workflow.ConnectorSlot{ConnectorName: "gmail"},
|
||||
workflow.ConnectorSlot{ConnectorName: "notion", CredentialsName: &creds},
|
||||
)
|
||||
b, err := json.Marshal(ext)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := `{"mistralai":{"connectors":{"bindings":[{"connector_name":"gmail"},{"connector_name":"notion","credentials_name":"work-account"}]}}}`
|
||||
if string(b) != want {
|
||||
t.Errorf("\nwant %s\ngot %s", want, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutionRequest_Extensions(t *testing.T) {
|
||||
req := workflow.ExecutionRequest{
|
||||
Extensions: workflow.BuildConnectorExtensions(workflow.ConnectorSlot{ConnectorName: "gmail"}),
|
||||
}
|
||||
b, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got map[string]any
|
||||
if err := json.Unmarshal(b, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := got["extensions"]; !ok {
|
||||
t.Fatalf("expected extensions key in marshalled request: %s", string(b))
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectorAuthTaskState_Roundtrip(t *testing.T) {
|
||||
authURL := "https://oauth.example.com/authorize"
|
||||
in := workflow.ConnectorAuthTaskState{
|
||||
ConnectorName: "gmail",
|
||||
ConnectorID: "conn-1",
|
||||
Status: workflow.ConnectorAuthWaitingForAuth,
|
||||
AuthURL: &authURL,
|
||||
}
|
||||
b, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var out workflow.ConnectorAuthTaskState
|
||||
if err := json.Unmarshal(b, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if out.Status != workflow.ConnectorAuthWaitingForAuth {
|
||||
t.Errorf("got status %q", out.Status)
|
||||
}
|
||||
if out.AuthURL == nil || *out.AuthURL != authURL {
|
||||
t.Errorf("auth_url roundtrip failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfirmationConstants_WireValues(t *testing.T) {
|
||||
// Reply constants.
|
||||
c := conversation.ToolCallConfirmation{
|
||||
ToolCallID: "call_1",
|
||||
Confirmation: string(conversation.ConfirmationAllow),
|
||||
}
|
||||
b, _ := json.Marshal(c)
|
||||
if string(b) != `{"tool_call_id":"call_1","confirmation":"allow"}` {
|
||||
t.Errorf("got %s", string(b))
|
||||
}
|
||||
// Inbound status constants.
|
||||
if conversation.ConfirmationStatusPending != "pending" ||
|
||||
conversation.ConfirmationStatusAllowed != "allowed" ||
|
||||
conversation.ConfirmationStatusDenied != "denied" {
|
||||
t.Errorf("unexpected confirmation status constants")
|
||||
}
|
||||
}
|
||||
20
workflows_workers.go
Normal file
20
workflows_workers.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package mistral
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/VikingOwl91/mistral-go-sdk/workflow"
|
||||
)
|
||||
|
||||
// GetWorkflowWorkerInfo returns the scheduler URL, namespace, and TLS setting
|
||||
// the API expects custom workers to connect with.
|
||||
//
|
||||
// Most callers using managed deployments do not need this — see
|
||||
// Registration.DeploymentID. It is exposed for users running custom workers.
|
||||
func (c *Client) GetWorkflowWorkerInfo(ctx context.Context) (*workflow.WorkerInfo, error) {
|
||||
var resp workflow.WorkerInfo
|
||||
if err := c.doJSON(ctx, "GET", "/v1/workflows/workers/whoami", nil, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
52
workflows_workers_test.go
Normal file
52
workflows_workers_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package mistral
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetWorkflowWorkerInfo_Success(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/v1/workflows/workers/whoami" {
|
||||
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"scheduler_url": "scheduler.example.com:7233",
|
||||
"namespace": "tenant-2",
|
||||
"tls": true,
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("key", WithBaseURL(server.URL))
|
||||
info, err := client.GetWorkflowWorkerInfo(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if info.SchedulerURL != "scheduler.example.com:7233" || info.Namespace != "tenant-2" || !info.TLS {
|
||||
t.Errorf("unexpected info: %+v", info)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWorkflowWorkerInfo_TLSDefault(t *testing.T) {
|
||||
// Server omits the tls field; the SDK should default it to false.
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"scheduler_url": "s",
|
||||
"namespace": "n",
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient("key", WithBaseURL(server.URL))
|
||||
info, err := client.GetWorkflowWorkerInfo(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if info.TLS {
|
||||
t.Errorf("expected default tls=false, got true")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user