mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-06 06:52:20 +02:00
keep compact agent core tools available
This commit is contained in:
+4
-39
@@ -3390,8 +3390,8 @@ def setup_chat_routes(
|
||||
if _clean_v3_preview:
|
||||
from dataclasses import replace
|
||||
from src.clean_agent_preview import (
|
||||
MODE, NATIVE_WORKSPACE_TOOLS, PREVIEW_TOOLS, canonical,
|
||||
scope_preview_contract, tool_family,
|
||||
INTERACTIVE_CORE_TOOLS, MODE, NATIVE_WORKSPACE_TOOLS, PREVIEW_TOOLS, canonical,
|
||||
scope_preview_contract,
|
||||
)
|
||||
from src.turn_contract import resolve_full_inventory_contract
|
||||
_warm_canonical = {canonical(name) for name in _warm_tools}
|
||||
@@ -3414,41 +3414,6 @@ def setup_chat_routes(
|
||||
if canonical(s['function']['name']) != 'bash'
|
||||
or canonical(s['function']['name']) in _warm_canonical
|
||||
]
|
||||
# Browser automation is a deliberate capability, not a side
|
||||
# effect of merely enabling ordinary Web search. Once a clean
|
||||
# turn successfully uses it, typed execution evidence keeps it
|
||||
# warm for the conversation so referential follow-ups can
|
||||
# inspect the same page.
|
||||
if (
|
||||
_explicit_browser_intent
|
||||
and not set(_selected_tools or ()).intersection(
|
||||
{'web_search', 'web_fetch'}
|
||||
)
|
||||
):
|
||||
# Navigation and interaction are browser operations. Do
|
||||
# not make the model choose between a site browser and the
|
||||
# search/fetch APIs after the request has already made
|
||||
# that distinction. An explicitly named brokered search or
|
||||
# fetch tool is stronger than the generic URL/open signal;
|
||||
# preserving it also prevents the browser-only filter from
|
||||
# intersecting an exact web_fetch contract down to zero
|
||||
# tools. A later turn can explicitly ask for Web search as
|
||||
# a fallback.
|
||||
_preview_schemas = [
|
||||
s for s in _preview_schemas
|
||||
if tool_family(s['function']['name']) != 'search_browser'
|
||||
or canonical(s['function']['name']) in (
|
||||
{'private_browser'} | NATIVE_WORKSPACE_TOOLS
|
||||
)
|
||||
or canonical(s['function']['name']) in _warm_canonical
|
||||
]
|
||||
elif not _clean_v3_private_browser_warm and not (
|
||||
_native_workspace_contract and _local_browser_render_intent
|
||||
) and 'private_browser' not in _warm_canonical:
|
||||
_preview_schemas = [
|
||||
s for s in _preview_schemas
|
||||
if canonical(s['function']['name']) != 'private_browser'
|
||||
]
|
||||
_turn_contract = scope_preview_contract(
|
||||
replace(resolve_full_inventory_contract(
|
||||
schemas=_preview_schemas,
|
||||
@@ -3467,9 +3432,9 @@ def setup_chat_routes(
|
||||
# OCR operation. Exact operations therefore stay exact;
|
||||
# ordinary native turns retain warm and workspace tools.
|
||||
extra_tools=(
|
||||
frozenset()
|
||||
INTERACTIVE_CORE_TOOLS
|
||||
if _exact_selected_native_chain
|
||||
else _warm_tools | (
|
||||
else INTERACTIVE_CORE_TOOLS | _warm_tools | (
|
||||
NATIVE_WORKSPACE_TOOLS | (
|
||||
{"private_browser"} if _local_browser_render_intent else frozenset()
|
||||
)
|
||||
|
||||
@@ -102,6 +102,13 @@ PREVIEW_TOOLS = (
|
||||
READ_TOOLS | SAFE_WRITE_TOOLS | EXPLICIT_EXECUTE_TOOLS | SAFE_UI_TOOLS
|
||||
| BROKERED_JOB_TOOLS | CONTRACT_REQUIRED_TOOLS
|
||||
)
|
||||
# Keep a small recovery-capable surface on every interactive compact agent
|
||||
# turn. Routing still adds domain tools, while policy and action guards remain
|
||||
# authoritative for execution. Python is deliberately excluded here because
|
||||
# the WebUI does not own a confined workspace.
|
||||
INTERACTIVE_CORE_TOOLS = frozenset({
|
||||
'web_search', 'web_fetch', 'private_browser', 'bash', 'ask_user',
|
||||
})
|
||||
# The interactive compact-v5 surface above stays unchanged. These tools are
|
||||
# added only for a server-validated ``odysseus-native`` request with an active,
|
||||
# confined workspace. This lets the model-specific clean runtime serve native
|
||||
|
||||
@@ -3970,6 +3970,17 @@ def requested_capabilities(message: str, history: Iterable = (), *, active_docum
|
||||
# Keep the common ``nees`` typo because a missed route leaves the model
|
||||
# with no way to answer and encourages it to ask unnecessary questions.
|
||||
return frozenset({"search_browser"})
|
||||
if (
|
||||
re.search(r"\b(?:online|on\s+the\s+(?:web|internet))\b", text, re.I)
|
||||
and re.search(
|
||||
r"\b(?:find|look|search|check|locate|get|download|available|manual|guide|docs?)\b",
|
||||
text,
|
||||
re.I,
|
||||
)
|
||||
):
|
||||
# Explicitly asking Odysseus to look online is sufficient web intent,
|
||||
# including referential follow-ups such as "Can you look online?".
|
||||
return frozenset({"search_browser"})
|
||||
if (
|
||||
len(concrete_urls) >= 2
|
||||
and re.search(r"\b(?:open|fetch|read|retrieve|check|use)\b", text, re.I)
|
||||
|
||||
@@ -3093,6 +3093,28 @@ def test_preview_contract_keeps_only_routed_family_from_trained_inventory():
|
||||
assert scoped.active_capabilities == {'notes'}
|
||||
|
||||
|
||||
def test_preview_contract_adds_safe_interactive_core_beside_routed_tools():
|
||||
core = {'web_search', 'web_fetch', 'private_browser', 'bash', 'ask_user'}
|
||||
schemas = [
|
||||
schema for schema in FUNCTION_TOOL_SCHEMAS
|
||||
if schema['function']['name'] in core | {'manage_notes'}
|
||||
]
|
||||
preview = resolve_full_inventory_contract(schemas=schemas, policy=ToolPolicy())
|
||||
routed = SimpleNamespace(
|
||||
unavailable=frozenset(), offered=frozenset({'manage_notes'}),
|
||||
required=frozenset({'manage_notes'}), capabilities=frozenset({'notes'}),
|
||||
required_read_operation=None,
|
||||
)
|
||||
|
||||
scoped = scope_preview_contract(
|
||||
preview, routed, {'notes'}, extra_tools=core,
|
||||
)
|
||||
|
||||
assert scoped.offered == core | {'manage_notes'}
|
||||
assert scoped.required == {'manage_notes'}
|
||||
assert {schema['function']['name'] for schema in scoped.schemas()} == core | {'manage_notes'}
|
||||
|
||||
|
||||
def test_preview_contract_keeps_available_family_when_an_independent_family_is_unavailable():
|
||||
schemas = [s for s in FUNCTION_TOOL_SCHEMAS if s['function']['name'] in {
|
||||
'write_file', 'web_search',
|
||||
|
||||
@@ -34,6 +34,15 @@ def test_latest_topic_info_and_common_news_typo_route_to_web_search():
|
||||
assert requested_capabilities(prompt) == frozenset({"search_browser"})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("prompt", [
|
||||
"I'm using a WIKING fireplace; can I find an English manual online?",
|
||||
"Can you look online",
|
||||
"Could you search online for the operator's manual?",
|
||||
])
|
||||
def test_explicit_online_lookup_requests_route_to_web_search(prompt):
|
||||
assert requested_capabilities(prompt) == frozenset({"search_browser"})
|
||||
|
||||
|
||||
def test_successfully_used_tool_stays_offered_when_next_turn_routes_elsewhere():
|
||||
contract = resolve(
|
||||
{"notes"},
|
||||
|
||||
Reference in New Issue
Block a user