mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-17 01:46:07 +00:00
test(agent): align tests with agent runner plugin path
This commit is contained in:
@@ -56,7 +56,25 @@ def mock_circular_import_chain():
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fake_app():
|
def fake_app():
|
||||||
"""Create FakeApp instance."""
|
"""Create FakeApp instance."""
|
||||||
return FakeApp()
|
import sys
|
||||||
|
|
||||||
|
app = FakeApp()
|
||||||
|
|
||||||
|
class FakeAgentRunOrchestrator:
|
||||||
|
async def try_claim_steering_from_query(self, query):
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def run_from_query(self, query):
|
||||||
|
runner_cls = sys.modules['langbot.pkg.provider.runner'].preregistered_runners[0]
|
||||||
|
runner = runner_cls(app, {})
|
||||||
|
async for result in runner.run(query):
|
||||||
|
yield result
|
||||||
|
|
||||||
|
def resolve_runner_id_for_telemetry(self, query):
|
||||||
|
return 'local-agent'
|
||||||
|
|
||||||
|
app.agent_run_orchestrator = FakeAgentRunOrchestrator()
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|||||||
@@ -35,6 +35,50 @@ def get_entities_module():
|
|||||||
return import_module('langbot.pkg.pipeline.entities')
|
return import_module('langbot.pkg.pipeline.entities')
|
||||||
|
|
||||||
|
|
||||||
|
RUNNER_ID = 'plugin:langbot/local-agent/default'
|
||||||
|
|
||||||
|
|
||||||
|
def attach_agent_runner_descriptor(app, *, multimodal_input=True, tool_calling=True):
|
||||||
|
"""Attach a schema-backed AgentRunner descriptor to a FakeApp."""
|
||||||
|
from langbot.pkg.agent.runner.descriptor import AgentRunnerDescriptor
|
||||||
|
|
||||||
|
descriptor = AgentRunnerDescriptor(
|
||||||
|
id=RUNNER_ID,
|
||||||
|
source='plugin',
|
||||||
|
label={'en_US': 'Local Agent'},
|
||||||
|
plugin_author='langbot',
|
||||||
|
plugin_name='local-agent',
|
||||||
|
runner_name='default',
|
||||||
|
config_schema=[
|
||||||
|
{'name': 'model', 'type': 'model-fallback-selector'},
|
||||||
|
{'name': 'prompt', 'type': 'prompt-editor', 'default': []},
|
||||||
|
],
|
||||||
|
capabilities={
|
||||||
|
'tool_calling': tool_calling,
|
||||||
|
'multimodal_input': multimodal_input,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
app.agent_runner_registry = Mock()
|
||||||
|
app.agent_runner_registry.get = AsyncMock(return_value=descriptor)
|
||||||
|
return descriptor
|
||||||
|
|
||||||
|
|
||||||
|
def agent_runner_pipeline_config(model_config, *, prompt='default'):
|
||||||
|
return {
|
||||||
|
'ai': {
|
||||||
|
'runner': {'id': RUNNER_ID},
|
||||||
|
'runner_config': {
|
||||||
|
RUNNER_ID: {
|
||||||
|
'model': model_config,
|
||||||
|
'prompt': prompt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'output': {'misc': {'at-sender': False}},
|
||||||
|
'trigger': {'misc': {}},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestPreProcessorNormalText:
|
class TestPreProcessorNormalText:
|
||||||
"""Tests for normal text message preprocessing."""
|
"""Tests for normal text message preprocessing."""
|
||||||
|
|
||||||
@@ -274,6 +318,7 @@ class TestPreProcessorModelSelection:
|
|||||||
mock_model.model_entity = Mock(uuid='primary-model-uuid', abilities=['func_call'])
|
mock_model.model_entity = Mock(uuid='primary-model-uuid', abilities=['func_call'])
|
||||||
app.model_mgr.get_model_by_uuid = AsyncMock(return_value=mock_model)
|
app.model_mgr.get_model_by_uuid = AsyncMock(return_value=mock_model)
|
||||||
app.tool_mgr.get_all_tools = AsyncMock(return_value=[])
|
app.tool_mgr.get_all_tools = AsyncMock(return_value=[])
|
||||||
|
attach_agent_runner_descriptor(app)
|
||||||
|
|
||||||
mock_event_ctx = Mock()
|
mock_event_ctx = Mock()
|
||||||
mock_event_ctx.event = Mock(default_prompt=[], prompt=[])
|
mock_event_ctx.event = Mock(default_prompt=[], prompt=[])
|
||||||
@@ -283,17 +328,9 @@ class TestPreProcessorModelSelection:
|
|||||||
query = text_query('hello')
|
query = text_query('hello')
|
||||||
|
|
||||||
# Set pipeline config with primary model
|
# Set pipeline config with primary model
|
||||||
query.pipeline_config = {
|
query.pipeline_config = agent_runner_pipeline_config(
|
||||||
'ai': {
|
{'primary': 'primary-model-uuid', 'fallbacks': []},
|
||||||
'runner': {'runner': 'local-agent'},
|
)
|
||||||
'local-agent': {
|
|
||||||
'model': {'primary': 'primary-model-uuid', 'fallbacks': []},
|
|
||||||
'prompt': 'default',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'output': {'misc': {'at-sender': False}},
|
|
||||||
'trigger': {'misc': {}},
|
|
||||||
}
|
|
||||||
|
|
||||||
result = await stage.process(query, 'PreProcessor')
|
result = await stage.process(query, 'PreProcessor')
|
||||||
|
|
||||||
@@ -333,6 +370,7 @@ class TestPreProcessorModelSelection:
|
|||||||
|
|
||||||
app.model_mgr.get_model_by_uuid = AsyncMock(side_effect=mock_get_model)
|
app.model_mgr.get_model_by_uuid = AsyncMock(side_effect=mock_get_model)
|
||||||
app.tool_mgr.get_all_tools = AsyncMock(return_value=[])
|
app.tool_mgr.get_all_tools = AsyncMock(return_value=[])
|
||||||
|
attach_agent_runner_descriptor(app)
|
||||||
|
|
||||||
mock_event_ctx = Mock()
|
mock_event_ctx = Mock()
|
||||||
mock_event_ctx.event = Mock(default_prompt=[], prompt=[])
|
mock_event_ctx.event = Mock(default_prompt=[], prompt=[])
|
||||||
@@ -341,17 +379,9 @@ class TestPreProcessorModelSelection:
|
|||||||
stage = preproc.PreProcessor(app)
|
stage = preproc.PreProcessor(app)
|
||||||
query = text_query('hello')
|
query = text_query('hello')
|
||||||
|
|
||||||
query.pipeline_config = {
|
query.pipeline_config = agent_runner_pipeline_config(
|
||||||
'ai': {
|
{'primary': 'primary-uuid', 'fallbacks': ['fallback-uuid']},
|
||||||
'runner': {'runner': 'local-agent'},
|
)
|
||||||
'local-agent': {
|
|
||||||
'model': {'primary': 'primary-uuid', 'fallbacks': ['fallback-uuid']},
|
|
||||||
'prompt': 'default',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'output': {'misc': {'at-sender': False}},
|
|
||||||
'trigger': {'misc': {}},
|
|
||||||
}
|
|
||||||
|
|
||||||
result = await stage.process(query, 'PreProcessor')
|
result = await stage.process(query, 'PreProcessor')
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,40 @@ import pytest
|
|||||||
from langbot_plugin.api.entities.builtin.platform import message as platform_message
|
from langbot_plugin.api.entities.builtin.platform import message as platform_message
|
||||||
|
|
||||||
|
|
||||||
|
RUNNER_ID = 'plugin:langbot/local-agent/default'
|
||||||
|
|
||||||
|
|
||||||
|
def _attach_agent_runner_descriptor(app):
|
||||||
|
from langbot.pkg.agent.runner.descriptor import AgentRunnerDescriptor
|
||||||
|
|
||||||
|
descriptor = AgentRunnerDescriptor(
|
||||||
|
id=RUNNER_ID,
|
||||||
|
source='plugin',
|
||||||
|
label={'en_US': 'Local Agent'},
|
||||||
|
plugin_author='langbot',
|
||||||
|
plugin_name='local-agent',
|
||||||
|
runner_name='default',
|
||||||
|
config_schema=[
|
||||||
|
{'name': 'model', 'type': 'model-fallback-selector'},
|
||||||
|
{'name': 'prompt', 'type': 'prompt-editor', 'default': []},
|
||||||
|
],
|
||||||
|
capabilities={'tool_calling': True, 'multimodal_input': True},
|
||||||
|
)
|
||||||
|
app.agent_runner_registry = Mock()
|
||||||
|
app.agent_runner_registry.get = AsyncMock(return_value=descriptor)
|
||||||
|
|
||||||
|
|
||||||
|
def _pipeline_config(model_config):
|
||||||
|
return {
|
||||||
|
'ai': {
|
||||||
|
'runner': {'id': RUNNER_ID},
|
||||||
|
'runner_config': {RUNNER_ID: {'model': model_config, 'prompt': []}},
|
||||||
|
},
|
||||||
|
'trigger': {'misc': {'combine-quote-message': False}},
|
||||||
|
'output': {'misc': {'exception-handling': 'show-hint'}},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _conversation():
|
def _conversation():
|
||||||
prompt = Mock()
|
prompt = Mock()
|
||||||
prompt.messages = []
|
prompt.messages = []
|
||||||
@@ -37,20 +71,14 @@ async def test_preprocessor_keeps_image_placeholder_for_text_only_local_agent(mo
|
|||||||
model.model_entity.abilities = []
|
model.model_entity.abilities = []
|
||||||
|
|
||||||
mock_app.model_mgr.get_model_by_uuid = AsyncMock(return_value=model)
|
mock_app.model_mgr.get_model_by_uuid = AsyncMock(return_value=model)
|
||||||
|
_attach_agent_runner_descriptor(mock_app)
|
||||||
mock_app.sess_mgr.get_session = AsyncMock(
|
mock_app.sess_mgr.get_session = AsyncMock(
|
||||||
return_value=SimpleNamespace(launcher_type=sample_query.launcher_type, launcher_id=sample_query.launcher_id)
|
return_value=SimpleNamespace(launcher_type=sample_query.launcher_type, launcher_id=sample_query.launcher_id)
|
||||||
)
|
)
|
||||||
mock_app.sess_mgr.get_conversation = AsyncMock(return_value=_conversation())
|
mock_app.sess_mgr.get_conversation = AsyncMock(return_value=_conversation())
|
||||||
mock_app.plugin_connector.emit_event = AsyncMock(return_value=_prompt_preprocessing_context())
|
mock_app.plugin_connector.emit_event = AsyncMock(return_value=_prompt_preprocessing_context())
|
||||||
|
|
||||||
sample_query.pipeline_config = {
|
sample_query.pipeline_config = _pipeline_config({'primary': 'text-only-model', 'fallbacks': []})
|
||||||
'ai': {
|
|
||||||
'runner': {'runner': 'local-agent'},
|
|
||||||
'local-agent': {'model': {'primary': 'text-only-model', 'fallbacks': []}, 'prompt': []},
|
|
||||||
},
|
|
||||||
'trigger': {'misc': {'combine-quote-message': False}},
|
|
||||||
'output': {'misc': {'exception-handling': 'show-hint'}},
|
|
||||||
}
|
|
||||||
sample_query.message_chain = platform_message.MessageChain(
|
sample_query.message_chain = platform_message.MessageChain(
|
||||||
[platform_message.Image(base64='data:image/png;base64,AAAA')]
|
[platform_message.Image(base64='data:image/png;base64,AAAA')]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ from langbot_plugin.api.entities.builtin.platform import message as platform_mes
|
|||||||
from langbot_plugin.api.entities import events as plugin_events
|
from langbot_plugin.api.entities import events as plugin_events
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def clear_proxy_env(monkeypatch):
|
||||||
|
for key in ('ALL_PROXY', 'HTTPS_PROXY', 'HTTP_PROXY', 'all_proxy', 'https_proxy', 'http_proxy'):
|
||||||
|
monkeypatch.delenv(key, raising=False)
|
||||||
|
|
||||||
|
|
||||||
class DummyLogger(AbstractEventLogger):
|
class DummyLogger(AbstractEventLogger):
|
||||||
async def info(self, text, images=None, message_session_id=None, no_throw=True):
|
async def info(self, text, images=None, message_session_id=None, no_throw=True):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ def make_pipeline_handler_import_mocks() -> dict[str, MagicMock]:
|
|||||||
'langbot.pkg.pipeline.controller': MagicMock(),
|
'langbot.pkg.pipeline.controller': MagicMock(),
|
||||||
'langbot.pkg.pipeline.pipelinemgr': MagicMock(),
|
'langbot.pkg.pipeline.pipelinemgr': MagicMock(),
|
||||||
'langbot.pkg.pipeline.process.process': MagicMock(),
|
'langbot.pkg.pipeline.process.process': MagicMock(),
|
||||||
|
'langbot.pkg.provider.runner': MagicMock(preregistered_runners=[]),
|
||||||
'langbot.pkg.utils.importutil': mock_importutil,
|
'langbot.pkg.utils.importutil': mock_importutil,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user