Repository navigation
Conversation
The Node OTLP exporter streams with Transfer-Encoding: chunked and sends no Content-Length. The Receiver read Content-Length only, so every Node export came back 400 with zero spans; two TH-8103 children had to put a de-chunking relay in front of it. Receiver now accepts both framings. It also records one entry per accepted export (path, lower-cased headers, flattened resource attributes) via requests(), so a contract test can assert X-Api-Key/X-Secret-Key and project_name through the shared harness instead of a private recorder. Verified: 6 harness tests pass, and the TanStack example's real Node exporter delivered 4 chunked exports (4 spans, collector path, both auth headers, project_name/project_type) with no relay. Refs: TH-8339, TH-8103
Add the test suite for traceAI-parallel before the package exists. The tests drive the real parallel-web client against a loopback fake of the Parallel API (POST /v1/search, /v1/extract, /v1/tasks/runs) and cover: - one RETRIEVER span per search/extract call with mode, query count, result count, search/extract/session ids, and no response content - the Parallel key redacted wherever the SDK holds it (api_key, default and per-call x-api-key headers, PARALLEL_API_KEY), redacted before the 1 KB UTF-8 cap on input.value / gen_ai.retrieval.query - warnings as span events, usage only when returned, unknown counts omitted, HTTP and connection errors, async cancellation - AsyncParallel parity, span current during the HTTP request, parent and root spans, uninstrument identity restore, instrumentation isolation - scope: task_run and /v1beta are not traced, raw/streaming responses are - TraceConfig hide flags, capture_urls / capture_objective opt-ins - packaging ranges, the runtime version check, classifiers - the shared harness Receiver contract and the example via harness.run They fail to collect until traceai_parallel exists. approved: Nikhil 2026-10-03 blanket Refs: TH-8326
…async Add traceAI-parallel (import traceai_parallel, ParallelInstrumentor). It wraps the four methods parallel-web 1.x defines on its own classes in parallel._client: Parallel.search, Parallel.extract, AsyncParallel.search and AsyncParallel.extract (POST /v1/search, /v1/extract). Task, Monitor, FindAll and the rest of client.beta are not wrapped. Each call is one span, parallel.search or parallel.extract, with fi.span.kind=RETRIEVER, current while the SDK sends HTTP (use_span with end_on_exit=False), a child of the active span or a root span. - request: parallel.mode, parallel.query_count, parallel.url_count, the joined search_queries in gen_ai.retrieval.query and input.value, and a caller session_id; the Parallel key is redacted from every value before a 1 KB UTF-8 cap on a character boundary. The key is read from client.api_key, auth_headers, default/custom headers and extra_headers. - response: parallel.result_count, parallel.failed_url_count (extract), parallel.search_id / parallel.extract_id, parallel.session_id, usage SKU names and counts only when returned, warnings as parallel.warning events. Unknown counts are omitted, never 0. No excerpts, titles, URLs or page text are read. - errors: ERROR status and one exception event, both redacted; the vendor's exception is re-raised unchanged. Async cancellation sets ERROR "cancelled" and parallel.cancelled=true. - capture_urls / capture_objective opt in to request URLs (at most 20) and the objective; TraceConfig hide_inputs / hide_outputs (and their FI_HIDE_* variables) drop request text and warning messages. - every instrumentation step is isolated, so its failure never reaches the caller; suppress_tracing is honoured; uninstrument() restores each method by identity and disables bound copies taken while instrumented. Dependencies are ranges: parallel-web >=1.0.1,<2 (the docs floor; every 1.x wheel has the same search/extract signatures and paths) and fi-instrumentation-otel >=1.1.0. Classifiers list the Pythons the suite ran on: 3.10, 3.11, 3.13. Two test fixes from the first run: await AsyncAPIResponse.parse() and use a query without the fake's "usage" trigger. The example test stays red until the example lands in the next commit. approved: Nikhil 2026-10-03 blanket Refs: TH-8326
One traced search and one extract with the real parallel-web client. The client reads PARALLEL_API_KEY and PARALLEL_BASE_URL itself and register() reads the FI_* variables, so the example takes no arguments. test_parallel_example runs it through harness.run against the loopback fake and harness.Receiver and checks both spans arrive at exit. approved: Nikhil 2026-10-03 blanket Refs: TH-8326
Document what traceAI-parallel wraps (search and extract, sync and async; not Task, Monitor, FindAll or beta), the correct keyword-only call shape, every span attribute and event, what is never recorded, the capture_urls / capture_objective opt-ins, where the API key is redacted from, the TraceConfig hide flags, error and cancellation status, and the known limits (with_raw_response copies, version range, /v1beta). approved: Nikhil 2026-10-03 blanket Refs: TH-8326
Add the Parallel row to the root README's Python "Tools and Libraries" table and support matrix, and to python/README.md's "Tools & Integrations" table, next to the sibling tool integrations. approved: Nikhil 2026-10-03 blanket Refs: TH-8326
Warning and error messages are written by the server and recorded with only the API key removed, so a message that quotes the request carries that text even with hide_inputs. State it in the privacy section. approved: Nikhil 2026-10-03 blanket Refs: TH-8326
The suite passes 79 tests with parallel-web 1.0.1 and 1.3.5 on Python 3.10, 3.12 and 3.13 (and on 3.11), so the README states the four Pythons and pyproject carries the 3.12 classifier. The packaging test now expects 3.10, 3.11, 3.12 and 3.13 and checks the python range admits 3.12. approved: Nikhil 2026-10-03 blanket Refs: TH-8326
exception.message and the status description were cut to 1 KB, but the stacktrace was only redacted, so a server error that echoes a large request produced an unbounded attribute. The stacktrace is now cut to 16 KB of UTF-8 on a character boundary, after the API key is replaced, so a key that straddles the cut leaves no prefix. Tests: a real 400 with a ~24 KB echoed body yields a 16 KB stacktrace without the key; a key straddling 16 KB is redacted whole; the cut keeps whole characters. The README states every size cap. approved: Nikhil 2026-10-03 blanket Refs: TH-8326
The instrumentor used a plain OTel tracer, so TraceConfig(pii_redaction) / FI_PII_REDACTION never ran on the default-on query text, and the using_session / using_user / using_metadata / using_tags / using_attributes context attributes were never stamped on parallel.* spans. The tracer is now FITracer(tracer, config=config), as in traceai-tavily. The package also applies the PII pass itself, after the API key is replaced and before the size caps, to every text it records: a cut can then not leave part of an email, and span events and the error status, which FiSpan does not mask, are covered too. FiSpan passes add_event, set_status and end through, so the package's own redacted exception event, the OK status, cancellation and isolation are unchanged. With hide_inputs, input.value is now FITracer's __REDACTED__ placeholder (as in traceai-tavily) instead of being absent; gen_ai.retrieval.query, parallel.urls and parallel.objective are still dropped and no query text is recorded. The two hide tests assert the placeholder. Tests: PII redaction from config and from the env var on both query keys (key and email each replaced once), PII before the 1 KB cap, PII in error text, using_session/using_user on sync and async search and extract, using_attributes metadata and tags. README updated. approved: Nikhil 2026-10-03 blanket Refs: TH-8326
with_streaming_response returns once the response headers arrive, and the span ends there. The README now says the span covers the request up to the headers, not the body read, so a failure while reading the body is not recorded on the span. The streaming test pins that the span has already ended before the body is read. approved: Nikhil 2026-10-03 blanket Refs: TH-8326
With hide_inputs / FI_HIDE_INPUTS=true the span dropped the query, URL and objective attributes, but parallel-web builds every APIStatusError message from the whole server body, so a server that quoted the request put the query back into the error status, exception.message, exception.stacktrace and warning messages. The README row said no query text was recorded while the paragraph below it said the opposite, and a test pinned the leak. As in traceai-tavily, under hide_inputs every verbatim occurrence of each search query, each requested extract URL and the objective (with or without the capture switches) now becomes __REDACTED__ in those texts. It runs after the API key is redacted, matching each input as it reads after that, and before the PII pass and the 1 KB / 16 KB caps. The replacement is one pass, longest input first, so an input inside a longer one or inside the placeholder cannot split a replacement. If the inputs cannot be read, those texts are recorded as __REDACTED__; the caller always gets the vendor's own exception. Sync and async share the path. With hide_inputs off nothing changes. Tests: the pinned test now asserts the query is absent with and without pii_redaction; new tests cover objective and URLs on search and extract, the key-then-input order, the one-pass replacement, warning messages, the async twin, unreadable inputs, and controls showing error and warning text unchanged without hide_inputs. The fake server quotes every query, URL and the objective back. README rows and the paragraph below now agree with the code and say that only verbatim copies are matched. approved: Nikhil 2026-10-03 blanket Refs: TH-8326
FITracer runs its own PII pass on span attributes after the package's cut, and a cut can leave a new match (for example the last ten digits of a longer number) whose token makes the value a few bytes longer than the cap. The README now says the joined queries, captured URLs and objective are cut to about 1 KB, and mode, ids and usage SKU names to about 256 bytes, and explains why. Warning messages, the error status and the exception event are not passed through FITracer again, so their caps stay exact. Docs only. approved: Nikhil 2026-10-03 blanket Refs: TH-8326
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New Python package
traceAI-parallel(python/frameworks/parallel) for the Parallel web API client (parallel-web). It wrapsParallel.search,Parallel.extractand theirAsyncParalleltwins, the methods the spec confirms.parallel.search/parallel.extract, withfi.span.kind=RETRIEVER. The span is current during the HTTP call, so HTTP spans nest under it, and one span covers the SDK's retries.parallel.warningeventsgen_ai.retrieval.queryandinput.value, with the key redacted first, then capped at about 1 KB (1024 UTF-8 bytes on a character boundary; withpii_redaction, FITracer's second PII pass can add a few bytes)capture_urls,capture_objective). TraceConfighide_inputs/hide_outputs/pii_redactionandFI_HIDE_*/FI_PII_REDACTIONare honoured. Spans come fromFITracer, sousing_session/using_user/using_metadata/using_tags/using_attributescontext reaches them. Withhide_inputs,input.valueis__REDACTED__(as in Tavily), and every verbatim copy of a search query, requested URL or the objective is replaced with__REDACTED__in the error status,exception.message,exception.stacktraceand warning messages (a server error can quote the request). Only verbatim copies are matched.api_key, auth/default/custom headers and per-callextra_headers, including in error status and exception events.exception.stacktraceis cut to 16 KB after the key (and PII, when enabled) is removed.cancelledplusparallel.cancelled=true. Instrumentation failures never reach the caller.uninstrument()restores all four methods by identity.parallel-web>=1.0.1,<2(1.0.1 and 1.3.5 tested),fi-instrumentation-otel>=1.1.0, Python 3.10, 3.11, 3.12 and 3.13.Deviations from the spec (with evidence)
client.search(query=...)raisesTypeErroron the real SDK, which takessearch_queries=[...]. The README uses the real signature.>=1.0.1,<2instead of a single pin. The 1.0.0-1.3.5 wheels have identical search/extract parameters and/v1/*paths; 0.6.0 still used/v1beta.parallel.apiattribute: no 1.x method reaches/v1beta/search(tested).gen_ai.retrieval.query, the repo's own constant (fi_types.py), rather than Exa'sfi.retrieval.query.Review round 1 and fixes (pr-reviewer t_6f817f29 APPROVE; pr-verifier t_20f2e54e CHANGES_REQUESTED at
7e56fd3)8002addexception.stacktrace967997cpii_redactionandusing_*contextb643847FITracer(tracer, config=config). PII redaction also covers warning messages, the error status and the exception event (FITracer masks attributes only), and runs before the size caps. Side effect, documented and tested: an id with 10 consecutive digits is masked as a phone number.with_streaming_responsetiming4ecdf34Tests written first: the final test files against
7e56fd3gave 12 failed, 78 passed (assertion failures). 12 deliberate breakages of the new code were each caught.Verification round 2 and fix (pr-verifier t_71379c0d CHANGES_REQUESTED at
4ecdf34: R1-R4 fixed, new N1)hide_inputs, the query reached the backend through error text (parallel-web puts the whole server body inAPIStatusError), while the README said no query text is recorded846410bhide_inputs, every verbatim search query, extract URL and the objective becomes__REDACTED__in the error status, exception message/stacktrace and warning messages. Order: key, then hidden inputs (one pass, longest first), then PII, then caps. Unreadable inputs underhide_inputsrecord__REDACTED__for those texts. Sync and async share the path; withhide_inputsoff nothing changes. Decision approved: Nikhil 2026-10-03 blanket.fadfee6exception.messagekeeps the message.Tests written first: the final test files against
4ecdf34gave 11 failed, 92 passed (10 assertion failures on leaked query/URL/objective text, 1 on the new helper); controls withhide_inputsoff pass on both heads.Tests
Head
fadfee6, coordinator rerun from the repo root on a clean tree (tickets/TH-8326/exact-head-fadfee6.txt):The round-3 delta (
4ecdf34..fadfee6) touches no file outside the package, and no secret-like lines were added. Across the whole branch, the only files outside the package are the package-table rows inREADME.md(+2) andpython/README.md(+1), added ina770cf0.redact_pii_in_stringandFITracerare present in the publishedfi-instrumentation-otel==1.1.0wheel (checked by import); the suite itself ran on the in-repofi_instrumentation.parallel-webSDK runs against a loopback fake of the Parallel API.fi_instrumentation.register()into the shared harnessReceiver. It asserts the/tracer/v1/tracespath, both auth headers,project_type=observe, and that no key or content reaches the wire. A control run shows the opt-in capture does arrive.harness.run.7e56fd3the implementer also ran the built package against the publishedfi-instrumentation-otel==1.1.0and the OTel 1.29.0 floor (79 passed each); those two runs were not repeated atfadfee6.Limits
Video demo
Narrated terminal demo, 6:01, recorded at head
fadfee6, the head verified in round 3 (pr-verifier t_9da92a74). 1080p H.264/AAC, 10 chapters, burned-in captions. sha256cb65aedddf78754e6db614b40ec1bf6cab2bb2a9f6ec06edc9fc16314063b731.The video, captions, transcript, chapter list, preview and media check are attached privately to Linear TH-8326 (Future AGI workspace access needed).
Every run uses the real
parallel-web1.3.5 client against the package's loopback fake Parallel API, with placeholder keys and the shared harness receiver. There is no vendor call and no live fi-collector.fadfee6: local and remote at the same SHA, 0 uncommitted files, 12 commits on3eaadc8; outside the package only the README table rows/tracer/v1/traces, auth header names only (values not shown),project_type=observe; aparallel.searchRETRIEVER span underagent-turnwith the pasted key as[redacted];using_session/using_userattributes (R1); no key, objective, titles, excerpts or result URLs in the exportcapture_urls/capture_objectiveopt-ins; caps (20 URLs, 1,024-byte objective); page text never recordedFI_HIDE_INPUTS, a 400 and a warning quoting the query, URL and objective show__REDACTED__in status, exception message, stacktrace and warning, while the caller's error keeps them. CONTROL (hide off) records them; BEFORE4ecdf34they leakedpii_redactionturns an email into<EMAIL_ADDRESS>everywhere on the span; session and user on 3 of 3 spans; BEFORE967997c(plain tracer) the email was in all 6 placesdefault_headersorextra_headersis[redacted]on the span; stacktrace capped at 16,384 bytes (BEFORE8002add: 25,098); cancellation ends ERRORcancelled; instrumentation failures never reach the caller_hidden_inputs(every query, extract URL and the objective, key-redacted, longest first) and the scrub orderOnly a 6.8 s frozen test wait was cut; on-screen output matches the coordinator-verified driver output in all terminal chapters.
Coordinator checks:
[redacted]only)Stacked on #203 (shared harness,
3eaadc8), basedev. Not merged.Linear: TH-8326. approved: Nikhil 2026-10-03 blanket