typevet 0.1.0.dev1__py3-none-any.whl
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.
- typevet/__init__.py +67 -0
- typevet/_version.py +43 -0
- typevet/adapters/__init__.py +16 -0
- typevet/adapters/diagnostics/__init__.py +59 -0
- typevet/adapters/diagnostics/fields.py +108 -0
- typevet/adapters/diagnostics/generation_events.py +75 -0
- typevet/adapters/diagnostics/http_events.py +91 -0
- typevet/adapters/diagnostics/logs.py +116 -0
- typevet/adapters/diagnostics/redaction.py +94 -0
- typevet/adapters/diagnostics/settings.py +77 -0
- typevet/adapters/inbound/__init__.py +65 -0
- typevet/adapters/inbound/api.py +61 -0
- typevet/adapters/inbound/backend_settings.py +608 -0
- typevet/adapters/inbound/cord_semantic_acceptance_cli.py +135 -0
- typevet/adapters/inbound/helpers.py +67 -0
- typevet/adapters/inbound/settings.py +154 -0
- typevet/adapters/outbound/__init__.py +62 -0
- typevet/adapters/outbound/async_fake.py +123 -0
- typevet/adapters/outbound/async_llama_cpp.py +168 -0
- typevet/adapters/outbound/chat_completion.py +148 -0
- typevet/adapters/outbound/fake.py +119 -0
- typevet/adapters/outbound/gemma/__init__.py +84 -0
- typevet/adapters/outbound/gemma/answer_binding.py +290 -0
- typevet/adapters/outbound/gemma/scoring_prefix.py +102 -0
- typevet/adapters/outbound/gemma/served_template.py +131 -0
- typevet/adapters/outbound/gemma_native_vision_factory.py +286 -0
- typevet/adapters/outbound/generation_finite.py +50 -0
- typevet/adapters/outbound/http_errors.py +38 -0
- typevet/adapters/outbound/judgment_scoring.py +471 -0
- typevet/adapters/outbound/llama_cpp.py +187 -0
- typevet/adapters/outbound/llama_cpp_http.py +103 -0
- typevet/adapters/outbound/llama_cpp_multimodal.py +145 -0
- typevet/adapters/outbound/llama_cpp_scoring.py +333 -0
- typevet/adapters/outbound/vllm_content.py +63 -0
- typevet/adapters/outbound/vllm_generation.py +163 -0
- typevet/adapters/outbound/vllm_generation_async.py +257 -0
- typevet/adapters/outbound/vllm_http.py +121 -0
- typevet/adapters/outbound/vllm_judgment_factory.py +210 -0
- typevet/adapters/outbound/vllm_scoring.py +372 -0
- typevet/domain/__init__.py +193 -0
- typevet/domain/candidate_scoring_request.py +134 -0
- typevet/domain/candidate_scoring_response.py +96 -0
- typevet/domain/candidate_scoring_validate.py +197 -0
- typevet/domain/decision_compile.py +368 -0
- typevet/domain/decision_execute.py +229 -0
- typevet/domain/decisions.py +129 -0
- typevet/domain/errors.py +235 -0
- typevet/domain/field_instructions.py +134 -0
- typevet/domain/judgment_answers.py +198 -0
- typevet/domain/judgment_normalize.py +243 -0
- typevet/domain/judgment_questions.py +187 -0
- typevet/domain/judgment_response.py +102 -0
- typevet/domain/media.py +101 -0
- typevet/domain/models.py +115 -0
- typevet/domain/question_schema.py +254 -0
- typevet/domain/scoring_stage.py +34 -0
- typevet/evaluation/__init__.py +19 -0
- typevet/evaluation/consumer_http_accounting.py +135 -0
- typevet/evaluation/cord_expense_call_accounting.py +108 -0
- typevet/evaluation/cord_expense_live_harness.py +75 -0
- typevet/evaluation/cord_expense_receipt_requirement.py +212 -0
- typevet/evaluation/cord_expense_smoke.py +328 -0
- typevet/evaluation/cord_semantic_acceptance.py +463 -0
- typevet/evaluation/cord_semantic_acceptance_report.py +90 -0
- typevet/evaluation/cord_semantic_metrics.py +57 -0
- typevet/evaluation/datasets/__init__.py +30 -0
- typevet/evaluation/datasets/banking77.py +270 -0
- typevet/evaluation/datasets/boolq.py +550 -0
- typevet/evaluation/datasets/boolq_download.py +132 -0
- typevet/evaluation/datasets/civil_comments.py +450 -0
- typevet/evaluation/datasets/clinc.py +125 -0
- typevet/evaluation/datasets/clinc_domains.json +172 -0
- typevet/evaluation/datasets/clinc_download.py +103 -0
- typevet/evaluation/datasets/clinc_plus_intent_names.json +153 -0
- typevet/evaluation/datasets/clinc_rows.py +306 -0
- typevet/evaluation/datasets/clinc_shard.py +156 -0
- typevet/evaluation/datasets/cord_expense.py +381 -0
- typevet/evaluation/datasets/difraud.py +256 -0
- typevet/evaluation/datasets/go_emotions.py +368 -0
- typevet/evaluation/datasets/go_emotions_download.py +154 -0
- typevet/evaluation/datasets/hyperpartisan.py +562 -0
- typevet/evaluation/datasets/partner_guard.py +170 -0
- typevet/evaluation/datasets/psai.py +415 -0
- typevet/evaluation/datasets/psai_download.py +167 -0
- typevet/evaluation/datasets/psai_evidence_pilot.py +570 -0
- typevet/evaluation/datasets/psai_schema.py +157 -0
- typevet/evaluation/datasets/psai_stream.py +166 -0
- typevet/evaluation/datasets/psai_vision.py +534 -0
- typevet/evaluation/datasets/psai_vision_controls.py +502 -0
- typevet/evaluation/datasets/pubmedqa.py +420 -0
- typevet/evaluation/experiment_identity.py +677 -0
- typevet/evaluation/psai_vision_consumer_accounting.py +190 -0
- typevet/evaluation/psai_vision_consumer_dispatch.py +216 -0
- typevet/evaluation/psai_vision_consumer_harness.py +342 -0
- typevet/evaluation/psai_vision_consumer_live.py +332 -0
- typevet/evaluation/psai_vision_consumer_live_identity.py +156 -0
- typevet/evaluation/psai_vision_consumer_live_receipt.py +132 -0
- typevet/evaluation/psai_vision_consumer_live_router.py +162 -0
- typevet/evaluation/psai_vision_consumer_offline.py +418 -0
- typevet/evaluation/psai_vision_consumer_outcomes.py +224 -0
- typevet/evaluation/psai_vision_consumer_protocol.py +69 -0
- typevet/evaluation/psai_vision_consumer_receipt.py +161 -0
- typevet/evaluation/psai_vision_consumer_receipt_structure.py +263 -0
- typevet/evaluation/psai_vision_probability_evidence.py +336 -0
- typevet/evaluation/runner/__init__.py +56 -0
- typevet/evaluation/runner/core.py +104 -0
- typevet/evaluation/runner/datasets.py +186 -0
- typevet/evaluation/runner/live_gate.py +149 -0
- typevet/evaluation/runner/report.py +116 -0
- typevet/evaluation/tpjep/__init__.py +81 -0
- typevet/evaluation/tpjep/loader.py +237 -0
- typevet/evaluation/tpjep/outcome.py +101 -0
- typevet/evaluation/tpjep/records.py +428 -0
- typevet/evaluation/tpjep/runner.py +325 -0
- typevet/ports/__init__.py +39 -0
- typevet/ports/async_generation.py +57 -0
- typevet/ports/framing.py +59 -0
- typevet/ports/generation.py +58 -0
- typevet/ports/judgment.py +71 -0
- typevet/ports/scoring.py +64 -0
- typevet/py.typed +0 -0
- typevet/runtime/__init__.py +50 -0
- typevet/runtime/categorical.py +196 -0
- typevet/runtime/judgment.py +17 -0
- typevet/runtime/llama_cpp_gemma_vision.py +28 -0
- typevet/runtime/scoring_prefix.py +14 -0
- typevet/runtime/vllm_judgment.py +28 -0
- typevet/testing/__init__.py +22 -0
- typevet/testing/fakes.py +152 -0
- typevet-0.1.0.dev1.dist-info/METADATA +125 -0
- typevet-0.1.0.dev1.dist-info/RECORD +133 -0
- typevet-0.1.0.dev1.dist-info/WHEEL +4 -0
- typevet-0.1.0.dev1.dist-info/licenses/LICENSE +21 -0
typevet/__init__.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""typevet: type-safe structured generation under hexagonal architecture.
|
|
2
|
+
|
|
3
|
+
Examples:
|
|
4
|
+
```python
|
|
5
|
+
from typevet import GenerationRequest, GenerationResult
|
|
6
|
+
from typevet.adapters.outbound import FakeGenerationAdapter
|
|
7
|
+
|
|
8
|
+
schema = {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {"ok": {"type": "boolean"}},
|
|
11
|
+
"required": ["ok"],
|
|
12
|
+
"additionalProperties": False,
|
|
13
|
+
}
|
|
14
|
+
port = FakeGenerationAdapter(value={"ok": True})
|
|
15
|
+
result = port.generate(
|
|
16
|
+
GenerationRequest(prompt="Say ok.", schema=schema, model="fake")
|
|
17
|
+
)
|
|
18
|
+
assert result.value["ok"] is True
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
See Also:
|
|
22
|
+
- [typevet._version][]: Resolves ``__version__`` from distribution metadata
|
|
23
|
+
- [typevet.domain][]: Request, result and error types
|
|
24
|
+
- [typevet.ports][]: GenerationPort protocol
|
|
25
|
+
- [typevet.adapters.outbound][]: llama.cpp and fake adapters
|
|
26
|
+
- [typevet.runtime][]: Source of the ``decide_categorical`` re-export
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
BackendHttpError (type): llama.cpp HTTP error status with body snippet.
|
|
30
|
+
GenerationError (type): Base failure for a generation call.
|
|
31
|
+
TransportError (type): HTTP client failure before a response.
|
|
32
|
+
AsyncGenerationPort (type): Structural protocol for async typed generation.
|
|
33
|
+
GenerationPort (type): Structural protocol for typed generation.
|
|
34
|
+
GenerationRequest (type): Prompt, schema and model ask.
|
|
35
|
+
GenerationResult (type): Validated structured value.
|
|
36
|
+
SchemaValidationError (type): Output failed the requested schema.
|
|
37
|
+
decide_categorical (callable): M1 categorical decision via scoring port;
|
|
38
|
+
re-exported from ``typevet.runtime.categorical``.
|
|
39
|
+
__version__ (str): Installed distribution version; matches
|
|
40
|
+
``importlib.metadata.version("typevet")`` when the package is on
|
|
41
|
+
``PYTHONPATH``. Exported in ``__all__``.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
from typevet._version import __version__
|
|
45
|
+
from typevet.domain.errors import (
|
|
46
|
+
BackendHttpError,
|
|
47
|
+
GenerationError,
|
|
48
|
+
SchemaValidationError,
|
|
49
|
+
TransportError,
|
|
50
|
+
)
|
|
51
|
+
from typevet.domain.models import GenerationRequest, GenerationResult
|
|
52
|
+
from typevet.ports.async_generation import AsyncGenerationPort
|
|
53
|
+
from typevet.ports.generation import GenerationPort
|
|
54
|
+
from typevet.runtime.categorical import decide_categorical
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
"AsyncGenerationPort",
|
|
58
|
+
"BackendHttpError",
|
|
59
|
+
"GenerationError",
|
|
60
|
+
"GenerationPort",
|
|
61
|
+
"GenerationRequest",
|
|
62
|
+
"GenerationResult",
|
|
63
|
+
"SchemaValidationError",
|
|
64
|
+
"TransportError",
|
|
65
|
+
"__version__",
|
|
66
|
+
"decide_categorical",
|
|
67
|
+
]
|
typevet/_version.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Resolve the package version from distribution metadata.
|
|
2
|
+
|
|
3
|
+
When the wheel or sdist is installed, ``__version__`` comes from
|
|
4
|
+
``importlib.metadata``. In editable checkouts without metadata, it falls back
|
|
5
|
+
to ``[project].version`` in ``pyproject.toml``.
|
|
6
|
+
|
|
7
|
+
Examples:
|
|
8
|
+
```python
|
|
9
|
+
import typevet
|
|
10
|
+
from importlib.metadata import version
|
|
11
|
+
|
|
12
|
+
assert typevet.__version__ == version("typevet")
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
See Also:
|
|
16
|
+
- [typevet][]: Re-exports ``__version__`` in ``__all__``
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import tomllib
|
|
20
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
__all__ = ["__version__"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _read_pyproject_version() -> str:
|
|
27
|
+
root = Path(__file__).resolve().parents[2]
|
|
28
|
+
data = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))
|
|
29
|
+
project_version = data["project"]["version"]
|
|
30
|
+
if not isinstance(project_version, str):
|
|
31
|
+
msg = "pyproject [project].version must be a string"
|
|
32
|
+
raise TypeError(msg)
|
|
33
|
+
return project_version
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _package_version() -> str:
|
|
37
|
+
try:
|
|
38
|
+
return version("typevet")
|
|
39
|
+
except PackageNotFoundError:
|
|
40
|
+
return _read_pyproject_version()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__version__ = _package_version()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Adapters between the domain and the outside world.
|
|
2
|
+
|
|
3
|
+
Examples:
|
|
4
|
+
```python
|
|
5
|
+
from typevet.adapters.inbound import generate
|
|
6
|
+
from typevet.adapters.outbound import FakeGenerationAdapter
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
See Also:
|
|
10
|
+
- [typevet.adapters.inbound][]: Library entry points
|
|
11
|
+
- [typevet.adapters.outbound][]: Inference backends
|
|
12
|
+
- [typevet.ports][]: Port protocols
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
None: This package provides organizational structure only.
|
|
16
|
+
"""
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Structured stderr diagnostics for generation and HTTP adapters.
|
|
2
|
+
|
|
3
|
+
Diagnostics are operator signals, not telemetry: no automatic network export.
|
|
4
|
+
Importing this package does not configure structlog.
|
|
5
|
+
|
|
6
|
+
Examples:
|
|
7
|
+
```python
|
|
8
|
+
from typevet.adapters.diagnostics import (
|
|
9
|
+
bind_run_id,
|
|
10
|
+
configure_from_environ,
|
|
11
|
+
generation_call_event,
|
|
12
|
+
http_request_event,
|
|
13
|
+
new_run_id,
|
|
14
|
+
)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
See Also:
|
|
18
|
+
- [typevet.adapters.diagnostics.logs][]: Composition-root configuration
|
|
19
|
+
- [typevet.adapters.diagnostics.redaction][]: Secret and prompt redaction
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
REDACTED (str): Placeholder written over redacted secret values.
|
|
23
|
+
SECRET_KEYS (frozenset[str]): Field names always masked in diagnostics.
|
|
24
|
+
LogSettings (type): Frozen log format, level and prompt policy.
|
|
25
|
+
bind_run_id (function): Bind ``run_id`` on every diagnostic line.
|
|
26
|
+
configure (function): Apply ``LogSettings`` to stderr structlog.
|
|
27
|
+
configure_from_environ (function): Load env settings and configure once.
|
|
28
|
+
diagnostic_model (function): Keep safe model aliases in event fields.
|
|
29
|
+
generation_call_event (function): Terminal ``generation.call`` context manager.
|
|
30
|
+
http_request_event (function): Terminal ``http.request`` context manager.
|
|
31
|
+
load_log_settings (function): Read ``TYPEVET_LOG__*`` variables.
|
|
32
|
+
new_run_id (function): Create a short hex invocation id.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from typevet.adapters.diagnostics.fields import diagnostic_model
|
|
36
|
+
from typevet.adapters.diagnostics.generation_events import generation_call_event
|
|
37
|
+
from typevet.adapters.diagnostics.http_events import http_request_event
|
|
38
|
+
from typevet.adapters.diagnostics.logs import (
|
|
39
|
+
bind_run_id,
|
|
40
|
+
configure,
|
|
41
|
+
configure_from_environ,
|
|
42
|
+
new_run_id,
|
|
43
|
+
)
|
|
44
|
+
from typevet.adapters.diagnostics.redaction import REDACTED, SECRET_KEYS
|
|
45
|
+
from typevet.adapters.diagnostics.settings import LogSettings, load_log_settings
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"REDACTED",
|
|
49
|
+
"SECRET_KEYS",
|
|
50
|
+
"LogSettings",
|
|
51
|
+
"bind_run_id",
|
|
52
|
+
"configure",
|
|
53
|
+
"configure_from_environ",
|
|
54
|
+
"diagnostic_model",
|
|
55
|
+
"generation_call_event",
|
|
56
|
+
"http_request_event",
|
|
57
|
+
"load_log_settings",
|
|
58
|
+
"new_run_id",
|
|
59
|
+
]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Filter built-in diagnostic events and bound model identifiers.
|
|
2
|
+
|
|
3
|
+
Built-in events form a closed set until the reference table lands (#40).
|
|
4
|
+
Importing this module does not configure logging.
|
|
5
|
+
|
|
6
|
+
Examples:
|
|
7
|
+
```python
|
|
8
|
+
from typevet.adapters.diagnostics.fields import diagnostic_model
|
|
9
|
+
|
|
10
|
+
assert diagnostic_model("gemma-4-test") == "gemma-4-test"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
See Also:
|
|
14
|
+
- [typevet.adapters.diagnostics.logs][]: Wires ``filter_event_fields`` into structlog
|
|
15
|
+
- [typevet.adapters.diagnostics.generation_events][]: ``generation.call`` field set
|
|
16
|
+
- [typevet.adapters.diagnostics.http_events][]: ``http.request`` field set
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
from contextvars import ContextVar, Token
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
_RUN_ID: ContextVar[str | None] = ContextVar("typevet_run_id", default=None)
|
|
26
|
+
_MODEL = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$")
|
|
27
|
+
_EVENT_FIELDS = {
|
|
28
|
+
"generation.call": frozenset(
|
|
29
|
+
{
|
|
30
|
+
"event",
|
|
31
|
+
"model",
|
|
32
|
+
"outcome",
|
|
33
|
+
"error_type",
|
|
34
|
+
"run_id",
|
|
35
|
+
}
|
|
36
|
+
),
|
|
37
|
+
"http.request": frozenset(
|
|
38
|
+
{
|
|
39
|
+
"event",
|
|
40
|
+
"method",
|
|
41
|
+
"path",
|
|
42
|
+
"model",
|
|
43
|
+
"status_code",
|
|
44
|
+
"outcome",
|
|
45
|
+
"error_type",
|
|
46
|
+
"run_id",
|
|
47
|
+
}
|
|
48
|
+
),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def current_run_id() -> str | None:
|
|
53
|
+
"""Return the bound invocation ``run_id``, if any.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The current ``run_id``, or ``None`` when nothing is bound.
|
|
57
|
+
"""
|
|
58
|
+
return _RUN_ID.get()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def set_run_id_context(run_id: str | None) -> Token[str | None]:
|
|
62
|
+
"""Store ``run_id`` in a context variable; returns the reset token.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
run_id: Identifier to attach to subsequent diagnostic lines.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Token passed to ``reset_run_id_context`` to restore the prior value.
|
|
69
|
+
"""
|
|
70
|
+
return _RUN_ID.set(run_id)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def reset_run_id_context(token: Token[str | None]) -> None:
|
|
74
|
+
"""Restore the previous ``run_id`` binding."""
|
|
75
|
+
_RUN_ID.reset(token)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def diagnostic_model(model: str | None) -> str | None:
|
|
79
|
+
"""Keep bounded model aliases in diagnostics without changing API values.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
model: Requested or resolved router model alias.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The identifier when it matches the safe pattern, else ``None``.
|
|
86
|
+
"""
|
|
87
|
+
if model is not None and _MODEL.fullmatch(model):
|
|
88
|
+
return model
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def filter_event_fields(
|
|
93
|
+
_logger: Any, _method: str, event_dict: dict[str, Any]
|
|
94
|
+
) -> dict[str, Any]:
|
|
95
|
+
"""Drop unknown keys from built-in events; keep application events intact.
|
|
96
|
+
|
|
97
|
+
Prompts, schemas, headers and response bodies never appear in the
|
|
98
|
+
closed field sets for ``generation.call`` and ``http.request``.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
The event dict unchanged for unknown events, or filtered to the
|
|
102
|
+
closed field set for built-in events.
|
|
103
|
+
"""
|
|
104
|
+
name = event_dict.get("event")
|
|
105
|
+
fields = _EVENT_FIELDS.get(name) if isinstance(name, str) else None
|
|
106
|
+
if fields is None:
|
|
107
|
+
return event_dict
|
|
108
|
+
return {key: value for key, value in event_dict.items() if key in fields}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Emit one terminal generation diagnostic per logical call when configured.
|
|
2
|
+
|
|
3
|
+
Examples:
|
|
4
|
+
```python
|
|
5
|
+
from typevet.adapters.diagnostics.generation_events import generation_call_event
|
|
6
|
+
|
|
7
|
+
with generation_call_event(model="fake") as event:
|
|
8
|
+
event.outcome = "success"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
See Also:
|
|
12
|
+
- [typevet.adapters.diagnostics.fields][]: ``run_id`` and model field helpers
|
|
13
|
+
- [typevet.adapters.diagnostics.logs][]: Configures structlog before events emit
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from collections.abc import Iterator
|
|
19
|
+
from contextlib import contextmanager
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
|
|
22
|
+
import structlog
|
|
23
|
+
|
|
24
|
+
from typevet.adapters.diagnostics.fields import current_run_id, diagnostic_model
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class GenerationCallEvent:
|
|
29
|
+
"""Terminal generation metadata without prompt or schema payloads.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
outcome (str): ``success`` or ``error`` after the adapter finishes.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
```python
|
|
36
|
+
from typevet.adapters.diagnostics.generation_events import GenerationCallEvent
|
|
37
|
+
|
|
38
|
+
event = GenerationCallEvent(outcome="success")
|
|
39
|
+
assert event.outcome == "success"
|
|
40
|
+
```
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
outcome: str = "error"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@contextmanager
|
|
47
|
+
def generation_call_event(*, model: str) -> Iterator[GenerationCallEvent]:
|
|
48
|
+
"""Yield terminal metadata and emit ``generation.call`` when configured.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
model: Router model alias for the call.
|
|
52
|
+
|
|
53
|
+
Yields:
|
|
54
|
+
Event state the adapter updates before the block exits.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
BaseException: Re-raises any exception from the wrapped block after
|
|
58
|
+
recording ``error_type`` on the terminal event.
|
|
59
|
+
"""
|
|
60
|
+
event = GenerationCallEvent()
|
|
61
|
+
failure: BaseException | None = None
|
|
62
|
+
try:
|
|
63
|
+
yield event
|
|
64
|
+
except BaseException as exc:
|
|
65
|
+
failure = exc
|
|
66
|
+
raise
|
|
67
|
+
finally:
|
|
68
|
+
if structlog.is_configured():
|
|
69
|
+
structlog.get_logger().debug(
|
|
70
|
+
"generation.call",
|
|
71
|
+
model=diagnostic_model(model),
|
|
72
|
+
outcome=event.outcome,
|
|
73
|
+
error_type=None if failure is None else type(failure).__name__,
|
|
74
|
+
run_id=current_run_id(),
|
|
75
|
+
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Emit one terminal HTTP diagnostic per logical request when logging is configured.
|
|
2
|
+
|
|
3
|
+
Unconfigured library calls stay silent. This module never opens a network sink;
|
|
4
|
+
it only writes through the configured stderr renderer.
|
|
5
|
+
|
|
6
|
+
Examples:
|
|
7
|
+
```python
|
|
8
|
+
from typevet.adapters.diagnostics.http_events import http_request_event
|
|
9
|
+
|
|
10
|
+
with http_request_event(model="fake", path="v1/chat/completions") as event:
|
|
11
|
+
event.status_code = 200
|
|
12
|
+
event.outcome = "success"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
See Also:
|
|
16
|
+
- [typevet.adapters.diagnostics.fields][]: ``run_id`` and model field helpers
|
|
17
|
+
- [typevet.adapters.diagnostics.logs][]: Configures structlog before events emit
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from collections.abc import Iterator
|
|
23
|
+
from contextlib import contextmanager
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
|
|
26
|
+
import structlog
|
|
27
|
+
|
|
28
|
+
from typevet.adapters.diagnostics.fields import current_run_id, diagnostic_model
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class HttpRequestEvent:
|
|
33
|
+
"""Terminal HTTP metadata without prompts, schemas or response bodies.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
status_code (int | None): HTTP status when the adapter knows it.
|
|
37
|
+
outcome (str): ``success`` or ``error`` after the request completes.
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
```python
|
|
41
|
+
from typevet.adapters.diagnostics.http_events import HttpRequestEvent
|
|
42
|
+
|
|
43
|
+
event = HttpRequestEvent(status_code=200, outcome="success")
|
|
44
|
+
assert event.status_code == 200
|
|
45
|
+
```
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
status_code: int | None = None
|
|
49
|
+
outcome: str = "error"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@contextmanager
|
|
53
|
+
def http_request_event(
|
|
54
|
+
*,
|
|
55
|
+
model: str,
|
|
56
|
+
method: str = "POST",
|
|
57
|
+
path: str = "v1/chat/completions",
|
|
58
|
+
) -> Iterator[HttpRequestEvent]:
|
|
59
|
+
"""Yield mutable terminal metadata and emit ``http.request`` when configured.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
model: Router model alias for the call.
|
|
63
|
+
method: HTTP method (for example ``POST``).
|
|
64
|
+
path: Path relative to the adapter base URL.
|
|
65
|
+
|
|
66
|
+
Yields:
|
|
67
|
+
Event state the adapter updates before the block exits.
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
BaseException: Re-raises any exception from the wrapped block after
|
|
71
|
+
recording ``error_type`` on the terminal event.
|
|
72
|
+
"""
|
|
73
|
+
event = HttpRequestEvent()
|
|
74
|
+
failure: BaseException | None = None
|
|
75
|
+
try:
|
|
76
|
+
yield event
|
|
77
|
+
except BaseException as exc:
|
|
78
|
+
failure = exc
|
|
79
|
+
raise
|
|
80
|
+
finally:
|
|
81
|
+
if structlog.is_configured():
|
|
82
|
+
structlog.get_logger().debug(
|
|
83
|
+
"http.request",
|
|
84
|
+
method=method,
|
|
85
|
+
path=path,
|
|
86
|
+
model=diagnostic_model(model),
|
|
87
|
+
status_code=event.status_code,
|
|
88
|
+
outcome=event.outcome,
|
|
89
|
+
error_type=None if failure is None else type(failure).__name__,
|
|
90
|
+
run_id=current_run_id(),
|
|
91
|
+
)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Configure stderr structlog diagnostics at the composition root.
|
|
2
|
+
|
|
3
|
+
Logs are diagnostics, not telemetry: no automatic network export. Library
|
|
4
|
+
imports stay silent until ``configure`` or ``configure_from_environ`` runs.
|
|
5
|
+
|
|
6
|
+
Examples:
|
|
7
|
+
```python
|
|
8
|
+
from typevet.adapters.diagnostics.logs import (
|
|
9
|
+
bind_run_id,
|
|
10
|
+
configure_from_environ,
|
|
11
|
+
new_run_id,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
configure_from_environ()
|
|
15
|
+
bind_run_id(new_run_id())
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
See Also:
|
|
19
|
+
- [typevet.adapters.diagnostics.http_events][]: HTTP terminal events
|
|
20
|
+
- [typevet.adapters.diagnostics.generation_events][]: Generation events
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
import sys
|
|
27
|
+
import uuid
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
import structlog
|
|
31
|
+
from structlog.tracebacks import ExceptionDictTransformer
|
|
32
|
+
|
|
33
|
+
from typevet.adapters.diagnostics.fields import filter_event_fields, set_run_id_context
|
|
34
|
+
from typevet.adapters.diagnostics.redaction import make_redact_processor
|
|
35
|
+
from typevet.adapters.diagnostics.settings import LogSettings, load_log_settings
|
|
36
|
+
|
|
37
|
+
LEVELS = {
|
|
38
|
+
"debug": logging.DEBUG,
|
|
39
|
+
"info": logging.INFO,
|
|
40
|
+
"warning": logging.WARNING,
|
|
41
|
+
"error": logging.ERROR,
|
|
42
|
+
"critical": logging.CRITICAL,
|
|
43
|
+
}
|
|
44
|
+
_DICT_TRACEBACKS = structlog.processors.ExceptionRenderer(
|
|
45
|
+
ExceptionDictTransformer(show_locals=False)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def wants_json(settings: LogSettings, stream: Any) -> bool:
|
|
50
|
+
"""Return true when lines should render as JSON objects.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
``True`` when JSON rendering is selected explicitly or inferred from
|
|
54
|
+
a non-TTY stream under ``auto`` format.
|
|
55
|
+
"""
|
|
56
|
+
if settings.format != "auto":
|
|
57
|
+
return settings.format == "json"
|
|
58
|
+
return not (hasattr(stream, "isatty") and stream.isatty())
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def configure(settings: LogSettings, stream: Any = None) -> None:
|
|
62
|
+
"""Send structured diagnostics to stderr with redaction and field filters.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
settings: Format, level and prompt logging policy.
|
|
66
|
+
stream: Output stream; defaults to ``sys.stderr``.
|
|
67
|
+
"""
|
|
68
|
+
target = sys.stderr if stream is None else stream
|
|
69
|
+
redact = make_redact_processor(log_prompts=settings.log_prompts)
|
|
70
|
+
processors: list[Any] = [
|
|
71
|
+
structlog.contextvars.merge_contextvars,
|
|
72
|
+
filter_event_fields,
|
|
73
|
+
structlog.processors.add_log_level,
|
|
74
|
+
structlog.processors.TimeStamper(fmt="iso", utc=True),
|
|
75
|
+
]
|
|
76
|
+
if wants_json(settings, target):
|
|
77
|
+
processors += [_DICT_TRACEBACKS, redact, structlog.processors.JSONRenderer()]
|
|
78
|
+
else:
|
|
79
|
+
processors += [
|
|
80
|
+
redact,
|
|
81
|
+
structlog.dev.ConsoleRenderer(
|
|
82
|
+
exception_formatter=structlog.dev.plain_traceback
|
|
83
|
+
),
|
|
84
|
+
]
|
|
85
|
+
structlog.configure(
|
|
86
|
+
processors=processors,
|
|
87
|
+
wrapper_class=structlog.make_filtering_bound_logger(LEVELS[settings.level]),
|
|
88
|
+
logger_factory=structlog.PrintLoggerFactory(target),
|
|
89
|
+
cache_logger_on_first_use=False,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def configure_from_environ(stream: Any = None) -> LogSettings:
|
|
94
|
+
"""Load ``TYPEVET_LOG__*`` settings and configure stderr diagnostics once.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
The settings that were applied.
|
|
98
|
+
"""
|
|
99
|
+
settings = load_log_settings()
|
|
100
|
+
configure(settings, stream=stream)
|
|
101
|
+
return settings
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def new_run_id() -> str:
|
|
105
|
+
"""Return a short hex id that joins lines for one invocation.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Twelve lowercase hex characters from a random UUID.
|
|
109
|
+
"""
|
|
110
|
+
return uuid.uuid4().hex[:12]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def bind_run_id(run_id: str) -> None:
|
|
114
|
+
"""Bind ``run_id`` on every line until the context is cleared or replaced."""
|
|
115
|
+
structlog.contextvars.bind_contextvars(run_id=run_id)
|
|
116
|
+
set_run_id_context(run_id)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Redact secrets and optional prompt content before stderr rendering.
|
|
2
|
+
|
|
3
|
+
Examples:
|
|
4
|
+
```python
|
|
5
|
+
from typevet.adapters.diagnostics.redaction import REDACTED, redact
|
|
6
|
+
|
|
7
|
+
assert redact(None, "info", {"api_key": "k"})["api_key"] == REDACTED
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
See Also:
|
|
11
|
+
- [typevet.adapters.diagnostics.logs][]: Installs the redact processor on configure
|
|
12
|
+
- [typevet.adapters.diagnostics.settings][]: ``log_prompts`` policy for prompt keys
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
REDACTED = "***"
|
|
21
|
+
SECRET_KEYS = frozenset(
|
|
22
|
+
{
|
|
23
|
+
"api_key",
|
|
24
|
+
"authorization",
|
|
25
|
+
"password",
|
|
26
|
+
"private_key",
|
|
27
|
+
"private_key_pem",
|
|
28
|
+
"token",
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
PROMPT_KEYS = frozenset(
|
|
32
|
+
{
|
|
33
|
+
"content",
|
|
34
|
+
"messages",
|
|
35
|
+
"prompt",
|
|
36
|
+
"raw_text",
|
|
37
|
+
"system",
|
|
38
|
+
"user",
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
_SCALARS = (bool, int, float)
|
|
42
|
+
_EXC_INFO = "exc_info"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _is_pem(value: str) -> bool:
|
|
46
|
+
return "-----BEGIN" in value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _redact_pair(key: Any, value: Any, *, log_prompts: bool) -> Any:
|
|
50
|
+
if isinstance(key, str):
|
|
51
|
+
lowered = key.lower()
|
|
52
|
+
if lowered in {k.lower() for k in SECRET_KEYS}:
|
|
53
|
+
return REDACTED
|
|
54
|
+
if not log_prompts and lowered in {k.lower() for k in PROMPT_KEYS}:
|
|
55
|
+
return REDACTED
|
|
56
|
+
return _mask(value, log_prompts=log_prompts)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _mask(value: Any, *, log_prompts: bool) -> Any:
|
|
60
|
+
if isinstance(value, str):
|
|
61
|
+
return REDACTED if _is_pem(value) else value
|
|
62
|
+
if value is None or isinstance(value, _SCALARS):
|
|
63
|
+
return value
|
|
64
|
+
if isinstance(value, dict):
|
|
65
|
+
return {
|
|
66
|
+
k: _redact_pair(k, v, log_prompts=log_prompts) for k, v in value.items()
|
|
67
|
+
}
|
|
68
|
+
if isinstance(value, (list, tuple, set, frozenset)):
|
|
69
|
+
return [_mask(item, log_prompts=log_prompts) for item in value]
|
|
70
|
+
return type(value).__name__
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def make_redact_processor(
|
|
74
|
+
*, log_prompts: bool
|
|
75
|
+
) -> Callable[[Any, str, dict[str, Any]], dict[str, Any]]:
|
|
76
|
+
"""Build a structlog processor that masks secrets and optional prompts.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
log_prompts: When false, prompt-like field names and long strings
|
|
80
|
+
are replaced with ``REDACTED``.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
A structlog-compatible processor callable.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def _redact_event(
|
|
87
|
+
_logger: Any, _method: str, event_dict: dict[str, Any]
|
|
88
|
+
) -> dict[str, Any]:
|
|
89
|
+
return {
|
|
90
|
+
k: v if k == _EXC_INFO else _redact_pair(k, v, log_prompts=log_prompts)
|
|
91
|
+
for k, v in event_dict.items()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return _redact_event
|