Skip to main content

Client-side meander.plan SDK: transport setup + a form-checked plan onto a real OTel span, with thin Claude/OpenAI bindings. Imports meander NOT.

Project description

meander-agent

Kundenseitiges SDK für die Plan-Sende-Seite von meander. Dein Agent hält fest, was er getan hat, als nachvollziehbare Behauptung mit Herkunft, und stellt GENAU EINE Frage an eine Regel deiner Ontologie. Das Paket baut daraus ein meander.plan, prüft dessen FORM und hängt es an einen echten OpenTelemetry-Span, den dein OTLP-Export zu meander trägt. Kein meander-Import, keine Ontologie im Client.

Das Modell in einem Absatz

Ein meander.plan ist eine Behauptung plus eine Frage, keine Entscheidung:

  • facts — was der Agent beobachtet/eingeschätzt hat. Jeder Fakt trägt SEINE Herkunft (origin.kind: tool | api | human | agent). Ein Tool-Ergebnis ist tool, die eigene Einschätzung des Agenten ist agent.
  • relations — behauptete Beziehungen zwischen Entities (optional).
  • question — GENAU EINE Frage an eine bestehende Derivation (Regel) deiner Welt, z.B. „gilt hier requires_review?". Der Agent entscheidet nicht; ob die Regel greift, bestimmt meander serverseitig, und ein Mensch prüft.

Der Client prüft nur die FORM (Pflichtfelder, Typen, genau eine Frage, origin-Vokabular, plan_version == 1). Ob die Entities/Properties/Derivations wirklich existieren, weiß nur der Server (er kennt deine Ontologie). Darum importiert dieses Paket meander NICHT und lädt keine Ontologie.

Garantien (nie stilles Nichtstun): ein Fehlerlauf hängt NIE ein Attribut an; ein nicht mehr beschreibbarer Span verweigert das Attribut laut (AttachResult(ok=False, span_not_recording)); ein formfehlerhafter Plan wird mit Feldpfad gemeldet, statt halb geschrieben zu werden.

Installation

pip install meander-agent            # Transport + Emitter (schlank, ohne LLM-SDK)
pip install "meander-agent[claude]"  # + Claude-Agent-SDK-Binding
pip install "meander-agent[openai]"  # + OpenAI-Agents-SDK-Binding

1. Dein Vokabular deklarieren

Du gibst dem Agenten die Namen deiner Welt (er soll nichts erfinden). Je Entity ihre identity-Felder und Properties, je Relation ihre Endpunkte, plus die Derivation-IDs, die er befragen darf:

vocabulary = {
    "entities": {
        "Order":   {"identity": ["order_id"], "properties": ["amount", "risk"]},
        "Case":    {"identity": ["case_id"],  "properties": []},
        "Outcome": {"identity": ["outcome_id"], "properties": []},
    },
    "relations": {
        "concerns": {"from": "Case", "to": "Order"},
    },
    "derivation_ids": ["requires_review.high_value"],
}

Daraus erzeugt das Paket den Prompt-Baustein (nennt dem Modell nur diese Namen) und das JSON-Schema der strukturierten Ausgabe (erlaubt nur diese Namen).

2. Transport einrichten

Ohne eigenes OTel genügt ein Aufruf. endpoint ist die volle OTLP-Traces-URL deiner Source, source_key das Bearer-Token dieser Source:

from meander_agent import init_meander

client = init_meander(
    endpoint="https://<host>/api/sources/<source_id>/v1/traces",
    source_key="<bearer-token>",
)
# client.tracer  -> der verdrahtete OTel-Tracer
# client.shutdown() / client.force_flush()  -> Export abschließen

init_meander setzt KEINEN globalen Provider; der Client hält seinen eigenen. Hast du bereits ein OTel-Setup, lass init_meander weg und übergib deinen Tracer direkt (siehe Abschnitt 5).

3. Einen Agenten-Run fahren (Claude)

from meander_agent.claude import run_with_plan

result = run_with_plan(
    "Bearbeite Bestellung ORD-42. Rufe die üblichen Tools auf, behaupte die "
    "gewonnenen Fakten mit ihrer Herkunft und stelle GENAU EINE Frage an die "
    "Derivation requires_review.high_value. Triff KEINE Entscheidung.",
    vocabulary=vocabulary,
    tracer=client.tracer,
)
print("angehängt" if result.attached else f"kein Plan gesetzt: {result.error_state}")
client.shutdown()   # Span exportieren

Das Binding öffnet den Root-Span, fährt das Modell mit strukturierter Ausgabe, sperrt bei Fehler/Abbruch und hängt bei Erfolg den geprüften Plan an. Es gibt ein RunResult zurück (siehe Abschnitt 4). Braucht das [claude]-Extra und einen ANTHROPIC_API_KEY.

3b. Derselbe Run über OpenAI

Identischer Aufruf, anderes Binding (Extra [openai], OPENAI_API_KEY):

from meander_agent.openai import run_with_plan

result = run_with_plan(task, vocabulary=vocabulary, tracer=client.tracer)

Beide Bindings gibt es auch async (run_with_plan_async).

4. Was du zurückbekommst

RunResult:

  • attached: bool — wurde meander.plan an den Span gehängt?
  • plan: dict | None — der angehängte Plan (bei Erfolg).
  • shape_errors: list[ShapeError] — Formfehler mit path / code / message, falls die Ausgabe die FORM-Prüfung nicht bestand.
  • error_stateNone bei Erfolg; sonst der Fehlerausgang des Laufs (SDK-Fehler, Abbruch, Typ-Mismatch). Ist er gesetzt, wird NIE angehängt.

Kein meander.plan heißt also immer: entweder ein Fehlerlauf (error_state) oder eine formfehlerhafte Ausgabe (shape_errors) — beides steht im Ergebnis, nichts verschwindet still.

5. Eigenes Tracing / eigenes SDK (der Kern)

Willst du dein eigenes SDK anbinden (oder deinen eigenen Tracer nutzen), fährst du den SDK-neutralen Kontextmanager selbst. Er liefert dir den Baustein und das Schema und übernimmt Parsen, Formprüfung, Sperren und Anhängen:

from meander_agent import meander_run

with meander_run(vocabulary=vocabulary, tracer=my_tracer) as run:
    # run.prompt_fragment : Format + erlaubtes Vokabular -> als Instruktion ans SDK
    # run.output_schema   : JSON-Schema, falls dein SDK strukturierte Ausgabe kann
    output, error = call_your_llm(task, instructions=run.prompt_fragment,
                                  schema=run.output_schema)
    # output: das strukturierte Ergebnis (dict) ODER ein reiner JSON-String.
    # error_state: None bei Erfolg, sonst ein beliebiges Detail (=> Sperre).
    result = run.finalize(output, error_state=error)

Der Kern übergibt nie selbst Prompts ans SDK und liest nie SDK-Resultate — das ist Sache deines Bindings. Genauso arbeiten die mitgelieferten Claude-/OpenAI-Bindings.

6. Deterministischer Plan ohne LLM

Baust du den Plan selbst (Tests, regelbasierte Agenten, auth-loser Pfad), nutzt du den Emitter direkt:

from meander_agent import attach_plan, validate_plan_shape

plan = {
    "plan_version": 1,
    "facts": [
        {"entity": "Order", "identity": {"order_id": "ORD-42"},
         "property": "amount", "value": 900,
         "origin": {"kind": "tool", "ref": "lookup_order"}},
    ],
    "relations": [
        {"relation": "concerns",
         "from": {"entity": "Case", "identity": {"case_id": "C-1"}},
         "to":   {"entity": "Order", "identity": {"order_id": "ORD-42"}},
         "origin": {"kind": "agent"}},
    ],
    "question": {
        "derivation_id": "requires_review.high_value",
        "subject": {"entity": "Case", "identity": {"case_id": "C-1"}},
    },
}

errors = validate_plan_shape(plan)            # reine FORM-Prüfung (leer = ok)
with client.tracer.start_as_current_span("agent.run") as span:
    res = attach_plan(span, plan)             # hängt bei Formgültigkeit an
    if not res.ok:
        print("nicht angehängt:", [e.as_dict() for e in res.errors])
client.shutdown()

Öffentliche Fläche

Symbol Zweck
init_meander(endpoint, source_key) -> MeanderClient Transport (OTel + OTLP) einrichten
MeanderClient.tracer / .run(vocabulary=…) / .force_flush() / .shutdown() Tracer, Kern-Zucker, Export
meander_agent.claude.run_with_plan[_async](task, *, vocabulary, tracer) Claude-Binding
meander_agent.openai.run_with_plan[_async](task, *, vocabulary, tracer) OpenAI-Binding
meander_run(vocabulary, tracer) -> Run SDK-neutraler Kern-Kontextmanager
Run.prompt_fragment / .output_schema / .finalize(output, error_state) Bausteine + Verarbeitung
attach_plan(span, plan) -> AttachResult FORM-Prüfung + Anhang
validate_plan_shape(plan) -> list[ShapeError] reine FORM-Prüfung
RunResult, AttachResult, ShapeError Ergebnis-/Fehlertypen
PLAN_ATTRIBUTE_KEY, ORIGIN_KINDS, PLAN_VERSION Client-Konstanten

Entwicklung

pixi run test          # zufällige Reihenfolge (pytest-randomly)
pixi run -- pytest -p no:randomly    # feste Reihenfolge

Die credential-gated Live-LLM-Tests (Claude/OpenAI) laufen dort, wo die Keys gesetzt sind, und werden sonst übersprungen; die deterministische Suite läuft immer und ohne Mocks.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

meander_agent-0.1.0.tar.gz (47.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

meander_agent-0.1.0-py3-none-any.whl (20.3 kB view details)

Uploaded Python 3

File details

Details for the file meander_agent-0.1.0.tar.gz.

File metadata

  • Download URL: meander_agent-0.1.0.tar.gz
  • Upload date:
  • Size: 47.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for meander_agent-0.1.0.tar.gz
Algorithm Hash digest
SHA256 50957cbad59bff5a93e524786627eb863d0674ea9e970b022ea330bea124d923
MD5 131d83db19e0e300acaa929c9ffb0f41
BLAKE2b-256 d6116365363f4d3cb5bf81b2693e2dee3303068f5d51935de67b44217fe8f1bd

See more details on using hashes here.

Provenance

The following attestation bundles were made for meander_agent-0.1.0.tar.gz:

Publisher: publish-pypi.yml on Symbolic-Intelligence-Org/meander-agent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file meander_agent-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: meander_agent-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 20.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for meander_agent-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f04f3fcd40778c7502be6a674942c2f59d3a4e92497cfc0f95e342272795564d
MD5 d05948b6361d0b1784e49e09d0904174
BLAKE2b-256 172e9e0ef35539088b6d05b003557a189438a346beb3da7ec9510b93b431218a

See more details on using hashes here.

Provenance

The following attestation bundles were made for meander_agent-0.1.0-py3-none-any.whl:

Publisher: publish-pypi.yml on Symbolic-Intelligence-Org/meander-agent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page