mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-21 07:55:18 +08:00
fix(events): restrict pipeline routes and load agent prompts
This commit is contained in:
@@ -5,9 +5,11 @@ from __future__ import annotations
|
||||
import time
|
||||
import asyncio
|
||||
import contextlib
|
||||
import copy
|
||||
import typing
|
||||
|
||||
from langbot_plugin.api.entities.builtin.provider import message as provider_message
|
||||
from langbot_plugin.api.entities.builtin.provider import prompt as provider_prompt
|
||||
from langbot_plugin.api.entities.builtin.pipeline import query as pipeline_query
|
||||
|
||||
from langbot_plugin.entities.io.actions.enums import PluginToRuntimeAction
|
||||
@@ -18,6 +20,7 @@ from ...pipeline.pool import get_query_execution_context
|
||||
from .binding_resolver import AgentBindingResolver
|
||||
from .context_builder import RunnerContextBuilder, RunnerContextPayload
|
||||
from .descriptor import RunnerDescriptor
|
||||
from .config_schema import extract_prompt_config
|
||||
from .execution_context import (
|
||||
append_mcp_resource_context_to_event,
|
||||
build_mcp_resource_context_addition,
|
||||
@@ -115,6 +118,12 @@ class AgentRunOrchestrator:
|
||||
|
||||
if execution_query is None:
|
||||
execution_query = build_execution_query(event, [])
|
||||
# Event runs bypass pipeline prompt preparation, just like debug runs.
|
||||
prompt_config = extract_prompt_config(descriptor, binding.runner_config, [])
|
||||
execution_query.prompt = provider_prompt.Prompt(
|
||||
name='default',
|
||||
messages=[provider_message.Message(**item) for item in copy.deepcopy(prompt_config)],
|
||||
)
|
||||
# Synthetic events must expose the same trusted scope as pipeline queries.
|
||||
for field_name in ('instance_uuid', 'workspace_uuid', 'placement_generation', 'query_uuid'):
|
||||
object.__setattr__(execution_query, field_name, getattr(execution_context, field_name))
|
||||
|
||||
@@ -47,7 +47,7 @@ from .tenant import TenantContext, require_workspace_uuid, scope_statement
|
||||
AGENT_KIND_AGENT = 'agent'
|
||||
AGENT_KIND_PIPELINE = 'pipeline'
|
||||
AGENT_KIND_EVENT_PROCESSOR = 'event_processor'
|
||||
PIPELINE_EVENT_PATTERNS = ['message.*']
|
||||
PIPELINE_EVENT_PATTERNS = ['message.received']
|
||||
AGENT_DEFAULT_EVENT_PATTERNS = ['*']
|
||||
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class BotService:
|
||||
|
||||
@staticmethod
|
||||
def _is_message_event_pattern(event_pattern: str) -> bool:
|
||||
return event_pattern == 'message.*' or event_pattern.startswith('message.')
|
||||
return event_pattern == 'message.received'
|
||||
|
||||
@staticmethod
|
||||
def _event_pattern_covers(supported_pattern: str, binding_pattern: str) -> bool:
|
||||
@@ -507,7 +507,7 @@ class BotService:
|
||||
|
||||
if target_type == 'pipeline':
|
||||
if not self._is_message_event_pattern(event_pattern):
|
||||
raise ValueError('Pipeline can only be bound to message events')
|
||||
raise ValueError('Pipeline can only be bound to message.received')
|
||||
result = await self.ap.persistence_mgr.execute_async(
|
||||
scope_statement(
|
||||
sqlalchemy.select(persistence_pipeline.LegacyPipeline.uuid).where(
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Narrow legacy Pipeline wildcard routes to incoming messages."""
|
||||
|
||||
import json
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = '0035_pipeline_received_only'
|
||||
down_revision = '0034_monitoring_execution_links'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
connection = op.get_bind()
|
||||
if not sa.inspect(connection).has_table('bots'):
|
||||
return
|
||||
bots = sa.table('bots', sa.column('uuid', sa.String()), sa.column('event_bindings', sa.JSON()))
|
||||
for bot_uuid, bindings in connection.execute(sa.select(bots.c.uuid, bots.c.event_bindings)).all():
|
||||
if isinstance(bindings, str):
|
||||
bindings = json.loads(bindings)
|
||||
if not isinstance(bindings, list):
|
||||
continue
|
||||
changed = False
|
||||
for binding in bindings:
|
||||
if (
|
||||
isinstance(binding, dict)
|
||||
and binding.get('target_type') == 'pipeline'
|
||||
and binding.get('event_pattern') == 'message.*'
|
||||
):
|
||||
binding['event_pattern'] = 'message.received'
|
||||
changed = True
|
||||
if changed:
|
||||
connection.execute(bots.update().where(bots.c.uuid == bot_uuid).values(event_bindings=bindings))
|
||||
|
||||
|
||||
def downgrade():
|
||||
# An exact incoming-message route remains valid with older versions.
|
||||
pass
|
||||
@@ -158,7 +158,7 @@ class RuntimeBot:
|
||||
|
||||
@classmethod
|
||||
def _is_message_event_type(cls, event_type: str) -> bool:
|
||||
return cls._match_event_pattern(event_type, 'message.*')
|
||||
return event_type == 'message.received'
|
||||
|
||||
@classmethod
|
||||
def _agent_supports_event_type(
|
||||
@@ -310,6 +310,12 @@ class RuntimeBot:
|
||||
step['reason'] = 'Event type does not match binding event_pattern'
|
||||
diagnostic_steps.append(step)
|
||||
continue
|
||||
if binding.get('target_type') == 'pipeline' and not cls._is_message_event_type(event_type):
|
||||
step['failure_code'] = 'processor_incompatible'
|
||||
step['reason'] = 'Pipeline targets only support message.received'
|
||||
diagnostic_steps.append(step)
|
||||
continue
|
||||
|
||||
if not cls._match_event_filters(event, binding.get('filters')):
|
||||
step['failure_code'] = 'filters_mismatch'
|
||||
step['reason'] = 'Event data does not satisfy binding filters'
|
||||
@@ -423,6 +429,8 @@ class RuntimeBot:
|
||||
|
||||
def get_pipeline_target_for_event_type(self, event_type: str = 'message.received') -> str | None:
|
||||
"""Return the first Pipeline target configured for an event type."""
|
||||
if not self._is_message_event_type(event_type):
|
||||
return None
|
||||
matched: list[tuple[int, int, str]] = []
|
||||
for index, binding in enumerate(self._get_event_bindings()):
|
||||
if not binding.get('enabled', True):
|
||||
|
||||
@@ -218,7 +218,7 @@ const BEHAVIOR_PRESETS = [
|
||||
// ── helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function isMessageEventPattern(p: string) {
|
||||
return p === 'message.*' || p.startsWith('message.');
|
||||
return p === 'message.received';
|
||||
}
|
||||
|
||||
interface RouteConflict {
|
||||
|
||||
@@ -886,7 +886,7 @@ const enUS = {
|
||||
enabled: 'Enabled',
|
||||
eventBindingDescriptionPlaceholder: 'Rule description',
|
||||
noEventBindings: 'No event routes',
|
||||
unsupportedPipelineEvent: 'Pipelines can only be used for message.* events',
|
||||
unsupportedPipelineEvent: 'Pipelines can only be used for message.received events',
|
||||
disable: 'Disable',
|
||||
enable: 'Enable',
|
||||
disabledBindings: 'Disabled',
|
||||
|
||||
@@ -904,7 +904,7 @@ const esES = {
|
||||
enabled: 'Activado',
|
||||
eventBindingDescriptionPlaceholder: 'Descripción de la regla',
|
||||
noEventBindings: 'No hay rutas de eventos',
|
||||
unsupportedPipelineEvent: 'Los flujos solo admiten eventos message.*',
|
||||
unsupportedPipelineEvent: 'Los flujos solo admiten eventos message.received',
|
||||
disable: 'Desactivar',
|
||||
enable: 'Activar',
|
||||
disabledBindings: 'Desactivado',
|
||||
|
||||
@@ -891,7 +891,7 @@ const jaJP = {
|
||||
eventBindingDescriptionPlaceholder: 'ルール説明',
|
||||
noEventBindings: 'イベントルートはありません',
|
||||
unsupportedPipelineEvent:
|
||||
'Pipeline は message.* イベントにのみ使用できます',
|
||||
'Pipeline は message.received イベントにのみ使用できます',
|
||||
disable: '無効化',
|
||||
enable: '有効化',
|
||||
disabledBindings: '無効',
|
||||
|
||||
@@ -899,7 +899,7 @@ const ruRU = {
|
||||
enabled: 'Включено',
|
||||
eventBindingDescriptionPlaceholder: 'Описание правила',
|
||||
noEventBindings: 'Нет маршрутов событий',
|
||||
unsupportedPipelineEvent: 'Конвейеры поддерживают только события message.*',
|
||||
unsupportedPipelineEvent: 'Конвейеры поддерживают только события message.received',
|
||||
disable: 'Отключить',
|
||||
enable: 'Включить',
|
||||
disabledBindings: 'Отключено',
|
||||
|
||||
@@ -873,7 +873,7 @@ const thTH = {
|
||||
enabled: 'เปิดใช้งาน',
|
||||
eventBindingDescriptionPlaceholder: 'คำอธิบายกฎ',
|
||||
noEventBindings: 'ไม่มีเส้นทางเหตุการณ์',
|
||||
unsupportedPipelineEvent: 'ไปป์ไลน์ใช้ได้กับเหตุการณ์ message.* เท่านั้น',
|
||||
unsupportedPipelineEvent: 'ไปป์ไลน์ใช้ได้กับเหตุการณ์ message.received เท่านั้น',
|
||||
disable: 'ปิดใช้งาน',
|
||||
enable: 'เปิดใช้งาน',
|
||||
disabledBindings: 'ปิดใช้งาน',
|
||||
|
||||
@@ -888,7 +888,7 @@ const viVN = {
|
||||
enabled: 'Đã bật',
|
||||
eventBindingDescriptionPlaceholder: 'Mô tả quy tắc',
|
||||
noEventBindings: 'Chưa có tuyến sự kiện',
|
||||
unsupportedPipelineEvent: 'Pipeline chỉ hỗ trợ sự kiện message.*',
|
||||
unsupportedPipelineEvent: 'Pipeline chỉ hỗ trợ sự kiện message.received',
|
||||
disable: 'Tắt',
|
||||
enable: 'Bật',
|
||||
disabledBindings: 'Đã tắt',
|
||||
|
||||
@@ -833,7 +833,7 @@ const zhHans = {
|
||||
enabled: '启用',
|
||||
eventBindingDescriptionPlaceholder: '规则说明',
|
||||
noEventBindings: '暂无事件路由',
|
||||
unsupportedPipelineEvent: '流水线 仅可用于 message.* 事件',
|
||||
unsupportedPipelineEvent: '流水线 仅可用于 message.received 事件',
|
||||
disable: '禁用',
|
||||
enable: '启用',
|
||||
disabledBindings: '已禁用',
|
||||
|
||||
@@ -832,7 +832,7 @@ const zhHant = {
|
||||
enabled: '啟用',
|
||||
eventBindingDescriptionPlaceholder: '規則說明',
|
||||
noEventBindings: '暫無事件路由',
|
||||
unsupportedPipelineEvent: '流水線 僅可用於 message.* 事件',
|
||||
unsupportedPipelineEvent: '流水線 僅可用於 message.received 事件',
|
||||
disable: '禁用',
|
||||
enable: '啟用',
|
||||
disabledBindings: '已禁用',
|
||||
|
||||
Reference in New Issue
Block a user