生成設定のmanifest化の実装
This commit is contained in:
@@ -9,6 +9,6 @@ Pod の宣言的設定を TOML マニフェストとして定義・パースす
|
||||
- `ModelConfig` — LLM モデル設定(scheme、base_url、model_id、auth)
|
||||
- `SchemeKind` — wire scheme 種別(`Anthropic`, `OpenaiChat`, `OpenaiResponses`, `Gemini`)
|
||||
- `AuthRef` — 認証参照(`None`, `ApiKey { env, file }`, `CodexOAuth`)
|
||||
- `WorkerManifest` — ワーカー設定(システムプロンプト、max_tokens、temperature)
|
||||
- `WorkerManifest` — ワーカー設定(システムプロンプト、生成設定、reasoning)
|
||||
- `ScopeConfig` / `ScopeRule` / `Permission` — allow / deny の宣言的スコープ設定
|
||||
- `Scope` — 実行時スコープ。`from_config(&ScopeConfig, pwd)` で構築し、`is_readable` / `is_writable` / `permission_at` で問い合わせる
|
||||
|
||||
@@ -65,6 +65,12 @@ pub struct WorkerManifestConfig {
|
||||
#[serde(default)]
|
||||
pub temperature: Option<f32>,
|
||||
#[serde(default)]
|
||||
pub top_p: Option<f32>,
|
||||
#[serde(default)]
|
||||
pub top_k: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub stop_sequences: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub reasoning: Option<ReasoningControl>,
|
||||
#[serde(default)]
|
||||
pub tool_output: ToolOutputLimitsPartial,
|
||||
@@ -226,6 +232,9 @@ impl WorkerManifestConfig {
|
||||
max_tokens: upper.max_tokens.or(self.max_tokens),
|
||||
max_turns: upper.max_turns.or(self.max_turns),
|
||||
temperature: upper.temperature.or(self.temperature),
|
||||
top_p: upper.top_p.or(self.top_p),
|
||||
top_k: upper.top_k.or(self.top_k),
|
||||
stop_sequences: upper.stop_sequences.or(self.stop_sequences),
|
||||
reasoning: upper.reasoning.or(self.reasoning),
|
||||
tool_output: self.tool_output.merge(upper.tool_output),
|
||||
}
|
||||
@@ -339,6 +348,9 @@ impl TryFrom<PodManifestConfig> for PodManifest {
|
||||
max_tokens: cfg.worker.max_tokens,
|
||||
max_turns: cfg.worker.max_turns,
|
||||
temperature: cfg.worker.temperature,
|
||||
top_p: cfg.worker.top_p,
|
||||
top_k: cfg.worker.top_k,
|
||||
stop_sequences: cfg.worker.stop_sequences.unwrap_or_default(),
|
||||
reasoning: cfg.worker.reasoning,
|
||||
tool_output: ToolOutputLimits {
|
||||
default_max_bytes: cfg
|
||||
@@ -586,6 +598,33 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_worker_generation_settings_upper_wins() {
|
||||
let lower = PodManifestConfig {
|
||||
worker: WorkerManifestConfig {
|
||||
top_p: Some(0.8),
|
||||
top_k: Some(20),
|
||||
stop_sequences: Some(vec!["lower".into()]),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let upper = PodManifestConfig {
|
||||
worker: WorkerManifestConfig {
|
||||
top_p: Some(0.9),
|
||||
stop_sequences: Some(vec!["upper".into()]),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let merged = lower.merge(upper);
|
||||
|
||||
assert_eq!(merged.worker.top_p, Some(0.9));
|
||||
assert_eq!(merged.worker.top_k, Some(20));
|
||||
assert_eq!(merged.worker.stop_sequences, Some(vec!["upper".into()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_scope_accumulates_allow_and_deny() {
|
||||
let lower = PodManifestConfig {
|
||||
@@ -732,6 +771,26 @@ reasoning = -1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_toml_accepts_worker_generation_settings() {
|
||||
let cfg = PodManifestConfig::from_toml(
|
||||
r#"
|
||||
[worker]
|
||||
top_p = 0.9
|
||||
top_k = 40
|
||||
stop_sequences = ["\n\n", "</stop>"]
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(cfg.worker.top_p, Some(0.9));
|
||||
assert_eq!(cfg.worker.top_k, Some(40));
|
||||
assert_eq!(
|
||||
cfg.worker.stop_sequences,
|
||||
Some(vec!["\n\n".into(), "</stop>".into()])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_toml_partial_layer_succeeds() {
|
||||
// A project-layer manifest with only scope set must parse fine.
|
||||
|
||||
@@ -102,6 +102,12 @@ pub struct WorkerManifest {
|
||||
#[serde(default)]
|
||||
pub temperature: Option<f32>,
|
||||
#[serde(default)]
|
||||
pub top_p: Option<f32>,
|
||||
#[serde(default)]
|
||||
pub top_k: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub stop_sequences: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub reasoning: Option<ReasoningControl>,
|
||||
/// Byte-size caps applied to tool `content` before it reaches the
|
||||
/// conversation history. The section is optional in TOML — when
|
||||
@@ -299,6 +305,9 @@ permission = "write"
|
||||
assert_eq!(manifest.scope.allow.len(), 1);
|
||||
assert!(manifest.scope.deny.is_empty());
|
||||
assert_eq!(manifest.worker.instruction, defaults::DEFAULT_INSTRUCTION);
|
||||
assert!(manifest.worker.top_p.is_none());
|
||||
assert!(manifest.worker.top_k.is_none());
|
||||
assert!(manifest.worker.stop_sequences.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -316,6 +325,9 @@ auth = { kind = "api_key", file = "/abs/keys/anthropic" }
|
||||
instruction = "$user/reviewer"
|
||||
max_tokens = 4096
|
||||
temperature = 0.3
|
||||
top_p = 0.9
|
||||
top_k = 40
|
||||
stop_sequences = ["\n\n", "</stop>"]
|
||||
reasoning = "medium"
|
||||
|
||||
[[scope.allow]]
|
||||
@@ -341,6 +353,9 @@ permission = "write"
|
||||
assert_eq!(manifest.worker.instruction, "$user/reviewer");
|
||||
assert_eq!(manifest.worker.max_tokens, Some(4096));
|
||||
assert_eq!(manifest.worker.temperature, Some(0.3));
|
||||
assert_eq!(manifest.worker.top_p, Some(0.9));
|
||||
assert_eq!(manifest.worker.top_k, Some(40));
|
||||
assert_eq!(manifest.worker.stop_sequences, vec!["\n\n", "</stop>"]);
|
||||
assert_eq!(
|
||||
manifest.worker.reasoning,
|
||||
Some(ReasoningControl::Effort(ReasoningEffort::Medium))
|
||||
|
||||
+40
-6
@@ -1384,6 +1384,15 @@ impl<St: Store> Pod<Box<dyn LlmClient>, St> {
|
||||
/// minijinja template that is parsed by `Pod::from_manifest` and
|
||||
/// rendered once at first turn in `ensure_system_prompt_materialized`.
|
||||
pub fn apply_worker_manifest<C: LlmClient>(worker: &mut Worker<C>, wm: &WorkerManifest) {
|
||||
worker.set_request_config(request_config_from_worker_manifest(wm));
|
||||
worker.set_max_turns(wm.max_turns.map(|n| n.get()));
|
||||
worker.set_tool_output_limits(Some(ToolOutputLimits {
|
||||
default_max_bytes: wm.tool_output.default_max_bytes,
|
||||
per_tool: wm.tool_output.per_tool.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
fn request_config_from_worker_manifest(wm: &WorkerManifest) -> RequestConfig {
|
||||
let mut config = RequestConfig::new();
|
||||
if let Some(max_tokens) = wm.max_tokens {
|
||||
config.max_tokens = Some(max_tokens);
|
||||
@@ -1391,13 +1400,15 @@ pub fn apply_worker_manifest<C: LlmClient>(worker: &mut Worker<C>, wm: &WorkerMa
|
||||
if let Some(temperature) = wm.temperature {
|
||||
config.temperature = Some(temperature);
|
||||
}
|
||||
if let Some(top_p) = wm.top_p {
|
||||
config.top_p = Some(top_p);
|
||||
}
|
||||
if let Some(top_k) = wm.top_k {
|
||||
config.top_k = Some(top_k);
|
||||
}
|
||||
config.stop_sequences = wm.stop_sequences.clone();
|
||||
config.reasoning = wm.reasoning.clone();
|
||||
worker.set_request_config(config);
|
||||
worker.set_max_turns(wm.max_turns.map(|n| n.get()));
|
||||
worker.set_tool_output_limits(Some(ToolOutputLimits {
|
||||
default_max_bytes: wm.tool_output.default_max_bytes,
|
||||
per_tool: wm.tool_output.per_tool.clone(),
|
||||
}));
|
||||
config
|
||||
}
|
||||
|
||||
/// Result of a Pod run.
|
||||
@@ -1616,6 +1627,29 @@ mod build_summary_prompt_tests {
|
||||
assert!(!prompt.contains("deliberation"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn worker_manifest_generation_settings_become_request_config() {
|
||||
let manifest = WorkerManifest {
|
||||
instruction: "unused".into(),
|
||||
max_tokens: Some(1024),
|
||||
max_turns: None,
|
||||
temperature: Some(0.2),
|
||||
top_p: Some(0.9),
|
||||
top_k: Some(40),
|
||||
stop_sequences: vec!["\n\n".into(), "</stop>".into()],
|
||||
reasoning: None,
|
||||
tool_output: manifest::ToolOutputLimits::default(),
|
||||
};
|
||||
|
||||
let config = request_config_from_worker_manifest(&manifest);
|
||||
|
||||
assert_eq!(config.max_tokens, Some(1024));
|
||||
assert_eq!(config.temperature, Some(0.2));
|
||||
assert_eq!(config.top_p, Some(0.9));
|
||||
assert_eq!(config.top_k, Some(40));
|
||||
assert_eq!(config.stop_sequences, vec!["\n\n", "</stop>"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_user_and_assistant_messages() {
|
||||
let items = vec![
|
||||
|
||||
Reference in New Issue
Block a user