llm-deepseek-ya 1.0.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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 delijati
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,157 @@
1
+ Metadata-Version: 2.4
2
+ Name: llm-deepseek-ya
3
+ Version: 1.0.0
4
+ Summary: Yet another LLM access to DeepSeek's API
5
+ Author: Josip Delic
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/delijati/llm-deepseek-ya
8
+ Project-URL: Issues, https://github.com/delijati/llm-deepseek-ya/issues
9
+ Project-URL: CI, https://github.com/delijati/llm-deepseek-ya/actions
10
+ Requires-Python: >=3.11
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: llm
14
+ Requires-Dist: httpx
15
+ Provides-Extra: test
16
+ Requires-Dist: pytest; extra == "test"
17
+ Requires-Dist: pytest-httpx; extra == "test"
18
+ Requires-Dist: pytest-recording; extra == "test"
19
+ Provides-Extra: dev
20
+ Requires-Dist: ruff; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # llm-deepseek-ya
24
+
25
+ [![PyPI](https://img.shields.io/pypi/v/llm-deepseek-ya.svg)](https://pypi.org/project/llm-deepseek-ya/0.1.0/)
26
+ [![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/delijati/llm-deepseek-ya?include_prereleases)](https://github.com/delijati/llm-deepseek-ya/releases)
27
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/delijati/llm-deepseek-ya/blob/main/LICENSE)
28
+
29
+ LLM access to DeepSeek's API
30
+
31
+ ## Installation
32
+
33
+ Install this plugin in the same environment as [LLM](https://llm.datasette.io/).
34
+
35
+ ```bash
36
+ llm install llm-deepseek-ya
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ First, set an [API key](https://platform.deepseek.com/api_keys) for DeepSeek:
42
+
43
+ ```bash
44
+ llm keys set deepseek
45
+ # Paste key here
46
+ ```
47
+
48
+ Run `llm models` to list the models, and `llm models --options` to include a list of their options.
49
+
50
+ ### Running Prompts
51
+
52
+ Run prompts like this:
53
+
54
+ ```bash
55
+ llm -m deepseek-chat "Describe a futuristic city on Mars"
56
+ llm -m deepseek-chat-completion "The AI began to dream, and in its dreams," -o echo true
57
+ llm -m deepseek-reasoner "Write a Python function to sort a list of numbers"
58
+ ```
59
+
60
+ Note: The DeepSeek Reasoner model only supports the chat endpoint, not the completion endpoint.
61
+
62
+ ### DeepSeek Reasoner Model
63
+
64
+ The DeepSeek Reasoner model uses a Chain of Thought (CoT) approach to solve complex problems, showing its reasoning process before providing the final answer.
65
+
66
+ The plugin shows the model's chain of thought reasoning by default in non-streaming mode. The reasoning feature is currently only supported in non-streaming mode.
67
+
68
+ ```bash
69
+ # Normal usage - will show reasoning by default
70
+ llm -m deepseek-reasoner "What is 537 * 943?"
71
+
72
+ # Hide reasoning when you only want the final answer
73
+ llm -m deepseek-reasoner "What is 537 * 943?" -o show_reasoning false
74
+ ```
75
+
76
+ ### Features
77
+
78
+ #### Prefill
79
+
80
+ The `prefill` option allows you to provide initial text for the model's response. This is useful for guiding the model's output.
81
+
82
+ Example:
83
+
84
+ ```bash
85
+ llm -m deepseek-chat "What are some wild and crazy activities for a holiday party?" -o prefill "Here are some off-the-wall ideas to make your holiday party unforgettable [warning: these may not be suitable for work holiday parties]:"
86
+ ```
87
+
88
+ You can also load prefill text from a file:
89
+
90
+ ```bash
91
+ # Create a file with your prefill text
92
+ echo "Here are some unique holiday party ideas:" > prefill.txt
93
+
94
+ # Use the file path as the prefill value
95
+ llm -m deepseek-chat "What are some fun activities for a holiday party?" -o prefill prefill.txt
96
+ ```
97
+
98
+ This is especially useful for longer prefill text that would be unwieldy on the command line.
99
+
100
+ #### JSON Response Format
101
+
102
+ The `response_format` option allows you to specify that the model should output its response in JSON format. To ensure the model outputs valid JSON, include the word "json" in the system or user prompt. Optionally, you can provide an example of the desired JSON format to guide the model.
103
+
104
+ Example:
105
+
106
+ ```bash
107
+ llm -m deepseek-chat "What are some fun activities for a holiday party?" -o response_format json_object --system "json"
108
+ ```
109
+
110
+ To guide the model further, you can provide an example JSON structure:
111
+
112
+ ```bash
113
+ llm -m deepseek-chat "What are some way to tell if a holiday party is fun?" -o response_format json_object --system 'EXAMPLE JSON OUTPUT: {"event": "holiday_party_fun", "success_metric": ["..."]}'
114
+ ```
115
+
116
+ #### JSON Schema Support
117
+
118
+ DeepSeek Chat models support JSON schema output (via LLM's `--schema` option). When you provide a schema, the plugin automatically enables JSON mode and includes the schema in the system message to guide the model's output.
119
+
120
+ **Important Note:** DeepSeek's API does not validate the output against the schema - it only uses JSON mode. The schema is provided to the model as guidance, so the model will attempt to follow it, but strict validation is not enforced.
121
+
122
+ Example:
123
+
124
+ ```bash
125
+ llm -m deepseek-chat "Generate a user profile" --schema '{"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "number"}, "email": {"type": "string"}}, "required": ["name", "age"]}'
126
+ ```
127
+
128
+ You can also use LLM's schema file support:
129
+
130
+ ```bash
131
+ # Create a schema file
132
+ cat > user_schema.json << 'EOF'
133
+ {
134
+ "type": "object",
135
+ "properties": {
136
+ "name": {"type": "string"},
137
+ "age": {"type": "number"},
138
+ "email": {"type": "string"},
139
+ "interests": {"type": "array", "items": {"type": "string"}}
140
+ },
141
+ "required": ["name", "age"]
142
+ }
143
+ EOF
144
+
145
+ # Use the schema file
146
+ llm -m deepseek-chat "Generate a user profile for a software developer" --schema user_schema.json
147
+ ```
148
+
149
+ ## Development
150
+
151
+ To set up this plugin locally, first checkout the code. Then create a new virtual environment:
152
+
153
+ ```bash
154
+ cd llm-deepseek-ya
155
+ python3 -m venv venv
156
+ source venv/bin/activate
157
+ ```
@@ -0,0 +1,135 @@
1
+ # llm-deepseek-ya
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/llm-deepseek-ya.svg)](https://pypi.org/project/llm-deepseek-ya/0.1.0/)
4
+ [![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/delijati/llm-deepseek-ya?include_prereleases)](https://github.com/delijati/llm-deepseek-ya/releases)
5
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/delijati/llm-deepseek-ya/blob/main/LICENSE)
6
+
7
+ LLM access to DeepSeek's API
8
+
9
+ ## Installation
10
+
11
+ Install this plugin in the same environment as [LLM](https://llm.datasette.io/).
12
+
13
+ ```bash
14
+ llm install llm-deepseek-ya
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ First, set an [API key](https://platform.deepseek.com/api_keys) for DeepSeek:
20
+
21
+ ```bash
22
+ llm keys set deepseek
23
+ # Paste key here
24
+ ```
25
+
26
+ Run `llm models` to list the models, and `llm models --options` to include a list of their options.
27
+
28
+ ### Running Prompts
29
+
30
+ Run prompts like this:
31
+
32
+ ```bash
33
+ llm -m deepseek-chat "Describe a futuristic city on Mars"
34
+ llm -m deepseek-chat-completion "The AI began to dream, and in its dreams," -o echo true
35
+ llm -m deepseek-reasoner "Write a Python function to sort a list of numbers"
36
+ ```
37
+
38
+ Note: The DeepSeek Reasoner model only supports the chat endpoint, not the completion endpoint.
39
+
40
+ ### DeepSeek Reasoner Model
41
+
42
+ The DeepSeek Reasoner model uses a Chain of Thought (CoT) approach to solve complex problems, showing its reasoning process before providing the final answer.
43
+
44
+ The plugin shows the model's chain of thought reasoning by default in non-streaming mode. The reasoning feature is currently only supported in non-streaming mode.
45
+
46
+ ```bash
47
+ # Normal usage - will show reasoning by default
48
+ llm -m deepseek-reasoner "What is 537 * 943?"
49
+
50
+ # Hide reasoning when you only want the final answer
51
+ llm -m deepseek-reasoner "What is 537 * 943?" -o show_reasoning false
52
+ ```
53
+
54
+ ### Features
55
+
56
+ #### Prefill
57
+
58
+ The `prefill` option allows you to provide initial text for the model's response. This is useful for guiding the model's output.
59
+
60
+ Example:
61
+
62
+ ```bash
63
+ llm -m deepseek-chat "What are some wild and crazy activities for a holiday party?" -o prefill "Here are some off-the-wall ideas to make your holiday party unforgettable [warning: these may not be suitable for work holiday parties]:"
64
+ ```
65
+
66
+ You can also load prefill text from a file:
67
+
68
+ ```bash
69
+ # Create a file with your prefill text
70
+ echo "Here are some unique holiday party ideas:" > prefill.txt
71
+
72
+ # Use the file path as the prefill value
73
+ llm -m deepseek-chat "What are some fun activities for a holiday party?" -o prefill prefill.txt
74
+ ```
75
+
76
+ This is especially useful for longer prefill text that would be unwieldy on the command line.
77
+
78
+ #### JSON Response Format
79
+
80
+ The `response_format` option allows you to specify that the model should output its response in JSON format. To ensure the model outputs valid JSON, include the word "json" in the system or user prompt. Optionally, you can provide an example of the desired JSON format to guide the model.
81
+
82
+ Example:
83
+
84
+ ```bash
85
+ llm -m deepseek-chat "What are some fun activities for a holiday party?" -o response_format json_object --system "json"
86
+ ```
87
+
88
+ To guide the model further, you can provide an example JSON structure:
89
+
90
+ ```bash
91
+ llm -m deepseek-chat "What are some way to tell if a holiday party is fun?" -o response_format json_object --system 'EXAMPLE JSON OUTPUT: {"event": "holiday_party_fun", "success_metric": ["..."]}'
92
+ ```
93
+
94
+ #### JSON Schema Support
95
+
96
+ DeepSeek Chat models support JSON schema output (via LLM's `--schema` option). When you provide a schema, the plugin automatically enables JSON mode and includes the schema in the system message to guide the model's output.
97
+
98
+ **Important Note:** DeepSeek's API does not validate the output against the schema - it only uses JSON mode. The schema is provided to the model as guidance, so the model will attempt to follow it, but strict validation is not enforced.
99
+
100
+ Example:
101
+
102
+ ```bash
103
+ llm -m deepseek-chat "Generate a user profile" --schema '{"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "number"}, "email": {"type": "string"}}, "required": ["name", "age"]}'
104
+ ```
105
+
106
+ You can also use LLM's schema file support:
107
+
108
+ ```bash
109
+ # Create a schema file
110
+ cat > user_schema.json << 'EOF'
111
+ {
112
+ "type": "object",
113
+ "properties": {
114
+ "name": {"type": "string"},
115
+ "age": {"type": "number"},
116
+ "email": {"type": "string"},
117
+ "interests": {"type": "array", "items": {"type": "string"}}
118
+ },
119
+ "required": ["name", "age"]
120
+ }
121
+ EOF
122
+
123
+ # Use the schema file
124
+ llm -m deepseek-chat "Generate a user profile for a software developer" --schema user_schema.json
125
+ ```
126
+
127
+ ## Development
128
+
129
+ To set up this plugin locally, first checkout the code. Then create a new virtual environment:
130
+
131
+ ```bash
132
+ cd llm-deepseek-ya
133
+ python3 -m venv venv
134
+ source venv/bin/activate
135
+ ```
@@ -0,0 +1,248 @@
1
+ import llm
2
+ from llm.default_plugins.openai_models import Chat
3
+ from pathlib import Path
4
+ import json
5
+ import time
6
+ import httpx
7
+ import os
8
+ from typing import Optional
9
+ from pydantic import Field
10
+
11
+ # Constants for cache timeout and API base URL
12
+ CACHE_TIMEOUT = 3600
13
+ DEEPSEEK_API_BASE = "https://api.deepseek.com/beta" # For inference
14
+ DEEPSEEK_MODELS_URL = "https://api.deepseek.com/models" # For listing models
15
+
16
+
17
+ def get_deepseek_models():
18
+ """Fetch and cache DeepSeek models."""
19
+ key = llm.get_key("", "deepseek", "LLM_DEEPSEEK_KEY")
20
+ headers = {"Authorization": f"Bearer {key}"} if key else None
21
+ ret = fetch_cached_json(
22
+ url=DEEPSEEK_MODELS_URL,
23
+ path=llm.user_dir() / "deepseek_models.json",
24
+ cache_timeout=CACHE_TIMEOUT,
25
+ headers=headers,
26
+ )["data"]
27
+ return ret
28
+
29
+
30
+ def get_model_ids_with_aliases(models):
31
+ """Extract model IDs and create empty aliases list."""
32
+ return [(model["id"], []) for model in models]
33
+
34
+
35
+ class DeepSeekChat(Chat):
36
+ needs_key = "deepseek"
37
+ key_env_var = "LLM_DEEPSEEK_KEY"
38
+
39
+ def __init__(self, model_id, **kwargs):
40
+ kwargs.setdefault("supports_schema", True)
41
+ super().__init__(model_id, **kwargs)
42
+ self.api_base = DEEPSEEK_API_BASE
43
+
44
+ def __str__(self):
45
+ return f"DeepSeek Chat: {self.model_id}"
46
+
47
+ class Options(Chat.Options):
48
+ prefill: Optional[str] = Field(
49
+ description="Initial text for the model's response (beta feature). Uses DeepSeek's Chat Prefix Completion.",
50
+ default=None,
51
+ )
52
+ response_format: Optional[str] = Field(
53
+ description="Format of the response (e.g., 'json_object').", default=None
54
+ )
55
+ show_reasoning: Optional[bool] = Field(
56
+ description="Show the chain of thought reasoning for the DeepSeek Reasoner model.",
57
+ default=True,
58
+ )
59
+
60
+ def execute(self, prompt, stream, response, conversation, key=None):
61
+ messages = self._build_messages(conversation, prompt)
62
+ response._prompt_json = {"messages": messages}
63
+ kwargs = self.build_kwargs(prompt, stream)
64
+
65
+ max_tokens = kwargs.pop("max_tokens", 8192)
66
+
67
+ # Enable JSON mode if schema is provided or response_format is set
68
+ if prompt.schema or prompt.options.response_format:
69
+ kwargs["response_format"] = {"type": "json_object"}
70
+
71
+ # If schema is provided, add it to the system message as guidance
72
+ # Note: DeepSeek doesn't validate against the schema, but the model can follow it
73
+ if prompt.schema:
74
+ schema_instruction = f"\n\nYou must respond with valid JSON matching this schema:\n{json.dumps(prompt.schema, indent=2)}"
75
+ # Add schema to system message or create one if it doesn't exist
76
+ if messages and messages[0].get("role") == "system":
77
+ messages[0]["content"] += schema_instruction
78
+ else:
79
+ messages.insert(
80
+ 0,
81
+ {
82
+ "role": "system",
83
+ "content": f"You are a helpful assistant.{schema_instruction}",
84
+ },
85
+ )
86
+
87
+ # Remove options that aren't supported by the OpenAI client
88
+ kwargs.pop("prefill", None)
89
+ kwargs.pop("show_reasoning", None)
90
+ show_reasoning = prompt.options.show_reasoning
91
+
92
+ client = self.get_client(key)
93
+
94
+ try:
95
+ completion = client.chat.completions.create(
96
+ model=self.model_name,
97
+ messages=messages,
98
+ stream=stream,
99
+ max_tokens=max_tokens,
100
+ **kwargs,
101
+ )
102
+
103
+ if stream:
104
+ for chunk in completion:
105
+ # Stream both reasoning content and regular content directly
106
+ content = chunk.choices[0].delta.content
107
+ reasoning_content = getattr(
108
+ chunk.choices[0].delta, "reasoning_content", None
109
+ )
110
+
111
+ if reasoning_content is not None and show_reasoning:
112
+ yield reasoning_content
113
+
114
+ if content is not None:
115
+ yield content
116
+ else:
117
+ # For non-streaming response
118
+ content = completion.choices[0].message.content
119
+ # If we have reasoning content and want to show it
120
+ if show_reasoning and hasattr(
121
+ completion.choices[0].message, "reasoning_content"
122
+ ):
123
+ reasoning = completion.choices[0].message.reasoning_content
124
+ if reasoning:
125
+ yield reasoning
126
+ yield "\n\n"
127
+ # Then output the regular content
128
+ yield content
129
+
130
+ response.response_json = {"content": "".join(response._chunks)}
131
+
132
+ # Store reasoning_content in response if available
133
+ if not stream and hasattr(
134
+ completion.choices[0].message, "reasoning_content"
135
+ ):
136
+ response.response_json["reasoning_content"] = completion.choices[
137
+ 0
138
+ ].message.reasoning_content
139
+
140
+ except httpx.HTTPError as e:
141
+ raise llm.ModelError(f"DeepSeek API error: {str(e)}")
142
+
143
+ def _build_messages(self, conversation, prompt):
144
+ """Build the messages list for the API call."""
145
+ messages = []
146
+ if conversation:
147
+ for prev_response in conversation.responses:
148
+ messages.append(
149
+ {"role": "user", "content": prev_response.prompt.prompt}
150
+ )
151
+ messages.append({"role": "assistant", "content": prev_response.text()})
152
+
153
+ # Add system message if provided
154
+ if prompt.system:
155
+ messages.append({"role": "system", "content": prompt.system})
156
+
157
+ messages.append({"role": "user", "content": prompt.prompt})
158
+
159
+ if prompt.options.prefill:
160
+ prefill_content = prompt.options.prefill
161
+ # Check if prefill value is a file path
162
+ if os.path.exists(prefill_content) and os.path.isfile(prefill_content):
163
+ try:
164
+ with open(prefill_content, "r") as file:
165
+ prefill_content = file.read()
166
+ except Exception as e:
167
+ print(
168
+ f"Warning: Could not read prefill file '{prompt.options.prefill}': {e}"
169
+ )
170
+
171
+ messages.append(
172
+ {"role": "assistant", "content": prefill_content, "prefix": True}
173
+ )
174
+
175
+ return messages
176
+
177
+
178
+ class DownloadError(Exception):
179
+ pass
180
+
181
+
182
+ def fetch_cached_json(url, path, cache_timeout, headers=None):
183
+ """Fetch JSON data from a URL and cache it."""
184
+ path = Path(path)
185
+ path.parent.mkdir(parents=True, exist_ok=True)
186
+
187
+ if path.is_file() and time.time() - path.stat().st_mtime < cache_timeout:
188
+ with open(path, "r") as file:
189
+ return json.load(file)
190
+
191
+ try:
192
+ response = httpx.get(url, headers=headers, follow_redirects=True)
193
+ response.raise_for_status()
194
+ with open(path, "w") as file:
195
+ json.dump(response.json(), file)
196
+ return response.json()
197
+ except httpx.HTTPError:
198
+ if path.is_file():
199
+ with open(path, "r") as file:
200
+ return json.load(file)
201
+ else:
202
+ raise DownloadError(
203
+ f"Failed to download data and no cache is available at {path}"
204
+ )
205
+
206
+
207
+ @llm.hookimpl
208
+ def register_models(register):
209
+ key = llm.get_key("", "deepseek", "LLM_DEEPSEEK_KEY")
210
+ if not key:
211
+ return
212
+ try:
213
+ models = get_deepseek_models()
214
+ models_with_aliases = get_model_ids_with_aliases(models)
215
+
216
+ # Register all Chat models
217
+ for model_id, aliases in models_with_aliases:
218
+ register(
219
+ DeepSeekChat(
220
+ model_id=f"deepseek/{model_id}",
221
+ model_name=model_id,
222
+ ),
223
+ aliases=[model_id],
224
+ )
225
+ except DownloadError as e:
226
+ print(f"Error fetching DeepSeek models: {e}")
227
+
228
+
229
+ @llm.hookimpl
230
+ def register_commands(cli):
231
+ @cli.command()
232
+ def deepseek_models():
233
+ """List available DeepSeek models."""
234
+ key = llm.get_key("", "deepseek", "LLM_DEEPSEEK_KEY")
235
+ if not key:
236
+ print("DeepSeek API key not set. Use 'llm keys set deepseek' to set it.")
237
+ return
238
+ try:
239
+ models = get_deepseek_models()
240
+ models_with_aliases = get_model_ids_with_aliases(models)
241
+
242
+ # Display all Chat models
243
+ for model_id, aliases in models_with_aliases:
244
+ print(f"DeepSeek Chat: deepseek/{model_id}")
245
+ print(f" Aliases: {model_id}")
246
+ print()
247
+ except DownloadError as e:
248
+ print(f"Error fetching DeepSeek models: {e}")
@@ -0,0 +1,157 @@
1
+ Metadata-Version: 2.4
2
+ Name: llm-deepseek-ya
3
+ Version: 1.0.0
4
+ Summary: Yet another LLM access to DeepSeek's API
5
+ Author: Josip Delic
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/delijati/llm-deepseek-ya
8
+ Project-URL: Issues, https://github.com/delijati/llm-deepseek-ya/issues
9
+ Project-URL: CI, https://github.com/delijati/llm-deepseek-ya/actions
10
+ Requires-Python: >=3.11
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: llm
14
+ Requires-Dist: httpx
15
+ Provides-Extra: test
16
+ Requires-Dist: pytest; extra == "test"
17
+ Requires-Dist: pytest-httpx; extra == "test"
18
+ Requires-Dist: pytest-recording; extra == "test"
19
+ Provides-Extra: dev
20
+ Requires-Dist: ruff; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # llm-deepseek-ya
24
+
25
+ [![PyPI](https://img.shields.io/pypi/v/llm-deepseek-ya.svg)](https://pypi.org/project/llm-deepseek-ya/0.1.0/)
26
+ [![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/delijati/llm-deepseek-ya?include_prereleases)](https://github.com/delijati/llm-deepseek-ya/releases)
27
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/delijati/llm-deepseek-ya/blob/main/LICENSE)
28
+
29
+ LLM access to DeepSeek's API
30
+
31
+ ## Installation
32
+
33
+ Install this plugin in the same environment as [LLM](https://llm.datasette.io/).
34
+
35
+ ```bash
36
+ llm install llm-deepseek-ya
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ First, set an [API key](https://platform.deepseek.com/api_keys) for DeepSeek:
42
+
43
+ ```bash
44
+ llm keys set deepseek
45
+ # Paste key here
46
+ ```
47
+
48
+ Run `llm models` to list the models, and `llm models --options` to include a list of their options.
49
+
50
+ ### Running Prompts
51
+
52
+ Run prompts like this:
53
+
54
+ ```bash
55
+ llm -m deepseek-chat "Describe a futuristic city on Mars"
56
+ llm -m deepseek-chat-completion "The AI began to dream, and in its dreams," -o echo true
57
+ llm -m deepseek-reasoner "Write a Python function to sort a list of numbers"
58
+ ```
59
+
60
+ Note: The DeepSeek Reasoner model only supports the chat endpoint, not the completion endpoint.
61
+
62
+ ### DeepSeek Reasoner Model
63
+
64
+ The DeepSeek Reasoner model uses a Chain of Thought (CoT) approach to solve complex problems, showing its reasoning process before providing the final answer.
65
+
66
+ The plugin shows the model's chain of thought reasoning by default in non-streaming mode. The reasoning feature is currently only supported in non-streaming mode.
67
+
68
+ ```bash
69
+ # Normal usage - will show reasoning by default
70
+ llm -m deepseek-reasoner "What is 537 * 943?"
71
+
72
+ # Hide reasoning when you only want the final answer
73
+ llm -m deepseek-reasoner "What is 537 * 943?" -o show_reasoning false
74
+ ```
75
+
76
+ ### Features
77
+
78
+ #### Prefill
79
+
80
+ The `prefill` option allows you to provide initial text for the model's response. This is useful for guiding the model's output.
81
+
82
+ Example:
83
+
84
+ ```bash
85
+ llm -m deepseek-chat "What are some wild and crazy activities for a holiday party?" -o prefill "Here are some off-the-wall ideas to make your holiday party unforgettable [warning: these may not be suitable for work holiday parties]:"
86
+ ```
87
+
88
+ You can also load prefill text from a file:
89
+
90
+ ```bash
91
+ # Create a file with your prefill text
92
+ echo "Here are some unique holiday party ideas:" > prefill.txt
93
+
94
+ # Use the file path as the prefill value
95
+ llm -m deepseek-chat "What are some fun activities for a holiday party?" -o prefill prefill.txt
96
+ ```
97
+
98
+ This is especially useful for longer prefill text that would be unwieldy on the command line.
99
+
100
+ #### JSON Response Format
101
+
102
+ The `response_format` option allows you to specify that the model should output its response in JSON format. To ensure the model outputs valid JSON, include the word "json" in the system or user prompt. Optionally, you can provide an example of the desired JSON format to guide the model.
103
+
104
+ Example:
105
+
106
+ ```bash
107
+ llm -m deepseek-chat "What are some fun activities for a holiday party?" -o response_format json_object --system "json"
108
+ ```
109
+
110
+ To guide the model further, you can provide an example JSON structure:
111
+
112
+ ```bash
113
+ llm -m deepseek-chat "What are some way to tell if a holiday party is fun?" -o response_format json_object --system 'EXAMPLE JSON OUTPUT: {"event": "holiday_party_fun", "success_metric": ["..."]}'
114
+ ```
115
+
116
+ #### JSON Schema Support
117
+
118
+ DeepSeek Chat models support JSON schema output (via LLM's `--schema` option). When you provide a schema, the plugin automatically enables JSON mode and includes the schema in the system message to guide the model's output.
119
+
120
+ **Important Note:** DeepSeek's API does not validate the output against the schema - it only uses JSON mode. The schema is provided to the model as guidance, so the model will attempt to follow it, but strict validation is not enforced.
121
+
122
+ Example:
123
+
124
+ ```bash
125
+ llm -m deepseek-chat "Generate a user profile" --schema '{"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "number"}, "email": {"type": "string"}}, "required": ["name", "age"]}'
126
+ ```
127
+
128
+ You can also use LLM's schema file support:
129
+
130
+ ```bash
131
+ # Create a schema file
132
+ cat > user_schema.json << 'EOF'
133
+ {
134
+ "type": "object",
135
+ "properties": {
136
+ "name": {"type": "string"},
137
+ "age": {"type": "number"},
138
+ "email": {"type": "string"},
139
+ "interests": {"type": "array", "items": {"type": "string"}}
140
+ },
141
+ "required": ["name", "age"]
142
+ }
143
+ EOF
144
+
145
+ # Use the schema file
146
+ llm -m deepseek-chat "Generate a user profile for a software developer" --schema user_schema.json
147
+ ```
148
+
149
+ ## Development
150
+
151
+ To set up this plugin locally, first checkout the code. Then create a new virtual environment:
152
+
153
+ ```bash
154
+ cd llm-deepseek-ya
155
+ python3 -m venv venv
156
+ source venv/bin/activate
157
+ ```
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ llm_deepseek.py
4
+ pyproject.toml
5
+ llm_deepseek_ya.egg-info/PKG-INFO
6
+ llm_deepseek_ya.egg-info/SOURCES.txt
7
+ llm_deepseek_ya.egg-info/dependency_links.txt
8
+ llm_deepseek_ya.egg-info/entry_points.txt
9
+ llm_deepseek_ya.egg-info/requires.txt
10
+ llm_deepseek_ya.egg-info/top_level.txt
11
+ tests/test_deepseek.py
@@ -0,0 +1,2 @@
1
+ [llm]
2
+ deepseek = llm_deepseek
@@ -0,0 +1,10 @@
1
+ llm
2
+ httpx
3
+
4
+ [dev]
5
+ ruff
6
+
7
+ [test]
8
+ pytest
9
+ pytest-httpx
10
+ pytest-recording
@@ -0,0 +1 @@
1
+ llm_deepseek
@@ -0,0 +1,44 @@
1
+ [project]
2
+ name = "llm-deepseek-ya"
3
+ version = "1.0.0"
4
+ description = "Yet another LLM access to DeepSeek's API"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.11"
8
+ dependencies = ["llm", "httpx"]
9
+ authors = [{ name = "Josip Delic" }]
10
+
11
+ [project.urls]
12
+ Homepage = "https://github.com/delijati/llm-deepseek-ya"
13
+ Issues = "https://github.com/delijati/llm-deepseek-ya/issues"
14
+ CI = "https://github.com/delijati/llm-deepseek-ya/actions"
15
+
16
+ [project.entry-points.llm]
17
+ deepseek = "llm_deepseek"
18
+
19
+ [project.optional-dependencies]
20
+ test = ["pytest", "pytest-httpx", "pytest-recording"]
21
+ dev = ["ruff"]
22
+
23
+ [tool.pytest.ini_options]
24
+ filterwarnings = ["ignore::DeprecationWarning"]
25
+
26
+ [tool.setuptools]
27
+ py-modules = ["llm_deepseek"]
28
+
29
+ [tool.ruff]
30
+ # Same as flake8 max-line-length
31
+ line-length = 88
32
+
33
+ [tool.ruff.lint]
34
+ # E501 - line too long (handled by formatter)
35
+ # E203 - whitespace before ':' (conflicts with black)
36
+ ignore = ["E501", "E203"]
37
+
38
+ # Enable pycodestyle (E, W), pyflakes (F), and mccabe (C) rules
39
+ select = ["E", "W", "F", "C90"]
40
+
41
+ [tool.ruff.lint.mccabe]
42
+ # Equivalent to flake8's max-complexity
43
+ max-complexity = 15
44
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,257 @@
1
+ from click.testing import CliRunner
2
+ import llm
3
+ from llm.cli import cli
4
+ import json
5
+ import os
6
+ import pytest
7
+ import pydantic
8
+ from pydantic import BaseModel
9
+ from typing import List, Optional
10
+
11
+ DEEPSEEK_API_KEY = os.environ.get("PYTEST_DEEPSEEK_API_KEY", None) or "sk-..."
12
+
13
+
14
+ @pytest.mark.vcr
15
+ def test_prompt():
16
+ """Test basic prompt with DeepSeek Chat model"""
17
+ model = llm.get_model("deepseek-chat")
18
+ response = model.prompt(
19
+ "Name for a pet pelican, just the name", key=DEEPSEEK_API_KEY
20
+ )
21
+ text = str(response).strip()
22
+ # DeepSeek should return a response
23
+ assert len(text) > 0
24
+ # DeepSeek should return a response
25
+ assert "content" in response.response_json
26
+
27
+
28
+ @pytest.mark.vcr
29
+ def test_prompt_with_pydantic_schema():
30
+ """Test prompt with Pydantic schema - DeepSeek will use JSON mode"""
31
+
32
+ class Dog(pydantic.BaseModel):
33
+ name: str
34
+ age: int
35
+ bio: str
36
+
37
+ model = llm.get_model("deepseek-chat")
38
+ response = model.prompt(
39
+ "Invent a cool dog", key=DEEPSEEK_API_KEY, schema=Dog, stream=False
40
+ )
41
+ result = json.loads(response.text())
42
+
43
+ # Verify the response has the required fields
44
+ assert "name" in result
45
+ assert "age" in result
46
+ assert "bio" in result
47
+ assert isinstance(result["name"], str)
48
+ assert isinstance(result["age"], int)
49
+ assert isinstance(result["bio"], str)
50
+
51
+
52
+ @pytest.mark.vcr
53
+ def test_prompt_with_multiple_dogs():
54
+ """Test prompt with nested Pydantic schema"""
55
+
56
+ class Dog(pydantic.BaseModel):
57
+ name: str
58
+ age: int
59
+ bio: str
60
+
61
+ class Dogs(BaseModel):
62
+ dogs: List[Dog]
63
+
64
+ model = llm.get_model("deepseek-chat")
65
+ response = model.prompt(
66
+ "Invent 3 cool dogs", key=DEEPSEEK_API_KEY, schema=Dogs, stream=False
67
+ )
68
+ result = json.loads(response.text())
69
+
70
+ # Verify we got 3 dogs
71
+ assert "dogs" in result
72
+ assert len(result["dogs"]) == 3
73
+
74
+ # Verify each dog has the required fields
75
+ for dog in result["dogs"]:
76
+ assert "name" in dog
77
+ assert "age" in dog
78
+ assert "bio" in dog
79
+ assert isinstance(dog["name"], str)
80
+ assert isinstance(dog["age"], int)
81
+ assert isinstance(dog["bio"], str)
82
+
83
+
84
+ @pytest.mark.vcr
85
+ def test_json_response_format():
86
+ """Test the response_format option"""
87
+ model = llm.get_model("deepseek-chat")
88
+ response = model.prompt(
89
+ "Return a JSON object with keys: name, color, and species for a parrot",
90
+ key=DEEPSEEK_API_KEY,
91
+ response_format="json_object",
92
+ stream=False,
93
+ )
94
+ result = json.loads(response.text())
95
+
96
+ # Should be valid JSON
97
+ assert isinstance(result, dict)
98
+
99
+
100
+ @pytest.mark.vcr
101
+ def test_reasoner_model():
102
+ """Test DeepSeek Reasoner model with reasoning output"""
103
+ model = llm.get_model("deepseek-reasoner")
104
+ response = model.prompt(
105
+ "What is 537 * 943?", key=DEEPSEEK_API_KEY, show_reasoning=True, stream=False
106
+ )
107
+ text = response.text()
108
+
109
+ # Should contain a numeric answer
110
+ assert "506" in text or "943" in text or "537" in text
111
+
112
+ # Check response_json contains reasoning_content if available
113
+ if response.response_json and "reasoning_content" in response.response_json:
114
+ assert isinstance(response.response_json["reasoning_content"], str)
115
+
116
+
117
+ @pytest.mark.vcr
118
+ def test_reasoner_model_hide_reasoning():
119
+ """Test DeepSeek Reasoner model with reasoning hidden"""
120
+ model = llm.get_model("deepseek-reasoner")
121
+ response = model.prompt(
122
+ "What is 2 + 2?", key=DEEPSEEK_API_KEY, show_reasoning=False, stream=False
123
+ )
124
+ text = response.text()
125
+
126
+ # Should contain the answer
127
+ assert "4" in text
128
+
129
+
130
+ @pytest.mark.vcr
131
+ def test_prefill_option():
132
+ """Test the prefill option for Chat Prefix Completion"""
133
+ model = llm.get_model("deepseek-chat")
134
+ response = model.prompt(
135
+ "Continue this story",
136
+ key=DEEPSEEK_API_KEY,
137
+ prefill="Once upon a time",
138
+ stream=False,
139
+ )
140
+ text = response.text()
141
+
142
+ # Should have generated some text
143
+ assert len(text) > 0
144
+
145
+
146
+ @pytest.mark.vcr
147
+ def test_nested_model_direct_reference():
148
+ """Test nested Pydantic models with DeepSeek"""
149
+
150
+ class Address(BaseModel):
151
+ street: str
152
+ city: str
153
+
154
+ class Person(BaseModel):
155
+ name: str
156
+ address: Address
157
+
158
+ model = llm.get_model("deepseek-chat")
159
+ response = model.prompt(
160
+ "Create a person named Alice living in San Francisco",
161
+ key=DEEPSEEK_API_KEY,
162
+ schema=Person,
163
+ stream=False,
164
+ )
165
+ result = json.loads(response.text())
166
+ assert "name" in result
167
+ assert "address" in result
168
+ assert "street" in result["address"]
169
+ assert "city" in result["address"]
170
+
171
+
172
+ @pytest.mark.vcr
173
+ def test_nested_model_optional():
174
+ """Test optional nested Pydantic models"""
175
+
176
+ class Company(BaseModel):
177
+ company_name: str
178
+
179
+ class Person(BaseModel):
180
+ name: str
181
+ employer: Optional[Company]
182
+
183
+ model = llm.get_model("deepseek-chat")
184
+ response = model.prompt(
185
+ "Create a person named Bob who works at TechCorp",
186
+ key=DEEPSEEK_API_KEY,
187
+ schema=Person,
188
+ stream=False,
189
+ )
190
+ result = json.loads(response.text())
191
+ assert "name" in result
192
+ assert "employer" in result
193
+ if result["employer"] is not None:
194
+ assert "company_name" in result["employer"]
195
+
196
+
197
+ @pytest.mark.vcr
198
+ def test_nested_model_deep_composition():
199
+ """Test deeply nested Pydantic models"""
200
+
201
+ class Item(BaseModel):
202
+ product_name: str
203
+ quantity: int
204
+
205
+ class Order(BaseModel):
206
+ items: List[Item]
207
+
208
+ class Customer(BaseModel):
209
+ name: str
210
+ orders: List[Order]
211
+
212
+ model = llm.get_model("deepseek-chat")
213
+ response = model.prompt(
214
+ "Create a customer named Carol with 2 orders, each containing 2 items",
215
+ key=DEEPSEEK_API_KEY,
216
+ schema=Customer,
217
+ stream=False,
218
+ )
219
+ result = json.loads(response.text())
220
+ assert "name" in result
221
+ assert "orders" in result
222
+ assert len(result["orders"]) > 0
223
+ for order in result["orders"]:
224
+ assert "items" in order
225
+ assert len(order["items"]) > 0
226
+ for item in order["items"]:
227
+ assert "product_name" in item
228
+ assert "quantity" in item
229
+
230
+
231
+ @pytest.mark.vcr
232
+ def test_cli_deepseek_models(tmpdir, monkeypatch):
233
+ """Test the deepseek-models CLI command"""
234
+ user_dir = tmpdir / "llm.datasette.io"
235
+ user_dir.mkdir()
236
+ monkeypatch.setenv("LLM_USER_PATH", str(user_dir))
237
+
238
+ # With no key set should show error message
239
+ runner = CliRunner()
240
+ result = runner.invoke(cli, ["deepseek-models"])
241
+ assert result.exit_code == 0
242
+ assert "DeepSeek API key not set" in result.output or result.exit_code == 0
243
+
244
+ # Try with key set
245
+ monkeypatch.setenv("LLM_DEEPSEEK_KEY", DEEPSEEK_API_KEY)
246
+ result2 = runner.invoke(cli, ["deepseek-models"])
247
+ assert result2.exit_code == 0
248
+ # Should list some models
249
+ assert "deepseek" in result2.output.lower() or "DeepSeek" in result2.output
250
+
251
+
252
+ @pytest.mark.vcr
253
+ def test_supports_schema_attribute():
254
+ """Test that DeepSeek Chat models have supports_schema set to True"""
255
+ model = llm.get_model("deepseek-chat")
256
+ assert hasattr(model, "supports_schema")
257
+ assert model.supports_schema is True