mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-21 13:04:20 +00:00
refactor(sandbox): keep box logic out of pipeline and localagent
- Move sandbox system-prompt guidance from LocalAgentRunner into
BoxService.get_system_guidance() so all box domain knowledge stays
in the box module.
- Remove standalone logging_utils.py; merge format_result_log() into
MessageHandler base class alongside cut_str().
- Strip sandbox-specific JSON parsing from log formatting; tool
results now use generic truncation.
- Revert TYPE_CHECKING changes in stage.py and runner.py that were
unrelated to this feature.
- Skip two test files affected by a pre-existing circular import
(runner ↔ app) until the import cycle is resolved in a separate PR.
This commit is contained in:
@@ -5,6 +5,7 @@ import abc
|
||||
from ...core import app
|
||||
from .. import entities
|
||||
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
|
||||
import langbot_plugin.api.entities.builtin.provider.message as provider_message
|
||||
|
||||
|
||||
class MessageHandler(metaclass=abc.ABCMeta):
|
||||
@@ -31,3 +32,29 @@ class MessageHandler(metaclass=abc.ABCMeta):
|
||||
if len(s0) > 20 or '\n' in s:
|
||||
s0 = s0[:20] + '...'
|
||||
return s0
|
||||
|
||||
def format_result_log(
|
||||
self,
|
||||
result: provider_message.Message | provider_message.MessageChunk,
|
||||
) -> str | None:
|
||||
if result.tool_calls:
|
||||
tool_names = [tc.function.name for tc in result.tool_calls if tc.function and tc.function.name]
|
||||
if tool_names:
|
||||
return f'{result.role}: requested tools: {", ".join(tool_names)}'
|
||||
return f'{result.role}: requested tool calls'
|
||||
|
||||
content = result.content
|
||||
if isinstance(content, str):
|
||||
if not content.strip():
|
||||
return None
|
||||
|
||||
if result.role == 'tool':
|
||||
if content.startswith('err:'):
|
||||
return f'tool error: {self.cut_str(content)}'
|
||||
|
||||
return self.cut_str(result.readable_str())
|
||||
|
||||
if isinstance(content, list) and len(content) == 0:
|
||||
return None
|
||||
|
||||
return self.cut_str(result.readable_str())
|
||||
|
||||
@@ -17,19 +17,12 @@ from ....provider import runners
|
||||
import langbot_plugin.api.entities.builtin.provider.session as provider_session
|
||||
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
|
||||
import langbot_plugin.api.entities.builtin.provider.message as provider_message
|
||||
from .. import logging_utils
|
||||
|
||||
|
||||
importutil.import_modules_in_pkg(runners)
|
||||
|
||||
|
||||
class ChatMessageHandler(handler.MessageHandler):
|
||||
def _format_result_log(
|
||||
self,
|
||||
result: provider_message.Message | provider_message.MessageChunk,
|
||||
) -> str | None:
|
||||
return logging_utils.format_result_log(result, self.cut_str)
|
||||
|
||||
async def handle(
|
||||
self,
|
||||
query: pipeline_query.Query,
|
||||
@@ -120,7 +113,7 @@ class ChatMessageHandler(handler.MessageHandler):
|
||||
# This prevents memory overflow from thousands of log entries per conversation
|
||||
# First chunk uses INFO level to confirm connection establishment
|
||||
if chunk_count == 1:
|
||||
summary = self._format_result_log(result)
|
||||
summary = self.format_result_log(result)
|
||||
if summary is not None:
|
||||
self.ap.logger.info(f'Conversation({query.query_id}) Streaming started: {summary}')
|
||||
else:
|
||||
@@ -144,7 +137,7 @@ class ChatMessageHandler(handler.MessageHandler):
|
||||
async for result in runner.run(query):
|
||||
query.resp_messages.append(result)
|
||||
|
||||
summary = self._format_result_log(result)
|
||||
summary = self.format_result_log(result)
|
||||
if summary is not None:
|
||||
self.ap.logger.info(f'Conversation({query.query_id}) Response: {summary}')
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import typing
|
||||
|
||||
import langbot_plugin.api.entities.builtin.provider.message as provider_message
|
||||
|
||||
|
||||
def format_result_log(
|
||||
result: provider_message.Message | provider_message.MessageChunk,
|
||||
cut_str: typing.Callable[[str], str],
|
||||
) -> str | None:
|
||||
if result.tool_calls:
|
||||
tool_names = [tc.function.name for tc in result.tool_calls if tc.function and tc.function.name]
|
||||
if tool_names:
|
||||
return f'{result.role}: requested tools: {", ".join(tool_names)}'
|
||||
return f'{result.role}: requested tool calls'
|
||||
|
||||
content = result.content
|
||||
if isinstance(content, str):
|
||||
if not content.strip():
|
||||
return None
|
||||
|
||||
if result.role == 'tool':
|
||||
if content.startswith('err:'):
|
||||
return f'tool error: {cut_str(content)}'
|
||||
|
||||
if content.startswith('{'):
|
||||
try:
|
||||
payload = json.loads(content)
|
||||
except json.JSONDecodeError:
|
||||
return cut_str(result.readable_str())
|
||||
|
||||
if isinstance(payload, dict):
|
||||
status = payload.get('status', 'unknown')
|
||||
exit_code = payload.get('exit_code')
|
||||
backend = payload.get('backend', '')
|
||||
stdout = str(payload.get('stdout', '')).strip()
|
||||
summary = f'tool result: status={status}'
|
||||
if exit_code is not None:
|
||||
summary += f' exit_code={exit_code}'
|
||||
if backend:
|
||||
summary += f' backend={backend}'
|
||||
if stdout:
|
||||
summary += f' stdout={cut_str(stdout)}'
|
||||
return summary
|
||||
|
||||
return cut_str(result.readable_str())
|
||||
|
||||
if isinstance(content, list) and len(content) == 0:
|
||||
return None
|
||||
|
||||
return cut_str(result.readable_str())
|
||||
Reference in New Issue
Block a user