my-llmkit 0.3.1__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.
Files changed (31) hide show
  1. my_llmkit-0.3.1/PKG-INFO +684 -0
  2. my_llmkit-0.3.1/README.md +668 -0
  3. my_llmkit-0.3.1/my_llmkit/__init__.py +7 -0
  4. my_llmkit-0.3.1/my_llmkit/chat/__init__.py +93 -0
  5. my_llmkit-0.3.1/my_llmkit/chat/approval.py +64 -0
  6. my_llmkit-0.3.1/my_llmkit/chat/base.py +193 -0
  7. my_llmkit-0.3.1/my_llmkit/chat/claude.py +492 -0
  8. my_llmkit-0.3.1/my_llmkit/chat/events.py +105 -0
  9. my_llmkit-0.3.1/my_llmkit/chat/model_settings.py +226 -0
  10. my_llmkit-0.3.1/my_llmkit/chat/openai_compatible.py +600 -0
  11. my_llmkit-0.3.1/my_llmkit/chat/processor.py +205 -0
  12. my_llmkit-0.3.1/my_llmkit/chat/runner.py +161 -0
  13. my_llmkit-0.3.1/my_llmkit/chat/tools.py +463 -0
  14. my_llmkit-0.3.1/my_llmkit/chat/types.py +562 -0
  15. my_llmkit-0.3.1/my_llmkit/image.py +1138 -0
  16. my_llmkit-0.3.1/my_llmkit/image_cli.py +167 -0
  17. my_llmkit-0.3.1/my_llmkit/log.py +9 -0
  18. my_llmkit-0.3.1/my_llmkit/mcp/__init__.py +30 -0
  19. my_llmkit-0.3.1/my_llmkit/mcp/mcp_client.py +452 -0
  20. my_llmkit-0.3.1/my_llmkit/mcp/mcp_config.py +829 -0
  21. my_llmkit-0.3.1/my_llmkit/models/__init__.py +70 -0
  22. my_llmkit-0.3.1/my_llmkit/models/capabilities.py +113 -0
  23. my_llmkit-0.3.1/my_llmkit/models/info.py +429 -0
  24. my_llmkit-0.3.1/my_llmkit.egg-info/PKG-INFO +684 -0
  25. my_llmkit-0.3.1/my_llmkit.egg-info/SOURCES.txt +29 -0
  26. my_llmkit-0.3.1/my_llmkit.egg-info/dependency_links.txt +1 -0
  27. my_llmkit-0.3.1/my_llmkit.egg-info/entry_points.txt +2 -0
  28. my_llmkit-0.3.1/my_llmkit.egg-info/requires.txt +9 -0
  29. my_llmkit-0.3.1/my_llmkit.egg-info/top_level.txt +1 -0
  30. my_llmkit-0.3.1/pyproject.toml +33 -0
  31. my_llmkit-0.3.1/setup.cfg +4 -0
@@ -0,0 +1,684 @@
1
+ Metadata-Version: 2.4
2
+ Name: my-llmkit
3
+ Version: 0.3.1
4
+ Summary: a kit for building LLM applications
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: anthropic>=0.75.0
8
+ Requires-Dist: google-genai>=2.14.0
9
+ Requires-Dist: httpx>=0.28.1
10
+ Requires-Dist: mcp>=1.12.4
11
+ Requires-Dist: openai>=2.14.0
12
+ Requires-Dist: openai-agents>=0.6.2
13
+ Requires-Dist: pytest>=9.0.2
14
+ Requires-Dist: pytest-asyncio>=1.3.0
15
+ Requires-Dist: python-dotenv>=1.2.1
16
+
17
+ # my-llmkit
18
+
19
+ 一个统一的 LLM 聊天接口工具包,支持多种 AI 模型提供商。
20
+
21
+ ## 架构概览
22
+
23
+ ```
24
+ ┌─────────────────────────────────────────────────────────────────────┐
25
+ │ my-llmkit │
26
+ ├─────────────────────────────────────────────────────────────────────┤
27
+ │ │
28
+ │ ┌──────────────────────────────────────────────────────────────┐ │
29
+ │ │ chat 模块 (核心) │ │
30
+ │ ├──────────────────────────────────────────────────────────────┤ │
31
+ │ │ │ │
32
+ │ │ LLMChatCompletion (抽象基类) │ │
33
+ │ │ ▲ ▲ │ │
34
+ │ │ │ │ │ │
35
+ │ │ │ │ │ │
36
+ │ │ ┌──────┴──────┐ ┌──────┴──────┐ │ │
37
+ │ │ │ OpenAI │ │ Claude │ │ │
38
+ │ │ │ Compatible │ │ Client │ │ │
39
+ │ │ └─────────────┘ └─────────────┘ │ │
40
+ │ │ │ │
41
+ │ │ ┌─────────────────────────────────────────────┐ │ │
42
+ │ │ │ ChatCompletionStreamRunner │ │ │
43
+ │ │ │ - 流式事件处理 │ │ │
44
+ │ │ │ - 工具调用管理 │ │ │
45
+ │ │ │ - 多轮对话控制 │ │ │
46
+ │ │ └─────────────────────────────────────────────┘ │ │
47
+ │ │ │ │ │
48
+ │ │ ▼ │ │
49
+ │ │ ┌─────────────────────────────────────────────┐ │ │
50
+ │ │ │ ChatCompletionStreamProcessor │ │ │
51
+ │ │ │ - 处理 UnifiedChunk 流 │ │ │
52
+ │ │ │ - 生成统一事件 │ │ │
53
+ │ │ │ - 结构化输出解析 │ │ │
54
+ │ │ └─────────────────────────────────────────────┘ │ │
55
+ │ │ │ │
56
+ │ │ 核心组件: │ │
57
+ │ │ • UnifiedMessage - 统一消息格式 │ │
58
+ │ │ • UnifiedChunk - 统一流式块 │ │
59
+ │ │ • ToolFunctions - 工具函数管理 │ │
60
+ │ │ • ToolExecutor - 工具执行器 │ │
61
+ │ │ • ModelSettings - 模型配置 │ │
62
+ │ └──────────────────────────────────────────────────────────────┘ │
63
+ │ │
64
+ │ ┌──────────────────────────────────────────────────────────────┐ │
65
+ │ │ mcp 模块 (扩展工具) │ │
66
+ │ ├──────────────────────────────────────────────────────────────┤ │
67
+ │ │ │ │
68
+ │ │ MCPServerBase (抽象基类) │ │
69
+ │ │ ▲ ▲ ▲ │ │
70
+ │ │ │ │ │ │ │
71
+ │ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │ │
72
+ │ │ │ Stdio │ │ SSE │ │ HTTP │ │ │
73
+ │ │ │ Server │ │ Server │ │ Server │ │ │
74
+ │ │ └─────────┘ └─────────┘ └─────────┘ │ │
75
+ │ │ │ │
76
+ │ │ MCPClientPool - 按需连接、复用与自动重连 │ │
77
+ │ │ MCPServersContext - 短生命周期的上下文管理 │ │
78
+ │ │ MCPManager - 配置文件管理 │ │
79
+ │ └──────────────────────────────────────────────────────────────┘ │
80
+ │ │
81
+ │ ┌──────────────────────────────────────────────────────────────┐ │
82
+ │ │ models 模块 (能力查询) │ │
83
+ │ ├──────────────────────────────────────────────────────────────┤ │
84
+ │ │ │ │
85
+ │ │ • supports_reasoning() - 推理能力检测 │ │
86
+ │ │ • supports_vision() - 视觉能力检测 │ │
87
+ │ │ • supports_function_calling() - 工具调用检测 │ │
88
+ │ │ • get_model_info() - 获取模型信息 │ │
89
+ │ │ │ │
90
+ │ │ 数据源: litellm, models.dev (自动缓存) │ │
91
+ │ └──────────────────────────────────────────────────────────────┘ │
92
+ │ │
93
+ └─────────────────────────────────────────────────────────────────────┘
94
+
95
+ 数据流向:
96
+
97
+ User Input (UnifiedMessage)
98
+ │
99
+ ▼
100
+ LLMChatCompletion
101
+ │
102
+ ├──> OpenAI API / Claude API / ...
103
+ │
104
+ ▼
105
+ Stream (UnifiedChunk)
106
+ │
107
+ ▼
108
+ ChatCompletionStreamProcessor
109
+ │
110
+ ├──> Content Events
111
+ ├──> Reasoning Events
112
+ ├──> Tool Call Events
113
+ ├──> Usage Events
114
+ │
115
+ ▼
116
+ ChatCompletionStreamRunner
117
+ │
118
+ ├──> Tool Execution (本地函数 + MCP 服务器)
119
+ ├──> Multi-turn Conversation
120
+ │
121
+ ▼
122
+ Final Response (UnifiedResponse + 结构化输出)
123
+ ```
124
+
125
+ ## 模块
126
+
127
+ - `my_llmkit.chat` - 统一的聊天接口
128
+ - `my_llmkit.image` - 多供应商图片生成与编辑接口
129
+ - `my_llmkit.mcp` - MCP (Model Context Protocol) 支持
130
+ - `my_llmkit.models` - 模型能力查询
131
+
132
+ 详细文档:
133
+
134
+ - [MCP 配置、连接池与生命周期](docs/mcp.md)
135
+ - [统一工具结果与多模态 MCP 返回值](docs/tool-results.md)
136
+ - [工具执行审批](docs/tool-approval.md)
137
+ - [每轮请求消息准备](docs/message-preparer.md)
138
+
139
+ ## 安装
140
+
141
+ ```bash
142
+ pip install -e .
143
+ ```
144
+
145
+ ## Chat 模块使用说明
146
+
147
+ ### 基本用法
148
+
149
+ #### 1. OpenAI 兼容接口
150
+
151
+ 支持所有兼容 OpenAI API 的模型,包括 GPT、Gemini、DeepSeek、Kimi、Grok 等。
152
+
153
+ ```python
154
+ from my_llmkit.chat import OpenAICompatibleChatCompletion, UnifiedMessage
155
+
156
+ # 创建客户端
157
+ client = OpenAICompatibleChatCompletion(
158
+ api_key="your-api-key",
159
+ api_base="https://api.openai.com/v1",
160
+ model="gpt-4"
161
+ )
162
+
163
+ # 发送消息
164
+ messages = [UnifiedMessage(role="user", content="你好")]
165
+ result = client.run_stream(messages=messages)
166
+
167
+ # 处理流式响应
168
+ async for event in result.stream_event():
169
+ if event.type == "content":
170
+ print(event.content, end="", flush=True)
171
+ elif event.type == "usage":
172
+ print(f"\n使用量: {event.usage}")
173
+ ```
174
+
175
+ #### 2. Claude 接口
176
+
177
+ ```python
178
+ from my_llmkit.chat import ClaudeChatCompletion, UnifiedMessage
179
+ from my_llmkit.chat.model_settings import ModelSettings
180
+
181
+ # 创建客户端(带思考模式)
182
+ model_settings = ModelSettings(
183
+ include_usage=True,
184
+ max_tokens=30000,
185
+ extra_body={
186
+ "thinking": {"type": "enabled", "budget_tokens": 10000}
187
+ }
188
+ )
189
+
190
+ client = ClaudeChatCompletion(
191
+ api_key="your-api-key",
192
+ api_base="https://api.anthropic.com",
193
+ model="claude-sonnet-4.5",
194
+ model_settings=model_settings
195
+ )
196
+
197
+ messages = [UnifiedMessage(role="user", content="解释一下量子计算")]
198
+ result = client.run_stream(messages=messages)
199
+
200
+ async for event in result.stream_event():
201
+ if event.type == "reasoning_content":
202
+ print(f"[思考] {event.content}", end="", flush=True)
203
+ elif event.type == "content":
204
+ print(event.content, end="", flush=True)
205
+ ```
206
+
207
+ ### 高级功能
208
+
209
+ #### 1. 工具调用
210
+
211
+ ```python
212
+ from my_llmkit.chat import ToolFunctions
213
+ from datetime import datetime
214
+
215
+ def get_weather_tool(day: str):
216
+ """
217
+ 获取指定日期的天气信息
218
+
219
+ Args:
220
+ day: 日期,格式为 YYYY-MM-DD
221
+ """
222
+ return f"{day} 的天气是晴天,温度 6°C"
223
+
224
+ def now_tool() -> str:
225
+ """
226
+ 获取当前日期和时间
227
+ """
228
+ now = datetime.now()
229
+ return f"当前时间: {now.strftime('%Y-%m-%d %H:%M:%S')}"
230
+
231
+ # 使用工具
232
+ messages = [UnifiedMessage(role="user", content="明天天气怎么样")]
233
+ result = client.run_stream(
234
+ messages=messages,
235
+ tools=ToolFunctions(now_tool, get_weather_tool)
236
+ )
237
+
238
+ async for event in result.stream_event():
239
+ if event.type == "tool_call_start":
240
+ print(f"\n[调用工具] {event.function_name}")
241
+ elif event.type == "tool_call_result":
242
+ print(f"\n[工具结果] {event.function_name}: {event.function_result}")
243
+ elif event.type == "content":
244
+ print(event.content, end="", flush=True)
245
+ ```
246
+
247
+ #### 2. MCP 工具集成
248
+
249
+ 短生命周期任务可以使用 `MCPServersContext`:
250
+
251
+ ```python
252
+ from my_llmkit.mcp import MCPServersContext
253
+
254
+ async with MCPServersContext("~/mcp.json") as servers:
255
+ messages = [UnifiedMessage(role="user", content="~/Downloads 里有哪些文件")]
256
+ result = client.run_stream(
257
+ messages=messages,
258
+ tools=ToolFunctions(now_tool),
259
+ mcp_servers=servers
260
+ )
261
+
262
+ async for event in result.stream_event():
263
+ if event.type == "content":
264
+ print(event.content, end="", flush=True)
265
+ ```
266
+
267
+ 服务端、TUI 等长生命周期进程建议使用 `MCPClientPool`。连接池会按需连接,
268
+ 在多个请求间复用连接,并在工具调用失败后让稳定 handle 在下次调用时自动重连:
269
+
270
+ ```python
271
+ from my_llmkit.mcp import MCPClientPool
272
+
273
+ pool = MCPClientPool.from_file("~/mcp.json")
274
+ try:
275
+ servers = await pool.resolve(["filesystem"])
276
+ result = client.run_stream(messages=messages, mcp_servers=servers)
277
+ async for event in result.stream_event():
278
+ if event.type == "content":
279
+ print(event.content, end="", flush=True)
280
+ finally:
281
+ await pool.close()
282
+ ```
283
+
284
+ 配置中的 `tool_timeout` 控制单次 MCP 工具调用超时,默认 120 秒;
285
+ 设为 `0`、负数或 `null` 可禁用。完整配置和生命周期说明见
286
+ [MCP 文档](docs/mcp.md)。
287
+
288
+ #### 3. 结构化输出
289
+
290
+ ##### JSON Schema 模式(推荐)
291
+
292
+ ```python
293
+ from pydantic import BaseModel
294
+
295
+ class TimeResult(BaseModel):
296
+ local_time: str
297
+ utc_time: str
298
+ tz: str
299
+ weekday: str
300
+
301
+ messages = [UnifiedMessage(role="user", content="现在几点?")]
302
+ result = client.run_stream(
303
+ messages=messages,
304
+ tools=ToolFunctions(now_tool),
305
+ response_format=TimeResult
306
+ )
307
+
308
+ # 处理流式输出
309
+ async for event in result.stream_event():
310
+ if event.type == "content":
311
+ print(event.content, end="", flush=True)
312
+
313
+ # 获取结构化输出结果
314
+ output: TimeResult = result.output_result
315
+ print(f"\n结构化输出: {output.local_time}, {output.weekday}")
316
+ ```
317
+
318
+ ##### JSON Object 模式
319
+
320
+ ```python
321
+ messages = [UnifiedMessage(role="user", content="""现在几点?
322
+ 请使用以下 JSON 格式来回答:
323
+ {
324
+ "local_time": "本地时间",
325
+ "utc_time": "UTC时间",
326
+ "tz": "时区",
327
+ "weekday": "星期几"
328
+ }
329
+ """)]
330
+
331
+ result = client.run_stream(
332
+ messages=messages,
333
+ tools=ToolFunctions(now_tool),
334
+ response_format={"type": "json_object"}
335
+ )
336
+
337
+ async for event in result.stream_event():
338
+ if event.type == "content":
339
+ print(event.content, end="", flush=True)
340
+
341
+ # 获取 JSON 输出(返回 dict)
342
+ output: dict = result.output_result
343
+ print(f"\nJSON 输出: {output}")
344
+ ```
345
+
346
+ #### 4. 图片输入
347
+
348
+ ```python
349
+ from my_llmkit.chat import TextContent, ImageContent
350
+
351
+ messages = [
352
+ UnifiedMessage(
353
+ role="user",
354
+ content=[
355
+ TextContent(text="图片里有什么"),
356
+ ImageContent(image_url="https://example.com/image.png"),
357
+ # 或从本地文件加载
358
+ # ImageContent.from_file("/path/to/image.png"),
359
+ ]
360
+ )
361
+ ]
362
+
363
+ result = client.run_stream(messages=messages)
364
+ ```
365
+
366
+ #### 5. 文档输入(PDF)
367
+
368
+ 支持 PDF 文档输入,可以通过 URL 或本地文件方式。
369
+
370
+ ##### Claude(支持 URL 和 Base64)
371
+
372
+ ```python
373
+ from my_llmkit.chat import TextContent, DocumentContent
374
+
375
+ # 通过 URL
376
+ messages = [
377
+ UnifiedMessage(
378
+ role="user",
379
+ content=[
380
+ TextContent(text="总结这个文档的主要内容"),
381
+ DocumentContent.from_url("https://example.com/document.pdf"),
382
+ ]
383
+ )
384
+ ]
385
+
386
+ # 通过本地文件
387
+ messages = [
388
+ UnifiedMessage(
389
+ role="user",
390
+ content=[
391
+ TextContent(text="分析这个研究报告"),
392
+ DocumentContent.from_file("/path/to/report.pdf"),
393
+ ]
394
+ )
395
+ ]
396
+
397
+ result = client.run_stream(messages=messages)
398
+ async for event in result.stream_event():
399
+ if event.type == "content":
400
+ print(event.content, end="", flush=True)
401
+ ```
402
+
403
+ ##### OpenAI(仅支持 Base64)
404
+
405
+ ```python
406
+ from my_llmkit.chat import TextContent, DocumentContent
407
+
408
+ # OpenAI 只支持 base64 方式,建议使用 from_file
409
+ messages = [
410
+ UnifiedMessage(
411
+ role="user",
412
+ content=[
413
+ TextContent(text="这个文档讲了什么?"),
414
+ DocumentContent.from_file("/path/to/document.pdf"),
415
+ ]
416
+ )
417
+ ]
418
+
419
+ # 或手动指定 base64 数据
420
+ messages = [
421
+ UnifiedMessage(
422
+ role="user",
423
+ content=[
424
+ TextContent(text="分析这个文档"),
425
+ DocumentContent.from_base64(
426
+ base64_data="your-base64-encoded-pdf-data",
427
+ filename="document.pdf"
428
+ ),
429
+ ]
430
+ )
431
+ ]
432
+
433
+ result = client.run_stream(messages=messages)
434
+ async for event in result.stream_event():
435
+ if event.type == "content":
436
+ print(event.content, end="", flush=True)
437
+ ```
438
+
439
+ **注意事项:**
440
+
441
+ - Claude 支持 URL 和 Base64 两种方式
442
+ - OpenAI 仅支持 Base64 方式,使用 URL 会抛出异常
443
+ - 当前仅支持 PDF 格式(`application/pdf`)
444
+ - 使用 `from_file()` 方法会自动读取文件并转换为 base64
445
+
446
+ #### 6. 推理模式(Reasoning)
447
+
448
+ ```python
449
+ from openai.types import Reasoning
450
+
451
+ # 创建支持推理的客户端
452
+ from my_llmkit.chat.model_settings import ModelSettings
453
+
454
+ model_settings = ModelSettings(
455
+ include_usage=True,
456
+ reasoning=Reasoning(effort="medium") # 可选: low, medium, high
457
+ )
458
+
459
+ client = OpenAICompatibleChatCompletion(
460
+ api_key="your-api-key",
461
+ api_base="https://api.provider.com/v1",
462
+ model="gpt-5.2", # 或其他支持推理的模型
463
+ model_settings=model_settings
464
+ )
465
+
466
+ messages = [UnifiedMessage(role="user", content="解决这个数学问题:...")]
467
+ result = client.run_stream(messages=messages)
468
+
469
+ async for event in result.stream_event():
470
+ if event.type == "reasoning_content":
471
+ # 推理过程
472
+ print(f"[推理] {event.content}", end="", flush=True)
473
+ elif event.type == "content":
474
+ # 最终回答
475
+ print(event.content, end="", flush=True)
476
+ ```
477
+
478
+ ### 事件类型
479
+
480
+ 流式响应中可能返回的事件类型:
481
+
482
+ - `content` - 普通文本内容
483
+ - `reasoning_content` - 推理过程内容(仅支持推理的模型)
484
+ - `tool_call_start` - 工具调用开始
485
+ - `tool_call_result` - 工具调用结果
486
+ - `usage` - Token 使用量统计
487
+
488
+ ### 支持的模型提供商
489
+
490
+ - **OpenAI**: GPT-4, GPT-5.2, etc.
491
+ - **Anthropic**: Claude Sonnet 4.5, Claude Sonnet 4.6, Claude Haiku 4.5
492
+ - **Google**: Gemini 3 Pro, Gemini 3 Flash
493
+ - **DeepSeek**: DeepSeek Reasoner, DeepSeek Chat
494
+ - **Moonshot**: Kimi K2 Thinking, Kimi K2.5, Kimi K2 Turbo
495
+ - **字节跳动**: Doubao Seed 1.8, Doubao Seed 2.0 Pro
496
+ - **百度**: ERNIE X 1.1, ERNIE 5 Thinking
497
+ - **阿里**: Qwen Plus
498
+ - **xAI**: Grok 4.1 Fast, Grok 4 Fast, Grok 4
499
+ - **MiniMax**: MiniMax M2.5
500
+
501
+ 以及通过 OpenRouter、ZenMux、302AI 等聚合平台访问的其他模型。
502
+
503
+ ## Image 模块使用说明
504
+
505
+ ### Python API
506
+
507
+ `my_llmkit.image` 提供统一的异步图片生成与编辑接口,支持 OpenAI
508
+ 兼容接口、OpenRouter、Google Gemini,以及 ZenMux、302AI 等聚合平台。
509
+
510
+ ```python
511
+ import asyncio
512
+ from pathlib import Path
513
+
514
+ from my_llmkit.image import draw, suffix_for_mime_type
515
+
516
+
517
+ async def main() -> None:
518
+ images = await draw(
519
+ model_path="openrouter/gpt-image-2",
520
+ prompt="一只放在白色桌面上的陶瓷杯,产品摄影风格",
521
+ size="1024x1024",
522
+ number=1,
523
+ input_images=[
524
+ "./reference.png",
525
+ "https://example.com/reference.webp",
526
+ ],
527
+ )
528
+
529
+ image = images[0]
530
+ output_path = Path(f"result{suffix_for_mime_type(image.mime_type)}")
531
+ output_path.write_bytes(image.data)
532
+
533
+
534
+ asyncio.run(main())
535
+ ```
536
+
537
+ `draw()` 返回 `GeneratedImage` 列表,每项包含图片二进制数据 `data` 和
538
+ MIME 类型 `mime_type`。`input_images` 可以传入本地文件路径或 HTTP/HTTPS
539
+ URL;URL 图片会缓存在 `/tmp/my_llmkit/input_images`。
540
+
541
+ 当前注册的模型路径:
542
+
543
+ - `openrouter/gpt-image-2`
544
+ - `openrouter/gemini-3-pro-image`
545
+ - `openrouter/gemini-3.1-flash-image`
546
+ - `openrouter/grok-image`
547
+ - `zenmux/gpt-image-2`
548
+ - `zenmux/gemini-3-pro-image`
549
+ - `zenmux/gemini-3.1-flash-image`
550
+ - `302ai/gpt-image-2`
551
+ - `google/gemini-3-pro-image`
552
+ - `google/gemini-3.1-flash-image`
553
+
554
+ 根据模型配置对应的 API Key:
555
+
556
+ ```shell
557
+ OPENROUTER_API_KEY=
558
+ ZENMUX_API_KEY=
559
+ AI302_API_KEY=
560
+ GEMINI_API_KEY=
561
+ ```
562
+
563
+ 模块依次读取项目根目录 `.env` 和 `~/.gede/config/.env`,已经存在的进程
564
+ 环境变量不会被 dotenv 文件覆盖。
565
+
566
+ OpenAI 兼容接口和 OpenRouter 会直接使用 `size`。Gemini 支持以下格式:
567
+
568
+ - `16:9`:设置宽高比
569
+ - `2K` 或 `4K`:设置图片尺寸
570
+ - `16:9@2K`:同时设置宽高比和图片尺寸
571
+ - `auto`:不显式指定 Gemini 图片配置
572
+
573
+ `web_search` 参数当前尚未实现,传入非 `False` 值会抛出
574
+ `NotImplementedError`。
575
+
576
+ ### Image CLI
577
+
578
+ 安装项目后可以使用 `draw` 命令生成图片:
579
+
580
+ ```bash
581
+ draw \
582
+ --model-path openrouter/gpt-image-2 \
583
+ --prompt "一只放在白色桌面上的陶瓷杯,产品摄影风格" \
584
+ --output ./result.png \
585
+ --size 1024x1024
586
+ ```
587
+
588
+ 使用 `-` 从标准输入读取提示词:
589
+
590
+ ```bash
591
+ echo "白色背景上的红色立方体" | draw \
592
+ --model-path openrouter/gpt-image-2 \
593
+ --prompt - \
594
+ --output ./result.png
595
+ ```
596
+
597
+ 通过重复 `--input-image` 提供本地或 URL 参考图:
598
+
599
+ ```bash
600
+ draw \
601
+ --model-path openrouter/gpt-image-2 \
602
+ --prompt "根据参考对象生成产品照片" \
603
+ --input-image ./reference.png \
604
+ --input-image https://example.com/reference.webp \
605
+ --output ./result.png
606
+ ```
607
+
608
+ `--number` 可以一次生成多张图片。第一张使用指定输出路径,后续文件依次
609
+ 增加 `-2`、`-3` 后缀。`--log-level` 支持 `DEBUG`、`INFO`、`WARNING`、
610
+ `ERROR` 和 `CRITICAL`。
611
+
612
+ ## 测试
613
+
614
+ 测试代码位于 `tests/`,当前入口是 `tests/model_tests.py`。这些用例会真实调用模型接口,运行前需要在 `~/.gede/config/.env` 配好对应 provider 的 API Key 和 Base URL。
615
+
616
+ `.env` 中会读取的变量包括:
617
+
618
+ ```shell
619
+ OPENROUTER_API_KEY=
620
+ OPENROUTER_BASE_URL=
621
+ ZENMUX_API_KEY=
622
+ ZENMUX_BASE_URL_OPENAI=
623
+ ZENMUX_BASE_URL_ANTHROPIC=
624
+ AI302_API_KEY=
625
+ AI302_BASE_URL=
626
+ DEEPSEEK_API_KEY=
627
+ DEEPSEEK_BASE_URL=
628
+ MOONSHOT_API_KEY=
629
+ MOONSHOT_BASE_URL=
630
+ ARK_API_KEY=
631
+ ARK_BASE_URL=
632
+ QIANFAN_API_KEY=
633
+ QIANFAN_BASE_URL=
634
+ DASHSCOPE_API_KEY=
635
+ DASHSCOPE_BASE_URL=
636
+ GOOGLE_API_KEY=
637
+ GOOGLE_BASE_URL=
638
+ MINIMAX_API_KEY=
639
+ MINIMAX_BASE_URL_ANTHROPIC=
640
+ ```
641
+
642
+ 部分图片和 PDF 测试依赖外部 URL;文件输入测试默认读取本地文件:
643
+
644
+ - `/Users/reynoldqin/Downloads/1.png`
645
+ - `/Users/reynoldqin/Downloads/planning-with-files.pdf`
646
+
647
+ ```shell
648
+ # 收集当前模型集成测试
649
+ pytest --collect-only -q tests/model_tests.py
650
+
651
+ # 运行全部模型集成测试
652
+ pytest -s --log-cli-level=INFO tests/model_tests.py
653
+
654
+ # 单独运行某个模型
655
+ pytest -s --log-cli-level=INFO tests/model_tests.py::test_gpt_5_2
656
+ pytest -s --log-cli-level=INFO tests/model_tests.py::test_claude_4_6_sonnet_zenmux
657
+ pytest -s --log-cli-level=INFO tests/model_tests.py::test_qwen_plus
658
+ ```
659
+
660
+ 当前覆盖的测试场景包括:
661
+
662
+ - 工具调用:流式和非流式
663
+ - 推理模式:OpenAI 兼容接口、Claude、Qwen
664
+ - 结构化输出:Pydantic JSON Schema 和 JSON Object 模式
665
+ - 图片输入:URL 和本地文件
666
+ - PDF 文档输入:URL 和本地文件
667
+
668
+ 当前模型用例:
669
+
670
+ - `test_gpt_5_2`
671
+ - `test_gemini_3_pro`
672
+ - `test_kimi_k2_thinking`
673
+ - `test_kimi_k2_5`
674
+ - `test_deepseek_reasoner`
675
+ - `test_doubao_seed_2_pro`
676
+ - `test_doubao_seed_2_lite`
677
+ - `test_doubao_seed_2_1_pro`
678
+ - `test_doubao_seed_1_8`
679
+ - `test_ernie_x_1_1`
680
+ - `test_grok_4_1_fast`
681
+ - `test_qwen_plus`
682
+ - `test_claude_4_5_sonnet_zenmux`
683
+ - `test_claude_4_6_sonnet_zenmux`
684
+ - `test_minimax_m2_5`