whileai 0.64__tar.gz → 0.65__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.
- {whileai-0.64/whileai.egg-info → whileai-0.65}/PKG-INFO +40 -2
- {whileai-0.64 → whileai-0.65}/README.md +38 -1
- {whileai-0.64 → whileai-0.65}/pyproject.toml +2 -2
- {whileai-0.64 → whileai-0.65}/whileai/ingest.py +13 -6
- whileai-0.65/whileai/platform.py +916 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/__init__.py +2 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/data.py +11 -11
- {whileai-0.64 → whileai-0.65}/whileai/simulations/ingest/platform.py +58 -1
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/judging.py +16 -6
- {whileai-0.64 → whileai-0.65/whileai.egg-info}/PKG-INFO +40 -2
- {whileai-0.64 → whileai-0.65}/whileai.egg-info/SOURCES.txt +1 -0
- {whileai-0.64 → whileai-0.65}/whileai.egg-info/requires.txt +1 -0
- {whileai-0.64 → whileai-0.65}/LICENSE +0 -0
- {whileai-0.64 → whileai-0.65}/MANIFEST.in +0 -0
- {whileai-0.64 → whileai-0.65}/setup.cfg +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/__init__.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/_env.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/auth.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/cli.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/init_evals.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/py.typed +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/__main__.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/environment.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/export.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/__init__.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/actionspace.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/adapters.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/agents.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/anthropic_backend.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/coverage.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/diversity.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/embeddings.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/explore.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/generator.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/offline_agent.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/scenarios.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/generate/usage_meter.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/ingest/__init__.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/ingest/otel.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/ingest/traces.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/monitor.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/py.typed +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/run/__init__.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/run/config.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/run/engine.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/run/rows.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/run/spec.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/schema.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/schemas/row-v1.json +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/__init__.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/agreement.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/audit.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/checklist.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/curriculum.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/delta.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/grade_llm.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/grading.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/grounding.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/hack_scan.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/hygiene.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/judge_trust.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/labels.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/llm_judge.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/logprobs.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/markers.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/optimize.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/pairwise.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/passat.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/preflight.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/privileged.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/publish_gate.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/quality.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/reference.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/rubric.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/spec.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/stage.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/stats.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/style.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/score/trace.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/simulation.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/text.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/training.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/verify/__init__.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/verify/base.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/verify/code.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/verify/math.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/verify/structured.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/verify/text.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/world/__init__.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/simulations/world/sandbox.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/templates/__init__.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai/templates/evals.py +0 -0
- {whileai-0.64 → whileai-0.65}/whileai.egg-info/dependency_links.txt +0 -0
- {whileai-0.64 → whileai-0.65}/whileai.egg-info/entry_points.txt +0 -0
- {whileai-0.64 → whileai-0.65}/whileai.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: whileai
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.65
|
|
4
4
|
Summary: While Python SDK: trace ingestion and verification client, plus agent simulations grounded in your tools and system prompt.
|
|
5
5
|
Author: While
|
|
6
6
|
License: Apache-2.0
|
|
@@ -24,6 +24,7 @@ Requires-Python: >=3.10
|
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Requires-Dist: requests>=2.25.0
|
|
27
|
+
Requires-Dist: pydantic>=2.0
|
|
27
28
|
Provides-Extra: dev
|
|
28
29
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
29
30
|
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
@@ -919,7 +920,8 @@ The platform's "Make training data" button, as one line:
|
|
|
919
920
|
|
|
920
921
|
```python
|
|
921
922
|
wai.send_score("4bf92f3577b34da6", 1.0) # this run passed
|
|
922
|
-
wai.cuts(agent="my-agent") # what a cut would hold
|
|
923
|
+
summary = wai.cuts(agent="my-agent") # what a cut would hold
|
|
924
|
+
print(wai.format_cuts(summary, agent="my-agent")) # the traces page's sentence
|
|
923
925
|
made = wai.cut(agent="my-agent", kind="rl") # make it
|
|
924
926
|
wai.pull(made["train"]["datasetId"], "train.jsonl")
|
|
925
927
|
made["holdout"]["datasetId"] # measure on this, never train on it
|
|
@@ -1117,6 +1119,42 @@ run.holdout(before=0.42, after=0.58) # did it work? the run page opens with thi
|
|
|
1117
1119
|
|
|
1118
1120
|
A run's page opens with one word — **Better**, **Worse**, **About the same** — over the held-out pass rate before and after. The platform's trainer measures it; a run on your own hardware says it with `run.holdout(before, after)`, or `wai.attach_holdout(run_id, before=..., after=...)` once the run has finished. Pass rates are 0 to 1, so 58% is `0.58`; `metric="loss"` sends held-out loss instead (SFT), where lower is better. `run.delta(...)` and `wai.attach_delta(...)` already measure both sides, so they fill the two numbers in themselves, and add `summary["holdout"]` (also `run.holdout_summary`): each side's pass rate with `n_tasks`, `k` and a `ci95`, plus the delta report's verdict word (`moved`, `moved_unreplicated`, `within_eval_noise`, `no_change_detected`). A hosted run read back with `run.refresh()` has the same block with the interval fields `None` and a note that the platform only returned two numbers.
|
|
1119
1121
|
|
|
1122
|
+
### Report a run so a person can decide
|
|
1123
|
+
|
|
1124
|
+
The platform draws one screen per tracked agent at [while.ai/platform/runs](https://while.ai/platform/runs): the held-out score by version with the frontier model as the line to beat, the training curve, what moved on the behaviors you did not train, the judge checks, live traffic on the served version, and cost. A coding agent fills it with `whileai.platform`; the person reads it and presses Promote. Your agent framework stays yours: `track` takes the agent object you already have (OpenAI Agents SDK, Pydantic AI, LangGraph, Claude Agent SDK) and reads the model, the instructions and the tools off it, or you describe it by hand.
|
|
1125
|
+
|
|
1126
|
+
```python
|
|
1127
|
+
from whileai.platform import Behavior, Frontier, Harness, Judge, track
|
|
1128
|
+
|
|
1129
|
+
tracked = track(
|
|
1130
|
+
"refund-bot", # or track(my_agent): name, model, prompt and tools come from the object
|
|
1131
|
+
model="Qwen/Qwen3-4B",
|
|
1132
|
+
harness=Harness(instructions=SYSTEM_PROMPT, tools=["lookup_order", "issue_refund"]),
|
|
1133
|
+
frontier=Frontier(name="Sonnet 5", score=81, cost_per_1k=18.0),
|
|
1134
|
+
)
|
|
1135
|
+
tracked.behavior(
|
|
1136
|
+
Behavior(
|
|
1137
|
+
name="refunds",
|
|
1138
|
+
test_version="v2",
|
|
1139
|
+
n=240,
|
|
1140
|
+
judge=Judge(agreement=0.86, human_n=60, length_bias=0.08),
|
|
1141
|
+
noise_floor=2.4,
|
|
1142
|
+
contamination=0,
|
|
1143
|
+
reward_is_judge=False,
|
|
1144
|
+
)
|
|
1145
|
+
)
|
|
1146
|
+
|
|
1147
|
+
run = tracked.run("v4", method="GRPO", targets=["refunds"], trained_on=["refunds-grpo"])
|
|
1148
|
+
run.log(10, reward=0.41, kl=0.01) # or trainer.add_callback(wai.TrainerCallback(run))
|
|
1149
|
+
run.score("refunds", 83, ci=2.7, n=240) # every behavior, not only the targets
|
|
1150
|
+
run.score("length", 76, ci=2.8, n=120)
|
|
1151
|
+
run.finish(hours=2.1, gpu="1xH100", cost_usd=31)
|
|
1152
|
+
|
|
1153
|
+
str(tracked.verdict()) # "refunds: v4 beats v3 by 5 (interval excludes zero); 1 regression"
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
Every object is a pydantic model that validates before it leaves the process, and each one's docstring names the rlhfbook.com chapter it comes from. A *harness* is the instructions, tools and model name around the weights; its fingerprint is its version, so a prompt edit shows up as a new version without anyone naming it (Evaluation: a score is only comparable with its setup held constant). A *behavior* has its own frozen held-out test (`test_version`), a `noise_floor` measured by scoring the same model twice, and a judge checked against people (`agreement` over `human_n`) and for `length_bias`. A *run* is scored on every behavior: `targets` are the claim, the rest are the check (Over-Optimization: verbosity, sycophancy and refusals are what moves when the reward is gamed). `ci` is the half-width of the 95% interval; the verdict is whether the candidate's and the served version's intervals overlap. `tracked.live(day, version=, replies=, flagged=)` reports a day of traffic when you serve the model yourself. Logging buffers and never raises into the training loop. Worked example: `recipes/04-train/report-run/`.
|
|
1157
|
+
|
|
1120
1158
|
### Is it hacking the reward right now?
|
|
1121
1159
|
|
|
1122
1160
|
```python
|
|
@@ -883,7 +883,8 @@ The platform's "Make training data" button, as one line:
|
|
|
883
883
|
|
|
884
884
|
```python
|
|
885
885
|
wai.send_score("4bf92f3577b34da6", 1.0) # this run passed
|
|
886
|
-
wai.cuts(agent="my-agent") # what a cut would hold
|
|
886
|
+
summary = wai.cuts(agent="my-agent") # what a cut would hold
|
|
887
|
+
print(wai.format_cuts(summary, agent="my-agent")) # the traces page's sentence
|
|
887
888
|
made = wai.cut(agent="my-agent", kind="rl") # make it
|
|
888
889
|
wai.pull(made["train"]["datasetId"], "train.jsonl")
|
|
889
890
|
made["holdout"]["datasetId"] # measure on this, never train on it
|
|
@@ -1081,6 +1082,42 @@ run.holdout(before=0.42, after=0.58) # did it work? the run page opens with thi
|
|
|
1081
1082
|
|
|
1082
1083
|
A run's page opens with one word — **Better**, **Worse**, **About the same** — over the held-out pass rate before and after. The platform's trainer measures it; a run on your own hardware says it with `run.holdout(before, after)`, or `wai.attach_holdout(run_id, before=..., after=...)` once the run has finished. Pass rates are 0 to 1, so 58% is `0.58`; `metric="loss"` sends held-out loss instead (SFT), where lower is better. `run.delta(...)` and `wai.attach_delta(...)` already measure both sides, so they fill the two numbers in themselves, and add `summary["holdout"]` (also `run.holdout_summary`): each side's pass rate with `n_tasks`, `k` and a `ci95`, plus the delta report's verdict word (`moved`, `moved_unreplicated`, `within_eval_noise`, `no_change_detected`). A hosted run read back with `run.refresh()` has the same block with the interval fields `None` and a note that the platform only returned two numbers.
|
|
1083
1084
|
|
|
1085
|
+
### Report a run so a person can decide
|
|
1086
|
+
|
|
1087
|
+
The platform draws one screen per tracked agent at [while.ai/platform/runs](https://while.ai/platform/runs): the held-out score by version with the frontier model as the line to beat, the training curve, what moved on the behaviors you did not train, the judge checks, live traffic on the served version, and cost. A coding agent fills it with `whileai.platform`; the person reads it and presses Promote. Your agent framework stays yours: `track` takes the agent object you already have (OpenAI Agents SDK, Pydantic AI, LangGraph, Claude Agent SDK) and reads the model, the instructions and the tools off it, or you describe it by hand.
|
|
1088
|
+
|
|
1089
|
+
```python
|
|
1090
|
+
from whileai.platform import Behavior, Frontier, Harness, Judge, track
|
|
1091
|
+
|
|
1092
|
+
tracked = track(
|
|
1093
|
+
"refund-bot", # or track(my_agent): name, model, prompt and tools come from the object
|
|
1094
|
+
model="Qwen/Qwen3-4B",
|
|
1095
|
+
harness=Harness(instructions=SYSTEM_PROMPT, tools=["lookup_order", "issue_refund"]),
|
|
1096
|
+
frontier=Frontier(name="Sonnet 5", score=81, cost_per_1k=18.0),
|
|
1097
|
+
)
|
|
1098
|
+
tracked.behavior(
|
|
1099
|
+
Behavior(
|
|
1100
|
+
name="refunds",
|
|
1101
|
+
test_version="v2",
|
|
1102
|
+
n=240,
|
|
1103
|
+
judge=Judge(agreement=0.86, human_n=60, length_bias=0.08),
|
|
1104
|
+
noise_floor=2.4,
|
|
1105
|
+
contamination=0,
|
|
1106
|
+
reward_is_judge=False,
|
|
1107
|
+
)
|
|
1108
|
+
)
|
|
1109
|
+
|
|
1110
|
+
run = tracked.run("v4", method="GRPO", targets=["refunds"], trained_on=["refunds-grpo"])
|
|
1111
|
+
run.log(10, reward=0.41, kl=0.01) # or trainer.add_callback(wai.TrainerCallback(run))
|
|
1112
|
+
run.score("refunds", 83, ci=2.7, n=240) # every behavior, not only the targets
|
|
1113
|
+
run.score("length", 76, ci=2.8, n=120)
|
|
1114
|
+
run.finish(hours=2.1, gpu="1xH100", cost_usd=31)
|
|
1115
|
+
|
|
1116
|
+
str(tracked.verdict()) # "refunds: v4 beats v3 by 5 (interval excludes zero); 1 regression"
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
Every object is a pydantic model that validates before it leaves the process, and each one's docstring names the rlhfbook.com chapter it comes from. A *harness* is the instructions, tools and model name around the weights; its fingerprint is its version, so a prompt edit shows up as a new version without anyone naming it (Evaluation: a score is only comparable with its setup held constant). A *behavior* has its own frozen held-out test (`test_version`), a `noise_floor` measured by scoring the same model twice, and a judge checked against people (`agreement` over `human_n`) and for `length_bias`. A *run* is scored on every behavior: `targets` are the claim, the rest are the check (Over-Optimization: verbosity, sycophancy and refusals are what moves when the reward is gamed). `ci` is the half-width of the 95% interval; the verdict is whether the candidate's and the served version's intervals overlap. `tracked.live(day, version=, replies=, flagged=)` reports a day of traffic when you serve the model yourself. Logging buffers and never raises into the training loop. Worked example: `recipes/04-train/report-run/`.
|
|
1120
|
+
|
|
1084
1121
|
### Is it hacking the reward right now?
|
|
1085
1122
|
|
|
1086
1123
|
```python
|
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "whileai"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.65"
|
|
8
8
|
description = "While Python SDK: trace ingestion and verification client, plus agent simulations grounded in your tools and system prompt."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
|
-
dependencies = ["requests>=2.25.0"]
|
|
12
|
+
dependencies = ["requests>=2.25.0", "pydantic>=2.0"]
|
|
13
13
|
authors = [{name = "While"}]
|
|
14
14
|
keywords = [
|
|
15
15
|
"zp", "zeroproof", "while", "evals", "agent-evals",
|
|
@@ -39,6 +39,13 @@ from whileai._env import getenv
|
|
|
39
39
|
_DEFAULT_TRACE_URL = "https://api.zeroproofai.com"
|
|
40
40
|
_GZIP_MAGIC = b"\x1f\x8b"
|
|
41
41
|
|
|
42
|
+
# The resource attribute that names the dataset. The gate reads
|
|
43
|
+
# `zeroproof.dataset` and nothing else, so sending only the whileai spelling
|
|
44
|
+
# lands every batch in a dataset called `traces` whatever you asked for, with
|
|
45
|
+
# a 202 that says so too late to notice. Both are written: the second costs
|
|
46
|
+
# one attribute and means the rename needs no release.
|
|
47
|
+
_DATASET_KEYS = ("zeroproof.dataset", "whileai.dataset")
|
|
48
|
+
|
|
42
49
|
|
|
43
50
|
class WhileIngestError(Exception):
|
|
44
51
|
"""Raised when the gate rejects a trace batch."""
|
|
@@ -67,7 +74,7 @@ def otel_env(api_key: str, dataset: str = "traces", base_url: str | None = None)
|
|
|
67
74
|
"OTEL_EXPORTER_OTLP_HEADERS": "x-api-key=" + api_key,
|
|
68
75
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
|
69
76
|
# Resource attribute the gate reads to name the dataset.
|
|
70
|
-
"OTEL_RESOURCE_ATTRIBUTES": "
|
|
77
|
+
"OTEL_RESOURCE_ATTRIBUTES": ",".join(k + "=" + dataset for k in _DATASET_KEYS),
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
|
|
@@ -133,9 +140,9 @@ def ingest_traces(
|
|
|
133
140
|
Push a local OTLP batch file end to end and return ``{datasetId, dataset,
|
|
134
141
|
rows}``.
|
|
135
142
|
|
|
136
|
-
``dataset`` overrides the dataset name by setting the
|
|
137
|
-
|
|
138
|
-
|
|
143
|
+
``dataset`` overrides the dataset name by setting the dataset resource
|
|
144
|
+
attribute on every resourceSpan, which requires reading the batch; leave
|
|
145
|
+
it unset to send the bytes untouched.
|
|
139
146
|
"""
|
|
140
147
|
with open(file, "rb") as fh:
|
|
141
148
|
body = fh.read()
|
|
@@ -148,9 +155,9 @@ def ingest_traces(
|
|
|
148
155
|
for resource_span in batch.get("resourceSpans", []):
|
|
149
156
|
resource = resource_span.setdefault("resource", {})
|
|
150
157
|
attributes = [
|
|
151
|
-
a for a in resource.get("attributes", []) if a.get("key")
|
|
158
|
+
a for a in resource.get("attributes", []) if a.get("key") not in _DATASET_KEYS
|
|
152
159
|
]
|
|
153
|
-
attributes
|
|
160
|
+
attributes += [{"key": k, "value": {"stringValue": dataset}} for k in _DATASET_KEYS]
|
|
154
161
|
resource["attributes"] = attributes
|
|
155
162
|
body = json.dumps(batch).encode("utf-8")
|
|
156
163
|
|