使用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


本页地址:http://www.foxtable.com/webhelp/topics/7227.htm