ni.agentkit 0.3.1__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.
- agentkit/__init__.py +57 -0
- agentkit/_cli.py +72 -0
- agentkit/agents/__init__.py +0 -0
- agentkit/agents/agent.py +364 -0
- agentkit/agents/base_agent.py +59 -0
- agentkit/agents/orchestrators.py +62 -0
- agentkit/docs/Architecture.md +536 -0
- agentkit/docs/QuickStart.md +806 -0
- agentkit/docs/README.md +119 -0
- agentkit/docs/Reference.md +576 -0
- agentkit/docs/TestReport.md +80 -0
- agentkit/examples/__init__.py +0 -0
- agentkit/examples/ollama/01_basic_chat.py +34 -0
- agentkit/examples/ollama/02_tool_calling.py +70 -0
- agentkit/examples/ollama/03_skill_usage.py +86 -0
- agentkit/examples/ollama/04_multi_agent.py +84 -0
- agentkit/examples/ollama/05_guardrail.py +101 -0
- agentkit/examples/ollama/06_orchestration.py +123 -0
- agentkit/examples/ollama/07_sync_async_stream.py +180 -0
- agentkit/examples/ollama/08_memory.py +201 -0
- agentkit/examples/ollama/README.md +51 -0
- agentkit/examples/ollama/__init__.py +0 -0
- agentkit/examples/quickstart.py +204 -0
- agentkit/examples/standard/01_basic_chat.py +33 -0
- agentkit/examples/standard/02_tool_calling.py +70 -0
- agentkit/examples/standard/03_skill_usage.py +89 -0
- agentkit/examples/standard/04_multi_agent.py +87 -0
- agentkit/examples/standard/05_guardrail.py +104 -0
- agentkit/examples/standard/06_orchestration.py +122 -0
- agentkit/examples/standard/07_sync_async_stream.py +171 -0
- agentkit/examples/standard/08_memory.py +140 -0
- agentkit/examples/standard/README.md +31 -0
- agentkit/examples/standard/__init__.py +0 -0
- agentkit/examples/test_ollama.py +272 -0
- agentkit/llm/__init__.py +0 -0
- agentkit/llm/adapters/__init__.py +0 -0
- agentkit/llm/adapters/anthropic_adapter.py +192 -0
- agentkit/llm/adapters/google_adapter.py +184 -0
- agentkit/llm/adapters/ollama_adapter.py +250 -0
- agentkit/llm/adapters/openai_adapter.py +183 -0
- agentkit/llm/adapters/openai_compatible.py +35 -0
- agentkit/llm/base.py +66 -0
- agentkit/llm/cache.py +121 -0
- agentkit/llm/middleware.py +133 -0
- agentkit/llm/registry.py +138 -0
- agentkit/llm/types.py +191 -0
- agentkit/memory/__init__.py +0 -0
- agentkit/memory/base.py +47 -0
- agentkit/memory/mem0_provider.py +48 -0
- agentkit/runner/__init__.py +0 -0
- agentkit/runner/context.py +47 -0
- agentkit/runner/events.py +36 -0
- agentkit/runner/runner.py +105 -0
- agentkit/safety/__init__.py +0 -0
- agentkit/safety/guardrails.py +55 -0
- agentkit/safety/permissions.py +41 -0
- agentkit/skills/__init__.py +0 -0
- agentkit/skills/loader.py +90 -0
- agentkit/skills/models.py +106 -0
- agentkit/skills/registry.py +48 -0
- agentkit/tools/__init__.py +0 -0
- agentkit/tools/base_tool.py +44 -0
- agentkit/tools/function_tool.py +118 -0
- agentkit/tools/skill_toolset.py +199 -0
- agentkit/utils/__init__.py +0 -0
- agentkit/utils/schema.py +83 -0
- ni_agentkit-0.3.1.dist-info/METADATA +157 -0
- ni_agentkit-0.3.1.dist-info/RECORD +72 -0
- ni_agentkit-0.3.1.dist-info/WHEEL +5 -0
- ni_agentkit-0.3.1.dist-info/entry_points.txt +2 -0
- ni_agentkit-0.3.1.dist-info/licenses/LICENSE +21 -0
- ni_agentkit-0.3.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
示例 8:记忆系统 — 让 Agent 拥有跨会话长期记忆(标准版)
|
|
3
|
+
|
|
4
|
+
演示三种记忆用法:
|
|
5
|
+
A. 无记忆(默认)
|
|
6
|
+
B. SimpleMemory(轻量内存记忆)
|
|
7
|
+
C. Mem0Provider(生产级)
|
|
8
|
+
|
|
9
|
+
运行前请设置环境变量:
|
|
10
|
+
export OPENAI_API_KEY="sk-..."
|
|
11
|
+
|
|
12
|
+
运行:
|
|
13
|
+
python examples/standard/08_memory.py
|
|
14
|
+
"""
|
|
15
|
+
import asyncio
|
|
16
|
+
import sys
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
|
|
20
|
+
|
|
21
|
+
from agentkit import Agent, Runner, BaseMemoryProvider, Memory
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ============================================================
|
|
25
|
+
# SimpleMemory:轻量内存记忆(无需外部依赖)
|
|
26
|
+
# ============================================================
|
|
27
|
+
|
|
28
|
+
class SimpleMemory(BaseMemoryProvider):
|
|
29
|
+
"""最简单的内存记忆实现"""
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self._store: list[Memory] = []
|
|
33
|
+
self._counter = 0
|
|
34
|
+
|
|
35
|
+
async def add(self, content, *, user_id=None, agent_id=None, metadata=None):
|
|
36
|
+
self._counter += 1
|
|
37
|
+
m = Memory(id=str(self._counter), content=content)
|
|
38
|
+
self._store.append(m)
|
|
39
|
+
print(f" 💾 记忆已存储: {content[:60]}...")
|
|
40
|
+
return [m]
|
|
41
|
+
|
|
42
|
+
async def search(self, query, *, user_id=None, agent_id=None, limit=10):
|
|
43
|
+
query_words = set(query)
|
|
44
|
+
scored = []
|
|
45
|
+
for m in self._store:
|
|
46
|
+
overlap = len(query_words & set(m.content))
|
|
47
|
+
if overlap > 0:
|
|
48
|
+
scored.append((overlap, m))
|
|
49
|
+
scored.sort(reverse=True, key=lambda x: x[0])
|
|
50
|
+
results = [m for _, m in scored[:limit]]
|
|
51
|
+
if results:
|
|
52
|
+
print(f" 🔍 检索到 {len(results)} 条相关记忆")
|
|
53
|
+
return results
|
|
54
|
+
|
|
55
|
+
async def get_all(self, *, user_id=None, agent_id=None):
|
|
56
|
+
return list(self._store)
|
|
57
|
+
|
|
58
|
+
async def delete(self, memory_id):
|
|
59
|
+
self._store = [m for m in self._store if m.id != memory_id]
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ============================================================
|
|
64
|
+
# 演示 A:无记忆
|
|
65
|
+
# ============================================================
|
|
66
|
+
|
|
67
|
+
async def demo_no_memory():
|
|
68
|
+
print("=" * 55)
|
|
69
|
+
print(" A. 无记忆(默认)— 每次对话独立")
|
|
70
|
+
print("=" * 55)
|
|
71
|
+
|
|
72
|
+
agent = Agent(
|
|
73
|
+
name="forgetful",
|
|
74
|
+
instructions="你是一个简洁的助手。回答尽量简短。",
|
|
75
|
+
model="gpt-4o",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
result = await Runner.run(agent, input="我叫小明,我喜欢喝咖啡")
|
|
79
|
+
print(f" 对话1: {result.final_output}")
|
|
80
|
+
|
|
81
|
+
result = await Runner.run(agent, input="我叫什么名字?我喜欢喝什么?")
|
|
82
|
+
print(f" 对话2: {result.final_output}")
|
|
83
|
+
print(" 📝 无记忆 → Agent 不记得之前的对话\n")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# ============================================================
|
|
87
|
+
# 演示 B:SimpleMemory
|
|
88
|
+
# ============================================================
|
|
89
|
+
|
|
90
|
+
async def demo_simple_memory():
|
|
91
|
+
print("=" * 55)
|
|
92
|
+
print(" B. SimpleMemory — 轻量内存记忆")
|
|
93
|
+
print("=" * 55)
|
|
94
|
+
|
|
95
|
+
memory = SimpleMemory()
|
|
96
|
+
|
|
97
|
+
agent = Agent(
|
|
98
|
+
name="remembering",
|
|
99
|
+
instructions="你是一个贴心的个人助手。根据相关记忆来个性化回答。回答简洁。",
|
|
100
|
+
model="gpt-4o",
|
|
101
|
+
memory=memory,
|
|
102
|
+
memory_async_write=False, # 多轮串行对话需要即时读取记忆
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
print("\n 第1轮:")
|
|
106
|
+
result = await Runner.run(agent, input="我叫小明,我喜欢喝咖啡,讨厌喝茶", user_id="user_001")
|
|
107
|
+
print(f" 助手: {result.final_output}")
|
|
108
|
+
|
|
109
|
+
print("\n 第2轮:")
|
|
110
|
+
result = await Runner.run(agent, input="帮我推荐一杯饮料吧", user_id="user_001")
|
|
111
|
+
print(f" 助手: {result.final_output}")
|
|
112
|
+
|
|
113
|
+
print("\n 第3轮:")
|
|
114
|
+
result = await Runner.run(agent, input="对了,我对牛奶过敏", user_id="user_001")
|
|
115
|
+
print(f" 助手: {result.final_output}")
|
|
116
|
+
|
|
117
|
+
print("\n 第4轮:")
|
|
118
|
+
result = await Runner.run(agent, input="再给我推荐一杯饮料,要考虑我的情况", user_id="user_001")
|
|
119
|
+
print(f" 助手: {result.final_output}")
|
|
120
|
+
|
|
121
|
+
all_memories = await memory.get_all()
|
|
122
|
+
print(f"\n 📋 记忆库中共 {len(all_memories)} 条记忆")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ============================================================
|
|
126
|
+
# 主入口
|
|
127
|
+
# ============================================================
|
|
128
|
+
|
|
129
|
+
async def main():
|
|
130
|
+
print("🚀 AgentKit 记忆系统演示\n")
|
|
131
|
+
await demo_no_memory()
|
|
132
|
+
await demo_simple_memory()
|
|
133
|
+
|
|
134
|
+
print(f"\n{'=' * 55}")
|
|
135
|
+
print(" 演示完成 🎉")
|
|
136
|
+
print("=" * 55)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 标准示例(需要 API Key)
|
|
2
|
+
|
|
3
|
+
使用 OpenAI GPT-4o 模型的示例。运行前请设置:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
export OPENAI_API_KEY="sk-..."
|
|
7
|
+
pip install openai>=1.0.0
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## 示例列表
|
|
11
|
+
|
|
12
|
+
| 文件 | 内容 | 对应教程 |
|
|
13
|
+
|------|------|---------|
|
|
14
|
+
| `01_basic_chat.py` | 最简 Agent — 纯对话 | QuickStart 示例 1 |
|
|
15
|
+
| `02_tool_calling.py` | 带工具的 Agent — Function Calling | QuickStart 示例 2 |
|
|
16
|
+
| `03_skill_usage.py` | 带 Skill 的 Agent — 领域知识包 | QuickStart 示例 3 |
|
|
17
|
+
| `04_multi_agent.py` | 多 Agent 协作 — Handoff 与 as_tool | QuickStart 示例 4 |
|
|
18
|
+
| `05_guardrail.py` | 安全护栏 — Guardrail 与权限控制 | QuickStart 示例 5 |
|
|
19
|
+
| `06_orchestration.py` | 编排 Agent — 流水线与循环 | QuickStart 示例 6 |
|
|
20
|
+
| `07_sync_async_stream.py` | 三种运行方式 — 同步/异步/流式 | 同步与异步 |
|
|
21
|
+
| `08_memory.py` | 记忆系统 — 跨会话长期记忆 | 记忆系统 |
|
|
22
|
+
|
|
23
|
+
## 运行
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python examples/standard/01_basic_chat.py
|
|
27
|
+
python examples/standard/02_tool_calling.py
|
|
28
|
+
# ... 以此类推
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> 💡 如果没有 OpenAI API Key,请使用 `examples/ollama/` 目录中的 Ollama 本地版示例。
|
|
File without changes
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
examples/test_ollama.py — 使用本地 Ollama (qwen3.5:cloud) 测试 AgentKit 框架
|
|
3
|
+
|
|
4
|
+
测试内容:
|
|
5
|
+
1. 基础对话(纯文本)
|
|
6
|
+
2. 工具调用(function calling)
|
|
7
|
+
3. Skill 使用(三级加载)
|
|
8
|
+
4. 安全护栏
|
|
9
|
+
"""
|
|
10
|
+
import asyncio
|
|
11
|
+
import sys
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
15
|
+
|
|
16
|
+
from agentkit.agents.agent import Agent
|
|
17
|
+
from agentkit.runner.runner import Runner
|
|
18
|
+
from agentkit.tools.function_tool import function_tool
|
|
19
|
+
from agentkit.skills.models import Skill, SkillFrontmatter, SkillResources
|
|
20
|
+
from agentkit.safety.guardrails import input_guardrail, GuardrailResult
|
|
21
|
+
from agentkit.llm.registry import LLMRegistry
|
|
22
|
+
|
|
23
|
+
# 使用的模型
|
|
24
|
+
MODEL = "ollama/qwen3.5:cloud"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def separator(title: str):
|
|
28
|
+
print(f"\n{'='*60}")
|
|
29
|
+
print(f" {title}")
|
|
30
|
+
print(f"{'='*60}\n")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ============================================================
|
|
34
|
+
# 测试 1:基础对话
|
|
35
|
+
# ============================================================
|
|
36
|
+
|
|
37
|
+
async def test_basic_chat():
|
|
38
|
+
separator("测试 1:基础对话")
|
|
39
|
+
|
|
40
|
+
agent = Agent(
|
|
41
|
+
name="basic-assistant",
|
|
42
|
+
instructions="你是一个简洁的中文助手。回答尽量简短。",
|
|
43
|
+
model=MODEL,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
result = await Runner.run(agent, input="请用一句话解释什么是人工智能。")
|
|
47
|
+
if result.success:
|
|
48
|
+
print(f"✅ 回复: {result.final_output}")
|
|
49
|
+
else:
|
|
50
|
+
print(f"❌ 错误: {result.error}")
|
|
51
|
+
|
|
52
|
+
# 打印事件流
|
|
53
|
+
for event in result.events:
|
|
54
|
+
print(f" [{event.type}] {str(event.data)[:80]}...")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ============================================================
|
|
58
|
+
# 测试 2:工具调用(Function Calling)
|
|
59
|
+
# ============================================================
|
|
60
|
+
|
|
61
|
+
@function_tool
|
|
62
|
+
def add(a: int, b: int) -> str:
|
|
63
|
+
"""两个数字相加"""
|
|
64
|
+
return str(a + b)
|
|
65
|
+
|
|
66
|
+
@function_tool
|
|
67
|
+
def multiply(a: int, b: int) -> str:
|
|
68
|
+
"""两个数字相乘"""
|
|
69
|
+
return str(a * b)
|
|
70
|
+
|
|
71
|
+
@function_tool
|
|
72
|
+
def get_weather(city: str) -> str:
|
|
73
|
+
"""获取指定城市的天气信息"""
|
|
74
|
+
weather_data = {
|
|
75
|
+
"北京": "晴,25°C",
|
|
76
|
+
"上海": "多云,22°C",
|
|
77
|
+
"深圳": "阵雨,28°C",
|
|
78
|
+
}
|
|
79
|
+
return weather_data.get(city, f"{city}:暂无数据")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def test_tool_calling():
|
|
83
|
+
separator("测试 2:工具调用")
|
|
84
|
+
|
|
85
|
+
agent = Agent(
|
|
86
|
+
name="math-assistant",
|
|
87
|
+
instructions="你是一个数学助手。需要计算时,请使用提供的工具。回答简洁。",
|
|
88
|
+
model=MODEL,
|
|
89
|
+
tools=[add, multiply],
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
result = await Runner.run(agent, input="请计算 15 + 27 的结果")
|
|
93
|
+
if result.success:
|
|
94
|
+
print(f"✅ 回复: {result.final_output}")
|
|
95
|
+
else:
|
|
96
|
+
print(f"❌ 错误: {result.error}")
|
|
97
|
+
|
|
98
|
+
# 打印事件
|
|
99
|
+
for event in result.events:
|
|
100
|
+
if event.type == "tool_result":
|
|
101
|
+
print(f" 🔧 工具调用: {event.data}")
|
|
102
|
+
elif event.type == "final_output":
|
|
103
|
+
print(f" 📝 最终输出: {event.data}")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def test_weather_tool():
|
|
107
|
+
separator("测试 2b:天气查询工具")
|
|
108
|
+
|
|
109
|
+
agent = Agent(
|
|
110
|
+
name="weather-assistant",
|
|
111
|
+
instructions="你是一个天气助手。用户问天气时,使用 get_weather 工具查询。回答简洁。",
|
|
112
|
+
model=MODEL,
|
|
113
|
+
tools=[get_weather],
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
result = await Runner.run(agent, input="北京今天天气怎么样?")
|
|
117
|
+
if result.success:
|
|
118
|
+
print(f"✅ 回复: {result.final_output}")
|
|
119
|
+
else:
|
|
120
|
+
print(f"❌ 错误: {result.error}")
|
|
121
|
+
|
|
122
|
+
for event in result.events:
|
|
123
|
+
if event.type == "tool_result":
|
|
124
|
+
print(f" 🔧 工具调用: {event.data}")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ============================================================
|
|
128
|
+
# 测试 3:Skill 使用
|
|
129
|
+
# ============================================================
|
|
130
|
+
|
|
131
|
+
async def test_skill():
|
|
132
|
+
separator("测试 3:Skill 使用")
|
|
133
|
+
|
|
134
|
+
# 定义一个天气分析 Skill
|
|
135
|
+
weather_skill = Skill(
|
|
136
|
+
frontmatter=SkillFrontmatter(
|
|
137
|
+
name="weather-analysis",
|
|
138
|
+
description="天气分析技能,能够查询天气并给出穿衣建议",
|
|
139
|
+
),
|
|
140
|
+
instructions="""## 天气分析步骤
|
|
141
|
+
|
|
142
|
+
1. 使用 get_weather 工具查询用户指定城市的天气
|
|
143
|
+
2. 根据天气情况给出穿衣建议:
|
|
144
|
+
- 温度 > 30°C:建议穿短袖
|
|
145
|
+
- 温度 20-30°C:建议穿薄外套
|
|
146
|
+
- 温度 < 20°C:建议穿厚外套
|
|
147
|
+
3. 用简洁的中文回复用户""",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
agent = Agent(
|
|
151
|
+
name="skill-agent",
|
|
152
|
+
instructions="你是一个智能助手,可以使用 Skill 来完成复杂任务。",
|
|
153
|
+
model=MODEL,
|
|
154
|
+
skills=[weather_skill],
|
|
155
|
+
tools=[get_weather],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
result = await Runner.run(agent, input="深圳今天适合穿什么衣服?")
|
|
159
|
+
if result.success:
|
|
160
|
+
print(f"✅ 回复: {result.final_output}")
|
|
161
|
+
else:
|
|
162
|
+
print(f"❌ 错误: {result.error}")
|
|
163
|
+
|
|
164
|
+
for event in result.events:
|
|
165
|
+
if event.type in ("tool_result", "final_output"):
|
|
166
|
+
print(f" [{event.type}] {str(event.data)[:100]}")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ============================================================
|
|
170
|
+
# 测试 4:安全护栏
|
|
171
|
+
# ============================================================
|
|
172
|
+
|
|
173
|
+
@input_guardrail
|
|
174
|
+
async def block_sensitive_words(ctx):
|
|
175
|
+
sensitive = ["密码", "身份证", "银行卡号"]
|
|
176
|
+
for word in sensitive:
|
|
177
|
+
if word in ctx.input:
|
|
178
|
+
return GuardrailResult(triggered=True, reason=f"包含敏感词: {word}")
|
|
179
|
+
return GuardrailResult(triggered=False)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def test_guardrail():
|
|
183
|
+
separator("测试 4:安全护栏")
|
|
184
|
+
|
|
185
|
+
agent = Agent(
|
|
186
|
+
name="safe-agent",
|
|
187
|
+
instructions="你是一个安全的助手。",
|
|
188
|
+
model=MODEL,
|
|
189
|
+
input_guardrails=[block_sensitive_words],
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# 测试 4a:敏感请求应被拦截
|
|
193
|
+
result = await Runner.run(agent, input="请告诉我你的密码")
|
|
194
|
+
if result.error:
|
|
195
|
+
print(f"✅ 敏感请求被正确拦截: {result.error}")
|
|
196
|
+
else:
|
|
197
|
+
print(f"❌ 敏感请求未被拦截!输出: {result.final_output}")
|
|
198
|
+
|
|
199
|
+
# 测试 4b:正常请求应通过
|
|
200
|
+
result = await Runner.run(agent, input="你好,今天天气怎么样?")
|
|
201
|
+
if result.success:
|
|
202
|
+
print(f"✅ 正常请求通过: {result.final_output}")
|
|
203
|
+
else:
|
|
204
|
+
print(f"❌ 正常请求被误拦截: {result.error}")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ============================================================
|
|
208
|
+
# 测试 5:多工具 Agent
|
|
209
|
+
# ============================================================
|
|
210
|
+
|
|
211
|
+
async def test_multi_tool():
|
|
212
|
+
separator("测试 5:多工具组合")
|
|
213
|
+
|
|
214
|
+
agent = Agent(
|
|
215
|
+
name="multi-tool-agent",
|
|
216
|
+
instructions="你是一个全能助手。可以做数学计算,也可以查天气。根据用户需求选择合适的工具。回答简洁。",
|
|
217
|
+
model=MODEL,
|
|
218
|
+
tools=[add, multiply, get_weather],
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
queries = [
|
|
222
|
+
"3乘以7等于多少?",
|
|
223
|
+
"上海今天天气如何?",
|
|
224
|
+
"100加200是多少?",
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
for q in queries:
|
|
228
|
+
print(f" 用户: {q}")
|
|
229
|
+
result = await Runner.run(agent, input=q)
|
|
230
|
+
if result.success:
|
|
231
|
+
print(f" 助手: {result.final_output}")
|
|
232
|
+
else:
|
|
233
|
+
print(f" ❌ 错误: {result.error}")
|
|
234
|
+
|
|
235
|
+
# 展示工具调用
|
|
236
|
+
for event in result.events:
|
|
237
|
+
if event.type == "tool_result":
|
|
238
|
+
print(f" 🔧 {event.data}")
|
|
239
|
+
print()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ============================================================
|
|
243
|
+
# 主入口
|
|
244
|
+
# ============================================================
|
|
245
|
+
|
|
246
|
+
async def main():
|
|
247
|
+
print("🚀 AgentKit 框架测试 — 使用 Ollama qwen3.5:cloud")
|
|
248
|
+
print(f" 模型标识: {MODEL}")
|
|
249
|
+
|
|
250
|
+
# 验证 Ollama 连接
|
|
251
|
+
try:
|
|
252
|
+
llm = LLMRegistry.create(MODEL)
|
|
253
|
+
print(f" 适配器: {type(llm).__name__}")
|
|
254
|
+
print(f" 实际模型: {llm.config.model}")
|
|
255
|
+
print(f" API 端点: {llm.config.api_base or llm._base_url}")
|
|
256
|
+
except Exception as e:
|
|
257
|
+
print(f"❌ 无法创建 LLM 实例: {e}")
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
# 依次运行测试
|
|
261
|
+
await test_basic_chat()
|
|
262
|
+
await test_tool_calling()
|
|
263
|
+
await test_weather_tool()
|
|
264
|
+
await test_skill()
|
|
265
|
+
await test_guardrail()
|
|
266
|
+
await test_multi_tool()
|
|
267
|
+
|
|
268
|
+
separator("全部测试完成 🎉")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
if __name__ == "__main__":
|
|
272
|
+
asyncio.run(main())
|
agentkit/llm/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agentkit/llm/adapters/anthropic_adapter.py — Anthropic Claude 系列适配器
|
|
3
|
+
|
|
4
|
+
差异最大的适配器:
|
|
5
|
+
1. system 消息单独传,不在 messages 中
|
|
6
|
+
2. tool_use 返回在 content 块中,不在独立字段
|
|
7
|
+
3. tool_result 通过 user 消息中的 content 块传递
|
|
8
|
+
4. 参数直接是 dict,不是 JSON 字符串
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any, AsyncGenerator
|
|
13
|
+
|
|
14
|
+
from ..base import BaseLLM
|
|
15
|
+
from ..types import (
|
|
16
|
+
FinishReason,
|
|
17
|
+
LLMConfig,
|
|
18
|
+
LLMResponse,
|
|
19
|
+
Message,
|
|
20
|
+
MessageRole,
|
|
21
|
+
StreamChunk,
|
|
22
|
+
ToolCall,
|
|
23
|
+
ToolDefinition,
|
|
24
|
+
Usage,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AnthropicAdapter(BaseLLM):
|
|
29
|
+
"""Anthropic Claude 系列适配器"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, config: LLMConfig):
|
|
32
|
+
super().__init__(config)
|
|
33
|
+
try:
|
|
34
|
+
from anthropic import AsyncAnthropic
|
|
35
|
+
except ImportError:
|
|
36
|
+
raise ImportError("请安装 anthropic: pip install 'agentkit[anthropic]'")
|
|
37
|
+
self._client = AsyncAnthropic(
|
|
38
|
+
api_key=config.api_key,
|
|
39
|
+
base_url=config.api_base,
|
|
40
|
+
timeout=config.timeout,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
async def generate(
|
|
44
|
+
self,
|
|
45
|
+
messages: list[Message],
|
|
46
|
+
*,
|
|
47
|
+
tools: list[ToolDefinition] | None = None,
|
|
48
|
+
tool_choice: str | None = None,
|
|
49
|
+
output_schema: type | None = None,
|
|
50
|
+
) -> LLMResponse:
|
|
51
|
+
system_prompt, anthropic_messages = self._split_system_messages(messages)
|
|
52
|
+
|
|
53
|
+
kwargs: dict[str, Any] = {
|
|
54
|
+
"model": self.config.model,
|
|
55
|
+
"messages": anthropic_messages,
|
|
56
|
+
"max_tokens": self.config.max_tokens or 4096,
|
|
57
|
+
"temperature": self.config.temperature,
|
|
58
|
+
"top_p": self.config.top_p,
|
|
59
|
+
**self.config.extra_params,
|
|
60
|
+
}
|
|
61
|
+
if system_prompt:
|
|
62
|
+
kwargs["system"] = system_prompt
|
|
63
|
+
if tools:
|
|
64
|
+
kwargs["tools"] = [t.to_anthropic_format() for t in tools]
|
|
65
|
+
if tool_choice:
|
|
66
|
+
kwargs["tool_choice"] = self._map_tool_choice(tool_choice)
|
|
67
|
+
|
|
68
|
+
response = await self._client.messages.create(**kwargs)
|
|
69
|
+
return self._parse_response(response)
|
|
70
|
+
|
|
71
|
+
async def generate_stream(
|
|
72
|
+
self,
|
|
73
|
+
messages: list[Message],
|
|
74
|
+
*,
|
|
75
|
+
tools: list[ToolDefinition] | None = None,
|
|
76
|
+
tool_choice: str | None = None,
|
|
77
|
+
) -> AsyncGenerator[StreamChunk, None]:
|
|
78
|
+
system_prompt, anthropic_messages = self._split_system_messages(messages)
|
|
79
|
+
|
|
80
|
+
kwargs: dict[str, Any] = {
|
|
81
|
+
"model": self.config.model,
|
|
82
|
+
"messages": anthropic_messages,
|
|
83
|
+
"max_tokens": self.config.max_tokens or 4096,
|
|
84
|
+
**self.config.extra_params,
|
|
85
|
+
}
|
|
86
|
+
if system_prompt:
|
|
87
|
+
kwargs["system"] = system_prompt
|
|
88
|
+
if tools:
|
|
89
|
+
kwargs["tools"] = [t.to_anthropic_format() for t in tools]
|
|
90
|
+
|
|
91
|
+
async with self._client.messages.stream(**kwargs) as stream:
|
|
92
|
+
async for event in stream:
|
|
93
|
+
chunk = self._parse_stream_event(event)
|
|
94
|
+
if chunk:
|
|
95
|
+
yield chunk
|
|
96
|
+
|
|
97
|
+
# ------------------------------------------------------------------
|
|
98
|
+
# 内部转换
|
|
99
|
+
# ------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def _split_system_messages(self, messages: list[Message]) -> tuple[str | None, list[dict]]:
|
|
102
|
+
system_parts: list[str] = []
|
|
103
|
+
other: list[dict] = []
|
|
104
|
+
for msg in messages:
|
|
105
|
+
if msg.role == MessageRole.SYSTEM:
|
|
106
|
+
system_parts.append(msg.content or "")
|
|
107
|
+
else:
|
|
108
|
+
other.append(self._convert_message(msg))
|
|
109
|
+
system_prompt = "\n\n".join(system_parts) if system_parts else None
|
|
110
|
+
return system_prompt, other
|
|
111
|
+
|
|
112
|
+
def _convert_message(self, msg: Message) -> dict:
|
|
113
|
+
if msg.role == MessageRole.ASSISTANT:
|
|
114
|
+
content: list[dict] = []
|
|
115
|
+
if msg.content:
|
|
116
|
+
content.append({"type": "text", "text": msg.content})
|
|
117
|
+
for tc in msg.tool_calls:
|
|
118
|
+
content.append({
|
|
119
|
+
"type": "tool_use",
|
|
120
|
+
"id": tc.id,
|
|
121
|
+
"name": tc.name,
|
|
122
|
+
"input": tc.arguments, # Anthropic 用 dict
|
|
123
|
+
})
|
|
124
|
+
return {"role": "assistant", "content": content}
|
|
125
|
+
|
|
126
|
+
if msg.role == MessageRole.TOOL:
|
|
127
|
+
return {
|
|
128
|
+
"role": "user",
|
|
129
|
+
"content": [{
|
|
130
|
+
"type": "tool_result",
|
|
131
|
+
"tool_use_id": msg.tool_call_id,
|
|
132
|
+
"content": msg.content or "",
|
|
133
|
+
}],
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {"role": msg.role.value, "content": msg.content or ""}
|
|
137
|
+
|
|
138
|
+
def _parse_response(self, response: Any) -> LLMResponse:
|
|
139
|
+
text_parts: list[str] = []
|
|
140
|
+
tool_calls: list[ToolCall] = []
|
|
141
|
+
|
|
142
|
+
for block in response.content:
|
|
143
|
+
if block.type == "text":
|
|
144
|
+
text_parts.append(block.text)
|
|
145
|
+
elif block.type == "tool_use":
|
|
146
|
+
tool_calls.append(ToolCall(id=block.id, name=block.name, arguments=block.input))
|
|
147
|
+
|
|
148
|
+
content = "\n".join(text_parts) if text_parts else None
|
|
149
|
+
return LLMResponse(
|
|
150
|
+
content=content,
|
|
151
|
+
tool_calls=tool_calls,
|
|
152
|
+
finish_reason=self._map_stop_reason(response.stop_reason),
|
|
153
|
+
usage=Usage(
|
|
154
|
+
prompt_tokens=response.usage.input_tokens,
|
|
155
|
+
completion_tokens=response.usage.output_tokens,
|
|
156
|
+
),
|
|
157
|
+
model=response.model,
|
|
158
|
+
_raw=response,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def _map_stop_reason(reason: str | None) -> FinishReason:
|
|
163
|
+
mapping = {
|
|
164
|
+
"end_turn": FinishReason.STOP,
|
|
165
|
+
"tool_use": FinishReason.TOOL_CALLS,
|
|
166
|
+
"max_tokens": FinishReason.LENGTH,
|
|
167
|
+
}
|
|
168
|
+
return mapping.get(reason or "", FinishReason.STOP)
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def _map_tool_choice(choice: str) -> dict:
|
|
172
|
+
if choice == "auto":
|
|
173
|
+
return {"type": "auto"}
|
|
174
|
+
if choice == "required":
|
|
175
|
+
return {"type": "any"}
|
|
176
|
+
if choice == "none":
|
|
177
|
+
return {"type": "none"}
|
|
178
|
+
return {"type": "tool", "name": choice}
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def _parse_stream_event(event: Any) -> StreamChunk | None:
|
|
182
|
+
if hasattr(event, "type"):
|
|
183
|
+
if event.type == "content_block_delta" and hasattr(event.delta, "text"):
|
|
184
|
+
return StreamChunk(delta_content=event.delta.text)
|
|
185
|
+
if event.type == "message_delta" and hasattr(event.delta, "stop_reason") and event.delta.stop_reason:
|
|
186
|
+
return StreamChunk(
|
|
187
|
+
finish_reason=AnthropicAdapter._map_stop_reason(event.delta.stop_reason)
|
|
188
|
+
)
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
def supports_structured_output(self) -> bool:
|
|
192
|
+
return True
|