mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-17 10:06:18 +00:00
* feat(agents): support Unicode display names for custom agents * fix(agents): preserve and validate Unicode display names * fix(agents): tolerate invalid stored labels and reject invisible names * fix(agents): identify agent in invalid display name warning * style(frontend): format agent display name fallback --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
93 lines
3.9 KiB
Python
93 lines
3.9 KiB
Python
import logging
|
|
|
|
from langchain_core.messages import ToolMessage
|
|
from langchain_core.tools import tool
|
|
from langgraph.types import Command
|
|
|
|
from deerflow.config.agents_config import SOUL_FILENAME, validate_agent_name
|
|
from deerflow.config.paths import get_paths
|
|
from deerflow.persistence.agents import get_agent_store
|
|
from deerflow.runtime.user_context import resolve_runtime_user_id
|
|
from deerflow.tools.types import Runtime
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@tool(parse_docstring=True)
|
|
def setup_agent(
|
|
soul: str,
|
|
description: str,
|
|
runtime: Runtime,
|
|
skills: list[str] | None = None,
|
|
) -> Command:
|
|
"""Setup the custom DeerFlow agent.
|
|
|
|
Args:
|
|
soul: Full SOUL.md content defining the agent's personality and behavior.
|
|
description: One-line description of what the agent does.
|
|
skills: Optional list of skill names this agent should use. None means use all enabled skills, empty list means no skills.
|
|
"""
|
|
|
|
# Reject empty / whitespace-only soul before touching the filesystem.
|
|
# Without this guard the tool would happily persist an empty SOUL.md and
|
|
# still report success, which caused the frontend to enter the "agent
|
|
# created" state for an unusable agent (issue #3549). Failing loud lets
|
|
# the model retry instead of silently producing a broken artifact and,
|
|
# together with the upstream agent_name fix, prevents the global default
|
|
# SOUL.md from being overwritten with empty content.
|
|
if not soul or not soul.strip():
|
|
return Command(
|
|
update={
|
|
"messages": [
|
|
ToolMessage(
|
|
content="Error: soul content is empty; refusing to create agent with an empty SOUL.md",
|
|
tool_call_id=runtime.tool_call_id,
|
|
)
|
|
]
|
|
}
|
|
)
|
|
|
|
agent_name: str | None = runtime.context.get("agent_name") if runtime.context else None
|
|
|
|
try:
|
|
agent_name = validate_agent_name(agent_name)
|
|
if agent_name:
|
|
# Custom agents are persisted under the current user's bucket (via
|
|
# the configured store — file or db) so different users, and
|
|
# different nodes, resolve the same agent. setup is idempotent, so
|
|
# this is an upsert.
|
|
user_id = resolve_runtime_user_id(runtime)
|
|
config_data: dict = {"name": agent_name}
|
|
store = get_agent_store()
|
|
try:
|
|
existing = store.get(agent_name, user_id=user_id)
|
|
except FileNotFoundError:
|
|
pass # First bootstrap has no user-authored label to preserve.
|
|
else:
|
|
if existing.display_name is not None:
|
|
config_data["display_name"] = existing.display_name
|
|
if description:
|
|
config_data["description"] = description
|
|
if skills is not None:
|
|
config_data["skills"] = skills
|
|
store.update(agent_name, config_data, soul, user_id=user_id)
|
|
else:
|
|
# Default agent (no agent_name): SOUL.md lives at the global base
|
|
# dir. It is not a custom-agent record, so it stays file-based
|
|
# regardless of the agent-storage backend.
|
|
paths = get_paths()
|
|
paths.base_dir.mkdir(parents=True, exist_ok=True)
|
|
(paths.base_dir / SOUL_FILENAME).write_text(soul, encoding="utf-8")
|
|
|
|
logger.info(f"[agent_creator] Created agent '{agent_name}'")
|
|
return Command(
|
|
update={
|
|
"created_agent_name": agent_name,
|
|
"messages": [ToolMessage(content=f"Agent '{agent_name}' created successfully!", tool_call_id=runtime.tool_call_id)],
|
|
}
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"[agent_creator] Failed to create agent '{agent_name}': {e}", exc_info=True)
|
|
return Command(update={"messages": [ToolMessage(content=f"Error: {e}", tool_call_id=runtime.tool_call_id)]})
|