tina-python 0.6.5__tar.gz → 0.6.7__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.
- {tina_python-0.6.5/tina_python.egg-info → tina_python-0.6.7}/PKG-INFO +47 -32
- {tina_python-0.6.5 → tina_python-0.6.7}/README.md +44 -29
- {tina_python-0.6.5 → tina_python-0.6.7}/pyproject.toml +3 -3
- {tina_python-0.6.5 → tina_python-0.6.7}/setup.py +1 -1
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/__init__.py +1 -1
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/agent.py +65 -5
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/mcp/client.py +210 -14
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/tui/tui.py +28 -11
- {tina_python-0.6.5 → tina_python-0.6.7/tina_python.egg-info}/PKG-INFO +47 -32
- {tina_python-0.6.5 → tina_python-0.6.7}/tina_python.egg-info/requires.txt +2 -2
- {tina_python-0.6.5 → tina_python-0.6.7}/LICENSE +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/MANIFEST.in +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/setup.cfg +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/__init__.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/core/__init__.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/core/agent_response.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/core/agent_runtime.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/core/context_manager.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/core/events.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/core/executor.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/core/keyword_actions.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/core/prompt.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/core/state.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/core/tools.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/agent/multimodal_agent.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/core/__init__.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/core/error.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/core/logger.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/llm/BaseNewAPI.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/llm/__init__.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/llm/base_api.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/llm/base_multimodal_api.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/llm/ollama_api.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/mcp/__init__.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/mcp/mcp_tools_executor.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/py.typed +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/__init__.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/agent_worker.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/deepseek.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/doc_parser.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/env_reader.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/multimodal_formatter.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/output_parser.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/run_agent_in_cli.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/system_tools.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/timer.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/tui/__init__.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/tui/context_manager.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/tui/token.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina/utils/type_mapper.py +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina_python.egg-info/SOURCES.txt +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina_python.egg-info/dependency_links.txt +0 -0
- {tina_python-0.6.5 → tina_python-0.6.7}/tina_python.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tina-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.7
|
|
4
4
|
Summary: tina is in your computer!
|
|
5
5
|
Home-page: https://gitee.com/wang-churi/tina
|
|
6
6
|
Author: 王出日
|
|
@@ -20,14 +20,14 @@ Requires-Dist: python-dotenv
|
|
|
20
20
|
Provides-Extra: tui
|
|
21
21
|
Requires-Dist: textual>=0.80; extra == "tui"
|
|
22
22
|
Provides-Extra: mcp
|
|
23
|
-
Requires-Dist: mcp; extra == "mcp"
|
|
23
|
+
Requires-Dist: mcp>=2.0; extra == "mcp"
|
|
24
24
|
Provides-Extra: test
|
|
25
25
|
Requires-Dist: pytest>=9.0; extra == "test"
|
|
26
26
|
Requires-Dist: pytest-asyncio>=1.4; extra == "test"
|
|
27
27
|
Requires-Dist: pytest-cov>=7.0; extra == "test"
|
|
28
28
|
Provides-Extra: all
|
|
29
29
|
Requires-Dist: textual>=0.80; extra == "all"
|
|
30
|
-
Requires-Dist: mcp; extra == "all"
|
|
30
|
+
Requires-Dist: mcp>=2.0; extra == "all"
|
|
31
31
|
Dynamic: author
|
|
32
32
|
Dynamic: home-page
|
|
33
33
|
Dynamic: license-file
|
|
@@ -50,6 +50,12 @@ pip install tina-python
|
|
|
50
50
|
# 如果需要使用 MCP(模型上下文协议)服务
|
|
51
51
|
pip install tina-python[mcp]
|
|
52
52
|
|
|
53
|
+
# 如果需要 TUI 终端界面
|
|
54
|
+
pip install tina-python[tui]
|
|
55
|
+
|
|
56
|
+
# 一次装齐所有可选功能(TUI + MCP)
|
|
57
|
+
pip install tina-python[all]
|
|
58
|
+
|
|
53
59
|
# 如果需要运行测试
|
|
54
60
|
pip install tina-python[test]
|
|
55
61
|
```
|
|
@@ -211,8 +217,9 @@ result = await llm.apredict(
|
|
|
211
217
|
stream=False
|
|
212
218
|
)
|
|
213
219
|
|
|
214
|
-
#
|
|
215
|
-
|
|
220
|
+
# 异步流式:先 await 拿到异步生成器
|
|
221
|
+
result = await llm.apredict(input_text="讲个故事", stream=True)
|
|
222
|
+
async for chunk in result:
|
|
216
223
|
print(chunk["content"], end="")
|
|
217
224
|
```
|
|
218
225
|
|
|
@@ -242,8 +249,6 @@ async for chunk in llm.apredict(input_text="讲个故事", stream=True):
|
|
|
242
249
|
{
|
|
243
250
|
"role": "assistant",
|
|
244
251
|
"content": "回复内容",
|
|
245
|
-
# 推理模型时会出现
|
|
246
|
-
"reasoning_content": "推理链...",
|
|
247
252
|
# 有工具调用时会出现
|
|
248
253
|
"tool_calls": [
|
|
249
254
|
{
|
|
@@ -255,7 +260,8 @@ async for chunk in llm.apredict(input_text="讲个故事", stream=True):
|
|
|
255
260
|
]
|
|
256
261
|
}
|
|
257
262
|
|
|
258
|
-
#
|
|
263
|
+
# 流式:逐块返回片段,工具调用以 tool_name / tool_arguments 片段给出;
|
|
264
|
+
# 推理模型的思维链在 reasoning_content 中,末尾可能带有 usage
|
|
259
265
|
```
|
|
260
266
|
|
|
261
267
|
### 1.8 获取可用模型列表
|
|
@@ -392,9 +398,12 @@ tool_calls = [
|
|
|
392
398
|
}
|
|
393
399
|
}
|
|
394
400
|
]
|
|
395
|
-
|
|
401
|
+
# execute 需要事件对象(Agent 内部会自动传入)
|
|
402
|
+
from tina import AgentEvents
|
|
403
|
+
|
|
404
|
+
results = tools.execute(tool_calls, events=AgentEvents())
|
|
396
405
|
# [
|
|
397
|
-
# {"role": "tool", "tool_call_id": "call_123", "
|
|
406
|
+
# {"role": "tool", "tool_call_id": "call_123", "tool_name": "get_weather", "content": "北京今天天气晴朗"}
|
|
398
407
|
# ]
|
|
399
408
|
```
|
|
400
409
|
|
|
@@ -542,7 +551,7 @@ elif agent.state == AgentState.ERROR:
|
|
|
542
551
|
# 获取消息列表
|
|
543
552
|
messages = agent.get_messages()
|
|
544
553
|
|
|
545
|
-
#
|
|
554
|
+
# 清空全部消息(含系统提示词,之后需重新 set_system_prompt)
|
|
546
555
|
agent.clear_messages()
|
|
547
556
|
|
|
548
557
|
# 获取系统提示词
|
|
@@ -630,7 +639,7 @@ def on_turn_end():
|
|
|
630
639
|
agent_a = Agent(llm=llm, tools=tools, name="agent_a")
|
|
631
640
|
agent_b = Agent(llm=llm, tools=tools, name="agent_b")
|
|
632
641
|
|
|
633
|
-
#
|
|
642
|
+
# 关联另一个 Agent(记录到 other_agents,目前不会自动共享消息列表)
|
|
634
643
|
agent_a.connect_agent(agent_b)
|
|
635
644
|
|
|
636
645
|
# 断开连接
|
|
@@ -707,23 +716,31 @@ mcp = MCPClient()
|
|
|
707
716
|
# 添加 stdio 类型 MCP 服务
|
|
708
717
|
mcp.add_server(
|
|
709
718
|
server_id="playwright",
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
"args": ["@playwright/mcp@latest"]
|
|
714
|
-
}
|
|
719
|
+
type="stdio",
|
|
720
|
+
command="npx",
|
|
721
|
+
args=["@playwright/mcp@latest"],
|
|
715
722
|
)
|
|
716
723
|
|
|
717
|
-
# 添加
|
|
724
|
+
# 添加 Streamable HTTP 类型 MCP 服务(推荐,取代旧 SSE)
|
|
718
725
|
mcp.add_server(
|
|
719
726
|
server_id="my-service",
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
727
|
+
type="http", # 也支持 streamable_http / streamableHttp
|
|
728
|
+
url="http://localhost:8080/mcp",
|
|
729
|
+
headers={"Authorization": "Bearer YOUR_API_KEY"}, # 可选
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
# 添加 SSE 类型 MCP 服务(旧传输,仍兼容)
|
|
733
|
+
mcp.add_server(
|
|
734
|
+
server_id="my-legacy-service",
|
|
735
|
+
type="sse",
|
|
736
|
+
url="http://localhost:8080/sse",
|
|
737
|
+
headers={"Authorization": "Bearer YOUR_API_KEY"}, # 可选
|
|
724
738
|
)
|
|
725
739
|
```
|
|
726
740
|
|
|
741
|
+
> `type` 为必填项,用于区分传输类型;`command`/`args`/`env` 与 `url`/`headers`
|
|
742
|
+
> 分别对应 stdio 与远程类型,编辑器会根据 `type` 给出对应的参数提示。
|
|
743
|
+
|
|
727
744
|
### 4.3 在 Agent 中使用 MCP
|
|
728
745
|
|
|
729
746
|
```python
|
|
@@ -734,11 +751,9 @@ from tina.mcp import MCPClient
|
|
|
734
751
|
mcp = MCPClient()
|
|
735
752
|
mcp.add_server(
|
|
736
753
|
server_id="playwright",
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
"args": ["@playwright/mcp@latest"]
|
|
741
|
-
}
|
|
754
|
+
type="stdio",
|
|
755
|
+
command="npx",
|
|
756
|
+
args=["@playwright/mcp@latest"],
|
|
742
757
|
)
|
|
743
758
|
|
|
744
759
|
llm = BaseAPI()
|
|
@@ -759,8 +774,8 @@ for chunk in result:
|
|
|
759
774
|
### 4.4 MCP 服务管理
|
|
760
775
|
|
|
761
776
|
```python
|
|
762
|
-
#
|
|
763
|
-
agent.add_mcp_server("server_id",
|
|
777
|
+
# 添加服务(参数与 MCPClient.add_server 一致)
|
|
778
|
+
agent.add_mcp_server("server_id", type="http", url="http://localhost:8080/mcp")
|
|
764
779
|
|
|
765
780
|
# 移除服务
|
|
766
781
|
agent.remove_mcp_server("server_id")
|
|
@@ -889,8 +904,8 @@ from tina import Agent, Tools
|
|
|
889
904
|
from tina.llm import BaseAPI
|
|
890
905
|
from tina.utils.system_tools import system_tools
|
|
891
906
|
|
|
892
|
-
tools = Tools()
|
|
893
|
-
tools += system_tools # 添加 tina
|
|
907
|
+
tools = Tools(name="my_tools")
|
|
908
|
+
tools += system_tools # 添加 tina 自带的系统工具包(工具集需命名)
|
|
894
909
|
|
|
895
910
|
agent = Agent(
|
|
896
911
|
llm=BaseAPI(),
|
|
@@ -992,7 +1007,7 @@ run_agent_in_cli(agent)
|
|
|
992
1007
|
|------|------|
|
|
993
1008
|
| `#context` | 查看当前对话上下文(消息列表) |
|
|
994
1009
|
| `#clear` | 清屏 |
|
|
995
|
-
|
|
|
1010
|
+
| `exit` / `quit` | 退出对话(`#exit` 不支持,会被当作普通输入) |
|
|
996
1011
|
|
|
997
1012
|
### 6.4 多模态消息构建器 `build_multimodal_message`
|
|
998
1013
|
|
|
@@ -15,6 +15,12 @@ pip install tina-python
|
|
|
15
15
|
# 如果需要使用 MCP(模型上下文协议)服务
|
|
16
16
|
pip install tina-python[mcp]
|
|
17
17
|
|
|
18
|
+
# 如果需要 TUI 终端界面
|
|
19
|
+
pip install tina-python[tui]
|
|
20
|
+
|
|
21
|
+
# 一次装齐所有可选功能(TUI + MCP)
|
|
22
|
+
pip install tina-python[all]
|
|
23
|
+
|
|
18
24
|
# 如果需要运行测试
|
|
19
25
|
pip install tina-python[test]
|
|
20
26
|
```
|
|
@@ -176,8 +182,9 @@ result = await llm.apredict(
|
|
|
176
182
|
stream=False
|
|
177
183
|
)
|
|
178
184
|
|
|
179
|
-
#
|
|
180
|
-
|
|
185
|
+
# 异步流式:先 await 拿到异步生成器
|
|
186
|
+
result = await llm.apredict(input_text="讲个故事", stream=True)
|
|
187
|
+
async for chunk in result:
|
|
181
188
|
print(chunk["content"], end="")
|
|
182
189
|
```
|
|
183
190
|
|
|
@@ -207,8 +214,6 @@ async for chunk in llm.apredict(input_text="讲个故事", stream=True):
|
|
|
207
214
|
{
|
|
208
215
|
"role": "assistant",
|
|
209
216
|
"content": "回复内容",
|
|
210
|
-
# 推理模型时会出现
|
|
211
|
-
"reasoning_content": "推理链...",
|
|
212
217
|
# 有工具调用时会出现
|
|
213
218
|
"tool_calls": [
|
|
214
219
|
{
|
|
@@ -220,7 +225,8 @@ async for chunk in llm.apredict(input_text="讲个故事", stream=True):
|
|
|
220
225
|
]
|
|
221
226
|
}
|
|
222
227
|
|
|
223
|
-
#
|
|
228
|
+
# 流式:逐块返回片段,工具调用以 tool_name / tool_arguments 片段给出;
|
|
229
|
+
# 推理模型的思维链在 reasoning_content 中,末尾可能带有 usage
|
|
224
230
|
```
|
|
225
231
|
|
|
226
232
|
### 1.8 获取可用模型列表
|
|
@@ -357,9 +363,12 @@ tool_calls = [
|
|
|
357
363
|
}
|
|
358
364
|
}
|
|
359
365
|
]
|
|
360
|
-
|
|
366
|
+
# execute 需要事件对象(Agent 内部会自动传入)
|
|
367
|
+
from tina import AgentEvents
|
|
368
|
+
|
|
369
|
+
results = tools.execute(tool_calls, events=AgentEvents())
|
|
361
370
|
# [
|
|
362
|
-
# {"role": "tool", "tool_call_id": "call_123", "
|
|
371
|
+
# {"role": "tool", "tool_call_id": "call_123", "tool_name": "get_weather", "content": "北京今天天气晴朗"}
|
|
363
372
|
# ]
|
|
364
373
|
```
|
|
365
374
|
|
|
@@ -507,7 +516,7 @@ elif agent.state == AgentState.ERROR:
|
|
|
507
516
|
# 获取消息列表
|
|
508
517
|
messages = agent.get_messages()
|
|
509
518
|
|
|
510
|
-
#
|
|
519
|
+
# 清空全部消息(含系统提示词,之后需重新 set_system_prompt)
|
|
511
520
|
agent.clear_messages()
|
|
512
521
|
|
|
513
522
|
# 获取系统提示词
|
|
@@ -595,7 +604,7 @@ def on_turn_end():
|
|
|
595
604
|
agent_a = Agent(llm=llm, tools=tools, name="agent_a")
|
|
596
605
|
agent_b = Agent(llm=llm, tools=tools, name="agent_b")
|
|
597
606
|
|
|
598
|
-
#
|
|
607
|
+
# 关联另一个 Agent(记录到 other_agents,目前不会自动共享消息列表)
|
|
599
608
|
agent_a.connect_agent(agent_b)
|
|
600
609
|
|
|
601
610
|
# 断开连接
|
|
@@ -672,23 +681,31 @@ mcp = MCPClient()
|
|
|
672
681
|
# 添加 stdio 类型 MCP 服务
|
|
673
682
|
mcp.add_server(
|
|
674
683
|
server_id="playwright",
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
"args": ["@playwright/mcp@latest"]
|
|
679
|
-
}
|
|
684
|
+
type="stdio",
|
|
685
|
+
command="npx",
|
|
686
|
+
args=["@playwright/mcp@latest"],
|
|
680
687
|
)
|
|
681
688
|
|
|
682
|
-
# 添加
|
|
689
|
+
# 添加 Streamable HTTP 类型 MCP 服务(推荐,取代旧 SSE)
|
|
683
690
|
mcp.add_server(
|
|
684
691
|
server_id="my-service",
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
692
|
+
type="http", # 也支持 streamable_http / streamableHttp
|
|
693
|
+
url="http://localhost:8080/mcp",
|
|
694
|
+
headers={"Authorization": "Bearer YOUR_API_KEY"}, # 可选
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
# 添加 SSE 类型 MCP 服务(旧传输,仍兼容)
|
|
698
|
+
mcp.add_server(
|
|
699
|
+
server_id="my-legacy-service",
|
|
700
|
+
type="sse",
|
|
701
|
+
url="http://localhost:8080/sse",
|
|
702
|
+
headers={"Authorization": "Bearer YOUR_API_KEY"}, # 可选
|
|
689
703
|
)
|
|
690
704
|
```
|
|
691
705
|
|
|
706
|
+
> `type` 为必填项,用于区分传输类型;`command`/`args`/`env` 与 `url`/`headers`
|
|
707
|
+
> 分别对应 stdio 与远程类型,编辑器会根据 `type` 给出对应的参数提示。
|
|
708
|
+
|
|
692
709
|
### 4.3 在 Agent 中使用 MCP
|
|
693
710
|
|
|
694
711
|
```python
|
|
@@ -699,11 +716,9 @@ from tina.mcp import MCPClient
|
|
|
699
716
|
mcp = MCPClient()
|
|
700
717
|
mcp.add_server(
|
|
701
718
|
server_id="playwright",
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
"args": ["@playwright/mcp@latest"]
|
|
706
|
-
}
|
|
719
|
+
type="stdio",
|
|
720
|
+
command="npx",
|
|
721
|
+
args=["@playwright/mcp@latest"],
|
|
707
722
|
)
|
|
708
723
|
|
|
709
724
|
llm = BaseAPI()
|
|
@@ -724,8 +739,8 @@ for chunk in result:
|
|
|
724
739
|
### 4.4 MCP 服务管理
|
|
725
740
|
|
|
726
741
|
```python
|
|
727
|
-
#
|
|
728
|
-
agent.add_mcp_server("server_id",
|
|
742
|
+
# 添加服务(参数与 MCPClient.add_server 一致)
|
|
743
|
+
agent.add_mcp_server("server_id", type="http", url="http://localhost:8080/mcp")
|
|
729
744
|
|
|
730
745
|
# 移除服务
|
|
731
746
|
agent.remove_mcp_server("server_id")
|
|
@@ -854,8 +869,8 @@ from tina import Agent, Tools
|
|
|
854
869
|
from tina.llm import BaseAPI
|
|
855
870
|
from tina.utils.system_tools import system_tools
|
|
856
871
|
|
|
857
|
-
tools = Tools()
|
|
858
|
-
tools += system_tools # 添加 tina
|
|
872
|
+
tools = Tools(name="my_tools")
|
|
873
|
+
tools += system_tools # 添加 tina 自带的系统工具包(工具集需命名)
|
|
859
874
|
|
|
860
875
|
agent = Agent(
|
|
861
876
|
llm=BaseAPI(),
|
|
@@ -957,7 +972,7 @@ run_agent_in_cli(agent)
|
|
|
957
972
|
|------|------|
|
|
958
973
|
| `#context` | 查看当前对话上下文(消息列表) |
|
|
959
974
|
| `#clear` | 清屏 |
|
|
960
|
-
|
|
|
975
|
+
| `exit` / `quit` | 退出对话(`#exit` 不支持,会被当作普通输入) |
|
|
961
976
|
|
|
962
977
|
### 6.4 多模态消息构建器 `build_multimodal_message`
|
|
963
978
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tina-python"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.7"
|
|
8
8
|
description = "tina is in your computer!"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -32,7 +32,7 @@ tui = [
|
|
|
32
32
|
"textual>=0.80",
|
|
33
33
|
]
|
|
34
34
|
mcp = [
|
|
35
|
-
"mcp",
|
|
35
|
+
"mcp>=2.0",
|
|
36
36
|
]
|
|
37
37
|
test = [
|
|
38
38
|
"pytest>=9.0",
|
|
@@ -41,7 +41,7 @@ test = [
|
|
|
41
41
|
]
|
|
42
42
|
all = [
|
|
43
43
|
"textual>=0.80",
|
|
44
|
-
"mcp",
|
|
44
|
+
"mcp>=2.0",
|
|
45
45
|
]
|
|
46
46
|
|
|
47
47
|
[tool.pytest.ini_options]
|
|
@@ -10,7 +10,17 @@ Agent类:基础智能体类,默认支持API调用
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import re
|
|
13
|
-
from typing import
|
|
13
|
+
from typing import (
|
|
14
|
+
List,
|
|
15
|
+
Literal,
|
|
16
|
+
Union,
|
|
17
|
+
Generator,
|
|
18
|
+
Dict,
|
|
19
|
+
Any,
|
|
20
|
+
Optional,
|
|
21
|
+
AsyncGenerator,
|
|
22
|
+
overload,
|
|
23
|
+
)
|
|
14
24
|
|
|
15
25
|
from tina.agent.core.agent_response import AgentResponse, ToolCall
|
|
16
26
|
from tina.agent.core.state import AgentState
|
|
@@ -18,7 +28,7 @@ from tina.agent.core.state import AgentState
|
|
|
18
28
|
from ..llm.base_api import BaseAPI
|
|
19
29
|
from .core.tools import Tools
|
|
20
30
|
from .core.keyword_actions import KeywordActions
|
|
21
|
-
from ..mcp.client import MCPClient
|
|
31
|
+
from ..mcp.client import MCPClient, RemoteType, StdioType
|
|
22
32
|
from .core.prompt import Prompt
|
|
23
33
|
from .core.context_manager import ContextManager
|
|
24
34
|
from .core.agent_runtime import ToolCallingAgentRuntime, BaseAgentRuntime
|
|
@@ -466,19 +476,69 @@ class Agent:
|
|
|
466
476
|
def get_tools_call(self) -> list:
|
|
467
477
|
return self.context_manager.get_tool_calls()
|
|
468
478
|
|
|
479
|
+
@overload
|
|
480
|
+
def add_mcp_server(
|
|
481
|
+
self,
|
|
482
|
+
server_id: str,
|
|
483
|
+
*,
|
|
484
|
+
type: StdioType,
|
|
485
|
+
command: str,
|
|
486
|
+
args: Optional[List[str]] = None,
|
|
487
|
+
env: Optional[Dict[str, str]] = None,
|
|
488
|
+
max_retries: int = 3,
|
|
489
|
+
timeout: int = 90,
|
|
490
|
+
) -> bool: ...
|
|
491
|
+
|
|
492
|
+
@overload
|
|
469
493
|
def add_mcp_server(
|
|
470
|
-
self,
|
|
494
|
+
self,
|
|
495
|
+
server_id: str,
|
|
496
|
+
*,
|
|
497
|
+
type: RemoteType,
|
|
498
|
+
url: str,
|
|
499
|
+
headers: Optional[Dict[str, str]] = None,
|
|
500
|
+
max_retries: int = 3,
|
|
501
|
+
timeout: int = 90,
|
|
502
|
+
) -> bool: ...
|
|
503
|
+
|
|
504
|
+
def add_mcp_server(
|
|
505
|
+
self,
|
|
506
|
+
server_id: str,
|
|
507
|
+
*,
|
|
508
|
+
type: str,
|
|
509
|
+
command: Optional[str] = None,
|
|
510
|
+
args: Optional[List[str]] = None,
|
|
511
|
+
env: Optional[Dict[str, str]] = None,
|
|
512
|
+
url: Optional[str] = None,
|
|
513
|
+
headers: Optional[Dict[str, str]] = None,
|
|
514
|
+
max_retries: int = 3,
|
|
515
|
+
timeout: int = 90,
|
|
471
516
|
) -> bool:
|
|
472
517
|
"""
|
|
473
518
|
添加MCP服务器
|
|
474
519
|
Args:
|
|
475
520
|
server_id:服务器ID
|
|
476
|
-
|
|
521
|
+
type:传输类型(`stdio` / `sse` / `http`)
|
|
522
|
+
command:`stdio` 类型的启动命令
|
|
523
|
+
args:`stdio` 类型的命令参数
|
|
524
|
+
env:`stdio` 类型的环境变量
|
|
525
|
+
url:`sse` / `http` 类型的服务地址
|
|
526
|
+
headers:`sse` / `http` 类型的请求头
|
|
477
527
|
max_retries:最大重试次数
|
|
478
528
|
timeout:超时时间
|
|
479
529
|
"""
|
|
480
530
|
if self.mcp_client is not None:
|
|
481
|
-
return self.mcp_client.add_server(
|
|
531
|
+
return self.mcp_client.add_server(
|
|
532
|
+
server_id,
|
|
533
|
+
type=type,
|
|
534
|
+
command=command,
|
|
535
|
+
args=args,
|
|
536
|
+
env=env,
|
|
537
|
+
url=url,
|
|
538
|
+
headers=headers,
|
|
539
|
+
max_retries=max_retries,
|
|
540
|
+
timeout=timeout,
|
|
541
|
+
)
|
|
482
542
|
else:
|
|
483
543
|
raise ValueError("MCP客户端未初始化,请先初始化MCP客户端")
|
|
484
544
|
|
|
@@ -3,7 +3,16 @@ import concurrent.futures
|
|
|
3
3
|
import json
|
|
4
4
|
import uuid
|
|
5
5
|
import threading
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import (
|
|
7
|
+
Dict,
|
|
8
|
+
List,
|
|
9
|
+
Any,
|
|
10
|
+
Optional,
|
|
11
|
+
Union,
|
|
12
|
+
Tuple,
|
|
13
|
+
Literal,
|
|
14
|
+
overload,
|
|
15
|
+
)
|
|
7
16
|
from contextlib import AsyncExitStack
|
|
8
17
|
from datetime import datetime
|
|
9
18
|
from ..core import logger # 假设你有统一的logger
|
|
@@ -12,12 +21,78 @@ try:
|
|
|
12
21
|
from mcp import ClientSession, StdioServerParameters
|
|
13
22
|
from mcp.client.stdio import stdio_client
|
|
14
23
|
from mcp.client.sse import sse_client
|
|
24
|
+
from mcp.client.streamable_http import (
|
|
25
|
+
streamable_http_client,
|
|
26
|
+
create_mcp_http_client,
|
|
27
|
+
)
|
|
15
28
|
|
|
16
29
|
MCP_AVAILABLE = True
|
|
17
30
|
except ImportError:
|
|
18
31
|
MCP_AVAILABLE = False
|
|
19
32
|
|
|
20
33
|
|
|
34
|
+
# 远程传输类型别名:Streamable HTTP 在不同客户端里叫法不一,这里统一归一化。
|
|
35
|
+
# 官方新规范为 Streamable HTTP,`sse` 为旧版(已废弃)传输。
|
|
36
|
+
_REMOTE_TYPE_ALIASES = {
|
|
37
|
+
"sse": "sse",
|
|
38
|
+
"http": "http",
|
|
39
|
+
"https": "http",
|
|
40
|
+
"streamable_http": "http",
|
|
41
|
+
"streamablehttp": "http",
|
|
42
|
+
"streamable-http": "http",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _normalize_server_type(server_type: str) -> str:
|
|
47
|
+
return _REMOTE_TYPE_ALIASES.get(str(server_type).lower(), str(server_type).lower())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
StdioType = Literal["stdio"]
|
|
51
|
+
RemoteType = Literal[
|
|
52
|
+
"sse",
|
|
53
|
+
"http",
|
|
54
|
+
"https",
|
|
55
|
+
"streamable_http",
|
|
56
|
+
"streamableHttp",
|
|
57
|
+
"streamable-http",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _build_server_config(
|
|
62
|
+
server_type: str,
|
|
63
|
+
*,
|
|
64
|
+
command: Optional[str] = None,
|
|
65
|
+
args: Optional[List[str]] = None,
|
|
66
|
+
env: Optional[Dict[str, str]] = None,
|
|
67
|
+
url: Optional[str] = None,
|
|
68
|
+
headers: Optional[Dict[str, str]] = None,
|
|
69
|
+
) -> Dict[str, Any]:
|
|
70
|
+
"""把扁平化参数组装为内部 config 字典,并做必要校验。"""
|
|
71
|
+
stype = _normalize_server_type(server_type)
|
|
72
|
+
if stype == "stdio":
|
|
73
|
+
if not command:
|
|
74
|
+
raise ValueError("stdio 类型必须提供 command 参数")
|
|
75
|
+
config: Dict[str, Any] = {
|
|
76
|
+
"type": "stdio",
|
|
77
|
+
"command": command,
|
|
78
|
+
"args": list(args) if args else [],
|
|
79
|
+
}
|
|
80
|
+
if env:
|
|
81
|
+
config["env"] = dict(env)
|
|
82
|
+
return config
|
|
83
|
+
if stype in ("sse", "http"):
|
|
84
|
+
if not url:
|
|
85
|
+
raise ValueError(f"{server_type} 类型必须提供 url 参数")
|
|
86
|
+
config = {"type": stype, "url": url}
|
|
87
|
+
if headers:
|
|
88
|
+
config["headers"] = dict(headers)
|
|
89
|
+
return config
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"Unsupported server type: {server_type} "
|
|
92
|
+
f"(supported: stdio, sse, http/streamable_http)"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
21
96
|
def _tool_input_schema(tool: Any) -> Dict[str, Any]:
|
|
22
97
|
"""获取 MCP 工具的输入 schema,兼容 SDK 的字段改名
|
|
23
98
|
|
|
@@ -30,6 +105,70 @@ def _tool_input_schema(tool: Any) -> Dict[str, Any]:
|
|
|
30
105
|
return schema or {}
|
|
31
106
|
|
|
32
107
|
|
|
108
|
+
def _normalize_property(prop: Any) -> Dict[str, Any]:
|
|
109
|
+
"""将单个 MCP 属性 schema 归一化为 Tina/大模型友好的形式。
|
|
110
|
+
|
|
111
|
+
修复原先只保留 `type` 且缺省退化为 Python 类型名 ``"str"`` 的问题:
|
|
112
|
+
- 缺少顶层 `type` 时,从 enum / anyOf 推断,最后回退为 JSON Schema 的
|
|
113
|
+
``"string"``,不再输出 ``"str"``;
|
|
114
|
+
- 保留 `enum` 等约束信息,避免模型失去取值范围;
|
|
115
|
+
- 将带 null 的 `anyOf`/`oneOf` 及 ``["string", "null"]`` 形式的 type
|
|
116
|
+
压平为 `type` + `nullable`,提升对不支持联合类型的模型兼容性。
|
|
117
|
+
"""
|
|
118
|
+
prop = dict(prop) if isinstance(prop, dict) else {}
|
|
119
|
+
description = prop.get("description", "")
|
|
120
|
+
|
|
121
|
+
# 压平 nullable 联合类型(例如 zod 的 z.enum([...]).nullable())
|
|
122
|
+
variants = prop.get("anyOf") or prop.get("oneOf")
|
|
123
|
+
if isinstance(variants, list):
|
|
124
|
+
non_null = [
|
|
125
|
+
v for v in variants if isinstance(v, dict) and v.get("type") != "null"
|
|
126
|
+
]
|
|
127
|
+
has_null = any(
|
|
128
|
+
isinstance(v, dict) and v.get("type") == "null" for v in variants
|
|
129
|
+
)
|
|
130
|
+
if len(non_null) == 1 and has_null:
|
|
131
|
+
merged = dict(non_null[0])
|
|
132
|
+
merged.setdefault("description", description)
|
|
133
|
+
merged["nullable"] = True
|
|
134
|
+
return _normalize_property(merged)
|
|
135
|
+
|
|
136
|
+
# 压平 type 为数组的形式,如 ["string", "null"]
|
|
137
|
+
raw_type = prop.get("type")
|
|
138
|
+
if isinstance(raw_type, list):
|
|
139
|
+
types = [t for t in raw_type if t != "null"]
|
|
140
|
+
prop["nullable"] = prop.get("nullable", "null" in raw_type)
|
|
141
|
+
if len(types) == 1:
|
|
142
|
+
prop["type"] = types[0]
|
|
143
|
+
else:
|
|
144
|
+
prop.pop("type", None)
|
|
145
|
+
|
|
146
|
+
# 缺少 type 时从 enum 推断,否则回退 "string"(而非 Python 的 "str")
|
|
147
|
+
if "type" not in prop:
|
|
148
|
+
values = prop.get("enum")
|
|
149
|
+
if isinstance(values, list) and values:
|
|
150
|
+
if all(isinstance(v, str) for v in values):
|
|
151
|
+
prop["type"] = "string"
|
|
152
|
+
elif all(
|
|
153
|
+
isinstance(v, (int, float)) and not isinstance(v, bool)
|
|
154
|
+
for v in values
|
|
155
|
+
):
|
|
156
|
+
prop["type"] = "number"
|
|
157
|
+
elif not (
|
|
158
|
+
prop.get("anyOf") or prop.get("oneOf") or prop.get("$ref")
|
|
159
|
+
):
|
|
160
|
+
prop["type"] = "string"
|
|
161
|
+
|
|
162
|
+
prop["description"] = description
|
|
163
|
+
return prop
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _convert_input_schema(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
167
|
+
"""将 MCP 工具的 input schema.properties 转为 Tina 工具参数格式"""
|
|
168
|
+
properties = schema.get("properties", {}) or {}
|
|
169
|
+
return {name: _normalize_property(prop) for name, prop in properties.items()}
|
|
170
|
+
|
|
171
|
+
|
|
33
172
|
class MCPClient:
|
|
34
173
|
def __init__(self):
|
|
35
174
|
if not MCP_AVAILABLE:
|
|
@@ -79,7 +218,8 @@ class MCPClient:
|
|
|
79
218
|
stack = AsyncExitStack()
|
|
80
219
|
stop = asyncio.Event()
|
|
81
220
|
try:
|
|
82
|
-
server_type = config.get("type", "")
|
|
221
|
+
server_type = _normalize_server_type(config.get("type", ""))
|
|
222
|
+
headers = config.get("headers") or None
|
|
83
223
|
if server_type == "stdio":
|
|
84
224
|
params = StdioServerParameters(
|
|
85
225
|
command=config["command"],
|
|
@@ -91,12 +231,28 @@ class MCPClient:
|
|
|
91
231
|
ClientSession(transport[0], transport[1])
|
|
92
232
|
)
|
|
93
233
|
elif server_type == "sse":
|
|
94
|
-
transport = await stack.enter_async_context(
|
|
234
|
+
transport = await stack.enter_async_context(
|
|
235
|
+
sse_client(config["url"], headers=headers)
|
|
236
|
+
)
|
|
237
|
+
session = await stack.enter_async_context(
|
|
238
|
+
ClientSession(transport[0], transport[1])
|
|
239
|
+
)
|
|
240
|
+
elif server_type == "http":
|
|
241
|
+
# Streamable HTTP:单端点,取代旧的 SSE 传输。
|
|
242
|
+
# headers 需通过预配置的 httpx 客户端传入;该客户端由 stack 负责关闭。
|
|
243
|
+
http_client = create_mcp_http_client(headers=headers)
|
|
244
|
+
await stack.enter_async_context(http_client)
|
|
245
|
+
transport = await stack.enter_async_context(
|
|
246
|
+
streamable_http_client(config["url"], http_client=http_client)
|
|
247
|
+
)
|
|
95
248
|
session = await stack.enter_async_context(
|
|
96
249
|
ClientSession(transport[0], transport[1])
|
|
97
250
|
)
|
|
98
251
|
else:
|
|
99
|
-
raise ValueError(
|
|
252
|
+
raise ValueError(
|
|
253
|
+
f"Unsupported server type: {server_type} "
|
|
254
|
+
f"(supported: stdio, sse, http/streamable_http)"
|
|
255
|
+
)
|
|
100
256
|
|
|
101
257
|
await session.initialize()
|
|
102
258
|
res = await session.list_tools()
|
|
@@ -127,14 +283,61 @@ class MCPClient:
|
|
|
127
283
|
|
|
128
284
|
# --- 同步接口桥接 ---
|
|
129
285
|
|
|
286
|
+
@overload
|
|
130
287
|
def add_server(
|
|
131
288
|
self,
|
|
132
289
|
server_id: str,
|
|
133
|
-
|
|
290
|
+
*,
|
|
291
|
+
type: StdioType,
|
|
292
|
+
command: str,
|
|
293
|
+
args: Optional[List[str]] = None,
|
|
294
|
+
env: Optional[Dict[str, str]] = None,
|
|
295
|
+
max_retries: int = 1,
|
|
296
|
+
timeout: int = 90,
|
|
297
|
+
) -> bool: ...
|
|
298
|
+
|
|
299
|
+
@overload
|
|
300
|
+
def add_server(
|
|
301
|
+
self,
|
|
302
|
+
server_id: str,
|
|
303
|
+
*,
|
|
304
|
+
type: RemoteType,
|
|
305
|
+
url: str,
|
|
306
|
+
headers: Optional[Dict[str, str]] = None,
|
|
307
|
+
max_retries: int = 1,
|
|
308
|
+
timeout: int = 90,
|
|
309
|
+
) -> bool: ...
|
|
310
|
+
|
|
311
|
+
def add_server(
|
|
312
|
+
self,
|
|
313
|
+
server_id: str,
|
|
314
|
+
*,
|
|
315
|
+
type: str,
|
|
316
|
+
command: Optional[str] = None,
|
|
317
|
+
args: Optional[List[str]] = None,
|
|
318
|
+
env: Optional[Dict[str, str]] = None,
|
|
319
|
+
url: Optional[str] = None,
|
|
320
|
+
headers: Optional[Dict[str, str]] = None,
|
|
134
321
|
max_retries: int = 1,
|
|
135
322
|
timeout: int = 90,
|
|
136
323
|
) -> bool:
|
|
137
|
-
"""添加并连接一个 MCP 服务器,失败时按 max_retries
|
|
324
|
+
"""添加并连接一个 MCP 服务器,失败时按 max_retries 重试。
|
|
325
|
+
|
|
326
|
+
参数按传输类型扁平化:
|
|
327
|
+
|
|
328
|
+
- ``type="stdio"``:需要 ``command``,可选 ``args`` / ``env``。
|
|
329
|
+
- ``type="http"``(或 ``streamable_http`` 等别名)/ ``type="sse"``:
|
|
330
|
+
需要 ``url``,可选 ``headers``。
|
|
331
|
+
"""
|
|
332
|
+
config = _build_server_config(
|
|
333
|
+
type,
|
|
334
|
+
command=command,
|
|
335
|
+
args=args,
|
|
336
|
+
env=env,
|
|
337
|
+
url=url,
|
|
338
|
+
headers=headers,
|
|
339
|
+
)
|
|
340
|
+
|
|
138
341
|
with self._lock:
|
|
139
342
|
if server_id in self.servers:
|
|
140
343
|
return True
|
|
@@ -269,17 +472,10 @@ class MCPClient:
|
|
|
269
472
|
# 注意:Tina Tools 内部可能还会根据包名加一层前缀
|
|
270
473
|
# 这里的 register_no_function 保持你习惯的格式
|
|
271
474
|
schema = _tool_input_schema(tool)
|
|
272
|
-
properties = schema.get("properties", {}) or {}
|
|
273
475
|
tina_tools.register_no_function(
|
|
274
476
|
name=f"{sid}_{tool.name}", # 外部包名是mcp,内部就是 mcp_sid_name
|
|
275
477
|
description=f"[MCP:{sid}] {tool.description}",
|
|
276
|
-
parameters=
|
|
277
|
-
k: {
|
|
278
|
-
"type": v.get("type", "str"),
|
|
279
|
-
"description": v.get("description", ""),
|
|
280
|
-
}
|
|
281
|
-
for k, v in properties.items()
|
|
282
|
-
},
|
|
478
|
+
parameters=_convert_input_schema(schema),
|
|
283
479
|
required_parameters=schema.get("required", []) or [],
|
|
284
480
|
)
|
|
285
481
|
return tina_tools
|
|
@@ -498,9 +498,10 @@ class TinaTUI(App):
|
|
|
498
498
|
|
|
499
499
|
# 渲染泵间隔:界面更新与滚动统一在这里做,与 chunk 速率解耦
|
|
500
500
|
PUMP_INTERVAL = 0.1
|
|
501
|
-
#
|
|
502
|
-
|
|
503
|
-
|
|
501
|
+
# 流式块重渲染的最小间隔(秒)。采用增量 append 后,开销与新增内容成正比,
|
|
502
|
+
# 因此只需轻微随长度放宽,长文本也能保持较顺滑的刷新。
|
|
503
|
+
RENDER_INTERVAL = 0.06
|
|
504
|
+
RENDER_INTERVAL_MAX = 0.2
|
|
504
505
|
|
|
505
506
|
# 确认框内联显示参数的上限,超过则只显示提示(按 Ctrl+O 查看完整参数)
|
|
506
507
|
CONFIRM_INLINE_ARGS = 200
|
|
@@ -1149,7 +1150,7 @@ class TinaTUI(App):
|
|
|
1149
1150
|
collapsed=bool(block.metadata.get("collapsed", False)),
|
|
1150
1151
|
markup=bool(block.metadata.get("markup", False)),
|
|
1151
1152
|
)
|
|
1152
|
-
return Markdown(
|
|
1153
|
+
return Markdown(classes="assistant-message")
|
|
1153
1154
|
|
|
1154
1155
|
def _ensure_widget(self, block: TuiBlock) -> Any:
|
|
1155
1156
|
widget = self._widgets.get(block.id)
|
|
@@ -1160,15 +1161,31 @@ class TinaTUI(App):
|
|
|
1160
1161
|
self._messages_widget().mount(widget)
|
|
1161
1162
|
return widget
|
|
1162
1163
|
|
|
1164
|
+
def _render_markdown(self, widget: Markdown, content: str) -> Any:
|
|
1165
|
+
"""增量渲染助手 Markdown:只解析新增片段,避免整篇重解析。
|
|
1166
|
+
|
|
1167
|
+
`Markdown.update()` 每次都会整篇重解析并重建所有子块,流式输出时呈
|
|
1168
|
+
O(n²) 开销,正文越长越卡。挂载完成后改用 `append()`,只解析新增片段,
|
|
1169
|
+
开销与新增内容成正比。
|
|
1170
|
+
|
|
1171
|
+
注意:`_initial_markdown` 要始终保持为最新内容,供 `Markdown._on_mount`
|
|
1172
|
+
首次渲染使用,避免挂载时用空串覆盖已写入的内容。
|
|
1173
|
+
"""
|
|
1174
|
+
widget._initial_markdown = content
|
|
1175
|
+
current = widget.source
|
|
1176
|
+
if content == current:
|
|
1177
|
+
return None # 内容未变化,跳过昂贵的 Markdown 解析
|
|
1178
|
+
if widget.is_mounted and content.startswith(current):
|
|
1179
|
+
# 纯追加:append() 只解析新增部分
|
|
1180
|
+
return widget.append(content[len(current):])
|
|
1181
|
+
# 尚未挂载完成 或 内容被替换/回退:整篇刷新一次
|
|
1182
|
+
return widget.update(content)
|
|
1183
|
+
|
|
1163
1184
|
def _apply_block(self, block: TuiBlock) -> Any:
|
|
1164
1185
|
"""把块更新到组件;Markdown 为异步渲染,返回可等待对象"""
|
|
1165
1186
|
widget = self._ensure_widget(block)
|
|
1166
1187
|
if isinstance(widget, Markdown):
|
|
1167
|
-
|
|
1168
|
-
# Textual 挂载时会用 _initial_markdown 再渲染一次,这里同步成最新内容,
|
|
1169
|
-
# 避免它用空串覆盖已流式写入的内容。
|
|
1170
|
-
widget._initial_markdown = block.content
|
|
1171
|
-
return widget.update(block.content)
|
|
1188
|
+
return self._render_markdown(widget, block.content)
|
|
1172
1189
|
if isinstance(widget, (ReasoningBlock, ToolBlock, ResultBlock)):
|
|
1173
1190
|
widget.set_block(block)
|
|
1174
1191
|
elif block.role in ("user", "error", "system"):
|
|
@@ -1178,10 +1195,10 @@ class TinaTUI(App):
|
|
|
1178
1195
|
return None
|
|
1179
1196
|
|
|
1180
1197
|
def _should_render(self, block_id: int, length: int = 0) -> bool:
|
|
1181
|
-
"""
|
|
1198
|
+
"""流式节流:增量 append 足够便宜,仅轻微随长度放宽间隔"""
|
|
1182
1199
|
now = time.monotonic()
|
|
1183
1200
|
interval = min(
|
|
1184
|
-
self.RENDER_INTERVAL_MAX, self.RENDER_INTERVAL + length /
|
|
1201
|
+
self.RENDER_INTERVAL_MAX, self.RENDER_INTERVAL + length / 100000
|
|
1185
1202
|
)
|
|
1186
1203
|
last = self._last_render.get(block_id, 0.0)
|
|
1187
1204
|
if now - last >= interval:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tina-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.7
|
|
4
4
|
Summary: tina is in your computer!
|
|
5
5
|
Home-page: https://gitee.com/wang-churi/tina
|
|
6
6
|
Author: 王出日
|
|
@@ -20,14 +20,14 @@ Requires-Dist: python-dotenv
|
|
|
20
20
|
Provides-Extra: tui
|
|
21
21
|
Requires-Dist: textual>=0.80; extra == "tui"
|
|
22
22
|
Provides-Extra: mcp
|
|
23
|
-
Requires-Dist: mcp; extra == "mcp"
|
|
23
|
+
Requires-Dist: mcp>=2.0; extra == "mcp"
|
|
24
24
|
Provides-Extra: test
|
|
25
25
|
Requires-Dist: pytest>=9.0; extra == "test"
|
|
26
26
|
Requires-Dist: pytest-asyncio>=1.4; extra == "test"
|
|
27
27
|
Requires-Dist: pytest-cov>=7.0; extra == "test"
|
|
28
28
|
Provides-Extra: all
|
|
29
29
|
Requires-Dist: textual>=0.80; extra == "all"
|
|
30
|
-
Requires-Dist: mcp; extra == "all"
|
|
30
|
+
Requires-Dist: mcp>=2.0; extra == "all"
|
|
31
31
|
Dynamic: author
|
|
32
32
|
Dynamic: home-page
|
|
33
33
|
Dynamic: license-file
|
|
@@ -50,6 +50,12 @@ pip install tina-python
|
|
|
50
50
|
# 如果需要使用 MCP(模型上下文协议)服务
|
|
51
51
|
pip install tina-python[mcp]
|
|
52
52
|
|
|
53
|
+
# 如果需要 TUI 终端界面
|
|
54
|
+
pip install tina-python[tui]
|
|
55
|
+
|
|
56
|
+
# 一次装齐所有可选功能(TUI + MCP)
|
|
57
|
+
pip install tina-python[all]
|
|
58
|
+
|
|
53
59
|
# 如果需要运行测试
|
|
54
60
|
pip install tina-python[test]
|
|
55
61
|
```
|
|
@@ -211,8 +217,9 @@ result = await llm.apredict(
|
|
|
211
217
|
stream=False
|
|
212
218
|
)
|
|
213
219
|
|
|
214
|
-
#
|
|
215
|
-
|
|
220
|
+
# 异步流式:先 await 拿到异步生成器
|
|
221
|
+
result = await llm.apredict(input_text="讲个故事", stream=True)
|
|
222
|
+
async for chunk in result:
|
|
216
223
|
print(chunk["content"], end="")
|
|
217
224
|
```
|
|
218
225
|
|
|
@@ -242,8 +249,6 @@ async for chunk in llm.apredict(input_text="讲个故事", stream=True):
|
|
|
242
249
|
{
|
|
243
250
|
"role": "assistant",
|
|
244
251
|
"content": "回复内容",
|
|
245
|
-
# 推理模型时会出现
|
|
246
|
-
"reasoning_content": "推理链...",
|
|
247
252
|
# 有工具调用时会出现
|
|
248
253
|
"tool_calls": [
|
|
249
254
|
{
|
|
@@ -255,7 +260,8 @@ async for chunk in llm.apredict(input_text="讲个故事", stream=True):
|
|
|
255
260
|
]
|
|
256
261
|
}
|
|
257
262
|
|
|
258
|
-
#
|
|
263
|
+
# 流式:逐块返回片段,工具调用以 tool_name / tool_arguments 片段给出;
|
|
264
|
+
# 推理模型的思维链在 reasoning_content 中,末尾可能带有 usage
|
|
259
265
|
```
|
|
260
266
|
|
|
261
267
|
### 1.8 获取可用模型列表
|
|
@@ -392,9 +398,12 @@ tool_calls = [
|
|
|
392
398
|
}
|
|
393
399
|
}
|
|
394
400
|
]
|
|
395
|
-
|
|
401
|
+
# execute 需要事件对象(Agent 内部会自动传入)
|
|
402
|
+
from tina import AgentEvents
|
|
403
|
+
|
|
404
|
+
results = tools.execute(tool_calls, events=AgentEvents())
|
|
396
405
|
# [
|
|
397
|
-
# {"role": "tool", "tool_call_id": "call_123", "
|
|
406
|
+
# {"role": "tool", "tool_call_id": "call_123", "tool_name": "get_weather", "content": "北京今天天气晴朗"}
|
|
398
407
|
# ]
|
|
399
408
|
```
|
|
400
409
|
|
|
@@ -542,7 +551,7 @@ elif agent.state == AgentState.ERROR:
|
|
|
542
551
|
# 获取消息列表
|
|
543
552
|
messages = agent.get_messages()
|
|
544
553
|
|
|
545
|
-
#
|
|
554
|
+
# 清空全部消息(含系统提示词,之后需重新 set_system_prompt)
|
|
546
555
|
agent.clear_messages()
|
|
547
556
|
|
|
548
557
|
# 获取系统提示词
|
|
@@ -630,7 +639,7 @@ def on_turn_end():
|
|
|
630
639
|
agent_a = Agent(llm=llm, tools=tools, name="agent_a")
|
|
631
640
|
agent_b = Agent(llm=llm, tools=tools, name="agent_b")
|
|
632
641
|
|
|
633
|
-
#
|
|
642
|
+
# 关联另一个 Agent(记录到 other_agents,目前不会自动共享消息列表)
|
|
634
643
|
agent_a.connect_agent(agent_b)
|
|
635
644
|
|
|
636
645
|
# 断开连接
|
|
@@ -707,23 +716,31 @@ mcp = MCPClient()
|
|
|
707
716
|
# 添加 stdio 类型 MCP 服务
|
|
708
717
|
mcp.add_server(
|
|
709
718
|
server_id="playwright",
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
"args": ["@playwright/mcp@latest"]
|
|
714
|
-
}
|
|
719
|
+
type="stdio",
|
|
720
|
+
command="npx",
|
|
721
|
+
args=["@playwright/mcp@latest"],
|
|
715
722
|
)
|
|
716
723
|
|
|
717
|
-
# 添加
|
|
724
|
+
# 添加 Streamable HTTP 类型 MCP 服务(推荐,取代旧 SSE)
|
|
718
725
|
mcp.add_server(
|
|
719
726
|
server_id="my-service",
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
727
|
+
type="http", # 也支持 streamable_http / streamableHttp
|
|
728
|
+
url="http://localhost:8080/mcp",
|
|
729
|
+
headers={"Authorization": "Bearer YOUR_API_KEY"}, # 可选
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
# 添加 SSE 类型 MCP 服务(旧传输,仍兼容)
|
|
733
|
+
mcp.add_server(
|
|
734
|
+
server_id="my-legacy-service",
|
|
735
|
+
type="sse",
|
|
736
|
+
url="http://localhost:8080/sse",
|
|
737
|
+
headers={"Authorization": "Bearer YOUR_API_KEY"}, # 可选
|
|
724
738
|
)
|
|
725
739
|
```
|
|
726
740
|
|
|
741
|
+
> `type` 为必填项,用于区分传输类型;`command`/`args`/`env` 与 `url`/`headers`
|
|
742
|
+
> 分别对应 stdio 与远程类型,编辑器会根据 `type` 给出对应的参数提示。
|
|
743
|
+
|
|
727
744
|
### 4.3 在 Agent 中使用 MCP
|
|
728
745
|
|
|
729
746
|
```python
|
|
@@ -734,11 +751,9 @@ from tina.mcp import MCPClient
|
|
|
734
751
|
mcp = MCPClient()
|
|
735
752
|
mcp.add_server(
|
|
736
753
|
server_id="playwright",
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
"args": ["@playwright/mcp@latest"]
|
|
741
|
-
}
|
|
754
|
+
type="stdio",
|
|
755
|
+
command="npx",
|
|
756
|
+
args=["@playwright/mcp@latest"],
|
|
742
757
|
)
|
|
743
758
|
|
|
744
759
|
llm = BaseAPI()
|
|
@@ -759,8 +774,8 @@ for chunk in result:
|
|
|
759
774
|
### 4.4 MCP 服务管理
|
|
760
775
|
|
|
761
776
|
```python
|
|
762
|
-
#
|
|
763
|
-
agent.add_mcp_server("server_id",
|
|
777
|
+
# 添加服务(参数与 MCPClient.add_server 一致)
|
|
778
|
+
agent.add_mcp_server("server_id", type="http", url="http://localhost:8080/mcp")
|
|
764
779
|
|
|
765
780
|
# 移除服务
|
|
766
781
|
agent.remove_mcp_server("server_id")
|
|
@@ -889,8 +904,8 @@ from tina import Agent, Tools
|
|
|
889
904
|
from tina.llm import BaseAPI
|
|
890
905
|
from tina.utils.system_tools import system_tools
|
|
891
906
|
|
|
892
|
-
tools = Tools()
|
|
893
|
-
tools += system_tools # 添加 tina
|
|
907
|
+
tools = Tools(name="my_tools")
|
|
908
|
+
tools += system_tools # 添加 tina 自带的系统工具包(工具集需命名)
|
|
894
909
|
|
|
895
910
|
agent = Agent(
|
|
896
911
|
llm=BaseAPI(),
|
|
@@ -992,7 +1007,7 @@ run_agent_in_cli(agent)
|
|
|
992
1007
|
|------|------|
|
|
993
1008
|
| `#context` | 查看当前对话上下文(消息列表) |
|
|
994
1009
|
| `#clear` | 清屏 |
|
|
995
|
-
|
|
|
1010
|
+
| `exit` / `quit` | 退出对话(`#exit` 不支持,会被当作普通输入) |
|
|
996
1011
|
|
|
997
1012
|
### 6.4 多模态消息构建器 `build_multimodal_message`
|
|
998
1013
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|