Skip to main content
These are complete, copy-paste executors for the SDKs teams reach for most. Each wraps the SDK directly with createExecutor from @agentmark-ai/prompt-core — there’s no AgentMark adapter package to install or version to track. You own the code: copy the closest one, drop it in, adjust the model mapping, and you’re done. Every executor here takes the neutral render (formatted.text_config.model_name, formatted.messages, and for object prompts formatted.object_config.schema) and returns { text | object, usage }. The builder turns that into AgentMark’s wire protocol for you. If your SDK isn’t listed, the shape is identical — see Connect your SDK for the full guide.

Vercel AI SDK

Wraps the ai package’s generateText / streamText (and generateObject for structured output). This example does text with both one-shot and streaming paths:
import { createExecutor } from "@agentmark-ai/prompt-core";
import { generateText, streamText, generateObject, jsonSchema } from "ai";
import { openai } from "@ai-sdk/openai";

// Minimal model resolution — strip an optional "openai/" prefix. Swap in your
// own provider map (anthropic, google, …) keyed off the model name as needed.
const model = (name: string) => openai(name.replace(/^openai\//, ""));

export const executor = createExecutor({
  name: "vercel-ai-sdk",
  text: async (formatted, ctx) => {
    const { text, usage } = await generateText({
      model: model(formatted.text_config.model_name),
      messages: formatted.messages,
      abortSignal: ctx.signal,
    });
    // AI SDK v5 usage is already canonical ({ inputTokens, outputTokens }).
    // On v4 it's { promptTokens, completionTokens } — rename those two.
    return { text, usage: { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens } };
  },
  streamText: async function* (formatted, ctx) {
    const result = streamText({
      model: model(formatted.text_config.model_name),
      messages: formatted.messages,
      abortSignal: ctx.signal,
    });
    for await (const delta of result.textStream) yield { type: "text-delta", text: delta };
    const usage = await result.usage;
    yield { type: "finish", reason: "stop", usage: { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens } };
  },
  object: async (formatted, ctx) => {
    const { object, usage } = await generateObject({
      model: model(formatted.object_config.model_name),
      messages: formatted.messages,
      schema: jsonSchema(formatted.object_config.schema),
      abortSignal: ctx.signal,
    });
    return { object, usage: { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens } };
  },
});

OpenAI (raw SDK)

The official openai package — chat.completions.create. Messages map straight through; structured output uses JSON-schema response format:
import { createExecutor } from "@agentmark-ai/prompt-core";
import OpenAI from "openai";

const openai = new OpenAI();
const model = (name: string) => name.replace(/^openai\//, "");

export const executor = createExecutor({
  name: "openai",
  text: async (formatted, ctx) => {
    const res = await openai.chat.completions.create(
      { model: model(formatted.text_config.model_name), messages: formatted.messages },
      { signal: ctx.signal },
    );
    return {
      text: res.choices[0].message.content ?? "",
      finishReason: res.choices[0].finish_reason,
      usage: { inputTokens: res.usage?.prompt_tokens ?? 0, outputTokens: res.usage?.completion_tokens ?? 0 },
    };
  },
  object: async (formatted, ctx) => {
    const res = await openai.chat.completions.create(
      {
        model: model(formatted.object_config.model_name),
        messages: formatted.messages,
        response_format: {
          type: "json_schema",
          json_schema: { name: "response", schema: formatted.object_config.schema, strict: true },
        },
      },
      { signal: ctx.signal },
    );
    return {
      object: JSON.parse(res.choices[0].message.content ?? "{}"),
      usage: { inputTokens: res.usage?.prompt_tokens ?? 0, outputTokens: res.usage?.completion_tokens ?? 0 },
    };
  },
});

Anthropic (raw SDK)

The @anthropic-ai/sdk messages.create. Anthropic takes system as a top-level field and requires max_tokens, so split the system message out of the neutral render:
import { createExecutor } from "@agentmark-ai/prompt-core";
import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic();
const model = (name: string) => name.replace(/^anthropic\//, "");

export const executor = createExecutor({
  name: "anthropic",
  text: async (formatted, ctx) => {
    const system = formatted.messages.filter((m) => m.role === "system").map((m) => m.content).join("\n");
    const messages = formatted.messages.filter((m) => m.role !== "system");
    const res = await anthropic.messages.create(
      {
        model: model(formatted.text_config.model_name),
        max_tokens: formatted.text_config.max_tokens ?? 1024,
        system,
        messages: messages as Anthropic.MessageParam[],
      },
      { signal: ctx.signal },
    );
    const text = res.content.map((b) => (b.type === "text" ? b.text : "")).join("");
    return { text, usage: { inputTokens: res.usage.input_tokens, outputTokens: res.usage.output_tokens } };
  },
});

Python (OpenAI)

The same shape in Python: a create_executor handler returning an ExecutorTextResult with a canonical UsageData.
from agentmark.prompt_core import create_executor, ExecutorTextResult, UsageData
from openai import OpenAI

client = OpenAI()

def _model(name: str) -> str:
    return name.removeprefix("openai/")

def _text(formatted, ctx) -> ExecutorTextResult:
    res = client.chat.completions.create(
        model=_model(formatted["text_config"]["model_name"]),
        messages=formatted["messages"],
    )
    usage = res.usage
    return ExecutorTextResult(
        text=res.choices[0].message.content or "",
        usage=UsageData(input_tokens=usage.prompt_tokens, output_tokens=usage.completion_tokens),
    )

executor = create_executor(name="openai", text=_text)
Pass object= for structured output and stream_text= / stream_object= async generators for streaming — the same handler set as TypeScript. See Connect your SDK for the streaming and object handlers.

Agent frameworks (Pydantic AI, Mastra, Claude Agent SDK)

Agent frameworks follow the identical shape — the only difference is that your handler runs an agent loop instead of a single completion. Feed the neutral render’s messages into your agent, run it, and return its final output plus token usage:
import { createExecutor } from "@agentmark-ai/prompt-core";

export const executor = createExecutor({
  name: "my-agent-framework",
  text: async (formatted, ctx) => {
    // Build + run your agent (Pydantic AI Agent.run, a Mastra Agent, Claude's
    // query(), …) with formatted.messages, honoring ctx.signal for cancellation.
    const result = await runMyAgent(formatted.messages, { signal: ctx.signal });
    return { text: result.output, usage: { inputTokens: result.inputTokens, outputTokens: result.outputTokens } };
  },
});
For token-by-token output and tool-call/tool-result events, use a streamText handler and yield text-delta / tool-call / tool-result events as the agent emits them — see the streaming section of Connect your SDK.

Serve it

Every executor above plugs into the same wiring. Build a runner, register your evals there (so they both run in experiments and list in the New Experiment dialog), and the deployed managed handler is one line:
import { createWebhookRunner } from "@agentmark-ai/sdk";

const runner = createWebhookRunner({ executor, loader, evals: myEvals });
export default (body) => runner.dispatch(body);
See Connect your SDK, Path B for serving locally vs. a managed deploy.

Validate whatever you build

Gate any executor — copied or hand-written — with the conformance suite. One call confirms a protocol-correct stream for every kind, streaming and one-shot, including the error path:
import { runExecutorConformance } from "@agentmark-ai/prompt-core";

await runExecutorConformance(executor, {
  text: { messages: [{ role: "user", content: "hello" }], text_config: { model_name: "openai/gpt-4o" } },
  object: { messages: [{ role: "user", content: "give me JSON" }], object_config: { model_name: "openai/gpt-4o" } },
  errorInput: { messages: null },
});
See Validate your executor for the Python equivalent and the full pattern.

Have Questions?

We’re here to help! Choose the best way to reach us: