mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-04 12:56:02 +00:00
feat(agent-runner): align protocol adapter terminology
This commit is contained in:
@@ -16,7 +16,6 @@ from .state_store import get_state_store
|
||||
from .persistent_state_store import get_persistent_state_store
|
||||
from . import events as runner_events
|
||||
from .host_models import AgentEventEnvelope, AgentBinding
|
||||
from .pipeline_compat_adapter import PipelineCompatAdapter
|
||||
|
||||
|
||||
DEFAULT_RUNNER_TIMEOUT_SECONDS = 300
|
||||
@@ -139,7 +138,7 @@ class AgentRunContextPayload(typing.TypedDict):
|
||||
runtime: AgentRuntimeContext
|
||||
config: dict[str, typing.Any] # Binding config from ai.runner_config[runner_id]
|
||||
bootstrap: dict[str, typing.Any] | None # Optional bootstrap context
|
||||
compatibility: dict[str, typing.Any] | None # Legacy compatibility context
|
||||
adapter: dict[str, typing.Any] | None # Pipeline adapter context
|
||||
metadata: dict[str, typing.Any] # Additional metadata
|
||||
|
||||
|
||||
@@ -148,7 +147,7 @@ class AgentRunContextBuilder:
|
||||
|
||||
Two entry points:
|
||||
- build_context_from_event(event, binding): Event-first Protocol v1
|
||||
- build_context(query, descriptor, resources): Legacy Query-based (calls event-based internally)
|
||||
- build_context(query, descriptor, resources): Pipeline adapter Query-based entry
|
||||
|
||||
Responsibilities:
|
||||
- Generate new run_id (UUID, not query id)
|
||||
@@ -212,14 +211,14 @@ class AgentRunContextBuilder:
|
||||
conversation: ConversationContext | None = None
|
||||
if event.conversation_id:
|
||||
conversation = {
|
||||
'session_id': None, # Legacy field
|
||||
'session_id': None, # Pipeline adapter field
|
||||
'conversation_id': event.conversation_id,
|
||||
'thread_id': event.thread_id,
|
||||
'launcher_type': None, # Will be filled from actor/subject if needed
|
||||
'launcher_id': None,
|
||||
'sender_id': event.actor.actor_id if event.actor else None,
|
||||
'bot_uuid': event.bot_id,
|
||||
'pipeline_uuid': binding.pipeline_uuid, # Legacy
|
||||
'pipeline_uuid': binding.pipeline_uuid, # Pipeline adapter field
|
||||
}
|
||||
|
||||
# Build event context (Protocol v1 event-first)
|
||||
@@ -293,12 +292,12 @@ class AgentRunContextBuilder:
|
||||
'platform_capabilities': event.delivery.platform_capabilities,
|
||||
}
|
||||
|
||||
# Build compatibility context (empty for event-first)
|
||||
compatibility_context = {
|
||||
# Build adapter context (empty for event-first)
|
||||
adapter_context = {
|
||||
'query_id': None,
|
||||
'pipeline_uuid': binding.pipeline_uuid,
|
||||
'max_round': binding.max_round, # For reference only
|
||||
'legacy_messages': [],
|
||||
'adapter_messages': [],
|
||||
'extra': {},
|
||||
}
|
||||
|
||||
@@ -318,7 +317,7 @@ class AgentRunContextBuilder:
|
||||
'runtime': runtime,
|
||||
'config': binding.runner_config,
|
||||
'bootstrap': None, # Optional - no messages inlined by default
|
||||
'compatibility': compatibility_context,
|
||||
'adapter': adapter_context,
|
||||
'metadata': {}, # Additional metadata
|
||||
}
|
||||
|
||||
@@ -332,12 +331,11 @@ class AgentRunContextBuilder:
|
||||
) -> AgentRunContextPayload:
|
||||
"""Build AgentRunContext envelope from Query.
|
||||
|
||||
This is a compatibility wrapper that converts Query to event + binding
|
||||
This is a Pipeline adapter wrapper that converts Query to event + binding
|
||||
and delegates to build_context_from_event().
|
||||
|
||||
For Protocol v1, messages are NOT inlined by default.
|
||||
Legacy max-round only affects bootstrap (via compatibility adapter),
|
||||
NOT Protocol v1 entities.
|
||||
Pipeline max-round only affects bootstrap, NOT Protocol v1 entities.
|
||||
|
||||
Args:
|
||||
query: Pipeline query
|
||||
@@ -354,7 +352,7 @@ class AgentRunContextBuilder:
|
||||
runner_id,
|
||||
)
|
||||
|
||||
# Extract max_round for compatibility (NOT Protocol v1)
|
||||
# Extract max_round for Pipeline adapter bootstrap (NOT Protocol v1)
|
||||
# Note: config uses 'max-round' with hyphen, not 'max_round'
|
||||
max_round = runner_config.get('max-round')
|
||||
if max_round is None:
|
||||
@@ -413,7 +411,7 @@ class AgentRunContextBuilder:
|
||||
|
||||
# Build delivery context from query adapter capabilities
|
||||
delivery_context = {
|
||||
'surface': 'pipeline', # Legacy pipeline surface
|
||||
'surface': 'pipeline',
|
||||
'reply_target': None,
|
||||
'supports_streaming': streaming_supported,
|
||||
'supports_edit': False,
|
||||
@@ -422,8 +420,8 @@ class AgentRunContextBuilder:
|
||||
'platform_capabilities': {},
|
||||
}
|
||||
|
||||
# Build context access (for legacy, minimal API availability)
|
||||
# Legacy Query-based mode does NOT have persistent state API
|
||||
# Build context access for the direct Query adapter helper.
|
||||
# The event-first run_from_query path uses build_context_from_event().
|
||||
context_access = {
|
||||
'conversation_id': conversation.get('conversation_id') if conversation else None,
|
||||
'thread_id': None,
|
||||
@@ -436,7 +434,7 @@ class AgentRunContextBuilder:
|
||||
'delivered_count': 0,
|
||||
'source_total_count': None,
|
||||
'messages_complete': False,
|
||||
'reason': 'legacy_pipeline',
|
||||
'reason': 'pipeline_adapter',
|
||||
},
|
||||
'available_apis': {
|
||||
'history_page': False,
|
||||
@@ -445,40 +443,40 @@ class AgentRunContextBuilder:
|
||||
'event_page': False,
|
||||
'artifact_metadata': False,
|
||||
'artifact_read': False,
|
||||
'state': False, # Legacy Query mode does not have persistent state API
|
||||
'state': False,
|
||||
'storage': True,
|
||||
},
|
||||
}
|
||||
|
||||
# Build compatibility context (for legacy Query/Pipeline fields)
|
||||
compatibility_context = {
|
||||
# Build adapter context (for Pipeline adapter fields)
|
||||
adapter_context = {
|
||||
'query_id': query.query_id,
|
||||
'pipeline_uuid': getattr(query, 'pipeline_uuid', None),
|
||||
'max_round': max_round, # For reference only
|
||||
'legacy_messages': [], # Will be filled if max_round is set
|
||||
'adapter_messages': [], # Will be filled if max_round is set
|
||||
'extra': {
|
||||
'params': params, # Put params in compatibility.extra
|
||||
'prompt': self._build_prompt(query), # Put prompt in compatibility.extra
|
||||
'params': params, # Put params in adapter.extra
|
||||
'prompt': self._build_prompt(query), # Put prompt in adapter.extra
|
||||
},
|
||||
}
|
||||
|
||||
# Build bootstrap context (optional, for legacy max-round)
|
||||
# Build bootstrap context (optional, for Pipeline adapter max-round)
|
||||
bootstrap_context = None
|
||||
|
||||
# For legacy compatibility: add bootstrap messages if max_round is set
|
||||
# For Pipeline adapter: add bootstrap messages if max_round is set
|
||||
# This goes into bootstrap.messages, NOT top-level messages
|
||||
if max_round and max_round > 0:
|
||||
packaged_context = self.context_packager.package_messages(query, runner_config)
|
||||
legacy_messages = self._build_messages(packaged_context.messages)
|
||||
adapter_messages = self._build_messages(packaged_context.messages)
|
||||
# Put in bootstrap for Protocol v1
|
||||
bootstrap_context = {
|
||||
'messages': legacy_messages,
|
||||
'messages': adapter_messages,
|
||||
'summary': None,
|
||||
'artifacts': [],
|
||||
'metadata': {},
|
||||
}
|
||||
# Also update compatibility for legacy runners
|
||||
compatibility_context['legacy_messages'] = legacy_messages
|
||||
# Also update adapter for transition runners
|
||||
adapter_context['adapter_messages'] = adapter_messages
|
||||
# Update runtime metadata
|
||||
runtime['metadata']['context_packaging'] = {
|
||||
'policy': packaged_context.policy,
|
||||
@@ -501,7 +499,7 @@ class AgentRunContextBuilder:
|
||||
'runtime': runtime,
|
||||
'config': runner_config,
|
||||
'bootstrap': bootstrap_context, # Optional bootstrap
|
||||
'compatibility': compatibility_context, # Legacy compatibility
|
||||
'adapter': adapter_context, # Pipeline adapter context
|
||||
'metadata': {}, # Additional metadata
|
||||
}
|
||||
|
||||
@@ -902,7 +900,7 @@ class AgentRunContextBuilder:
|
||||
artifact_read_enabled = 'read' in artifact_permissions
|
||||
|
||||
# Determine state API availability based on binding state_policy (event-first mode)
|
||||
# For legacy Query-based mode, state is NOT available (no persistent state API)
|
||||
# Direct Query context builder does not expose persistent state API.
|
||||
state_enabled = False
|
||||
if binding is not None:
|
||||
state_policy = binding.state_policy
|
||||
|
||||
@@ -7,7 +7,7 @@ import typing
|
||||
from langbot_plugin.api.entities.builtin.pipeline import query as pipeline_query
|
||||
|
||||
|
||||
DEFAULT_LEGACY_MAX_ROUND = 10
|
||||
DEFAULT_MAX_ROUND = 10
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@@ -19,21 +19,16 @@ class ContextPackagingResult:
|
||||
history: dict[str, typing.Any]
|
||||
|
||||
|
||||
def get_legacy_max_round(runner_config: dict[str, typing.Any]) -> typing.Any:
|
||||
"""Return the configured legacy max-round value.
|
||||
|
||||
Keep the existing config semantics intact: callers are expected to pass the
|
||||
already-resolved runner binding config, and invalid values fail the same way
|
||||
the old truncator failed when comparing them with an integer round count.
|
||||
"""
|
||||
return runner_config.get('max-round', DEFAULT_LEGACY_MAX_ROUND)
|
||||
def get_max_round(runner_config: dict[str, typing.Any]) -> typing.Any:
|
||||
"""Return the configured Pipeline adapter max-round value."""
|
||||
return runner_config.get('max-round', DEFAULT_MAX_ROUND)
|
||||
|
||||
|
||||
def select_legacy_max_round_messages(
|
||||
def select_max_round_messages(
|
||||
messages: list[typing.Any] | None,
|
||||
max_round: typing.Any,
|
||||
) -> list[typing.Any]:
|
||||
"""Select the same message window as the legacy round truncator."""
|
||||
"""Select a bounded recent message window by user-round count."""
|
||||
if not messages:
|
||||
return []
|
||||
|
||||
@@ -59,15 +54,15 @@ class AgentContextPackager:
|
||||
query: pipeline_query.Query,
|
||||
runner_config: dict[str, typing.Any],
|
||||
) -> ContextPackagingResult:
|
||||
"""Package query messages using the current legacy max-round policy."""
|
||||
"""Package query messages using the Pipeline adapter max-round policy."""
|
||||
source_messages = query.messages or []
|
||||
max_round = get_legacy_max_round(runner_config)
|
||||
packaged_messages = select_legacy_max_round_messages(source_messages, max_round)
|
||||
max_round = get_max_round(runner_config)
|
||||
packaged_messages = select_max_round_messages(source_messages, max_round)
|
||||
|
||||
return ContextPackagingResult(
|
||||
messages=packaged_messages,
|
||||
policy={
|
||||
'mode': 'legacy_max_round',
|
||||
'mode': 'max_round',
|
||||
'max_round': max_round,
|
||||
},
|
||||
history={
|
||||
|
||||
@@ -163,9 +163,9 @@ class AgentBinding(pydantic.BaseModel):
|
||||
enabled: bool = True
|
||||
"""Whether binding is enabled."""
|
||||
|
||||
# Legacy fields for compatibility adapter
|
||||
# Fields for Pipeline adapter
|
||||
pipeline_uuid: str | None = None
|
||||
"""Legacy pipeline UUID (for compatibility)."""
|
||||
"""Pipeline UUID (for Pipeline adapter)."""
|
||||
|
||||
max_round: int | None = None
|
||||
"""Legacy max-round (for compatibility adapter, not Protocol v1)."""
|
||||
"""max-round (for Pipeline adapter bootstrap, not Protocol v1)."""
|
||||
|
||||
@@ -49,8 +49,7 @@ def parse_runner_id(runner_id: str) -> RunnerIdParts:
|
||||
runner_name=runner_name,
|
||||
)
|
||||
else:
|
||||
# For backward compatibility with old built-in runner names
|
||||
# This should eventually be removed after migration
|
||||
# Only plugin runner IDs are valid at the protocol boundary.
|
||||
raise ValueError(
|
||||
f'Invalid runner ID format: {runner_id}. '
|
||||
f'Expected: plugin:author/plugin_name/runner_name'
|
||||
@@ -89,4 +88,4 @@ def is_plugin_runner_id(runner_id: str) -> bool:
|
||||
Returns:
|
||||
True if runner ID starts with 'plugin:'
|
||||
"""
|
||||
return runner_id.startswith('plugin:')
|
||||
return runner_id.startswith('plugin:')
|
||||
|
||||
@@ -21,7 +21,7 @@ from .persistent_state_store import get_persistent_state_store, PersistentStateS
|
||||
from .session_registry import get_session_registry, AgentRunSessionRegistry
|
||||
from .config_migration import ConfigMigration
|
||||
from .host_models import AgentEventEnvelope, AgentBinding
|
||||
from .pipeline_compat_adapter import PipelineCompatAdapter
|
||||
from .pipeline_adapter import PipelineAdapter
|
||||
from .errors import (
|
||||
RunnerNotFoundError,
|
||||
RunnerExecutionError,
|
||||
@@ -48,7 +48,7 @@ class AgentRunOrchestrator:
|
||||
|
||||
Entry points:
|
||||
- run(event, binding): Main entry for event-first Protocol v1
|
||||
- run_from_query(query): Compatibility wrapper for Pipeline
|
||||
- run_from_query(query): Pipeline adapter wrapper
|
||||
"""
|
||||
|
||||
ap: app.Application
|
||||
@@ -86,7 +86,7 @@ class AgentRunOrchestrator:
|
||||
event: AgentEventEnvelope,
|
||||
binding: AgentBinding,
|
||||
bound_plugins: list[str] | None = None,
|
||||
compatibility_context: dict[str, typing.Any] | None = None,
|
||||
adapter_context: dict[str, typing.Any] | None = None,
|
||||
) -> typing.AsyncGenerator[provider_message.Message | provider_message.MessageChunk, None]:
|
||||
"""Run agent runner from event-first envelope.
|
||||
|
||||
@@ -97,7 +97,7 @@ class AgentRunOrchestrator:
|
||||
event: Event envelope from event gateway
|
||||
binding: Agent binding configuration
|
||||
bound_plugins: Optional list of bound plugin identities for authorization
|
||||
compatibility_context: Optional compatibility context from Pipeline adapter
|
||||
adapter_context: Optional adapter context from Pipeline adapter
|
||||
|
||||
Yields:
|
||||
Message or MessageChunk for pipeline response
|
||||
@@ -127,27 +127,27 @@ class AgentRunOrchestrator:
|
||||
resources=resources,
|
||||
)
|
||||
|
||||
# Merge compatibility context if provided (for Pipeline compatibility)
|
||||
if compatibility_context:
|
||||
# Merge params into compatibility.extra
|
||||
if 'params' in compatibility_context:
|
||||
context['compatibility']['extra']['params'] = compatibility_context['params']
|
||||
# Merge prompt into compatibility.extra (for legacy runners)
|
||||
if 'prompt' in compatibility_context:
|
||||
context['compatibility']['extra']['prompt'] = compatibility_context['prompt']
|
||||
# Merge adapter context if provided (for Pipeline adapter)
|
||||
if adapter_context:
|
||||
# Merge params into adapter.extra
|
||||
if 'params' in adapter_context:
|
||||
context['adapter']['extra']['params'] = adapter_context['params']
|
||||
# Merge prompt into adapter.extra (for transition runners)
|
||||
if 'prompt' in adapter_context:
|
||||
context['adapter']['extra']['prompt'] = adapter_context['prompt']
|
||||
# Merge bootstrap if provided
|
||||
if compatibility_context.get('bootstrap'):
|
||||
context['bootstrap'] = compatibility_context['bootstrap']
|
||||
# Also set legacy_messages for legacy runners
|
||||
bootstrap_messages = compatibility_context['bootstrap'].get('messages')
|
||||
if adapter_context.get('bootstrap'):
|
||||
context['bootstrap'] = adapter_context['bootstrap']
|
||||
# Also set adapter_messages for transition runners
|
||||
bootstrap_messages = adapter_context['bootstrap'].get('messages')
|
||||
if bootstrap_messages:
|
||||
context['compatibility']['legacy_messages'] = bootstrap_messages
|
||||
context['adapter']['adapter_messages'] = bootstrap_messages
|
||||
# Merge runtime metadata if provided
|
||||
if compatibility_context.get('runtime_metadata'):
|
||||
context['runtime']['metadata'].update(compatibility_context['runtime_metadata'])
|
||||
if adapter_context.get('runtime_metadata'):
|
||||
context['runtime']['metadata'].update(adapter_context['runtime_metadata'])
|
||||
# Set query_id if provided
|
||||
if compatibility_context.get('query_id'):
|
||||
context['runtime']['query_id'] = compatibility_context['query_id']
|
||||
if adapter_context.get('query_id'):
|
||||
context['runtime']['query_id'] = adapter_context['query_id']
|
||||
|
||||
# Build state context for State API handlers
|
||||
state_context = self._build_state_context(event, binding, descriptor)
|
||||
@@ -243,7 +243,7 @@ class AgentRunOrchestrator:
|
||||
) -> typing.AsyncGenerator[provider_message.Message | provider_message.MessageChunk, None]:
|
||||
"""Run agent runner from pipeline query.
|
||||
|
||||
This is a compatibility wrapper for the legacy Query-based flow.
|
||||
This is the Pipeline adapter wrapper for the Query-based flow.
|
||||
It delegates to the event-first run(event, binding) method.
|
||||
|
||||
For the new event-first Protocol v1, use run(event, binding) instead.
|
||||
@@ -265,34 +265,34 @@ class AgentRunOrchestrator:
|
||||
raise RunnerNotFoundError('no runner configured')
|
||||
|
||||
# Convert Query to event-first envelope
|
||||
event = PipelineCompatAdapter.query_to_event(query)
|
||||
event = PipelineAdapter.query_to_event(query)
|
||||
|
||||
# Convert Pipeline config to binding
|
||||
binding = PipelineCompatAdapter.pipeline_config_to_binding(query, runner_id)
|
||||
binding = PipelineAdapter.pipeline_config_to_binding(query, runner_id)
|
||||
|
||||
# Extract bound plugins for authorization
|
||||
bound_plugins = query.variables.get('_pipeline_bound_plugins')
|
||||
|
||||
# Build compatibility context for Pipeline-specific fields
|
||||
compatibility_context = await self._build_compatibility_context(query, binding)
|
||||
# Build adapter context for Pipeline-specific fields
|
||||
adapter_context = await self._build_adapter_context(query, binding)
|
||||
|
||||
# Delegate to event-first run()
|
||||
async for result in self.run(
|
||||
event,
|
||||
binding,
|
||||
bound_plugins=bound_plugins,
|
||||
compatibility_context=compatibility_context,
|
||||
adapter_context=adapter_context,
|
||||
):
|
||||
yield result
|
||||
|
||||
async def _build_compatibility_context(
|
||||
async def _build_adapter_context(
|
||||
self,
|
||||
query: pipeline_query.Query,
|
||||
binding: AgentBinding,
|
||||
) -> dict[str, typing.Any]:
|
||||
"""Build compatibility context for Pipeline Query-based flow.
|
||||
"""Build adapter context for Pipeline Query-based flow.
|
||||
|
||||
This extracts legacy fields from Query that aren't available in
|
||||
This extracts adapter-specific fields from Query that aren't available in
|
||||
the event-first flow:
|
||||
- params (from query.variables)
|
||||
- bootstrap messages (for max-round)
|
||||
@@ -304,7 +304,7 @@ class AgentRunOrchestrator:
|
||||
binding: Agent binding with max_round
|
||||
|
||||
Returns:
|
||||
Compatibility context dict
|
||||
Adapter context dict
|
||||
"""
|
||||
from .context_packager import AgentContextPackager
|
||||
|
||||
@@ -312,10 +312,10 @@ class AgentRunOrchestrator:
|
||||
# (excludes internal vars, sensitive patterns, permission vars, non-JSON values)
|
||||
params = self.context_builder._build_params(query)
|
||||
|
||||
# Build prompt from query.prompt.messages (for legacy compatibility)
|
||||
# Build prompt from query.prompt.messages (for transition runners)
|
||||
prompt = self.context_builder._build_prompt(query)
|
||||
|
||||
# Build bootstrap context for legacy max-round
|
||||
# Build bootstrap context for max-round
|
||||
bootstrap = None
|
||||
runtime_metadata = {}
|
||||
max_round = binding.max_round
|
||||
@@ -327,12 +327,12 @@ class AgentRunOrchestrator:
|
||||
packaged_context = context_packager.package_messages(query, runner_config)
|
||||
|
||||
# Build messages list
|
||||
legacy_messages = []
|
||||
adapter_messages = []
|
||||
for msg in packaged_context.messages:
|
||||
legacy_messages.append(msg.model_dump(mode='json'))
|
||||
adapter_messages.append(msg.model_dump(mode='json'))
|
||||
|
||||
bootstrap = {
|
||||
'messages': legacy_messages,
|
||||
'messages': adapter_messages,
|
||||
'summary': None,
|
||||
'artifacts': [],
|
||||
'metadata': {},
|
||||
@@ -497,7 +497,7 @@ class AgentRunOrchestrator:
|
||||
"""
|
||||
data = result_dict.get('data', {})
|
||||
|
||||
# Extract scope (default to 'conversation' for backward compat)
|
||||
# Extract scope (default to conversation when omitted by the runner)
|
||||
scope = data.get('scope', 'conversation')
|
||||
|
||||
# Extract key and value
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Persistent state store for AgentRunner protocol state.
|
||||
|
||||
This module provides a database-backed state store for event-first Protocol v1,
|
||||
while preserving in-memory state store for legacy Query-based flow.
|
||||
This module provides a database-backed state store for event-first Protocol v1.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -25,8 +24,8 @@ from ...entity.persistence.agent_runner_state import AgentRunnerState
|
||||
# Valid state scopes for agent runner state updates.
|
||||
VALID_STATE_SCOPES = ('conversation', 'actor', 'subject', 'runner')
|
||||
|
||||
# Key mapping for backward compatibility
|
||||
LEGACY_KEY_MAPPING = {
|
||||
# External-facing key aliases accepted from runners.
|
||||
STATE_KEY_ALIASES = {
|
||||
'conversation_id': 'external.conversation_id',
|
||||
}
|
||||
|
||||
@@ -276,9 +275,9 @@ class PersistentStateStore:
|
||||
if not self._check_scope_enabled(scope, binding):
|
||||
return False, f'Scope "{scope}" not enabled by binding policy'
|
||||
|
||||
# Map legacy key names
|
||||
if key in LEGACY_KEY_MAPPING:
|
||||
key = LEGACY_KEY_MAPPING[key]
|
||||
# Map accepted key aliases
|
||||
if key in STATE_KEY_ALIASES:
|
||||
key = STATE_KEY_ALIASES[key]
|
||||
|
||||
# Get scope key
|
||||
scope_key = self._get_scope_key(scope, event, binding, descriptor)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Pipeline compatibility adapter for converting Query to event-first envelope.
|
||||
"""Pipeline adapter for converting Query to event-first envelope.
|
||||
|
||||
This adapter bridges the legacy Query/Pipeline approach with the new
|
||||
event-first Protocol v1 architecture. It is a compatibility layer only.
|
||||
This adapter bridges the Query/Pipeline entry point with the event-first
|
||||
Protocol v1 architecture.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -32,14 +32,14 @@ from .host_models import (
|
||||
from . import events as runner_events
|
||||
|
||||
|
||||
class PipelineCompatAdapter:
|
||||
class PipelineAdapter:
|
||||
"""Adapter for converting Pipeline Query to event-first envelope.
|
||||
|
||||
This adapter is responsible for:
|
||||
- Converting Query to AgentEventEnvelope
|
||||
- Converting Pipeline config to temporary AgentBinding
|
||||
- Handling legacy max-round as bootstrap policy
|
||||
- Putting Query-only fields into compatibility context
|
||||
- Handling max-round as bootstrap policy
|
||||
- Putting Query-only fields into adapter context
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@@ -80,7 +80,7 @@ class PipelineCompatAdapter:
|
||||
event_id=event.event_id or str(query.query_id),
|
||||
event_type=event.event_type or runner_events.MESSAGE_RECEIVED,
|
||||
event_time=event.event_time,
|
||||
source="pipeline_compat",
|
||||
source="pipeline_adapter",
|
||||
bot_id=query.bot_uuid,
|
||||
workspace_id=None, # Not available in Query
|
||||
conversation_id=conversation.conversation_id,
|
||||
@@ -111,7 +111,7 @@ class PipelineCompatAdapter:
|
||||
ai_config = pipeline_config.get('ai', {})
|
||||
runner_config = ai_config.get('runner_config', {}).get(runner_id, {})
|
||||
|
||||
# Extract max_round for compatibility (used in bootstrap, not Protocol v1)
|
||||
# Extract max_round for adapter (used in bootstrap, not Protocol v1)
|
||||
# Note: config uses 'max-round' with hyphen, not 'max_round' with underscore
|
||||
max_round = runner_config.get('max-round') or ai_config.get('max-round')
|
||||
|
||||
@@ -135,7 +135,6 @@ class PipelineCompatAdapter:
|
||||
)
|
||||
|
||||
# Build delivery policy
|
||||
output_config = pipeline_config.get('output', {})
|
||||
delivery_policy = DeliveryPolicy(
|
||||
enable_streaming=True,
|
||||
enable_reply=True,
|
||||
@@ -161,10 +160,10 @@ class PipelineCompatAdapter:
|
||||
query: pipeline_query.Query,
|
||||
binding: AgentBinding,
|
||||
) -> dict[str, typing.Any]:
|
||||
"""Build bootstrap context from binding for legacy max-round.
|
||||
"""Build bootstrap context from binding for max-round.
|
||||
|
||||
This method handles the legacy max-round -> bootstrap conversion.
|
||||
max-round is NOT part of Protocol v1, only used by compatibility adapter.
|
||||
This method handles the max-round -> bootstrap conversion.
|
||||
max-round is NOT part of Protocol v1, only used by Pipeline adapter.
|
||||
|
||||
Args:
|
||||
query: Pipeline query
|
||||
@@ -183,42 +182,42 @@ class PipelineCompatAdapter:
|
||||
"artifacts": [],
|
||||
"metadata": {
|
||||
"policy": "self_managed",
|
||||
"legacy_max_round": None,
|
||||
"max_round": None,
|
||||
},
|
||||
}
|
||||
|
||||
# Legacy max-round packaging (will be handled by context_packager)
|
||||
# max-round packaging (will be handled by context_packager)
|
||||
return {
|
||||
"messages": [], # Will be filled by context_packager
|
||||
"summary": None,
|
||||
"artifacts": [],
|
||||
"metadata": {
|
||||
"policy": "legacy_max_round",
|
||||
"legacy_max_round": max_round,
|
||||
"policy": "max_round",
|
||||
"max_round": max_round,
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def build_compatibility_context(
|
||||
def build_adapter_context(
|
||||
cls,
|
||||
query: pipeline_query.Query,
|
||||
) -> dict[str, typing.Any]:
|
||||
"""Build compatibility context for legacy Query/Pipeline fields.
|
||||
"""Build adapter context for Pipeline adapter fields.
|
||||
|
||||
These fields are for migration purposes only.
|
||||
These fields are for transition purposes only.
|
||||
Runners should NOT depend on them for long-term capabilities.
|
||||
|
||||
Args:
|
||||
query: Pipeline query
|
||||
|
||||
Returns:
|
||||
Compatibility context data
|
||||
Adapter context data
|
||||
"""
|
||||
return {
|
||||
"query_id": query.query_id,
|
||||
"pipeline_uuid": query.pipeline_uuid,
|
||||
"max_round": None, # Moved to binding, not here
|
||||
"legacy_messages": [], # Will be filled by context_packager
|
||||
"adapter_messages": [], # Will be filled by context_packager
|
||||
"extra": {
|
||||
"bot_uuid": query.bot_uuid,
|
||||
"sender_id": str(query.sender_id) if query.sender_id else None,
|
||||
@@ -266,7 +265,7 @@ class PipelineCompatAdapter:
|
||||
event_id=str(message_id or query.query_id),
|
||||
event_type=runner_events.MESSAGE_RECEIVED,
|
||||
event_time=event_time,
|
||||
source="pipeline_compat",
|
||||
source="pipeline_adapter",
|
||||
source_event_type=source_event_type,
|
||||
data=event_data,
|
||||
)
|
||||
@@ -32,7 +32,7 @@ class AgentResourceBuilder:
|
||||
|
||||
Entry points:
|
||||
- build_resources_from_binding(event, binding, descriptor): Event-first Protocol v1
|
||||
- build_resources(query, descriptor): Legacy Query-based
|
||||
- build_resources(query, descriptor): Pipeline adapter Query-based
|
||||
|
||||
Note: This only builds the resource declaration. The actual proxy actions
|
||||
in handler.py must still validate against ctx.resources at runtime.
|
||||
@@ -216,7 +216,7 @@ class AgentResourceBuilder:
|
||||
) -> AgentResources:
|
||||
"""Build AgentResources from query and runner descriptor.
|
||||
|
||||
This is a compatibility wrapper for Query-based flow.
|
||||
This is a Pipeline adapter wrapper for Query-based flow.
|
||||
|
||||
Args:
|
||||
query: Pipeline query with pipeline_config and variables
|
||||
|
||||
@@ -13,8 +13,8 @@ from .host_models import AgentEventEnvelope
|
||||
# Valid state scopes for agent runner state updates.
|
||||
VALID_STATE_SCOPES = ('conversation', 'actor', 'subject', 'runner')
|
||||
|
||||
# Key mapping for backward compatibility
|
||||
LEGACY_KEY_MAPPING = {
|
||||
# External-facing key aliases accepted from runners.
|
||||
STATE_KEY_ALIASES = {
|
||||
'conversation_id': 'external.conversation_id',
|
||||
}
|
||||
|
||||
@@ -226,12 +226,12 @@ class RunnerScopedStateStore:
|
||||
)
|
||||
return False
|
||||
|
||||
# Map legacy key names
|
||||
if key in LEGACY_KEY_MAPPING:
|
||||
mapped_key = LEGACY_KEY_MAPPING[key]
|
||||
# Map accepted key aliases
|
||||
if key in STATE_KEY_ALIASES:
|
||||
mapped_key = STATE_KEY_ALIASES[key]
|
||||
if logger:
|
||||
logger.debug(
|
||||
f'Runner {descriptor.id} state.updated legacy key "{key}" mapped to "{mapped_key}"'
|
||||
f'Runner {descriptor.id} state.updated key alias "{key}" mapped to "{mapped_key}"'
|
||||
)
|
||||
key = mapped_key
|
||||
|
||||
@@ -245,8 +245,7 @@ class RunnerScopedStateStore:
|
||||
# Sync external.conversation_id to query.session.using_conversation.uuid
|
||||
if scope == 'conversation' and key == 'external.conversation_id':
|
||||
if query.session and query.session.using_conversation:
|
||||
# Update conversation uuid for backward compatibility
|
||||
# This ensures old conversation continuation behavior works
|
||||
# Keep the active conversation UUID aligned with runner-owned state.
|
||||
setattr(query.session.using_conversation, 'uuid', value)
|
||||
if logger:
|
||||
logger.debug(
|
||||
@@ -564,12 +563,12 @@ class RunnerScopedStateStore:
|
||||
)
|
||||
return False
|
||||
|
||||
# Map legacy key names
|
||||
if key in LEGACY_KEY_MAPPING:
|
||||
mapped_key = LEGACY_KEY_MAPPING[key]
|
||||
# Map accepted key aliases
|
||||
if key in STATE_KEY_ALIASES:
|
||||
mapped_key = STATE_KEY_ALIASES[key]
|
||||
if logger:
|
||||
logger.debug(
|
||||
f'Runner {descriptor.id} state.updated legacy key "{key}" mapped to "{mapped_key}"'
|
||||
f'Runner {descriptor.id} state.updated key alias "{key}" mapped to "{mapped_key}"'
|
||||
)
|
||||
key = mapped_key
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class EventLog(Base):
|
||||
"""When the event occurred."""
|
||||
|
||||
source = sqlalchemy.Column(sqlalchemy.String(50), nullable=False)
|
||||
"""Event source (platform, webui, api, scheduler, system, pipeline_compat)."""
|
||||
"""Event source (platform, webui, api, scheduler, system, pipeline_adapter)."""
|
||||
|
||||
bot_id = sqlalchemy.Column(sqlalchemy.String(255), nullable=True, index=True)
|
||||
"""Bot UUID that handled this event."""
|
||||
|
||||
@@ -4,8 +4,8 @@ from .. import truncator
|
||||
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
|
||||
from ....agent.runner.config_migration import ConfigMigration
|
||||
from ....agent.runner.context_packager import (
|
||||
get_legacy_max_round,
|
||||
select_legacy_max_round_messages,
|
||||
get_max_round,
|
||||
select_max_round_messages,
|
||||
)
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ class RoundTruncator(truncator.Truncator):
|
||||
else:
|
||||
runner_config = query.pipeline_config.get('msg-truncate', {}).get('round', {})
|
||||
|
||||
query.messages = select_legacy_max_round_messages(
|
||||
query.messages = select_max_round_messages(
|
||||
query.messages,
|
||||
get_legacy_max_round(runner_config),
|
||||
get_max_round(runner_config),
|
||||
)
|
||||
|
||||
return query
|
||||
|
||||
Reference in New Issue
Block a user