reactifact 0.15.1__tar.gz → 0.15.2__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.15.1/reactifact.egg-info → reactifact-0.15.2}/PKG-INFO +2 -2
- {reactifact-0.15.1 → reactifact-0.15.2}/README.md +1 -1
- {reactifact-0.15.1 → reactifact-0.15.2}/pyproject.toml +1 -1
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/__init__.py +1 -1
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/agents.py +42 -1
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/produce.py +35 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/runtime.py +24 -9
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/__init__.py +2 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/langfuse.py +73 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/models.py +20 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/otlp.py +11 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/postgres.py +31 -13
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/store.py +29 -13
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/templates/app.css +12 -1
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/templates/ui_run.html +30 -4
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/tracer.py +13 -1
- {reactifact-0.15.1 → reactifact-0.15.2/reactifact.egg-info}/PKG-INFO +2 -2
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_produce_styles.py +17 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_public_api.py +1 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_tracing.py +117 -2
- {reactifact-0.15.1 → reactifact-0.15.2}/LICENSE +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/__main__.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/_extras.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/_httpx.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/a2a/__init__.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/a2a/client.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/a2a/models.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/a2a/remote.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/a2a/server.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/agent_tool.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/artifacts.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/audit.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/authz.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/branching.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/budget.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/cache.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/chat.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/checkpoints.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/cli/__init__.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/cli/branch.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/cli/common.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/cli/context.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/cli/eval.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/cli/graph.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/cli/replay.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/cli/scenario.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/cli/skills.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/cli/trace.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/commit.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/commit_log.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/consume.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/context.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/context_builder.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/effects.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/eval/__init__.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/eval/dataset.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/eval/evaluators.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/eval/judge.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/eval/online.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/eval/runner.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/eval/scoring.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/events.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/guardrails.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/interrupt.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/llm_agent.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/logging.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/mcp/__init__.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/mcp/client.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/mcp/oauth.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/mcp/server.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/metrics.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/native_tool_use.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/operations.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/patches.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/pricing.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/prompts.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/__init__.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/_retry.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/anthropic.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/azure.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/cerebras.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/chat.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/contracts.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/deepseek.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/fake.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/fireworks.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/gemini.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/github_models.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/groq.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/image.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/mistral.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/nvidia.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/ollama.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/openai.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/openrouter.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/perplexity.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/qwen.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/speech.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/together.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/video.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/xai.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/providers/zai.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/py.typed +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/quick/__init__.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/quick/_shared.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/quick/agent.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/quick/chat.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/quick/models.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/quick/rag.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/quick/tools_agent.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/quota.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/__init__.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/cleanup.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/conversation.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/identity.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/inputs.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/memory.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/plan_execute.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/reflection.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/resolve.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/rollback.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/search.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/skills.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/status.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/supervisor.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/text.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/recipes/tool_loop.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/redaction.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/relations.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/replay.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/request.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/resources.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/routing.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/scheduler.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/session.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact/SKILL.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact/references/effects-and-state.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact/references/trust-and-safety.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-agents/SKILL.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-agents/references/consume.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-agents/references/effects.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-eval/SKILL.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-eval/references/online.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-from-langchain/SKILL.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-llm/SKILL.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-llm/references/providers.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-llm/references/tools.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-observability/SKILL.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-rag/SKILL.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-rag/references/sources.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/skills/reactifact-testing/SKILL.md +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/sources.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/streaming.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/structured.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/testing/__init__.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/testing/assertions.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/testing/exceptions.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/testing/fault.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/testing/golden.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/testing/lab.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/testing/mock.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/testing/pytest_plugin.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/testing/record.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/testing/registry.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tool_use.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tools.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/_otlp.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/columns.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/templates/sessions.html +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/templates/ui.html +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/tracing/web.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/triggers.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/types.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/verify.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/viz.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact/web.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact.egg-info/SOURCES.txt +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact.egg-info/dependency_links.txt +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact.egg-info/entry_points.txt +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact.egg-info/requires.txt +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/reactifact.egg-info/top_level.txt +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/setup.cfg +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_a2a.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_a2a_example.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_a2a_interop.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_absent_consume.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_adaptive.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_agent_tool.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_anthropic_provider.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_artifacts.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_audit.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_authz.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_backbone.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_branching.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_budget.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_cache.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_canonical_ports.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_chat_example.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_chat_memory.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_chat_outbox.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_chat_web.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_checkpoint.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_checkpoints_concurrency.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_cli.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_cli_scenario.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_commit_log.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_compaction.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_concurrency.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_consumes_produces.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_context_builder.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_correlated_consume.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_debounce.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_determinism.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_devops.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_devops_online.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_devops_web.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_effects.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_eval.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_eval_dataset.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_eval_judge.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_eval_online.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_eval_trajectory.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_fintech_audit.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_forklab.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_forklab_web.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_friendly_api.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_gemini_provider.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_golden.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_guardrails.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_hitl.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_image_provider.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_incident_commander.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_invalidation.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_join_consume.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_knowledge.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_knowledge_web.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_llm_ladder.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_logging.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_loop_bound_client.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_mcp.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_medic_lab.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_medic_lab_web.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_metrics.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_multisource.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_native_tool_use.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_openai_provider.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_outbox.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_patches.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_pending_actions.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_ports.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_produce_trigger.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_prompts.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_provider_auth.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_provider_retry.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_providers_integration.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_quick.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_quota.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_reacts_to.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_recipe_conversation.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_recipe_plan_execute.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_recipe_reflection.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_recipe_supervisor.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_recipe_tool_loop.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_recipes.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_recipes_inputs.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_recipes_memory.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_recipes_skills.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_redaction.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_relation_graph.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_relations.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_repair.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_repair_web.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_replay.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_repo_agent.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_request.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_research.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_resources.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_retry.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_routing.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_runtime.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_runtime_errors.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_scheduler_semantics.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_sessions.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_shutdown.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_skills.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_sources.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_sources_search.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_sources_vector.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_speech_provider.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_starter_app.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_streaming.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_structured.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_support_copilot.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_testing_assertions.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_testing_golden.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_testing_lab.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_testing_mock.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_testing_provenance.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_testing_pytest_plugin.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_testing_registry.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_token_budget.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_tools.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_trace_columns.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_trace_tags.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_type_registry.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_vendor_factories.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_vendor_multimodal_factories.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_verify.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_video_provider.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_view.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_viz.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_web_source.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_workspace.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/test_workspace_with_sources.py +0 -0
- {reactifact-0.15.1 → reactifact-0.15.2}/tests/tests_checkpoints_sqlite.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: reactifact
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.2
|
|
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
|
|
@@ -380,7 +380,7 @@ uv sync --extra dev --extra web
|
|
|
380
380
|
|
|
381
381
|
## Status & contributing
|
|
382
382
|
|
|
383
|
-
Active, `0.
|
|
383
|
+
Active, `0.15.x`, pre-1.0 — the API is stabilizing and `1.0` will freeze it
|
|
384
384
|
(see the [roadmap](docs/roadmap.md)); releases follow
|
|
385
385
|
[Keep a Changelog](CHANGELOG.md). Questions and design discussion live in
|
|
386
386
|
[GitHub Discussions](https://github.com/bzdvdn/reactifact/discussions); a star,
|
|
@@ -334,7 +334,7 @@ uv sync --extra dev --extra web
|
|
|
334
334
|
|
|
335
335
|
## Status & contributing
|
|
336
336
|
|
|
337
|
-
Active, `0.
|
|
337
|
+
Active, `0.15.x`, pre-1.0 — the API is stabilizing and `1.0` will freeze it
|
|
338
338
|
(see the [roadmap](docs/roadmap.md)); releases follow
|
|
339
339
|
[Keep a Changelog](CHANGELOG.md). Questions and design discussion live in
|
|
340
340
|
[GitHub Discussions](https://github.com/bzdvdn/reactifact/discussions); a star,
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "reactifact"
|
|
7
|
-
version = "0.15.
|
|
7
|
+
version = "0.15.2"
|
|
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,17 +1,42 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import time
|
|
3
4
|
from abc import ABC
|
|
4
5
|
from collections.abc import Sequence
|
|
6
|
+
from contextvars import ContextVar, Token
|
|
5
7
|
from typing import Any
|
|
6
8
|
|
|
7
9
|
from .artifacts import Artifact
|
|
8
10
|
from .consume import Consume
|
|
9
11
|
from .context import Context
|
|
12
|
+
from .effects import current_effects
|
|
10
13
|
from .events import Event, EventType
|
|
11
14
|
from .patches import Patch
|
|
12
15
|
from .produce import Produce, ProduceCall
|
|
13
16
|
from .triggers import Trigger
|
|
14
17
|
|
|
18
|
+
#: Per-task log of the produces that ran for the current agent execution:
|
|
19
|
+
#: ``(produce_class_name, operations_authored, latency_ms)``. ``Runtime._execute``
|
|
20
|
+
#: opens it (and later records the span), ``Agent.execute`` appends — a
|
|
21
|
+
#: contextvar so concurrent executions sharing one ``Agent`` instance never race.
|
|
22
|
+
_produce_runs: ContextVar[list[tuple[str, int, float]] | None] = ContextVar(
|
|
23
|
+
"reactifact_produce_runs", default=None
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def begin_produce_runs() -> Token[Any]:
|
|
28
|
+
"""Opens the per-task produce-run log (see ``_produce_runs``)."""
|
|
29
|
+
return _produce_runs.set([])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def produce_runs() -> list[tuple[str, int, float]]:
|
|
33
|
+
"""The produce runs recorded for the current task (empty if none)."""
|
|
34
|
+
return _produce_runs.get() or []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def end_produce_runs(token: Token[Any]) -> None:
|
|
38
|
+
_produce_runs.reset(token)
|
|
39
|
+
|
|
15
40
|
|
|
16
41
|
class Agent(ABC): # noqa: B024 — interface without abstract methods, run() has a default
|
|
17
42
|
"""Base container: consumes some artifacts and produces others.
|
|
@@ -154,14 +179,30 @@ class Agent(ABC): # noqa: B024 — interface without abstract methods, run() ha
|
|
|
154
179
|
if not self.produces:
|
|
155
180
|
return None
|
|
156
181
|
inputs = self._collect_inputs(context)
|
|
182
|
+
slot = current_effects()
|
|
183
|
+
log = _produce_runs.get()
|
|
157
184
|
for p in self.produces:
|
|
158
185
|
runs, trigger = self._resolve_produce_call(p, event, context)
|
|
159
186
|
if not runs:
|
|
160
187
|
continue
|
|
188
|
+
before = len(slot.operations) if slot is not None else 0
|
|
189
|
+
started = time.monotonic()
|
|
161
190
|
call = ProduceCall(
|
|
162
191
|
context=context, inputs=inputs, event=event, trigger=trigger
|
|
163
192
|
)
|
|
164
|
-
|
|
193
|
+
try:
|
|
194
|
+
await p.produce(call)
|
|
195
|
+
finally:
|
|
196
|
+
# Record the produce even when it raises — a failing produce is
|
|
197
|
+
# exactly what a trace should show (the span is then the error
|
|
198
|
+
# span, with this produce's partial operations/latency).
|
|
199
|
+
if log is not None:
|
|
200
|
+
operations = (
|
|
201
|
+
len(slot.operations) if slot is not None else 0
|
|
202
|
+
) - before
|
|
203
|
+
log.append(
|
|
204
|
+
(p.name, operations, (time.monotonic() - started) * 1000)
|
|
205
|
+
)
|
|
165
206
|
return None
|
|
166
207
|
|
|
167
208
|
@staticmethod
|
|
@@ -266,6 +266,37 @@ class Produce(Generic[TOut]):
|
|
|
266
266
|
"""
|
|
267
267
|
return None
|
|
268
268
|
|
|
269
|
+
#: Explicit display name (set by e.g. tool-use produces); when unset,
|
|
270
|
+
#: `name` derives a readable one from the class/function/artifact type.
|
|
271
|
+
_name: str | None = None
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def name(self) -> str:
|
|
275
|
+
"""A human-readable name for traces/logs.
|
|
276
|
+
|
|
277
|
+
Subclass-style produces use their class name; an ``@produce``-decorated
|
|
278
|
+
function uses the wrapped function's name; a bare ``Produce(Model)``
|
|
279
|
+
(declared only to widen an agent's allowed ``Create`` types) falls back
|
|
280
|
+
to its output artifact type — otherwise every such no-op would show up
|
|
281
|
+
as a generic ``Produce``. An explicit ``name`` (e.g. a tool-use produce's)
|
|
282
|
+
always wins.
|
|
283
|
+
"""
|
|
284
|
+
explicit = getattr(self, "_name", None)
|
|
285
|
+
if explicit:
|
|
286
|
+
return str(explicit)
|
|
287
|
+
cls = type(self).__name__
|
|
288
|
+
if cls not in ("Produce", "_FunctionProduce"):
|
|
289
|
+
return cls
|
|
290
|
+
func = getattr(self, "_func", None)
|
|
291
|
+
if func is not None:
|
|
292
|
+
return str(getattr(func, "__name__", cls))
|
|
293
|
+
artifact = getattr(self.artifact_type, "__name__", "")
|
|
294
|
+
return f"Produce[{artifact}]" if artifact else cls
|
|
295
|
+
|
|
296
|
+
@name.setter
|
|
297
|
+
def name(self, value: str) -> None:
|
|
298
|
+
self._name = value
|
|
299
|
+
|
|
269
300
|
def _apply_result(self, result: Any) -> None:
|
|
270
301
|
"""Writes a `@produce`-decorated function's return-style result into
|
|
271
302
|
the effect slot (§24).
|
|
@@ -319,6 +350,8 @@ def produce(
|
|
|
319
350
|
)
|
|
320
351
|
|
|
321
352
|
class _FunctionProduce(Produce[Any]):
|
|
353
|
+
_func: Callable[[ProduceCall], Any]
|
|
354
|
+
|
|
322
355
|
async def produce(self, call: ProduceCall) -> None:
|
|
323
356
|
result = func(call)
|
|
324
357
|
if asyncio.iscoroutine(result):
|
|
@@ -329,6 +362,8 @@ def produce(
|
|
|
329
362
|
instance = _FunctionProduce(
|
|
330
363
|
artifact_type=artifact_type, also_creates=also_creates, reacts_to=reacts_to
|
|
331
364
|
)
|
|
365
|
+
# Keep the function for `Produce.name` (a readable trace/log label).
|
|
366
|
+
instance._func = func
|
|
332
367
|
return instance
|
|
333
368
|
|
|
334
369
|
return decorator
|
|
@@ -7,7 +7,7 @@ from collections.abc import AsyncIterator, Awaitable, Callable, Mapping
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from typing import Any, Literal, cast
|
|
9
9
|
|
|
10
|
-
from .agents import Agent
|
|
10
|
+
from .agents import Agent, begin_produce_runs, end_produce_runs, produce_runs
|
|
11
11
|
from .artifacts import Artifact
|
|
12
12
|
from .budget import Budget, BudgetLLM, BudgetTracker, RunOutcome, RunStats
|
|
13
13
|
from .commit import Commit, Read, Write
|
|
@@ -31,9 +31,17 @@ logger = get_logger(__name__)
|
|
|
31
31
|
#: A scheduled patch with its trigger reads and (optional) trace span.
|
|
32
32
|
PatchWork = tuple[Patch, Agent, list[Read], AgentSpan | None]
|
|
33
33
|
#: One agent execution result: patch (if any), the agent/event/reads that
|
|
34
|
-
#: produced it, latency,
|
|
35
|
-
#: `isolate_errors` was set.
|
|
36
|
-
AgentResult = tuple[
|
|
34
|
+
#: produced it, latency, the exception if the produce raised and
|
|
35
|
+
#: `isolate_errors` was set, and the produces that ran (name/ops/latency).
|
|
36
|
+
AgentResult = tuple[
|
|
37
|
+
Patch | None,
|
|
38
|
+
Agent,
|
|
39
|
+
Event,
|
|
40
|
+
list[Read],
|
|
41
|
+
float,
|
|
42
|
+
BaseException | None,
|
|
43
|
+
list[tuple[str, int, float]],
|
|
44
|
+
]
|
|
37
45
|
#: Performs one committed `PendingAction` (outbox). Called by the runtime after
|
|
38
46
|
#: the intent is committed; the app performs the real I/O and returns (or
|
|
39
47
|
#: raises). Must be idempotent on `idempotency_key` (§42).
|
|
@@ -650,18 +658,22 @@ class Runtime:
|
|
|
650
658
|
"""
|
|
651
659
|
patches_to_apply: list[PatchWork] = []
|
|
652
660
|
runs = 0
|
|
653
|
-
for patch, agent, event, reads, latency, error in results:
|
|
661
|
+
for patch, agent, event, reads, latency, error, produce_run_list in results:
|
|
654
662
|
if self._budget_exhausted():
|
|
655
663
|
break
|
|
656
664
|
runs += 1
|
|
657
665
|
self._runs_used += 1
|
|
658
666
|
if error is not None:
|
|
659
667
|
self._errors_used += 1
|
|
660
|
-
self._trace.record_span(
|
|
668
|
+
self._trace.record_span(
|
|
669
|
+
agent, event, reads, latency, error=error, produces=produce_run_list
|
|
670
|
+
)
|
|
661
671
|
continue
|
|
662
672
|
if patch is None or patch.is_empty():
|
|
663
673
|
continue
|
|
664
|
-
span = self._trace.record_span(
|
|
674
|
+
span = self._trace.record_span(
|
|
675
|
+
agent, event, reads, latency, produces=produce_run_list
|
|
676
|
+
)
|
|
665
677
|
self._validate_patch_types(patch, agent)
|
|
666
678
|
self._enforce_authorization(patch)
|
|
667
679
|
patch = self._apply_guardrails(patch)
|
|
@@ -794,6 +806,7 @@ class Runtime:
|
|
|
794
806
|
task = asyncio.current_task()
|
|
795
807
|
self._trace.register_task(task, agent.name)
|
|
796
808
|
effects_token = set_effects(Effects(self.context))
|
|
809
|
+
produce_runs_token = begin_produce_runs()
|
|
797
810
|
slot: Effects | None = None
|
|
798
811
|
patch: Patch | None = None
|
|
799
812
|
error: BaseException | None = None
|
|
@@ -817,6 +830,8 @@ class Runtime:
|
|
|
817
830
|
if self.on_agent_error is not None:
|
|
818
831
|
self.on_agent_error(agent, event, exc)
|
|
819
832
|
finally:
|
|
833
|
+
runs = produce_runs()
|
|
834
|
+
end_produce_runs(produce_runs_token)
|
|
820
835
|
reset_effects(effects_token)
|
|
821
836
|
self._trace.unregister_task(task)
|
|
822
837
|
latency = (time.monotonic() - started) * 1000
|
|
@@ -829,7 +844,7 @@ class Runtime:
|
|
|
829
844
|
"error": repr(error),
|
|
830
845
|
},
|
|
831
846
|
)
|
|
832
|
-
return None, agent, event, reads, latency, error
|
|
847
|
+
return None, agent, event, reads, latency, error, runs
|
|
833
848
|
logger.debug(
|
|
834
849
|
"agent finished",
|
|
835
850
|
extra={"agent": agent.name, "latency_ms": round(latency, 2)},
|
|
@@ -843,7 +858,7 @@ class Runtime:
|
|
|
843
858
|
combined.merge(patch)
|
|
844
859
|
combined.merge(slot.to_patch())
|
|
845
860
|
patch = combined
|
|
846
|
-
return patch, agent, event, reads, latency, None
|
|
861
|
+
return patch, agent, event, reads, latency, None, runs
|
|
847
862
|
|
|
848
863
|
async def arun(
|
|
849
864
|
self,
|
|
@@ -11,6 +11,7 @@ from .models import (
|
|
|
11
11
|
AgentSpan,
|
|
12
12
|
ArtifactRef,
|
|
13
13
|
LLMCall,
|
|
14
|
+
ProduceRun,
|
|
14
15
|
RelationRef,
|
|
15
16
|
RunTrace,
|
|
16
17
|
Tag,
|
|
@@ -35,6 +36,7 @@ __all__ = [
|
|
|
35
36
|
"LangfuseTracer",
|
|
36
37
|
"OTLPTracer",
|
|
37
38
|
"PostgresStore",
|
|
39
|
+
"ProduceRun",
|
|
38
40
|
"RecordingLLM",
|
|
39
41
|
"RelationRef",
|
|
40
42
|
"RunTrace",
|
|
@@ -96,6 +96,11 @@ class LangfuseTracer(Tracer):
|
|
|
96
96
|
)
|
|
97
97
|
span_start = span.started_at or run_start
|
|
98
98
|
agent_span_id = _span_id(f"{trace_id}:span:{i}:{span.agent}")
|
|
99
|
+
spans.extend(
|
|
100
|
+
self._produce_spans(
|
|
101
|
+
trace_id, agent_span_id, trace_attrs, run_start, i, span
|
|
102
|
+
)
|
|
103
|
+
)
|
|
99
104
|
for j, call in enumerate(span.llm_calls):
|
|
100
105
|
spans.append(
|
|
101
106
|
self._llm_span(
|
|
@@ -145,8 +150,29 @@ class LangfuseTracer(Tracer):
|
|
|
145
150
|
_type_summary(span.writes),
|
|
146
151
|
),
|
|
147
152
|
]
|
|
153
|
+
if span.event_artifact_type:
|
|
154
|
+
attrs.append(
|
|
155
|
+
_attr(
|
|
156
|
+
"langfuse.observation.metadata.event_artifact_type",
|
|
157
|
+
span.event_artifact_type,
|
|
158
|
+
)
|
|
159
|
+
)
|
|
148
160
|
if span.error:
|
|
149
161
|
attrs.append(_attr("langfuse.observation.metadata.error", span.error))
|
|
162
|
+
if span.produces:
|
|
163
|
+
attrs.append(
|
|
164
|
+
_attr(
|
|
165
|
+
"langfuse.observation.metadata.produces",
|
|
166
|
+
[
|
|
167
|
+
{
|
|
168
|
+
"name": p.name,
|
|
169
|
+
"operations": p.operations,
|
|
170
|
+
"latency_ms": p.latency_ms,
|
|
171
|
+
}
|
|
172
|
+
for p in span.produces
|
|
173
|
+
],
|
|
174
|
+
)
|
|
175
|
+
)
|
|
150
176
|
return {
|
|
151
177
|
"traceId": trace_id,
|
|
152
178
|
"spanId": _span_id(f"{trace_id}:span:{index}:{span.agent}"),
|
|
@@ -158,6 +184,53 @@ class LangfuseTracer(Tracer):
|
|
|
158
184
|
"attributes": attrs,
|
|
159
185
|
}
|
|
160
186
|
|
|
187
|
+
def _produce_spans(
|
|
188
|
+
self,
|
|
189
|
+
trace_id: str,
|
|
190
|
+
parent_span_id: str,
|
|
191
|
+
trace_attrs: list[dict[str, Any]],
|
|
192
|
+
run_start: datetime,
|
|
193
|
+
span_index: int,
|
|
194
|
+
span: AgentSpan,
|
|
195
|
+
) -> list[dict[str, Any]]:
|
|
196
|
+
"""One child observation per produce the agent ran, so Langfuse's
|
|
197
|
+
waterfall shows the ``Consume → Produce`` steps instead of a single
|
|
198
|
+
opaque agent span.
|
|
199
|
+
|
|
200
|
+
Produces run sequentially within an agent, so they are laid out
|
|
201
|
+
back-to-back from the span start by each one's measured latency.
|
|
202
|
+
"""
|
|
203
|
+
if not span.produces:
|
|
204
|
+
return []
|
|
205
|
+
cursor = span.started_at or run_start
|
|
206
|
+
out: list[dict[str, Any]] = []
|
|
207
|
+
for k, produce in enumerate(span.produces):
|
|
208
|
+
end = cursor + timedelta(milliseconds=produce.latency_ms)
|
|
209
|
+
out.append(
|
|
210
|
+
{
|
|
211
|
+
"traceId": trace_id,
|
|
212
|
+
"spanId": _span_id(
|
|
213
|
+
f"{trace_id}:produce:{span_index}:{k}:{produce.name}"
|
|
214
|
+
),
|
|
215
|
+
"parentSpanId": parent_span_id,
|
|
216
|
+
"name": produce.name,
|
|
217
|
+
"kind": 1,
|
|
218
|
+
"startTimeUnixNano": _unix_nanos(cursor),
|
|
219
|
+
"endTimeUnixNano": _unix_nanos(end),
|
|
220
|
+
"attributes": [
|
|
221
|
+
*trace_attrs,
|
|
222
|
+
_attr("langfuse.observation.type", "span"),
|
|
223
|
+
_attr("langfuse.observation.metadata.agent", span.agent),
|
|
224
|
+
_attr(
|
|
225
|
+
"langfuse.observation.metadata.operations",
|
|
226
|
+
produce.operations,
|
|
227
|
+
),
|
|
228
|
+
],
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
cursor = end
|
|
232
|
+
return out
|
|
233
|
+
|
|
161
234
|
def _llm_span(
|
|
162
235
|
self,
|
|
163
236
|
trace_id: str,
|
|
@@ -88,11 +88,31 @@ class TagAssignment(BaseModel):
|
|
|
88
88
|
updated_at: datetime | None = None
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
class ProduceRun(BaseModel):
|
|
92
|
+
"""One produce execution within an agent span (§54).
|
|
93
|
+
|
|
94
|
+
An agent runs one or more ``Produce``s per event; the span records which
|
|
95
|
+
ones actually ran and how much each contributed (``operations`` = authored
|
|
96
|
+
effect operations, so the produce that did the work is distinguishable from
|
|
97
|
+
one that merely matched the event).
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
name: str
|
|
101
|
+
operations: int = 0
|
|
102
|
+
latency_ms: float = 0.0
|
|
103
|
+
|
|
104
|
+
|
|
91
105
|
class AgentSpan(BaseModel):
|
|
92
106
|
"""A single agent execution within a run (§54)."""
|
|
93
107
|
|
|
94
108
|
agent: str
|
|
95
109
|
event_type: str = ""
|
|
110
|
+
#: The artifact type whose change triggered this agent (its matched
|
|
111
|
+
#: ``Consume``) — e.g. ``DraftAnswer`` vs ``FinalResponse`` for an agent that
|
|
112
|
+
#: consumes both. ``event_type`` alone (created/updated) can't tell them apart.
|
|
113
|
+
event_artifact_type: str = ""
|
|
114
|
+
#: The produces that ran for this event, in execution order.
|
|
115
|
+
produces: list[ProduceRun] = Field(default_factory=list)
|
|
96
116
|
reads: list[ArtifactRef] = Field(default_factory=list)
|
|
97
117
|
writes: list[ArtifactRef] = Field(default_factory=list)
|
|
98
118
|
relations: list[RelationRef] = Field(default_factory=list)
|
|
@@ -123,8 +123,19 @@ class OTLPTracer(Tracer):
|
|
|
123
123
|
attr("reactifact.reads", type_summary(span.reads)),
|
|
124
124
|
attr("reactifact.writes", type_summary(span.writes)),
|
|
125
125
|
]
|
|
126
|
+
if span.event_artifact_type:
|
|
127
|
+
attrs.append(
|
|
128
|
+
attr("reactifact.event_artifact_type", span.event_artifact_type)
|
|
129
|
+
)
|
|
126
130
|
if span.error:
|
|
127
131
|
attrs.append(attr("error.type", span.error))
|
|
132
|
+
if span.produces:
|
|
133
|
+
attrs.append(
|
|
134
|
+
attr(
|
|
135
|
+
"reactifact.produces",
|
|
136
|
+
", ".join(f"{p.name}({p.operations})" for p in span.produces),
|
|
137
|
+
)
|
|
138
|
+
)
|
|
128
139
|
return {
|
|
129
140
|
"traceId": tid,
|
|
130
141
|
"spanId": span_id(f"{tid}:span:{index}:{span.agent}"),
|
|
@@ -27,6 +27,7 @@ from .models import (
|
|
|
27
27
|
AgentSpan,
|
|
28
28
|
ArtifactRef,
|
|
29
29
|
LLMCall,
|
|
30
|
+
ProduceRun,
|
|
30
31
|
RelationRef,
|
|
31
32
|
RunTrace,
|
|
32
33
|
TagAssignment,
|
|
@@ -70,13 +71,15 @@ class PostgresStore:
|
|
|
70
71
|
run_id TEXT NOT NULL REFERENCES runs(id),
|
|
71
72
|
agent TEXT NOT NULL,
|
|
72
73
|
event_type TEXT NOT NULL DEFAULT '',
|
|
74
|
+
event_artifact_type TEXT NOT NULL DEFAULT '',
|
|
73
75
|
latency_ms REAL NOT NULL DEFAULT 0,
|
|
74
76
|
error TEXT,
|
|
75
77
|
started_at TIMESTAMPTZ,
|
|
76
78
|
reads JSONB NOT NULL DEFAULT '[]',
|
|
77
79
|
writes JSONB NOT NULL DEFAULT '[]',
|
|
78
80
|
relations JSONB NOT NULL DEFAULT '[]',
|
|
79
|
-
llm_calls JSONB NOT NULL DEFAULT '[]'
|
|
81
|
+
llm_calls JSONB NOT NULL DEFAULT '[]',
|
|
82
|
+
produces JSONB NOT NULL DEFAULT '[]'
|
|
80
83
|
)
|
|
81
84
|
"""
|
|
82
85
|
)
|
|
@@ -122,6 +125,14 @@ class PostgresStore:
|
|
|
122
125
|
await cur.execute(
|
|
123
126
|
"ALTER TABLE spans ADD COLUMN IF NOT EXISTS started_at TIMESTAMPTZ"
|
|
124
127
|
)
|
|
128
|
+
await cur.execute(
|
|
129
|
+
"ALTER TABLE spans ADD COLUMN IF NOT EXISTS "
|
|
130
|
+
"produces JSONB NOT NULL DEFAULT '[]'"
|
|
131
|
+
)
|
|
132
|
+
await cur.execute(
|
|
133
|
+
"ALTER TABLE spans ADD COLUMN IF NOT EXISTS "
|
|
134
|
+
"event_artifact_type TEXT NOT NULL DEFAULT ''"
|
|
135
|
+
)
|
|
125
136
|
await conn.commit()
|
|
126
137
|
finally:
|
|
127
138
|
await conn.close()
|
|
@@ -159,13 +170,15 @@ class PostgresStore:
|
|
|
159
170
|
if span_started is not None and span_started.tzinfo is None:
|
|
160
171
|
span_started = span_started.replace(tzinfo=UTC)
|
|
161
172
|
await cur.execute(
|
|
162
|
-
"INSERT INTO spans (run_id, agent, event_type,
|
|
163
|
-
"started_at, reads, writes, relations, llm_calls
|
|
164
|
-
"
|
|
173
|
+
"INSERT INTO spans (run_id, agent, event_type, event_artifact_type, "
|
|
174
|
+
"latency_ms, error, started_at, reads, writes, relations, llm_calls, "
|
|
175
|
+
"produces) "
|
|
176
|
+
"VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
|
|
165
177
|
(
|
|
166
178
|
trace.id,
|
|
167
179
|
span.agent,
|
|
168
180
|
span.event_type,
|
|
181
|
+
span.event_artifact_type,
|
|
169
182
|
span.latency_ms,
|
|
170
183
|
span.error,
|
|
171
184
|
span_started,
|
|
@@ -181,6 +194,9 @@ class PostgresStore:
|
|
|
181
194
|
psycopg.types.json.Jsonb(
|
|
182
195
|
[c.model_dump(mode="json") for c in span.llm_calls]
|
|
183
196
|
),
|
|
197
|
+
psycopg.types.json.Jsonb(
|
|
198
|
+
[p.model_dump(mode="json") for p in span.produces]
|
|
199
|
+
),
|
|
184
200
|
),
|
|
185
201
|
)
|
|
186
202
|
await conn.commit()
|
|
@@ -387,8 +403,8 @@ class PostgresStore:
|
|
|
387
403
|
if row is None:
|
|
388
404
|
return None
|
|
389
405
|
await cur.execute(
|
|
390
|
-
"SELECT agent, event_type, latency_ms, error,
|
|
391
|
-
"reads, writes, relations, llm_calls "
|
|
406
|
+
"SELECT agent, event_type, event_artifact_type, latency_ms, error, "
|
|
407
|
+
"started_at, reads, writes, relations, llm_calls, produces "
|
|
392
408
|
"FROM spans WHERE run_id = %s ORDER BY id",
|
|
393
409
|
(trace_id,),
|
|
394
410
|
)
|
|
@@ -408,13 +424,15 @@ class PostgresStore:
|
|
|
408
424
|
AgentSpan(
|
|
409
425
|
agent=r[0],
|
|
410
426
|
event_type=r[1],
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
427
|
+
event_artifact_type=r[2],
|
|
428
|
+
latency_ms=r[3],
|
|
429
|
+
error=r[4],
|
|
430
|
+
started_at=r[5],
|
|
431
|
+
reads=[ArtifactRef(**d) for d in r[6]],
|
|
432
|
+
writes=[ArtifactRef(**d) for d in r[7]],
|
|
433
|
+
relations=[RelationRef(**d) for d in r[8]],
|
|
434
|
+
llm_calls=[LLMCall(**d) for d in r[9]],
|
|
435
|
+
produces=[ProduceRun(**d) for d in r[10]],
|
|
418
436
|
)
|
|
419
437
|
for r in span_rows
|
|
420
438
|
]
|
|
@@ -30,6 +30,7 @@ from .models import (
|
|
|
30
30
|
AgentSpan,
|
|
31
31
|
ArtifactRef,
|
|
32
32
|
LLMCall,
|
|
33
|
+
ProduceRun,
|
|
33
34
|
RelationRef,
|
|
34
35
|
RunTrace,
|
|
35
36
|
TagAssignment,
|
|
@@ -174,13 +175,15 @@ class TraceStore:
|
|
|
174
175
|
run_id TEXT NOT NULL REFERENCES runs(id),
|
|
175
176
|
agent TEXT NOT NULL,
|
|
176
177
|
event_type TEXT NOT NULL DEFAULT '',
|
|
178
|
+
event_artifact_type TEXT NOT NULL DEFAULT '',
|
|
177
179
|
latency_ms REAL NOT NULL DEFAULT 0,
|
|
178
180
|
error TEXT,
|
|
179
181
|
started_at REAL,
|
|
180
182
|
reads TEXT NOT NULL DEFAULT '[]',
|
|
181
183
|
writes TEXT NOT NULL DEFAULT '[]',
|
|
182
184
|
relations TEXT NOT NULL DEFAULT '[]',
|
|
183
|
-
llm_calls TEXT NOT NULL DEFAULT '[]'
|
|
185
|
+
llm_calls TEXT NOT NULL DEFAULT '[]',
|
|
186
|
+
produces TEXT NOT NULL DEFAULT '[]'
|
|
184
187
|
);
|
|
185
188
|
CREATE TABLE IF NOT EXISTS tags (
|
|
186
189
|
name TEXT PRIMARY KEY,
|
|
@@ -218,6 +221,14 @@ class TraceStore:
|
|
|
218
221
|
)
|
|
219
222
|
if "started_at" not in cols:
|
|
220
223
|
self._conn.execute("ALTER TABLE spans ADD COLUMN started_at REAL")
|
|
224
|
+
if "produces" not in cols:
|
|
225
|
+
self._conn.execute(
|
|
226
|
+
"ALTER TABLE spans ADD COLUMN produces TEXT NOT NULL DEFAULT '[]'"
|
|
227
|
+
)
|
|
228
|
+
if "event_artifact_type" not in cols:
|
|
229
|
+
self._conn.execute(
|
|
230
|
+
"ALTER TABLE spans ADD COLUMN event_artifact_type TEXT NOT NULL DEFAULT ''"
|
|
231
|
+
)
|
|
221
232
|
run_cols = {row[1] for row in self._conn.execute("PRAGMA table_info(runs)")}
|
|
222
233
|
if "prompt_tokens" not in run_cols:
|
|
223
234
|
self._conn.execute(
|
|
@@ -328,13 +339,15 @@ class TraceStore:
|
|
|
328
339
|
)
|
|
329
340
|
for span in trace.spans:
|
|
330
341
|
self._conn.execute(
|
|
331
|
-
"INSERT INTO spans (run_id, agent, event_type,
|
|
332
|
-
"started_at, reads, writes, relations, llm_calls
|
|
333
|
-
"
|
|
342
|
+
"INSERT INTO spans (run_id, agent, event_type, event_artifact_type, "
|
|
343
|
+
"latency_ms, error, started_at, reads, writes, relations, llm_calls, "
|
|
344
|
+
"produces) "
|
|
345
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
334
346
|
(
|
|
335
347
|
trace.id,
|
|
336
348
|
span.agent,
|
|
337
349
|
span.event_type,
|
|
350
|
+
span.event_artifact_type,
|
|
338
351
|
span.latency_ms,
|
|
339
352
|
span.error,
|
|
340
353
|
span.started_at.timestamp() if span.started_at else None,
|
|
@@ -342,6 +355,7 @@ class TraceStore:
|
|
|
342
355
|
json.dumps([w.model_dump(mode="json") for w in span.writes]),
|
|
343
356
|
json.dumps([r.model_dump(mode="json") for r in span.relations]),
|
|
344
357
|
json.dumps([c.model_dump(mode="json") for c in span.llm_calls]),
|
|
358
|
+
json.dumps([p.model_dump(mode="json") for p in span.produces]),
|
|
345
359
|
),
|
|
346
360
|
)
|
|
347
361
|
self._conn.commit()
|
|
@@ -595,8 +609,8 @@ class TraceStore:
|
|
|
595
609
|
if row is None:
|
|
596
610
|
return None
|
|
597
611
|
span_rows = self._conn.execute(
|
|
598
|
-
"SELECT agent, event_type, latency_ms, error, started_at, "
|
|
599
|
-
"reads, writes, relations, llm_calls "
|
|
612
|
+
"SELECT agent, event_type, event_artifact_type, latency_ms, error, started_at, "
|
|
613
|
+
"reads, writes, relations, llm_calls, produces "
|
|
600
614
|
"FROM spans WHERE run_id = ? ORDER BY id",
|
|
601
615
|
(trace_id,),
|
|
602
616
|
).fetchall()
|
|
@@ -604,15 +618,17 @@ class TraceStore:
|
|
|
604
618
|
AgentSpan(
|
|
605
619
|
agent=r[0],
|
|
606
620
|
event_type=r[1],
|
|
607
|
-
|
|
608
|
-
|
|
621
|
+
event_artifact_type=r[2],
|
|
622
|
+
latency_ms=r[3],
|
|
623
|
+
error=r[4],
|
|
609
624
|
started_at=(
|
|
610
|
-
datetime.fromtimestamp(r[
|
|
625
|
+
datetime.fromtimestamp(r[5], tz=UTC) if r[5] is not None else None
|
|
611
626
|
),
|
|
612
|
-
reads=[ArtifactRef(**d) for d in json.loads(r[
|
|
613
|
-
writes=[ArtifactRef(**d) for d in json.loads(r[
|
|
614
|
-
relations=[RelationRef(**d) for d in json.loads(r[
|
|
615
|
-
llm_calls=[LLMCall(**d) for d in json.loads(r[
|
|
627
|
+
reads=[ArtifactRef(**d) for d in json.loads(r[6])],
|
|
628
|
+
writes=[ArtifactRef(**d) for d in json.loads(r[7])],
|
|
629
|
+
relations=[RelationRef(**d) for d in json.loads(r[8])],
|
|
630
|
+
llm_calls=[LLMCall(**d) for d in json.loads(r[9])],
|
|
631
|
+
produces=[ProduceRun(**d) for d in json.loads(r[10])],
|
|
616
632
|
)
|
|
617
633
|
for r in span_rows
|
|
618
634
|
]
|
|
@@ -258,11 +258,22 @@ tbody tr.row.selected td{background:var(--accent-soft);}
|
|
|
258
258
|
details.agent{border:1px solid var(--border);border-radius:var(--r-md);background:var(--panel);overflow:hidden;}
|
|
259
259
|
details.agent>summary{display:flex;align-items:center;gap:12px;padding:12px 16px;cursor:pointer;font-size:14px;}
|
|
260
260
|
details.agent>summary:hover{background:var(--hover);}
|
|
261
|
-
.caret{color:var(--muted);transition:transform .15s;} details[open]
|
|
261
|
+
.caret{color:var(--muted);transition:transform .15s;} details[open]>summary .caret,.caret.open{transform:rotate(90deg);}
|
|
262
262
|
.agent .muted,.spans-stat{color:var(--muted);font-size:12.5px;font-variant-numeric:tabular-nums;}
|
|
263
263
|
.agent-body{padding:6px 16px 16px;border-top:1px solid var(--border);background:var(--panel-alt);}
|
|
264
264
|
.chips{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0;}
|
|
265
265
|
.chip{display:inline-flex;align-items:center;gap:6px;background:var(--accent-soft);color:var(--accent);border-radius:12px;padding:2px 9px;font-family:var(--mono);font-size:11.5px;}
|
|
266
|
+
.flow{display:inline-flex;align-items:center;gap:7px;min-width:0;flex-wrap:wrap;}
|
|
267
|
+
.flow .c-side{display:inline-flex;align-items:center;gap:5px;min-width:0;}
|
|
268
|
+
.flow .consume{font-family:var(--mono);font-size:11.5px;color:var(--chip-text);background:var(--chip);border-radius:6px;padding:1px 7px;white-space:nowrap;}
|
|
269
|
+
.flow .consume.muted{color:var(--muted);background:transparent;border:1px dashed var(--border-strong);}
|
|
270
|
+
.flow .ev{font-family:var(--mono);font-size:10.5px;color:var(--faint);white-space:nowrap;}
|
|
271
|
+
.flow .arrow{color:var(--muted);font-family:var(--mono);}
|
|
272
|
+
.produces{display:inline-flex;flex-wrap:wrap;gap:5px;min-width:0;}
|
|
273
|
+
.chip.produce{display:inline-flex;align-items:center;gap:6px;font-family:var(--mono);font-size:11.5px;background:var(--ok-soft);color:var(--ok);border-radius:8px;padding:1px 4px 1px 8px;white-space:nowrap;}
|
|
274
|
+
.chip.produce.noop{background:var(--panel-alt);color:var(--muted);border:1px solid var(--border);}
|
|
275
|
+
.chip.produce .ops{background:rgba(21,128,61,.16);color:var(--ok);border-radius:7px;padding:0 6px;font-size:10px;font-variant-numeric:tabular-nums;}
|
|
276
|
+
.chip.produce.noop .ops.zero{background:var(--border);color:var(--faint);}
|
|
266
277
|
.spans{display:flex;flex-direction:column;gap:8px;}
|
|
267
278
|
details.node{border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:var(--r-sm);background:var(--panel);overflow:hidden;}
|
|
268
279
|
details.node summary{display:flex;align-items:center;gap:10px;padding:9px 14px;cursor:pointer;font-size:13px;}
|