fix(tui): auto-generate thinking-effort presets from actor schema in create_default_persona_for_actor #9451
@@ -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]}"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"},
|
||||
)
|
||||
|
|
||||
)
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user
BLOCKING — Spec deviation: Wrong temperature values
The
preciseandcreativepreset temperature values do not match ADR-045 §Auto-Generated Presets.ADR-045 explicitly specifies:
This code uses
0.0and1.0instead. Please correct to:Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker