使用HttpClient接入
普通用户请忽略本节内容,除非你想了解LLMCaller的底层原理。
LLMCaller是基于HttpClient封装的,有关HttpClient请参考:HttpClient简介
你可以通过HttpClient直接用最原生的方式访问大模型的API接口,下面我们给出一些简单的示例,这些示例对普通用户来说还是太复杂,且很难保证质量,所以除非有特别情况,一般使用LLMCaller接入即可。
基础接入
Dim
hc
As
New
HttpClient("https://api.deepseek.com/chat/completions")
hc.SkipError = True
hc.ContentType =
"application/json"
'必须设置
hc.Headers.Add("Authorization",
"Bearer sk-a734b83536da45e9aedc3e42605bfb3a")
'记得替换为自己的密钥哦
'构造请求体,JSON格式:
hc.Content =
<![CDATA[
{
"model": "deepseek-flash",
"messages": [
{"role": "system", "content": "你是唐朝诗人李白"},
{"role": "user", "content": "请写一首睹月思人的情诗"}
],
"thinking": {"type": "enabled"},
"reasoning_effort": "high",
"stream": false
}
]]>.Value
Dim
res
As
String
= hc.GetData()
If
hc.StatusCode = 200
Then
Dim
resp
As
JObject = JsonConvert.DeserializeObject(res)
Dim
answer
As
String
= resp("choices")(0)("message")("content").ToString()
MessageBoxA.Show("AI回答:"
& vbCrLf & answer)
Else
MessageBoxA.Show("请求失败,状态码:"
& hc.StatusCode & vbCrLf &
"状态描述:"
& hc.StatusDescription)
End
If
使用流输出
构造请求JSON时,如果将stream字段设置为true,大模型接口会启用 SSE
流式分片返回数据;若为false则一次性返回全部数据。
HttpClient有一个ReadOpenAiSseAsync方法,专门用于接收大模型的API接口按SSE流格式输出的分片内容。
ReadOpenAiSseAsync方法是异步的,需要配合Await关键词使用:
Dim
res
As
JObject
=
Await
hc.ReadOpenAiSseAsync(chunkHandler)
chunkHandler:用于处理分片数据的回调函数,类型为:Action(Of String, String)
ReadOpenAiSseAsync每收到一个分片数据,都会调用chunkHandler参数指定的回调函数进行处理,同时会将所有分片数据拼接在一起,在函数执行结束后返回
。
这个回调函数的类型为Action(Of
String, String),包括两个String型参数:
回调函数一般使用lambda函数定义,关于lambda函数的定义和注意事项,参考:lambda函数
内容类型有三种:
需要注意的是,不管是否指定回调函数,ReadOpenAiSseAsync都会正常返回全部数据,回调函数作用通常仅限于刷新界面,实现打字效果,而不是获取数据。
以下代码可以在命令窗口测试执行,
'''Async
'定义三个StringBuilder用于接收不同类型的数据片段,实际开发的时候可以将StringBuilder替换为显示内容的控件
Dim
answers
As
New
StringBuilder()
Dim
thinks
As
New
StringBuilder()
Dim
errors
As
New
StringBuilder()
'定义一个函数用于接收流式输出内容,这里按内容类型分别存储在三个StringBuilder中
Dim
chunkHandler
As
Action(Of
String,
String)
=
Sub(ttype
As
String,
tcontent
As
String)
Select
Case
ttype
Case
"think"
thinks.Append(tcontent)
Case
"answer"
answers.Append(tcontent)
Case
"error"
errors.Append(tcontent)
End
Select
End
Sub
'发起访问
Dim
hc
As
New HttpClient("https://api.deepseek.com/chat/completions")
hc.ContentType =
"application/json" '必须设置
hc.Headers.Add("Authorization", "Bearer
sk-a734b83536da45e9aedc3e42605bfb3a") '替换为自己的密钥哦
'构造请求体,JSON格式:
hc.Content = <![CDATA[
{
"model": "deepseek-flash",
"messages": [
{"role": "system", "content":
"你是一个唐朝诗人"},
{"role":
"user", "content": "请写一首睹月思人的情诗"}
],
"stream": true
}
]]>.Value
Dim retJO As JObject = Await hc.ReadOpenAiSseAsync(chunkHandler) '指定用于接收片段内容的回调函数
If retJO.ContainsKey("error") Then
MessageBoxA.Show(retJO("error").ToString(), "请求失败")
Else
Dim answer = retJO("choices")(0)("message")("content").ToString()
Output.Show("正常答案:" & vbCrLf & answer) '即使指定了回调函数,ReadOpenAiSseAsync还是会正常返回全部数据
'显示一下通过片段内容拼接起来的结果
Output.Show("拼接的推理过程:" & vbCrLf & thinks.ToString())
Output.Show("拼接的答案:" & vbCrLf & answers.ToString())
MessageBoxA.Show("测试完毕")
End If
使用Files API
'''Async
' ---- 第1步:上传图片,获取
file_id ----
Dim
imgPath
As
String
=
"C:\test\test.jpg"
'本地图片文件
Dim
apiKey
As
String
=
"sk-a734b83536da45e9aedc3e42605bfb3a"
'请替换为自己的密钥
Dim
uploadHc
As
New
HttpClient("https://api.deepseek.com/files")
uploadHc.SkipError =
True
uploadHc.Headers.Add("Authorization",
"Bearer "
& apiKey)
uploadHc.FormData.Add("purpose",
"user_data")
uploadHc.Files.Add("file",
imgPath)
Dim
uploadRes
As
String
=
Await
uploadHc.GetDataAsync()
If
uploadHc.StatusCode <> 200
Then
MessageBoxA.Show("文件上传失败:"
& vbCrLf &
"状态码:"
& uploadHc.StatusCode & vbCrLf &
"状态描述:"
& uploadHc.StatusDescription)
Return
""
End
If
Dim
uploadJO
As
JObject = JsonConvert.DeserializeObject(uploadRes)
Dim
fileId
As
String
= uploadJO("id").ToString()
' ----
第2步:构造聊天请求体,引用刚上传的图片
----
Dim
jo
As
New
JObject
jo("model")
=
"deepseek-flash"
Dim
msgArray
As
New
JArray
msgArray.Add(New
JObject
From
{
{"type",
"text"},
{"text",
"这张图片里有什么?"}
})
msgArray.Add(New
JObject
From
{
{"type",
"file"},
{"file_id",
fileId}
})
Dim
userMsg
As
New
JObject
userMsg("role")
=
"user"
userMsg("content")
= msgArray
Dim
messages
As
New
JArray
messages.Add(userMsg)
jo("messages")
= messages
' ---- 第3步:发送聊天请求
----
Dim
chatHc
As
New
HttpClient("https://api.deepseek.com/chat/completions")
chatHc.SkipError =
True
chatHc.ContentType =
"application/json"
chatHc.Headers.Add("Authorization",
"Bearer "
& apiKey)
chatHc.Content = jo.ToString()
Dim
chatRes
As
String
=
Await
chatHc.GetDataAsync()
If
chatHc.StatusCode <> 200
Then
MessageBoxA.Show("请求失败:"
& vbCrLf &
"状态码:"
& chatHc.StatusCode & vbCrLf &
"状态描述:"
& chatHc.StatusDescription)
Return
""
End
If
Dim
resp
As
JObject = JsonConvert.DeserializeObject(chatRes)
Dim
answer
As
String
= resp("choices")(0)("message")("content").ToString()
MessageBoxA.Show("AI回答:"
& vbCrLf & answer)
多轮对话
Dim apiKey As String = "sk-a734b83536da45e9aedc3e42605bfb3a" ' 请替换为你的真实密钥
Dim msgs As New JArray '消息数组,用于存储前n-1轮用户问题和答案,以及第n轮问题
Dim jo As New JObject '请求体
'设置AI模型
jo("model") = "deepseek-flash"
jo("messages") = msgs '将消息数组添加到请求体
'设置AI角色
Dim msg As New JObject()
msg("role") = "system"
msg("content") = "你是一个地理专家"
msgs.Add(msg) '将AI角色设置信息添加的消息数组
' ---- 第一轮 ----
msg = New JObject()
msg("role") = "user"
msg("content") = "珠穆朗玛峰有多高?"
msgs.Add(msg) '添加第一轮用户问题到消息数组
' 发起第一轮访问请求
Dim hc As New HttpClient("https://api.deepseek.com/chat/completions")
hc.ContentType =
"application/json"
hc.Headers.Add("Authorization", "Bearer " & apiKey)
hc.Content =
jo.ToString()
hc.SkipError = True
'获取第一轮结果
Dim res As String = hc.GetData()
If hc.StatusCode <> 200 Then
MessageBoxA.Show("第一轮失败,状态码:" & hc.StatusCode & vbCrLf & "状态描述:" & hc.StatusDescription)
Return ""
End If
Dim retjo As JObject =
JsonConvert.DeserializeObject(res)
Dim retmsg As JObject = retjo("choices")(0)("message")
Output.Show("第一轮答案" & vbCrLf & retmsg("content").ToString())
'
在第二轮开始前,必须将第一轮的答案追加到messages数组!!!
Dim answerJO As New JObject()
answerJO("role") = "assistant"
answerJO("content") = retmsg("content")
'answerJO("reasoning_content") =
retmsg("reasoning_content") '需要传递推理过程到下一轮时可取消屏蔽这行代码
msgs.Add(answerJO)
'添加第一轮答案到消息数组
' ----
第二轮 ----
msg = New JObject()
msg("role") = "user"
msg("content") = "第二高的呢?"
msgs.Add(msg) '添加第二轮用户问题到消息数组
jo("messages") = msgs
' 发起第二轮访问请求
hc = hc.Clone()
'Foxtable的HttpClient不能复用,需要克隆,克隆后修改变化的属性即可
hc.Content = jo.ToString()
res = hc.GetData()
'获取第二轮结果
If hc.StatusCode <> 200 Then
MessageBoxA.Show("第二轮失败,状态码:" & hc.StatusCode & vbCrLf & "状态描述:" & hc.StatusDescription)
Return ""
End If
retjo =
JsonConvert.DeserializeObject(res)
retmsg =
retjo("choices")(0)("message")
Output.Show("第二轮答案" & vbCrLf &
retmsg("content").ToString())
工具定义与调用
Dim apiKey As String = "sk-a734b83536da45e9aedc3e42605bfb3a" '请替换为自己的密钥
' ---- 第1步:定义两个工具 ----
Dim toolsJson As String = <![CDATA[
[
{
"type":
"function",
"function": {
"name": "add",
"description":
"计算两个数字的和(加法)",
"parameters": {
"type":
"object",
"properties": {
"a":
{"type": "number", "description": "第一个数字"},
"b":
{"type": "number", "description": "第二个数字"}
},
"required":
["a", "b"]
}
}
},
{
"type":
"function",
"function": {
"name": "subtract",
"description":
"计算两个数字的差(减法),结果为 a - b",
"parameters": {
"type":
"object",
"properties": {
"a":
{"type": "number", "description": "被减数"},
"b":
{"type": "number", "description": "减数"}
},
"required":
["a", "b"]
}
}
}
]
]]>.Value
Dim tools As JArray =
JsonConvert.DeserializeObject(toolsJson)
Dim jo As New JObject '请求体
Dim messages As New JArray '消息数组
' ---- 第2步:初始化请求 ----
Dim sysJO As New JObject()
sysJO("role") = "system"
sysJO("content") = "你是一个好助手,对于使用外部工具返回的结果,不得质疑篡改,无论结果对错,都直接使用即可,对于无法使用外部工具解决的问题,请依靠你的知识回复"
messages.Add(sysJO)
'将系统角色添加到消息数组
Dim userMsg As New JObject '用户问题
userMsg("role") = "user"
userMsg("content") = "3 加 5 等于多少?然后 减 4 呢?"
messages.Add(userMsg)
'将用户问题加入消息数组
'设置模型
jo("model") = "deepseek-flash"
jo("messages") = messages '将消息数组添加到请求体
jo("tools") = tools '将工具添加到请求体
'准备HttpClient
Dim hc As New HttpClient("https://api.deepseek.com/chat/completions")
hc.SkipError =
True
hc.ContentType =
"application/json"
hc.Headers.Add("Authorization", "Bearer " & apiKey)
hc.Content =
jo.ToString()
' ---- 第3步:循环发起请求和执行工具调用 ----
Dim maxStep As Integer = 10 '工具调用最大步数,这里允许最多10步
Dim cnt As Integer = 0
Do While True
cnt = cnt +
1
If cnt > maxStep Then
MessageBoxA.Show("工具调用最多允许10步!", "请求失败")
Return
""
End If
Dim res As String = hc.GetData()
If hc.StatusCode <> 200 Then
MessageBoxA.Show("状态码:" & hc.StatusCode & vbCrLf & "状态描述:" & hc.StatusDescription, "请求失败")
Return
""
End If
Dim resp As JObject = JsonConvert.DeserializeObject(res)
Dim msg As JObject = resp("choices")(0)("message")
' 检查是否有工具调用
If msg.ContainsKey("tool_calls") = False OrElse msg("tool_calls").Count = 0 Then
MessageBoxA.Show("最终答案:" & vbCrLf & msg("content").ToString())' 没有工具调用,输出最终答案后返回
Exit Do
End If
'有工具调用,则将消息加入消息数组
messages.Add(msg)
' 执行所有工具调用
For Each toolCall As JObject In msg("tool_calls")
Dim toolName As String = toolCall("function")("name").ToString()
Dim callId As String = toolCall("id").ToString()
Dim arguments As String = toolCall("function")("arguments").ToString()
Dim argsObj As JObject = JsonConvert.DeserializeObject(arguments)
Dim result As String = ""
Select Case toolName
Case "add"
Dim a As Double = argsObj("a").ToObject(Of Double)()
Dim b As Double = argsObj("b").ToObject(Of Double)()
result = (a * 10 + b * 10).ToString()
Case "subtract"
Dim a As Double = argsObj("a").ToObject(Of Double)()
Dim b As Double = argsObj("b").ToObject(Of Double)()
result = (a * 10 - b * 10).ToString()
Case Else
result = "未知工具:" & toolName
End Select
' 构造工具结果消息
Dim toolMsg As New JObject
toolMsg("role") = "tool"
toolMsg("tool_call_id") = callId
toolMsg("content") = result
messages.Add(toolMsg) '将工具执行结果追加到消息数组
Next
' ----
构造下一次请求的请求体(带上所有工具的执行结果) ----
hc =
hc.Clone()
jo("messages") = messages '此时消息数组包括所有工具执行结果
hc.Content =
jo.ToString()
Loop