tketool.llm 1.3.4__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.
- tketool/llm/__init__.py +7 -0
- tketool/lmc/__init__.py +5 -0
- tketool/lmc/agent_framework/__init__.py +5 -0
- tketool/lmc/agent_framework/mcp_tools/__init__.py +4 -0
- tketool/lmc/agent_framework/mcp_tools/demo_tools.py +35 -0
- tketool/lmc/agent_framework/mcp_tools/function_handler.py +224 -0
- tketool/lmc/agent_framework/mcp_tools/interfaces.py +91 -0
- tketool/lmc/agent_framework/mcp_tools/registry.py +76 -0
- tketool/lmc/agent_framework/mcp_tools/simple_code.py +73 -0
- tketool/lmc/agent_framework/mcp_tools/tool_types.py +122 -0
- tketool/lmc/agent_framework/memory/__init__.py +2 -0
- tketool/lmc/agent_framework/memory/interfaces.py +71 -0
- tketool/lmc/agent_framework/memory/local_memory.py +160 -0
- tketool/lmc/agent_framework/memory/memory_imps/__init__.py +49 -0
- tketool/lmc/agent_framework/memory/memory_imps/hindsight_like.py +532 -0
- tketool/lmc/agent_framework/memory/memory_imps/ml2_indexes.py +25 -0
- tketool/lmc/agent_framework/memory/storage_extensions/__init__.py +43 -0
- tketool/lmc/agent_framework/memory/storage_extensions/in_memory_bank_storage.py +25 -0
- tketool/lmc/agent_framework/memory/storage_extensions/in_memory_entity_index.py +61 -0
- tketool/lmc/agent_framework/memory/storage_extensions/in_memory_sparse_index.py +32 -0
- tketool/lmc/agent_framework/memory/storage_extensions/in_memory_temporal_index.py +28 -0
- tketool/lmc/agent_framework/memory/storage_extensions/in_memory_vector_index.py +34 -0
- tketool/lmc/agent_framework/memory/storage_extensions/ml2_bank_storage.py +90 -0
- tketool/lmc/agent_framework/memory/storage_extensions/ml2_entity_index.py +73 -0
- tketool/lmc/agent_framework/memory/storage_extensions/ml2_sparse_index.py +41 -0
- tketool/lmc/agent_framework/memory/storage_extensions/ml2_temporal_index.py +35 -0
- tketool/lmc/agent_framework/memory/storage_extensions/ml2_vector_index.py +44 -0
- tketool/lmc/agent_framework/memory/types.py +239 -0
- tketool/lmc/agent_framework/observer/__init__.py +13 -0
- tketool/lmc/agent_framework/observer/observer.py +322 -0
- tketool/lmc/agent_framework/observer/redactor.py +68 -0
- tketool/lmc/agent_framework/observer/report.py +67 -0
- tketool/lmc/agent_framework/observer/sink.py +57 -0
- tketool/lmc/agent_framework/observer/types.py +74 -0
- tketool/lmc/agent_framework/scheduler2/__init__.py +4 -0
- tketool/lmc/agent_framework/scheduler2/base.py +32 -0
- tketool/lmc/agent_framework/scheduler2/chains/__init__.py +4 -0
- tketool/lmc/agent_framework/scheduler2/chains/base.py +492 -0
- tketool/lmc/agent_framework/scheduler2/chains/main.py +674 -0
- tketool/lmc/agent_framework/scheduler2/chains/plan_main.py +368 -0
- tketool/lmc/agent_framework/scheduler2/chains/tool_call.py +461 -0
- tketool/lmc/agent_framework/scheduler2/graph.py +19 -0
- tketool/lmc/agent_framework/scheduler2/memory_kit/__init__.py +35 -0
- tketool/lmc/agent_framework/scheduler2/memory_kit/common.py +47 -0
- tketool/lmc/agent_framework/scheduler2/memory_kit/main_chain_memory.py +82 -0
- tketool/lmc/agent_framework/scheduler2/memory_kit/tool_call_memory.py +214 -0
- tketool/lmc/agent_framework/scheduler2/node_types.py +12 -0
- tketool/lmc/agent_framework/scheduler2/nodes/__init__.py +21 -0
- tketool/lmc/agent_framework/scheduler2/nodes/common.py +5 -0
- tketool/lmc/agent_framework/scheduler2/nodes/decision.py +25 -0
- tketool/lmc/agent_framework/scheduler2/nodes/info_summary.py +15 -0
- tketool/lmc/agent_framework/scheduler2/nodes/main_chain/__init__.py +3 -0
- tketool/lmc/agent_framework/scheduler2/nodes/main_chain/experience.py +87 -0
- tketool/lmc/agent_framework/scheduler2/nodes/main_chain/finalize.py +83 -0
- tketool/lmc/agent_framework/scheduler2/nodes/plan_main_chain/__init__.py +9 -0
- tketool/lmc/agent_framework/scheduler2/nodes/plan_main_chain/plan_build.py +125 -0
- tketool/lmc/agent_framework/scheduler2/nodes/plan_main_chain/plan_check.py +60 -0
- tketool/lmc/agent_framework/scheduler2/nodes/plan_main_chain/plan_select.py +56 -0
- tketool/lmc/agent_framework/scheduler2/nodes/plan_main_chain/plan_synthesize.py +101 -0
- tketool/lmc/agent_framework/scheduler2/nodes/plan_main_chain/plan_types.py +60 -0
- tketool/lmc/agent_framework/scheduler2/nodes/plan_main_chain/plan_update.py +71 -0
- tketool/lmc/agent_framework/scheduler2/nodes/plan_main_chain/subtask_run.py +77 -0
- tketool/lmc/agent_framework/scheduler2/nodes/task_identify.py +133 -0
- tketool/lmc/agent_framework/scheduler2/nodes/tool_call_chain/__init__.py +5 -0
- tketool/lmc/agent_framework/scheduler2/nodes/tool_call_chain/parameter_generate.py +134 -0
- tketool/lmc/agent_framework/scheduler2/nodes/tool_call_chain/tool_call.py +109 -0
- tketool/lmc/agent_framework/scheduler2/nodes/tool_call_chain/tool_decision.py +128 -0
- tketool/lmc/agent_framework/scheduler2/nodes/tool_call_chain/tool_experience_write.py +81 -0
- tketool/lmc/agent_framework/scheduler2/nodes/tool_call_chain/tool_result_verify.py +99 -0
- tketool/lmc/agent_framework/scheduler2/nodes/verify.py +135 -0
- tketool/lmc/agent_framework/scheduler2/prompting.py +126 -0
- tketool/lmc/agent_framework/scheduler2/types.py +194 -0
- tketool/lmc/embeddings.py +17 -0
- tketool/lmc/llms/__init__.py +1 -0
- tketool/lmc/llms/embeddings.py +215 -0
- tketool/lmc/llms/openai.py +326 -0
- tketool/lmc/lmc_linked.py +604 -0
- tketool/lmc/lmc_linked_flow.py +7 -0
- tketool/lmc/lmc_prompt_invoker.py +426 -0
- tketool/lmc/models.py +97 -0
- tketool/lmc/prompts/__init__.py +0 -0
- tketool/lmc/prompts/prompt_controller.py +462 -0
- tketool/lmc/prompts/templates/agent_scheduler/auto_repair.txt +38 -0
- tketool/lmc/prompts/templates/agent_scheduler/context_compact.txt +41 -0
- tketool/lmc/prompts/templates/agent_scheduler/context_resolve_reference.txt +42 -0
- tketool/lmc/prompts/templates/agent_scheduler/decompose_task_plan.txt +71 -0
- tketool/lmc/prompts/templates/agent_scheduler/plan_tool_use.txt +53 -0
- tketool/lmc/prompts/templates/agent_scheduler/prepare_tool_call.txt +56 -0
- tketool/lmc/prompts/templates/agent_scheduler/re_understand_task.txt +61 -0
- tketool/lmc/prompts/templates/agent_scheduler/reason.txt +35 -0
- tketool/lmc/prompts/templates/agent_scheduler/replan_task_plan.txt +66 -0
- tketool/lmc/prompts/templates/agent_scheduler/summarize_user_need.txt +50 -0
- tketool/lmc/prompts/templates/agent_scheduler/understand_task.txt +47 -0
- tketool/lmc/prompts/templates/agent_scheduler/validate.txt +41 -0
- tketool/lmc/prompts/templates/agent_scheduler/validate_task_plan.txt +46 -0
- tketool/lmc/prompts/templates/agents/memory_method_plan.txt +51 -0
- tketool/lmc/prompts/templates/agents/memory_range.txt +34 -0
- tketool/lmc/prompts/templates/agents/memory_recall.txt +25 -0
- tketool/lmc/prompts/templates/agents/memory_thinking_recall.txt +36 -0
- tketool/lmc/prompts/templates/agents/rule_compress.txt +26 -0
- tketool/lmc/prompts/templates/mcps/context_summary.txt +28 -0
- tketool/lmc/prompts/templates/mcps/mcp_page_use.txt +33 -0
- tketool/lmc/prompts/templates/mcps/mcp_step_summary.txt +39 -0
- tketool/lmc/prompts/templates/mcps/mcp_summary.txt +41 -0
- tketool/lmc/prompts/templates/scheduler2/scheduler2_main_decide.txt +55 -0
- tketool/lmc/prompts/templates/scheduler2/scheduler2_main_experience_organize.txt +23 -0
- tketool/lmc/prompts/templates/scheduler2/scheduler2_main_experience_summarize.txt +33 -0
- tketool/lmc/prompts/templates/scheduler2/scheduler2_main_finalize.txt +33 -0
- tketool/lmc/prompts/templates/scheduler2/scheduler2_plan_build.txt +42 -0
- tketool/lmc/prompts/templates/scheduler2/scheduler2_plan_synthesize.txt +44 -0
- tketool/lmc/prompts/templates/scheduler2/scheduler2_task_identify.txt +47 -0
- tketool/lmc/prompts/templates/scheduler2/scheduler2_tool_decision.txt +38 -0
- tketool/lmc/prompts/templates/scheduler2/scheduler2_tool_experience_write.txt +65 -0
- tketool/lmc/prompts/templates/scheduler2/scheduler2_tool_parameter_generate.txt +53 -0
- tketool/lmc/prompts/templates/scheduler2/scheduler2_tool_result_verify.txt +48 -0
- tketool/lmc/prompts/templates/scheduler2/scheduler2_verify.txt +41 -0
- tketool/lmc_console_tool.py +24 -0
- tketool_llm-1.3.4.dist-info/METADATA +99 -0
- tketool_llm-1.3.4.dist-info/RECORD +122 -0
- tketool_llm-1.3.4.dist-info/WHEEL +5 -0
- tketool_llm-1.3.4.dist-info/entry_points.txt +2 -0
- tketool_llm-1.3.4.dist-info/top_level.txt +1 -0
tketool/llm/__init__.py
ADDED
tketool/lmc/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from tketool.lmc.agent_framework.mcp_tools import *
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@tool_definition(
|
|
7
|
+
name="calculate_revenue",
|
|
8
|
+
description="计算总收入:上午收入 + 下午收入。",
|
|
9
|
+
parameters=[
|
|
10
|
+
ToolParameter(name="morning_count", type="int", description="上午销售数量"),
|
|
11
|
+
ToolParameter(name="morning_price", type="float", description="上午单价"),
|
|
12
|
+
ToolParameter(name="afternoon_count", type="int", description="下午销售数量"),
|
|
13
|
+
ToolParameter(name="afternoon_price", type="float", description="下午单价"),
|
|
14
|
+
],
|
|
15
|
+
output_schema={"required": ["revenue", "formula"]},
|
|
16
|
+
)
|
|
17
|
+
def calculate_revenue(
|
|
18
|
+
morning_count: int,
|
|
19
|
+
morning_price: float,
|
|
20
|
+
afternoon_count: int,
|
|
21
|
+
afternoon_price: float,
|
|
22
|
+
) -> dict:
|
|
23
|
+
"""计算今日总收入。"""
|
|
24
|
+
revenue = morning_count * morning_price + afternoon_count * afternoon_price
|
|
25
|
+
formula = (
|
|
26
|
+
f"{morning_count} x {morning_price:g} + "
|
|
27
|
+
f"{afternoon_count} x {afternoon_price:g} = {revenue:g}"
|
|
28
|
+
)
|
|
29
|
+
return {"revenue": revenue, "formula": formula}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_demo_tool_registry() -> InMemoryToolRegistry:
|
|
33
|
+
tools = InMemoryToolRegistry()
|
|
34
|
+
tools.register(FunctionToolHandler(calculate_revenue))
|
|
35
|
+
return tools
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from inspect import Signature, signature
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from tketool.lmc.agent_framework.mcp_tools.interfaces import ToolHandler
|
|
8
|
+
from tketool.lmc.agent_framework.mcp_tools.tool_types import (
|
|
9
|
+
ToolCall,
|
|
10
|
+
ToolDefinition,
|
|
11
|
+
ToolParameter,
|
|
12
|
+
ToolResult,
|
|
13
|
+
ToolResultStatus,
|
|
14
|
+
)
|
|
15
|
+
from tketool.lmc.agent_framework.observer import FrameworkObserver
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FunctionToolHandler(ToolHandler):
|
|
19
|
+
"""把普通 Python 函数适配为 ToolHandler。"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, func: Callable, definition: ToolDefinition | None = None):
|
|
22
|
+
self.func = func
|
|
23
|
+
self.definition = definition or self._definition_from_func(func)
|
|
24
|
+
|
|
25
|
+
def before(self, call: ToolCall) -> ToolCall:
|
|
26
|
+
"""执行前补齐默认参数并校验必填参数。"""
|
|
27
|
+
arguments = dict(call.arguments)
|
|
28
|
+
missing = []
|
|
29
|
+
for parameter in self.definition.parameters:
|
|
30
|
+
if parameter.name not in arguments:
|
|
31
|
+
if parameter.required:
|
|
32
|
+
missing.append(parameter.name)
|
|
33
|
+
else:
|
|
34
|
+
arguments[parameter.name] = parameter.default
|
|
35
|
+
if missing:
|
|
36
|
+
raise ValueError(f"缺少必填参数:{', '.join(missing)}")
|
|
37
|
+
call.arguments = arguments
|
|
38
|
+
return call
|
|
39
|
+
|
|
40
|
+
def execute(self, call: ToolCall) -> ToolResult:
|
|
41
|
+
"""执行函数并把异常转换为标准 ToolResult。"""
|
|
42
|
+
try:
|
|
43
|
+
content = self.func(**call.arguments)
|
|
44
|
+
except Exception as exc: # noqa: BLE001
|
|
45
|
+
return ToolResult(
|
|
46
|
+
status=ToolResultStatus.FATAL_ERROR,
|
|
47
|
+
error=str(exc),
|
|
48
|
+
retryable=False,
|
|
49
|
+
metadata={"tool_name": self.definition.name},
|
|
50
|
+
)
|
|
51
|
+
return ToolResult(
|
|
52
|
+
status=ToolResultStatus.SUCCESS,
|
|
53
|
+
content=content,
|
|
54
|
+
metadata={"tool_name": self.definition.name},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def after(self, call: ToolCall, result: ToolResult) -> ToolResult:
|
|
58
|
+
"""执行后补充调用信息。"""
|
|
59
|
+
result.metadata = {
|
|
60
|
+
**result.metadata,
|
|
61
|
+
"call_id": call.call_id,
|
|
62
|
+
"task_id": call.context.task_id,
|
|
63
|
+
"step_id": call.context.step_id,
|
|
64
|
+
}
|
|
65
|
+
return result
|
|
66
|
+
|
|
67
|
+
def validate(self, call: ToolCall, result: ToolResult) -> ToolResult:
|
|
68
|
+
"""校验输出是否满足 output_schema 中的 required 字段要求。"""
|
|
69
|
+
if result.status != ToolResultStatus.SUCCESS:
|
|
70
|
+
return result
|
|
71
|
+
required_fields = (self.definition.output_schema or {}).get("required", [])
|
|
72
|
+
if required_fields:
|
|
73
|
+
if not isinstance(result.content, dict):
|
|
74
|
+
return ToolResult(
|
|
75
|
+
status=ToolResultStatus.VALIDATION_ERROR,
|
|
76
|
+
content=result.content,
|
|
77
|
+
error="工具输出不是 dict,无法校验 required 字段。",
|
|
78
|
+
retryable=False,
|
|
79
|
+
metadata=result.metadata,
|
|
80
|
+
)
|
|
81
|
+
missing = [field for field in required_fields if field not in result.content]
|
|
82
|
+
if missing:
|
|
83
|
+
return ToolResult(
|
|
84
|
+
status=ToolResultStatus.VALIDATION_ERROR,
|
|
85
|
+
content=result.content,
|
|
86
|
+
error=f"工具输出缺少字段:{', '.join(missing)}",
|
|
87
|
+
retryable=False,
|
|
88
|
+
metadata=result.metadata,
|
|
89
|
+
)
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
def run(self, call: ToolCall, observer_object: FrameworkObserver | None = None) -> ToolResult:
|
|
93
|
+
"""按 before -> execute -> after -> validate 顺序执行工具。"""
|
|
94
|
+
span_id = None
|
|
95
|
+
if observer_object is not None:
|
|
96
|
+
span_id = observer_object.event_start(
|
|
97
|
+
"mcp.tool_call",
|
|
98
|
+
source="mcp",
|
|
99
|
+
phase="tool",
|
|
100
|
+
task_id=call.context.task_id,
|
|
101
|
+
node_id=call.context.step_id,
|
|
102
|
+
subject={"kind": "tool", "id": self.definition.name},
|
|
103
|
+
payload={
|
|
104
|
+
"tool_name": self.definition.name,
|
|
105
|
+
"call_id": call.call_id,
|
|
106
|
+
"arguments": dict(call.arguments),
|
|
107
|
+
"context_metadata": dict(call.context.metadata),
|
|
108
|
+
},
|
|
109
|
+
)
|
|
110
|
+
try:
|
|
111
|
+
prepared_call = self.before(call)
|
|
112
|
+
except Exception as exc: # noqa: BLE001
|
|
113
|
+
if observer_object is not None:
|
|
114
|
+
observer_object.error(
|
|
115
|
+
"mcp.tool_call_error",
|
|
116
|
+
exc,
|
|
117
|
+
source="mcp",
|
|
118
|
+
phase="tool",
|
|
119
|
+
task_id=call.context.task_id,
|
|
120
|
+
node_id=call.context.step_id,
|
|
121
|
+
payload={"tool_name": self.definition.name, "call_id": call.call_id, "stage": "before"},
|
|
122
|
+
)
|
|
123
|
+
if span_id:
|
|
124
|
+
observer_object.event_end(
|
|
125
|
+
span_id,
|
|
126
|
+
level="error",
|
|
127
|
+
status="failed",
|
|
128
|
+
payload={"tool_name": self.definition.name, "call_id": call.call_id, "stage": "before", "error": str(exc)},
|
|
129
|
+
)
|
|
130
|
+
return ToolResult(
|
|
131
|
+
status=ToolResultStatus.FATAL_ERROR,
|
|
132
|
+
error=str(exc),
|
|
133
|
+
retryable=False,
|
|
134
|
+
metadata={"tool_name": self.definition.name},
|
|
135
|
+
)
|
|
136
|
+
attempts = max(1, self.definition.retry_policy.max_attempts)
|
|
137
|
+
last_result: ToolResult | None = None
|
|
138
|
+
for attempt in range(1, attempts + 1):
|
|
139
|
+
result = self.execute(prepared_call)
|
|
140
|
+
result.metadata = {**result.metadata, "attempt": attempt}
|
|
141
|
+
result = self.after(prepared_call, result)
|
|
142
|
+
result = self.validate(prepared_call, result)
|
|
143
|
+
last_result = result
|
|
144
|
+
if observer_object is not None:
|
|
145
|
+
observer_object.record(
|
|
146
|
+
"mcp.tool_call_attempt",
|
|
147
|
+
source="mcp",
|
|
148
|
+
phase="tool",
|
|
149
|
+
level="error" if result.status != ToolResultStatus.SUCCESS else "info",
|
|
150
|
+
task_id=prepared_call.context.task_id,
|
|
151
|
+
node_id=prepared_call.context.step_id,
|
|
152
|
+
message=result.error or "",
|
|
153
|
+
payload={
|
|
154
|
+
"tool_name": self.definition.name,
|
|
155
|
+
"call_id": prepared_call.call_id,
|
|
156
|
+
"attempt": attempt,
|
|
157
|
+
"status": result.status,
|
|
158
|
+
"retryable": result.retryable,
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
# 只有 retry_policy 明确允许的状态才进入下一次尝试。
|
|
162
|
+
if result.status not in self.definition.retry_policy.retry_on:
|
|
163
|
+
if observer_object is not None:
|
|
164
|
+
observer_object.event_end(
|
|
165
|
+
span_id,
|
|
166
|
+
level="error" if result.status != ToolResultStatus.SUCCESS else "info",
|
|
167
|
+
status=result.status,
|
|
168
|
+
message=result.error or "",
|
|
169
|
+
payload={
|
|
170
|
+
"tool_name": self.definition.name,
|
|
171
|
+
"call_id": prepared_call.call_id,
|
|
172
|
+
"status": result.status,
|
|
173
|
+
"attempts": attempt,
|
|
174
|
+
"has_content": result.content is not None,
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
return result
|
|
178
|
+
assert last_result is not None
|
|
179
|
+
if observer_object is not None:
|
|
180
|
+
observer_object.event_end(
|
|
181
|
+
span_id,
|
|
182
|
+
level="error" if last_result.status != ToolResultStatus.SUCCESS else "info",
|
|
183
|
+
status=last_result.status,
|
|
184
|
+
message=last_result.error or "",
|
|
185
|
+
payload={
|
|
186
|
+
"tool_name": self.definition.name,
|
|
187
|
+
"call_id": prepared_call.call_id,
|
|
188
|
+
"status": last_result.status,
|
|
189
|
+
"attempts": attempts,
|
|
190
|
+
"has_content": last_result.content is not None,
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
return last_result
|
|
194
|
+
|
|
195
|
+
def _definition_from_func(self, func: Callable) -> ToolDefinition:
|
|
196
|
+
"""从函数签名生成工具定义,优先使用装饰器附加的定义。"""
|
|
197
|
+
if hasattr(func, "_tool_definition"):
|
|
198
|
+
return func._tool_definition
|
|
199
|
+
parameters = [
|
|
200
|
+
ToolParameter(
|
|
201
|
+
name=name,
|
|
202
|
+
type=self._type_name(param.annotation),
|
|
203
|
+
description=f"函数参数 {name}",
|
|
204
|
+
required=param.default is param.empty,
|
|
205
|
+
default=None if param.default is param.empty else param.default,
|
|
206
|
+
)
|
|
207
|
+
for name, param in signature(func).parameters.items()
|
|
208
|
+
]
|
|
209
|
+
return ToolDefinition(
|
|
210
|
+
name=func.__name__,
|
|
211
|
+
description=func.__doc__ or f"函数工具:{func.__name__}",
|
|
212
|
+
parameters=parameters,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def _type_name(self, annotation: Any) -> str:
|
|
216
|
+
"""把 Python 类型注解转为简短字符串。"""
|
|
217
|
+
if annotation is Signature.empty:
|
|
218
|
+
return "Any"
|
|
219
|
+
return getattr(annotation, "__name__", str(annotation))
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def build_function_tool_handler(func: Callable) -> FunctionToolHandler:
|
|
223
|
+
"""从普通函数构造 FunctionToolHandler。"""
|
|
224
|
+
return FunctionToolHandler(func)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
|
|
4
|
+
from tketool.lmc.agent_framework.observer import FrameworkObserver
|
|
5
|
+
from tketool.lmc.agent_framework.mcp_tools.tool_types import (
|
|
6
|
+
ToolCall,
|
|
7
|
+
ToolDefinition,
|
|
8
|
+
ToolResult,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ToolHandler(ABC):
|
|
13
|
+
"""工具处理器接口,定义一次工具调用的完整生命周期。"""
|
|
14
|
+
|
|
15
|
+
definition: ToolDefinition
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def before(self, call: ToolCall) -> ToolCall:
|
|
19
|
+
"""执行前处理,例如参数补全、权限检查或日志记录。"""
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def execute(self, call: ToolCall) -> ToolResult:
|
|
24
|
+
"""执行实际工具逻辑。"""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def after(self, call: ToolCall, result: ToolResult) -> ToolResult:
|
|
29
|
+
"""执行后处理,例如格式整理、指标记录或结果裁剪。"""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def validate(self, call: ToolCall, result: ToolResult) -> ToolResult:
|
|
34
|
+
"""校验工具结果是否满足调度步骤要求。"""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
def run(self, call: ToolCall, observer_object: FrameworkObserver | None = None) -> ToolResult:
|
|
38
|
+
"""执行完整工具生命周期。"""
|
|
39
|
+
prepared_call = self.before(call)
|
|
40
|
+
result = self.execute(prepared_call)
|
|
41
|
+
result = self.after(prepared_call, result)
|
|
42
|
+
return self.validate(prepared_call, result)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ToolRegistry(ABC):
|
|
46
|
+
"""工具注册表接口,供调度器按名称查找可用工具。"""
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def mcplibcode(self) -> str:
|
|
51
|
+
"""当前注册工具名集合的稳定短 ID。"""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def register(self, handler: ToolHandler) -> None:
|
|
56
|
+
"""注册一个工具处理器。"""
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def get(self, name: str) -> ToolHandler:
|
|
61
|
+
"""根据工具名称获取处理器。"""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def list_definitions(self) -> list[ToolDefinition]:
|
|
66
|
+
"""列出当前可用工具定义。"""
|
|
67
|
+
...
|
|
68
|
+
|
|
69
|
+
def search_definitions(self, query: str, top_k: int = 8) -> list[ToolDefinition]:
|
|
70
|
+
"""按查询文本检索候选工具定义;默认实现回退到全量列表截断。"""
|
|
71
|
+
return self.list_definitions()[:top_k]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def tool_definition(
|
|
75
|
+
name: str,
|
|
76
|
+
description: str,
|
|
77
|
+
parameters: list | None = None,
|
|
78
|
+
**kwargs,
|
|
79
|
+
) -> Callable:
|
|
80
|
+
"""为普通函数附加工具定义元数据。"""
|
|
81
|
+
|
|
82
|
+
def decorator(func: Callable) -> Callable:
|
|
83
|
+
func._tool_definition = ToolDefinition(
|
|
84
|
+
name=name,
|
|
85
|
+
description=description,
|
|
86
|
+
parameters=parameters or [],
|
|
87
|
+
**kwargs,
|
|
88
|
+
)
|
|
89
|
+
return func
|
|
90
|
+
|
|
91
|
+
return decorator
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from tketool.lmc.agent_framework.mcp_tools.interfaces import ToolHandler, ToolRegistry
|
|
7
|
+
from tketool.lmc.agent_framework.mcp_tools.tool_types import ToolDefinition
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ToolRegistrationError(ValueError):
|
|
11
|
+
"""工具注册错误。"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ToolNotFoundError(KeyError):
|
|
15
|
+
"""工具不存在错误。"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InMemoryToolRegistry(ToolRegistry):
|
|
19
|
+
"""内存版工具注册表,用于本地运行和单元测试。"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self._handlers: dict[str, ToolHandler] = {}
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def mcplibcode(self) -> str:
|
|
26
|
+
"""当前工具名集合的稳定短 ID。"""
|
|
27
|
+
tool_names = sorted(self._handlers)
|
|
28
|
+
raw = "\n".join(tool_names)
|
|
29
|
+
return hashlib.sha1(raw.encode("utf-8")).hexdigest()[:12]
|
|
30
|
+
|
|
31
|
+
def register(self, handler: ToolHandler) -> None:
|
|
32
|
+
"""注册一个工具处理器;重名工具默认拒绝。"""
|
|
33
|
+
name = handler.definition.name
|
|
34
|
+
if name in self._handlers:
|
|
35
|
+
raise ToolRegistrationError(f"工具已注册:{name}")
|
|
36
|
+
self._handlers[name] = handler
|
|
37
|
+
|
|
38
|
+
def get(self, name: str) -> ToolHandler:
|
|
39
|
+
"""根据名称获取工具处理器;不存在时抛出明确异常。"""
|
|
40
|
+
if name not in self._handlers:
|
|
41
|
+
raise ToolNotFoundError(f"工具不存在:{name}")
|
|
42
|
+
return self._handlers[name]
|
|
43
|
+
|
|
44
|
+
def list_definitions(self) -> list[ToolDefinition]:
|
|
45
|
+
"""按工具名稳定列出工具定义。"""
|
|
46
|
+
return [
|
|
47
|
+
self._handlers[name].definition
|
|
48
|
+
for name in sorted(self._handlers)
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
def search_definitions(self, query: str, top_k: int = 8) -> list[ToolDefinition]:
|
|
52
|
+
"""用轻量关键词打分检索工具定义。"""
|
|
53
|
+
definitions = self.list_definitions()
|
|
54
|
+
query_terms = _tokenize_tool_text(query)
|
|
55
|
+
if not query_terms:
|
|
56
|
+
return definitions[:top_k]
|
|
57
|
+
scored = []
|
|
58
|
+
for definition in definitions:
|
|
59
|
+
text = " ".join(
|
|
60
|
+
[
|
|
61
|
+
definition.name,
|
|
62
|
+
definition.description,
|
|
63
|
+
" ".join(parameter.name for parameter in definition.parameters),
|
|
64
|
+
" ".join(parameter.description for parameter in definition.parameters),
|
|
65
|
+
" ".join(str(value) for value in definition.metadata.values()),
|
|
66
|
+
]
|
|
67
|
+
)
|
|
68
|
+
terms = set(_tokenize_tool_text(text))
|
|
69
|
+
score = sum(1 for term in query_terms if term in terms)
|
|
70
|
+
scored.append((score, definition.name, definition))
|
|
71
|
+
scored.sort(key=lambda item: (-item[0], item[1]))
|
|
72
|
+
return [definition for score, _, definition in scored[:top_k] if score > 0] or definitions[:top_k]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _tokenize_tool_text(text: str) -> list[str]:
|
|
76
|
+
return [token.lower() for token in re.findall(r"[A-Za-z0-9_/-]+|[\u4e00-\u9fff]{2,}", text)]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""MCP Tools 模块最小运行示例。
|
|
2
|
+
|
|
3
|
+
运行方式:
|
|
4
|
+
python -m tketool.lmc.agent_framework.mcp_tools.simple_code
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 允许从当前文件直接运行,同时避免 mcp_tools 目录里的模块名影响标准库导入。
|
|
14
|
+
for path in list(sys.path):
|
|
15
|
+
if path == "" or path.endswith("/lmc/agent_framework/mcp_tools"):
|
|
16
|
+
sys.path.remove(path)
|
|
17
|
+
|
|
18
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[4]
|
|
19
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
20
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
21
|
+
|
|
22
|
+
from tketool.lmc.agent_framework.mcp_tools import (
|
|
23
|
+
FunctionToolHandler,
|
|
24
|
+
InMemoryToolRegistry,
|
|
25
|
+
ToolCall,
|
|
26
|
+
ToolContext,
|
|
27
|
+
ToolParameter,
|
|
28
|
+
ToolResultStatus,
|
|
29
|
+
tool_definition,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@tool_definition(
|
|
34
|
+
name="calculate_revenue",
|
|
35
|
+
description="计算收入:数量乘以单价。",
|
|
36
|
+
parameters=[
|
|
37
|
+
ToolParameter(name="count", type="int", description="销售数量"),
|
|
38
|
+
ToolParameter(name="price", type="float", description="单价"),
|
|
39
|
+
],
|
|
40
|
+
output_schema={"required": ["revenue"]},
|
|
41
|
+
)
|
|
42
|
+
def calculate_revenue(count: int, price: float) -> dict:
|
|
43
|
+
"""计算销售收入。"""
|
|
44
|
+
return {"revenue": count * price}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def main() -> None:
|
|
48
|
+
registry = InMemoryToolRegistry()
|
|
49
|
+
registry.register(FunctionToolHandler(calculate_revenue))
|
|
50
|
+
|
|
51
|
+
print("已注册工具:")
|
|
52
|
+
for definition in registry.list_definitions():
|
|
53
|
+
print(f"- {definition.name}: {definition.description}")
|
|
54
|
+
|
|
55
|
+
call = ToolCall(
|
|
56
|
+
tool_name="calculate_revenue",
|
|
57
|
+
arguments={"count": 3, "price": 20},
|
|
58
|
+
context=ToolContext(task_id="demo-task", step_id="execute_tool"),
|
|
59
|
+
call_id="tool-call-1",
|
|
60
|
+
)
|
|
61
|
+
handler = registry.get(call.tool_name)
|
|
62
|
+
result = handler.run(call)
|
|
63
|
+
|
|
64
|
+
print("\n工具调用:")
|
|
65
|
+
print(f"输入: {call.arguments}")
|
|
66
|
+
print(f"状态: {result.status.value}")
|
|
67
|
+
print(f"输出: {result.content}")
|
|
68
|
+
if result.status != ToolResultStatus.SUCCESS:
|
|
69
|
+
print(f"错误: {result.error}")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
main()
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ToolRisk(str, Enum):
|
|
8
|
+
"""工具风险等级,用于调度器决定是否需要额外确认或降级。"""
|
|
9
|
+
|
|
10
|
+
# 低风险工具,例如只读查询、纯计算、格式转换。
|
|
11
|
+
LOW = "low"
|
|
12
|
+
# 中风险工具,例如写入临时文件、调用外部服务、产生费用。
|
|
13
|
+
MEDIUM = "medium"
|
|
14
|
+
# 高风险工具,例如删除数据、提交变更、影响生产系统。
|
|
15
|
+
HIGH = "high"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ToolResultStatus(str, Enum):
|
|
19
|
+
"""工具调用结果状态。"""
|
|
20
|
+
|
|
21
|
+
# 调用成功。
|
|
22
|
+
SUCCESS = "success"
|
|
23
|
+
# 可重试错误,例如短暂网络失败、限流。
|
|
24
|
+
RETRYABLE_ERROR = "retryable_error"
|
|
25
|
+
# 不可重试错误,例如参数非法、工具不存在、权限不足。
|
|
26
|
+
FATAL_ERROR = "fatal_error"
|
|
27
|
+
# 工具执行完成,但结果不满足输出要求。
|
|
28
|
+
VALIDATION_ERROR = "validation_error"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class RetryPolicy:
|
|
33
|
+
"""工具重试策略。"""
|
|
34
|
+
|
|
35
|
+
# 最大尝试次数,包含第一次调用。
|
|
36
|
+
max_attempts: int = 1
|
|
37
|
+
# 哪些结果状态允许重试。
|
|
38
|
+
retry_on: list[ToolResultStatus] = field(
|
|
39
|
+
default_factory=lambda: [ToolResultStatus.RETRYABLE_ERROR]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class ToolParameter:
|
|
45
|
+
"""工具参数定义。"""
|
|
46
|
+
|
|
47
|
+
# 参数名,与 ToolCall.arguments 中的 key 对应。
|
|
48
|
+
name: str
|
|
49
|
+
# 参数类型描述,例如 str、int、float、bool、dict。
|
|
50
|
+
type: str
|
|
51
|
+
# 参数说明,供 LLM 或调用方理解如何填写。
|
|
52
|
+
description: str
|
|
53
|
+
# 是否必填;缺失必填参数时 before 阶段会拒绝。
|
|
54
|
+
required: bool = True
|
|
55
|
+
# 默认值;非必填参数缺失时可自动补齐。
|
|
56
|
+
default: Any = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class ToolDefinition:
|
|
61
|
+
"""工具能力定义,描述名称、参数、输出和风险等级。"""
|
|
62
|
+
|
|
63
|
+
# 工具唯一名称,注册表按该字段查找 ToolHandler。
|
|
64
|
+
name: str
|
|
65
|
+
# 工具用途说明,供调度器或 LLM 选择工具时使用。
|
|
66
|
+
description: str
|
|
67
|
+
# 工具参数列表。
|
|
68
|
+
parameters: list[ToolParameter] = field(default_factory=list)
|
|
69
|
+
# 输出结构约束;第一版只做轻量存在性校验。
|
|
70
|
+
output_schema: Optional[dict[str, Any]] = None
|
|
71
|
+
# 工具风险等级。
|
|
72
|
+
risk: ToolRisk = ToolRisk.LOW
|
|
73
|
+
# 工具重试策略。
|
|
74
|
+
retry_policy: RetryPolicy = field(default_factory=RetryPolicy)
|
|
75
|
+
# 工具扩展信息,例如标签、版本、外部系统名称。
|
|
76
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class ToolContext:
|
|
81
|
+
"""工具调用上下文,记录任务、步骤和调用侧元数据。"""
|
|
82
|
+
|
|
83
|
+
# 调用所属任务 ID。
|
|
84
|
+
task_id: str
|
|
85
|
+
# 调用所属调度步骤 ID。
|
|
86
|
+
step_id: str
|
|
87
|
+
# 调用用户或主体 ID。
|
|
88
|
+
user: Optional[str] = None
|
|
89
|
+
# 调用侧扩展信息,例如 trace_id、审批信息、环境。
|
|
90
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class ToolCall:
|
|
95
|
+
"""一次工具调用请求。"""
|
|
96
|
+
|
|
97
|
+
# 要调用的工具名称。
|
|
98
|
+
tool_name: str
|
|
99
|
+
# 工具入参。
|
|
100
|
+
arguments: dict[str, Any]
|
|
101
|
+
# 工具调用上下文。
|
|
102
|
+
context: ToolContext
|
|
103
|
+
# 调用 ID,用于日志、重试和幂等;为空时可由调用方后续补齐。
|
|
104
|
+
call_id: Optional[str] = None
|
|
105
|
+
# 调用创建时间。
|
|
106
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class ToolResult:
|
|
111
|
+
"""一次工具调用结果。"""
|
|
112
|
+
|
|
113
|
+
# 调用结果状态。
|
|
114
|
+
status: ToolResultStatus
|
|
115
|
+
# 成功时的返回内容。
|
|
116
|
+
content: Any = None
|
|
117
|
+
# 失败时的错误说明。
|
|
118
|
+
error: Optional[str] = None
|
|
119
|
+
# 是否可重试,通常和 status/retry_policy 联合使用。
|
|
120
|
+
retryable: bool = False
|
|
121
|
+
# 结果扩展信息,例如耗时、尝试次数、校验细节。
|
|
122
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|