Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmark/locomo/openclaw/import_to_ov.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ async def viking_ingest(
task_id = result.get("task_id")
if task_id:
# 轮询任务状态直到完成
max_attempts = 1200 # 最多等待20分钟
max_attempts = 3600 # 最多等待1小时
for attempt in range(max_attempts):
task = await client.get_task(task_id)
status = task.get("status") if task else "unknown"
Expand Down
62 changes: 33 additions & 29 deletions bot/vikingbot/agent/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def _ensure_templates_once(self):
self._templates_ensured = True

async def build_system_prompt(
self, session_key: SessionKey, current_message: str, history: list[dict[str, Any]]
self, session_key: SessionKey, current_message: str, history: list[dict[str, Any]], ov_tools_enable: bool = True
) -> str:
"""
Build the system prompt from bootstrap files, memory, and skills.
Expand Down Expand Up @@ -123,22 +123,23 @@ async def build_system_prompt(

{skills_summary}""")

# Viking user profile
start = _time.time()
profile = await self.memory.get_viking_user_profile(
workspace_id=workspace_id, user_id=self._sender_id
)
cost = round(_time.time() - start, 2)
logger.info(
f"[READ_USER_PROFILE]: cost {cost}s, profile={profile[:50] if profile else 'None'}"
)
if profile:
parts.append(f"## Current user's information\n{profile}")
# Viking user profile (only if ov tools are enabled)
if ov_tools_enable:
start = _time.time()
profile = await self.memory.get_viking_user_profile(
workspace_id=workspace_id, user_id=self._sender_id
)
cost = round(_time.time() - start, 2)
logger.info(
f"[READ_USER_PROFILE]: cost {cost}s, profile={profile[:50] if profile else 'None'}"
)
if profile:
parts.append(f"## Current user's information\n{profile}")

return "\n\n---\n\n".join(parts)

async def _build_user_memory(
self, session_key: SessionKey, current_message: str, sender_id: str
self, session_key: SessionKey, current_message: str, sender_id: str, ov_tools_enable: bool = True
) -> str:
"""
Build the system prompt from bootstrap files, memory, and skills.
Expand Down Expand Up @@ -168,21 +169,22 @@ async def _build_user_memory(

workspace_id = self.sandbox_manager.to_workspace_id(session_key)

# Viking agent memory
start = _time.time()
viking_memory = await self.memory.get_viking_memory_context(
current_message=current_message, workspace_id=workspace_id, sender_id=sender_id
)
logger.info(f'viking_memory={viking_memory}')
cost = round(_time.time() - start, 2)
logger.info(
f"[READ_USER_MEMORY]: cost {cost}s, memory={viking_memory[:50] if viking_memory else 'None'}"
)
if viking_memory:
parts.append(
f"## openviking_search(query=[user_query])\n"
f"{viking_memory}"
# Viking agent memory (only if ov tools are enabled)
if ov_tools_enable:
start = _time.time()
viking_memory = await self.memory.get_viking_memory_context(
current_message=current_message, workspace_id=workspace_id, sender_id=sender_id
)
logger.info(f'viking_memory={viking_memory}')
cost = round(_time.time() - start, 2)
logger.info(
f"[READ_USER_MEMORY]: cost {cost}s, memory={viking_memory[:50] if viking_memory else 'None'}"
)
if viking_memory:
parts.append(
f"## openviking_search(query=[user_query])\n"
f"{viking_memory}"
)

parts.append("Reply in the same language as the user's query, ignoring the language of the reference materials. User's query:")

Expand Down Expand Up @@ -249,6 +251,7 @@ async def build_messages(
current_message: str,
media: list[str] | None = None,
session_key: SessionKey | None = None,
ov_tools_enable: bool = True,
) -> list[dict[str, Any]]:
"""
Build the complete message list for an LLM call.
Expand All @@ -258,14 +261,15 @@ async def build_messages(
current_message: The new user message.
media: Optional list of local file paths for images/media.
session_key: Optional session key.
ov_tools_enable: Whether to enable OpenViking tools and memory.

Returns:
List of messages including system prompt.
"""
messages = []

# System prompt
system_prompt = await self.build_system_prompt(session_key, current_message, history)
system_prompt = await self.build_system_prompt(session_key, current_message, history, ov_tools_enable=ov_tools_enable)
messages.append({"role": "system", "content": system_prompt})
# logger.debug(f"system_prompt: {system_prompt}")

Expand All @@ -274,7 +278,7 @@ async def build_messages(
messages.extend(history)

# User
user_info = await self._build_user_memory(session_key, current_message, self._sender_id)
user_info = await self._build_user_memory(session_key, current_message, self._sender_id, ov_tools_enable=ov_tools_enable)
messages.append({"role": "user", "content": user_info})

# Current message (with optional image attachments)
Expand Down
53 changes: 44 additions & 9 deletions bot/vikingbot/agent/loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ async def _run_agent_loop(
session_key: SessionKey,
publish_events: bool = True,
sender_id: str | None = None,
ov_tools_enable: bool = True,
) -> tuple[str | None, list[dict], dict[str, int], int]:
"""
Run the core agent loop: call LLM, execute tools, repeat until done.
Expand All @@ -225,6 +226,7 @@ async def _run_agent_loop(
messages: Initial message list
session_key: Session key for tool execution context
publish_events: Whether to publish ITERATION/REASONING/TOOL_CALL events to the bus
ov_tools_enable: Whether to enable OpenViking tools for this session

Returns:
tuple of (final_content, tools_used)
Expand Down Expand Up @@ -252,7 +254,7 @@ async def _run_agent_loop(

response = await self.provider.chat(
messages=messages,
tools=self.tools.get_definitions(),
tools=self.tools.get_definitions(ov_tools_enable=ov_tools_enable),
model=self.model,
session_id=session_key.safe_name(),
)
Expand Down Expand Up @@ -526,21 +528,24 @@ async def check_long_running():
eval=self._eval,
)

ov_tools_enable = self._get_ov_tools_enable(session_key)
# Build initial messages (use get_history for LLM-formatted messages)
messages = await message_context.build_messages(
history=session.get_history(),
current_message=msg.content,
media=msg.media if msg.media else None,
session_key=msg.session_key,
ov_tools_enable=ov_tools_enable,
)
# logger.info(f"New messages: {messages}")
logger.info(f"New messages: {messages}")

# Run agent loop
final_content, tools_used, token_usage, iteration = await self._run_agent_loop(
messages=messages,
session_key=session_key,
publish_events=True,
sender_id=msg.sender_id,
ov_tools_enable=ov_tools_enable,
)

# Log response preview
Expand Down Expand Up @@ -577,6 +582,29 @@ async def check_long_running():
except asyncio.CancelledError:
pass

def _get_channel_config(self, session_key: SessionKey):
"""Get channel config for a session key.

Args:
session_key: Session key to get channel config for

Returns:
Channel config object if found, None otherwise
"""
return self.config.channels_config.get_channel_by_key(session_key.channel_key())

def _get_ov_tools_enable(self, session_key: SessionKey) -> bool:
"""Get ov_tools_enable setting from channel config.

Args:
session_key: Session key to get channel config for

Returns:
True if ov tools should be enabled, False otherwise
"""
channel_config = self._get_channel_config(session_key)
return getattr(channel_config, "ov_tools_enable", True) if channel_config else True

async def _process_system_message(self, msg: InboundMessage) -> OutboundMessage | None:
"""
Process a system message (e.g., subagent announce).
Expand All @@ -589,15 +617,23 @@ async def _process_system_message(self, msg: InboundMessage) -> OutboundMessage
session = self.sessions.get_or_create(msg.session_key)

# Build messages with the announce content
ov_tools_enable = self._get_ov_tools_enable(msg.session_key)
messages = await self.context.build_messages(
history=session.get_history(), current_message=msg.content, session_key=msg.session_key
history=session.get_history(),
current_message=msg.content,
session_key=msg.session_key,
ov_tools_enable=ov_tools_enable,
)

# Check channel config for ov_tools_enable setting
ov_tools_enable = self._get_ov_tools_enable(msg.session_key)

# Run agent loop (no events published)
final_content, tools_used, token_usage, iteration = await self._run_agent_loop(
messages=messages,
session_key=msg.session_key,
publish_events=False,
ov_tools_enable=ov_tools_enable,
)

if final_content is None or (
Expand Down Expand Up @@ -753,12 +789,11 @@ def _check_cmd_auth(self, msg: InboundMessage) -> bool:
allow_from = []
if self.config.ov_server and self.config.ov_server.admin_user_id:
allow_from.append(self.config.ov_server.admin_user_id)
for channel in self.config.channels_config.get_all_channels():
if channel.channel_key() == msg.session_key.channel_key():
allow_cmd = getattr(channel, 'allow_cmd_from', [])
if allow_cmd:
allow_from.extend(allow_cmd)
break
channel_config = self._get_channel_config(msg.session_key)
if channel_config:
allow_cmd = getattr(channel_config, 'allow_cmd_from', [])
if allow_cmd:
allow_from.extend(allow_cmd)

# If channel not found or sender not in allow_from list, ignore message
if msg.sender_id not in allow_from:
Expand Down
11 changes: 9 additions & 2 deletions bot/vikingbot/agent/tools/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,17 @@ def has(self, name: str) -> bool:
"""
return name in self._tools

def get_definitions(self) -> list[dict[str, Any]]:
def get_definitions(self, ov_tools_enable: bool = True) -> list[dict[str, Any]]:
"""
Get all tool definitions in OpenAI format.

Converts all registered tools to the OpenAI function schema format,
suitable for use with OpenAI's function calling API.

Args:
ov_tools_enable: Whether to include OpenViking tools. If False,
tools with names starting with "openviking_" will be excluded.

Returns:
List of tool schemas in OpenAI format, where each schema contains
the tool's type, name, description, and parameters.
Expand All @@ -116,7 +120,10 @@ def get_definitions(self) -> list[dict[str, Any]]:
>>> for defn in definitions:
... print(f"Tool: {defn['function']['name']}")
"""
return [tool.to_schema() for tool in self._tools.values()]
tools = self._tools.values()
if not ov_tools_enable:
tools = [tool for tool in tools if not tool.name.startswith("openviking_")]
return [tool.to_schema() for tool in tools]

async def execute(
self,
Expand Down
15 changes: 15 additions & 0 deletions bot/vikingbot/config/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class BaseChannelConfig(BaseModel):

type: Any = ChannelType.TELEGRAM # Default for backwards compatibility
enabled: bool = True
ov_tools_enable: bool = True

def channel_id(self) -> str:
return "default"
Expand Down Expand Up @@ -403,6 +404,20 @@ def get_all_channels(self) -> list[BaseChannelConfig]:
result.append(item)
return result

def get_channel_by_key(self, channel_key: str) -> BaseChannelConfig | None:
"""Get channel config by channel key.

Args:
channel_key: Channel key in format "type__channel_id"

Returns:
Channel config if found, None otherwise
"""
for channel_config in self.get_all_channels():
if channel_config.channel_key() == channel_key:
return channel_config
return None


class AgentsConfig(BaseModel):
"""Agent configuration."""
Expand Down
Loading