rootdriver 0.1.0__tar.gz

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.
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: rootdriver
3
+ Version: 0.1.0
4
+ Summary: Rooted in Origin, Driving All Things
5
+ Author-email: zimvir <zimvir@qq.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: openai>=1.0.0
10
+ Requires-Dist: pydantic>=2.0.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
13
+
14
+ # RootDriver
15
+
16
+ 根源出发, 驱动万物
17
+
18
+ 一个轻量级的 Python AI Agent 开发框架。
19
+
20
+ ## 特性
21
+
22
+ - **简洁易用**:装饰器方式定义工具,快速构建 Agent
23
+ - **模块化设计**:LLM 适配器、工具系统、会话管理解耦
24
+ - **工具调用**:支持 function calling,自动执行工具并返回结果
25
+ - **多适配器**:基于适配器模式,方便扩展其他 LLM Provider
26
+
27
+ ## 安装
28
+
29
+ ```bash
30
+ pip install rootdriver
31
+ ```
32
+
33
+ ## 快速开始
34
+
35
+ ### 定义工具
36
+
37
+ ```python
38
+ from rootdriver import tool
39
+
40
+ @tool
41
+ def get_weather(city: str) -> str:
42
+ """获取城市天气"""
43
+ return f"{city} 晴天"
44
+ ```
45
+
46
+ ### 创建 Agent
47
+
48
+ ```python
49
+ from rootdriver import Agent, AgentLLM, OpenAIAdapter
50
+ agent_llm=AgentLLM(
51
+ adapter=OpenAIAdapter(
52
+ api_key="YOUR_API_KEY",
53
+ base_url="BASE_URL"
54
+ ),
55
+ model="gpt-4",
56
+
57
+ ),
58
+ agent = Agent(
59
+ agent_llm=agent_llm,
60
+ tools=[get_weather],
61
+ system_prompt="你是一个有用的助手",
62
+ )
63
+
64
+ # 单次对话
65
+ response = agent.talk("北京天气怎么样?")
66
+ print(response)
67
+ ```
68
+
69
+ ### 使用工具
70
+
71
+ ```python
72
+ # 完整对话循环(包含工具调用)
73
+ response = Agent.react("帮我查下上海天气")
74
+ print(response)
75
+ ```
76
+
77
+ ## 核心组件
78
+
79
+ | 组件 | 说明 |
80
+ |------|------|
81
+ | `Agent` | 智能体入口,整合 LLM、工具、会话 |
82
+ | `Engine` | 核心引擎,处理对话循环和工具调用 |
83
+ | `Conversation` | 会话管理,维护消息历史 |
84
+ | `LLM` | LLM 调用封装 |
85
+ | `Tool` | 工具集合,管理所有可调用工具 |
86
+
87
+ ## 项目结构
88
+
89
+ ```
90
+ rootdriver/
91
+ ├── agent.py # Agent 智能体
92
+ ├── engine.py # 引擎核心
93
+ ├── conversation.py # 对话管理
94
+ ├── llm/ # LLM 适配器
95
+ ├── tool/ # 工具系统
96
+ ├── types/ # 类型定义
97
+ └── utils/ # 工具函数
98
+ ```
99
+
100
+ ## License
101
+
102
+ MIT
@@ -0,0 +1,89 @@
1
+ # RootDriver
2
+
3
+ 根源出发, 驱动万物
4
+
5
+ 一个轻量级的 Python AI Agent 开发框架。
6
+
7
+ ## 特性
8
+
9
+ - **简洁易用**:装饰器方式定义工具,快速构建 Agent
10
+ - **模块化设计**:LLM 适配器、工具系统、会话管理解耦
11
+ - **工具调用**:支持 function calling,自动执行工具并返回结果
12
+ - **多适配器**:基于适配器模式,方便扩展其他 LLM Provider
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ pip install rootdriver
18
+ ```
19
+
20
+ ## 快速开始
21
+
22
+ ### 定义工具
23
+
24
+ ```python
25
+ from rootdriver import tool
26
+
27
+ @tool
28
+ def get_weather(city: str) -> str:
29
+ """获取城市天气"""
30
+ return f"{city} 晴天"
31
+ ```
32
+
33
+ ### 创建 Agent
34
+
35
+ ```python
36
+ from rootdriver import Agent, AgentLLM, OpenAIAdapter
37
+ agent_llm=AgentLLM(
38
+ adapter=OpenAIAdapter(
39
+ api_key="YOUR_API_KEY",
40
+ base_url="BASE_URL"
41
+ ),
42
+ model="gpt-4",
43
+
44
+ ),
45
+ agent = Agent(
46
+ agent_llm=agent_llm,
47
+ tools=[get_weather],
48
+ system_prompt="你是一个有用的助手",
49
+ )
50
+
51
+ # 单次对话
52
+ response = agent.talk("北京天气怎么样?")
53
+ print(response)
54
+ ```
55
+
56
+ ### 使用工具
57
+
58
+ ```python
59
+ # 完整对话循环(包含工具调用)
60
+ response = Agent.react("帮我查下上海天气")
61
+ print(response)
62
+ ```
63
+
64
+ ## 核心组件
65
+
66
+ | 组件 | 说明 |
67
+ |------|------|
68
+ | `Agent` | 智能体入口,整合 LLM、工具、会话 |
69
+ | `Engine` | 核心引擎,处理对话循环和工具调用 |
70
+ | `Conversation` | 会话管理,维护消息历史 |
71
+ | `LLM` | LLM 调用封装 |
72
+ | `Tool` | 工具集合,管理所有可调用工具 |
73
+
74
+ ## 项目结构
75
+
76
+ ```
77
+ rootdriver/
78
+ ├── agent.py # Agent 智能体
79
+ ├── engine.py # 引擎核心
80
+ ├── conversation.py # 对话管理
81
+ ├── llm/ # LLM 适配器
82
+ ├── tool/ # 工具系统
83
+ ├── types/ # 类型定义
84
+ └── utils/ # 工具函数
85
+ ```
86
+
87
+ ## License
88
+
89
+ MIT
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "rootdriver"
7
+ version = "0.1.0"
8
+ description = "Rooted in Origin, Driving All Things"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ authors = [
12
+ {name = "zimvir", email = "zimvir@qq.com"}
13
+ ]
14
+ requires-python = ">=3.10"
15
+ dependencies = [
16
+ "openai>=1.0.0",
17
+ "pydantic>=2.0.0",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ dev = [
22
+ "pytest>=7.0.0",
23
+ ]
24
+
25
+ [tool.setuptools]
26
+ packages = ["rootdriver"]
@@ -0,0 +1,32 @@
1
+ __version__ = "0.1.0"
2
+ __author__ = "zimvir"
3
+ __email__ = "zimvir@qq.com"
4
+
5
+
6
+
7
+
8
+
9
+ from .agent import Agent
10
+ from .engine import Engine
11
+ from .conversation import Conversation
12
+ from .llm import LLM
13
+ from .llm.base_adapter import BaseAdapter
14
+ from .llm.adapter import OpenAIAdapter
15
+ from .tool import tool, Tool
16
+ from .types.agent import AgentLLM
17
+ from .types import Message, ToolDefinition, ToolCall
18
+
19
+ __all__ = [
20
+ "Agent",
21
+ "Engine",
22
+ "Conversation",
23
+ "LLM",
24
+ "BaseAdapter",
25
+ "OpenAIAdapter",
26
+ "tool",
27
+ "Tool",
28
+ "AgentLLM",
29
+ "Message",
30
+ "ToolDefinition",
31
+ "ToolCall",
32
+ ]
@@ -0,0 +1,48 @@
1
+ from pathlib import Path
2
+ from uuid import uuid4
3
+
4
+ from .engine import Engine
5
+ from .llm import LLM
6
+ from .conversation import Conversation
7
+ from .tool import Tool, BaseTool
8
+ from .types.agent import AgentLLM
9
+ from .types import ToolDefinition
10
+ from .utils import build_system_message, build_message, build_tool_message, build_user_message, build_assistant_message
11
+
12
+ class Agent:
13
+
14
+ def __init__(
15
+ self,
16
+ agent_llm: AgentLLM,
17
+ *,
18
+ tools: list[BaseTool] | None = None,
19
+ system_prompt: str | None = None,
20
+ id: str | None = None,
21
+ # save_path: str | Path | None = None,
22
+ # max_retries: int = 3,
23
+ timeout: float | None = None,
24
+ ):
25
+ self.conversation = Conversation(system_prompt)
26
+ self.tool = Tool(tools if tools else [])
27
+ self.id = id if id else uuid4().hex
28
+ # self.save_path = save_path
29
+
30
+ self.engine = Engine(
31
+ model=agent_llm.model,
32
+ llm=LLM(agent_llm.adapter),
33
+ conversation=self.conversation,
34
+ tool=self.tool,
35
+ )
36
+
37
+
38
+ def react(self, input_prompt:str) -> "str":
39
+ """一次 react 循环"""
40
+ response = self.engine.run(build_user_message(input_prompt))
41
+ return response.content
42
+
43
+ def talk(self, input_prompt:str) -> "str":
44
+ """一次 agent 调用"""
45
+ return self.engine.invoke(build_user_message(input_prompt)).content
46
+
47
+
48
+
@@ -0,0 +1,2 @@
1
+ ROLE_ENUM = ("system", "user", "assistant", "tool")
2
+
@@ -0,0 +1,72 @@
1
+ from .types import Message
2
+ from .utils import get_iso_timestamp, build_system_message
3
+
4
+
5
+ class Conversation:
6
+ def __init__(self, system_prompt: str = None):
7
+ self.messages: list[Message] = []
8
+ self.system_prompt = system_prompt
9
+ if self.system_prompt:
10
+ self.append(build_system_message(system_prompt))
11
+
12
+ def append(self, message: Message) -> "Conversation":
13
+ self.messages.append(message)
14
+ return self
15
+
16
+ def append_many(self, messages: list[Message]) -> "Conversation":
17
+ self.messages.extend(messages)
18
+ return self
19
+
20
+ # def append_user(self, content: str) -> "Conversation":
21
+ # self.messages.append(Message(role="user", content=content, created_at=get_iso_timestamp()))
22
+ # return self
23
+ #
24
+ # def append_assistant(self, content: str | None = None, tool_calls: list | None = None) -> "Conversation":
25
+ # msg = Message(role="assistant", content=content, created_at=get_iso_timestamp())
26
+ # if tool_calls:
27
+ # msg.tool_calls = tool_calls
28
+ # self.messages.append(msg)
29
+ # return self
30
+ #
31
+ # def append_tool(self, tool_call_id: str, content: str) -> "Conversation":
32
+ # self.messages.append(Message(
33
+ # role="tool",
34
+ # tool_call_id=tool_call_id,
35
+ # content=content,
36
+ # created_at=get_iso_timestamp()
37
+ # ))
38
+ # return self
39
+
40
+ def append_system(self, content: str) -> "Conversation":
41
+ self.messages.append(Message(role="system", content=content, created_at=get_iso_timestamp()))
42
+ return self
43
+
44
+ def delete(self, index: int=-1) -> "Conversation":
45
+ self.messages.pop(index)
46
+ return self
47
+
48
+ def get_messages(self) -> list[Message]:
49
+ '''返回 list[message<obj>]'''
50
+ return self.messages
51
+
52
+ def get_messages_in_list(self) -> list[dict]:
53
+ '''返回 字典加列表组成的message(可转成json用于网络、跨语言传输) 组成的列表'''
54
+ return [m.model_dump(exclude_none=True) for m in self.messages]
55
+
56
+
57
+ @classmethod
58
+ def from_dict_list(cls, messages: list[dict]) -> "Conversation":
59
+ conv = cls()
60
+ conv.messages = [Message.model_validate(m) for m in messages]
61
+ return conv
62
+
63
+ @classmethod
64
+ def from_list(cls, messages: list[Message]) -> "Conversation":
65
+ conv = cls()
66
+ conv.messages = list(messages)
67
+ return conv
68
+
69
+ def clear(self) -> "Conversation":
70
+ self.messages = []
71
+ return self
72
+
@@ -0,0 +1,108 @@
1
+
2
+
3
+ from .llm import LLM
4
+ from .conversation import Conversation
5
+ from .tool import Tool
6
+ from .types import LLMRequest, Message
7
+ from .utils import get_iso_timestamp,build_tool_message, build_message
8
+
9
+ class Engine:
10
+ def __init__(self, model:str, llm:LLM, conversation:Conversation, tool:Tool, ):
11
+ self.llm = llm
12
+ self.conversation = conversation
13
+ self.tool = tool
14
+ self.model = model
15
+
16
+ def invoke(self, input_message:Message) -> Message:
17
+ self.conversation.append(input_message)
18
+ message = self.chat()
19
+ return message
20
+
21
+ def chat(self) -> Message:
22
+ """
23
+
24
+ 1. 读会话消息
25
+ 2. 大模型交流
26
+ 3. 追加会话消息
27
+ 4. 判断 是否调用工具
28
+ 5. 是: tool 返回结果并追加,否: 追加并返回
29
+ """
30
+ # 1. 读会话消息
31
+ message = self.conversation.get_messages()
32
+
33
+ # 2. llm交互
34
+ response = self.llm.invoke(self.messages_to_llm_request(message))
35
+
36
+ # 3. 追加会话消息
37
+ response_message = self.llm_response_to_message(response)
38
+ self.conversation.append(response_message)
39
+
40
+ return response_message
41
+
42
+ def deal_tool_or_output(self, response_message:Message) -> Message|None:
43
+ """
44
+ 1. 追加输入
45
+ 2. 读会话消息
46
+ 3. 大模型交流
47
+ 4. 追加会话消息
48
+ 5. 判断 是否调用工具
49
+ 6. 是: tool 返回结果并追加,否: 追加并返回
50
+
51
+
52
+ Returns:
53
+
54
+ """
55
+
56
+ # 5. 判断 是否调用工具
57
+ if response_message.tool_calls:
58
+ # 6. 是: tool 返回结果追加
59
+ for tool_call in response_message.tool_calls:
60
+ tool_result = self.tool.invoke(tool_call)
61
+ self.conversation.append(build_tool_message(tool_result.tool_call_id, tool_result.content))
62
+ return None
63
+ else:
64
+ # 6. 否: 追加并返回
65
+ return response_message
66
+
67
+
68
+ def run(self, input_message:Message) ->Message:
69
+ """
70
+ 1. 追加输入
71
+ 2. 读会话消息
72
+ 3. 大模型交流
73
+ 4. 追加会话消息
74
+ 5. 判断 是否调用工具
75
+ 6. 是: tool 返回结果并追加,否: 追加并返回
76
+
77
+
78
+ Returns:
79
+
80
+ """
81
+ self.conversation.append(input_message)
82
+ while True:
83
+
84
+ response_message = self.chat()
85
+ result = self.deal_tool_or_output(response_message)
86
+ if result is None:
87
+ continue
88
+ elif isinstance(result, Message):
89
+ return result
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+ def messages_to_llm_request(self,messages:list[Message]) -> LLMRequest:
99
+ return LLMRequest(
100
+ model=self.model,
101
+ messages=messages,
102
+ tools=self.tool.to_definitions(),
103
+ )
104
+
105
+ def llm_response_to_message(self, response) -> Message:
106
+ """把 LLM 响应转成 Message 。"""
107
+ return response.message
108
+
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: rootdriver
3
+ Version: 0.1.0
4
+ Summary: Rooted in Origin, Driving All Things
5
+ Author-email: zimvir <zimvir@qq.com>
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: openai>=1.0.0
10
+ Requires-Dist: pydantic>=2.0.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
13
+
14
+ # RootDriver
15
+
16
+ 根源出发, 驱动万物
17
+
18
+ 一个轻量级的 Python AI Agent 开发框架。
19
+
20
+ ## 特性
21
+
22
+ - **简洁易用**:装饰器方式定义工具,快速构建 Agent
23
+ - **模块化设计**:LLM 适配器、工具系统、会话管理解耦
24
+ - **工具调用**:支持 function calling,自动执行工具并返回结果
25
+ - **多适配器**:基于适配器模式,方便扩展其他 LLM Provider
26
+
27
+ ## 安装
28
+
29
+ ```bash
30
+ pip install rootdriver
31
+ ```
32
+
33
+ ## 快速开始
34
+
35
+ ### 定义工具
36
+
37
+ ```python
38
+ from rootdriver import tool
39
+
40
+ @tool
41
+ def get_weather(city: str) -> str:
42
+ """获取城市天气"""
43
+ return f"{city} 晴天"
44
+ ```
45
+
46
+ ### 创建 Agent
47
+
48
+ ```python
49
+ from rootdriver import Agent, AgentLLM, OpenAIAdapter
50
+ agent_llm=AgentLLM(
51
+ adapter=OpenAIAdapter(
52
+ api_key="YOUR_API_KEY",
53
+ base_url="BASE_URL"
54
+ ),
55
+ model="gpt-4",
56
+
57
+ ),
58
+ agent = Agent(
59
+ agent_llm=agent_llm,
60
+ tools=[get_weather],
61
+ system_prompt="你是一个有用的助手",
62
+ )
63
+
64
+ # 单次对话
65
+ response = agent.talk("北京天气怎么样?")
66
+ print(response)
67
+ ```
68
+
69
+ ### 使用工具
70
+
71
+ ```python
72
+ # 完整对话循环(包含工具调用)
73
+ response = Agent.react("帮我查下上海天气")
74
+ print(response)
75
+ ```
76
+
77
+ ## 核心组件
78
+
79
+ | 组件 | 说明 |
80
+ |------|------|
81
+ | `Agent` | 智能体入口,整合 LLM、工具、会话 |
82
+ | `Engine` | 核心引擎,处理对话循环和工具调用 |
83
+ | `Conversation` | 会话管理,维护消息历史 |
84
+ | `LLM` | LLM 调用封装 |
85
+ | `Tool` | 工具集合,管理所有可调用工具 |
86
+
87
+ ## 项目结构
88
+
89
+ ```
90
+ rootdriver/
91
+ ├── agent.py # Agent 智能体
92
+ ├── engine.py # 引擎核心
93
+ ├── conversation.py # 对话管理
94
+ ├── llm/ # LLM 适配器
95
+ ├── tool/ # 工具系统
96
+ ├── types/ # 类型定义
97
+ └── utils/ # 工具函数
98
+ ```
99
+
100
+ ## License
101
+
102
+ MIT
@@ -0,0 +1,16 @@
1
+ README.md
2
+ pyproject.toml
3
+ rootdriver/__init__.py
4
+ rootdriver/agent.py
5
+ rootdriver/constants.py
6
+ rootdriver/conversation.py
7
+ rootdriver/engine.py
8
+ rootdriver/exception.py
9
+ rootdriver.egg-info/PKG-INFO
10
+ rootdriver.egg-info/SOURCES.txt
11
+ rootdriver.egg-info/dependency_links.txt
12
+ rootdriver.egg-info/requires.txt
13
+ rootdriver.egg-info/top_level.txt
14
+ tests/test_base_tool.py
15
+ tests/test_conversation.py
16
+ tests/test_tool.py
@@ -0,0 +1,5 @@
1
+ openai>=1.0.0
2
+ pydantic>=2.0.0
3
+
4
+ [dev]
5
+ pytest>=7.0.0
@@ -0,0 +1 @@
1
+ rootdriver
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,103 @@
1
+ import pytest
2
+ from rootdriver.tool.base_tool import BaseTool
3
+
4
+
5
+ def get_weather(city: str) -> str:
6
+ """获取城市天气"""
7
+ return f"{city} 晴天"
8
+
9
+
10
+ @pytest.fixture
11
+ def weather_tool():
12
+ return BaseTool(get_weather)
13
+
14
+
15
+ def test_base_tool_name(weather_tool):
16
+ assert weather_tool.name == "get_weather"
17
+
18
+
19
+ def test_base_tool_description(weather_tool):
20
+ assert weather_tool.description == "获取城市天气"
21
+
22
+
23
+ def test_invoke_success(weather_tool):
24
+ result = weather_tool.invoke({"city": "北京"})
25
+ assert result == "北京 晴天"
26
+
27
+
28
+ def test_invoke_error_returns_string(weather_tool):
29
+ def bad_func(city: str) -> str:
30
+ raise ValueError("参数错误")
31
+ bad_tool = BaseTool(bad_func)
32
+ result = bad_tool.invoke({"city": "北京"})
33
+ assert isinstance(result, str)
34
+ assert "ValueError" in result or "参数错误" in result
35
+
36
+
37
+ def test_to_definition(weather_tool):
38
+ definition = weather_tool.to_definition()
39
+ assert definition.name == "get_weather"
40
+ assert definition.description == "获取城市天气"
41
+ assert "properties" in definition.parameters
42
+ assert "city" in definition.parameters["properties"]
43
+
44
+
45
+ def test_custom_name():
46
+ def foo():
47
+ pass
48
+ tool = BaseTool(foo, name="custom_name")
49
+ assert tool.name == "custom_name"
50
+
51
+
52
+ def test_custom_description():
53
+ def foo():
54
+ """custom desc"""
55
+ pass
56
+ tool = BaseTool(foo)
57
+ assert tool.description == "custom desc"
58
+
59
+
60
+ def test_callable(weather_tool):
61
+ result = weather_tool(city="上海")
62
+ assert result == "上海 晴天"
63
+
64
+
65
+ def test_invoke_retry_success():
66
+ """retry=1 第一次就成功"""
67
+ call_count = 0
68
+ def counter(city: str) -> str:
69
+ nonlocal call_count
70
+ call_count += 1
71
+ return f"{city} 晴天"
72
+ t = BaseTool(counter)
73
+ result = t.invoke({"city": "北京"}, retry=3)
74
+ assert result == "北京 晴天"
75
+ assert call_count == 1
76
+
77
+
78
+ def test_invoke_retry_then_success():
79
+ """retry=3 前两次失败,第三次成功"""
80
+ call_count = 0
81
+ def flaky(city: str) -> str:
82
+ nonlocal call_count
83
+ call_count += 1
84
+ if call_count < 3:
85
+ raise ValueError(f"第{call_count}次失败")
86
+ return f"{city} 晴天"
87
+ t = BaseTool(flaky)
88
+ result = t.invoke({"city": "北京"}, retry=3)
89
+ assert result == "北京 晴天"
90
+ assert call_count == 3
91
+
92
+
93
+ def test_invoke_retry_all_fail():
94
+ """retry=3 三次都失败,返回最后一次错误"""
95
+ call_count = 0
96
+ def always_fail(city: str) -> str:
97
+ nonlocal call_count
98
+ call_count += 1
99
+ raise ValueError(f"错误{call_count}")
100
+ t = BaseTool(always_fail)
101
+ result = t.invoke({"city": "北京"}, retry=3)
102
+ assert "错误3" in result
103
+ assert call_count == 3
@@ -0,0 +1,78 @@
1
+ import pytest
2
+ from rootdriver.conversation import Conversation
3
+ from rootdriver.types import Message
4
+
5
+
6
+ def test_conversation_empty():
7
+ conv = Conversation()
8
+ assert conv.get_messages() == []
9
+
10
+
11
+ def test_conversation_with_system_prompt():
12
+ conv = Conversation(system_prompt="你是一个助手")
13
+ messages = conv.get_messages()
14
+ assert len(messages) == 1
15
+ assert messages[0].role == "system"
16
+ assert messages[0].content == "你是一个助手"
17
+
18
+
19
+ def test_append_message():
20
+ conv = Conversation()
21
+ conv.append(Message(role="user", content="你好", created_at="2026-01-01T00:00:00"))
22
+ assert len(conv.get_messages()) == 1
23
+ assert conv.get_messages()[0].content == "你好"
24
+
25
+
26
+ def test_append_many():
27
+ conv = Conversation()
28
+ messages = [
29
+ Message(role="user", content="你好", created_at="2026-01-01T00:00:00"),
30
+ Message(role="assistant", content="你好!", created_at="2026-01-01T00:00:01"),
31
+ ]
32
+ conv.append_many(messages)
33
+ assert len(conv.get_messages()) == 2
34
+
35
+
36
+ def test_append_system():
37
+ conv = Conversation()
38
+ conv.append_system("新的系统提示")
39
+ assert conv.get_messages()[-1].role == "system"
40
+ assert conv.get_messages()[-1].content == "新的系统提示"
41
+
42
+
43
+ def test_delete_message():
44
+ conv = Conversation()
45
+ conv.append(Message(role="user", content="你好", created_at="2026-01-01T00:00:00"))
46
+ conv.append(Message(role="assistant", content="你好!", created_at="2026-01-01T00:00:01"))
47
+ conv.delete()
48
+ assert len(conv.get_messages()) == 1
49
+
50
+
51
+ def test_clear():
52
+ conv = Conversation(system_prompt="test")
53
+ conv.clear()
54
+ assert len(conv.get_messages()) == 0
55
+
56
+
57
+ def test_get_messages_in_list():
58
+ conv = Conversation()
59
+ conv.append(Message(role="user", content="你好", created_at="2026-01-01T00:00:00"))
60
+ result = conv.get_messages_in_list()
61
+ assert isinstance(result, list)
62
+ assert result[0]["content"] == "你好"
63
+
64
+
65
+ def test_from_dict_list():
66
+ data = [
67
+ {"role": "user", "content": "你好", "created_at": "2026-01-01T00:00:00"},
68
+ ]
69
+ conv = Conversation.from_dict_list(data)
70
+ assert len(conv.get_messages()) == 1
71
+
72
+
73
+ def test_from_list():
74
+ messages = [
75
+ Message(role="user", content="你好", created_at="2026-01-01T00:00:00"),
76
+ ]
77
+ conv = Conversation.from_list(messages)
78
+ assert len(conv.get_messages()) == 1
@@ -0,0 +1,76 @@
1
+ import pytest
2
+ from rootdriver.tool.base_tool import BaseTool
3
+ from rootdriver.tool.tools import Tool
4
+ from rootdriver.types.tool import ToolCall
5
+
6
+
7
+ def get_weather(city: str) -> str:
8
+ """获取城市天气"""
9
+ return f"{city} 晴天"
10
+
11
+
12
+ def get_time(city: str) -> str:
13
+ """获取城市时间"""
14
+ return f"{city} 10:00"
15
+
16
+
17
+ @pytest.fixture
18
+ def weather_tool():
19
+ return BaseTool(get_weather)
20
+
21
+
22
+ @pytest.fixture
23
+ def time_tool():
24
+ return BaseTool(get_time)
25
+
26
+
27
+ @pytest.fixture
28
+ def tool_registry(weather_tool, time_tool):
29
+ return Tool([weather_tool, time_tool])
30
+
31
+
32
+ def test_tool_add():
33
+ t = Tool()
34
+ t.add(BaseTool(get_weather))
35
+ assert t.get("get_weather") is not None
36
+
37
+
38
+ def test_tool_get(weather_tool):
39
+ t = Tool([weather_tool])
40
+ assert t.get("get_weather") is weather_tool
41
+
42
+
43
+ def test_tool_get_not_found():
44
+ t = Tool()
45
+ assert t.get("not_exist") is None
46
+
47
+
48
+ def test_tool_invoke_success(tool_registry):
49
+ result = tool_registry.invoke(ToolCall(id="1", name="get_weather", arguments={"city": "北京"}))
50
+ assert result.tool_call_id == "1"
51
+ assert result.content == "北京 晴天"
52
+
53
+
54
+ def test_tool_invoke_not_found(tool_registry):
55
+ result = tool_registry.invoke(ToolCall(id="2", name="not_exist", arguments={}))
56
+ assert result.tool_call_id == "2"
57
+ assert "not found" in result.content
58
+
59
+
60
+ def test_tool_invoke_many(tool_registry):
61
+ calls = [
62
+ ToolCall(id="1", name="get_weather", arguments={"city": "北京"}),
63
+ ToolCall(id="2", name="get_time", arguments={"city": "北京"}),
64
+ ]
65
+ results = tool_registry.invoke_many(calls)
66
+ assert len(results) == 2
67
+ assert results[0].content == "北京 晴天"
68
+ assert results[1].content == "北京 10:00"
69
+
70
+
71
+ def test_tool_to_definitions(tool_registry):
72
+ defs = tool_registry.to_definitions()
73
+ assert len(defs) == 2
74
+ names = [d.name for d in defs]
75
+ assert "get_weather" in names
76
+ assert "get_time" in names