fix(tui): auto-generate thinking-effort presets from actor schema in create_default_persona_for_actor #9451

Closed
HAL9000 wants to merge 3 commits from fix/tui-auto-generate-presets-actor-schema into master
3 changed files with 289 additions and 1 deletions
+130
View File
@@ -488,3 +488,133 @@ def step_persona_bar_reflects_actor(context: object) -> None:
assert "anthropic/claude-4-sonnet" in bar._text, (
f"Expected actor in persona bar, got: {bar._text}"
)
# -----------------------------------------------------------------------
# Auto-generated presets from actor schema
# -----------------------------------------------------------------------
@given('an actor service with a mock actor "{actor}" with thinking_effort argument')
def step_actor_service_with_thinking_effort(context: object, actor: str) -> None:
from cleveragents.domain.models.core import Actor
mock_actor = MagicMock(spec=Actor)
mock_actor.name = actor
mock_actor.config_blob = {
"options": {
"thinking_effort": {
"type": "string",
"enum": ["low", "high", "max"],
}
}
}
mock_service = MagicMock()
mock_service.get_actor.return_value = mock_actor
context._actor_service = mock_service
@given('an actor service with a mock actor "{actor}" with temperature argument')
def step_actor_service_with_temperature(context: object, actor: str) -> None:
from cleveragents.domain.models.core import Actor
mock_actor = MagicMock(spec=Actor)
mock_actor.name = actor
mock_actor.config_blob = {
"options": {
"temperature": {
"type": "number",
"minimum": 0.0,
"maximum": 2.0,
}
}
}
mock_service = MagicMock()
mock_service.get_actor.return_value = mock_actor
context._actor_service = mock_service
@given(
'an actor service with a mock actor "{actor}" with thinking_effort and temperature arguments'
)
def step_actor_service_with_both(context: object, actor: str) -> None:
from cleveragents.domain.models.core import Actor
mock_actor = MagicMock(spec=Actor)
mock_actor.name = actor
mock_actor.config_blob = {
"options": {
"thinking_effort": {
"type": "string",
"enum": ["low", "high", "max"],
},
"temperature": {
"type": "number",
"minimum": 0.0,
"maximum": 2.0,
},
}
}
mock_service = MagicMock()
mock_service.get_actor.return_value = mock_actor
context._actor_service = mock_service
@given('an actor service with a mock actor "{actor}" with no recognized arguments')
def step_actor_service_with_no_args(context: object, actor: str) -> None:
from cleveragents.domain.models.core import Actor
mock_actor = MagicMock(spec=Actor)
mock_actor.name = actor
mock_actor.config_blob = {
"options": {
"custom_param": {
"type": "string",
}
}
}
mock_service = MagicMock()
mock_service.get_actor.return_value = mock_actor
context._actor_service = mock_service
@given("an actor service that raises an exception on get_actor")
def step_actor_service_raises_exception(context: object) -> None:
mock_service = MagicMock()
mock_service.get_actor.side_effect = RuntimeError("Actor not found")
context._actor_service = mock_service
@when('I call create_default_persona_for_actor with actor "{actor}" and actor_service')
def step_create_persona_with_service(context: object, actor: str) -> None:
from cleveragents.tui.first_run import create_default_persona_for_actor
context._created_persona = create_default_persona_for_actor(
context._registry, actor, actor_service=context._actor_service
)
@then('the default persona should have preset "{preset_name}"')
def step_persona_has_preset(context: object, preset_name: str) -> None:
persona = context._registry.get("default")
assert persona is not None, "Expected persona 'default' in registry"
preset_names = [p.name for p in persona.argument_presets]
assert preset_name in preset_names, (
f"Expected preset '{preset_name}' in persona presets, got: {preset_names}"
)
@then("the default persona should have exactly {count:d} preset")
@then("the default persona should have exactly {count:d} presets")
def step_persona_has_exact_presets(context: object, count: int) -> None:
persona = context._registry.get("default")
assert persona is not None, "Expected persona 'default' in registry"
actual_count = len(persona.argument_presets)
assert actual_count == count, (
f"Expected {count} preset(s), got {actual_count}: "
f"{[p.name for p in persona.argument_presets]}"
)
+57
View File
@@ -194,3 +194,60 @@ Feature: TUI first-run experience with actor selection overlay
And I call _complete_first_run with actor "anthropic/claude-4-sonnet"
Then the registry should contain a persona named "default"
And the persona bar should reflect the new actor
# -----------------------------------------------------------------------
# Auto-generated presets from actor schema
# -----------------------------------------------------------------------
Scenario: create_default_persona_for_actor auto-generates presets for actor with thinking_effort
Given an empty persona registry
And an actor service with a mock actor "anthropic/claude-4-sonnet" with thinking_effort argument
When I call create_default_persona_for_actor with actor "anthropic/claude-4-sonnet" and actor_service
Then the registry should contain a persona named "default"
And the default persona should have preset "default"
And the default persona should have preset "think: high"
And the default persona should have preset "think: max"
And the default persona should have exactly 3 presets
Scenario: create_default_persona_for_actor auto-generates presets for actor with temperature
Given an empty persona registry
And an actor service with a mock actor "openai/gpt-4o" with temperature argument
When I call create_default_persona_for_actor with actor "openai/gpt-4o" and actor_service
Then the registry should contain a persona named "default"
And the default persona should have preset "default"
And the default persona should have preset "precise"
And the default persona should have preset "creative"
Scenario: create_default_persona_for_actor auto-generates presets for actor with both thinking_effort and temperature
Given an empty persona registry
And an actor service with a mock actor "anthropic/claude-4-sonnet" with thinking_effort and temperature arguments
When I call create_default_persona_for_actor with actor "anthropic/claude-4-sonnet" and actor_service
Then the registry should contain a persona named "default"
And the default persona should have preset "default"
And the default persona should have preset "think: high"
And the default persona should have preset "think: max"
And the default persona should have preset "precise"
And the default persona should have preset "creative"
Scenario: create_default_persona_for_actor creates only default preset for actor with no recognized arguments
Given an empty persona registry
And an actor service with a mock actor "local/mock" with no recognized arguments
When I call create_default_persona_for_actor with actor "local/mock" and actor_service
Then the registry should contain a persona named "default"
And the default persona should have preset "default"
And the default persona should have exactly 1 preset
Scenario: create_default_persona_for_actor without actor_service creates only default preset
Given an empty persona registry
When I call create_default_persona_for_actor with actor "anthropic/claude-4-sonnet"
Then the registry should contain a persona named "default"
And the default persona should have preset "default"
And the default persona should have exactly 1 preset
Scenario: create_default_persona_for_actor falls back to default preset when actor lookup fails
Given an empty persona registry
And an actor service that raises an exception on get_actor
When I call create_default_persona_for_actor with actor "anthropic/claude-4-sonnet" and actor_service
Then the registry should contain a persona named "default"
And the default persona should have preset "default"
And the default persona should have exactly 1 preset
+102 -1
View File
@@ -2,8 +2,13 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from cleveragents.tui.persona.registry import PersonaRegistry
from cleveragents.tui.persona.schema import Persona
from cleveragents.tui.persona.schema import Persona, PersonaPreset
if TYPE_CHECKING:
from cleveragents.application.services.actor_service import ActorService
def is_first_run(registry: PersonaRegistry) -> bool:
@@ -27,9 +32,90 @@ def is_first_run(registry: PersonaRegistry) -> bool:
return len(registry.list_personas()) == 0
def _auto_generate_presets(
actor_service: ActorService,
actor: str,
) -> list[PersonaPreset]:
"""Auto-generate presets from actor schema.
Inspects the actor's argument schema to detect thinking_effort and
temperature arguments, then generates appropriate presets.
Parameters
----------
actor_service:
The :class:`~cleveragents.application.services.actor_service.ActorService`
instance to use for actor lookup.
actor:
Fully-qualified actor reference (e.g. ``"anthropic/claude-4-sonnet"``).
Returns
-------
list[PersonaPreset]
List of auto-generated presets. Always includes "default" preset.
May include "think: high", "think: max", "precise", and "creative"
presets depending on actor schema.
"""
presets: list[PersonaPreset] = [
PersonaPreset(name="default", display="default", overrides={})
]
try:
actor_obj = actor_service.get_actor(actor)
except Exception:
# If actor lookup fails, return only default preset
return presets
# Inspect actor's config_blob for options schema
config_blob = actor_obj.config_blob or {}
options = config_blob.get("options", {})
if not isinstance(options, dict):
return presets
# Check for thinking_effort argument
has_thinking_effort = "thinking_effort" in options
if has_thinking_effort:
presets.append(
PersonaPreset(
name="think: high",
display="think: high",
overrides={"thinking_effort": "high"},
)
Review

BLOCKING — Spec deviation: Wrong temperature values

The precise and creative preset temperature values do not match ADR-045 §Auto-Generated Presets.

ADR-045 explicitly specifies:

"precise" → { temperature: 0.1 }
"creative" → { temperature: 0.9 }

This code uses 0.0 and 1.0 instead. Please correct to:

PersonaPreset(
    name="precise",
    display="precise",
    overrides={"temperature": 0.1},  # per ADR-045
)
PersonaPreset(
    name="creative",
    display="creative",
    overrides={"temperature": 0.9},  # per ADR-045
)

Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

**BLOCKING — Spec deviation: Wrong temperature values** The `precise` and `creative` preset temperature values do not match ADR-045 §Auto-Generated Presets. ADR-045 explicitly specifies: ``` "precise" → { temperature: 0.1 } "creative" → { temperature: 0.9 } ``` This code uses `0.0` and `1.0` instead. Please correct to: ```python PersonaPreset( name="precise", display="precise", overrides={"temperature": 0.1}, # per ADR-045 ) PersonaPreset( name="creative", display="creative", overrides={"temperature": 0.9}, # per ADR-045 ) ``` --- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
)
presets.append(
PersonaPreset(
name="think: max",
display="think: max",
overrides={"thinking_effort": "max"},
)
)
# Check for temperature argument
has_temperature = "temperature" in options
if has_temperature:
presets.append(
PersonaPreset(
name="precise",
display="precise",
overrides={"temperature": 0.0},
)
)
presets.append(
PersonaPreset(
name="creative",
display="creative",
overrides={"temperature": 1.0},
)
)
return presets
def create_default_persona_for_actor(
registry: PersonaRegistry,
actor: str,
actor_service: ActorService | None = None,
) -> Persona:
"""Create and persist a ``"default"`` persona for the given actor.
@@ -37,6 +123,9 @@ def create_default_persona_for_actor(
overlay. The persona is saved to the registry and set as the last
active persona so that subsequent launches restore it.
If an actor_service is provided, auto-generates presets based on the
actor's schema (thinking_effort and temperature arguments).
Parameters
----------
registry:
@@ -44,16 +133,28 @@ def create_default_persona_for_actor(
instance to write to.
actor:
Fully-qualified actor reference (e.g. ``"anthropic/claude-4-sonnet"``).
actor_service:
Optional :class:`~cleveragents.application.services.actor_service.ActorService`
instance for auto-generating presets from actor schema.
Returns
-------
Persona
The newly created and persisted default persona.
"""
# Auto-generate presets if actor_service is provided
presets: list[PersonaPreset] = []
if actor_service is not None:
presets = _auto_generate_presets(actor_service, actor)
else:
# Default to just the default preset
presets = [PersonaPreset(name="default", display="default", overrides={})]
persona = Persona(
name="default",
actor=actor,
description="Default persona",
argument_presets=presets,
)
registry.save(persona)
registry.set_last_persona(persona.name)