agent-runtime-sdk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_runtime/__init__.py +84 -0
- agent_runtime/builder.py +317 -0
- agent_runtime/config/__init__.py +29 -0
- agent_runtime/config/definitions.py +144 -0
- agent_runtime/config/policies.py +63 -0
- agent_runtime/config/storage.py +117 -0
- agent_runtime/context.py +10 -0
- agent_runtime/definitions.py +33 -0
- agent_runtime/discovery.py +16 -0
- agent_runtime/exceptions.py +74 -0
- agent_runtime/mcp/__init__.py +28 -0
- agent_runtime/mcp/discovery.py +146 -0
- agent_runtime/mcp/metadata.py +68 -0
- agent_runtime/mcp/utils.py +52 -0
- agent_runtime/model_registry.py +40 -0
- agent_runtime/plugins/__init__.py +4 -0
- agent_runtime/plugins/base.py +90 -0
- agent_runtime/plugins/default.py +19 -0
- agent_runtime/plugins/instructions.py +38 -0
- agent_runtime/plugins/loader.py +59 -0
- agent_runtime/policies.py +15 -0
- agent_runtime/runtime.py +110 -0
- agent_runtime/runtime_engine/__init__.py +22 -0
- agent_runtime/runtime_engine/a2a_bridge.py +190 -0
- agent_runtime/runtime_engine/a2a_task_io.py +165 -0
- agent_runtime/runtime_engine/agent_build.py +315 -0
- agent_runtime/runtime_engine/context.py +469 -0
- agent_runtime/runtime_engine/loading.py +170 -0
- agent_runtime/runtime_engine/observability.py +154 -0
- agent_runtime/runtime_engine/policy_registry.py +98 -0
- agent_runtime/runtime_engine/protocol_tools.py +94 -0
- agent_runtime/runtime_engine/task_flow.py +897 -0
- agent_runtime/runtime_engine/tool_flow.py +332 -0
- agent_runtime/sdk_agent.py +548 -0
- agent_runtime/server/__init__.py +15 -0
- agent_runtime/server/app_factory.py +37 -0
- agent_runtime/server/bootstrap.py +48 -0
- agent_runtime/server/endpoint_utils.py +37 -0
- agent_runtime/server/management.py +107 -0
- agent_runtime/smol/__init__.py +4 -0
- agent_runtime/smol/agents.py +431 -0
- agent_runtime/smol/llm_models.py +212 -0
- agent_runtime/smol/memory.py +111 -0
- agent_runtime/smol/models.py +69 -0
- agent_runtime/standalone.py +57 -0
- agent_runtime/storage.py +5 -0
- agent_runtime/tools.py +5 -0
- agent_runtime_sdk-0.1.0.dist-info/METADATA +125 -0
- agent_runtime_sdk-0.1.0.dist-info/RECORD +51 -0
- agent_runtime_sdk-0.1.0.dist-info/WHEEL +5 -0
- agent_runtime_sdk-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from openai import OpenAI
|
|
7
|
+
from smolagents import ApiModel, ChatMessage, MessageRole, TokenUsage
|
|
8
|
+
from smolagents.models import remove_content_after_stop_sequences
|
|
9
|
+
from smolagents.tools import Tool
|
|
10
|
+
|
|
11
|
+
from .models import (
|
|
12
|
+
OpenAIMessageAssistant,
|
|
13
|
+
OpenAIMessageSystem,
|
|
14
|
+
OpenAIMessageTool,
|
|
15
|
+
OpenAIMessageUser,
|
|
16
|
+
get_tool_json_schema,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DeepSeekModel(ApiModel):
|
|
21
|
+
"""Thin OpenAI-compatible wrapper based on the original agent-side DeepSeek model."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
model_id: str,
|
|
26
|
+
api_base: str,
|
|
27
|
+
api_key: str | None = None,
|
|
28
|
+
organization: str | None = None,
|
|
29
|
+
project: str | None = None,
|
|
30
|
+
client_kwargs: dict[str, Any] | None = None,
|
|
31
|
+
custom_role_conversions: dict[str, str] | None = None,
|
|
32
|
+
flatten_messages_as_text: bool = False,
|
|
33
|
+
**kwargs,
|
|
34
|
+
):
|
|
35
|
+
self.client_kwargs = {
|
|
36
|
+
**(client_kwargs or {}),
|
|
37
|
+
"api_key": api_key,
|
|
38
|
+
"base_url": api_base,
|
|
39
|
+
"organization": organization,
|
|
40
|
+
"project": project,
|
|
41
|
+
"timeout": 300,
|
|
42
|
+
}
|
|
43
|
+
super().__init__(
|
|
44
|
+
model_id=model_id,
|
|
45
|
+
custom_role_conversions=custom_role_conversions,
|
|
46
|
+
flatten_messages_as_text=flatten_messages_as_text,
|
|
47
|
+
retry=True,
|
|
48
|
+
**kwargs,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def create_client(self):
|
|
52
|
+
return OpenAI(**self.client_kwargs)
|
|
53
|
+
|
|
54
|
+
def generate(
|
|
55
|
+
self,
|
|
56
|
+
messages: list[ChatMessage | dict],
|
|
57
|
+
stop_sequences: list[str] | None = None,
|
|
58
|
+
response_format: dict[str, str] | None = None,
|
|
59
|
+
tools_to_call_from: list[Tool] | None = None,
|
|
60
|
+
**kwargs,
|
|
61
|
+
) -> ChatMessage:
|
|
62
|
+
completion_kwargs = self._prepare_completion_kwargs(
|
|
63
|
+
messages=messages,
|
|
64
|
+
stop_sequences=stop_sequences,
|
|
65
|
+
response_format=response_format,
|
|
66
|
+
tools_to_call_from=tools_to_call_from,
|
|
67
|
+
model=self.model_id,
|
|
68
|
+
custom_role_conversions=self.custom_role_conversions,
|
|
69
|
+
convert_images_to_image_urls=True,
|
|
70
|
+
**kwargs,
|
|
71
|
+
)
|
|
72
|
+
self._apply_rate_limit()
|
|
73
|
+
response = self.retryer(self.client.chat.completions.create, **completion_kwargs)
|
|
74
|
+
content = response.choices[0].message.content
|
|
75
|
+
if stop_sequences is not None and not self.supports_stop_parameter:
|
|
76
|
+
content = remove_content_after_stop_sequences(content, stop_sequences)
|
|
77
|
+
return ChatMessage(
|
|
78
|
+
role=response.choices[0].message.role,
|
|
79
|
+
content=content,
|
|
80
|
+
tool_calls=response.choices[0].message.tool_calls,
|
|
81
|
+
raw=response,
|
|
82
|
+
token_usage=TokenUsage(
|
|
83
|
+
input_tokens=response.usage.prompt_tokens,
|
|
84
|
+
output_tokens=response.usage.completion_tokens,
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def _prepare_completion_kwargs(
|
|
89
|
+
self,
|
|
90
|
+
messages: list[ChatMessage],
|
|
91
|
+
stop_sequences: list[str] | None = None,
|
|
92
|
+
response_format: dict[str, str] | None = None,
|
|
93
|
+
tools_to_call_from: list[Tool] | None = None,
|
|
94
|
+
custom_role_conversions: dict[str, str] | None = None,
|
|
95
|
+
convert_images_to_image_urls: bool = False,
|
|
96
|
+
tool_choice: str | dict | None = "required",
|
|
97
|
+
**kwargs,
|
|
98
|
+
) -> dict[str, Any]:
|
|
99
|
+
completion_kwargs = {
|
|
100
|
+
"messages": self.chat_messages2deepseek_messages(messages),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if stop_sequences is not None and self.supports_stop_parameter:
|
|
104
|
+
completion_kwargs["stop"] = stop_sequences
|
|
105
|
+
if response_format is not None:
|
|
106
|
+
completion_kwargs["response_format"] = response_format
|
|
107
|
+
if tools_to_call_from:
|
|
108
|
+
completion_kwargs["tools"] = [get_tool_json_schema(tool) for tool in tools_to_call_from]
|
|
109
|
+
if tool_choice is not None:
|
|
110
|
+
completion_kwargs["tool_choice"] = tool_choice
|
|
111
|
+
|
|
112
|
+
completion_kwargs.update(kwargs)
|
|
113
|
+
# Keep model-level defaults working the same way as the original agent model.
|
|
114
|
+
completion_kwargs.update(self.kwargs)
|
|
115
|
+
return completion_kwargs
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def chat_messages2deepseek_messages(messages: list[ChatMessage]) -> list[dict[str, Any]]:
|
|
119
|
+
message_list: list[
|
|
120
|
+
OpenAIMessageSystem | OpenAIMessageUser | OpenAIMessageAssistant | OpenAIMessageTool
|
|
121
|
+
] = []
|
|
122
|
+
|
|
123
|
+
for chat_message in messages:
|
|
124
|
+
match chat_message.role:
|
|
125
|
+
case MessageRole.SYSTEM:
|
|
126
|
+
message_list.append(OpenAIMessageSystem(content=_flatten_text_content(chat_message.content)))
|
|
127
|
+
|
|
128
|
+
case MessageRole.USER:
|
|
129
|
+
message_list.append(OpenAIMessageUser(content=_flatten_text_content(chat_message.content)))
|
|
130
|
+
|
|
131
|
+
case MessageRole.ASSISTANT:
|
|
132
|
+
content = _flatten_text_content(chat_message.content)
|
|
133
|
+
tool_calls = _serialize_tool_calls(chat_message.tool_calls)
|
|
134
|
+
if tool_calls:
|
|
135
|
+
message_list.append(OpenAIMessageAssistant(content=content or "", tool_calls=tool_calls))
|
|
136
|
+
elif content:
|
|
137
|
+
message_list.append(OpenAIMessageAssistant(content=content))
|
|
138
|
+
|
|
139
|
+
case MessageRole.TOOL_CALL:
|
|
140
|
+
tool_calls = _serialize_tool_calls(chat_message.tool_calls)
|
|
141
|
+
if tool_calls:
|
|
142
|
+
message_list.append(OpenAIMessageAssistant(content="", tool_calls=tool_calls))
|
|
143
|
+
|
|
144
|
+
case MessageRole.TOOL_RESPONSE:
|
|
145
|
+
# Prefer an explicit call id in the observation text. If it is missing,
|
|
146
|
+
# fall back to the most recent assistant tool call so OpenAI-compatible
|
|
147
|
+
# servers still accept the tool response.
|
|
148
|
+
tool_call_id = _extract_tool_call_id_from_history(message_list)
|
|
149
|
+
for tool_response in chat_message.content or []:
|
|
150
|
+
if tool_response.get("type") != "text":
|
|
151
|
+
continue
|
|
152
|
+
text = tool_response.get("text", "")
|
|
153
|
+
message_list.append(
|
|
154
|
+
OpenAIMessageTool(
|
|
155
|
+
content=text,
|
|
156
|
+
tool_call_id=_extract_tool_call_id(text) or tool_call_id,
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
return [message.to_dict() for message in message_list]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _flatten_text_content(content: Any) -> str:
|
|
163
|
+
if isinstance(content, str):
|
|
164
|
+
return content
|
|
165
|
+
if isinstance(content, list):
|
|
166
|
+
return "\n".join(item.get("text", "") for item in content if item.get("type") == "text")
|
|
167
|
+
return ""
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _extract_tool_call_id(text: str) -> str:
|
|
171
|
+
prefix = "Call id: "
|
|
172
|
+
if text.startswith(prefix):
|
|
173
|
+
return text.splitlines()[0][len(prefix):].strip()
|
|
174
|
+
return ""
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _extract_tool_call_id_from_history(message_list: list[Any]) -> str:
|
|
178
|
+
if not message_list:
|
|
179
|
+
return ""
|
|
180
|
+
assistant_message = message_list[-1]
|
|
181
|
+
if isinstance(assistant_message, OpenAIMessageAssistant) and assistant_message.tool_calls:
|
|
182
|
+
return assistant_message.tool_calls[-1].get("id", "")
|
|
183
|
+
return ""
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _serialize_tool_calls(tool_calls: Any) -> list[dict[str, Any]]:
|
|
187
|
+
if not isinstance(tool_calls, list):
|
|
188
|
+
return []
|
|
189
|
+
|
|
190
|
+
serialized: list[dict[str, Any]] = []
|
|
191
|
+
for tool_call in tool_calls:
|
|
192
|
+
function = getattr(tool_call, "function", None)
|
|
193
|
+
if function is None:
|
|
194
|
+
continue
|
|
195
|
+
serialized.append(
|
|
196
|
+
{
|
|
197
|
+
"id": getattr(tool_call, "id", ""),
|
|
198
|
+
"type": getattr(tool_call, "type", "function"),
|
|
199
|
+
"function": {
|
|
200
|
+
"name": getattr(function, "name", ""),
|
|
201
|
+
# OpenAI-compatible tool calls require function.arguments to be a string.
|
|
202
|
+
"arguments": _stringify_tool_arguments(getattr(function, "arguments", None)),
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
return serialized
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _stringify_tool_arguments(value: Any) -> str:
|
|
210
|
+
if isinstance(value, str):
|
|
211
|
+
return value
|
|
212
|
+
return json.dumps(value, ensure_ascii=False)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from smolagents import (
|
|
8
|
+
ActionStep as BaseActionStep,
|
|
9
|
+
AgentError,
|
|
10
|
+
ChatMessage,
|
|
11
|
+
ChatMessageToolCall,
|
|
12
|
+
MessageRole,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from .models import ChatMessageToolCallFunction
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ActionStep(BaseActionStep):
|
|
20
|
+
def to_messages(self, summary_mode: bool = False) -> list[ChatMessage]:
|
|
21
|
+
messages = []
|
|
22
|
+
if self.model_output is not None and self.model_output and not summary_mode:
|
|
23
|
+
messages.append(
|
|
24
|
+
ChatMessage(
|
|
25
|
+
role=MessageRole.ASSISTANT,
|
|
26
|
+
content=[{"type": "text", "text": self.model_output.strip()}],
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if self.tool_calls is not None:
|
|
31
|
+
messages.append(
|
|
32
|
+
ChatMessage(
|
|
33
|
+
role=MessageRole.TOOL_CALL,
|
|
34
|
+
content=[
|
|
35
|
+
{
|
|
36
|
+
"type": "text",
|
|
37
|
+
"text": "Calling tools:\n" + str([tc.dict() for tc in self.tool_calls]),
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
tool_calls=self.tool_call2chat_message_tool_call(),
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if self.observations_images:
|
|
45
|
+
messages.append(
|
|
46
|
+
ChatMessage(
|
|
47
|
+
role=MessageRole.USER,
|
|
48
|
+
content=[{"type": "image", "image": image} for image in self.observations_images],
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if self.observations is not None:
|
|
53
|
+
observation_text = f"Observation:\n{self.observations}"
|
|
54
|
+
if self.tool_calls:
|
|
55
|
+
observation_text = f"Call id: {self.tool_calls[0].id}\n" + observation_text
|
|
56
|
+
messages.append(
|
|
57
|
+
ChatMessage(
|
|
58
|
+
role=MessageRole.TOOL_RESPONSE,
|
|
59
|
+
content=[{"type": "text", "text": observation_text}],
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if self.error is not None:
|
|
64
|
+
error_message = (
|
|
65
|
+
"Error:\n"
|
|
66
|
+
+ str(self.error)
|
|
67
|
+
+ "\nNow let's retry: take care not to repeat previous errors! "
|
|
68
|
+
+ "If you have retried several times, try a completely different approach.\n"
|
|
69
|
+
)
|
|
70
|
+
message_content = f"Call id: {self.tool_calls[0].id}\n" if self.tool_calls else ""
|
|
71
|
+
message_content += error_message
|
|
72
|
+
messages.append(
|
|
73
|
+
ChatMessage(role=MessageRole.TOOL_RESPONSE, content=[{"type": "text", "text": message_content}])
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return messages
|
|
77
|
+
|
|
78
|
+
def tool_call2chat_message_tool_call(self) -> list[ChatMessageToolCall]:
|
|
79
|
+
chat_message_tool_call_list = []
|
|
80
|
+
for tool_call in self.tool_calls:
|
|
81
|
+
tool_call_dict = tool_call.dict()
|
|
82
|
+
chat_message_tool_call_list.append(
|
|
83
|
+
ChatMessageToolCall(
|
|
84
|
+
id=tool_call_dict.get("id"),
|
|
85
|
+
type=tool_call_dict.get("type"),
|
|
86
|
+
function=ChatMessageToolCallFunction(
|
|
87
|
+
name=tool_call_dict.get("function").get("name"),
|
|
88
|
+
arguments=tool_call_dict.get("function").get("arguments"),
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
return chat_message_tool_call_list
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ToolCallbackRegistry:
|
|
96
|
+
def __init__(self):
|
|
97
|
+
self._callbacks: dict[str, list[Callable]] = {}
|
|
98
|
+
|
|
99
|
+
def register(self, tool_name: str, callback: Callable):
|
|
100
|
+
if tool_name not in self._callbacks:
|
|
101
|
+
self._callbacks[tool_name] = []
|
|
102
|
+
self._callbacks[tool_name].append(callback)
|
|
103
|
+
|
|
104
|
+
def callback(self, chat_message: ChatMessage, **kwargs):
|
|
105
|
+
for tool_call in chat_message.tool_calls or []:
|
|
106
|
+
callbacks = self._callbacks.get(tool_call.function.name, [])
|
|
107
|
+
for callback in callbacks:
|
|
108
|
+
if len(inspect.signature(callback).parameters) == 1:
|
|
109
|
+
callback(chat_message)
|
|
110
|
+
else:
|
|
111
|
+
callback(chat_message, **kwargs)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from dataclasses import asdict, dataclass
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from smolagents import Tool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class OpenAIMessageObject:
|
|
12
|
+
content: str
|
|
13
|
+
|
|
14
|
+
def to_dict(self):
|
|
15
|
+
return asdict(self)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class OpenAIMessageSystem(OpenAIMessageObject):
|
|
20
|
+
role: Literal["system"] = "system"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class OpenAIMessageUser(OpenAIMessageObject):
|
|
25
|
+
role: Literal["user"] = "user"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class OpenAIMessageAssistant(OpenAIMessageObject):
|
|
30
|
+
role: Literal["assistant"] = "assistant"
|
|
31
|
+
tool_calls: list[dict] | None = None
|
|
32
|
+
|
|
33
|
+
def to_dict(self):
|
|
34
|
+
return asdict(self)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class OpenAIMessageTool(OpenAIMessageObject):
|
|
39
|
+
tool_call_id: str
|
|
40
|
+
role: Literal["tool"] = "tool"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class ChatMessageToolCallFunction:
|
|
45
|
+
arguments: Any
|
|
46
|
+
name: str
|
|
47
|
+
description: str | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_tool_json_schema(tool: Tool) -> dict[str, Any]:
|
|
51
|
+
properties = deepcopy(tool.inputs)
|
|
52
|
+
required: list[str] = []
|
|
53
|
+
for key, value in properties.items():
|
|
54
|
+
if value["type"] == "any":
|
|
55
|
+
value["type"] = "string"
|
|
56
|
+
if not ("nullable" in value and value["nullable"]):
|
|
57
|
+
required.append(key)
|
|
58
|
+
return {
|
|
59
|
+
"type": "function",
|
|
60
|
+
"function": {
|
|
61
|
+
"name": tool.name,
|
|
62
|
+
"description": tool.description,
|
|
63
|
+
"parameters": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"properties": properties,
|
|
66
|
+
"required": required,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""单 agent 模式下的对外启动 facade。"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from fastapi import FastAPI
|
|
9
|
+
import uvicorn
|
|
10
|
+
|
|
11
|
+
from .server.app_factory import build_app_from_runtime
|
|
12
|
+
from .server.bootstrap import DEFAULT_DEFINITION_PATH, load_single_agent_runtime
|
|
13
|
+
from .server.endpoint_utils import port_from_public_base_url
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def build_single_agent_app(
|
|
20
|
+
definition_path: str | Path = DEFAULT_DEFINITION_PATH,
|
|
21
|
+
public_base_url: str | None = None,
|
|
22
|
+
bind_port: int | None = None,
|
|
23
|
+
discoverer=None,
|
|
24
|
+
) -> FastAPI:
|
|
25
|
+
"""从 definition 直接产出一个可运行的 FastAPI 应用。"""
|
|
26
|
+
|
|
27
|
+
runtime = load_single_agent_runtime(
|
|
28
|
+
definition_path=definition_path,
|
|
29
|
+
public_base_url=public_base_url,
|
|
30
|
+
bind_port=bind_port,
|
|
31
|
+
discoverer=discoverer,
|
|
32
|
+
)
|
|
33
|
+
return build_app_from_runtime(runtime, enable_management=False)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def run_single_agent_app(
|
|
37
|
+
app: FastAPI,
|
|
38
|
+
*,
|
|
39
|
+
host: str = "0.0.0.0",
|
|
40
|
+
port: int | None = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""按单 agent SDK 的默认约定启动 uvicorn。"""
|
|
43
|
+
|
|
44
|
+
bind_port = port if port is not None else port_from_public_base_url(app.state.runtime.public_url())
|
|
45
|
+
if bind_port is None or bind_port <= 0:
|
|
46
|
+
raise SystemExit(
|
|
47
|
+
"没有可用的默认监听端口;请在 agent_app/agent.yaml 配置带端口的 public_base_url,"
|
|
48
|
+
"或者在 main.py 里调用 run_single_agent_app(app, port=<port>)。"
|
|
49
|
+
)
|
|
50
|
+
logger.info(
|
|
51
|
+
"Starting uvicorn for agent_id=%s host=%s port=%s public_url=%s",
|
|
52
|
+
app.state.runtime.definition.agent.agent_id,
|
|
53
|
+
host,
|
|
54
|
+
bind_port,
|
|
55
|
+
app.state.runtime.public_url(),
|
|
56
|
+
)
|
|
57
|
+
uvicorn.run(app, host=host, port=bind_port)
|
agent_runtime/storage.py
ADDED
agent_runtime/tools.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-runtime-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Single-agent runtime SDK for A2A agents backed by MCP tools.
|
|
5
|
+
Project-URL: Homepage, https://pypi.org/project/agent-runtime-sdk/
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: a2a-sdk==0.3.22
|
|
9
|
+
Requires-Dist: smolagents[mcp]
|
|
10
|
+
Requires-Dist: openai
|
|
11
|
+
Requires-Dist: fastapi
|
|
12
|
+
Requires-Dist: uvicorn
|
|
13
|
+
Requires-Dist: httpx
|
|
14
|
+
Requires-Dist: jinja2
|
|
15
|
+
Requires-Dist: pyyaml
|
|
16
|
+
Requires-Dist: loguru
|
|
17
|
+
Requires-Dist: pydantic
|
|
18
|
+
Requires-Dist: rich
|
|
19
|
+
Provides-Extra: observability
|
|
20
|
+
Requires-Dist: langfuse; extra == "observability"
|
|
21
|
+
Requires-Dist: openinference-instrumentation-smolagents; extra == "observability"
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: build; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest; extra == "dev"
|
|
25
|
+
Requires-Dist: twine; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# agent-runtime-sdk
|
|
28
|
+
|
|
29
|
+
Single-agent runtime SDK for exposing MCP tools as an A2A-compatible agent service.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install agent-runtime-sdk
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Install optional Langfuse/smolagents observability support:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install "agent-runtime-sdk[observability]"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Basic Usage
|
|
44
|
+
|
|
45
|
+
Create an agent project:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
my-agent/
|
|
49
|
+
├── agent_app/
|
|
50
|
+
│ ├── agent.yaml
|
|
51
|
+
│ └── plugin.py
|
|
52
|
+
└── main.py
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Example `main.py`:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from pathlib import Path
|
|
59
|
+
|
|
60
|
+
from agent_runtime.standalone import build_single_agent_app, run_single_agent_app
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
app = build_single_agent_app(
|
|
64
|
+
Path(__file__).resolve().parent / "agent_app" / "agent.yaml"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
run_single_agent_app(app)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Example SDK import:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from agent_runtime import AgentBuilder
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Minimal Configuration
|
|
79
|
+
|
|
80
|
+
Example `agent_app/agent.yaml`:
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
agent:
|
|
84
|
+
agent_id: example-agent
|
|
85
|
+
name: Example Agent
|
|
86
|
+
description: Example A2A agent backed by MCP tools.
|
|
87
|
+
|
|
88
|
+
runtime:
|
|
89
|
+
public_base_url: ${AGENT_PUBLIC_BASE_URL:-http://127.0.0.1:10020}
|
|
90
|
+
model:
|
|
91
|
+
provider: openai_compatible
|
|
92
|
+
api_base: ${MODEL_SOURCE_API_BASE}
|
|
93
|
+
model_id: ${MODEL_SOURCE_MODEL_ID}
|
|
94
|
+
api_key_env: MODEL_SOURCE_API_KEY
|
|
95
|
+
|
|
96
|
+
mcps:
|
|
97
|
+
- name: example
|
|
98
|
+
url: ${MCP_EXAMPLE_URL}
|
|
99
|
+
transport: streamable-http
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Run:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
export MODEL_SOURCE_API_BASE="https://your-model-api.example.com"
|
|
106
|
+
export MODEL_SOURCE_MODEL_ID="your-model-id"
|
|
107
|
+
export MODEL_SOURCE_API_KEY="your-api-key"
|
|
108
|
+
export MCP_EXAMPLE_URL="http://127.0.0.1:8000/mcp"
|
|
109
|
+
export AGENT_PUBLIC_BASE_URL="http://127.0.0.1:10020"
|
|
110
|
+
|
|
111
|
+
python main.py
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Langfuse
|
|
115
|
+
|
|
116
|
+
After installing `agent-runtime-sdk[observability]`, configure:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
export MCP_AGENT_LANGFUSE_ENABLED=true
|
|
120
|
+
export LANGFUSE_BASE_URL="http://your-langfuse-host:3000"
|
|
121
|
+
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
|
|
122
|
+
export LANGFUSE_SECRET_KEY="sk-lf-..."
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Set `MCP_AGENT_LANGFUSE_ENABLED=false` to disable instrumentation.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
agent_runtime/__init__.py,sha256=vZfLvJTs7TGfotvY4Mn5yTguQflMX9IbRbQm7W5Sr-U,2155
|
|
2
|
+
agent_runtime/builder.py,sha256=dluDyF5yg9RjUBySHFt782hNA6gtnXHBBoSnBnhe4fM,10541
|
|
3
|
+
agent_runtime/context.py,sha256=5InRJd84fZj03e9I8xdqdlG94TbZICLyP4gy9-uTpXw,250
|
|
4
|
+
agent_runtime/definitions.py,sha256=GGWReUrP2Y-fnojsUb2dxrSH36DQPjtC1vS2j6s9ft0,704
|
|
5
|
+
agent_runtime/discovery.py,sha256=ie76iVQ7XlvqBF1q5UfDG92wuJ-wv9VTCCV6Xan9wSw,386
|
|
6
|
+
agent_runtime/exceptions.py,sha256=NIjRcKM8r-ZnyB3f4wQa4IyeHy-3Fu6QaEBsh3pgNAw,2304
|
|
7
|
+
agent_runtime/model_registry.py,sha256=pzLs3sF4GmEcJea1wKAm1Gt4g3fEMcHIkO6Q2bm2UvI,1008
|
|
8
|
+
agent_runtime/policies.py,sha256=c0Ttz-0Wn1jz8mgmw_YJl4hIUEtBA_teifw_ql1XVhc,295
|
|
9
|
+
agent_runtime/runtime.py,sha256=WMmNmAa5JBKCkjbl7TcLLwNssGdsGfYjYVNAU61fkuA,3721
|
|
10
|
+
agent_runtime/sdk_agent.py,sha256=iUTI66ccgktu9zEgOPCVkQl4N3NT_ld7TNMimFUf7iw,17446
|
|
11
|
+
agent_runtime/standalone.py,sha256=T3NwUJ7Uly-J9zssjB9HvqY6N-ZNThcy45saUE8dB2c,1775
|
|
12
|
+
agent_runtime/storage.py,sha256=ug3oWJefD2fpBtFs7WU_XirW_Jz6AzvrKpv39zNBjM0,130
|
|
13
|
+
agent_runtime/tools.py,sha256=w9rxqjXUNhCQFR2nUeTiGlkpDzJzjc4Baw2NvmbVF4c,199
|
|
14
|
+
agent_runtime/config/__init__.py,sha256=rY1oFQFqqBG0Ih6Mt5TeCUZ9J_bkFSwjxvIokN3FM4g,552
|
|
15
|
+
agent_runtime/config/definitions.py,sha256=i130Ojung9ShVkLKIR2qIgdnDobbNhRFSJUO8tbwy8o,4290
|
|
16
|
+
agent_runtime/config/policies.py,sha256=shhoRQbUlPytOEG0pJCQ_DOkeDxyWi1ECnHA4uZAAGk,1966
|
|
17
|
+
agent_runtime/config/storage.py,sha256=_xjwS1ItHVqtbjOkZnKmZ5dDwfU__5w8ddMKUtSCvVc,4691
|
|
18
|
+
agent_runtime/mcp/__init__.py,sha256=EZSRhs4RxoHXRGSM4nv-4kbSZuAgtGR0VkqIUsyEVFc,721
|
|
19
|
+
agent_runtime/mcp/discovery.py,sha256=nvLTdcdHpMqYrzdL7ymeIcXJclIAoI-buk8senXVnGs,4824
|
|
20
|
+
agent_runtime/mcp/metadata.py,sha256=cGuMJjZWGeqP6Pn5r37uQHhYre_r14aDnGinWVhdew4,2135
|
|
21
|
+
agent_runtime/mcp/utils.py,sha256=ZmaPV2y88XshC71GzBGWY0hpHRLvlW2R-dhZ2QgLSj8,1492
|
|
22
|
+
agent_runtime/plugins/__init__.py,sha256=so756bRTF3SAZ-yrql5hxr3nX2QwWN88MpTzAA1aHE0,200
|
|
23
|
+
agent_runtime/plugins/base.py,sha256=MJuDcH9yJVUGV1CpvaXk0RqWHkmUNNGsbZXRPMbCPJs,2690
|
|
24
|
+
agent_runtime/plugins/default.py,sha256=uXxMqgKLwOqC673zERtE8eW72McxwLUGz64zviSomug,568
|
|
25
|
+
agent_runtime/plugins/instructions.py,sha256=T8f7v7XMlBAY_s_6mDt5HkRC1fevLT9nCRK99eTzo4A,1295
|
|
26
|
+
agent_runtime/plugins/loader.py,sha256=uM9yiK-rIzkU-4LBlXxuDDkh0s9Ci5v0xLE5Nd2kIDY,2131
|
|
27
|
+
agent_runtime/runtime_engine/__init__.py,sha256=OeH5vZG_mHsqcWl0NnYV7A4Wf2Vf5vHdzjo_XtEtNQQ,949
|
|
28
|
+
agent_runtime/runtime_engine/a2a_bridge.py,sha256=Qhq_H9BbqUzmlHOQNWwVl4YaaxrkLiny0QfpPmTA6rk,7089
|
|
29
|
+
agent_runtime/runtime_engine/a2a_task_io.py,sha256=L3BmDPiRgF3yPw49znpRD-6IlPcLe83rRJeJVF3_IwE,5288
|
|
30
|
+
agent_runtime/runtime_engine/agent_build.py,sha256=6IFCqnuha_84hP1nHtw3BiJx_uTVrinPSAU23714Tbw,11146
|
|
31
|
+
agent_runtime/runtime_engine/context.py,sha256=gPG6U208heGO1-vMiaGL52hGUzKffsFSG-BL_hN5ak8,13405
|
|
32
|
+
agent_runtime/runtime_engine/loading.py,sha256=XtmvdabFst1gcaQujG2nhO6UoQfVZU4hoHMJrBaaWkA,5767
|
|
33
|
+
agent_runtime/runtime_engine/observability.py,sha256=L3oI9Y5Zw7ISXT0GMqTHZ-WXdb3hOFaGM1iphUw4gog,4418
|
|
34
|
+
agent_runtime/runtime_engine/policy_registry.py,sha256=Ng7EswD-umdF4XjlVwnZlytwAbjf5ArU5hwSrOnNjSg,3715
|
|
35
|
+
agent_runtime/runtime_engine/protocol_tools.py,sha256=SMtXcoT4JDXrQM0dGXzrXM6Ch8w4aLGRJOdZEv3TGKs,2333
|
|
36
|
+
agent_runtime/runtime_engine/task_flow.py,sha256=94KmcGIlqozfqt0jkinCzyolVOy56JRAfRPv5cRWpo0,28016
|
|
37
|
+
agent_runtime/runtime_engine/tool_flow.py,sha256=pBRwR76WtRDZTR4zOW_ertGZkQNPCdp8w5mOHDikWfc,11352
|
|
38
|
+
agent_runtime/server/__init__.py,sha256=RWGoV_RAB_XoSipKhgK2vurJ22ZrJIALmR8M7wd_XAg,493
|
|
39
|
+
agent_runtime/server/app_factory.py,sha256=AcMIDkIHfGoIhKaqyNTH8dUmUhclPukkPPqUxMT3m9U,1015
|
|
40
|
+
agent_runtime/server/bootstrap.py,sha256=HRs7-XuyUVeqcz3W59S7IO4e3IUjZoUbzDfU3NxlI1s,1582
|
|
41
|
+
agent_runtime/server/endpoint_utils.py,sha256=Y7zi61nFzbFY52IaEN1oWUUs7WEcW018PwK-2IhrSpI,1243
|
|
42
|
+
agent_runtime/server/management.py,sha256=D7p8BKFGG-wYxbQuftjA-0ZyE5rwFV_gTY8rvcdDDQY,3800
|
|
43
|
+
agent_runtime/smol/__init__.py,sha256=WeKTbvwrR8GzdTQsB4h-u35R1lCC1weFdzTvrifyTUc,134
|
|
44
|
+
agent_runtime/smol/agents.py,sha256=rHdGiWGNcyIroKfJEddDts_LEWRJVqtjVnMcjQ4GyRU,16782
|
|
45
|
+
agent_runtime/smol/llm_models.py,sha256=d8xXarHrrkw60GUZCMBF_vtef8qoJjb49WjC9z1d8dI,8172
|
|
46
|
+
agent_runtime/smol/memory.py,sha256=2Cd29kXgqOx51yIHnXdKQhTqeneUn2y8JFDir40UBQM,4057
|
|
47
|
+
agent_runtime/smol/models.py,sha256=0mMMuR50E4Ehs9vFIGliVaWjwKi4ghQTH1pV1uvOz4Y,1548
|
|
48
|
+
agent_runtime_sdk-0.1.0.dist-info/METADATA,sha256=Z73KZZ92m04ySXOqbPi8ndphgf4QBe9f52DbVPhob2k,2760
|
|
49
|
+
agent_runtime_sdk-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
50
|
+
agent_runtime_sdk-0.1.0.dist-info/top_level.txt,sha256=vDappP7R62mPBfF_Va17SkMnaUilueFJe_Fdyom03jw,14
|
|
51
|
+
agent_runtime_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agent_runtime
|