Skip to content

Commit 769c476

Browse files
committed
Sync fxtr plugin 0.1.2
1 parent b761be3 commit 769c476

9 files changed

Lines changed: 119 additions & 31 deletions

File tree

‎.claude-plugin/marketplace.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"name": "fxtr",
2222
"source": "./plugins/fxtr",
2323
"description": "Skills for writing, running, and viewing fxtr experiments, and for calling models from them with behaviors",
24-
"version": "0.1.1",
24+
"version": "0.1.2",
2525
"author": {
2626
"name": "TransluceAI"
2727
},

‎plugins/fxtr/.claude-plugin/plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
"author": {
55
"name": "TransluceAI"
66
},
7-
"version": "0.1.1"
7+
"version": "0.1.2"
88
}

‎plugins/fxtr/skills/behaviors/provenance.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"skill": "behaviors",
33
"documentation": "https://docs.transluce.ai/behaviors",
44
"packages": {
5-
"fxtr": "0.0.1a2",
6-
"behaviors": "0.0.1a2"
5+
"fxtr": "0.0.1a4",
6+
"behaviors": "0.0.1a3"
77
},
8-
"revision": "81bc041101951f16dd70e69340d64e80abd34f01",
8+
"revision": "051a5d3ab1f6df872092af2365b34c5d0f6671d3",
99
"modified": false,
10-
"content_sha256": "de0ca958d570464776b006b595d0f26b7be280a6c5baf88c46629d2982757eee"
10+
"content_sha256": "9ca8c278e92e77f29f281313b17717930e6ecc67e13027fedde3152fc37ea5b9"
1111
}

‎plugins/fxtr/skills/behaviors/references/guides/conversation-records.mdx‎

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,89 @@ answer's text alone, keep the `ContentText` blocks of the turn's `AssistantMessa
1616
[judge step's](language-models.mdx#a-judge-step) `answer_text` does, and keep the
1717
structured rollout for later inspection.
1818

19-
A model turn made by an API-backed session records the provider's response in
20-
`ModelTurn.metadata["response"]`, as JSON, including its stop reason and usage. Metadata is defined
21-
by whatever produced the turn, so validate it before scoring with it.
19+
## Metadata
20+
21+
Turns (`ContextTurn`, `ModelTurn`, `UserTurn`, `ToolTurn`), `EndRollout`, and `ChatRollout` carry
22+
`metadata`: a tuple of `DynamicData` entries, each holding one
23+
[tagged dataclass](../../../fxtr/references/concepts/entities-and-custom-types.mdx#inline-tagged-unions) in its stored
24+
form. Whatever produced the turn decides which types it records, so a reader names the type it
25+
expects. `find_metadata(metadata, SomeType)` returns the first entry of that type, decoded, or
26+
`None` if there is none; `entry.try_as_type(SomeType)` does the same for a single entry. An entry
27+
tagged with the type but holding fields that don't match it raises `ConversionError`.
28+
29+
A model turn made by an API-backed session records the provider's response as a `ModelResponse`,
30+
including its stop reason and usage:
31+
32+
```python
33+
from behaviors.types import ModelResponse, find_metadata
34+
35+
response = find_metadata(model_turn.metadata, ModelResponse)
36+
if response is not None:
37+
print(response.stop_reason.type, response.usage.output_tokens)
38+
```
39+
40+
To record something of your own, such as which question a context turn asked, define a frozen
41+
tagged dataclass with a type ID in a domain you control, and wrap each value with
42+
`DynamicData.from_tagged`:
43+
44+
```python
45+
from dataclasses import dataclass
46+
47+
from behaviors.types import ContentText, ContextTurn, DynamicData, UserMessage, find_metadata
48+
from fxtr.entity_defns.dataclass_bases import TaggedDataclass
49+
50+
51+
@dataclass(frozen=True)
52+
class QuestionIndex(TaggedDataclass, fxtr_type="org.example.my_experiment.QuestionIndex.v1"):
53+
index: int
54+
55+
56+
turn = ContextTurn(
57+
messages=(UserMessage(content=(ContentText(text="What is 2 + 2?"),)),),
58+
available_tools=None,
59+
metadata=(DynamicData.from_tagged(QuestionIndex(index=0)),),
60+
)
61+
assert find_metadata(turn.metadata, QuestionIndex) == QuestionIndex(index=0)
62+
```
63+
64+
Add an entry to existing metadata with `turn.metadata + (DynamicData.from_tagged(value),)`.
65+
`Transcript`, `TranscriptGroup`, and `AgentRun` metadata is different: it is a JSON object with
66+
string keys, matching [Docent](docent-readings.mdx)'s metadata.
2267

2368
## Storing records
2469

2570
`ChatRollout`, `Transcript`, `TranscriptGroup`, `AgentRun`, and the concrete session
2671
initializations, such as `SetSystemMessage`, are fxtr entities, with type IDs of the form
27-
`org.transluce.behaviors.<Class>.v1`. A step that returns a `ChatRollout` has it stored like any
28-
other entity (see [Entities and custom types](../../../fxtr/references/concepts/entities-and-custom-types.mdx)), and
72+
`org.transluce.behaviors.<Class>.v<N>`, such as `org.transluce.behaviors.ChatRollout.v2`. A
73+
version is bumped when a type's stored shape changes, and a record stored under an earlier version
74+
doesn't load as the current type. Versions are per type: `ChatRollout` and its turns use `.v2`,
75+
while messages and `ModelResponse` use `.v1`. Only the current version of each type is supported.
76+
77+
A step that returns a `ChatRollout` has it stored like any other entity
78+
(see [Entities and custom types](../../../fxtr/references/concepts/entities-and-custom-types.mdx)), and
2979
`await storage.store(entity)` and `await storage.load(ref)` work on them too. To load a bare ID,
3080
pass the concrete type, as in `as_type=ChatRollout`, not an abstract base such as
3181
`SessionInitialization`. A tree of transcripts and groups is stored as one entity, so there is no
3282
need to store each child separately.
3383

84+
A stored rollout includes every field, including `initialization`. It keeps its initialization
85+
inline, as `DynamicData` (or an explicit null if there was none), since initialization types are
86+
open-ended. In Python, read it by naming the type you handle, which gives `None` for
87+
any other type:
88+
89+
```python
90+
from behaviors.types import SetSystemMessage
91+
92+
init = rollout.initialization
93+
set_system = init.try_as_type(SetSystemMessage) if init is not None else None
94+
```
95+
3496
fxtr checks stored records strictly: give fields their declared types (`temperature=0.0`, not
35-
`0`), and keep metadata to JSON values with string keys.
97+
`0`), and keep transcript metadata to JSON values with string keys.
98+
99+
The step cache can still hold results written under another type version. When changing the
100+
rollout version a project uses, launch under a fresh root or clear the affected steps' cache
101+
entries before rerunning (see [Managing the step cache](../../../fxtr/references/concepts/managing-the-step-cache.mdx)).
36102

37103
## Transcripts
38104

@@ -69,7 +135,11 @@ A new project's renderer package doesn't depend on it yet: add
69135
## Checkpointing sessions
70136

71137
A serializable session can save its state and be restored from it. `await session.get_state()`
72-
returns a JSON object, and `await factory.restore_session(state)` starts a session in that state.
138+
returns the state as `DynamicData`, tagged with the implementation's own state type, and
139+
`await factory.restore_session(state)` starts a session in that state; it raises `ConversionError`
140+
for a state that isn't its own. To keep a state as JSON, write
141+
`json.dumps(thaw_fxtr_data(state.data))` (`thaw_fxtr_data` is in `fxtr.core.entity_data`), and
142+
read it back with `DynamicData.from_data(json.loads(text))`.
73143
The sessions of `chat_session_model_from_api` and of `ScriptedUserSimulator` support this, and the
74144
`serializable_context_policy_from_*` combinators build checkpointable policies when every component
75145
they combine is checkpointable. Generator-based policies can't be checkpointed, because a

‎plugins/fxtr/skills/behaviors/references/guides/policies-and-tools.mdx‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,24 +55,36 @@ tools offered only at some turns), write the policy as an async generator: each
5555
next context turn, and the model's reply comes back as the value of the `yield`:
5656

5757
```python
58+
from dataclasses import dataclass
59+
5860
from behaviors.implementations.context_policies import (
5961
ContextPolicyGenerator, context_policy_from_generator,
6062
)
61-
from behaviors.types import ContentText, ContextTurn, EndRollout, UserMessage
63+
from behaviors.types import ContentText, ContextTurn, DynamicData, EndRollout, UserMessage
64+
from fxtr.entity_defns.dataclass_bases import TaggedDataclass
65+
66+
67+
@dataclass(frozen=True)
68+
class ReplyReceived(TaggedDataclass, fxtr_type="org.example.my_experiment.ReplyReceived.v1"):
69+
received: bool
70+
6271

6372
async def turns() -> ContextPolicyGenerator:
6473
reply = yield ContextTurn(
6574
messages=(UserMessage(content=(ContentText(text="Choose a color."),)),),
6675
available_tools=None,
6776
)
6877
# Inspect reply.messages here to choose a data-dependent next turn if needed.
69-
yield EndRollout(metadata={"received_reply": reply is not None})
78+
outcome = ReplyReceived(received=reply is not None)
79+
yield EndRollout(metadata=(DynamicData.from_tagged(outcome),))
7080

7181
policy = context_policy_from_generator(turns, initialization=None)
7282
```
7383

7484
Pass the generator function (`turns`), not a running generator: each rollout starts its own.
7585
Yield `EndRollout` explicitly to finish, since a generator that simply returns raises an error.
86+
Its `metadata`, like a turn's, holds tagged records (see
87+
[Metadata](conversation-records.mdx#metadata)).
7688
For a reusable policy with settings of its own, subclass `GeneratorContextPolicy`, overriding
7789
`generate_turns`, and `generate_initialization` if its sessions need one.
7890

‎plugins/fxtr/skills/first-experiment/provenance.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"skill": "first-experiment",
33
"documentation": null,
44
"packages": {
5-
"fxtr": "0.0.1a2",
6-
"behaviors": "0.0.1a2"
5+
"fxtr": "0.0.1a4",
6+
"behaviors": "0.0.1a3"
77
},
8-
"revision": "81bc041101951f16dd70e69340d64e80abd34f01",
8+
"revision": "051a5d3ab1f6df872092af2365b34c5d0f6671d3",
99
"modified": false,
1010
"content_sha256": "1b39e083738e6d10b515568bf7912241a1874cda150eb40908b92e7927d38b5f"
1111
}

‎plugins/fxtr/skills/fxtr/SKILL.md‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@ views before drawing anything in a renderer.
5757
Launching and following jobs, and the step cache that decides what a launch recomputes. Read
5858
Running and viewing jobs before the first launch.
5959

60-
| Task | Read |
61-
| ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
62-
| Launch a job, watch it in the viewer, return a report and read results, resume, inspect, or share jobs | [Running and viewing jobs](references/guides/running-and-viewing-jobs.mdx) |
63-
| Reuse or recompute cached results, resolve cache conflicts, change a step's logic | [Managing the step cache](references/concepts/managing-the-step-cache.mdx) |
64-
| Look up a command's options | [CLI reference](references/reference/cli.mdx) |
60+
| Task | Read |
61+
| ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
62+
| Launch a job, watch it in the viewer, return a report and read results, resume, inspect, or share jobs | [Running and viewing jobs](references/guides/running-and-viewing-jobs.mdx) |
63+
| Choose how many steps run at once, or speed up a job whose steps call APIs | [Choose how many steps run at once](references/guides/running-and-viewing-jobs.mdx#choose-how-many-steps-run-at-once) |
64+
| Reuse or recompute cached results, resolve cache conflicts, change a step's logic | [Managing the step cache](references/concepts/managing-the-step-cache.mdx) |
65+
| Look up a command's options | [CLI reference](references/reference/cli.mdx) |
6566

6667
## Best practices
6768

‎plugins/fxtr/skills/fxtr/provenance.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
"skill": "fxtr",
33
"documentation": "https://docs.transluce.ai/fxtr",
44
"packages": {
5-
"fxtr": "0.0.1a2",
6-
"behaviors": "0.0.1a2"
5+
"fxtr": "0.0.1a4",
6+
"behaviors": "0.0.1a3"
77
},
8-
"revision": "81bc041101951f16dd70e69340d64e80abd34f01",
8+
"revision": "051a5d3ab1f6df872092af2365b34c5d0f6671d3",
99
"modified": false,
10-
"content_sha256": "84eda286f7e7179dc378d160b5fba2e343752cda4a7f6afc1a7c629aa71c330c"
10+
"content_sha256": "51ba3d88cfa95b80fee856d1d9a8672f7df95e54d5ae269b9ceee5fcee63bbe9"
1111
}

‎plugins/fxtr/skills/fxtr/references/guides/running-and-viewing-jobs.mdx‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ if __name__ == "__main__":
4242
[(("greeting",), "Hello there"), (("question",), "Why?")],
4343
("[prompt: str]", str),
4444
)
45-
launch_cli(experiment, {"prompts": prompts}, anchor=__file__, max_concurrency=8)
45+
launch_cli(experiment, {"prompts": prompts}, anchor=__file__, max_concurrency=64)
4646
```
4747

4848
`anchor=__file__` tells fxtr which project the launcher belongs to. `max_concurrency` caps how
49-
many steps run at once; workflow functions don't count against it. If the job doesn't succeed,
50-
`launch_cli` exits with status 1.
49+
many steps run at once (see [Choose how many steps run at once](#choose-how-many-steps-run-at-once)).
50+
If the job doesn't succeed, `launch_cli` exits with status 1.
5151

5252
For more control, such as choosing the `root`, loading datasets, or reading results, write an
5353
async launcher with `open_local_client` and `run_job`:
@@ -65,7 +65,7 @@ async def main() -> None:
6565
[(("greeting",), "Hello there"), (("question",), "Why?")],
6666
("[prompt: str]", str),
6767
)
68-
async with open_local_client(__file__, max_concurrency=8) as client:
68+
async with open_local_client(__file__, max_concurrency=64) as client:
6969
result = await run_job(client, experiment, {"prompts": prompts}, root="word-count-v1")
7070
print(list(result.items()))
7171

@@ -87,6 +87,11 @@ if __name__ == "__main__":
8787
`run_job`.
8888
* In a notebook, call `await main()` instead of `anyio.run(main)`.
8989

90+
## Choose how many steps run at once
91+
92+
`max_concurrency`, on `launch_cli` and `open_local_client`, caps how many of a job's steps run at
93+
once. Note that all jobs run on a single event loop, so CPU-bound work will probably need a lower concurrency limit.
94+
9095
## Watch jobs in the viewer
9196

9297
Start the viewer from the project, in a terminal of its own, and leave it running:

0 commit comments

Comments
 (0)