又双叒叕很久不写博客,今天吐一口老曹。
一、为啥干这个
之前在修改OJ的时候,本着少修改多收益的原则,用Python写了一些DeepSeek的调用,真的很简单,用拉下来OpenAI按照官方文档复制粘贴就可以。接口文档页面(首次调用 API | DeepSeek API Docs)上至今有三种:curl(哦天哪,我在捣鼓ubuntu的时候好像用这个)、python(这个看着脸熟……当时敲的就是,那没事了)、node.js(dot是后加的,不然还真不认识)。
但是,随着需求越搞越多,顺手就祭起冷门编程语言VB.NET了。完成了各种远程多Judge服务器之后,发现".py"越看越不顺眼。于是乎,来吧NuGet一下……emmmmm……两下、三四五六下,He,Tui~~~
然后,再次祭起冷门语言VB.NET。因为之前就是多线程在搞一些队列呀、JudgeAPI呀,所以这个线程突然就顺手了。众所周知,我也不知道到底OpenAI支持多少参数,咱也搜不到,咱也不敢搜。所以,就比着能找到的就行了。并且,为了凑字数,成员定义的非常啰嗦:
Imports System
Imports System.Net.Http
Imports System.Text
Imports System.Threading.Tasks
Imports System.Text.Json
Imports System.Text.Json.Serialization
Imports System.Net
Imports System.Threading
Public Class DeepSeekClient
Private ReadOnly _sharedHttpClient As HttpClient
Private ReadOnly _apiKey As String
Private ReadOnly _apiBaseUrl As String
Private _model As String = "deepseek-chat"
Private _temperature As Single = 0.1
Private _maxTokens As Integer = 2048
Private _top_p As Single = 0.2
Private _stream As Boolean = False
''' <summary>
''' 初始化 DeepSeek 客户端(使用共享 HttpClient)
''' </summary>
''' <param name="sharedHttpClient">共享的 HttpClient 实例</param>
''' <param name="apiKey">API 密钥</param>
''' <param name="apiBaseUrl">API 基础地址 (可选,默认为官方地址)</param>
Public Sub New(sharedHttpClient As HttpClient, apiKey As String, Optional apiBaseUrl As String = "https://api.deepseek.com/")
If sharedHttpClient Is Nothing Then
Throw New ArgumentNullException(NameOf(sharedHttpClient))
End If
If String.IsNullOrWhiteSpace(apiKey) Then
Throw New ArgumentException("APIKey不能为空。", NameOf(apiKey))
End If
_sharedHttpClient = sharedHttpClient
_apiKey = apiKey
_apiBaseUrl = apiBaseUrl.TrimEnd("/"c)
' 如果 HttpClient 还没有 Authorization 头,则添加
If Not _sharedHttpClient.DefaultRequestHeaders.Contains("Authorization") Then
_sharedHttpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}")
End If
End Sub
''' <summary>
''' 设置模型名称
''' </summary>
Public Property Model As String
Get
Return _model
End Get
Set(value As String)
If String.IsNullOrWhiteSpace(value) Then
Throw New ArgumentException("模型名称不能为空。")
End If
_model = value
End Set
End Property
''' <summary>
''' 设置温度参数 (0-2)
''' </summary>
Public Property Temperature As Single
Get
Return _temperature
End Get
Set(value As Single)
If value < 0 OrElse value > 2 Then
Throw New ArgumentOutOfRangeException(NameOf(Temperature), "温度值应在[0,2]区间")
End If
_temperature = value
End Set
End Property
''' <summary>
''' 设置最大令牌数
''' </summary>
Public Property MaxTokens As Integer
Get
Return _maxTokens
End Get
Set(value As Integer)
If value <= 0 Then
Throw New ArgumentOutOfRangeException(NameOf(MaxTokens), "最大Token数不应小于0。")
End If
_maxTokens = value
End Set
End Property
''' <summary>
''' 生成多样性控制
''' </summary>
Public Property Top_p As Single
Get
Return _top_p
End Get
Set(value As Single)
If value < 0 OrElse value > 1 Then
Throw New ArgumentOutOfRangeException(NameOf(Temperature), "Top_p应在[0,1]区间。")
End If
_top_p = value
End Set
End Property
''' <summary>
''' 是否流式响应
''' </summary>
Public Property Stream As Boolean
Get
Return _stream
End Get
Set(value As Boolean)
_stream = value
End Set
End Property
''' <summary>
''' 发送消息到 DeepSeek API 并获取响应
''' </summary>
''' <param name="userMessage">用户消息内容</param>
''' <returns>API 响应内容</returns>
Public Async Function SendMessageAsync(systemMessage As String, userMessage As String, timeout As TimeSpan) As Task(Of String)
If String.IsNullOrWhiteSpace(systemMessage) Then
Throw New ArgumentException("系统消息不能为空。", NameOf(systemMessage))
End If
If String.IsNullOrWhiteSpace(userMessage) Then
Throw New ArgumentException("用户消息不能为空。", NameOf(userMessage))
End If
If timeout = Nothing Then
Throw New ArgumentException("超时时间不能为空。", NameOf(timeout))
End If
' 创建请求体对象
Dim requestBody = New DeepSeekRequest With {
.Model = _model,
.Messages = New List(Of ChatMessage) From {
New ChatMessage With {.Role = "system", .Content = systemMessage},
New ChatMessage With {.Role = "user", .Content = userMessage}
},
.Temperature = _temperature,
.MaxTokens = _maxTokens,
.Top_p = Top_p,
.Stream = False
}
' 使用 System.Text.Json 序列化
Dim jsonContent = JsonSerializer.Serialize(requestBody)
Dim content = New StringContent(jsonContent, Encoding.UTF8, "application/json")
Try
' 创建带有超时设置的请求
Dim request = New HttpRequestMessage(HttpMethod.Post, $"{_apiBaseUrl}/chat/completions") With {
.Content = content
}
' 使用 CancellationTokenSource 实现单次请求超时
Using cts = New CancellationTokenSource(timeout)
Dim response = Await _sharedHttpClient.SendAsync(request, cts.Token)
Dim responseContent = Await response.Content.ReadAsStringAsync()
If Not response.IsSuccessStatusCode Then
HandleApiError(response.StatusCode, responseContent)
End If
' 使用 System.Text.Json 反序列化
Dim responseObject = JsonSerializer.Deserialize(Of DeepSeekResponse)(responseContent)
Return responseObject?.Choices?.FirstOrDefault()?.Message?.Content
End Using
Catch ex As TaskCanceledException When Not ex.CancellationToken.IsCancellationRequested
Throw New TimeoutException("请求超时。", ex)
Catch ex As OperationCanceledException
Throw New TimeoutException("由于超时,请求被取消。", ex)
Catch ex As HttpRequestException
Throw New Exception("发送请求时出错。", ex)
End Try
End Function
''' <summary>
''' 处理 API 错误响应
''' </summary>
Private Sub HandleApiError(statusCode As HttpStatusCode, responseContent As String)
Dim errorMessage As String = "发生未知错误"
Try
If Not String.IsNullOrEmpty(responseContent) Then
Dim errorResponse = JsonSerializer.Deserialize(Of DeepSeekErrorResponse)(responseContent)
errorMessage = If(errorResponse?.Error?.Message, errorMessage)
End If
Catch
' 如果 JSON 解析失败,使用默认错误消息
End Try
Select Case statusCode
Case HttpStatusCode.BadRequest ' 400
Throw New DeepSeekApiException("错误请求:" & errorMessage, statusCode)
Case HttpStatusCode.Unauthorized ' 401
Throw New DeepSeekApiException("身份验证失败。请检查您的API密钥。", statusCode)
Case HttpStatusCode.PaymentRequired ' 402
Throw New DeepSeekApiException("余额不足。请给您的帐户充值。", statusCode)
Case CType(422, HttpStatusCode) ' 422
Throw New DeepSeekApiException("参数错误。" & errorMessage, statusCode)
Case CType(429, HttpStatusCode) ' 429
Throw New DeepSeekApiException("超出速率限制。请降低您的请求频率。", statusCode)
Case HttpStatusCode.InternalServerError ' 500
Throw New DeepSeekApiException("服务器错误。请稍后再试。", statusCode)
Case HttpStatusCode.ServiceUnavailable ' 503
Throw New DeepSeekApiException("服务器繁忙。请稍后再试。", statusCode)
Case Else
Throw New DeepSeekApiException($"API 错误 ({statusCode}): {errorMessage}", statusCode)
End Select
End Sub
End Class
' 请求和响应数据模型
Public Class DeepSeekRequest
<JsonPropertyName("model")>
Public Property Model As String
<JsonPropertyName("messages")>
Public Property Messages As List(Of ChatMessage)
<JsonPropertyName("temperature")>
Public Property Temperature As Single
<JsonPropertyName("top_p")>
Public Property Top_p As Single
<JsonPropertyName("max_tokens")>
Public Property MaxTokens As Integer
<JsonPropertyName("stream")>
Public Property Stream As Boolean
End Class
Public Class ChatMessage
<JsonPropertyName("role")>
Public Property Role As String
<JsonPropertyName("content")>
Public Property Content As String
End Class
Public Class DeepSeekResponse
<JsonPropertyName("choices")>
Public Property Choices As List(Of Choice)
End Class
Public Class Choice
<JsonPropertyName("message")>
Public Property Message As ChatMessage
End Class
Public Class DeepSeekErrorResponse
<JsonPropertyName("error")>
Public Property [Error] As ApiError
End Class
Public Class ApiError
<JsonPropertyName("message")>
Public Property Message As String
<JsonPropertyName("type")>
Public Property Type As String
<JsonPropertyName("code")>
Public Property Code As String
End Class
''' <summary>
''' DeepSeek API 异常类
''' </summary>
Public Class DeepSeekApiException
Inherits Exception
Public ReadOnly Property StatusCode As HttpStatusCode
Public Sub New(message As String, statusCode As HttpStatusCode)
MyBase.New(message)
Me.StatusCode = statusCode
End Sub
Public Sub New(message As String, statusCode As HttpStatusCode, innerException As Exception)
MyBase.New(message, innerException)
Me.StatusCode = statusCode
End Sub
End Class
以上就是DeepSeek写的调用自己的代码(添加了几个属性、增加了几个参数、翻译了几段英文错误说明)之后的样子。
接下来就是调用咯:
' 全局共享的 HttpClient
Private Shared ReadOnly HttpClientInstance As New HttpClient()
Async Function TestDeepSeekClient(modeName As String, systemMessage As String, userMessage As String, timeOut As TimeSpan) As Task(Of (Boolean, String))
Try
' 初始化客户端(使用共享的 HttpClient)
Dim apiKey = "sk-差点得去换APIKEY- -!!!"
Dim client = New DeepSeekClient(HttpClientInstance, apiKey)
' 自定义参数 (可选)
client.Model = modeName
client.Temperature = 0.1
client.MaxTokens = 1024
client.Top_p = 0.2
client.Stream = False
' 发送消息
Dim response = Await client.SendMessageAsync(systemMessage, userMessage, timeOut)
Return (True, response)
Catch ex As DeepSeekApiException
Return (False, $"API Error ({ex.StatusCode}): {ex.Message}")
Catch ex As Exception
Return (False, $"Error: {ex.Message}")
End Try
End Function
最后,就是调用的调用:
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'"deepseek-chat" "deepseek-reasoner"
Dim result = Await TestDeepSeekClient("deepseek-chat", "你是一个AI。", "你的名字是?", TimeSpan.FromSeconds(600))
If result.Item1 Then
Debug.Print(result.Item2)
Else
Debug.Print("发生错误:" & result.Item2)
End If
Debug.Print("完成。")
End Sub
好了,到此为止吧。