scriptedAssistant function
- AssistantConfig config, {
- List<
ScriptedTurn> script = const [], - bool validateAgainstGrammar = true,
- String chatTemplate = qwen35ChatTemplate,
- PrefixStateStore? prefixStore,
- DateTime wallClock()?,
An Assistant with config on a ScriptedEngine whose outputs are script, its model loaded (from no file:
ModelIdentity.fixed('scripted.gguf')). Add more outputs to the engine's outputs as the test goes.
With validateAgainstGrammar, every generation with a grammar checks its output first: the part from the lazy
grammar's trigger on (the whole output for an eager grammar) must be admitted by the grammar (GbnfRecognizer),
else it fails with ScriptRejectedByGrammar. The engine saves prefix states only with a prefixStore (else the
model prefills every prompt, and no file is written). Throws StateError when the model does not load (a
chatTemplate the config's profile refuses), and ArgumentError for a ScriptedTurn.toolCall of script that
the model's profile does not read back as that call (a family other than Qwen3.5's: script it as text).
Implementation
Future<(Assistant, ScriptedEngine)> scriptedAssistant(
AssistantConfig config, {
List<ScriptedTurn> script = const [],
bool validateAgainstGrammar = true,
String chatTemplate = qwen35ChatTemplate,
PrefixStateStore? prefixStore,
DateTime Function()? wallClock,
}) async {
final persistence = prefixStore != null;
final engine = validateAgainstGrammar
? _GrammarCheckingEngine(chatTemplate: chatTemplate, supportsStatePersistence: persistence)
: ScriptedEngine(chatTemplate: chatTemplate, supportsStatePersistence: persistence);
engine.outputs.addAll([for (final turn in script) turn.output]);
final assistant = Assistant(
config,
engine: () => engine,
prefixStore: prefixStore ?? InMemoryPrefixStateStore(),
wallClock: wallClock,
);
const file = 'scripted.gguf';
if (!await assistant.loadModel(file, identity: const ModelIdentity.fixed(file))) {
final error = assistant.state.loadError;
await assistant.dispose();
throw StateError('the scripted model did not load: $error');
}
final model = assistant.model!;
if (model.profile is! Qwen35Profile) {
final specs = config.toolkit.specs;
final prompt = model.profile.prepare(model.info, const [ChatMessage.user('.')], tools: specs);
for (final turn in script.whereType<ScriptedToolCall>()) {
final read = model.profile.parse(turn.output, specs, prompt).toolCalls;
if (read.length != 1 || read.single.name != turn.name) {
await assistant.dispose();
throw ArgumentError.value(
turn.name,
'script',
"${model.profile} does not read ScriptedTurn.toolCall's Qwen3.5 XML envelope: script the call as "
"ScriptedTurn.text in the family's own syntax",
);
}
}
}
return (assistant, engine);
}