mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-21 07:55:18 +08:00
Core no longer assembles a whole trace locally. Every workflow node is uploaded
the moment it completes as its own `execution_node` record, and each execution
closes with exactly one `execution_chain` record; all records of one execution
carry the same `event_id`, so Space groups them back into a single chain.
- every execution starts at an inbound event: event-less Pipeline lanes and
WebUI/API debug runs synthesize their virtual inbound event at the boundary
and are marked debug=true, so a Runner is never a trace origin
- sampling is decided once per event from a stable hash of the event id (the
previous `int(id[:8], 16)` raised on every `platform:`-prefixed id and so
dropped every successful inbound-event chain) and is reported on each record
as sample{mode,denominator}; the default mode is now `all`
- the local trace buffer, the 120s TTL, the per-process trace cap and the shared
open-node mirror are gone, which also removes the truncated-chain upload and
the re-buffered-closed-trace leak they caused
- a broken chain is always uploaded, and its reason falls back to the first
failed node so the chain explains itself
- flush keeps records until the sender accepts them and counts what it drops;
telemetry health replaces the unused send-task queue
- heartbeat reports bound Agent / plugin-processor / event-binding counts
- bound Pipeline and Agent resources (plugins, MCP servers, knowledge bases,
tools with their Host source, skills, models and the Runner schema selectors
that picked them) are reported on the chain record
- failure text is bounded and redacted instead of passing exception text through
- the dead per-query feature counters and their call sites are removed
Wire contract: schema 2 (`execution_node` / `execution_chain`).
38 lines
1.3 KiB
Python
38 lines
1.3 KiB
Python
from types import SimpleNamespace
|
|
|
|
from langbot.pkg.telemetry.execution import ExecutionCounters
|
|
from langbot.pkg.telemetry.trace import TraceState
|
|
from langbot.pkg.utils import constants
|
|
|
|
|
|
def test_cloud_counts_are_not_sampled(monkeypatch):
|
|
monkeypatch.setattr(constants, 'edition', 'cloud')
|
|
counters = ExecutionCounters(SimpleNamespace(telemetry_config={'execution_trace': 'sampled'}))
|
|
assert counters.trace_mode() == 'all'
|
|
state = TraceState('00000001')
|
|
counters.configure(state)
|
|
assert state.emit_nodes is True
|
|
assert counters._chain_emitted(state) is True
|
|
|
|
|
|
def test_community_retains_sampling(monkeypatch):
|
|
monkeypatch.setattr(constants, 'edition', 'community')
|
|
counters = ExecutionCounters(SimpleNamespace(telemetry_config={'execution_trace': 'sampled'}))
|
|
assert counters.trace_mode() == 'sampled'
|
|
|
|
|
|
def test_dns_survives_saturated_executor():
|
|
import socket
|
|
import threading
|
|
from langbot.pkg.utils.bounded_executor import BoundedThreadPoolExecutor
|
|
|
|
gate = threading.Event()
|
|
executor = BoundedThreadPoolExecutor(max_workers=1, max_pending=0)
|
|
try:
|
|
blocked = executor.submit(gate.wait)
|
|
assert executor.submit(socket.getaddrinfo, '127.0.0.1', 443).result(timeout=2)
|
|
finally:
|
|
gate.set()
|
|
blocked.result(timeout=2)
|
|
executor.shutdown()
|