[Haik]: ckpt, dashboards subapp now done fully. Also made the resolve_sdk_env which is used now both for normal agents and quick llm calls, also modded the agents subapp to properly make use of this helper, also modded the run_browser_agent to inheret this from its parent agent

This commit is contained in:
haikdc
2026-04-05 11:09:15 -07:00
parent 84a87b715e
commit c71e25d37b
7 changed files with 76 additions and 60 deletions
@@ -3,8 +3,8 @@ from uuid import uuid4
from pydantic import BaseModel, Field
from typeguard import typechecked
from backend.apps.agents.utils.comms_utils.classes.FutureBridge import FutureBridge
from backend.apps.agents.utils.comms_utils.classes.FrontendBroadcaster import FrontendBroadcaster
from backend.apps.agents.COMMS_MANAGER.classes.FutureBridge import FutureBridge
from backend.apps.agents.COMMS_MANAGER.classes.FrontendBroadcaster import FrontendBroadcaster
from backend.core.events.events import AnyEvent, ApprovalRequestEvent, EventCallback
+11
View File
@@ -28,6 +28,9 @@ from backend.core.tools.make_builtin_toolkit.make_builtin_toolkit import make_bu
from backend.apps.agents.agent_utils.create_sdk_hooks import create_sdk_hooks
from backend.apps.agents.agent_utils.build_search_text import build_search_text
from backend.apps.agents.COMMS_MANAGER.COMMS_MANAGER import COMMS_MANAGER
from backend.apps.settings.settings import load_settings
from backend.core.llm.resolve_sdk_env import resolve_sdk_env
from backend.ports import NINE_ROUTER_PORT
from claude_agent_sdk import ClaudeAgentOptions
from claude_agent_sdk.types import HookMatcher, McpServerConfig
from backend.core.tools.shared_structs.Toolkit import Toolkit
@@ -118,7 +121,15 @@ async def launch(body: LaunchBody) -> dict:
can_use_tool, pre_tool_hook, post_tool_hook = create_sdk_hooks(agent)
settings = load_settings()
env = resolve_sdk_env(
api_key=settings.anthropic_api_key,
nine_router_port=NINE_ROUTER_PORT if not settings.anthropic_api_key else None,
)
agent.config = ClaudeAgentOptions(
env=env,
model=body.model,
system_prompt=system_prompt,
max_turns=body.max_turns,
mcp_servers=mcp_servers if mcp_servers else None,
+8 -33
View File
@@ -1,13 +1,11 @@
import json
import os
import logging
from contextlib import asynccontextmanager
from datetime import datetime
from uuid import uuid4
from pydantic import BaseModel
from backend.config.Apps import SubApp
# from backend.apps.common.json_store import JsonStore
from backend.core.db.PydanticStore import PydanticStore
from backend.apps.dashboards.Dashboard import (
Dashboard,
@@ -15,16 +13,9 @@ from backend.apps.dashboards.Dashboard import (
)
from backend.apps.agents.agents import list_sessions, delete_session
from backend.apps.settings.settings import load_settings
from backend.core.llm.quick_llm_call import quick_llm_call
from backend.apps.dashboards.generate_dashboard_name import generate_dashboard_name
from backend.ports import NINE_ROUTER_PORT
from typing import Optional
logger = logging.getLogger(__name__)
# from backend.config.paths import DASHBOARDS_DIR as DATA_DIR, SESSIONS_DIR
from backend.config.paths import DB_ROOT
@@ -75,7 +66,6 @@ async def create_dashboard(body: DashboardCreate):
# TODO: Maybe parse the output of list_sessions into actual Agent objects?
@dashboards.router.post("/{dashboard_id}/generate-name")
async def generate_name(dashboard_id: str):
dashboard = DASHBOARD_STORE.load(dashboard_id)
if not dashboard.auto_named and dashboard.name != "Untitled Dashboard":
@@ -94,33 +84,18 @@ async def generate_name(dashboard_id: str):
if not prompts:
return {"name": dashboard.name, "auto_named": dashboard.auto_named}
settings = load_settings()
fallback = prompts[0][:40]
try:
if len(prompts) == 1:
system = "Generate a concise 2-5 word workspace name for a project based on this task. Return only the name, nothing else."
user_content = prompts[0]
else:
system = "Generate a concise 2-5 word workspace name that captures the overall theme of these tasks. Return only the name, nothing else."
user_content = "\n".join(f"- {p}" for p in prompts)
settings = load_settings()
llm_kwargs = {}
if settings.anthropic_api_key:
llm_kwargs["api_key"] = settings.anthropic_api_key
else:
llm_kwargs["nine_router_port"] = NINE_ROUTER_PORT
generated = await quick_llm_call(
system, user_content,
model="claude-haiku-4-5-20251001",
max_tokens=30,
**llm_kwargs,
generated = await generate_dashboard_name(
prompts=prompts,
api_key=settings.anthropic_api_key,
nine_router_port=NINE_ROUTER_PORT,
)
generated = generated.strip('"\'')
if generated:
fallback = generated
except Exception as e:
logger.warning(f"Dashboard name generation failed, using fallback: {e}")
print(f"[dashboards.generate_name] ERROR: Dashboard name generation failed, using fallback: {e}")
dashboard.name = fallback
dashboard.auto_named = True
@@ -166,7 +141,7 @@ async def delete_dashboard(dashboard_id: str):
try:
await delete_session(session["session_id"])
except Exception:
logger.warning(f"Failed to delete session {session.get('session_id')} during dashboard deletion")
print(f"[dashboards.delete_dashboard] ERROR: Failed to delete session {session.get('session_id')} during dashboard deletion")
DASHBOARD_STORE.delete(dashboard_id)
return {"ok": True}
@@ -0,0 +1,30 @@
from typing import List, Optional
from backend.core.llm.quick_llm_call import quick_llm_call
from typeguard import typechecked
SINGLE_AGENT_SYSTEM_PROMPT = "Generate a concise 2-5 word workspace name for a project based on this task. Return only the name with no markdown formatting, nothing else."
MULTI_AGENT_SYSTEM_PROMPT = "Generate a concise 2-5 word workspace name that captures the overall theme of these tasks. Return only the name with no markdown formatting, nothing else."
@typechecked
async def generate_dashboard_name(
prompts: List[str],
api_key: Optional[str] = None,
nine_router_port: Optional[int] = None,
) -> str:
"""Given user prompts from a dashboard's sessions, generate a short name via LLM."""
if len(prompts) == 1:
system_prompt = SINGLE_AGENT_SYSTEM_PROMPT
user_prompt = prompts[0]
else:
system_prompt = MULTI_AGENT_SYSTEM_PROMPT
user_prompt = "\n".join(f"- {p}" for p in prompts)
result = await quick_llm_call(
api_key=api_key,
nine_router_port=nine_router_port,
system_prompt=system_prompt,
user_prompt=user_prompt,
model="claude-haiku-4-5-20251001",
max_tokens=30,
)
return result.strip("\"'")
+11 -6
View File
@@ -1,20 +1,25 @@
from backend.core.llm.get_llm_client import get_llm_client
from backend.core.llm.resolve_sdk_env import resolve_sdk_env, SDK_ENV_DICT
from typing import Optional
from anthropic.types import Message as AnthropicMessage
import anthropic
async def quick_llm_call(
system: str,
user_content: str,
system_prompt: str,
user_prompt: str,
model: str,
max_tokens: int,
api_key: Optional[str] = None,
nine_router_port: Optional[int] = None,
) -> str:
client = get_llm_client(api_key=api_key, nine_router_port=nine_router_port)
env_dict: SDK_ENV_DICT = resolve_sdk_env(api_key=api_key, nine_router_port=nine_router_port)
client = anthropic.AsyncAnthropic(
api_key=env_dict["ANTHROPIC_API_KEY"],
base_url=env_dict.get("ANTHROPIC_BASE_URL"),
)
resp: AnthropicMessage = await client.messages.create(
model=model,
max_tokens=max_tokens,
system=system,
messages=[{"role": "user", "content": user_content}],
system=system_prompt,
messages=[{"role": "user", "content": user_prompt}],
)
return resp.content[0].text.strip()
@@ -1,7 +1,6 @@
import anthropic
import httpx
from typeguard import typechecked
from typing import Optional
from typing import Optional, Literal, Dict
from backend.core.generic_utils.assert_exactly_one_optional import assert_exactly_one_optional
@typechecked
@@ -12,28 +11,23 @@ def p_check_9router(nine_router_port: int) -> bool:
except Exception:
return False
SDK_ENV_DICT = Dict[Literal["ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL"], str]
@typechecked
def get_llm_client(
def resolve_sdk_env(
api_key: Optional[str] = None,
nine_router_port: Optional[int] = None,
) -> anthropic.AsyncAnthropic:
"""Build an AsyncAnthropic client.
Priority: API key 9Router subscription
"""
) -> SDK_ENV_DICT:
"""Resolve credentials into an env dict for ClaudeAgentOptions."""
assert_exactly_one_optional([api_key, nine_router_port])
if api_key is not None:
return anthropic.AsyncAnthropic(api_key=api_key)
elif nine_router_port is not None:
if api_key:
return {"ANTHROPIC_API_KEY": api_key}
elif nine_router_port:
if not p_check_9router(nine_router_port):
raise ValueError("9Router is not running. Set an API key or connect a subscription.")
return anthropic.AsyncAnthropic(
api_key="9router",
base_url=f"http://localhost:{nine_router_port}",
)
return {
"ANTHROPIC_API_KEY": "9router",
"ANTHROPIC_BASE_URL": f"http://localhost:{nine_router_port}",
}
else:
raise ValueError("No AI provider configured. Set an API key or a 9Router port.")
raise ValueError("No AI provider configured. Set an API key or a 9router port")
@@ -34,6 +34,7 @@ async def run_browser_agent(
tool_names.append(tool.to_sdk_args())
browser_config = ClaudeAgentOptions(
env=parent.config.env,
model=parent.model,
system_prompt=BROWSER_AGENT_SYSTEM_PROMPT,
tools=tool_names,