reactifact 0.16.0__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.0/reactifact.egg-info → reactifact-0.17.0}/PKG-INFO +1 -1
- {reactifact-0.16.0 → reactifact-0.17.0}/pyproject.toml +1 -1
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/__init__.py +1 -1
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/resources.py +24 -5
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/runtime.py +1 -1
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/__init__.py +4 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/models.py +43 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/postgres.py +195 -55
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/store.py +139 -33
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/templates/app.css +20 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/templates/sessions.html +10 -2
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/templates/ui_run.html +166 -50
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/tracer.py +77 -3
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/web.py +97 -35
- {reactifact-0.16.0 → reactifact-0.17.0/reactifact.egg-info}/PKG-INFO +1 -1
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact.egg-info/SOURCES.txt +1 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_public_api.py +2 -0
- {reactifact-0.16.0 → 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.0 → reactifact-0.17.0}/tests/test_trace_ui.py +12 -1
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_tracing.py +13 -4
- {reactifact-0.16.0 → reactifact-0.17.0}/LICENSE +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/README.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/__main__.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/_extras.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/_httpx.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/_persistence.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/a2a/__init__.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/a2a/client.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/a2a/models.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/a2a/remote.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/a2a/server.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/agent_tool.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/agents.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/artifacts.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/audit.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/authz.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/branching.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/budget.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/cache.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/chat.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/checkpoints.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/cli/__init__.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/cli/branch.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/cli/common.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/cli/context.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/cli/eval.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/cli/graph.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/cli/replay.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/cli/scenario.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/cli/skills.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/cli/trace.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/commit.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/commit_log.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/consume.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/context.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/context_builder.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/effects.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/eval/__init__.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/eval/dataset.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/eval/evaluators.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/eval/judge.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/eval/online.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/eval/runner.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/eval/scoring.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/events.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/guardrails.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/interrupt.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/llm_agent.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/logging.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/mcp/__init__.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/mcp/client.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/mcp/oauth.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/mcp/server.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/metrics.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/native_tool_use.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/operations.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/patches.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/pricing.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/produce.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/prompts.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/__init__.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/_retry.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/anthropic.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/azure.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/cerebras.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/chat.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/contracts.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/deepseek.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/fake.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/fireworks.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/gemini.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/github_models.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/groq.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/image.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/mistral.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/nvidia.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/ollama.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/openai.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/openrouter.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/perplexity.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/qwen.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/speech.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/together.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/video.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/xai.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/providers/zai.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/py.typed +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/quick/__init__.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/quick/_shared.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/quick/agent.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/quick/chat.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/quick/models.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/quick/rag.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/quick/tools_agent.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/quota.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/__init__.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/cleanup.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/conversation.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/identity.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/inputs.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/memory.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/plan_execute.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/reflection.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/resolve.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/rollback.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/search.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/skills.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/status.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/supervisor.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/text.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/recipes/tool_loop.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/redaction.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/relations.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/replay.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/request.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/routing.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/scheduler.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/session.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact/SKILL.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact/references/effects-and-state.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact/references/trust-and-safety.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-agents/SKILL.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-agents/references/consume.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-agents/references/effects.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-eval/SKILL.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-eval/references/online.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-from-langchain/SKILL.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-llm/SKILL.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-llm/references/providers.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-llm/references/tools.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-observability/SKILL.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-rag/SKILL.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-rag/references/sources.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/skills/reactifact-testing/SKILL.md +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/sources.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/streaming.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/structured.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/testing/__init__.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/testing/assertions.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/testing/exceptions.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/testing/fault.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/testing/golden.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/testing/lab.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/testing/mock.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/testing/pytest_plugin.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/testing/record.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/testing/registry.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tool_use.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tools.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/_otlp.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/columns.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/langfuse.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/otlp.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/tracing/templates/ui.html +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/triggers.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/types.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/verify.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/viz.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact/web.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact.egg-info/dependency_links.txt +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact.egg-info/entry_points.txt +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact.egg-info/requires.txt +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/reactifact.egg-info/top_level.txt +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/setup.cfg +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_a2a.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_a2a_example.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_a2a_interop.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_absent_consume.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_adaptive.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_agent_tool.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_anthropic_provider.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_artifacts.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_audit.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_authz.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_backbone.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_branching.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_budget.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_cache.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_canonical_ports.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_chat_example.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_chat_memory.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_chat_outbox.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_chat_web.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_checkpoint.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_checkpoints_concurrency.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_cli.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_cli_scenario.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_commit_log.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_compaction.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_concurrency.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_consumes_produces.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_context_builder.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_correlated_consume.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_debounce.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_determinism.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_devops.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_devops_online.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_devops_web.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_effects.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_eval.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_eval_dataset.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_eval_judge.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_eval_online.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_eval_trajectory.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_fintech_audit.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_forklab.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_forklab_web.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_friendly_api.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_gemini_provider.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_golden.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_guardrails.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_hitl.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_image_provider.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_incident_commander.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_invalidation.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_join_consume.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_knowledge.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_knowledge_web.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_llm_ladder.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_logging.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_loop_bound_client.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_mcp.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_medic_lab.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_medic_lab_web.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_metrics.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_multisource.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_native_tool_use.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_openai_provider.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_outbox.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_patches.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_pending_actions.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_ports.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_produce_styles.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_produce_trigger.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_prompts.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_provider_auth.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_provider_retry.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_providers_integration.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_quick.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_quota.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_reacts_to.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_recipe_conversation.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_recipe_plan_execute.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_recipe_reflection.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_recipe_supervisor.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_recipe_tool_loop.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_recipes.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_recipes_inputs.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_recipes_memory.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_recipes_skills.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_redaction.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_relation_graph.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_relations.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_repair.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_repair_web.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_replay.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_repo_agent.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_request.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_research.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_resources.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_retry.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_routing.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_runtime.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_runtime_errors.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_scheduler_semantics.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_session_append.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_sessions.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_shutdown.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_skills.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_sources.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_sources_search.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_sources_vector.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_speech_provider.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_starter_app.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_streaming.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_structured.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_support_copilot.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_testing_assertions.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_testing_golden.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_testing_lab.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_testing_mock.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_testing_provenance.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_testing_pytest_plugin.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_testing_registry.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_token_budget.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_tools.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_trace_tags.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_type_registry.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_vendor_factories.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_vendor_multimodal_factories.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_verify.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_video_provider.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_view.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_viz.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_web_source.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_workspace.py +0 -0
- {reactifact-0.16.0 → reactifact-0.17.0}/tests/test_workspace_with_sources.py +0 -0
- {reactifact-0.16.0 → 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
|
|
@@ -9,6 +9,7 @@ from ..tools import ToolRun
|
|
|
9
9
|
from .columns import TraceColumn
|
|
10
10
|
from .langfuse import LangfuseTracer
|
|
11
11
|
from .models import (
|
|
12
|
+
AgentContract,
|
|
12
13
|
AgentSpan,
|
|
13
14
|
ArtifactRef,
|
|
14
15
|
LLMCall,
|
|
@@ -17,6 +18,7 @@ from .models import (
|
|
|
17
18
|
RunTrace,
|
|
18
19
|
Tag,
|
|
19
20
|
TagAssignment,
|
|
21
|
+
TraceLimits,
|
|
20
22
|
)
|
|
21
23
|
from .otlp import OTLPTracer
|
|
22
24
|
from .postgres import PostgresStore
|
|
@@ -30,6 +32,7 @@ from .store import (
|
|
|
30
32
|
from .tracer import CompositeTracer, RecordingLLM, RunTracer, Tracer
|
|
31
33
|
|
|
32
34
|
__all__ = [
|
|
35
|
+
"AgentContract",
|
|
33
36
|
"AgentSpan",
|
|
34
37
|
"ArtifactRef",
|
|
35
38
|
"CompositeTracer",
|
|
@@ -46,6 +49,7 @@ __all__ = [
|
|
|
46
49
|
"TagAssignment",
|
|
47
50
|
"TraceAnnotator",
|
|
48
51
|
"TraceColumn",
|
|
52
|
+
"TraceLimits",
|
|
49
53
|
"TraceReader",
|
|
50
54
|
"TraceSink",
|
|
51
55
|
"TraceStore",
|
|
@@ -90,6 +90,20 @@ class TagAssignment(BaseModel):
|
|
|
90
90
|
updated_at: datetime | None = None
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
class AgentContract(BaseModel):
|
|
94
|
+
"""The agent's declared contract, recorded on each span for the UI.
|
|
95
|
+
|
|
96
|
+
`consumes`/`produces`/`tools` are the *declared* names (not only what ran),
|
|
97
|
+
so the dashboard can show a consume/produce that never fired as a muted
|
|
98
|
+
chip instead of hiding it.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
consumes: list[str] = Field(default_factory=list)
|
|
102
|
+
produces: list[str] = Field(default_factory=list)
|
|
103
|
+
tools: list[str] = Field(default_factory=list)
|
|
104
|
+
priority: int = 0
|
|
105
|
+
|
|
106
|
+
|
|
93
107
|
class ProduceRun(BaseModel):
|
|
94
108
|
"""One produce execution within an agent span (§54).
|
|
95
109
|
|
|
@@ -104,6 +118,23 @@ class ProduceRun(BaseModel):
|
|
|
104
118
|
latency_ms: float = 0.0
|
|
105
119
|
|
|
106
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
|
+
|
|
107
138
|
class AgentSpan(BaseModel):
|
|
108
139
|
"""A single agent execution within a run (§54)."""
|
|
109
140
|
|
|
@@ -113,6 +144,10 @@ class AgentSpan(BaseModel):
|
|
|
113
144
|
#: ``Consume``) — e.g. ``DraftAnswer`` vs ``FinalResponse`` for an agent that
|
|
114
145
|
#: consumes both. ``event_type`` alone (created/updated) can't tell them apart.
|
|
115
146
|
event_artifact_type: str = ""
|
|
147
|
+
#: The concrete artifact whose change triggered this span.
|
|
148
|
+
event_artifact_id: str = ""
|
|
149
|
+
#: The agent's declared consumes/produces/tools (for the UI contract view).
|
|
150
|
+
contract: AgentContract = Field(default_factory=AgentContract)
|
|
116
151
|
#: The produces that ran for this event, in execution order.
|
|
117
152
|
produces: list[ProduceRun] = Field(default_factory=list)
|
|
118
153
|
#: Tool executions that happened during this agent run (name/latency/status).
|
|
@@ -135,6 +170,14 @@ class RunTrace(BaseModel):
|
|
|
135
170
|
duration_ms: float = 0.0
|
|
136
171
|
outcome: str = ""
|
|
137
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
|
|
138
181
|
#: Reviewer annotations attached *after* the run (UI review workflow, §54).
|
|
139
182
|
#: The runtime always leaves this empty; `TraceStore.get` populates it.
|
|
140
183
|
annotations: list[TagAssignment] = Field(default_factory=list)
|
|
@@ -25,6 +25,7 @@ from typing import Any
|
|
|
25
25
|
from ..tools import ToolRun
|
|
26
26
|
from .columns import SpanArtifacts, TraceColumn, extract_columns
|
|
27
27
|
from .models import (
|
|
28
|
+
AgentContract,
|
|
28
29
|
AgentSpan,
|
|
29
30
|
ArtifactRef,
|
|
30
31
|
LLMCall,
|
|
@@ -39,11 +40,21 @@ from .store import _SORT_COLUMNS, _like
|
|
|
39
40
|
class PostgresStore:
|
|
40
41
|
"""Postgres trace sink with async write + read. Requires the `pg` extra."""
|
|
41
42
|
|
|
42
|
-
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
|
+
):
|
|
43
51
|
from .._extras import require_extra
|
|
44
52
|
|
|
45
53
|
self._psycopg = require_extra("PostgresStore", "psycopg", "pg")
|
|
46
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
|
|
47
58
|
self._schema_ready = False
|
|
48
59
|
|
|
49
60
|
async def _ensure_schema(self) -> None:
|
|
@@ -61,7 +72,8 @@ class PostgresStore:
|
|
|
61
72
|
duration_ms REAL NOT NULL,
|
|
62
73
|
outcome TEXT NOT NULL,
|
|
63
74
|
prompt_tokens INTEGER NOT NULL DEFAULT 0,
|
|
64
|
-
completion_tokens INTEGER NOT NULL DEFAULT 0
|
|
75
|
+
completion_tokens INTEGER NOT NULL DEFAULT 0,
|
|
76
|
+
truncated INTEGER NOT NULL DEFAULT 0
|
|
65
77
|
)
|
|
66
78
|
"""
|
|
67
79
|
)
|
|
@@ -81,7 +93,9 @@ class PostgresStore:
|
|
|
81
93
|
relations JSONB NOT NULL DEFAULT '[]',
|
|
82
94
|
llm_calls JSONB NOT NULL DEFAULT '[]',
|
|
83
95
|
produces JSONB NOT NULL DEFAULT '[]',
|
|
84
|
-
tools JSONB NOT NULL DEFAULT '[]'
|
|
96
|
+
tools JSONB NOT NULL DEFAULT '[]',
|
|
97
|
+
event_artifact_id TEXT NOT NULL DEFAULT '',
|
|
98
|
+
contract JSONB NOT NULL DEFAULT '{}'
|
|
85
99
|
)
|
|
86
100
|
"""
|
|
87
101
|
)
|
|
@@ -124,6 +138,10 @@ class PostgresStore:
|
|
|
124
138
|
"ALTER TABLE runs ADD COLUMN IF NOT EXISTS "
|
|
125
139
|
"completion_tokens INTEGER NOT NULL DEFAULT 0"
|
|
126
140
|
)
|
|
141
|
+
await cur.execute(
|
|
142
|
+
"ALTER TABLE runs ADD COLUMN IF NOT EXISTS "
|
|
143
|
+
"truncated INTEGER NOT NULL DEFAULT 0"
|
|
144
|
+
)
|
|
127
145
|
await cur.execute(
|
|
128
146
|
"ALTER TABLE spans ADD COLUMN IF NOT EXISTS started_at TIMESTAMPTZ"
|
|
129
147
|
)
|
|
@@ -139,6 +157,14 @@ class PostgresStore:
|
|
|
139
157
|
"ALTER TABLE spans ADD COLUMN IF NOT EXISTS "
|
|
140
158
|
"tools JSONB NOT NULL DEFAULT '[]'"
|
|
141
159
|
)
|
|
160
|
+
await cur.execute(
|
|
161
|
+
"ALTER TABLE spans ADD COLUMN IF NOT EXISTS "
|
|
162
|
+
"event_artifact_id TEXT NOT NULL DEFAULT ''"
|
|
163
|
+
)
|
|
164
|
+
await cur.execute(
|
|
165
|
+
"ALTER TABLE spans ADD COLUMN IF NOT EXISTS "
|
|
166
|
+
"contract JSONB NOT NULL DEFAULT '{}'"
|
|
167
|
+
)
|
|
142
168
|
await conn.commit()
|
|
143
169
|
finally:
|
|
144
170
|
await conn.close()
|
|
@@ -159,8 +185,8 @@ class PostgresStore:
|
|
|
159
185
|
async with conn.cursor() as cur:
|
|
160
186
|
await cur.execute(
|
|
161
187
|
"INSERT INTO runs (id, session_id, started_at, duration_ms, "
|
|
162
|
-
"outcome, prompt_tokens, completion_tokens) "
|
|
163
|
-
"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)",
|
|
164
190
|
(
|
|
165
191
|
trace.id,
|
|
166
192
|
trace.session_id,
|
|
@@ -169,6 +195,7 @@ class PostgresStore:
|
|
|
169
195
|
trace.outcome,
|
|
170
196
|
prompt_tokens,
|
|
171
197
|
completion_tokens,
|
|
198
|
+
1 if trace.trace_truncated else 0,
|
|
172
199
|
),
|
|
173
200
|
)
|
|
174
201
|
for span in trace.spans:
|
|
@@ -178,8 +205,8 @@ class PostgresStore:
|
|
|
178
205
|
await cur.execute(
|
|
179
206
|
"INSERT INTO spans (run_id, agent, event_type, event_artifact_type, "
|
|
180
207
|
"latency_ms, error, started_at, reads, writes, relations, llm_calls, "
|
|
181
|
-
"produces, tools) "
|
|
182
|
-
"VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
|
|
208
|
+
"produces, tools, event_artifact_id, contract) "
|
|
209
|
+
"VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
|
|
183
210
|
(
|
|
184
211
|
trace.id,
|
|
185
212
|
span.agent,
|
|
@@ -206,11 +233,91 @@ class PostgresStore:
|
|
|
206
233
|
psycopg.types.json.Jsonb(
|
|
207
234
|
[tr.model_dump(mode="json") for tr in span.tools]
|
|
208
235
|
),
|
|
236
|
+
span.event_artifact_id,
|
|
237
|
+
psycopg.types.json.Jsonb(
|
|
238
|
+
span.contract.model_dump(mode="json")
|
|
239
|
+
),
|
|
209
240
|
),
|
|
210
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)
|
|
275
|
+
await conn.commit()
|
|
276
|
+
finally:
|
|
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
|
+
)
|
|
211
317
|
await conn.commit()
|
|
212
318
|
finally:
|
|
213
319
|
await conn.close()
|
|
320
|
+
return len(ids)
|
|
214
321
|
|
|
215
322
|
def _where(
|
|
216
323
|
self,
|
|
@@ -317,21 +424,32 @@ class PostgresStore:
|
|
|
317
424
|
)
|
|
318
425
|
fields_by_run: dict[str, dict[str, str]] = {}
|
|
319
426
|
if run_ids and columns:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
SpanArtifacts(
|
|
330
|
-
agent=agent,
|
|
331
|
-
reads=[ArtifactRef(**d) for d in reads],
|
|
332
|
-
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"
|
|
333
436
|
)
|
|
334
|
-
|
|
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)
|
|
335
453
|
needs_session = any(c.scope == "session" for c in columns)
|
|
336
454
|
session_order: dict[str, list[tuple[str, list[SpanArtifacts]]]] = {}
|
|
337
455
|
if needs_session:
|
|
@@ -345,21 +463,10 @@ class PostgresStore:
|
|
|
345
463
|
ordered = [x[0] for x in await cur.fetchall()]
|
|
346
464
|
if not ordered:
|
|
347
465
|
continue
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
tuple(ordered),
|
|
353
|
-
)
|
|
354
|
-
by_run: dict[str, list[SpanArtifacts]] = {}
|
|
355
|
-
for run_id, agent, reads, writes in await cur.fetchall():
|
|
356
|
-
by_run.setdefault(run_id, []).append(
|
|
357
|
-
SpanArtifacts(
|
|
358
|
-
agent=agent,
|
|
359
|
-
reads=[ArtifactRef(**d) for d in reads],
|
|
360
|
-
writes=[ArtifactRef(**d) for d in writes],
|
|
361
|
-
)
|
|
362
|
-
)
|
|
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)
|
|
363
470
|
session_order[sid] = [
|
|
364
471
|
(rid, by_run.get(rid, [])) for rid in ordered
|
|
365
472
|
]
|
|
@@ -393,18 +500,29 @@ class PostgresStore:
|
|
|
393
500
|
"spans": r[7],
|
|
394
501
|
"tags": tags_by_run.get(r[0], []),
|
|
395
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
|
+
),
|
|
396
508
|
}
|
|
397
509
|
for r in rows
|
|
398
510
|
]
|
|
399
511
|
return {"items": items, "total": total, "stats": stats}
|
|
400
512
|
|
|
401
|
-
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:
|
|
402
520
|
await self._ensure_schema()
|
|
403
521
|
conn = await self._psycopg.AsyncConnection.connect(self.dsn)
|
|
404
522
|
try:
|
|
405
523
|
async with conn.cursor() as cur:
|
|
406
524
|
await cur.execute(
|
|
407
|
-
"SELECT id, session_id, started_at, duration_ms, outcome "
|
|
525
|
+
"SELECT id, session_id, started_at, duration_ms, outcome, truncated "
|
|
408
526
|
"FROM runs WHERE id = %s",
|
|
409
527
|
(trace_id,),
|
|
410
528
|
)
|
|
@@ -412,11 +530,20 @@ class PostgresStore:
|
|
|
412
530
|
if row is None:
|
|
413
531
|
return None
|
|
414
532
|
await cur.execute(
|
|
415
|
-
"SELECT
|
|
416
|
-
"started_at, reads, writes, relations, llm_calls, produces, tools "
|
|
417
|
-
"FROM spans WHERE run_id = %s ORDER BY id",
|
|
418
|
-
(trace_id,),
|
|
533
|
+
"SELECT COUNT(*) FROM spans WHERE run_id = %s", (trace_id,)
|
|
419
534
|
)
|
|
535
|
+
span_total = (await cur.fetchone())[0]
|
|
536
|
+
span_sql = (
|
|
537
|
+
"SELECT agent, event_type, event_artifact_type, latency_ms, error, "
|
|
538
|
+
"started_at, reads, writes, relations, llm_calls, produces, tools, "
|
|
539
|
+
"event_artifact_id, contract "
|
|
540
|
+
"FROM spans WHERE run_id = %s ORDER BY id"
|
|
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)
|
|
420
547
|
span_rows = await cur.fetchall()
|
|
421
548
|
await cur.execute(
|
|
422
549
|
"SELECT rt.tag, rt.note, COALESCE(t.color, ''), "
|
|
@@ -443,6 +570,8 @@ class PostgresStore:
|
|
|
443
570
|
llm_calls=[LLMCall(**d) for d in r[9]],
|
|
444
571
|
produces=[ProduceRun(**d) for d in r[10]],
|
|
445
572
|
tools=[ToolRun(**d) for d in r[11]],
|
|
573
|
+
event_artifact_id=r[12],
|
|
574
|
+
contract=AgentContract(**r[13]),
|
|
446
575
|
)
|
|
447
576
|
for r in span_rows
|
|
448
577
|
]
|
|
@@ -466,6 +595,10 @@ class PostgresStore:
|
|
|
466
595
|
duration_ms=row[3],
|
|
467
596
|
outcome=row[4],
|
|
468
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]),
|
|
469
602
|
annotations=annotations,
|
|
470
603
|
)
|
|
471
604
|
|
|
@@ -489,21 +622,25 @@ class PostgresStore:
|
|
|
489
622
|
async def spans_of(
|
|
490
623
|
run_ids: list[str],
|
|
491
624
|
) -> dict[str, list[SpanArtifacts]]:
|
|
492
|
-
placeholders = ",".join("%s" for _ in run_ids)
|
|
493
|
-
await cur.execute(
|
|
494
|
-
"SELECT run_id, agent, reads, writes FROM spans "
|
|
495
|
-
f"WHERE run_id IN ({placeholders}) ORDER BY id",
|
|
496
|
-
tuple(run_ids),
|
|
497
|
-
)
|
|
498
625
|
out: dict[str, list[SpanArtifacts]] = {}
|
|
499
|
-
for rid
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
reads=[ArtifactRef(**d) for d in reads],
|
|
504
|
-
writes=[ArtifactRef(**d) for d in writes],
|
|
505
|
-
)
|
|
626
|
+
for rid in run_ids:
|
|
627
|
+
sql = (
|
|
628
|
+
"SELECT agent, reads, writes FROM spans "
|
|
629
|
+
"WHERE run_id = %s ORDER BY id"
|
|
506
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
|
+
)
|
|
507
644
|
return out
|
|
508
645
|
|
|
509
646
|
run_spans = (await spans_of([run_id])).get(run_id, [])
|
|
@@ -516,6 +653,9 @@ class PostgresStore:
|
|
|
516
653
|
)
|
|
517
654
|
ordered = [r[0] for r in await cur.fetchall()]
|
|
518
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:]
|
|
519
659
|
by_run = await spans_of(ordered)
|
|
520
660
|
session_spans = []
|
|
521
661
|
for rid in ordered:
|