> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bitfab.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Jev

> Record the model, token usage, and duration of Jev calls with Bitfab

Bitfab records calls to [Jev](https://typesafe.ai), TypeSafe's System One model, the way it records BAML calls: the model, provider, token counts, and duration land in the span the call belongs to, with no manual `addContext` calls. Under an opt-out trace root, Jev calls are recorded on their own, whichever client makes them ([see below](#under-a-trace-root-opt-out-tracing)). Anywhere else, wrap the function that calls Jev with `wrapJev` / `wrap_jev` / `WrapJev`.

## Supported Languages

| Language | Wrapper | Recorded on its own under a trace root |
| - | - | :-: |
| TypeScript | `wrapJev()` | ✅ calls to `systemOne` |
| Python | `wrap_jev()` | ✅ calls to `system_one` |
| Ruby | `wrap_jev()` | ✅ calls to `system_one` |
| Go | `bitfab.WrapJev()` | ✅ calls to `SystemOne` |

Under a trace root, Bitfab recognizes a Jev call by the method's name and the reply's shape, so it works with TypeSafe's official clients (`@typesafe-ai/sdk`, `typesafe-sdk`), third-party clients, and your own.

## What Gets Captured

Each call adds one entry to the span's contexts, read from the reply Jev returns (`{ model, answers, usage: { input_tokens, output_tokens } }`):

| Data | Span Field | Source |
| - | - | - |
| Model name | `span_data.contexts[].model` | Reply `model` |
| Provider | `span_data.contexts[].provider` | Always `typesafe` |
| Input tokens | `span_data.contexts[].inputTokens` | Reply `usage.input_tokens`, left out when Jev did not report it |
| Output tokens | `span_data.contexts[].outputTokens` | Reply `usage.output_tokens`, left out when Jev did not report it |
| Duration | `span_data.contexts[].durationMs` | Time from the call starting to the reply arriving |

A call that fails passes its error through unchanged and records nothing.

## Under a trace root (opt-out tracing)

Beneath a `withTrace()` / `trace()` / `bitfab_trace` / `Client.Trace` root, Jev calls are recorded without a wrapper. A call to a method named `systemOne` (Python and Ruby: `system_one`, Go: `SystemOne`) becomes its own `llm` span, named after the client and method the way other recorded calls are (for example `TypeSafeClient.systemOne`). When the reply looks like Jev's, with a text `model` and a `usage` object, the span carries the fields above. Any other reply leaves the span without them.

```typescript TypeScript theme={null}
import { Bitfab } from "@bitfab/sdk"
import { noul, TypeSafeClient } from "@typesafe-ai/sdk"

const bitfab = new Bitfab({ apiKey: process.env.BITFAB_API_KEY })
const jev = new TypeSafeClient()

const screenTicket = bitfab.withTrace("screen-ticket", async (text: string) => {
  const { answers } = await jev.systemOne({
    state: text,
    questions: { billing: noul("Is this about billing?") },
  })
  return answers.billing.noul > 0.5
})
```

```python Python theme={null}
import os
from bitfab import Bitfab
from typesafe_sdk import AsyncTypeSafeClient

bitfab = Bitfab(api_key=os.environ["BITFAB_API_KEY"])
jev = AsyncTypeSafeClient()

@bitfab.trace("screen-ticket")
async def screen_ticket(text: str):
    reply = await jev.system_one(
        state=text,
        questions={"billing": {"type": "noul", "instructions": "Is this about billing?"}},
    )
    return reply.answers["billing"].noul > 0.5
```

* **TypeScript** needs the SDK's build transform in the server build, the same requirement as the rest of opt-out tracing. The transform rewrites `x.systemOne(...)` calls in your own files, including optional chaining, so it works in bundled apps (Next.js with Turbopack, for example) with nothing extra to import. The call still returns the client's own result, so `@typesafe-ai/sdk`'s `.withResponse()` keeps working. A method taken out first (`const ask = jev.systemOne.bind(jev)`) is not recognized; wrap it with `wrapJev` instead.
* **Python** needs Python 3.12+, as opt-out tracing does. Your own `system_one` functions are recorded, and the official `typesafe_sdk` clients are recorded whether they are imported before or after the root starts.
* **Ruby** needs Ruby 3.4+, as opt-out tracing does. `system_one` is recorded in your own code and in gems; other methods in those gems stay unrecorded.
* **Go** needs `bitfab-instrument`, as opt-out tracing does. It rewrites `x.SystemOne(ctx, ...)` calls whose method takes a `context.Context` first and returns `(reply, error)`, including clients from other packages and calls through an interface. A method value taken first (`ask := c.SystemOne`) is not recognized; wrap it with `bitfab.WrapJev` instead.
* **`exclude` matches the method name** (`systemOne` / `system_one` / `SystemOne`). Excluding it runs the call without recording it. Replay mocking follows the root's `mockOnReplay` / `mock_on_replay` default.
* **Usage is recorded once.** A wrapper around a recorded call adds nothing, and when one `systemOne` calls another (your own client calling TypeSafe's, say), the usage lands on the inner call.
* **`node()` / `bitfab_node` / `Node` on the method configures that call's one span** (Python, Ruby, Go): its name, replay mocking, and other options. Its type stays `llm` unless the node sets one. In TypeScript, `withNode` does not reach a recognized Jev call: the call's span keeps its own name and settings.

```python Python theme={null}
class JevClient:
    @bitfab.node(name="score-chunk")
    async def system_one(self, **request):
        return await post_to_jev(request)
```

```go Go theme={null}
client.Node((*jevclient.Client).SystemOne, bitfab.NodeOptions{Name: "score-chunk"})
```

* Outside a trace root, calls run exactly as before and record nothing.

## Wrapping a Jev call

`wrapJev` / `wrap_jev` / `WrapJev` wraps any function that calls Jev and returns its reply: the official client's `systemOne` / `system_one`, or your own function that sends the request and returns the JSON body. Like `wrapBAML`, it opens no span of its own. It writes into the span that is current when the call starts, so call it inside a traced function.

```typescript TypeScript theme={null}
const askJev = bitfab.wrapJev(jev.systemOne.bind(jev), {
  onReply: (reply) => reportCost(reply.usage),
})

const scoreChunk = bitfab.withSpan("score-chunk", async (excerpt: string) => {
  const { answers } = await askJev({ state: excerpt, questions })
  return answers
})
```

```python Python theme={null}
ask_jev = bitfab.wrap_jev(jev.system_one, on_reply=lambda reply: report_cost(reply.usage))

@bitfab.span("score-chunk")
async def score_chunk(excerpt: str):
    reply = await ask_jev(state=excerpt, questions=questions)
    return reply.answers
```

```ruby Ruby theme={null}
ask_jev = bitfab.wrap_jev(on_reply: ->(reply) { report_cost(reply["usage"]) }) do |excerpt|
  post_to_jev(state: excerpt, questions: questions)
end

ask_jev.call(excerpt)
```

```go Go theme={null}
askJev := bitfab.WrapJev(
	func(ctx context.Context, req JevRequest) (JevReply, error) {
		return postToJev(ctx, req)
	},
	bitfab.WithOnReply(func(reply JevReply) { reportCost(reply.Usage) }),
)

reply, err := askJev(ctx, JevRequest{State: excerpt, Questions: questions})
```

### Signatures

```typescript TypeScript theme={null}
bitfab.wrapJev(method: (...args) => Reply | Promise<Reply>, options?: WrapJevOptions<Reply>): (...args) => Promise<Reply>
bitfab.getFunction(key).wrapJev(method, options?)
```

* `method` must be a named function (bind the client's method: `jev.systemOne.bind(jev)`).
* `options.onReply?: (reply) => void` is called with each reply after it is recorded.

```python Python theme={null}
bitfab.wrap_jev(fn, *, on_reply=None)
bitfab.get_function(key).wrap_jev(fn, *, on_reply=None)
```

* `fn` may be sync or async and must have a name. The reply may be a dict or an object with `model` and `usage` attributes, such as the official client's `SystemOneResponse`.

```ruby Ruby theme={null}
bitfab.wrap_jev(callable = nil, on_reply: nil, &block)
bitfab.get_function(key).wrap_jev(...)
```

* Pass a callable or a block, not both. The reply may be a Hash with string or symbol keys, or an object that responds to `model` and `usage`.

```go Go theme={null}
func WrapJev[Req, Reply any](call func(context.Context, Req) (Reply, error), opts ...WrapJevOption[Reply]) func(context.Context, Req) (Reply, error)
func WithOnReply[Reply any](fn func(Reply)) WrapJevOption[Reply]
```

* The span is the one current in the `ctx` passed to the call. The reply may be a struct with `json` tags, a pointer to one, a `map[string]any`, a `json.RawMessage`, or a `[]byte` or `string` holding the JSON body.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.