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 ¶
- 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) Track(p TrackParams)
- func (c *Client) TrackAndWait(ctx context.Context, p TrackParams)
- type Config
- type DecideParams
- type Decision
- type GuardKind
- type GuardOutcome
- type OpenRouterPromptDetails
- type OpenRouterUsage
- type Outcome
- type ProviderCall
- type TrackParams
- type Usage
Constants ¶
This section is empty.
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) 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
Feature string
ExpectedUsage Usage
}
DecideParams asks about the call you are about to make.
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 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
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. |