Documentation
¶
Overview ¶
Package marginfuse is the server-side SDK for MarginFuse: profitability guardrails for AI SaaS. Connect revenue to per-request AI cost, see gross margin per customer, and stop loss-making requests before they run.
Reliability contract: this SDK never panics into application code and never blocks a request on MarginFuse availability. Decide fails open to ActionAllow on any timeout or error; Track and Acknowledge retry in the background and surface problems only through Config.OnError.
Server side only: it carries a secret API key.
Index ¶
- Constants
- type Acknowledgment
- type Action
- type Client
- func (c *Client) Acknowledge(decisionID string, ack Acknowledgment)
- func (c *Client) Close()
- func (c *Client) Decide(ctx context.Context, p DecideParams) Decision
- func (c *Client) Flush(ctx context.Context)
- func (c *Client) Guard(ctx context.Context, p DecideParams, ...) (GuardOutcome, error)
- func (c *Client) Identify(ctx context.Context, p IdentifyParams) (Identity, error)
- func (c *Client) Track(p TrackParams)
- func (c *Client) TrackAndWait(ctx context.Context, p TrackParams)
- type Config
- type DecideParams
- type Decision
- type GuardKind
- type GuardOutcome
- type IdentifyParams
- type Identity
- type OpenRouterPromptDetails
- type OpenRouterUsage
- type Outcome
- type ProviderCall
- type TrackParams
- type Usage
Constants ¶
const ContractVersion = 2
ContractVersion is the version of the shared SDK contract this build was verified against.
Module versions differ per language, because each tracks its own breaking changes: a rename in Python must not tell Go users something broke. What makes the SDKs interchangeable is this, not the module version. Two SDKs reporting the same contract version have passed the same scenarios and the same vectors.
See github.com/marginfuse/sdk-contract.
const Version = "0.3.0"
Version is the released version of this module, as sent in the user-agent.
A Go module has no manifest to read this from: the version is the git tag, and the module proxy caches a tag permanently the first time anyone fetches it, so a wrong one cannot be corrected in place. The release workflow therefore refuses to publish a tag that disagrees with this constant.
The Node SDK shipped two releases reporting 0.1.0 because it had a literal like this one with nothing checking it.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Acknowledgment ¶
type Acknowledgment string
Acknowledgment is what the application actually did with a decision.
const ( AckProceededAsRequested Acknowledgment = "proceeded_as_requested" AckUsedDowngradeModel Acknowledgment = "used_downgrade_model" AckPresentedTopup Acknowledgment = "presented_topup" AckBlockedBeforeProviderCall Acknowledgment = "blocked_before_provider_call" AckFailedToApply Acknowledgment = "failed_to_apply" )
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is safe for concurrent use.
func New ¶
New returns a Client. It returns an error only for a missing API key, because that is a programming mistake rather than a runtime condition.
func (*Client) Acknowledge ¶
func (c *Client) Acknowledge(decisionID string, ack Acknowledgment)
Acknowledge tells MarginFuse what your application did with a decision.
func (*Client) Decide ¶
func (c *Client) Decide(ctx context.Context, p DecideParams) Decision
Decide asks whether the next call should run. It always returns a verdict.
There is no error return on purpose. A failed decision is not a condition the caller should branch on: it is an allow with Degraded set, because MarginFuse being unreachable must never become your outage. Transport failures go to Config.OnError.
func (*Client) Flush ¶
Flush waits for queued events and acknowledgments. It never panics, and it returns when ctx is done even if work is still in flight.
func (*Client) Guard ¶
func (c *Client) Guard( ctx context.Context, p DecideParams, run func(context.Context, Decision) (ProviderCall, error), ) (GuardOutcome, error)
Guard runs the whole loop: ask, run, report, acknowledge.
run receives the decision and must return what the call consumed. Use decision.Model: a downgrade verdict changes it.
It takes a callback rather than returning a decision for you to act on, because enforcement must not depend on the caller remembering to check anything. When the verdict is block, run is never invoked.
An error from run is returned unchanged: your error handling owns provider failures. The attempt is recorded before it is returned, because the provider may still have charged for it.
func (*Client) Identify ¶ added in v0.2.0
Identify tells MarginFuse who a customer is and what plan they are on.
Plan is the key of a plan you declared in MarginFuse Settings, not a Stripe price id. MarginFuse derives that customer's revenue from the plan's price for every cycle, which is what makes margin per customer and margin policies work with no revenue source connected. Those figures are labeled as a declared price wherever they appear, because nobody confirmed collection.
Safe to call on every sign-in: sending the plan the customer is already on changes nothing. Sending a different one ends the current cycle at that moment and prorates what accrued.
This is the one call that returns an error, and the only one that should. Decide fails open and Track retries, because both have a safe default; "I could not record what this customer pays" has none, and a wrong plan is a wrong margin. The error also goes to Config.OnError.
func (*Client) Track ¶
func (c *Client) Track(p TrackParams)
Track reports a call that already happened. It returns immediately and sends in the background with retries.
Call Flush before a process exits, or the last events go with it.
func (*Client) TrackAndWait ¶
func (c *Client) TrackAndWait(ctx context.Context, p TrackParams)
TrackAndWait is Track for jobs and scripts that must not exit early.
type Config ¶
type Config struct {
// APIKey is your project API key. Required.
APIKey string
// BaseURL points at your own deployment in development.
BaseURL string
// Timeout is how long Decide waits before failing open. Default 1.5s.
Timeout time.Duration
// OnError receives transport failures the SDK swallowed. Without it they
// are silent by design: this SDK is in your request path and must not
// become your outage.
OnError func(err error, context string)
// HTTPClient replaces the default. Useful for proxies and test doubles.
HTTPClient *http.Client
}
Config configures a Client. Every field except APIKey has a usable zero value.
type DecideParams ¶
type DecideParams struct {
CustomerID string
Provider string
Model string
Plan string
Feature string
ExpectedUsage Usage
}
DecideParams asks about the call you are about to make.
Plan is the key of a plan you declared in MarginFuse. It is a hint: a key that does not resolve is ignored rather than failing the decision.
type Decision ¶
type Decision struct {
ID string `json:"id,omitempty"`
Action Action `json:"action"`
Model string `json:"model"`
Provider string `json:"provider"`
TopupContext string `json:"topupContext,omitempty"`
Degraded bool `json:"degraded"`
DegradedReason string `json:"degradedReason,omitempty"`
}
Decision is a verdict from MarginFuse.
Degraded is true when MarginFuse could not reach a verdict and the request was allowed through unprotected. ID is empty in that case, which is exactly why enforcement must depend on Action alone.
type GuardOutcome ¶
GuardOutcome is the result of the whole loop.
type IdentifyParams ¶ added in v0.2.0
type IdentifyParams struct {
CustomerID string
Plan string
ClearPlan bool
PeriodStart time.Time
Name string
Email string
Metadata map[string]string
}
IdentifyParams says who a customer is and what plan they pay for.
Plan is the key of a plan you declared in MarginFuse Settings, not a Stripe price id. Leave it empty to change nothing about the plan; set ClearPlan to take the customer off plans entirely. PeriodStart backdates the cycle for a customer who has been paying since an earlier date.
type Identity ¶ added in v0.2.0
type Identity struct {
CustomerID string `json:"customerId"`
Plan string `json:"plan"`
PeriodStart string `json:"periodStart,omitempty"`
PeriodEnd string `json:"periodEnd,omitempty"`
}
Identity is what MarginFuse recorded for a customer.
Plan is empty when the customer is on none.
type OpenRouterPromptDetails ¶
type OpenRouterPromptDetails struct {
CachedTokens float64 `json:"cached_tokens"`
CacheWriteTokens float64 `json:"cache_write_tokens"`
AudioTokens float64 `json:"audio_tokens"`
}
OpenRouterPromptDetails is the cache breakdown inside prompt_tokens.
type OpenRouterUsage ¶
type OpenRouterUsage struct {
PromptTokens float64 `json:"prompt_tokens"`
CompletionTokens float64 `json:"completion_tokens"`
Cost *float64 `json:"cost"`
PromptTokensDetails *OpenRouterPromptDetails `json:"prompt_tokens_details"`
}
OpenRouterUsage is the shape this helper reads from an OpenRouter usage object. Structural on purpose: it accepts a decoded response from any client without either side importing the other's types.
type ProviderCall ¶
ProviderCall is what your callback did, handed back to Guard so it can be reported.
CostUSD is a decimal string, not a float: money that round-trips through a float stops being the number the provider charged.
type TrackParams ¶
type TrackParams struct {
EventID string
CustomerID string
Provider string
Model string
Plan string
Feature string
RequestedModel string
Usage Usage
CostUSD string
OccurredAt time.Time
Outcome Outcome
DecisionID string
RetryOfEventID string
CorrectsEventID string
}
TrackParams reports a call that already happened.
EventID is the idempotency key. Leave it empty and one is generated; set it yourself when you already have an id you can retry with safely.
type Usage ¶
type Usage struct {
InputTokens int `json:"inputTokens,omitempty"`
OutputTokens int `json:"outputTokens,omitempty"`
CachedInputTokens int `json:"cachedInputTokens,omitempty"`
CacheCreationTokens int `json:"cacheCreationTokens,omitempty"`
Images int `json:"images,omitempty"`
AudioSeconds float64 `json:"audioSeconds,omitempty"`
}
Usage is what a provider call consumed.
Zero means not reported, not "used none": the field is omitted from the request entirely, because claiming a call used zero input tokens is a different statement from not knowing what it used. Report what you have.
func FromOpenRouter ¶
func FromOpenRouter(u *OpenRouterUsage) (Usage, string)
FromOpenRouter maps an OpenRouter usage object to the MarginFuse fields.
var body struct{ Usage marginfuse.OpenRouterUsage `json:"usage"` }
json.Unmarshal(raw, &body)
usage, cost := marginfuse.FromOpenRouter(&body.Usage)
mf.Track(marginfuse.TrackParams{..., Usage: usage, CostUSD: cost})
It exists because mapping the fields by hand gets two things silently wrong, and both misstate margin without producing an error anywhere:
First, prompt_tokens is the TOTAL input count. Cached reads and cache writes are already inside it, and MarginFuse prices those as three separate charges and adds them up, so passing the total straight through charges every cached token twice at the full uncached rate.
Second, cost is a float, and strconv.FormatFloat with 'g' renders small ones in exponent notation ("1.2e-07"), which the API rejects as a decimal string.
The returned cost is empty when the response carried none, which lets the event fall through to MarginFuse's own pricing instead of claiming a $0 charge.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
conformance-runner
command
Command conformance-runner drives this SDK through one shared conformance scenario.
|
Command conformance-runner drives this SDK through one shared conformance scenario. |