interlens 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.
- interlens/__init__.py +85 -0
- interlens/context/__init__.py +23 -0
- interlens/context/context_policy.py +46 -0
- interlens/context/drop_oldest_policy.py +32 -0
- interlens/context/error_policy.py +22 -0
- interlens/context/sliding_window_policy.py +33 -0
- interlens/context/summarize_policy.py +47 -0
- interlens/context_item.py +23 -0
- interlens/conversation.py +393 -0
- interlens/execution_mode.py +23 -0
- interlens/factories.py +120 -0
- interlens/hooks/__init__.py +3 -0
- interlens/hooks/message_hook.py +49 -0
- interlens/interp/__init__.py +29 -0
- interlens/interp/activation_cache.py +132 -0
- interlens/interp/capture.py +85 -0
- interlens/interp/layers.py +23 -0
- interlens/interp/logprobs.py +30 -0
- interlens/interp/patching.py +50 -0
- interlens/interp/steering.py +62 -0
- interlens/loading/__init__.py +28 -0
- interlens/loading/load.py +69 -0
- interlens/loading/model_cache.py +38 -0
- interlens/loading/registry.py +106 -0
- interlens/message.py +24 -0
- interlens/participant/__init__.py +4 -0
- interlens/participant/config/__init__.py +10 -0
- interlens/participant/config/api_participant_config.py +46 -0
- interlens/participant/config/model_participant_config.py +122 -0
- interlens/participant/config/participant_config.py +79 -0
- interlens/participant/participant.py +105 -0
- interlens/participant/participants/api_client.py +54 -0
- interlens/participant/participants/api_participant.py +80 -0
- interlens/participant/participants/gemma.py +68 -0
- interlens/participant/participants/model_participant.py +404 -0
- interlens/participant/participants/qwen.py +4 -0
- interlens/participant/role.py +3 -0
- interlens/reasoning_visibility.py +24 -0
- interlens/runner/__init__.py +19 -0
- interlens/runner/analyzer_registry.py +23 -0
- interlens/runner/batched.py +49 -0
- interlens/runner/devices.py +15 -0
- interlens/runner/pool.py +160 -0
- interlens/runner/rollout.py +40 -0
- interlens/runner/spec.py +26 -0
- interlens/runner/worker_init.py +18 -0
- interlens/stop/__init__.py +16 -0
- interlens/stop/conditions.py +74 -0
- interlens/stop/stop_condition.py +41 -0
- interlens/template.py +87 -0
- interlens/tools/__init__.py +5 -0
- interlens/tools/registry.py +33 -0
- interlens/tools/tool.py +28 -0
- interlens/tools/tool_call.py +26 -0
- interlens/transcript.py +179 -0
- interlens/view.py +31 -0
- interlens-0.1.0.dist-info/METADATA +74 -0
- interlens-0.1.0.dist-info/RECORD +60 -0
- interlens-0.1.0.dist-info/WHEEL +4 -0
- interlens-0.1.0.dist-info/licenses/LICENSE +21 -0
interlens/__init__.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from .message import Message
|
|
2
|
+
from .transcript import Transcript
|
|
3
|
+
from .context_item import ContextItem
|
|
4
|
+
from .conversation import Conversation
|
|
5
|
+
from .template import ConversationTemplate
|
|
6
|
+
from .reasoning_visibility import ReasoningVisibility
|
|
7
|
+
from .execution_mode import ExecutionMode
|
|
8
|
+
from .participant import Participant
|
|
9
|
+
from .participant.participants.model_participant import ModelParticipant
|
|
10
|
+
from .participant.participants.qwen import QwenModelParticipant
|
|
11
|
+
from .participant.participants.gemma import GemmaModelParticipant
|
|
12
|
+
from .participant.participants.api_participant import APIParticipant
|
|
13
|
+
from .participant.config import ParticipantConfig, ModelParticipantConfig, APIParticipantConfig
|
|
14
|
+
from .context import ContextPolicy, ErrorPolicy, DropOldestPolicy, SlidingWindowPolicy, SummarizePolicy
|
|
15
|
+
from .hooks import MessageHook, MessageHookResult, HookAction
|
|
16
|
+
from .stop import (
|
|
17
|
+
StopCondition,
|
|
18
|
+
AnyStopCondition,
|
|
19
|
+
TurnStopCondition,
|
|
20
|
+
TokenStopCondition,
|
|
21
|
+
ElapsedTimeStopCondition,
|
|
22
|
+
StopStringCondition,
|
|
23
|
+
)
|
|
24
|
+
from .interp import ActivationCache, CaptureSpec, SteeringSpec, Patch, token_logprobs, decoder_layers
|
|
25
|
+
from .tools import Tool, ToolCall, ToolResult, ToolRegistry, DEFAULT_REGISTRY
|
|
26
|
+
from .runner import (
|
|
27
|
+
available_devices, ConversationSpec, run_conversations, RunResult, RunReport, rollout,
|
|
28
|
+
register_analyzer, register_worker_init,
|
|
29
|
+
)
|
|
30
|
+
from .factories import conversation_from_models, conversation_from_ids, AutoModelParticipant, ModelLike
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"Message",
|
|
34
|
+
"Transcript",
|
|
35
|
+
"ContextItem",
|
|
36
|
+
"Conversation",
|
|
37
|
+
"ConversationTemplate",
|
|
38
|
+
"ReasoningVisibility",
|
|
39
|
+
"ExecutionMode",
|
|
40
|
+
"Participant",
|
|
41
|
+
"ModelParticipant",
|
|
42
|
+
"QwenModelParticipant",
|
|
43
|
+
"GemmaModelParticipant",
|
|
44
|
+
"APIParticipant",
|
|
45
|
+
"ParticipantConfig",
|
|
46
|
+
"ModelParticipantConfig",
|
|
47
|
+
"APIParticipantConfig",
|
|
48
|
+
"ContextPolicy",
|
|
49
|
+
"ErrorPolicy",
|
|
50
|
+
"DropOldestPolicy",
|
|
51
|
+
"SlidingWindowPolicy",
|
|
52
|
+
"SummarizePolicy",
|
|
53
|
+
"MessageHook",
|
|
54
|
+
"MessageHookResult",
|
|
55
|
+
"HookAction",
|
|
56
|
+
"StopCondition",
|
|
57
|
+
"AnyStopCondition",
|
|
58
|
+
"TurnStopCondition",
|
|
59
|
+
"TokenStopCondition",
|
|
60
|
+
"ElapsedTimeStopCondition",
|
|
61
|
+
"StopStringCondition",
|
|
62
|
+
"ActivationCache",
|
|
63
|
+
"CaptureSpec",
|
|
64
|
+
"SteeringSpec",
|
|
65
|
+
"Patch",
|
|
66
|
+
"token_logprobs",
|
|
67
|
+
"decoder_layers",
|
|
68
|
+
"Tool",
|
|
69
|
+
"ToolCall",
|
|
70
|
+
"ToolResult",
|
|
71
|
+
"ToolRegistry",
|
|
72
|
+
"DEFAULT_REGISTRY",
|
|
73
|
+
"available_devices",
|
|
74
|
+
"ConversationSpec",
|
|
75
|
+
"run_conversations",
|
|
76
|
+
"RunResult",
|
|
77
|
+
"RunReport",
|
|
78
|
+
"rollout",
|
|
79
|
+
"register_analyzer",
|
|
80
|
+
"register_worker_init",
|
|
81
|
+
"conversation_from_models",
|
|
82
|
+
"conversation_from_ids",
|
|
83
|
+
"AutoModelParticipant",
|
|
84
|
+
"ModelLike",
|
|
85
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .context_policy import ContextPolicy
|
|
2
|
+
from .error_policy import ErrorPolicy
|
|
3
|
+
from .drop_oldest_policy import DropOldestPolicy
|
|
4
|
+
from .sliding_window_policy import SlidingWindowPolicy
|
|
5
|
+
from .summarize_policy import SummarizePolicy
|
|
6
|
+
|
|
7
|
+
# Registry for (de)serializing policies by ``kind`` (their class name), so templates round-trip.
|
|
8
|
+
_POLICIES = {c.__name__: c for c in (ErrorPolicy, DropOldestPolicy, SlidingWindowPolicy, SummarizePolicy)}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def context_policy_from_dict(data: dict) -> ContextPolicy:
|
|
12
|
+
params = {k: v for k, v in data.items() if k != "kind"}
|
|
13
|
+
return _POLICIES[data["kind"]](**params)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"ContextPolicy",
|
|
18
|
+
"ErrorPolicy",
|
|
19
|
+
"DropOldestPolicy",
|
|
20
|
+
"SlidingWindowPolicy",
|
|
21
|
+
"SummarizePolicy",
|
|
22
|
+
"context_policy_from_dict",
|
|
23
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
from ..view import ViewSegment
|
|
6
|
+
|
|
7
|
+
# Origins that must never be trimmed by a context policy: the system block, the scenario/moderator seed, and
|
|
8
|
+
# private briefings all carry essential framing. Only ``turn`` segments (the growing dialogue middle) are
|
|
9
|
+
# eligible for trimming.
|
|
10
|
+
PRESERVED_ORIGINS = ("system", "moderator", "private_context")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ContextPolicy(ABC):
|
|
14
|
+
"""Decides how to fit a participant's view within its model's context window.
|
|
15
|
+
|
|
16
|
+
Crucially, ``fit`` runs on the **typed** ``ViewSegment`` list, *before* the family-specific ``finalize_view``
|
|
17
|
+
folds/merges anything. Operating pre-finalize means the policy can reliably preserve the system block and
|
|
18
|
+
moderator seed (their ``origin`` is still intact) and trim only ``turn`` segments, instead of trying to
|
|
19
|
+
reverse-engineer meaning out of already-folded text.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def fit(self, segments: list[ViewSegment], tokenizer, limit: int | None) -> list[ViewSegment]:
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
def to_dict(self) -> dict:
|
|
27
|
+
"""Serialize as ``{"kind": ..., **params}``. Subclasses with parameters extend the params; the default
|
|
28
|
+
covers parameterless policies."""
|
|
29
|
+
return {"kind": type(self).__name__}
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def _limit(tokenizer, limit: int | None) -> int:
|
|
33
|
+
"""Resolve the effective token budget; falls back to the tokenizer's declared max length."""
|
|
34
|
+
if limit is not None:
|
|
35
|
+
return limit
|
|
36
|
+
return int(getattr(tokenizer, "model_max_length", 1_000_000_000) or 1_000_000_000)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def _seg_tokens(segment: ViewSegment, tokenizer) -> int:
|
|
40
|
+
"""Approximate token cost of a segment's content (ignores per-template wrapper tokens — close enough for
|
|
41
|
+
fitting decisions)."""
|
|
42
|
+
return len(tokenizer(segment.content, add_special_tokens=False).input_ids)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def _total_tokens(cls, segments: list[ViewSegment], tokenizer) -> int:
|
|
46
|
+
return sum(cls._seg_tokens(s, tokenizer) for s in segments)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .context_policy import ContextPolicy, PRESERVED_ORIGINS
|
|
4
|
+
from ..view import ViewSegment
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DropOldestPolicy(ContextPolicy):
|
|
8
|
+
"""Drop the oldest ``turn`` segments (preserving system/moderator/private_context) until the view fits."""
|
|
9
|
+
|
|
10
|
+
def fit(self, segments: list[ViewSegment], tokenizer, limit: int | None) -> list[ViewSegment]:
|
|
11
|
+
budget = self._limit(tokenizer, limit)
|
|
12
|
+
if self._total_tokens(segments, tokenizer) <= budget:
|
|
13
|
+
return segments
|
|
14
|
+
|
|
15
|
+
# Indices of trimmable turns, oldest first.
|
|
16
|
+
turn_indices = [i for i, s in enumerate(segments) if s.origin == "turn"]
|
|
17
|
+
dropped: set[int] = set()
|
|
18
|
+
total = self._total_tokens(segments, tokenizer)
|
|
19
|
+
for i in turn_indices:
|
|
20
|
+
if total <= budget:
|
|
21
|
+
break
|
|
22
|
+
total -= self._seg_tokens(segments[i], tokenizer)
|
|
23
|
+
dropped.add(i)
|
|
24
|
+
|
|
25
|
+
kept = [s for i, s in enumerate(segments) if i not in dropped]
|
|
26
|
+
# If preserved framing alone still overflows, there's nothing left to trim — surface it rather than lie.
|
|
27
|
+
if self._total_tokens(kept, tokenizer) > budget:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
f"cannot fit view within {budget} tokens even after dropping all turns "
|
|
30
|
+
f"(preserved {PRESERVED_ORIGINS} framing is too large)"
|
|
31
|
+
)
|
|
32
|
+
return kept
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .context_policy import ContextPolicy
|
|
4
|
+
from ..view import ViewSegment
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ErrorPolicy(ContextPolicy):
|
|
8
|
+
"""The safe default: raise if the view exceeds the context window rather than silently dropping content.
|
|
9
|
+
|
|
10
|
+
Silent truncation reads as "everything fit" when it didn't, which is exactly the kind of quiet data loss this
|
|
11
|
+
harness avoids. Callers who want trimming opt into ``DropOldestPolicy``/``SlidingWindowPolicy`` explicitly.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def fit(self, segments: list[ViewSegment], tokenizer, limit: int | None) -> list[ViewSegment]:
|
|
15
|
+
budget = self._limit(tokenizer, limit)
|
|
16
|
+
total = self._total_tokens(segments, tokenizer)
|
|
17
|
+
if total > budget:
|
|
18
|
+
raise ValueError(
|
|
19
|
+
f"view ~{total} tokens exceeds context limit {budget}; choose a DropOldest/SlidingWindow "
|
|
20
|
+
f"context policy or shorten the scenario"
|
|
21
|
+
)
|
|
22
|
+
return segments
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .context_policy import ContextPolicy
|
|
4
|
+
from ..view import ViewSegment
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SlidingWindowPolicy(ContextPolicy):
|
|
8
|
+
"""Keep the preserved framing plus the most recent ``keep_last`` turns; drop older turns.
|
|
9
|
+
|
|
10
|
+
``keep_system=True`` (the default) preserves the system/moderator/private_context framing regardless of the
|
|
11
|
+
window; set it False to also let framing fall outside the window (rarely wanted). Unlike ``DropOldestPolicy``
|
|
12
|
+
this is a fixed-size window rather than a fit-to-budget trim, so it's predictable turn-to-turn.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, keep_last: int, keep_system: bool = True):
|
|
16
|
+
self.keep_last = keep_last
|
|
17
|
+
self.keep_system = keep_system
|
|
18
|
+
|
|
19
|
+
def to_dict(self) -> dict:
|
|
20
|
+
return {"kind": "SlidingWindowPolicy", "keep_last": self.keep_last, "keep_system": self.keep_system}
|
|
21
|
+
|
|
22
|
+
def fit(self, segments: list[ViewSegment], tokenizer, limit: int | None) -> list[ViewSegment]:
|
|
23
|
+
turn_indices = [i for i, s in enumerate(segments) if s.origin == "turn"]
|
|
24
|
+
keep_turns = set(turn_indices[-self.keep_last:]) if self.keep_last > 0 else set()
|
|
25
|
+
|
|
26
|
+
kept = []
|
|
27
|
+
for i, s in enumerate(segments):
|
|
28
|
+
if s.origin == "turn":
|
|
29
|
+
if i in keep_turns:
|
|
30
|
+
kept.append(s)
|
|
31
|
+
elif self.keep_system:
|
|
32
|
+
kept.append(s)
|
|
33
|
+
return kept
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .context_policy import ContextPolicy, PRESERVED_ORIGINS
|
|
4
|
+
from ..view import ViewSegment
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SummarizePolicy(ContextPolicy):
|
|
8
|
+
"""Compress older turns into a single summary segment instead of dropping them outright (the heaviest
|
|
9
|
+
policy).
|
|
10
|
+
|
|
11
|
+
Keeps the preserved framing (system / moderator / private_context) and the most recent ``keep_last`` turns
|
|
12
|
+
verbatim, and replaces the older middle turns with one summary segment produced by ``summarizer`` — a
|
|
13
|
+
callable ``list[str] -> str`` over the dropped turns' contents. With no summarizer it inserts a neutral
|
|
14
|
+
placeholder, so it degrades to a labelled drop rather than silently losing content.
|
|
15
|
+
|
|
16
|
+
``summarizer`` is a live callable and so is not serialized; a loaded template gets ``summarizer=None`` and
|
|
17
|
+
the caller re-injects one if needed.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, keep_last: int = 4, summarizer=None):
|
|
21
|
+
self.keep_last = keep_last
|
|
22
|
+
self.summarizer = summarizer
|
|
23
|
+
|
|
24
|
+
def to_dict(self) -> dict:
|
|
25
|
+
return {"kind": "SummarizePolicy", "keep_last": self.keep_last}
|
|
26
|
+
|
|
27
|
+
def _summarize(self, texts: list[str]) -> str:
|
|
28
|
+
if self.summarizer is not None:
|
|
29
|
+
return self.summarizer(texts)
|
|
30
|
+
return f"[{len(texts)} earlier turns omitted]"
|
|
31
|
+
|
|
32
|
+
def fit(self, segments: list[ViewSegment], tokenizer, limit: int | None) -> list[ViewSegment]:
|
|
33
|
+
budget = self._limit(tokenizer, limit)
|
|
34
|
+
if self._total_tokens(segments, tokenizer) <= budget:
|
|
35
|
+
return segments
|
|
36
|
+
|
|
37
|
+
preserved = [s for s in segments if s.origin in PRESERVED_ORIGINS]
|
|
38
|
+
turns = [s for s in segments if s.origin == "turn"]
|
|
39
|
+
older = turns[: -self.keep_last] if self.keep_last > 0 else turns
|
|
40
|
+
recent = turns[-self.keep_last:] if self.keep_last > 0 else []
|
|
41
|
+
if not older:
|
|
42
|
+
return segments # nothing old enough to summarize; leave as-is
|
|
43
|
+
|
|
44
|
+
summary = ViewSegment(role="user", origin="moderator",
|
|
45
|
+
content="[Summary of earlier conversation]\n" + self._summarize([s.content for s in older]))
|
|
46
|
+
# Preserve original ordering: framing first, then the summary standing in for older turns, then recent.
|
|
47
|
+
return preserved + [summary] + recent
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from .participant.role import Role
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class ContextItem:
|
|
10
|
+
"""A single item of *private* asymmetric knowledge given to one participant (a briefing, a document, a fact).
|
|
11
|
+
|
|
12
|
+
Deliberately distinct from ``Message``: a briefing is not a dialogue turn — it has no turn index and its
|
|
13
|
+
``author`` is nominal — so overloading ``Message`` would give it misleading turn/authorship semantics.
|
|
14
|
+
|
|
15
|
+
Role semantics are pinned rather than role-swapped: a briefing renders by default as **user-provided
|
|
16
|
+
context** (``role_hint=USER``) — the participant reads it as information handed to it, not as its own prior
|
|
17
|
+
speech — with ``SYSTEM`` available for standing private instructions. Private context is injected only into
|
|
18
|
+
the owning participant's view and never enters the shared transcript.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
content: str
|
|
22
|
+
role_hint: Role = "user"
|
|
23
|
+
author: str = "moderator"
|