mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-16 09:26:07 +00:00
refactor(agent-runner): remove protocol_version from various components and update related documentation
This commit is contained in:
committed by
huanghuoguoguo
parent
818f5926cd
commit
c859fc37bb
@@ -72,7 +72,7 @@ LangBot 不提供 host-side inline history window。简单 runner 如果需要
|
|||||||
### 4.1 History
|
### 4.1 History
|
||||||
|
|
||||||
```python
|
```python
|
||||||
await api.history.page(conversation_id=ctx.context.conversation_id,
|
await api.history_page(conversation_id=ctx.context.conversation_id,
|
||||||
before_cursor=ctx.context.latest_cursor,
|
before_cursor=ctx.context.latest_cursor,
|
||||||
limit=50, direction="backward", include_artifacts=False)
|
limit=50, direction="backward", include_artifacts=False)
|
||||||
```
|
```
|
||||||
@@ -92,7 +92,7 @@ class HistoryPage(BaseModel):
|
|||||||
### 4.2 Search
|
### 4.2 Search
|
||||||
|
|
||||||
```python
|
```python
|
||||||
await api.history.search(query="用户之前提到的数据库连接信息",
|
await api.history_search(query="用户之前提到的数据库连接信息",
|
||||||
filters={"conversation_id": ..., "event_types": ["message.received"]},
|
filters={"conversation_id": ..., "event_types": ["message.received"]},
|
||||||
top_k=10)
|
top_k=10)
|
||||||
```
|
```
|
||||||
@@ -154,4 +154,4 @@ Agent 自管 context 不代表无限制访问。LangBot 仍必须控制:每次
|
|||||||
|
|
||||||
官方 runner 插件可以把状态寄宿在 LangBot,但必须和第三方 runner 一样通过公开 Host API 消费。LangBot core 不内置官方 agent 的业务流程(prompt 组装、tool loop、RAG 编排、summary/compaction、"local-agent 专用"状态字段)。
|
官方 runner 插件可以把状态寄宿在 LangBot,但必须和第三方 runner 一样通过公开 Host API 消费。LangBot core 不内置官方 agent 的业务流程(prompt 组装、tool loop、RAG 编排、summary/compaction、"local-agent 专用"状态字段)。
|
||||||
|
|
||||||
官方 local-agent 应作为"依附 LangBot 基础设施的复杂 runner 参考实现":transcript/history 通过 `api.history` 读取,summary/checkpoint/外部 session id/用户偏好通过 `api.state` 或 `api.storage` 保存,图片/文件/工具大结果通过 `api.artifacts` 读取,模型/工具/知识库通过 `api.models` / `api.tools` / `api.knowledge` 调用。这样 LangBot 保持为通用 agent host,不变成内置 agent 框架。具体迁移要求见 [OFFICIAL_RUNNER_PLUGINS.md](./OFFICIAL_RUNNER_PLUGINS.md)。
|
官方 local-agent 应作为"依附 LangBot 基础设施的复杂 runner 参考实现":transcript/history 通过 `api.history_page()` / `api.history_search()` 读取,summary/checkpoint/外部 session id/用户偏好通过 `api.state_get()` / `api.state_set()` 或 storage 方法保存,图片/文件/工具大结果通过 `api.artifact_metadata()` / `api.artifact_read_range()` 读取,模型/工具/知识库通过 `api.invoke_llm()` / `api.call_tool()` / `api.retrieve_knowledge()` 调用。这样 LangBot 保持为通用 agent host,不变成内置 agent 框架。具体迁移要求见 [OFFICIAL_RUNNER_PLUGINS.md](./OFFICIAL_RUNNER_PLUGINS.md)。
|
||||||
|
|||||||
@@ -137,14 +137,13 @@ class AgentRunnerDescriptor(BaseModel):
|
|||||||
source: Literal["plugin"]
|
source: Literal["plugin"]
|
||||||
label: I18nObject
|
label: I18nObject
|
||||||
description: I18nObject | None = None
|
description: I18nObject | None = None
|
||||||
protocol_version: str = "1"
|
|
||||||
capabilities: AgentRunnerCapabilities # 见 PROTOCOL_V1 §4.3
|
capabilities: AgentRunnerCapabilities # 见 PROTOCOL_V1 §4.3
|
||||||
permissions: AgentRunnerPermissions # 见 PROTOCOL_V1 §4.4
|
permissions: AgentRunnerPermissions # 见 PROTOCOL_V1 §4.4
|
||||||
config_schema: list[DynamicFormItemSchema]
|
config_schema: list[DynamicFormItemSchema]
|
||||||
plugin: PluginRef | None = None
|
plugin: PluginRef | None = None
|
||||||
```
|
```
|
||||||
|
|
||||||
职责:调用 `plugin_connector.list_agent_runners()` 拉取 runner、校验 manifest(`kind == AgentRunner`、`metadata.name/label` 存在、`protocol_version` 兼容、`spec.*` 类型正确)、输出 descriptor、缓存 discovery 结果并提供 `refresh()`。单个插件 manifest 失败只记 warning,不影响其它 runner。`plugin:author/name/runner` 是稳定 id 格式;插件实例边界见 PROTOCOL_V1 §13。
|
职责:调用 `plugin_connector.list_agent_runners()` 拉取 runner、校验 manifest(`kind == AgentRunner`、`metadata.name/label` 存在、`spec.*` 类型正确)、输出 descriptor、缓存 discovery 结果并提供 `refresh()`。单个插件 manifest 失败只记 warning,不影响其它 runner。`plugin:author/name/runner` 是稳定 id 格式;插件实例边界见 PROTOCOL_V1 §13。
|
||||||
|
|
||||||
Host 内置 runner / adapter 不能作为 `AgentRunnerDescriptor.source` 绕过插件
|
Host 内置 runner / adapter 不能作为 `AgentRunnerDescriptor.source` 绕过插件
|
||||||
runtime、`run_id`、`ctx.resources` 和 `AgentRunAPIProxy` 权限链。若需要
|
runtime、`run_id`、`ctx.resources` 和 `AgentRunAPIProxy` 权限链。若需要
|
||||||
@@ -225,16 +224,7 @@ LangBot 可提供 host-owned state 让 runner 寄宿状态(conversation / acto
|
|||||||
|
|
||||||
三类数据与 working context 的边界、读取约束见 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md)。AgentRunner 可读取这些能力,但不被迫使用 LangBot 作为唯一记忆系统。
|
三类数据与 working context 的边界、读取约束见 [AGENT_CONTEXT_PROTOCOL.md](./AGENT_CONTEXT_PROTOCOL.md)。AgentRunner 可读取这些能力,但不被迫使用 LangBot 作为唯一记忆系统。
|
||||||
|
|
||||||
### 4.8 Prompt / Instruction Package(占位)
|
### 4.8 External harness resource projection
|
||||||
|
|
||||||
当前 Query 入口不把 preprocessing 后的有效 prompt 放进 adapter metadata。目标形态是 Host 保存或生成一个 run-scoped instruction package,runner 通过 Host API 拉取:
|
|
||||||
|
|
||||||
- Host 记录静态绑定 prompt、host hook / user plugin 产生的 instruction fragment、来源和审计信息。
|
|
||||||
- `ctx.context.available_apis` 增加 `prompt_get` 能力位表示拉取是否可用。
|
|
||||||
- Runner 拉取后仍由自己决定如何与 history、RAG、tool 结果、memory 和当前输入组装最终 prompt。
|
|
||||||
- Host 不实现通用 agentic prompt assembler,也不把 Query entry adapter prompt 作为长期业务输入契约。
|
|
||||||
|
|
||||||
### 4.9 External harness resource projection
|
|
||||||
|
|
||||||
Claude Code、Codex、Kimi Code 等外部 harness runner 可能不直接调用 LangBot 的 model/tool loop,而是把 LangBot 事件和授权资源句柄投影到自己的 harness 执行。Host 侧仍保持统一边界:Host 负责构造 event-first context、资源授权、state/storage、EventLog/Transcript/ArtifactStore 和审计;Host 或 binding policy 决定哪些 MCP bridge、skill-backed tool、artifact、history/state 句柄可投影给 runner;runner plugin 把 scoped projection 转成目标 harness 可消费形式;所有 LangBot 资源访问必须经 SDK runtime / `AgentRunAPIProxy` / SDK-owned MCP bridge 转发并接受 Host 校验;外部 harness 负责自己的 native session、tool loop、压缩、权限模式和 resume,但不能用 native tools 绕过 Host 授权。
|
Claude Code、Codex、Kimi Code 等外部 harness runner 可能不直接调用 LangBot 的 model/tool loop,而是把 LangBot 事件和授权资源句柄投影到自己的 harness 执行。Host 侧仍保持统一边界:Host 负责构造 event-first context、资源授权、state/storage、EventLog/Transcript/ArtifactStore 和审计;Host 或 binding policy 决定哪些 MCP bridge、skill-backed tool、artifact、history/state 句柄可投影给 runner;runner plugin 把 scoped projection 转成目标 harness 可消费形式;所有 LangBot 资源访问必须经 SDK runtime / `AgentRunAPIProxy` / SDK-owned MCP bridge 转发并接受 Host 校验;外部 harness 负责自己的 native session、tool loop、压缩、权限模式和 resume,但不能用 native tools 绕过 Host 授权。
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ metadata:
|
|||||||
en_US: Run a Dify application as a LangBot AgentRunner.
|
en_US: Run a Dify application as a LangBot AgentRunner.
|
||||||
zh_Hans: 将 Dify 应用作为 LangBot AgentRunner 运行。
|
zh_Hans: 将 Dify 应用作为 LangBot AgentRunner 运行。
|
||||||
spec:
|
spec:
|
||||||
protocol_version: "1"
|
|
||||||
config: []
|
config: []
|
||||||
capabilities: # 字段语义见 PROTOCOL_V1 §4.3
|
capabilities: # 字段语义见 PROTOCOL_V1 §4.3
|
||||||
streaming: true
|
streaming: true
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ Agent / binding 的持久化形态。
|
|||||||
|
|
||||||
外部 harness runner(Claude Code、Codex、Kimi Code 等)也是 `AgentRunner`:它们消费 event-first `AgentRunContext`、返回 `AgentRunResult`,并通过 Host 授权的 state/storage/artifact API 保存跨轮次指针。它们内部可以继续使用自己的 session、tool loop、MCP、上下文压缩和权限模型。
|
外部 harness runner(Claude Code、Codex、Kimi Code 等)也是 `AgentRunner`:它们消费 event-first `AgentRunContext`、返回 `AgentRunResult`,并通过 Host 授权的 state/storage/artifact API 保存跨轮次指针。它们内部可以继续使用自己的 session、tool loop、MCP、上下文压缩和权限模型。
|
||||||
|
|
||||||
## 3. 版本协商
|
## 3. 协议演进
|
||||||
|
|
||||||
- `AgentRunnerManifest.protocol_version` 声明 runner 实现的协议大版本,当前为 `"1"`。
|
当前 AgentRunner 合同不暴露显式 `protocol_version` 字段。协议演进先按字段级兼容规则处理:
|
||||||
- `AgentRuntimeContext.protocol_version`(`ctx.runtime.protocol_version`)声明 Host 下发的协议大版本。
|
|
||||||
- Host 发现 runner 时校验 `protocol_version` 兼容性;不兼容的 runner 不进入可用列表,只记 warning。
|
- 新增可选字段保持向后兼容。
|
||||||
- 字段级演进规则:新增可选字段不提升大版本;删除或改语义需要提升大版本。
|
- 删除字段或改变既有字段语义,需要在 SDK 发布前完成;发布后应走新的显式兼容方案。
|
||||||
- 结果流演进:Host **必须忽略未知 result type 并记录 warning**(除非该 type 明确要求强校验)。新增 result type 不提升大版本。
|
- 结果流演进:Host **必须忽略未知 result type 并记录 warning**(除非该 type 明确要求强校验)。新增 result type 不提升大版本。
|
||||||
|
|
||||||
## 4. Discovery 协议
|
## 4. Discovery 协议
|
||||||
@@ -68,7 +68,6 @@ class AgentRunnerManifest(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
label: I18nObject
|
label: I18nObject
|
||||||
description: I18nObject | None = None
|
description: I18nObject | None = None
|
||||||
protocol_version: str = "1"
|
|
||||||
capabilities: AgentRunnerCapabilities
|
capabilities: AgentRunnerCapabilities
|
||||||
permissions: AgentRunnerPermissions
|
permissions: AgentRunnerPermissions
|
||||||
context: AgentRunnerContextPolicy
|
context: AgentRunnerContextPolicy
|
||||||
@@ -324,7 +323,6 @@ class ContextAPICapabilities(BaseModel):
|
|||||||
```python
|
```python
|
||||||
class AgentRuntimeContext(BaseModel):
|
class AgentRuntimeContext(BaseModel):
|
||||||
host: str = "langbot"
|
host: str = "langbot"
|
||||||
protocol_version: str = "1"
|
|
||||||
langbot_version: str | None = None
|
langbot_version: str | None = None
|
||||||
trace_id: str
|
trace_id: str
|
||||||
deadline_at: float | None = None
|
deadline_at: float | None = None
|
||||||
@@ -548,38 +546,36 @@ Host 必须校验 `state.updated` 的 scope、key、value 大小和 JSON 可序
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
# Model
|
# Model
|
||||||
await api.models.invoke(model_id, messages, tools=None, extra_args=None)
|
await api.invoke_llm(model_id, messages, funcs=None, extra_args=None)
|
||||||
await api.models.stream(model_id, messages, tools=None, extra_args=None)
|
async for chunk in api.invoke_llm_stream(model_id, messages, funcs=None, extra_args=None):
|
||||||
await api.models.rerank(model_id, query, documents, top_k=None)
|
...
|
||||||
|
await api.invoke_rerank(rerank_model_id, query, documents, top_k=None)
|
||||||
|
|
||||||
# Tool
|
# Tool
|
||||||
await api.tools.get_detail(tool_name)
|
await api.get_tool_detail(tool_name)
|
||||||
await api.tools.call(tool_name, parameters)
|
await api.call_tool(tool_name, parameters)
|
||||||
|
|
||||||
# Knowledge
|
# Knowledge
|
||||||
await api.knowledge.retrieve(kb_id, query_text, top_k=5, filters=None)
|
await api.retrieve_knowledge(kb_id, query_text, top_k=5, filters=None)
|
||||||
|
|
||||||
# History(返回 Transcript projection,不返回原始平台 payload)
|
# History(返回 Transcript projection,不返回原始平台 payload)
|
||||||
await api.history.page(conversation_id=None, before_cursor=None, after_cursor=None,
|
await api.history_page(conversation_id=None, before_cursor=None, after_cursor=None,
|
||||||
limit=50, direction="backward", include_artifacts=False)
|
limit=50, direction="backward", include_artifacts=False)
|
||||||
await api.history.search(query, filters=None, top_k=10)
|
await api.history_search(query, filters=None, top_k=10)
|
||||||
|
|
||||||
# Event(返回稳定 event envelope 或受限 raw ref,不默认返回大 payload)
|
# Event(返回稳定 event envelope 或受限 raw ref,不默认返回大 payload)
|
||||||
await api.events.get(event_id)
|
await api.event_get(event_id)
|
||||||
await api.events.page(before_cursor=None, limit=50)
|
await api.event_page(before_cursor=None, limit=50)
|
||||||
|
|
||||||
# Artifact(必须支持大小限制、MIME 校验、过期时间和授权范围)
|
# Artifact(必须支持大小限制、MIME 校验、过期时间和授权范围)
|
||||||
await api.artifacts.metadata(artifact_id)
|
await api.artifact_metadata(artifact_id)
|
||||||
await api.artifacts.read_range(artifact_id, offset=0, length=65536)
|
await api.artifact_read_range(artifact_id, offset=0, length=65536)
|
||||||
await api.artifacts.open_stream(artifact_id)
|
|
||||||
|
|
||||||
# State / Storage
|
# State / Storage
|
||||||
await api.state.get(scope, key); await api.state.set(scope, key, value); await api.state.delete(scope, key)
|
await api.state_get(scope, key); await api.state_set(scope, key, value); await api.state_delete(scope, key)
|
||||||
await api.storage.get(area, key); await api.storage.set(area, key, value)
|
await api.state_list(scope, prefix=None)
|
||||||
await api.storage.delete(area, key); await api.storage.list(area, prefix=None)
|
await api.get_plugin_storage(key); await api.set_plugin_storage(key, value); await api.delete_plugin_storage(key)
|
||||||
|
await api.get_workspace_storage(key); await api.set_workspace_storage(key, value); await api.delete_workspace_storage(key)
|
||||||
# Platform(受限能力,默认不开放,需 manifest + binding policy + 用户审批同时允许)
|
|
||||||
await api.platform.request_action(action, target, payload)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`state` 与 `storage` 的建议边界:`state` 放小型 JSON(conversation / actor / runner / binding),`storage` 放 blob 或较大数据(插件私有数据、workspace 数据、checkpoint)。
|
`state` 与 `storage` 的建议边界:`state` 放小型 JSON(conversation / actor / runner / binding),`storage` 放 blob 或较大数据(插件私有数据、workspace 数据、checkpoint)。
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ class AgentRuntimeContext(typing.TypedDict):
|
|||||||
"""Agent runtime context."""
|
"""Agent runtime context."""
|
||||||
|
|
||||||
langbot_version: str | None
|
langbot_version: str | None
|
||||||
protocol_version: str
|
|
||||||
trace_id: str | None
|
trace_id: str | None
|
||||||
deadline_at: float | None
|
deadline_at: float | None
|
||||||
metadata: dict[str, typing.Any]
|
metadata: dict[str, typing.Any]
|
||||||
@@ -272,7 +271,6 @@ class AgentRunContextBuilder:
|
|||||||
# Build runtime context
|
# Build runtime context
|
||||||
runtime: AgentRuntimeContext = {
|
runtime: AgentRuntimeContext = {
|
||||||
'langbot_version': self.ap.ver_mgr.get_current_version(),
|
'langbot_version': self.ap.ver_mgr.get_current_version(),
|
||||||
'protocol_version': descriptor.protocol_version,
|
|
||||||
'trace_id': run_id,
|
'trace_id': run_id,
|
||||||
'deadline_at': self._build_deadline_from_binding(binding),
|
'deadline_at': self._build_deadline_from_binding(binding),
|
||||||
'metadata': {
|
'metadata': {
|
||||||
@@ -424,6 +422,5 @@ class AgentRunContextBuilder:
|
|||||||
'artifact_read': artifact_read_enabled,
|
'artifact_read': artifact_read_enabled,
|
||||||
'state': state_enabled,
|
'state': state_enabled,
|
||||||
'storage': True,
|
'storage': True,
|
||||||
'prompt_get': False,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,9 +36,6 @@ class AgentRunnerDescriptor(pydantic.BaseModel):
|
|||||||
plugin_version: str | None = None
|
plugin_version: str | None = None
|
||||||
"""Optional plugin version"""
|
"""Optional plugin version"""
|
||||||
|
|
||||||
protocol_version: str = '1'
|
|
||||||
"""SDK protocol version, default '1'"""
|
|
||||||
|
|
||||||
config_schema: list[dict[str, typing.Any]] = []
|
config_schema: list[dict[str, typing.Any]] = []
|
||||||
"""Configuration schema using DynamicForm format"""
|
"""Configuration schema using DynamicForm format"""
|
||||||
|
|
||||||
|
|||||||
@@ -97,8 +97,6 @@ class AgentRunOrchestrator:
|
|||||||
session_query_id = adapter_context.get('query_id')
|
session_query_id = adapter_context.get('query_id')
|
||||||
if 'params' in adapter_context:
|
if 'params' in adapter_context:
|
||||||
context['adapter']['extra']['params'] = adapter_context['params']
|
context['adapter']['extra']['params'] = adapter_context['params']
|
||||||
if adapter_context.get('prompt_get'):
|
|
||||||
context['context']['available_apis']['prompt_get'] = True
|
|
||||||
|
|
||||||
state_context = build_state_context(event, binding, descriptor)
|
state_context = build_state_context(event, binding, descriptor)
|
||||||
run_id = context['run_id']
|
run_id = context['run_id']
|
||||||
|
|||||||
@@ -149,7 +149,6 @@ class QueryEntryAdapter:
|
|||||||
return {
|
return {
|
||||||
'params': cls.build_params(query),
|
'params': cls.build_params(query),
|
||||||
'query_id': getattr(query, 'query_id', None),
|
'query_id': getattr(query, 'query_id', None),
|
||||||
'prompt_get': cls._has_effective_prompt(query),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -187,12 +186,6 @@ class QueryEntryAdapter:
|
|||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _has_effective_prompt(cls, query: pipeline_query.Query) -> bool:
|
|
||||||
prompt = getattr(query, 'prompt', None)
|
|
||||||
messages = getattr(prompt, 'messages', None) if prompt is not None else None
|
|
||||||
return isinstance(messages, list)
|
|
||||||
|
|
||||||
# Private helper methods
|
# Private helper methods
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class AgentRunnerRegistry:
|
|||||||
runner_data: Raw runner data from plugin runtime with fields:
|
runner_data: Raw runner data from plugin runtime with fields:
|
||||||
- plugin_author, plugin_name, runner_name
|
- plugin_author, plugin_name, runner_name
|
||||||
- manifest (full component manifest dict)
|
- manifest (full component manifest dict)
|
||||||
- protocol_version, capabilities, permissions, config (extracted from spec)
|
- capabilities, permissions, config (extracted from spec)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
AgentRunnerDescriptor if valid, None if invalid
|
AgentRunnerDescriptor if valid, None if invalid
|
||||||
@@ -114,7 +114,6 @@ class AgentRunnerRegistry:
|
|||||||
|
|
||||||
# SDK now provides these directly extracted from spec. Fall back to
|
# SDK now provides these directly extracted from spec. Fall back to
|
||||||
# manifest.spec for older runtimes/tests that return the raw manifest.
|
# manifest.spec for older runtimes/tests that return the raw manifest.
|
||||||
protocol_version = runner_data.get('protocol_version') or spec.get('protocol_version', '1')
|
|
||||||
config_schema = runner_data.get('config') or spec.get('config', [])
|
config_schema = runner_data.get('config') or spec.get('config', [])
|
||||||
capabilities = runner_data.get('capabilities') or spec.get('capabilities', {})
|
capabilities = runner_data.get('capabilities') or spec.get('capabilities', {})
|
||||||
permissions = runner_data.get('permissions') or spec.get('permissions', {})
|
permissions = runner_data.get('permissions') or spec.get('permissions', {})
|
||||||
@@ -136,7 +135,6 @@ class AgentRunnerRegistry:
|
|||||||
plugin_name=plugin_name,
|
plugin_name=plugin_name,
|
||||||
runner_name=runner_name,
|
runner_name=runner_name,
|
||||||
plugin_version=runner_data.get('plugin_version'),
|
plugin_version=runner_data.get('plugin_version'),
|
||||||
protocol_version=protocol_version,
|
|
||||||
config_schema=config_schema,
|
config_schema=config_schema,
|
||||||
capabilities=capabilities,
|
capabilities=capabilities,
|
||||||
permissions=permissions,
|
permissions=permissions,
|
||||||
|
|||||||
@@ -378,25 +378,6 @@ def _resolve_remove_think(data: dict[str, Any], query: Any | None) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _dump_prompt_messages(query: Any) -> list[dict[str, Any]]:
|
|
||||||
"""Serialize the current effective prompt from a cached Query."""
|
|
||||||
prompt = getattr(query, 'prompt', None)
|
|
||||||
messages = getattr(prompt, 'messages', None) if prompt is not None else None
|
|
||||||
if not isinstance(messages, list):
|
|
||||||
return []
|
|
||||||
|
|
||||||
dumped: list[dict[str, Any]] = []
|
|
||||||
for message in messages:
|
|
||||||
if hasattr(message, 'model_dump'):
|
|
||||||
try:
|
|
||||||
dumped.append(message.model_dump(mode='json'))
|
|
||||||
except TypeError:
|
|
||||||
dumped.append(message.model_dump())
|
|
||||||
elif isinstance(message, dict):
|
|
||||||
dumped.append(message)
|
|
||||||
return dumped
|
|
||||||
|
|
||||||
|
|
||||||
def _merge_model_extra_args(model: Any, call_extra_args: Any) -> dict[str, Any]:
|
def _merge_model_extra_args(model: Any, call_extra_args: Any) -> dict[str, Any]:
|
||||||
"""Merge persisted model extra_args with action-level overrides."""
|
"""Merge persisted model extra_args with action-level overrides."""
|
||||||
merged: dict[str, Any] = {}
|
merged: dict[str, Any] = {}
|
||||||
@@ -1250,7 +1231,7 @@ class RuntimeConnectionHandler(handler.Handler):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return _make_rag_error_response(e, 'VectorStoreError', collection_id=collection_id)
|
return _make_rag_error_response(e, 'VectorStoreError', collection_id=collection_id)
|
||||||
|
|
||||||
@self.action(PluginToRuntimeAction.GET_KNOWLEDGE_FILE_STREAM)
|
@self.action(PluginToRuntimeAction.GET_KNOWLEDEGE_FILE_STREAM)
|
||||||
async def get_knowledge_file_stream(data: dict[str, Any]) -> handler.ActionResponse:
|
async def get_knowledge_file_stream(data: dict[str, Any]) -> handler.ActionResponse:
|
||||||
storage_path = data['storage_path']
|
storage_path = data['storage_path']
|
||||||
try:
|
try:
|
||||||
@@ -1458,32 +1439,6 @@ class RuntimeConnectionHandler(handler.Handler):
|
|||||||
|
|
||||||
# ================= Agent History/Event APIs =================
|
# ================= Agent History/Event APIs =================
|
||||||
|
|
||||||
@self.action(PluginToRuntimeAction.PROMPT_GET)
|
|
||||||
async def prompt_get(data: dict[str, Any]) -> handler.ActionResponse:
|
|
||||||
"""Return the post-preprocessing effective prompt for a query-backed run."""
|
|
||||||
run_id = data.get('run_id')
|
|
||||||
caller_plugin_identity = data.get('caller_plugin_identity')
|
|
||||||
|
|
||||||
if not run_id:
|
|
||||||
return handler.ActionResponse.error(message='run_id is required')
|
|
||||||
|
|
||||||
session, error = await _validate_agent_run_session(
|
|
||||||
run_id,
|
|
||||||
caller_plugin_identity,
|
|
||||||
self.ap,
|
|
||||||
'Prompt get',
|
|
||||||
)
|
|
||||||
if error:
|
|
||||||
return error
|
|
||||||
|
|
||||||
query = _resolve_action_query(data, session, self.ap)
|
|
||||||
if query is None:
|
|
||||||
return handler.ActionResponse.error(
|
|
||||||
message='Prompt get is only available for query-backed agent runs',
|
|
||||||
)
|
|
||||||
|
|
||||||
return handler.ActionResponse.success(data={'prompt': _dump_prompt_messages(query)})
|
|
||||||
|
|
||||||
@self.action(PluginToRuntimeAction.HISTORY_PAGE)
|
@self.action(PluginToRuntimeAction.HISTORY_PAGE)
|
||||||
async def history_page(data: dict[str, Any]) -> handler.ActionResponse:
|
async def history_page(data: dict[str, Any]) -> handler.ActionResponse:
|
||||||
"""Page through transcript history for a conversation.
|
"""Page through transcript history for a conversation.
|
||||||
@@ -2236,7 +2191,6 @@ class RuntimeConnectionHandler(handler.Handler):
|
|||||||
- runner_name
|
- runner_name
|
||||||
- runner_description
|
- runner_description
|
||||||
- manifest
|
- manifest
|
||||||
- protocol_version
|
|
||||||
- capabilities
|
- capabilities
|
||||||
- permissions
|
- permissions
|
||||||
- config
|
- config
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class RAGRuntimeService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def get_file_stream(self, storage_path: str) -> bytes:
|
async def get_file_stream(self, storage_path: str) -> bytes:
|
||||||
"""Handle GET_KNOWLEDGE_FILE_STREAM action.
|
"""Handle GET_KNOWLEDEGE_FILE_STREAM action.
|
||||||
|
|
||||||
Uses the storage manager abstraction to load file content,
|
Uses the storage manager abstraction to load file content,
|
||||||
regardless of the underlying storage provider.
|
regardless of the underlying storage provider.
|
||||||
|
|||||||
@@ -159,4 +159,4 @@ class TestBuildAdapterContext:
|
|||||||
|
|
||||||
context = QueryEntryAdapter.build_adapter_context(query, binding=None)
|
context = QueryEntryAdapter.build_adapter_context(query, binding=None)
|
||||||
|
|
||||||
assert context == {'params': {}, 'query_id': 123, 'prompt_get': False}
|
assert context == {'params': {}, 'query_id': 123}
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ class TestContextAccessStateDetermination:
|
|||||||
"""Create mock runner descriptor."""
|
"""Create mock runner descriptor."""
|
||||||
descriptor = MagicMock()
|
descriptor = MagicMock()
|
||||||
descriptor.id = 'plugin:test/runner/default'
|
descriptor.id = 'plugin:test/runner/default'
|
||||||
descriptor.protocol_version = '1.0'
|
|
||||||
descriptor.permissions = {}
|
descriptor.permissions = {}
|
||||||
return descriptor
|
return descriptor
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ class TestContextValidation:
|
|||||||
"""Create a mock runner descriptor."""
|
"""Create a mock runner descriptor."""
|
||||||
descriptor = MagicMock()
|
descriptor = MagicMock()
|
||||||
descriptor.id = "plugin:test/plugin/runner"
|
descriptor.id = "plugin:test/plugin/runner"
|
||||||
descriptor.protocol_version = "1"
|
|
||||||
descriptor.permissions = {
|
descriptor.permissions = {
|
||||||
'history': ['page', 'search'],
|
'history': ['page', 'search'],
|
||||||
'events': ['get', 'page'],
|
'events': ['get', 'page'],
|
||||||
@@ -145,8 +144,7 @@ class TestContextValidation:
|
|||||||
assert isinstance(validated.resources, AgentResources)
|
assert isinstance(validated.resources, AgentResources)
|
||||||
assert validated.runtime is not None
|
assert validated.runtime is not None
|
||||||
assert isinstance(validated.runtime, AgentRuntimeContext)
|
assert isinstance(validated.runtime, AgentRuntimeContext)
|
||||||
assert validated.runtime.protocol_version == "1"
|
assert "protocol_version" not in validated.runtime.model_dump()
|
||||||
assert "protocol_version" in validated.runtime.model_dump()
|
|
||||||
assert "sdk_protocol_version" not in validated.runtime.model_dump()
|
assert "sdk_protocol_version" not in validated.runtime.model_dump()
|
||||||
assert "sdk_protocol_version" not in context_dict["runtime"]
|
assert "sdk_protocol_version" not in context_dict["runtime"]
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ def make_descriptor() -> AgentRunnerDescriptor:
|
|||||||
plugin_author="langbot",
|
plugin_author="langbot",
|
||||||
plugin_name="local-agent",
|
plugin_name="local-agent",
|
||||||
runner_name="default",
|
runner_name="default",
|
||||||
protocol_version="1",
|
|
||||||
capabilities={
|
capabilities={
|
||||||
"streaming": True,
|
"streaming": True,
|
||||||
"tool_calling": True,
|
"tool_calling": True,
|
||||||
@@ -591,7 +590,7 @@ class TestQueryEntryAdapterParams:
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_prompt_not_pushed_into_adapter_extra(self, clean_agent_state):
|
async def test_prompt_not_pushed_into_adapter_extra(self, clean_agent_state):
|
||||||
"""Pipeline prompt is not pushed into adapter.extra."""
|
"""Pipeline prompt is not pushed into adapter.extra or exposed via prompt_get."""
|
||||||
from langbot_plugin.api.entities.builtin.provider import prompt as provider_prompt
|
from langbot_plugin.api.entities.builtin.provider import prompt as provider_prompt
|
||||||
|
|
||||||
db_engine = clean_agent_state
|
db_engine = clean_agent_state
|
||||||
@@ -621,7 +620,7 @@ class TestQueryEntryAdapterParams:
|
|||||||
context = plugin_connector.contexts[0]
|
context = plugin_connector.contexts[0]
|
||||||
assert "prompt" not in context
|
assert "prompt" not in context
|
||||||
assert "prompt" not in context["adapter"]["extra"]
|
assert "prompt" not in context["adapter"]["extra"]
|
||||||
assert context["context"]["available_apis"]["prompt_get"] is True
|
assert "prompt_get" not in context["context"]["available_apis"]
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_params_filtering_keeps_public_param(self, clean_agent_state):
|
async def test_params_filtering_keeps_public_param(self, clean_agent_state):
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ class FakeApplication:
|
|||||||
'label': {'en_US': 'Local Agent'},
|
'label': {'en_US': 'Local Agent'},
|
||||||
},
|
},
|
||||||
'spec': {
|
'spec': {
|
||||||
'protocol_version': '1',
|
|
||||||
'config': [],
|
'config': [],
|
||||||
'capabilities': {'streaming': True},
|
'capabilities': {'streaming': True},
|
||||||
'permissions': {},
|
'permissions': {},
|
||||||
@@ -63,7 +62,6 @@ class FakeApplication:
|
|||||||
'label': {'en_US': 'Custom Agent'},
|
'label': {'en_US': 'Custom Agent'},
|
||||||
},
|
},
|
||||||
'spec': {
|
'spec': {
|
||||||
'protocol_version': '1',
|
|
||||||
'config': [{'name': 'param1', 'type': 'string'}],
|
'config': [{'name': 'param1', 'type': 'string'}],
|
||||||
'capabilities': {},
|
'capabilities': {},
|
||||||
'permissions': {},
|
'permissions': {},
|
||||||
@@ -266,7 +264,7 @@ class TestDescriptorValidation:
|
|||||||
|
|
||||||
assert descriptor.id == 'plugin:test/my-runner/default'
|
assert descriptor.id == 'plugin:test/my-runner/default'
|
||||||
assert descriptor.get_plugin_id() == 'test/my-runner'
|
assert descriptor.get_plugin_id() == 'test/my-runner'
|
||||||
assert descriptor.protocol_version == '1'
|
assert 'protocol_version' not in AgentRunnerDescriptor.model_fields
|
||||||
|
|
||||||
def test_descriptor_capabilities(self):
|
def test_descriptor_capabilities(self):
|
||||||
"""Descriptor capability helper methods."""
|
"""Descriptor capability helper methods."""
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ def make_descriptor():
|
|||||||
plugin_author='langbot',
|
plugin_author='langbot',
|
||||||
plugin_name='local-agent',
|
plugin_name='local-agent',
|
||||||
runner_name='default',
|
runner_name='default',
|
||||||
protocol_version='1',
|
|
||||||
capabilities={'streaming': True},
|
capabilities={'streaming': True},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ def make_descriptor(runner_id: str = 'plugin:test/my-runner/default') -> AgentRu
|
|||||||
plugin_author='test',
|
plugin_author='test',
|
||||||
plugin_name='my-runner',
|
plugin_name='my-runner',
|
||||||
runner_name='default',
|
runner_name='default',
|
||||||
protocol_version='1',
|
|
||||||
capabilities={'streaming': True},
|
capabilities={'streaming': True},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user