Hi, I have trying to use nim for Deepseek API, but got suck with handling SSE response. I have a working example for the non-stream version, but I want to also support the stream version. can anyone help?
dpsk.nim ( this one only works with "stream": false )
import httpclient, json, os, strutils
const
DeepSeekBaseUrl = "https://api.deepseek.com/v1"
DefaultModel = "deepseek-chat"
type
DeepSeekClient* = ref object
apiKey*: string
model*: string
client*: HttpClient
proc newDeepSeekClient*(apiKey: string, model = DefaultModel): DeepSeekClient =
new result
result.apiKey = apiKey
result.model = model
result.client = newHttpClient(headers = newHttpHeaders({
"Content-Type": "application/json",
"Authorization": "Bearer " & apiKey
}))
proc chat*(chat: DeepSeekClient, messages: JsonNode): JsonNode =
let payload = %*{
"model": chat.model,
"messages": messages,
"stream": false
}
let response = chat.client.post(
DeepSeekBaseUrl & "/chat/completions",
body = $payload
)
return response.body.parseJson()
proc getMessageText*(response: JsonNode): string =
result = response["choices"][0]["message"]["content"].getStr()
when isMainModule:
let apiKey = getEnv("DEEPSEEK_API_KEY")
if apiKey.len == 0:
echo "Please set DEEPSEEK_API_KEY environment variable"
quit(1)
var dpsk = newDeepSeekClient(apiKey)
var messages = %*[{"role": "system", "content": "You are a helpful assistant."}]
while true:
stdout.write("\nYou: ")
let userInput = stdin.readLine()
if toLowerAscii(userInput) == "exit" or toLowerAscii(userInput) == "quit":
break
# Add user message to history
messages.add(%*{"role": "user", "content": userInput})
# Display streaming response
stdout.write("\nDeepSeek: ")
let response = dpsk.chat(messages)
# write response token-wise
for t in response.getMessageText.tokenize():
stdout.write(t[0])
stdout.flushFile()
sleep(100)
echo ""
it will probably not help you, I made a SSE client once based on Harpoon, @juancarlospaco, but it shows the protocol (half way down the iterator). It is the only Nim http client where I got it working.
https://gist.github.com/ingoogni/459c79707065a8492651c0130954e5db
I have update the original code. added timer for chat() and chatStream(), it seems that bodyStream*: Stream did not get the response immediately...
import httpclient, json, os, strutils
import times
import streams
const
DeepSeekBaseUrl = "https://api.deepseek.com/v1"
DefaultModel = "deepseek-chat"
type
# DeepSeekClient* = ref object
DeepSeekClient* = object
apiKey*: string
model*: string
client*: HttpClient
proc newDeepSeekClient*(apiKey: string, model = DefaultModel): DeepSeekClient =
# new result
result.apiKey = apiKey
result.model = model
result.client = newHttpClient(headers = newHttpHeaders({
"Content-Type": "application/json",
"Authorization": "Bearer " & apiKey
}))
proc chat*(chat: DeepSeekClient, messages: JsonNode): JsonNode =
let payload = %*{
"model": chat.model,
"messages": messages,
"stream": false
}
let response = chat.client.post(
DeepSeekBaseUrl & "/chat/completions",
body = $payload
)
return response.body.parseJson()
proc chatStream*(chat: DeepSeekClient, messages: JsonNode): Stream =
let payload = %*{
"model": chat.model,
"messages": messages,
"stream": true
}
let response = chat.client.post(
DeepSeekBaseUrl & "/chat/completions",
body = $payload
)
return response.bodyStream
proc echoStream*(s: Stream) =
while not s.atEnd:
let line = s.readLine()
if line.startsWith("data: "):
let payload = line[6..^1].strip()
echo getTime().toUnix, " | ", payload
elif line.len > 0:
echo "Raw stream line: ", line
# Maintain responsiveness
# sleep(50)
proc getMsgText*(response: JsonNode): string =
result = response["choices"][0]["message"]["content"].getStr()
proc getMsgID*(response: JsonNode): string =
result = response["id"].getStr()
template timeIt*(body: untyped): untyped =
let start = getTime()
body
let finish = getTime()
let duration = finish - start
echo "Time taken: ", duration.inMilliseconds, "ms"
# nim c -r -d:ssl dpsk.nim
when isMainModule:
let apiKey = getEnv("DEEPSEEK_API_KEY")
if apiKey.len == 0:
echo "Please set DEEPSEEK_API_KEY environment variable"
quit(1)
var dpsk = newDeepSeekClient(apiKey)
var messages = %*[{"role": "system", "content": "You are a helpful assistant."}]
while true:
stdout.write("\nYou: ")
let userInput = stdin.readLine()
if toLowerAscii(userInput) == "exit" or toLowerAscii(userInput) == "quit":
break
# Add user message to history
messages.add(%*{"role": "user", "content": userInput})
# Display streaming response
# stdout.write("\nDeepSeek: ")
timeIt:
let response = dpsk.chat(messages)
echo response.getMsgID
timeIt:
dpsk.chatStream(messages).echoStream
I have also tried curl method, it does work. reply is write to terminal one by one, code below
import json, os, strutils
import times
import streams
import osproc
const
DeepSeekBaseUrl = "https://api.deepseek.com/v1"
DefaultModel = "deepseek-chat"
proc chatStreamCurl*(messages: JsonNode, apiKey: string) =
let payload = %*{
"model": DefaultModel,
"messages": messages,
"stream": true
}
let curlCmd = @[
"curl", "-sS", "-N", # -N disables buffering
"-X", "POST",
"-H", "Authorization: Bearer " & apiKey,
"-H", "Content-Type: application/json",
"-H", "Accept: text/event-stream",
"-d", $payload,
DeepSeekBaseUrl & "/chat/completions"
]
var p = startProcess(command=curlCmd[0], args=curlCmd[1..^1], options={poUsePath})
let output = p.outputStream
var buffer: string
while not output.atEnd:
let line = output.readLine()
if line.startsWith("data: "):
let payload = line[6..^1].strip()
if payload == "[DONE]": break
try:
let jsonNode = parseJson(payload)
let content = jsonNode["choices"][0]["delta"]["content"].getStr()
buffer.add(content)
stdout.write(content)
stdout.flushFile()
except:
echo "Error processing chunk: ", getCurrentExceptionMsg()
echo "\n\nFinal response: ", buffer
p.close()
template timeIt*(body: untyped): untyped =
let start = getTime()
body
let finish = getTime()
let duration = finish - start
echo "Time taken: ", duration.inMilliseconds, "ms"
when isMainModule:
let apiKey = getEnv("DEEPSEEK_API_KEY")
if apiKey.len == 0:
echo "Please set DEEPSEEK_API_KEY environment variable"
quit(1)
var messages = %*[{"role": "system", "content": "You are a helpful assistant."}]
while true:
stdout.write("\nYou: ")
let userInput = stdin.readLine()
if toLowerAscii(userInput) == "exit" or toLowerAscii(userInput) == "quit":
break
# Add user message to history
messages.add(%*{"role": "user", "content": userInput})
timeIt:
chatStreamCurl(messages, apiKey)