contextattr 0.1.0__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.
- contextattr/__init__.py +22 -0
- contextattr/__main__.py +7 -0
- contextattr/cache.py +64 -0
- contextattr/cli.py +181 -0
- contextattr/evaluate.py +137 -0
- contextattr/methods/__init__.py +27 -0
- contextattr/methods/base.py +86 -0
- contextattr/methods/indices.py +59 -0
- contextattr/methods/interactions.py +215 -0
- contextattr/methods/kernelshap.py +114 -0
- contextattr/methods/linear.py +104 -0
- contextattr/methods/loo.py +31 -0
- contextattr/prompt/__init__.py +9 -0
- contextattr/prompt/io.py +222 -0
- contextattr/prompt/render.py +70 -0
- contextattr/prompt/split.py +153 -0
- contextattr/prompt/units.py +271 -0
- contextattr/report.py +120 -0
- contextattr/responses/__init__.py +6 -0
- contextattr/responses/checks.py +156 -0
- contextattr/responses/sample.py +36 -0
- contextattr/result.py +120 -0
- contextattr/run.py +306 -0
- contextattr/targets/__init__.py +7 -0
- contextattr/targets/sampled.py +58 -0
- contextattr/targets/scored.py +177 -0
- contextattr/value.py +201 -0
- contextattr/wrapper/__init__.py +9 -0
- contextattr/wrapper/check.py +163 -0
- contextattr/wrapper/contract.py +166 -0
- contextattr/wrapper/template.py +56 -0
- contextattr/wrapper/toy.py +129 -0
- contextattr-0.1.0.dist-info/METADATA +129 -0
- contextattr-0.1.0.dist-info/RECORD +38 -0
- contextattr-0.1.0.dist-info/WHEEL +5 -0
- contextattr-0.1.0.dist-info/entry_points.txt +2 -0
- contextattr-0.1.0.dist-info/licenses/LICENSE +21 -0
- contextattr-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""ToyModel: a compatible wrapper with known answers and no real model behind it, for tests and a first try."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import math
|
|
7
|
+
import random
|
|
8
|
+
import re
|
|
9
|
+
from collections.abc import Mapping, Sequence
|
|
10
|
+
|
|
11
|
+
from .contract import Rendered, Sampling, Scored
|
|
12
|
+
|
|
13
|
+
_TOKEN = re.compile(r"\s*\S+|\s+$") # each word with the spaces before it; trailing spaces as a last token
|
|
14
|
+
_EDGE = re.compile(r"^\W+|\W+$")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ToyModel:
|
|
18
|
+
"""A stand-in model whose answers are known exactly.
|
|
19
|
+
|
|
20
|
+
A response is cut into words, each with the spaces before it. Word w gets the log-probability
|
|
21
|
+
log σ(bias + Σ rules[cue][w] + Σ pairs[(cue1, cue2)][w]), summed over the cues present in the prompt, and
|
|
22
|
+
over the pairs whose two cues are both present. A cue is present when its text occurs in the prompt, case
|
|
23
|
+
ignored (a chat prompt's message contents are joined with newlines); a word is matched lower-cased, without
|
|
24
|
+
punctuation around it.
|
|
25
|
+
|
|
26
|
+
`generate` at temperature 0 gives the reply of the first cue in `replies` that is present, else `default_reply`,
|
|
27
|
+
sampling.n times. Above temperature 0 each reply is drawn uniformly from the replies of all the cues present (the
|
|
28
|
+
default when none is), so its rate is known exactly; the draw is seeded by the seed and the prompt's text, and
|
|
29
|
+
seed None draws afresh."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, rules: Mapping[str, Mapping[str, float]] | None = None, *,
|
|
32
|
+
pairs: Mapping[tuple[str, str], Mapping[str, float]] | None = None, bias: float = 0.0,
|
|
33
|
+
replies: Mapping[str, str] | None = None, default_reply: str = "I do not know.",
|
|
34
|
+
name: str = "toy"):
|
|
35
|
+
self.rules = {_cue(c): _weights(w, c) for c, w in (rules or {}).items()}
|
|
36
|
+
self.pairs = {}
|
|
37
|
+
for key, w in (pairs or {}).items():
|
|
38
|
+
if not (isinstance(key, tuple) and len(key) == 2):
|
|
39
|
+
raise TypeError(f"ToyModel: a pair is a tuple of two cues, not {key!r}")
|
|
40
|
+
self.pairs[(_cue(key[0]), _cue(key[1]))] = _weights(w, key)
|
|
41
|
+
if isinstance(bias, bool) or not isinstance(bias, (int, float)) or not math.isfinite(bias):
|
|
42
|
+
raise TypeError("ToyModel: bias must be a finite number")
|
|
43
|
+
self.bias = float(bias)
|
|
44
|
+
self.replies = {_cue(c): r for c, r in (replies or {}).items()}
|
|
45
|
+
if not all(isinstance(r, str) for r in self.replies.values()) or not isinstance(default_reply, str):
|
|
46
|
+
raise TypeError("ToyModel: replies must be strings")
|
|
47
|
+
self.default_reply = default_reply
|
|
48
|
+
self.name = name
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def demo(cls) -> ToyModel:
|
|
52
|
+
"""A toy model of the interface's Rio example: Brazil's winter makes "cold" likely, its summer "hot" and
|
|
53
|
+
"humid", more so when the prompt also says Carnival falls in February or March; it replies "cold" when it
|
|
54
|
+
reads only of the winter."""
|
|
55
|
+
return cls(rules={"winter in brazil": {"cold": 2.0, "winter": 2.0},
|
|
56
|
+
"summer in brazil": {"hot": 2.0, "humid": 1.0, "summer": 2.0}},
|
|
57
|
+
pairs={("february or march", "summer in brazil"): {"hot": 1.5, "summer": 1.5}},
|
|
58
|
+
bias=-1.0,
|
|
59
|
+
replies={"summer in brazil": "It is hot and humid: it is summer in Brazil.",
|
|
60
|
+
"winter in brazil": "It is cold, because Carnival is in winter."},
|
|
61
|
+
name="toy-demo")
|
|
62
|
+
|
|
63
|
+
def score(self, prompts: Sequence[Rendered], responses: Sequence[str]) -> list[Scored]:
|
|
64
|
+
if len(prompts) != len(responses):
|
|
65
|
+
raise ValueError(f"ToyModel.score: {len(prompts)} prompts but {len(responses)} responses")
|
|
66
|
+
out = []
|
|
67
|
+
for prompt, response in zip(prompts, responses):
|
|
68
|
+
if not isinstance(response, str):
|
|
69
|
+
raise TypeError(f"ToyModel.score: a response is a string, not {type(response).__name__}")
|
|
70
|
+
text = _text(prompt)
|
|
71
|
+
tokens = _TOKEN.findall(response)
|
|
72
|
+
out.append(Scored(tuple(self._logprob(text, t) for t in tokens), tuple(tokens)))
|
|
73
|
+
return out
|
|
74
|
+
|
|
75
|
+
def generate(self, prompts: Sequence[Rendered], sampling: Sampling) -> list[list[str]]:
|
|
76
|
+
out = []
|
|
77
|
+
for prompt in prompts:
|
|
78
|
+
text = _text(prompt)
|
|
79
|
+
present = [r for c, r in self.replies.items() if c in text] or [self.default_reply]
|
|
80
|
+
if sampling.temperature == 0 or len(present) == 1:
|
|
81
|
+
out.append([present[0]] * sampling.n)
|
|
82
|
+
continue
|
|
83
|
+
rng = random.Random(None if sampling.seed is None else _seed(text, sampling.seed))
|
|
84
|
+
out.append([rng.choice(present) for _ in range(sampling.n)])
|
|
85
|
+
return out
|
|
86
|
+
|
|
87
|
+
def _logprob(self, text: str, token: str) -> float:
|
|
88
|
+
word = _EDGE.sub("", token.strip()).lower()
|
|
89
|
+
x = self.bias
|
|
90
|
+
x += sum(w.get(word, 0.0) for c, w in self.rules.items() if c in text)
|
|
91
|
+
x += sum(w.get(word, 0.0) for (c1, c2), w in self.pairs.items() if c1 in text and c2 in text)
|
|
92
|
+
return log_sigmoid(x)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _seed(text: str, seed: int) -> int:
|
|
96
|
+
return int.from_bytes(hashlib.sha256(f"{seed}\0{text}".encode()).digest()[:8], "big")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def log_sigmoid(x: float) -> float:
|
|
100
|
+
"""log σ(x) = log(1 / (1 + e^-x)), without overflow."""
|
|
101
|
+
return min(x, 0.0) - math.log1p(math.exp(-abs(x)))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _text(prompt: Rendered) -> str:
|
|
105
|
+
"""The prompt's text, lower-cased; a chat prompt's contents joined with newlines. Checks the rendered shape."""
|
|
106
|
+
if isinstance(prompt, str):
|
|
107
|
+
return prompt.lower()
|
|
108
|
+
if not isinstance(prompt, list) or not all(isinstance(m, dict) and set(m) == {"role", "content"} and
|
|
109
|
+
isinstance(m["role"], str) and isinstance(m["content"], str)
|
|
110
|
+
for m in prompt):
|
|
111
|
+
raise TypeError("ToyModel: a prompt is a string or a list of {'role', 'content'} string dicts")
|
|
112
|
+
return "\n".join(m["content"] for m in prompt).lower()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _cue(cue) -> str:
|
|
116
|
+
if not isinstance(cue, str) or not cue:
|
|
117
|
+
raise TypeError(f"ToyModel: a cue is a non-empty string, not {cue!r}")
|
|
118
|
+
return cue.lower()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _weights(w, cue) -> dict[str, float]:
|
|
122
|
+
if not isinstance(w, Mapping):
|
|
123
|
+
raise TypeError(f"ToyModel: the weights of {cue!r} are a mapping word → number")
|
|
124
|
+
out = {}
|
|
125
|
+
for word, x in w.items():
|
|
126
|
+
if not isinstance(word, str) or isinstance(x, bool) or not isinstance(x, (int, float)) or not math.isfinite(x):
|
|
127
|
+
raise TypeError(f"ToyModel: weight {word!r}: {x!r} of {cue!r} must be a word and a finite number")
|
|
128
|
+
out[word.lower()] = float(x)
|
|
129
|
+
return out
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: contextattr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Context attribution: which parts of a prompt make a model give an output, for a model hosted anywhere
|
|
5
|
+
Author: Surya Rayala
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/Surya-Rayala/contextattr
|
|
8
|
+
Project-URL: Documentation, https://github.com/Surya-Rayala/contextattr/tree/main/docs
|
|
9
|
+
Project-URL: Issues, https://github.com/Surya-Rayala/contextattr/issues
|
|
10
|
+
Keywords: context attribution,explainability,interpretability,LLM,feature interactions,Shapley values,SPEX
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: numpy>=1.26
|
|
25
|
+
Requires-Dist: scikit-learn>=1.9
|
|
26
|
+
Provides-Extra: interactions
|
|
27
|
+
Requires-Dist: shapiq[sparse]<1.9,>=1.8; extra == "interactions"
|
|
28
|
+
Requires-Dist: sparse-transform<0.3,>=0.2.1; extra == "interactions"
|
|
29
|
+
Requires-Dist: lightgbm>=4; extra == "interactions"
|
|
30
|
+
Provides-Extra: test
|
|
31
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
32
|
+
Requires-Dist: hypothesis>=6.100; extra == "test"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# ContextAttr
|
|
36
|
+
|
|
37
|
+
Find out which parts of a prompt make a language model give an answer.
|
|
38
|
+
|
|
39
|
+
You split a prompt into parts: sentences, documents, words, or any pieces you choose. ContextAttr asks your model about many copies of the prompt with some parts removed. It then tells you how much each part, and each small group of parts working together, pushes the model towards an answer, and how far to trust that result. It works with any model, wherever it runs: you connect yours with a short wrapper.
|
|
40
|
+
|
|
41
|
+
Version 0.1.0 is an early release: the interface may still change.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
pip install contextattr
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Add `[interactions]` (`pip install "contextattr[interactions]"`) for SPEX and the other methods that find parts which matter only together. With uv: `uv add contextattr`. Needs Python 3.12 or later.
|
|
50
|
+
|
|
51
|
+
## Try it
|
|
52
|
+
|
|
53
|
+
This runs anywhere in seconds: `ToyModel` stands in for a real model.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import contextattr as ca
|
|
57
|
+
|
|
58
|
+
prompt = ca.Prompt("rio", messages=[{"role": "user", "content": [
|
|
59
|
+
ca.Unit("d1", "Rio Carnival takes place in February or March.", sep="\n"),
|
|
60
|
+
ca.Unit("d2", "Summer in Brazil runs from December to March.", sep="\n"),
|
|
61
|
+
ca.Unit("d3", "Winter in Brazil runs from June to September.", sep="\n"),
|
|
62
|
+
"Question: What is the weather like during Rio Carnival?"]}],
|
|
63
|
+
responses=[ca.Response("bad", "It is cold, because Carnival is in winter.", label="negative"),
|
|
64
|
+
ca.Response("good", "It is hot and humid: it is summer in Brazil.", label="positive")])
|
|
65
|
+
|
|
66
|
+
model = ca.ToyModel.demo() # a stand-in: put your own model's wrapper here
|
|
67
|
+
e = ca.explain(prompt, model, ca.Contrast(), method="kernelshap", budget=8)
|
|
68
|
+
print(e)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
It prints:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
rio · KernelSHAP(paired=True) · 8 masks · R² 0.991 on 128 test masks · noise 0 · 22 requests
|
|
75
|
+
d3 +0.2500 Winter in Brazil runs from June to September.
|
|
76
|
+
d1 -0.0234 Rio Carnival takes place in February or March.
|
|
77
|
+
d2 -0.2854 Summer in Brazil runs from December to March.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**What the example says:**
|
|
81
|
+
|
|
82
|
+
| In the code | Means |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `ca` | the package, under the short name all the docs use |
|
|
85
|
+
| `ca.Prompt("rio", messages=...)` | one prompt, named `rio`, as chat messages |
|
|
86
|
+
| `ca.Unit("d1", "...", sep="\n")` | a part that may be removed, named `d1`. `sep` is text that goes with it, here the line break. Everything else, like the question, always stays |
|
|
87
|
+
| `ca.Response("bad", "...", label="negative")` | an answer the model could give. `negative` marks the answer you do not want, `positive` the one you do |
|
|
88
|
+
| `model` | the wrapper around your model |
|
|
89
|
+
| `ca.Contrast()` | what to explain: how much more likely the model finds the unwanted answers than the wanted ones |
|
|
90
|
+
| `method="kernelshap"`, `budget=8` | how the scores are worked out, and how many copies of the prompt, each with some parts removed, the model is asked about |
|
|
91
|
+
| `e` | the result |
|
|
92
|
+
|
|
93
|
+
**How to read the output:**
|
|
94
|
+
|
|
95
|
+
- The first line names the prompt and the method and says how far to trust the result. R² close to 1 means the scores describe the model well; close to 0, do not use them.
|
|
96
|
+
- Then each part with its score, highest first. A positive score pushes the model towards the unwanted answer: `d3`, the sentence about winter, is what makes this model say "cold". A negative score pushes it away: `d2` protects against it. A score near zero means the part hardly matters.
|
|
97
|
+
- `e.scores` gives the scores as a dictionary, and `e.to_html("report.html")` writes a page with each part shaded by its score.
|
|
98
|
+
|
|
99
|
+
[Reading a result](https://github.com/Surya-Rayala/contextattr/blob/main/docs/results.md) shows the output of every method on this example and what its scores mean.
|
|
100
|
+
|
|
101
|
+
## Use it with your own model
|
|
102
|
+
|
|
103
|
+
ContextAttr reaches your model through a small class that you write, a *wrapper*. [Writing a wrapper](https://github.com/Surya-Rayala/contextattr/blob/main/docs/wrapper.md) is the guide, with two complete wrappers to copy from: one for an API and one for a model you run yourself.
|
|
104
|
+
|
|
105
|
+
1. **Get the file to fill in.** `contextattr template my_wrapper.py` writes it. It needs one or both of:
|
|
106
|
+
- `generate`: writes replies to a prompt. Every model can do this, including the OpenAI, Gemini and Anthropic APIs.
|
|
107
|
+
- `score`: gives the log-probability of each token of a given answer. Possible when you run the model yourself, in your own code or on a vLLM or SGLang server.
|
|
108
|
+
2. **Check it.** `python my_wrapper.py` runs `ca.check_model` on it and says what, if anything, is wrong.
|
|
109
|
+
3. **Use it.** Put your wrapper where the example has `model`.
|
|
110
|
+
|
|
111
|
+
## Choose what to explain, and how
|
|
112
|
+
|
|
113
|
+
- **Parts:** mark them with `ca.Unit`, as above, or cut a text automatically: `ca.split(text, "sentence", id="s")` (or `"word"`, `"line"`, `"paragraph"`).
|
|
114
|
+
- **Answer:** `ca.LogProb()` explains one answer. `ca.Contrast()` explains what moves the model from the answers you want to the ones you don't. `ca.Sampled(check)` works when your model can only generate text.
|
|
115
|
+
- **Method:** `contextcite`, the default, takes a quick and cheap look, even at long prompts. `spex` finds parts that matter only together. `budget` sets how many copies of the prompt the model is asked about: more is steadier and costs more.
|
|
116
|
+
- **Many prompts:** `contextattr run run.toml` explains a whole file of prompts and writes a report.
|
|
117
|
+
|
|
118
|
+
## Learn more
|
|
119
|
+
|
|
120
|
+
| Document | Covers |
|
|
121
|
+
|---|---|
|
|
122
|
+
| [docs/wrapper.md](https://github.com/Surya-Rayala/contextattr/blob/main/docs/wrapper.md) | connecting your model: what to write, two complete wrappers, the rules, and what to do when the check fails |
|
|
123
|
+
| [docs/results.md](https://github.com/Surya-Rayala/contextattr/blob/main/docs/results.md) | reading a result: the output of every method on one example, what its scores mean, and how far to trust them |
|
|
124
|
+
| [docs/methods.md](https://github.com/Surya-Rayala/contextattr/blob/main/docs/methods.md) | which method to use, how many masks to give it, and every setting with its default |
|
|
125
|
+
| [docs/interface.md](https://github.com/Surya-Rayala/contextattr/blob/main/docs/interface.md) | the reference: prompts and the prompt file, answers and labels, what can be explained, costs, runs and the command line |
|
|
126
|
+
|
|
127
|
+
## Licence
|
|
128
|
+
|
|
129
|
+
ContextAttr is released under the MIT licence: see [LICENSE](https://github.com/Surya-Rayala/contextattr/blob/main/LICENSE).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
contextattr/__init__.py,sha256=4nyU5K4kR14X4qybjSQYifex2DykprwCseegxdC7xGs,1247
|
|
2
|
+
contextattr/__main__.py,sha256=csk-ut-m00mlCsYR1xRESKPb4qfCxaWwn87vxJzrhvU,109
|
|
3
|
+
contextattr/cache.py,sha256=MGoAyvplajOjZMepLZesGc9Uig8G7An2LyV7W4jp0V8,2752
|
|
4
|
+
contextattr/cli.py,sha256=MDV-L3zJjnR4piZazuJ5CrnKaDo71TbxoL6t-3Wcn4k,8590
|
|
5
|
+
contextattr/evaluate.py,sha256=iPIkWkc45ZnnTpuIGA49GrRKCKkjp302SZSTwmSZd30,7103
|
|
6
|
+
contextattr/report.py,sha256=pUDAlxLjP1omdHCagh4PkeLTUTQpQN_JKXqdatPO-rA,5682
|
|
7
|
+
contextattr/result.py,sha256=ZgxpYoeYzRxL8Nxrqhz5slJKHYoILpR8BfPNBxreYuo,5947
|
|
8
|
+
contextattr/run.py,sha256=6iSypAPfC0n4Bo_QQQXuOg8ZiVZLIkt-keOffKUAEi4,17517
|
|
9
|
+
contextattr/value.py,sha256=K11gSSACqUTfboanHo4ZhImUwtfVkbfqxvoBr-x_2uc,11068
|
|
10
|
+
contextattr/methods/__init__.py,sha256=QEeQvEbAdLZrnzQfQpU1vRxY0zxoDn0Cev2BIkiZLJY,1336
|
|
11
|
+
contextattr/methods/base.py,sha256=xvhk-rR7_-S6HGvW9cLhZuu4B1GVQQ8HAeAbbelocBM,3587
|
|
12
|
+
contextattr/methods/indices.py,sha256=-KaXDhqb5Io1f0g-w5SDzDXYvv8kR8n8zIZIR-7EUdQ,1986
|
|
13
|
+
contextattr/methods/interactions.py,sha256=4wUDWCpVw-LHAN3t7Bl7exq03htATEWceM_xHuDH5tc,11284
|
|
14
|
+
contextattr/methods/kernelshap.py,sha256=56MUsb14hAIPnsGMdxBt2Ta-9yrv4iSZ4d_qPT5Cmdo,5830
|
|
15
|
+
contextattr/methods/linear.py,sha256=5iQnHESFy2yeLy8IA_68Llz11PZdShshAbsNhifw5TY,5195
|
|
16
|
+
contextattr/methods/loo.py,sha256=R3blTynnnImRq_OtIFSvwQhL3EsFZi58ydveTQnrRQo,1344
|
|
17
|
+
contextattr/prompt/__init__.py,sha256=P7j6cR5CEpRpVHBrwD0IYT0gNZjZMf5koLAZ3VGP8MI,517
|
|
18
|
+
contextattr/prompt/io.py,sha256=1-VcVpzyTp_rAzv3M81aT1maynEFdiY5GxWhUxpXgfc,8875
|
|
19
|
+
contextattr/prompt/render.py,sha256=yzeodc5-_d_A55DDoo6woCTmO-uLaBBF9B22OxI30rg,2964
|
|
20
|
+
contextattr/prompt/split.py,sha256=wEZFORXpzJgHsL-gp0JrGOqbb0yRbFjW7ZI5TwWu8SM,6892
|
|
21
|
+
contextattr/prompt/units.py,sha256=jvBOt7AXnSSieC7GpnluGSmPSCihE7fHvcIYeTwB6DM,11209
|
|
22
|
+
contextattr/responses/__init__.py,sha256=wr1dp5MiGSEXzSnjIF6FwXwFo1sOupSQVMjFfDoiEHo,313
|
|
23
|
+
contextattr/responses/checks.py,sha256=oXHbYZ82M_bdlHXYmEWCmqAUqc-hLibG-yeOFEPb_9k,7024
|
|
24
|
+
contextattr/responses/sample.py,sha256=ARNb0jzmfW4ZIx8darHNhYfQKSL4Wd0hsugjP7EeS94,1886
|
|
25
|
+
contextattr/targets/__init__.py,sha256=i4JxFaHGQqi9mrsgBR6ceYuYdgAsRP5yUL9rarqjdis,362
|
|
26
|
+
contextattr/targets/sampled.py,sha256=3u60m_NCHdOxUWWz1dXmsHwn7upZ8MitDV8rsmBklhA,2813
|
|
27
|
+
contextattr/targets/scored.py,sha256=n7p6iaNdn051bqHz8ZI17K2vxDGsgxgfsadVY_U84Og,6371
|
|
28
|
+
contextattr/wrapper/__init__.py,sha256=jFzFVUwRW-ECkuQ8bxZT6ySuvuHC_bWqy8aJuuosrek,552
|
|
29
|
+
contextattr/wrapper/check.py,sha256=nxkVF1dUEdvW1YnK0XV6l_CKg793J7TsucsNbydItKY,8190
|
|
30
|
+
contextattr/wrapper/contract.py,sha256=d3Qjml5rRNRKvu6HIRX24rn8OCKHhlVs7ZdLU_0Sf7I,7206
|
|
31
|
+
contextattr/wrapper/template.py,sha256=lskvy8hwqSd7ZxhwEunZpN14QWgxky_pOGiqVP27oLM,3047
|
|
32
|
+
contextattr/wrapper/toy.py,sha256=oyQX-Lu3IlJv1z9N9tlH1ALjC27Vi43fIjPW79lvPvk,6610
|
|
33
|
+
contextattr-0.1.0.dist-info/licenses/LICENSE,sha256=lsxcqFqfXFICgMR2fHHX8MrOD8p2cH-GPv5AffW1gDg,1069
|
|
34
|
+
contextattr-0.1.0.dist-info/METADATA,sha256=YpkRmuSWa8z12CVgBLc93GG5tzP-wlx-7_rAQpiHz4c,7846
|
|
35
|
+
contextattr-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
36
|
+
contextattr-0.1.0.dist-info/entry_points.txt,sha256=IyucNr2RurEFZpf0-NqwjKxCV2DCiks3p7VkC-I-Y3w,53
|
|
37
|
+
contextattr-0.1.0.dist-info/top_level.txt,sha256=tqF9BghHlsVfEpUVelZMxQWkSxQ6ZMJMktUqSRjuqG8,12
|
|
38
|
+
contextattr-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Surya Rayala
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
contextattr
|