kosong-x 0.52.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.
- kosong_x-0.52.0/PKG-INFO +203 -0
- kosong_x-0.52.0/README.md +183 -0
- kosong_x-0.52.0/pyproject.toml +76 -0
- kosong_x-0.52.0/src/kosong/__init__.py +216 -0
- kosong_x-0.52.0/src/kosong/__main__.py +164 -0
- kosong_x-0.52.0/src/kosong/_generate.py +120 -0
- kosong_x-0.52.0/src/kosong/chat_provider/__init__.py +186 -0
- kosong_x-0.52.0/src/kosong/chat_provider/chaos.py +292 -0
- kosong_x-0.52.0/src/kosong/chat_provider/echo/__init__.py +9 -0
- kosong_x-0.52.0/src/kosong/chat_provider/echo/dsl.py +234 -0
- kosong_x-0.52.0/src/kosong/chat_provider/echo/echo.py +125 -0
- kosong_x-0.52.0/src/kosong/chat_provider/echo/scripted_echo.py +103 -0
- kosong_x-0.52.0/src/kosong/chat_provider/kimi.py +518 -0
- kosong_x-0.52.0/src/kosong/chat_provider/mock.py +80 -0
- kosong_x-0.52.0/src/kosong/chat_provider/openai_common.py +167 -0
- kosong_x-0.52.0/src/kosong/contrib/__init__.py +0 -0
- kosong_x-0.52.0/src/kosong/contrib/chat_provider/__init__.py +0 -0
- kosong_x-0.52.0/src/kosong/contrib/chat_provider/anthropic.py +723 -0
- kosong_x-0.52.0/src/kosong/contrib/chat_provider/common.py +5 -0
- kosong_x-0.52.0/src/kosong/contrib/chat_provider/google_genai.py +769 -0
- kosong_x-0.52.0/src/kosong/contrib/chat_provider/openai_legacy.py +370 -0
- kosong_x-0.52.0/src/kosong/contrib/chat_provider/openai_responses.py +602 -0
- kosong_x-0.52.0/src/kosong/contrib/context/__init__.py +0 -0
- kosong_x-0.52.0/src/kosong/contrib/context/linear.py +145 -0
- kosong_x-0.52.0/src/kosong/message.py +303 -0
- kosong_x-0.52.0/src/kosong/py.typed +0 -0
- kosong_x-0.52.0/src/kosong/tooling/__init__.py +354 -0
- kosong_x-0.52.0/src/kosong/tooling/empty.py +24 -0
- kosong_x-0.52.0/src/kosong/tooling/error.py +41 -0
- kosong_x-0.52.0/src/kosong/tooling/mcp.py +72 -0
- kosong_x-0.52.0/src/kosong/tooling/simple.py +133 -0
- kosong_x-0.52.0/src/kosong/utils/__init__.py +0 -0
- kosong_x-0.52.0/src/kosong/utils/aio.py +14 -0
- kosong_x-0.52.0/src/kosong/utils/jsonschema.py +250 -0
- kosong_x-0.52.0/src/kosong/utils/typing.py +3 -0
kosong_x-0.52.0/PKG-INFO
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: kosong-x
|
|
3
|
+
Version: 0.52.0
|
|
4
|
+
Summary: The LLM abstraction layer for modern AI agent applications.
|
|
5
|
+
Requires-Dist: anthropic>=0.78.0
|
|
6
|
+
Requires-Dist: certifi>=2024.0.0
|
|
7
|
+
Requires-Dist: google-genai>=1.56.0
|
|
8
|
+
Requires-Dist: jsonschema>=4.25.1
|
|
9
|
+
Requires-Dist: loguru>=0.6.0,<0.8
|
|
10
|
+
Requires-Dist: openai>=2.14.0,<2.15.0
|
|
11
|
+
Requires-Dist: pydantic>=2.12.5
|
|
12
|
+
Requires-Dist: python-dotenv>=1.2.1
|
|
13
|
+
Requires-Dist: typing-extensions>=4.15.0
|
|
14
|
+
Requires-Dist: mcp>=1,<2
|
|
15
|
+
Requires-Dist: anthropic>=0.78.0 ; extra == 'contrib'
|
|
16
|
+
Requires-Dist: google-genai>=1.55.0 ; extra == 'contrib'
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Provides-Extra: contrib
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Kosong
|
|
22
|
+
|
|
23
|
+
Kosong is an LLM abstraction layer designed for modern AI agent applications. It unifies message structures, asynchronous tool orchestration, and pluggable chat providers so you can build agents with ease and avoid vendor lock-in.
|
|
24
|
+
|
|
25
|
+
> Kosong means "empty" in Malay and Indonesian.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
Kosong requires Python 3.12 or higher. We recommend using uv as the package manager.
|
|
30
|
+
|
|
31
|
+
Init your project with:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv init --python 3.12 # or higher
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then add Kosong as a dependency:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uv add kosong
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
To enable chat providers other than Kimi (e.g. Anthropic and Google Gemini), install the optional extra:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv add 'kosong[contrib]'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Examples
|
|
50
|
+
|
|
51
|
+
### Simple chat completion
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import asyncio
|
|
55
|
+
|
|
56
|
+
import kosong
|
|
57
|
+
from kosong.chat_provider.kimi import Kimi
|
|
58
|
+
from kosong.message import Message
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def main() -> None:
|
|
62
|
+
kimi = Kimi(
|
|
63
|
+
base_url="https://api.moonshot.ai/v1",
|
|
64
|
+
api_key="your_kimi_api_key_here",
|
|
65
|
+
model="kimi-k2-turbo-preview",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
history = [
|
|
69
|
+
Message(role="user", content="Who are you?"),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
result = await kosong.generate(
|
|
73
|
+
chat_provider=kimi,
|
|
74
|
+
system_prompt="You are a helpful assistant.",
|
|
75
|
+
tools=[],
|
|
76
|
+
history=history,
|
|
77
|
+
)
|
|
78
|
+
print(result.message)
|
|
79
|
+
print(result.usage)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
asyncio.run(main())
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Streaming output
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import asyncio
|
|
89
|
+
|
|
90
|
+
import kosong
|
|
91
|
+
from kosong.chat_provider import StreamedMessagePart
|
|
92
|
+
from kosong.chat_provider.kimi import Kimi
|
|
93
|
+
from kosong.message import Message
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
async def main() -> None:
|
|
97
|
+
kimi = Kimi(
|
|
98
|
+
base_url="https://api.moonshot.ai/v1",
|
|
99
|
+
api_key="your_kimi_api_key_here",
|
|
100
|
+
model="kimi-k2-turbo-preview",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
history = [
|
|
104
|
+
Message(role="user", content="Who are you?"),
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
def output(message_part: StreamedMessagePart):
|
|
108
|
+
print(message_part)
|
|
109
|
+
|
|
110
|
+
result = await kosong.generate(
|
|
111
|
+
chat_provider=kimi,
|
|
112
|
+
system_prompt="You are a helpful assistant.",
|
|
113
|
+
tools=[],
|
|
114
|
+
history=history,
|
|
115
|
+
on_message_part=output,
|
|
116
|
+
)
|
|
117
|
+
print(result.message)
|
|
118
|
+
print(result.usage)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
asyncio.run(main())
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Tool calling with `kosong.step`
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
import asyncio
|
|
128
|
+
|
|
129
|
+
from pydantic import BaseModel
|
|
130
|
+
|
|
131
|
+
import kosong
|
|
132
|
+
from kosong import StepResult
|
|
133
|
+
from kosong.chat_provider.kimi import Kimi
|
|
134
|
+
from kosong.message import Message
|
|
135
|
+
from kosong.tooling import CallableTool2, ToolOk, ToolReturnValue
|
|
136
|
+
from kosong.tooling.simple import SimpleToolset
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class AddToolParams(BaseModel):
|
|
140
|
+
a: int
|
|
141
|
+
b: int
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class AddTool(CallableTool2[AddToolParams]):
|
|
145
|
+
name: str = "add"
|
|
146
|
+
description: str = "Add two integers."
|
|
147
|
+
params: type[AddToolParams] = AddToolParams
|
|
148
|
+
|
|
149
|
+
async def __call__(self, params: AddToolParams) -> ToolReturnValue:
|
|
150
|
+
return ToolOk(output=str(params.a + params.b))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def main() -> None:
|
|
154
|
+
kimi = Kimi(
|
|
155
|
+
base_url="https://api.moonshot.ai/v1",
|
|
156
|
+
api_key="your_kimi_api_key_here",
|
|
157
|
+
model="kimi-k2-turbo-preview",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
toolset = SimpleToolset()
|
|
161
|
+
toolset += AddTool()
|
|
162
|
+
|
|
163
|
+
history = [
|
|
164
|
+
Message(role="user", content="Please add 2 and 3 with the add tool."),
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
result: StepResult = await kosong.step(
|
|
168
|
+
chat_provider=kimi,
|
|
169
|
+
system_prompt="You are a precise math tutor.",
|
|
170
|
+
toolset=toolset,
|
|
171
|
+
history=history,
|
|
172
|
+
)
|
|
173
|
+
print(result.message)
|
|
174
|
+
print(await result.tool_results())
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
asyncio.run(main())
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Builtin Demo
|
|
181
|
+
|
|
182
|
+
Kosong comes with a builtin demo agent that you can run locally. To start the demo, run:
|
|
183
|
+
|
|
184
|
+
```sh
|
|
185
|
+
export KIMI_BASE_URL="https://api.moonshot.ai/v1"
|
|
186
|
+
export KIMI_API_KEY="your_kimi_api_key"
|
|
187
|
+
|
|
188
|
+
uv run python -m kosong kimi --with-bash
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Development
|
|
192
|
+
|
|
193
|
+
To set up a development environment, clone the repository and install the dependencies:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
git clone https://github.com/MoonshotAI/kosong.git
|
|
197
|
+
cd kosong
|
|
198
|
+
uv sync --all-extras
|
|
199
|
+
|
|
200
|
+
make check # run lint and type checks
|
|
201
|
+
make test # run tests
|
|
202
|
+
make format # format code
|
|
203
|
+
```
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Kosong
|
|
2
|
+
|
|
3
|
+
Kosong is an LLM abstraction layer designed for modern AI agent applications. It unifies message structures, asynchronous tool orchestration, and pluggable chat providers so you can build agents with ease and avoid vendor lock-in.
|
|
4
|
+
|
|
5
|
+
> Kosong means "empty" in Malay and Indonesian.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Kosong requires Python 3.12 or higher. We recommend using uv as the package manager.
|
|
10
|
+
|
|
11
|
+
Init your project with:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv init --python 3.12 # or higher
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then add Kosong as a dependency:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv add kosong
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
To enable chat providers other than Kimi (e.g. Anthropic and Google Gemini), install the optional extra:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv add 'kosong[contrib]'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
### Simple chat completion
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import asyncio
|
|
35
|
+
|
|
36
|
+
import kosong
|
|
37
|
+
from kosong.chat_provider.kimi import Kimi
|
|
38
|
+
from kosong.message import Message
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def main() -> None:
|
|
42
|
+
kimi = Kimi(
|
|
43
|
+
base_url="https://api.moonshot.ai/v1",
|
|
44
|
+
api_key="your_kimi_api_key_here",
|
|
45
|
+
model="kimi-k2-turbo-preview",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
history = [
|
|
49
|
+
Message(role="user", content="Who are you?"),
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
result = await kosong.generate(
|
|
53
|
+
chat_provider=kimi,
|
|
54
|
+
system_prompt="You are a helpful assistant.",
|
|
55
|
+
tools=[],
|
|
56
|
+
history=history,
|
|
57
|
+
)
|
|
58
|
+
print(result.message)
|
|
59
|
+
print(result.usage)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
asyncio.run(main())
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Streaming output
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import asyncio
|
|
69
|
+
|
|
70
|
+
import kosong
|
|
71
|
+
from kosong.chat_provider import StreamedMessagePart
|
|
72
|
+
from kosong.chat_provider.kimi import Kimi
|
|
73
|
+
from kosong.message import Message
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def main() -> None:
|
|
77
|
+
kimi = Kimi(
|
|
78
|
+
base_url="https://api.moonshot.ai/v1",
|
|
79
|
+
api_key="your_kimi_api_key_here",
|
|
80
|
+
model="kimi-k2-turbo-preview",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
history = [
|
|
84
|
+
Message(role="user", content="Who are you?"),
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
def output(message_part: StreamedMessagePart):
|
|
88
|
+
print(message_part)
|
|
89
|
+
|
|
90
|
+
result = await kosong.generate(
|
|
91
|
+
chat_provider=kimi,
|
|
92
|
+
system_prompt="You are a helpful assistant.",
|
|
93
|
+
tools=[],
|
|
94
|
+
history=history,
|
|
95
|
+
on_message_part=output,
|
|
96
|
+
)
|
|
97
|
+
print(result.message)
|
|
98
|
+
print(result.usage)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
asyncio.run(main())
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Tool calling with `kosong.step`
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import asyncio
|
|
108
|
+
|
|
109
|
+
from pydantic import BaseModel
|
|
110
|
+
|
|
111
|
+
import kosong
|
|
112
|
+
from kosong import StepResult
|
|
113
|
+
from kosong.chat_provider.kimi import Kimi
|
|
114
|
+
from kosong.message import Message
|
|
115
|
+
from kosong.tooling import CallableTool2, ToolOk, ToolReturnValue
|
|
116
|
+
from kosong.tooling.simple import SimpleToolset
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class AddToolParams(BaseModel):
|
|
120
|
+
a: int
|
|
121
|
+
b: int
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class AddTool(CallableTool2[AddToolParams]):
|
|
125
|
+
name: str = "add"
|
|
126
|
+
description: str = "Add two integers."
|
|
127
|
+
params: type[AddToolParams] = AddToolParams
|
|
128
|
+
|
|
129
|
+
async def __call__(self, params: AddToolParams) -> ToolReturnValue:
|
|
130
|
+
return ToolOk(output=str(params.a + params.b))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def main() -> None:
|
|
134
|
+
kimi = Kimi(
|
|
135
|
+
base_url="https://api.moonshot.ai/v1",
|
|
136
|
+
api_key="your_kimi_api_key_here",
|
|
137
|
+
model="kimi-k2-turbo-preview",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
toolset = SimpleToolset()
|
|
141
|
+
toolset += AddTool()
|
|
142
|
+
|
|
143
|
+
history = [
|
|
144
|
+
Message(role="user", content="Please add 2 and 3 with the add tool."),
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
result: StepResult = await kosong.step(
|
|
148
|
+
chat_provider=kimi,
|
|
149
|
+
system_prompt="You are a precise math tutor.",
|
|
150
|
+
toolset=toolset,
|
|
151
|
+
history=history,
|
|
152
|
+
)
|
|
153
|
+
print(result.message)
|
|
154
|
+
print(await result.tool_results())
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
asyncio.run(main())
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Builtin Demo
|
|
161
|
+
|
|
162
|
+
Kosong comes with a builtin demo agent that you can run locally. To start the demo, run:
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
export KIMI_BASE_URL="https://api.moonshot.ai/v1"
|
|
166
|
+
export KIMI_API_KEY="your_kimi_api_key"
|
|
167
|
+
|
|
168
|
+
uv run python -m kosong kimi --with-bash
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Development
|
|
172
|
+
|
|
173
|
+
To set up a development environment, clone the repository and install the dependencies:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
git clone https://github.com/MoonshotAI/kosong.git
|
|
177
|
+
cd kosong
|
|
178
|
+
uv sync --all-extras
|
|
179
|
+
|
|
180
|
+
make check # run lint and type checks
|
|
181
|
+
make test # run tests
|
|
182
|
+
make format # format code
|
|
183
|
+
```
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "kosong-x"
|
|
3
|
+
version = "0.52.0"
|
|
4
|
+
description = "The LLM abstraction layer for modern AI agent applications."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"anthropic>=0.78.0",
|
|
9
|
+
"certifi>=2024.0.0",
|
|
10
|
+
"google-genai>=1.56.0",
|
|
11
|
+
"jsonschema>=4.25.1",
|
|
12
|
+
"loguru>=0.6.0,<0.8",
|
|
13
|
+
"openai>=2.14.0,<2.15.0",
|
|
14
|
+
"pydantic>=2.12.5",
|
|
15
|
+
"python-dotenv>=1.2.1",
|
|
16
|
+
"typing-extensions>=4.15.0",
|
|
17
|
+
"mcp>=1,<2",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
contrib = [
|
|
22
|
+
"anthropic>=0.78.0",
|
|
23
|
+
"google-genai>=1.55.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[dependency-groups]
|
|
27
|
+
dev = [
|
|
28
|
+
"pyright>=1.1.407",
|
|
29
|
+
"ty>=0.0.7",
|
|
30
|
+
"pytest>=9.0.2",
|
|
31
|
+
"pytest-asyncio>=1.3.0",
|
|
32
|
+
"respx>=0.22.0",
|
|
33
|
+
"ruff>=0.14.10",
|
|
34
|
+
"inline-snapshot[black]>=0.31.1",
|
|
35
|
+
"pdoc>=16.0.0",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["uv_build>=0.8.5,<0.10.0"]
|
|
40
|
+
build-backend = "uv_build"
|
|
41
|
+
|
|
42
|
+
[tool.uv.build-backend]
|
|
43
|
+
module-name = ["kosong"]
|
|
44
|
+
|
|
45
|
+
[tool.ruff]
|
|
46
|
+
line-length = 100
|
|
47
|
+
|
|
48
|
+
[tool.ruff.format]
|
|
49
|
+
docstring-code-format = true
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint]
|
|
52
|
+
select = [
|
|
53
|
+
"E", # pycodestyle
|
|
54
|
+
"F", # Pyflakes
|
|
55
|
+
"UP", # pyupgrade
|
|
56
|
+
"B", # flake8-bugbear
|
|
57
|
+
"SIM", # flake8-simplify
|
|
58
|
+
"I", # isort
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[tool.pyright]
|
|
62
|
+
typeCheckingMode = "strict"
|
|
63
|
+
pythonVersion = "3.14"
|
|
64
|
+
include = [
|
|
65
|
+
"src/**/*.py",
|
|
66
|
+
"tests/**/*.py",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[tool.ty.environment]
|
|
70
|
+
python-version = "3.14"
|
|
71
|
+
|
|
72
|
+
[tool.ty.src]
|
|
73
|
+
include = [
|
|
74
|
+
"src/**/*.py",
|
|
75
|
+
"tests/**/*.py",
|
|
76
|
+
]
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kosong is an LLM abstraction layer designed for modern AI agent applications.
|
|
3
|
+
It unifies message structures, asynchronous tool orchestration, and pluggable chat providers so you
|
|
4
|
+
can build agents with ease and avoid vendor lock-in.
|
|
5
|
+
|
|
6
|
+
Key features:
|
|
7
|
+
|
|
8
|
+
- `kosong.generate` creates a completion stream and merges streamed message parts (including
|
|
9
|
+
content and tool calls) from any `ChatProvider` into a complete `Message` plus optional
|
|
10
|
+
`TokenUsage`.
|
|
11
|
+
- `kosong.step` layers tool dispatch (`Tool`, `Toolset`, `SimpleToolset`) over `generate`,
|
|
12
|
+
exposing `StepResult` with awaited tool outputs and streaming callbacks.
|
|
13
|
+
- Message structures and tool abstractions live under `kosong.message` and `kosong.tooling`.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import asyncio
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel
|
|
21
|
+
|
|
22
|
+
import kosong
|
|
23
|
+
from kosong import StepResult
|
|
24
|
+
from kosong.chat_provider.kimi import Kimi
|
|
25
|
+
from kosong.message import Message
|
|
26
|
+
from kosong.tooling import CallableTool2, ToolOk, ToolReturnValue
|
|
27
|
+
from kosong.tooling.simple import SimpleToolset
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AddToolParams(BaseModel):
|
|
31
|
+
a: int
|
|
32
|
+
b: int
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AddTool(CallableTool2[AddToolParams]):
|
|
36
|
+
name: str = "add"
|
|
37
|
+
description: str = "Add two integers."
|
|
38
|
+
params: type[AddToolParams] = AddToolParams
|
|
39
|
+
|
|
40
|
+
async def __call__(self, params: AddToolParams) -> ToolReturnValue:
|
|
41
|
+
return ToolOk(output=str(params.a + params.b))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def main() -> None:
|
|
45
|
+
kimi = Kimi(
|
|
46
|
+
base_url="https://api.moonshot.ai/v1",
|
|
47
|
+
api_key="your_kimi_api_key_here",
|
|
48
|
+
model="kimi-k2-turbo-preview",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
toolset = SimpleToolset()
|
|
52
|
+
toolset += AddTool()
|
|
53
|
+
|
|
54
|
+
history = [
|
|
55
|
+
Message(role="user", content="Please add 2 and 3 with the add tool."),
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
result: StepResult = await kosong.step(
|
|
59
|
+
chat_provider=kimi,
|
|
60
|
+
system_prompt="You are a precise math tutor.",
|
|
61
|
+
toolset=toolset,
|
|
62
|
+
history=history,
|
|
63
|
+
)
|
|
64
|
+
print(result.message)
|
|
65
|
+
print(await result.tool_results())
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
asyncio.run(main())
|
|
69
|
+
```
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
import asyncio
|
|
73
|
+
from collections.abc import Callable, Sequence
|
|
74
|
+
from dataclasses import dataclass
|
|
75
|
+
|
|
76
|
+
from loguru import logger
|
|
77
|
+
|
|
78
|
+
from kosong._generate import GenerateResult, generate
|
|
79
|
+
from kosong.chat_provider import ChatProvider, ChatProviderError, StreamedMessagePart, TokenUsage
|
|
80
|
+
from kosong.message import Message, ToolCall
|
|
81
|
+
from kosong.tooling import ToolResult, ToolResultFuture, Toolset
|
|
82
|
+
from kosong.utils.aio import Callback
|
|
83
|
+
|
|
84
|
+
# Explicitly import submodules
|
|
85
|
+
from . import chat_provider, contrib, message, tooling, utils
|
|
86
|
+
|
|
87
|
+
logger.disable("kosong")
|
|
88
|
+
|
|
89
|
+
__all__ = [
|
|
90
|
+
# submodules
|
|
91
|
+
"chat_provider",
|
|
92
|
+
"tooling",
|
|
93
|
+
"message",
|
|
94
|
+
"utils",
|
|
95
|
+
"contrib",
|
|
96
|
+
# classes and functions
|
|
97
|
+
"generate",
|
|
98
|
+
"GenerateResult",
|
|
99
|
+
"step",
|
|
100
|
+
"StepResult",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
async def step(
|
|
105
|
+
chat_provider: ChatProvider,
|
|
106
|
+
system_prompt: str,
|
|
107
|
+
toolset: Toolset,
|
|
108
|
+
history: Sequence[Message],
|
|
109
|
+
*,
|
|
110
|
+
on_message_part: Callback[[StreamedMessagePart], None] | None = None,
|
|
111
|
+
on_tool_result: Callable[[ToolResult], None] | None = None,
|
|
112
|
+
) -> "StepResult":
|
|
113
|
+
"""
|
|
114
|
+
Run one agent "step". In one step, the function generates LLM response based on the given
|
|
115
|
+
context for exactly one time. All new message parts will be streamed to `on_message_part` in
|
|
116
|
+
real-time if provided. Tool calls will be handled by `toolset`. The generated message will be
|
|
117
|
+
returned in a `StepResult`. Depending on the toolset implementation, the tool calls may be
|
|
118
|
+
handled asynchronously and the results need to be fetched with `await result.tool_results()`.
|
|
119
|
+
|
|
120
|
+
The message history will NOT be modified in this function.
|
|
121
|
+
|
|
122
|
+
The token usage will be returned in the `StepResult` if available.
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
APIConnectionError: If the API connection fails.
|
|
126
|
+
APITimeoutError: If the API request times out.
|
|
127
|
+
APIStatusError: If the API returns a status code of 4xx or 5xx.
|
|
128
|
+
APIEmptyResponseError: If the API returns an empty response.
|
|
129
|
+
ChatProviderError: If any other recognized chat provider error occurs.
|
|
130
|
+
asyncio.CancelledError: If the step is cancelled.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
tool_calls: list[ToolCall] = []
|
|
134
|
+
tool_result_futures: dict[str, ToolResultFuture] = {}
|
|
135
|
+
|
|
136
|
+
def future_done_callback(future: ToolResultFuture):
|
|
137
|
+
if on_tool_result:
|
|
138
|
+
try:
|
|
139
|
+
result = future.result()
|
|
140
|
+
on_tool_result(result)
|
|
141
|
+
except asyncio.CancelledError:
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
async def on_tool_call(tool_call: ToolCall):
|
|
145
|
+
tool_calls.append(tool_call)
|
|
146
|
+
result = toolset.handle(tool_call)
|
|
147
|
+
|
|
148
|
+
if isinstance(result, ToolResult):
|
|
149
|
+
future = ToolResultFuture()
|
|
150
|
+
future.add_done_callback(future_done_callback)
|
|
151
|
+
future.set_result(result)
|
|
152
|
+
tool_result_futures[tool_call.id] = future
|
|
153
|
+
else:
|
|
154
|
+
result.add_done_callback(future_done_callback)
|
|
155
|
+
tool_result_futures[tool_call.id] = result
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
result = await generate(
|
|
159
|
+
chat_provider,
|
|
160
|
+
system_prompt,
|
|
161
|
+
toolset.tools,
|
|
162
|
+
history,
|
|
163
|
+
on_message_part=on_message_part,
|
|
164
|
+
on_tool_call=on_tool_call,
|
|
165
|
+
)
|
|
166
|
+
except (ChatProviderError, asyncio.CancelledError):
|
|
167
|
+
# cancel all the futures to avoid hanging tasks
|
|
168
|
+
for future in tool_result_futures.values():
|
|
169
|
+
future.remove_done_callback(future_done_callback)
|
|
170
|
+
future.cancel()
|
|
171
|
+
await asyncio.gather(*tool_result_futures.values(), return_exceptions=True)
|
|
172
|
+
raise
|
|
173
|
+
|
|
174
|
+
return StepResult(
|
|
175
|
+
result.id,
|
|
176
|
+
result.message,
|
|
177
|
+
result.usage,
|
|
178
|
+
tool_calls,
|
|
179
|
+
tool_result_futures,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@dataclass(frozen=True, slots=True)
|
|
184
|
+
class StepResult:
|
|
185
|
+
id: str | None
|
|
186
|
+
"""The ID of the generated message."""
|
|
187
|
+
|
|
188
|
+
message: Message
|
|
189
|
+
"""The message generated in this step."""
|
|
190
|
+
|
|
191
|
+
usage: TokenUsage | None
|
|
192
|
+
"""The token usage in this step."""
|
|
193
|
+
|
|
194
|
+
tool_calls: list[ToolCall]
|
|
195
|
+
"""All the tool calls generated in this step."""
|
|
196
|
+
|
|
197
|
+
_tool_result_futures: dict[str, ToolResultFuture]
|
|
198
|
+
"""@private The futures of the results of the spawned tool calls."""
|
|
199
|
+
|
|
200
|
+
async def tool_results(self) -> list[ToolResult]:
|
|
201
|
+
"""All the tool results returned by corresponding tool calls."""
|
|
202
|
+
if not self._tool_result_futures:
|
|
203
|
+
return []
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
results: list[ToolResult] = []
|
|
207
|
+
for tool_call in self.tool_calls:
|
|
208
|
+
future = self._tool_result_futures[tool_call.id]
|
|
209
|
+
result = await future
|
|
210
|
+
results.append(result)
|
|
211
|
+
return results
|
|
212
|
+
finally:
|
|
213
|
+
# one exception should cancel all the futures to avoid hanging tasks
|
|
214
|
+
for future in self._tool_result_futures.values():
|
|
215
|
+
future.cancel()
|
|
216
|
+
await asyncio.gather(*self._tool_result_futures.values(), return_exceptions=True)
|