Skip to content

Commit

Permalink
Add DeepSeek support
Browse files Browse the repository at this point in the history
Fixes #242
  • Loading branch information
hadley committed Jan 13, 2025
1 parent 587d627 commit fd19172
Show file tree
Hide file tree
Showing 20 changed files with 248 additions and 0 deletions.
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Collate:
'provider-claude.R'
'provider-cortex.R'
'provider-databricks.R'
'provider-deepseek.R'
'provider-gemini.R'
'provider-github.R'
'provider-groq.R'
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export(chat_bedrock)
export(chat_claude)
export(chat_cortex)
export(chat_databricks)
export(chat_deepseek)
export(chat_gemini)
export(chat_github)
export(chat_groq)
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ellmer (development version)

* `chat_deepseek()` provides support for DeepSeek models (#242)

* `print(Chat)` no longer wraps long lines, making it easier to read code and bulleted lists (#246).

* `chat_openai()` should be less likely to timeout when not streaming chat results (#213).
Expand Down
88 changes: 88 additions & 0 deletions R/provider-deepseek.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#' @include provider-openai.R
NULL

#' Chat with a model hosted on DeepSeek
#'
#' @description
#' Sign up at <https://platform.deepseek.com>.
#'
#' This function is a lightweight wrapper around [chat_openai()] with
#' default settings tailored for DeepSeek.
#'
#' ## Known limitations
#'
#' * Structured data extraction is not supported..
#' * Function calling is currently [unstable](https://api-docs.deepseek.com/guides/function_calling).
#' * Images are not supported.
#'
#' @export
#' @family chatbots
#' @inheritParams chat_openai
#' @inherit chat_openai return
#' @examples
#' \dontrun{
#' chat <- chat_deepseek()
#' chat$chat("Tell me three jokes about statisticians")
#' }
chat_deepseek <- function(system_prompt = NULL,
turns = NULL,
api_key = deepseek_key(),
model = NULL,
seed = NULL,
api_args = list(),
echo = NULL) {

turns <- normalize_turns(turns, system_prompt)
model <- set_default(model, "deepseek-chat")
echo <- check_echo(echo)

if (is_testing() && is.null(seed)) {
seed <- seed %||% 1014
}

provider <- ProviderDeepSeek(
base_url = "https://api.deepseek.com",
model = model,
seed = seed,
extra_args = api_args,
api_key = api_key
)
Chat$new(provider = provider, turns = turns, echo = echo)
}

ProviderDeepSeek <- new_class("ProviderDeepSeek", parent = ProviderOpenAI)

method(as_json, list(ProviderDeepSeek, ContentText)) <- function(provider, x) {
x@text
}

method(as_json, list(ProviderDeepSeek, Turn)) <- function(provider, x) {
if (x@role == "user") {
# Text and tool results go in separate messages
texts <- keep(x@contents, S7_inherits, ContentText)
texts_out <- lapply(texts, function(text) {
list(role = "user", content = as_json(provider, text))
})

tools <- keep(x@contents, S7_inherits, ContentToolResult)
tools_out <- lapply(tools, function(tool) {
list(role = "tool", content = tool_string(tool), tool_call_id = tool@id)
})

c(texts_out, tools_out)
} else if (x@role == "assistant") {
# Tool requests come out of content and go into own argument
text <- detect(x@contents, S7_inherits, ContentText)
tools <- keep(x@contents, S7_inherits, ContentToolRequest)

list(compact(list(
role = "assistant",
content = as_json(provider, text),
tool_calls = as_json(provider, tools)
)))
} else {
as_json(super(provider, ProviderOpenAI), x)
}
}

deepseek_key <- function() key_get("DEEPSEEK_API_KEY")
2 changes: 2 additions & 0 deletions R/provider-openai.R
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ method(chat_request, ProviderOpenAI) <- function(provider,
req <- req_error(req, body = function(resp) {
if (resp_content_type(resp) == "application/json") {
resp_body_json(resp)$error$message
} else if (resp_content_type(resp) == "text/plain") {
resp_body_string(resp)
}
})

Expand Down
1 change: 1 addition & 0 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ ellmer supports a wide variety of model providers:
* AWS Bedrock: `chat_bedrock()`.
* Azure OpenAI: `chat_azure()`.
* Databricks: `chat_databricks()`.
* DeepSeek: `chat_deepseek()`.
* GitHub model marketplace: `chat_github()`.
* Google Gemini: `chat_gemini()`.
* Groq: `chat_groq()`.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ ellmer supports a wide variety of model providers:
- AWS Bedrock: `chat_bedrock()`.
- Azure OpenAI: `chat_azure()`.
- Databricks: `chat_databricks()`.
- DeepSeek: `chat_deepseek()`.
- GitHub model marketplace: `chat_github()`.
- Google Gemini: `chat_gemini()`.
- Groq: `chat_groq()`.
Expand Down
1 change: 1 addition & 0 deletions man/chat_bedrock.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/chat_claude.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/chat_cortex.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/chat_databricks.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 83 additions & 0 deletions man/chat_deepseek.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/chat_gemini.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/chat_github.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/chat_groq.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/chat_ollama.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/chat_openai.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/chat_perplexity.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions tests/testthat/_snaps/provider-deepseek.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# defaults are reported

Code
. <- chat_deepseek()
Message
Using model = "deepseek-chat".

52 changes: 52 additions & 0 deletions tests/testthat/test-provider-deepseek.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Getting started --------------------------------------------------------

test_that("can make simple request", {
chat <- chat_deepseek("Be as terse as possible; no punctuation")
resp <- chat$chat("What is 1 + 1?", echo = FALSE)
expect_match(resp, "2")
expect_equal(chat$last_turn()@tokens, c(20, 1))
})

test_that("can make simple streaming request", {
chat <- chat_deepseek("Be as terse as possible; no punctuation")
resp <- coro::collect(chat$stream("What is 1 + 1?"))
expect_match(paste0(unlist(resp), collapse = ""), "2")
})

# Common provider interface -----------------------------------------------

test_that("defaults are reported", {
expect_snapshot(. <- chat_deepseek())
})

test_that("respects turns interface", {
chat_fun <- chat_deepseek

test_turns_system(chat_fun)
test_turns_existing(chat_fun)
})

# Only partially works
# test_that("all tool variations work", {
# chat_fun <- chat_deepseek

# test_tools_simple(chat_fun)
# test_tools_async(chat_fun)
# test_tools_parallel(chat_fun)
# test_tools_sequential(chat_fun, total_calls = 6)
# })

# # Doesn't support data extraction
# test_that("can extract data", {
# chat_fun <- chat_deepseek

# test_data_extraction(chat_fun)
# })

# # Doesn't support images
# test_that("can use images", {
# chat_fun <- chat_deepseek_test

# test_images_inline(chat_fun)
# test_images_remote(chat_fun)
# })

0 comments on commit fd19172

Please sign in to comment.