mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-04 12:56:02 +00:00
fix(agent-runner): package context for plugin execution
This commit is contained in:
@@ -12,6 +12,7 @@ from .errors import (
|
||||
)
|
||||
from .registry import AgentRunnerRegistry
|
||||
from .context_builder import AgentRunContextBuilder
|
||||
from .context_packager import AgentContextPackager
|
||||
from .resource_builder import AgentResourceBuilder
|
||||
from .result_normalizer import AgentResultNormalizer
|
||||
from .orchestrator import AgentRunOrchestrator
|
||||
@@ -37,6 +38,7 @@ __all__ = [
|
||||
'RunnerExecutionError',
|
||||
'AgentRunnerRegistry',
|
||||
'AgentRunContextBuilder',
|
||||
'AgentContextPackager',
|
||||
'AgentResourceBuilder',
|
||||
'AgentResultNormalizer',
|
||||
'AgentRunOrchestrator',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Agent run context builder for converting Query to AgentRunContext."""
|
||||
"""Agent run context builder for provisioning AgentRunContext envelopes."""
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
@@ -11,6 +11,7 @@ from langbot_plugin.api.entities.builtin.platform import message as platform_mes
|
||||
from ...core import app
|
||||
from .descriptor import AgentRunnerDescriptor
|
||||
from .config_migration import ConfigMigration
|
||||
from .context_packager import AgentContextPackager
|
||||
from .state_store import get_state_store
|
||||
from . import events as runner_events
|
||||
|
||||
@@ -136,13 +137,13 @@ class AgentRunContextPayload(typing.TypedDict):
|
||||
|
||||
|
||||
class AgentRunContextBuilder:
|
||||
"""Builder for converting Query to AgentRunContext.
|
||||
"""Builder for provisioning AgentRunContext from a Pipeline Query.
|
||||
|
||||
Responsibilities:
|
||||
- Generate new run_id (UUID, not query id)
|
||||
- Set trigger type to 'message.received' for pipeline
|
||||
- Build conversation context from session
|
||||
- Convert messages to SDK format
|
||||
- Package and convert messages to SDK format
|
||||
- Build input from user_message and message_chain
|
||||
- Build params from query.variables with filtering
|
||||
- Build state snapshot from state_store
|
||||
@@ -165,6 +166,7 @@ class AgentRunContextBuilder:
|
||||
|
||||
def __init__(self, ap: app.Application):
|
||||
self.ap = ap
|
||||
self.context_packager = AgentContextPackager()
|
||||
|
||||
async def build_context(
|
||||
self,
|
||||
@@ -172,7 +174,7 @@ class AgentRunContextBuilder:
|
||||
descriptor: AgentRunnerDescriptor,
|
||||
resources: AgentResources,
|
||||
) -> AgentRunContextPayload:
|
||||
"""Build AgentRunContext from Query.
|
||||
"""Build AgentRunContext envelope from Query.
|
||||
|
||||
Args:
|
||||
query: Pipeline query
|
||||
@@ -205,19 +207,6 @@ class AgentRunContextBuilder:
|
||||
'pipeline_uuid': query.pipeline_uuid,
|
||||
}
|
||||
|
||||
# Build input
|
||||
input: AgentInput = self._build_input(query)
|
||||
|
||||
# Build messages
|
||||
messages = self._build_messages(query)
|
||||
|
||||
# Build params from query.variables with filtering
|
||||
params = self._build_params(query)
|
||||
|
||||
# Build state snapshot from state_store
|
||||
state_store = get_state_store()
|
||||
state: AgentRunState = state_store.build_snapshot(query, descriptor)
|
||||
|
||||
# Get runner binding config from ai.runner_config[runner_id]
|
||||
# This is Pipeline's configuration for this specific runner binding,
|
||||
# passed through AgentRunContext.config to the runner
|
||||
@@ -226,6 +215,20 @@ class AgentRunContextBuilder:
|
||||
descriptor.id,
|
||||
)
|
||||
|
||||
# Build input
|
||||
input: AgentInput = self._build_input(query)
|
||||
|
||||
# Build bounded working context window for the runner.
|
||||
packaged_context = self.context_packager.package_messages(query, runner_config)
|
||||
messages = self._build_messages(packaged_context.messages)
|
||||
|
||||
# Build params from query.variables with filtering
|
||||
params = self._build_params(query)
|
||||
|
||||
# Build state snapshot from state_store
|
||||
state_store = get_state_store()
|
||||
state: AgentRunState = state_store.build_snapshot(query, descriptor)
|
||||
|
||||
streaming_supported = await self._is_stream_output_supported(query)
|
||||
remove_think = query.pipeline_config.get('output', {}).get('misc', {}).get('remove-think', False)
|
||||
|
||||
@@ -241,6 +244,10 @@ class AgentRunContextBuilder:
|
||||
'pipeline_name': query.variables.get('_monitoring_pipeline_name', 'Unknown'),
|
||||
'streaming_supported': streaming_supported,
|
||||
'remove_think': remove_think,
|
||||
'context_packaging': {
|
||||
'policy': packaged_context.policy,
|
||||
'history': packaged_context.history,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -526,13 +533,12 @@ class AgentRunContextBuilder:
|
||||
|
||||
return prompt_messages
|
||||
|
||||
def _build_messages(self, query: pipeline_query.Query) -> list[dict[str, typing.Any]]:
|
||||
"""Build messages list from query."""
|
||||
def _build_messages(self, source_messages: list[typing.Any]) -> list[dict[str, typing.Any]]:
|
||||
"""Build messages list from packaged source messages."""
|
||||
messages: list[dict[str, typing.Any]] = []
|
||||
|
||||
if query.messages:
|
||||
for msg in query.messages:
|
||||
messages.append(msg.model_dump(mode='json'))
|
||||
for msg in source_messages:
|
||||
messages.append(msg.model_dump(mode='json'))
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
79
src/langbot/pkg/agent/runner/context_packager.py
Normal file
79
src/langbot/pkg/agent/runner/context_packager.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Agent context packaging helpers."""
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import typing
|
||||
|
||||
from langbot_plugin.api.entities.builtin.pipeline import query as pipeline_query
|
||||
|
||||
|
||||
DEFAULT_LEGACY_MAX_ROUND = 10
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class ContextPackagingResult:
|
||||
"""Packaged working context for one AgentRunner run."""
|
||||
|
||||
messages: list[typing.Any]
|
||||
policy: dict[str, typing.Any]
|
||||
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 select_legacy_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."""
|
||||
if not messages:
|
||||
return []
|
||||
|
||||
temp_messages: list[typing.Any] = []
|
||||
current_round = 0
|
||||
|
||||
for msg in messages[::-1]:
|
||||
if current_round < max_round:
|
||||
temp_messages.append(msg)
|
||||
if getattr(msg, 'role', None) == 'user':
|
||||
current_round += 1
|
||||
else:
|
||||
break
|
||||
|
||||
return temp_messages[::-1]
|
||||
|
||||
|
||||
class AgentContextPackager:
|
||||
"""Build the bounded working context for AgentRunner execution."""
|
||||
|
||||
def package_messages(
|
||||
self,
|
||||
query: pipeline_query.Query,
|
||||
runner_config: dict[str, typing.Any],
|
||||
) -> ContextPackagingResult:
|
||||
"""Package query messages using the current legacy 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)
|
||||
|
||||
return ContextPackagingResult(
|
||||
messages=packaged_messages,
|
||||
policy={
|
||||
'mode': 'legacy_max_round',
|
||||
'max_round': max_round,
|
||||
},
|
||||
history={
|
||||
'source': 'query.messages',
|
||||
'source_total_count': len(source_messages),
|
||||
'delivered_count': len(packaged_messages),
|
||||
'messages_complete': len(packaged_messages) == len(source_messages),
|
||||
},
|
||||
)
|
||||
@@ -31,7 +31,7 @@ class AgentRunOrchestrator:
|
||||
Responsibilities:
|
||||
- Resolve runner ID from pipeline config (new or old format)
|
||||
- Get runner descriptor from registry
|
||||
- Build AgentRunContext from Query
|
||||
- Provision AgentRunContext envelope from Query
|
||||
- Build AgentResources with permission filtering
|
||||
- Invoke plugin runtime RUN_AGENT action
|
||||
- Normalize AgentRunResult to Pipeline messages
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from .. import stage, entities
|
||||
from . import truncator
|
||||
from ...utils import importutil
|
||||
from ...agent.runner.config_migration import ConfigMigration
|
||||
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
|
||||
from . import truncators
|
||||
|
||||
@@ -30,6 +31,9 @@ class ConversationMessageTruncator(stage.PipelineStage):
|
||||
|
||||
async def process(self, query: pipeline_query.Query, stage_inst_name: str) -> entities.StageProcessResult:
|
||||
"""处理"""
|
||||
if ConfigMigration.resolve_runner_id(query.pipeline_config):
|
||||
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||
|
||||
query = await self.trun.truncate(query)
|
||||
|
||||
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||
|
||||
@@ -3,6 +3,10 @@ from __future__ import annotations
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
@truncator.truncator_class('round')
|
||||
@@ -11,25 +15,15 @@ class RoundTruncator(truncator.Truncator):
|
||||
|
||||
async def truncate(self, query: pipeline_query.Query) -> pipeline_query.Query:
|
||||
"""截断"""
|
||||
# max-round remains a pipeline-side trimming knob until token-budget
|
||||
# based compaction replaces this stage.
|
||||
runner_id = ConfigMigration.resolve_runner_id(query.pipeline_config)
|
||||
runner_config = ConfigMigration.resolve_runner_config(query.pipeline_config, runner_id) if runner_id else {}
|
||||
max_round = runner_config.get('max-round', 10)
|
||||
if runner_id:
|
||||
runner_config = ConfigMigration.resolve_runner_config(query.pipeline_config, runner_id)
|
||||
else:
|
||||
runner_config = query.pipeline_config.get('msg-truncate', {}).get('round', {})
|
||||
|
||||
temp_messages = []
|
||||
|
||||
current_round = 0
|
||||
|
||||
# Traverse from back to front
|
||||
for msg in query.messages[::-1]:
|
||||
if current_round < max_round:
|
||||
temp_messages.append(msg)
|
||||
if msg.role == 'user':
|
||||
current_round += 1
|
||||
else:
|
||||
break
|
||||
|
||||
query.messages = temp_messages[::-1]
|
||||
query.messages = select_legacy_max_round_messages(
|
||||
query.messages,
|
||||
get_legacy_max_round(runner_config),
|
||||
)
|
||||
|
||||
return query
|
||||
|
||||
Reference in New Issue
Block a user