reactifact 0.16.1__tar.gz → 0.17.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reactifact-0.16.1/reactifact.egg-info → reactifact-0.17.0}/PKG-INFO +1 -1
- {reactifact-0.16.1 → reactifact-0.17.0}/pyproject.toml +1 -1
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/__init__.py +1 -1
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/resources.py +24 -5
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/runtime.py +1 -1
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/__init__.py +2 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/models.py +25 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/postgres.py +172 -50
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/store.py +119 -29
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/templates/sessions.html +10 -2
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/templates/ui_run.html +56 -12
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/tracer.py +47 -2
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/web.py +97 -35
- {reactifact-0.16.1 → reactifact-0.17.0/reactifact.egg-info}/PKG-INFO +1 -1
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact.egg-info/SOURCES.txt +1 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_public_api.py +1 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_trace_columns.py +7 -3
- reactifact-0.17.0/tests/test_trace_limits.py +131 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_tracing.py +6 -4
- {reactifact-0.16.1 → reactifact-0.17.0}/LICENSE +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/README.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/__main__.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/_extras.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/_httpx.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/_persistence.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/a2a/__init__.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/a2a/client.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/a2a/models.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/a2a/remote.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/a2a/server.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/agent_tool.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/agents.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/artifacts.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/audit.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/authz.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/branching.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/budget.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/cache.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/chat.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/checkpoints.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/cli/__init__.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/cli/branch.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/cli/common.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/cli/context.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/cli/eval.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/cli/graph.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/cli/replay.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/cli/scenario.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/cli/skills.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/cli/trace.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/commit.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/commit_log.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/consume.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/context.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/context_builder.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/effects.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/eval/__init__.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/eval/dataset.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/eval/evaluators.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/eval/judge.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/eval/online.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/eval/runner.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/eval/scoring.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/events.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/guardrails.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/interrupt.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/llm_agent.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/logging.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/mcp/__init__.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/mcp/client.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/mcp/oauth.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/mcp/server.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/metrics.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/native_tool_use.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/operations.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/patches.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/pricing.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/produce.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/prompts.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/__init__.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/_retry.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/anthropic.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/azure.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/cerebras.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/chat.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/contracts.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/deepseek.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/fake.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/fireworks.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/gemini.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/github_models.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/groq.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/image.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/mistral.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/nvidia.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/ollama.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/openai.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/openrouter.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/perplexity.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/qwen.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/speech.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/together.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/video.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/xai.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/providers/zai.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/py.typed +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/quick/__init__.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/quick/_shared.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/quick/agent.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/quick/chat.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/quick/models.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/quick/rag.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/quick/tools_agent.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/quota.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/__init__.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/cleanup.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/conversation.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/identity.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/inputs.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/memory.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/plan_execute.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/reflection.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/resolve.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/rollback.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/search.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/skills.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/status.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/supervisor.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/text.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/recipes/tool_loop.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/redaction.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/relations.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/replay.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/request.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/routing.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/scheduler.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/session.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact/SKILL.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact/references/effects-and-state.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact/references/trust-and-safety.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-agents/SKILL.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-agents/references/consume.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-agents/references/effects.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-eval/SKILL.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-eval/references/online.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-from-langchain/SKILL.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-llm/SKILL.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-llm/references/providers.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-llm/references/tools.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-observability/SKILL.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-rag/SKILL.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-rag/references/sources.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/skills/reactifact-testing/SKILL.md +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/sources.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/streaming.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/structured.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/testing/__init__.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/testing/assertions.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/testing/exceptions.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/testing/fault.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/testing/golden.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/testing/lab.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/testing/mock.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/testing/pytest_plugin.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/testing/record.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/testing/registry.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tool_use.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tools.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/_otlp.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/columns.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/langfuse.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/otlp.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/templates/app.css +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/tracing/templates/ui.html +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/triggers.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/types.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/verify.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/viz.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact/web.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact.egg-info/dependency_links.txt +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact.egg-info/entry_points.txt +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact.egg-info/requires.txt +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/reactifact.egg-info/top_level.txt +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/setup.cfg +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_a2a.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_a2a_example.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_a2a_interop.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_absent_consume.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_adaptive.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_agent_tool.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_anthropic_provider.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_artifacts.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_audit.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_authz.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_backbone.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_branching.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_budget.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_cache.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_canonical_ports.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_chat_example.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_chat_memory.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_chat_outbox.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_chat_web.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_checkpoint.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_checkpoints_concurrency.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_cli.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_cli_scenario.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_commit_log.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_compaction.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_concurrency.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_consumes_produces.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_context_builder.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_correlated_consume.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_debounce.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_determinism.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_devops.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_devops_online.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_devops_web.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_effects.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_eval.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_eval_dataset.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_eval_judge.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_eval_online.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_eval_trajectory.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_fintech_audit.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_forklab.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_forklab_web.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_friendly_api.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_gemini_provider.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_golden.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_guardrails.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_hitl.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_image_provider.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_incident_commander.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_invalidation.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_join_consume.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_knowledge.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_knowledge_web.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_llm_ladder.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_logging.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_loop_bound_client.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_mcp.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_medic_lab.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_medic_lab_web.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_metrics.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_multisource.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_native_tool_use.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_openai_provider.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_outbox.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_patches.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_pending_actions.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_ports.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_produce_styles.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_produce_trigger.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_prompts.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_provider_auth.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_provider_retry.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_providers_integration.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_quick.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_quota.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_reacts_to.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_recipe_conversation.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_recipe_plan_execute.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_recipe_reflection.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_recipe_supervisor.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_recipe_tool_loop.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_recipes.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_recipes_inputs.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_recipes_memory.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_recipes_skills.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_redaction.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_relation_graph.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_relations.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_repair.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_repair_web.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_replay.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_repo_agent.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_request.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_research.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_resources.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_retry.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_routing.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_runtime.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_runtime_errors.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_scheduler_semantics.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_session_append.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_sessions.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_shutdown.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_skills.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_sources.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_sources_search.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_sources_vector.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_speech_provider.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_starter_app.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_streaming.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_structured.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_support_copilot.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_testing_assertions.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_testing_golden.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_testing_lab.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_testing_mock.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_testing_provenance.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_testing_pytest_plugin.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_testing_registry.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_token_budget.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_tools.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_trace_tags.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_trace_ui.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_type_registry.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_vendor_factories.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_vendor_multimodal_factories.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_verify.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_video_provider.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_view.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_viz.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_web_source.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_workspace.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/test_workspace_with_sources.py +0 -0
- {reactifact-0.16.1 → reactifact-0.17.0}/tests/tests_checkpoints_sqlite.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reactifact
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: Reactive, artifact-driven agent runtime: agents transform versioned, typed, provenance-aware artifacts inside an evolving context
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/bzdvdn/reactifact
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "reactifact"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.17.0"
|
|
8
8
|
description = "Reactive, artifact-driven agent runtime: agents transform versioned, typed, provenance-aware artifacts inside an evolving context"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
+
import warnings
|
|
4
5
|
from collections.abc import Awaitable, Callable
|
|
5
6
|
from contextlib import AbstractAsyncContextManager
|
|
6
7
|
from typing import (
|
|
@@ -27,6 +28,15 @@ if TYPE_CHECKING:
|
|
|
27
28
|
|
|
28
29
|
T = TypeVar("T")
|
|
29
30
|
|
|
31
|
+
|
|
32
|
+
class _UnsetTraceTruncate:
|
|
33
|
+
"""Sentinel so an explicit `trace_truncate=` can be told apart from the default."""
|
|
34
|
+
|
|
35
|
+
__slots__ = ()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
_UNSET_TRACE_TRUNCATE = _UnsetTraceTruncate()
|
|
39
|
+
|
|
30
40
|
#: Mints an artifact id from its model/type name — injectable for deterministic
|
|
31
41
|
#: runs (`reactifact.replay.counter_ids`). `None` keeps the uuid default.
|
|
32
42
|
IdFactory = Callable[[str], str]
|
|
@@ -85,7 +95,7 @@ class RuntimeResources:
|
|
|
85
95
|
context_builder: ContextBuilder | None = None,
|
|
86
96
|
verification_threshold: float | None = None,
|
|
87
97
|
redactor: Redactor | None = None,
|
|
88
|
-
trace_truncate: int | None =
|
|
98
|
+
trace_truncate: int | None | _UnsetTraceTruncate = _UNSET_TRACE_TRUNCATE,
|
|
89
99
|
record_tool_args: bool = False,
|
|
90
100
|
id_factory: IdFactory | None = None,
|
|
91
101
|
pricer: Pricer | None = None,
|
|
@@ -136,10 +146,19 @@ class RuntimeResources:
|
|
|
136
146
|
# `redactor` when set). Off by default because args may carry secrets.
|
|
137
147
|
self.record_tool_args = record_tool_args
|
|
138
148
|
# How many characters of an artifact's JSON (and an LLM response) are
|
|
139
|
-
# kept in a trace. `None` disables truncation.
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
|
|
149
|
+
# kept in a trace. `None` disables truncation. Deprecated: configure
|
|
150
|
+
# `Tracer(limits=TraceLimits(max_data_chars=...))` instead; this stays
|
|
151
|
+
# as a fallback for older code.
|
|
152
|
+
if isinstance(trace_truncate, _UnsetTraceTruncate):
|
|
153
|
+
self.trace_truncate: int | None = 1500
|
|
154
|
+
else:
|
|
155
|
+
warnings.warn(
|
|
156
|
+
"RuntimeResources(trace_truncate=...) is deprecated; use "
|
|
157
|
+
"Tracer(limits=TraceLimits(max_data_chars=...)) instead.",
|
|
158
|
+
DeprecationWarning,
|
|
159
|
+
stacklevel=2,
|
|
160
|
+
)
|
|
161
|
+
self.trace_truncate = trace_truncate
|
|
143
162
|
# Framework-wide pass/fail cutoff for `Verify` (verify.py): `None`
|
|
144
163
|
# means "use Verify's own DEFAULT_THRESHOLD". A `Verify` instance's
|
|
145
164
|
# own explicit `threshold=` still overrides this per agent.
|
|
@@ -853,7 +853,7 @@ class Runtime:
|
|
|
853
853
|
tool_runs_token = begin_tool_runs(
|
|
854
854
|
record_args=self.context.resources.record_tool_args,
|
|
855
855
|
redactor=self.context.resources.redactor,
|
|
856
|
-
truncate=self.
|
|
856
|
+
truncate=self._trace.data_char_limit,
|
|
857
857
|
)
|
|
858
858
|
slot: Effects | None = None
|
|
859
859
|
patch: Patch | None = None
|
|
@@ -18,6 +18,7 @@ from .models import (
|
|
|
18
18
|
RunTrace,
|
|
19
19
|
Tag,
|
|
20
20
|
TagAssignment,
|
|
21
|
+
TraceLimits,
|
|
21
22
|
)
|
|
22
23
|
from .otlp import OTLPTracer
|
|
23
24
|
from .postgres import PostgresStore
|
|
@@ -48,6 +49,7 @@ __all__ = [
|
|
|
48
49
|
"TagAssignment",
|
|
49
50
|
"TraceAnnotator",
|
|
50
51
|
"TraceColumn",
|
|
52
|
+
"TraceLimits",
|
|
51
53
|
"TraceReader",
|
|
52
54
|
"TraceSink",
|
|
53
55
|
"TraceStore",
|
|
@@ -118,6 +118,23 @@ class ProduceRun(BaseModel):
|
|
|
118
118
|
latency_ms: float = 0.0
|
|
119
119
|
|
|
120
120
|
|
|
121
|
+
class TraceLimits(BaseModel):
|
|
122
|
+
"""Write-time limits for one recorded run.
|
|
123
|
+
|
|
124
|
+
Configure on the tracer (`Tracer(store=..., limits=TraceLimits(...))`),
|
|
125
|
+
not on `RuntimeResources`: these are recording policy, independent of the
|
|
126
|
+
app's providers/sources. When a cap is hit, extra data is dropped and the
|
|
127
|
+
trace is marked `trace_truncated` instead of growing without bound.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
max_spans: int | None = 5000
|
|
131
|
+
max_refs_per_span: int | None = 50
|
|
132
|
+
max_llm_calls_per_span: int | None = 20
|
|
133
|
+
#: Per-artifact/response text cap. `None` = use
|
|
134
|
+
#: `RuntimeResources.trace_truncate` (the legacy setting).
|
|
135
|
+
max_data_chars: int | None = None
|
|
136
|
+
|
|
137
|
+
|
|
121
138
|
class AgentSpan(BaseModel):
|
|
122
139
|
"""A single agent execution within a run (§54)."""
|
|
123
140
|
|
|
@@ -153,6 +170,14 @@ class RunTrace(BaseModel):
|
|
|
153
170
|
duration_ms: float = 0.0
|
|
154
171
|
outcome: str = ""
|
|
155
172
|
spans: list[AgentSpan] = Field(default_factory=list)
|
|
173
|
+
#: Total spans stored for this run (even when `spans` is a page).
|
|
174
|
+
span_total: int = 0
|
|
175
|
+
#: Offset of `spans` within the stored run (for pagination).
|
|
176
|
+
span_offset: int = 0
|
|
177
|
+
#: Whether the store has more spans beyond this page.
|
|
178
|
+
has_more_spans: bool = False
|
|
179
|
+
#: Whether the run was truncated while it was recorded (write-time cap).
|
|
180
|
+
trace_truncated: bool = False
|
|
156
181
|
#: Reviewer annotations attached *after* the run (UI review workflow, §54).
|
|
157
182
|
#: The runtime always leaves this empty; `TraceStore.get` populates it.
|
|
158
183
|
annotations: list[TagAssignment] = Field(default_factory=list)
|
|
@@ -40,11 +40,21 @@ from .store import _SORT_COLUMNS, _like
|
|
|
40
40
|
class PostgresStore:
|
|
41
41
|
"""Postgres trace sink with async write + read. Requires the `pg` extra."""
|
|
42
42
|
|
|
43
|
-
def __init__(
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
dsn: str,
|
|
46
|
+
*,
|
|
47
|
+
max_runs: int | None = 200,
|
|
48
|
+
column_scan_limit: int | None = 1000,
|
|
49
|
+
max_session_runs: int = 50,
|
|
50
|
+
):
|
|
44
51
|
from .._extras import require_extra
|
|
45
52
|
|
|
46
53
|
self._psycopg = require_extra("PostgresStore", "psycopg", "pg")
|
|
47
54
|
self.dsn = dsn
|
|
55
|
+
self.max_runs = max_runs
|
|
56
|
+
self.column_scan_limit = column_scan_limit
|
|
57
|
+
self.max_session_runs = max_session_runs
|
|
48
58
|
self._schema_ready = False
|
|
49
59
|
|
|
50
60
|
async def _ensure_schema(self) -> None:
|
|
@@ -62,7 +72,8 @@ class PostgresStore:
|
|
|
62
72
|
duration_ms REAL NOT NULL,
|
|
63
73
|
outcome TEXT NOT NULL,
|
|
64
74
|
prompt_tokens INTEGER NOT NULL DEFAULT 0,
|
|
65
|
-
completion_tokens INTEGER NOT NULL DEFAULT 0
|
|
75
|
+
completion_tokens INTEGER NOT NULL DEFAULT 0,
|
|
76
|
+
truncated INTEGER NOT NULL DEFAULT 0
|
|
66
77
|
)
|
|
67
78
|
"""
|
|
68
79
|
)
|
|
@@ -127,6 +138,10 @@ class PostgresStore:
|
|
|
127
138
|
"ALTER TABLE runs ADD COLUMN IF NOT EXISTS "
|
|
128
139
|
"completion_tokens INTEGER NOT NULL DEFAULT 0"
|
|
129
140
|
)
|
|
141
|
+
await cur.execute(
|
|
142
|
+
"ALTER TABLE runs ADD COLUMN IF NOT EXISTS "
|
|
143
|
+
"truncated INTEGER NOT NULL DEFAULT 0"
|
|
144
|
+
)
|
|
130
145
|
await cur.execute(
|
|
131
146
|
"ALTER TABLE spans ADD COLUMN IF NOT EXISTS started_at TIMESTAMPTZ"
|
|
132
147
|
)
|
|
@@ -170,8 +185,8 @@ class PostgresStore:
|
|
|
170
185
|
async with conn.cursor() as cur:
|
|
171
186
|
await cur.execute(
|
|
172
187
|
"INSERT INTO runs (id, session_id, started_at, duration_ms, "
|
|
173
|
-
"outcome, prompt_tokens, completion_tokens) "
|
|
174
|
-
"VALUES (%s, %s, %s, %s, %s, %s, %s)",
|
|
188
|
+
"outcome, prompt_tokens, completion_tokens, truncated) "
|
|
189
|
+
"VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
|
|
175
190
|
(
|
|
176
191
|
trace.id,
|
|
177
192
|
trace.session_id,
|
|
@@ -180,6 +195,7 @@ class PostgresStore:
|
|
|
180
195
|
trace.outcome,
|
|
181
196
|
prompt_tokens,
|
|
182
197
|
completion_tokens,
|
|
198
|
+
1 if trace.trace_truncated else 0,
|
|
183
199
|
),
|
|
184
200
|
)
|
|
185
201
|
for span in trace.spans:
|
|
@@ -223,9 +239,85 @@ class PostgresStore:
|
|
|
223
239
|
),
|
|
224
240
|
),
|
|
225
241
|
)
|
|
242
|
+
if self.max_runs is not None:
|
|
243
|
+
await self._prune_cur(cur, self.max_runs)
|
|
244
|
+
await conn.commit()
|
|
245
|
+
finally:
|
|
246
|
+
await conn.close()
|
|
247
|
+
|
|
248
|
+
async def _prune_cur(self, cur: Any, keep: int) -> int:
|
|
249
|
+
await cur.execute(
|
|
250
|
+
"SELECT id FROM runs ORDER BY started_at DESC, id DESC OFFSET %s",
|
|
251
|
+
(keep,),
|
|
252
|
+
)
|
|
253
|
+
ids = [r[0] for r in await cur.fetchall()]
|
|
254
|
+
if not ids:
|
|
255
|
+
return 0
|
|
256
|
+
placeholders = ",".join("%s" for _ in ids)
|
|
257
|
+
await cur.execute(
|
|
258
|
+
f"DELETE FROM spans WHERE run_id IN ({placeholders})", tuple(ids)
|
|
259
|
+
)
|
|
260
|
+
await cur.execute(
|
|
261
|
+
f"DELETE FROM run_tags WHERE run_id IN ({placeholders})", tuple(ids)
|
|
262
|
+
)
|
|
263
|
+
await cur.execute(f"DELETE FROM runs WHERE id IN ({placeholders})", tuple(ids))
|
|
264
|
+
return len(ids)
|
|
265
|
+
|
|
266
|
+
async def prune(self, keep: int | None = None) -> int:
|
|
267
|
+
keep = self.max_runs if keep is None else keep
|
|
268
|
+
if keep is None:
|
|
269
|
+
return 0
|
|
270
|
+
await self._ensure_schema()
|
|
271
|
+
conn = await self._psycopg.AsyncConnection.connect(self.dsn)
|
|
272
|
+
try:
|
|
273
|
+
async with conn.cursor() as cur:
|
|
274
|
+
removed = await self._prune_cur(cur, keep)
|
|
226
275
|
await conn.commit()
|
|
227
276
|
finally:
|
|
228
277
|
await conn.close()
|
|
278
|
+
return removed
|
|
279
|
+
|
|
280
|
+
async def delete_run(self, trace_id: str) -> bool:
|
|
281
|
+
await self._ensure_schema()
|
|
282
|
+
conn = await self._psycopg.AsyncConnection.connect(self.dsn)
|
|
283
|
+
try:
|
|
284
|
+
async with conn.cursor() as cur:
|
|
285
|
+
await cur.execute("SELECT 1 FROM runs WHERE id = %s", (trace_id,))
|
|
286
|
+
if await cur.fetchone() is None:
|
|
287
|
+
return False
|
|
288
|
+
await cur.execute("DELETE FROM spans WHERE run_id = %s", (trace_id,))
|
|
289
|
+
await cur.execute("DELETE FROM run_tags WHERE run_id = %s", (trace_id,))
|
|
290
|
+
await cur.execute("DELETE FROM runs WHERE id = %s", (trace_id,))
|
|
291
|
+
await conn.commit()
|
|
292
|
+
finally:
|
|
293
|
+
await conn.close()
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
async def delete_session(self, session_id: str) -> int:
|
|
297
|
+
await self._ensure_schema()
|
|
298
|
+
conn = await self._psycopg.AsyncConnection.connect(self.dsn)
|
|
299
|
+
try:
|
|
300
|
+
async with conn.cursor() as cur:
|
|
301
|
+
await cur.execute(
|
|
302
|
+
"SELECT id FROM runs WHERE session_id = %s", (session_id,)
|
|
303
|
+
)
|
|
304
|
+
ids = [r[0] for r in await cur.fetchall()]
|
|
305
|
+
if not ids:
|
|
306
|
+
return 0
|
|
307
|
+
placeholders = ",".join("%s" for _ in ids)
|
|
308
|
+
await cur.execute(
|
|
309
|
+
f"DELETE FROM spans WHERE run_id IN ({placeholders})", tuple(ids)
|
|
310
|
+
)
|
|
311
|
+
await cur.execute(
|
|
312
|
+
f"DELETE FROM run_tags WHERE run_id IN ({placeholders})", tuple(ids)
|
|
313
|
+
)
|
|
314
|
+
await cur.execute(
|
|
315
|
+
f"DELETE FROM runs WHERE id IN ({placeholders})", tuple(ids)
|
|
316
|
+
)
|
|
317
|
+
await conn.commit()
|
|
318
|
+
finally:
|
|
319
|
+
await conn.close()
|
|
320
|
+
return len(ids)
|
|
229
321
|
|
|
230
322
|
def _where(
|
|
231
323
|
self,
|
|
@@ -332,21 +424,32 @@ class PostgresStore:
|
|
|
332
424
|
)
|
|
333
425
|
fields_by_run: dict[str, dict[str, str]] = {}
|
|
334
426
|
if run_ids and columns:
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
SpanArtifacts(
|
|
345
|
-
agent=agent,
|
|
346
|
-
reads=[ArtifactRef(**d) for d in reads],
|
|
347
|
-
writes=[ArtifactRef(**d) for d in writes],
|
|
427
|
+
|
|
428
|
+
async def spans_of(
|
|
429
|
+
ids: list[str],
|
|
430
|
+
) -> dict[str, list[SpanArtifacts]]:
|
|
431
|
+
out: dict[str, list[SpanArtifacts]] = {}
|
|
432
|
+
for rid in ids:
|
|
433
|
+
sql = (
|
|
434
|
+
"SELECT agent, reads, writes FROM spans "
|
|
435
|
+
"WHERE run_id = %s ORDER BY id"
|
|
348
436
|
)
|
|
349
|
-
|
|
437
|
+
args: tuple[Any, ...] = (rid,)
|
|
438
|
+
if self.column_scan_limit is not None:
|
|
439
|
+
sql += " LIMIT %s"
|
|
440
|
+
args = (rid, self.column_scan_limit)
|
|
441
|
+
await cur.execute(sql, args)
|
|
442
|
+
for agent, reads, writes in await cur.fetchall():
|
|
443
|
+
out.setdefault(rid, []).append(
|
|
444
|
+
SpanArtifacts(
|
|
445
|
+
agent=agent,
|
|
446
|
+
reads=[ArtifactRef(**d) for d in reads],
|
|
447
|
+
writes=[ArtifactRef(**d) for d in writes],
|
|
448
|
+
)
|
|
449
|
+
)
|
|
450
|
+
return out
|
|
451
|
+
|
|
452
|
+
spans_by_run = await spans_of(run_ids)
|
|
350
453
|
needs_session = any(c.scope == "session" for c in columns)
|
|
351
454
|
session_order: dict[str, list[tuple[str, list[SpanArtifacts]]]] = {}
|
|
352
455
|
if needs_session:
|
|
@@ -360,21 +463,10 @@ class PostgresStore:
|
|
|
360
463
|
ordered = [x[0] for x in await cur.fetchall()]
|
|
361
464
|
if not ordered:
|
|
362
465
|
continue
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
tuple(ordered),
|
|
368
|
-
)
|
|
369
|
-
by_run: dict[str, list[SpanArtifacts]] = {}
|
|
370
|
-
for run_id, agent, reads, writes in await cur.fetchall():
|
|
371
|
-
by_run.setdefault(run_id, []).append(
|
|
372
|
-
SpanArtifacts(
|
|
373
|
-
agent=agent,
|
|
374
|
-
reads=[ArtifactRef(**d) for d in reads],
|
|
375
|
-
writes=[ArtifactRef(**d) for d in writes],
|
|
376
|
-
)
|
|
377
|
-
)
|
|
466
|
+
if len(ordered) > self.max_session_runs:
|
|
467
|
+
half = max(1, self.max_session_runs // 2)
|
|
468
|
+
ordered = ordered[:half] + ordered[-half:]
|
|
469
|
+
by_run = await spans_of(ordered)
|
|
378
470
|
session_order[sid] = [
|
|
379
471
|
(rid, by_run.get(rid, [])) for rid in ordered
|
|
380
472
|
]
|
|
@@ -408,18 +500,29 @@ class PostgresStore:
|
|
|
408
500
|
"spans": r[7],
|
|
409
501
|
"tags": tags_by_run.get(r[0], []),
|
|
410
502
|
"fields": fields_by_run.get(r[0], {}),
|
|
503
|
+
"fields_truncated": bool(
|
|
504
|
+
columns
|
|
505
|
+
and self.column_scan_limit is not None
|
|
506
|
+
and r[7] > self.column_scan_limit
|
|
507
|
+
),
|
|
411
508
|
}
|
|
412
509
|
for r in rows
|
|
413
510
|
]
|
|
414
511
|
return {"items": items, "total": total, "stats": stats}
|
|
415
512
|
|
|
416
|
-
async def get(
|
|
513
|
+
async def get(
|
|
514
|
+
self,
|
|
515
|
+
trace_id: str,
|
|
516
|
+
*,
|
|
517
|
+
span_limit: int | None = 500,
|
|
518
|
+
span_offset: int = 0,
|
|
519
|
+
) -> RunTrace | None:
|
|
417
520
|
await self._ensure_schema()
|
|
418
521
|
conn = await self._psycopg.AsyncConnection.connect(self.dsn)
|
|
419
522
|
try:
|
|
420
523
|
async with conn.cursor() as cur:
|
|
421
524
|
await cur.execute(
|
|
422
|
-
"SELECT id, session_id, started_at, duration_ms, outcome "
|
|
525
|
+
"SELECT id, session_id, started_at, duration_ms, outcome, truncated "
|
|
423
526
|
"FROM runs WHERE id = %s",
|
|
424
527
|
(trace_id,),
|
|
425
528
|
)
|
|
@@ -427,12 +530,20 @@ class PostgresStore:
|
|
|
427
530
|
if row is None:
|
|
428
531
|
return None
|
|
429
532
|
await cur.execute(
|
|
533
|
+
"SELECT COUNT(*) FROM spans WHERE run_id = %s", (trace_id,)
|
|
534
|
+
)
|
|
535
|
+
span_total = (await cur.fetchone())[0]
|
|
536
|
+
span_sql = (
|
|
430
537
|
"SELECT agent, event_type, event_artifact_type, latency_ms, error, "
|
|
431
538
|
"started_at, reads, writes, relations, llm_calls, produces, tools, "
|
|
432
539
|
"event_artifact_id, contract "
|
|
433
|
-
"FROM spans WHERE run_id = %s ORDER BY id"
|
|
434
|
-
(trace_id,),
|
|
540
|
+
"FROM spans WHERE run_id = %s ORDER BY id"
|
|
435
541
|
)
|
|
542
|
+
span_args: tuple[Any, ...] = (trace_id,)
|
|
543
|
+
if span_limit is not None:
|
|
544
|
+
span_sql += " LIMIT %s OFFSET %s"
|
|
545
|
+
span_args = (trace_id, span_limit, span_offset)
|
|
546
|
+
await cur.execute(span_sql, span_args)
|
|
436
547
|
span_rows = await cur.fetchall()
|
|
437
548
|
await cur.execute(
|
|
438
549
|
"SELECT rt.tag, rt.note, COALESCE(t.color, ''), "
|
|
@@ -484,6 +595,10 @@ class PostgresStore:
|
|
|
484
595
|
duration_ms=row[3],
|
|
485
596
|
outcome=row[4],
|
|
486
597
|
spans=spans,
|
|
598
|
+
span_total=span_total,
|
|
599
|
+
span_offset=span_offset,
|
|
600
|
+
has_more_spans=span_offset + len(spans) < span_total,
|
|
601
|
+
trace_truncated=bool(row[5]),
|
|
487
602
|
annotations=annotations,
|
|
488
603
|
)
|
|
489
604
|
|
|
@@ -507,21 +622,25 @@ class PostgresStore:
|
|
|
507
622
|
async def spans_of(
|
|
508
623
|
run_ids: list[str],
|
|
509
624
|
) -> dict[str, list[SpanArtifacts]]:
|
|
510
|
-
placeholders = ",".join("%s" for _ in run_ids)
|
|
511
|
-
await cur.execute(
|
|
512
|
-
"SELECT run_id, agent, reads, writes FROM spans "
|
|
513
|
-
f"WHERE run_id IN ({placeholders}) ORDER BY id",
|
|
514
|
-
tuple(run_ids),
|
|
515
|
-
)
|
|
516
625
|
out: dict[str, list[SpanArtifacts]] = {}
|
|
517
|
-
for rid
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
reads=[ArtifactRef(**d) for d in reads],
|
|
522
|
-
writes=[ArtifactRef(**d) for d in writes],
|
|
523
|
-
)
|
|
626
|
+
for rid in run_ids:
|
|
627
|
+
sql = (
|
|
628
|
+
"SELECT agent, reads, writes FROM spans "
|
|
629
|
+
"WHERE run_id = %s ORDER BY id"
|
|
524
630
|
)
|
|
631
|
+
args: tuple[Any, ...] = (rid,)
|
|
632
|
+
if self.column_scan_limit is not None:
|
|
633
|
+
sql += " LIMIT %s"
|
|
634
|
+
args = (rid, self.column_scan_limit)
|
|
635
|
+
await cur.execute(sql, args)
|
|
636
|
+
for agent, reads, writes in await cur.fetchall():
|
|
637
|
+
out.setdefault(rid, []).append(
|
|
638
|
+
SpanArtifacts(
|
|
639
|
+
agent=agent,
|
|
640
|
+
reads=[ArtifactRef(**d) for d in reads],
|
|
641
|
+
writes=[ArtifactRef(**d) for d in writes],
|
|
642
|
+
)
|
|
643
|
+
)
|
|
525
644
|
return out
|
|
526
645
|
|
|
527
646
|
run_spans = (await spans_of([run_id])).get(run_id, [])
|
|
@@ -534,6 +653,9 @@ class PostgresStore:
|
|
|
534
653
|
)
|
|
535
654
|
ordered = [r[0] for r in await cur.fetchall()]
|
|
536
655
|
if ordered:
|
|
656
|
+
if len(ordered) > self.max_session_runs:
|
|
657
|
+
half = max(1, self.max_session_runs // 2)
|
|
658
|
+
ordered = ordered[:half] + ordered[-half:]
|
|
537
659
|
by_run = await spans_of(ordered)
|
|
538
660
|
session_spans = []
|
|
539
661
|
for rid in ordered:
|