fix(agent-runner): stabilize event context and streams

This commit is contained in:
huanghuoguoguo
2026-05-29 21:05:20 +08:00
parent 056e62aa03
commit 58e4b35770
13 changed files with 232 additions and 56 deletions

View File

@@ -229,7 +229,7 @@ class AgentRunContextBuilder:
subject_context = {
'subject_type': event.subject.subject_type,
'subject_id': event.subject.subject_id,
'subject_data': event.subject.data,
'data': event.subject.data,
}
# Build input from event

View File

@@ -5,8 +5,8 @@ Protocol v1 architecture.
"""
from __future__ import annotations
import hashlib
import typing
import time
from langbot_plugin.api.entities.builtin.pipeline import query as pipeline_query
from langbot_plugin.api.entities.builtin.platform import message as platform_message
@@ -19,7 +19,6 @@ from langbot_plugin.api.entities.builtin.agent_runner.event import (
)
from langbot_plugin.api.entities.builtin.agent_runner.input import AgentInput
from langbot_plugin.api.entities.builtin.agent_runner.delivery import DeliveryContext
from langbot_plugin.api.entities.builtin.agent_runner.trigger import AgentTrigger
from .host_models import (
AgentEventEnvelope,
@@ -305,8 +304,9 @@ class PipelineAdapter:
if isinstance(event_time, (int, float)):
event_time = int(event_time)
source_event_id = str(message_id or query.query_id)
return AgentEventContext(
event_id=str(message_id or query.query_id),
event_id=cls._build_scoped_event_id(query, source_event_id, event_time),
event_type=runner_events.MESSAGE_RECEIVED,
event_time=event_time,
source="pipeline_adapter",
@@ -314,20 +314,36 @@ class PipelineAdapter:
data=event_data,
)
@classmethod
def _build_scoped_event_id(
cls,
query: pipeline_query.Query,
source_event_id: str,
event_time: int | None,
) -> str:
"""Build a globally unique host event id from pipeline-local ids."""
launcher_type = getattr(query, 'launcher_type', None)
launcher_type_value = getattr(launcher_type, 'value', launcher_type) if launcher_type is not None else None
scope_parts = [
'pipeline_adapter',
getattr(query, 'pipeline_uuid', None),
getattr(query, 'bot_uuid', None),
launcher_type_value,
getattr(query, 'launcher_id', None),
getattr(query, 'sender_id', None),
source_event_id,
event_time,
]
scoped = '|'.join('' if part is None else str(part) for part in scope_parts)
digest = hashlib.sha256(scoped.encode('utf-8')).hexdigest()[:32]
return f'pipeline:{digest}'
@classmethod
def _build_conversation_context(
cls,
query: pipeline_query.Query,
) -> ConversationContext:
"""Build ConversationContext from Query."""
# Handle session and conversation_id
conversation_id = None
session = getattr(query, 'session', None)
if session:
conversation = getattr(session, 'using_conversation', None)
if conversation:
conversation_id = getattr(conversation, 'uuid', None)
# Handle launcher_type safely
launcher_type = getattr(query, 'launcher_type', None)
launcher_type_value = None
@@ -337,6 +353,26 @@ class PipelineAdapter:
# Handle launcher_id
launcher_id = getattr(query, 'launcher_id', None)
# Build session_id from launcher info if available
session_id = None
if launcher_type_value and launcher_id:
session_id = f'{launcher_type_value}_{launcher_id}'
# Handle session and conversation_id
conversation_id = None
session = getattr(query, 'session', None)
if session:
conversation = getattr(session, 'using_conversation', None)
if conversation:
conversation_id = getattr(conversation, 'uuid', None)
if not conversation_id:
variables = getattr(query, 'variables', None) or {}
conversation_id = variables.get('conversation_id') or None
if not conversation_id:
conversation_id = session_id
# Handle sender_id
sender_id = getattr(query, 'sender_id', None)
if sender_id is not None:
@@ -348,13 +384,8 @@ class PipelineAdapter:
# Handle pipeline_uuid
pipeline_uuid = getattr(query, 'pipeline_uuid', None)
# Build session_id from launcher info if available
session_id = None
if launcher_type_value and launcher_id:
session_id = f'{launcher_type_value}_{launcher_id}'
return ConversationContext(
conversation_id=conversation_id,
conversation_id=str(conversation_id) if conversation_id is not None else None,
thread_id=None,
launcher_type=launcher_type_value,
launcher_id=launcher_id,

View File

@@ -674,6 +674,8 @@ class RuntimeConnectionHandler(handler.Handler):
extra_args=effective_extra_args,
remove_think=remove_think,
):
if chunk is None:
continue
yield handler.ActionResponse.success(
data={
'chunk': chunk.model_dump(),

View File

@@ -171,7 +171,8 @@ class BailianChatCompletions(modelscopechatcmpl.ModelScopeChatCompletions):
# 解析 chunk 数据
if hasattr(chunk, 'choices') and chunk.choices:
choice = chunk.choices[0]
delta = choice.delta.model_dump() if hasattr(choice, 'delta') else {}
delta_obj = getattr(choice, 'delta', None)
delta = delta_obj.model_dump() if delta_obj is not None else {}
finish_reason = getattr(choice, 'finish_reason', None)
else:
delta = {}

View File

@@ -359,7 +359,8 @@ class OpenAIChatCompletions(requester.ProviderAPIRequester):
if hasattr(chunk, 'choices') and chunk.choices:
choice = chunk.choices[0]
delta = choice.delta.model_dump() if hasattr(choice, 'delta') else {}
delta_obj = getattr(choice, 'delta', None)
delta = delta_obj.model_dump() if delta_obj is not None else {}
finish_reason = getattr(choice, 'finish_reason', None)
else:

View File

@@ -132,7 +132,8 @@ class GeminiChatCompletions(chatcmpl.OpenAIChatCompletions):
if hasattr(chunk, 'choices') and chunk.choices:
choice = chunk.choices[0]
delta = choice.delta.model_dump() if hasattr(choice, 'delta') else {}
delta_obj = getattr(choice, 'delta', None)
delta = delta_obj.model_dump() if delta_obj is not None else {}
finish_reason = getattr(choice, 'finish_reason', None)
else:

View File

@@ -144,7 +144,8 @@ class JieKouAIChatCompletions(chatcmpl.OpenAIChatCompletions):
# 解析 chunk 数据
if hasattr(chunk, 'choices') and chunk.choices:
choice = chunk.choices[0]
delta = choice.delta.model_dump() if hasattr(choice, 'delta') else {}
delta_obj = getattr(choice, 'delta', None)
delta = delta_obj.model_dump() if delta_obj is not None else {}
finish_reason = getattr(choice, 'finish_reason', None)
else:
delta = {}
@@ -159,7 +160,7 @@ class JieKouAIChatCompletions(chatcmpl.OpenAIChatCompletions):
# reasoning_content = delta.get('reasoning_content', '')
if remove_think:
if delta['content'] is not None:
if delta.get('content') is not None:
if '<think>' in delta['content'] and not thinking_started and not thinking_ended:
thinking_started = True
continue

View File

@@ -391,7 +391,8 @@ class ModelScopeChatCompletions(requester.ProviderAPIRequester):
# 解析 chunk 数据
if hasattr(chunk, 'choices') and chunk.choices:
choice = chunk.choices[0]
delta = choice.delta.model_dump() if hasattr(choice, 'delta') else {}
delta_obj = getattr(choice, 'delta', None)
delta = delta_obj.model_dump() if delta_obj is not None else {}
finish_reason = getattr(choice, 'finish_reason', None)
else:
delta = {}

View File

@@ -144,7 +144,8 @@ class PPIOChatCompletions(chatcmpl.OpenAIChatCompletions):
# 解析 chunk 数据
if hasattr(chunk, 'choices') and chunk.choices:
choice = chunk.choices[0]
delta = choice.delta.model_dump() if hasattr(choice, 'delta') else {}
delta_obj = getattr(choice, 'delta', None)
delta = delta_obj.model_dump() if delta_obj is not None else {}
finish_reason = getattr(choice, 'finish_reason', None)
else:
delta = {}
@@ -159,7 +160,7 @@ class PPIOChatCompletions(chatcmpl.OpenAIChatCompletions):
# reasoning_content = delta.get('reasoning_content', '')
if remove_think:
if delta['content'] is not None:
if delta.get('content') is not None:
if '<think>' in delta['content'] and not thinking_started and not thinking_ended:
thinking_started = True
continue