生成設定の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))
|
||||
|
||||
Reference in New Issue
Block a user