Refactor: modularize and streamline LLM providers and utility functions
- Migrate `compress_image.go` to `internal/imageutil` for better encapsulation. - Reorganize LLM provider implementations into distinct packages (`google`, `openai`, and `anthropic`). - Replace `go_llm` package name with `llm`. - Refactor internal APIs for improved clarity, including renaming `anthropic` to `anthropicImpl` and `google` to `googleImpl`. - Add helper methods and restructure message handling for better separation of concerns.
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.claude
|
||||||
|
.idea
|
||||||
20
anthropic.go
20
anthropic.go
@@ -1,4 +1,4 @@
|
|||||||
package go_llm
|
package llm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -10,19 +10,19 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gitea.stevedudenhoeffer.com/steve/go-llm/utils"
|
"gitea.stevedudenhoeffer.com/steve/go-llm/internal/imageutil"
|
||||||
|
|
||||||
anth "github.com/liushuangls/go-anthropic/v2"
|
anth "github.com/liushuangls/go-anthropic/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type anthropic struct {
|
type anthropicImpl struct {
|
||||||
key string
|
key string
|
||||||
model string
|
model string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ LLM = anthropic{}
|
var _ LLM = anthropicImpl{}
|
||||||
|
|
||||||
func (a anthropic) ModelVersion(modelVersion string) (ChatCompletion, error) {
|
func (a anthropicImpl) ModelVersion(modelVersion string) (ChatCompletion, error) {
|
||||||
a.model = modelVersion
|
a.model = modelVersion
|
||||||
|
|
||||||
// TODO: model verification?
|
// TODO: model verification?
|
||||||
@@ -36,7 +36,7 @@ func deferClose(c io.Closer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest {
|
func (a anthropicImpl) requestToAnthropicRequest(req Request) anth.MessagesRequest {
|
||||||
res := anth.MessagesRequest{
|
res := anth.MessagesRequest{
|
||||||
Model: anth.Model(a.model),
|
Model: anth.Model(a.model),
|
||||||
MaxTokens: 1000,
|
MaxTokens: 1000,
|
||||||
@@ -90,7 +90,7 @@ func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest {
|
|||||||
// Check if image size exceeds 5MiB (5242880 bytes)
|
// Check if image size exceeds 5MiB (5242880 bytes)
|
||||||
if len(raw) >= 5242880 {
|
if len(raw) >= 5242880 {
|
||||||
|
|
||||||
compressed, mime, err := utils.CompressImage(img.Base64, 5*1024*1024)
|
compressed, mime, err := imageutil.CompressImage(img.Base64, 5*1024*1024)
|
||||||
|
|
||||||
// just replace the image with the compressed one
|
// just replace the image with the compressed one
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -157,7 +157,7 @@ func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tool := range req.Toolbox.functions {
|
for _, tool := range req.Toolbox.Functions() {
|
||||||
res.Tools = append(res.Tools, anth.ToolDefinition{
|
res.Tools = append(res.Tools, anth.ToolDefinition{
|
||||||
Name: tool.Name,
|
Name: tool.Name,
|
||||||
Description: tool.Description,
|
Description: tool.Description,
|
||||||
@@ -177,7 +177,7 @@ func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a anthropic) responseToLLMResponse(in anth.MessagesResponse) Response {
|
func (a anthropicImpl) responseToLLMResponse(in anth.MessagesResponse) Response {
|
||||||
choice := ResponseChoice{}
|
choice := ResponseChoice{}
|
||||||
for _, msg := range in.Content {
|
for _, msg := range in.Content {
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ func (a anthropic) responseToLLMResponse(in anth.MessagesResponse) Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a anthropic) ChatComplete(ctx context.Context, req Request) (Response, error) {
|
func (a anthropicImpl) ChatComplete(ctx context.Context, req Request) (Response, error) {
|
||||||
cl := anth.NewClient(a.key)
|
cl := anth.NewClient(a.key)
|
||||||
|
|
||||||
res, err := cl.CreateMessages(ctx, a.requestToAnthropicRequest(req))
|
res, err := cl.CreateMessages(ctx, a.requestToAnthropicRequest(req))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package go_llm
|
package llm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package go_llm
|
package llm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package go_llm
|
package llm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|||||||
16
google.go
16
google.go
@@ -1,4 +1,4 @@
|
|||||||
package go_llm
|
package llm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -11,22 +11,24 @@ import (
|
|||||||
"google.golang.org/genai"
|
"google.golang.org/genai"
|
||||||
)
|
)
|
||||||
|
|
||||||
type google struct {
|
type googleImpl struct {
|
||||||
key string
|
key string
|
||||||
model string
|
model string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g google) ModelVersion(modelVersion string) (ChatCompletion, error) {
|
var _ LLM = googleImpl{}
|
||||||
|
|
||||||
|
func (g googleImpl) ModelVersion(modelVersion string) (ChatCompletion, error) {
|
||||||
g.model = modelVersion
|
g.model = modelVersion
|
||||||
|
|
||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g google) requestToContents(in Request) ([]*genai.Content, *genai.GenerateContentConfig) {
|
func (g googleImpl) requestToContents(in Request) ([]*genai.Content, *genai.GenerateContentConfig) {
|
||||||
var contents []*genai.Content
|
var contents []*genai.Content
|
||||||
var cfg genai.GenerateContentConfig
|
var cfg genai.GenerateContentConfig
|
||||||
|
|
||||||
for _, tool := range in.Toolbox.functions {
|
for _, tool := range in.Toolbox.Functions() {
|
||||||
cfg.Tools = append(cfg.Tools, &genai.Tool{
|
cfg.Tools = append(cfg.Tools, &genai.Tool{
|
||||||
FunctionDeclarations: []*genai.FunctionDeclaration{
|
FunctionDeclarations: []*genai.FunctionDeclaration{
|
||||||
{
|
{
|
||||||
@@ -101,7 +103,7 @@ func (g google) requestToContents(in Request) ([]*genai.Content, *genai.Generate
|
|||||||
return contents, &cfg
|
return contents, &cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g google) responseToLLMResponse(in *genai.GenerateContentResponse) (Response, error) {
|
func (g googleImpl) responseToLLMResponse(in *genai.GenerateContentResponse) (Response, error) {
|
||||||
res := Response{}
|
res := Response{}
|
||||||
|
|
||||||
for _, c := range in.Candidates {
|
for _, c := range in.Candidates {
|
||||||
@@ -142,7 +144,7 @@ func (g google) responseToLLMResponse(in *genai.GenerateContentResponse) (Respon
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g google) ChatComplete(ctx context.Context, req Request) (Response, error) {
|
func (g googleImpl) ChatComplete(ctx context.Context, req Request) (Response, error) {
|
||||||
cl, err := genai.NewClient(ctx, &genai.ClientConfig{
|
cl, err := genai.NewClient(ctx, &genai.ClientConfig{
|
||||||
APIKey: g.key,
|
APIKey: g.key,
|
||||||
Backend: genai.BackendGeminiAPI,
|
Backend: genai.BackendGeminiAPI,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package utils
|
package imageutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
"golang.org/x/image/draw"
|
"golang.org/x/image/draw"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompressImage takes a base‑64‑encoded image (JPEG, PNG or GIF) and returns
|
// CompressImage takes a base-64-encoded image (JPEG, PNG or GIF) and returns
|
||||||
// a base‑64‑encoded version that is at most maxLength in size, or an error.
|
// a base-64-encoded version that is at most maxLength in size, or an error.
|
||||||
func CompressImage(b64 string, maxLength int) (string, string, error) {
|
func CompressImage(b64 string, maxLength int) (string, string, error) {
|
||||||
raw, err := base64.StdEncoding.DecodeString(b64)
|
raw, err := base64.StdEncoding.DecodeString(b64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -29,12 +29,12 @@ func CompressImage(b64 string, maxLength int) (string, string, error) {
|
|||||||
case "image/gif":
|
case "image/gif":
|
||||||
return compressGIF(raw, maxLength)
|
return compressGIF(raw, maxLength)
|
||||||
|
|
||||||
default: // jpeg, png, webp, etc. → treat as raster
|
default: // jpeg, png, webp, etc. -> treat as raster
|
||||||
return compressRaster(raw, maxLength)
|
return compressRaster(raw, maxLength)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Raster path (jpeg / png / single‑frame gif) ----------
|
// ---------- Raster path (jpeg / png / single-frame gif) ----------
|
||||||
|
|
||||||
func compressRaster(src []byte, maxLength int) (string, string, error) {
|
func compressRaster(src []byte, maxLength int) (string, string, error) {
|
||||||
img, _, err := image.Decode(bytes.NewReader(src))
|
img, _, err := image.Decode(bytes.NewReader(src))
|
||||||
@@ -57,7 +57,7 @@ func compressRaster(src []byte, maxLength int) (string, string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// down‑scale 80%
|
// down-scale 80%
|
||||||
b := img.Bounds()
|
b := img.Bounds()
|
||||||
if b.Dx() < 100 || b.Dy() < 100 {
|
if b.Dx() < 100 || b.Dy() < 100 {
|
||||||
return "", "", fmt.Errorf("cannot compress below %.02fMiB without destroying image", float64(maxLength)/1048576.0)
|
return "", "", fmt.Errorf("cannot compress below %.02fMiB without destroying image", float64(maxLength)/1048576.0)
|
||||||
@@ -86,7 +86,7 @@ func compressGIF(src []byte, maxLength int) (string, string, error) {
|
|||||||
return base64.StdEncoding.EncodeToString(buf.Bytes()), "image/gif", nil
|
return base64.StdEncoding.EncodeToString(buf.Bytes()), "image/gif", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// down‑scale every frame by 80%
|
// down-scale every frame by 80%
|
||||||
w, h := g.Config.Width, g.Config.Height
|
w, h := g.Config.Width, g.Config.Height
|
||||||
if w < 100 || h < 100 {
|
if w < 100 || h < 100 {
|
||||||
return "", "", fmt.Errorf("cannot compress animated GIF below 5 MiB without excessive quality loss")
|
return "", "", fmt.Errorf("cannot compress animated GIF below 5 MiB without excessive quality loss")
|
||||||
@@ -94,7 +94,7 @@ func compressGIF(src []byte, maxLength int) (string, string, error) {
|
|||||||
|
|
||||||
nw, nh := int(float64(w)*0.8), int(float64(h)*0.8)
|
nw, nh := int(float64(w)*0.8), int(float64(h)*0.8)
|
||||||
for i, frm := range g.Image {
|
for i, frm := range g.Image {
|
||||||
// convert paletted frame → RGBA for scaling
|
// convert paletted frame -> RGBA for scaling
|
||||||
rgba := image.NewRGBA(frm.Bounds())
|
rgba := image.NewRGBA(frm.Bounds())
|
||||||
draw.Draw(rgba, rgba.Bounds(), frm, frm.Bounds().Min, draw.Src)
|
draw.Draw(rgba, rgba.Bounds(), frm, frm.Bounds().Min, draw.Src)
|
||||||
|
|
||||||
@@ -109,6 +109,6 @@ func compressGIF(src []byte, maxLength int) (string, string, error) {
|
|||||||
g.Image[i] = paletted
|
g.Image[i] = paletted
|
||||||
}
|
}
|
||||||
g.Config.Width, g.Config.Height = nw, nh
|
g.Config.Width, g.Config.Height = nw, nh
|
||||||
// loop back and test size again …
|
// loop back and test size again ...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
272
llm.go
272
llm.go
@@ -1,286 +1,30 @@
|
|||||||
package go_llm
|
package llm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/openai/openai-go"
|
|
||||||
"github.com/openai/openai-go/packages/param"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Role string
|
// ChatCompletion is the interface for chat completion.
|
||||||
|
|
||||||
const (
|
|
||||||
RoleSystem Role = "system"
|
|
||||||
RoleUser Role = "user"
|
|
||||||
RoleAssistant Role = "assistant"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Image struct {
|
|
||||||
Base64 string
|
|
||||||
ContentType string
|
|
||||||
Url string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i Image) toRaw() map[string]any {
|
|
||||||
res := map[string]any{
|
|
||||||
"base64": i.Base64,
|
|
||||||
"contenttype": i.ContentType,
|
|
||||||
"url": i.Url,
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Image) fromRaw(raw map[string]any) Image {
|
|
||||||
var res Image
|
|
||||||
|
|
||||||
res.Base64 = raw["base64"].(string)
|
|
||||||
res.ContentType = raw["contenttype"].(string)
|
|
||||||
res.Url = raw["url"].(string)
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Role Role
|
|
||||||
Name string
|
|
||||||
Text string
|
|
||||||
Images []Image
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Message) toRaw() map[string]any {
|
|
||||||
res := map[string]any{
|
|
||||||
"role": m.Role,
|
|
||||||
"name": m.Name,
|
|
||||||
"text": m.Text,
|
|
||||||
}
|
|
||||||
|
|
||||||
images := make([]map[string]any, 0, len(m.Images))
|
|
||||||
for _, img := range m.Images {
|
|
||||||
images = append(images, img.toRaw())
|
|
||||||
}
|
|
||||||
|
|
||||||
res["images"] = images
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Message) fromRaw(raw map[string]any) Message {
|
|
||||||
var res Message
|
|
||||||
|
|
||||||
res.Role = Role(raw["role"].(string))
|
|
||||||
res.Name = raw["name"].(string)
|
|
||||||
res.Text = raw["text"].(string)
|
|
||||||
|
|
||||||
images := raw["images"].([]map[string]any)
|
|
||||||
for _, img := range images {
|
|
||||||
var i Image
|
|
||||||
|
|
||||||
res.Images = append(res.Images, i.fromRaw(img))
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Message) toChatCompletionMessages(model string) []openai.ChatCompletionMessageParamUnion {
|
|
||||||
var res openai.ChatCompletionMessageParamUnion
|
|
||||||
|
|
||||||
var arrayOfContentParts []openai.ChatCompletionContentPartUnionParam
|
|
||||||
var textContent param.Opt[string]
|
|
||||||
|
|
||||||
for _, img := range m.Images {
|
|
||||||
if img.Base64 != "" {
|
|
||||||
arrayOfContentParts = append(arrayOfContentParts,
|
|
||||||
openai.ChatCompletionContentPartUnionParam{
|
|
||||||
OfImageURL: &openai.ChatCompletionContentPartImageParam{
|
|
||||||
ImageURL: openai.ChatCompletionContentPartImageImageURLParam{
|
|
||||||
URL: "data:" + img.ContentType + ";base64," + img.Base64,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else if img.Url != "" {
|
|
||||||
arrayOfContentParts = append(arrayOfContentParts,
|
|
||||||
openai.ChatCompletionContentPartUnionParam{
|
|
||||||
OfImageURL: &openai.ChatCompletionContentPartImageParam{
|
|
||||||
ImageURL: openai.ChatCompletionContentPartImageImageURLParam{
|
|
||||||
URL: img.Url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Text != "" {
|
|
||||||
if len(arrayOfContentParts) > 0 {
|
|
||||||
arrayOfContentParts = append(arrayOfContentParts,
|
|
||||||
openai.ChatCompletionContentPartUnionParam{
|
|
||||||
OfText: &openai.ChatCompletionContentPartTextParam{
|
|
||||||
Text: "\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
textContent = openai.String(m.Text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a := strings.Split(model, "-")
|
|
||||||
|
|
||||||
useSystemInsteadOfDeveloper := true
|
|
||||||
if len(a) > 1 && a[0][0] == 'o' {
|
|
||||||
useSystemInsteadOfDeveloper = false
|
|
||||||
}
|
|
||||||
|
|
||||||
switch m.Role {
|
|
||||||
case RoleSystem:
|
|
||||||
if useSystemInsteadOfDeveloper {
|
|
||||||
res = openai.ChatCompletionMessageParamUnion{
|
|
||||||
OfSystem: &openai.ChatCompletionSystemMessageParam{
|
|
||||||
Content: openai.ChatCompletionSystemMessageParamContentUnion{
|
|
||||||
OfString: textContent,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res = openai.ChatCompletionMessageParamUnion{
|
|
||||||
OfDeveloper: &openai.ChatCompletionDeveloperMessageParam{
|
|
||||||
Content: openai.ChatCompletionDeveloperMessageParamContentUnion{
|
|
||||||
OfString: textContent,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case RoleUser:
|
|
||||||
var name param.Opt[string]
|
|
||||||
if m.Name != "" {
|
|
||||||
name = openai.String(m.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
res = openai.ChatCompletionMessageParamUnion{
|
|
||||||
OfUser: &openai.ChatCompletionUserMessageParam{
|
|
||||||
Name: name,
|
|
||||||
Content: openai.ChatCompletionUserMessageParamContentUnion{
|
|
||||||
OfString: textContent,
|
|
||||||
OfArrayOfContentParts: arrayOfContentParts,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
case RoleAssistant:
|
|
||||||
var name param.Opt[string]
|
|
||||||
if m.Name != "" {
|
|
||||||
name = openai.String(m.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
res = openai.ChatCompletionMessageParamUnion{
|
|
||||||
OfAssistant: &openai.ChatCompletionAssistantMessageParam{
|
|
||||||
Name: name,
|
|
||||||
Content: openai.ChatCompletionAssistantMessageParamContentUnion{
|
|
||||||
OfString: textContent,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return []openai.ChatCompletionMessageParamUnion{res}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ToolCall struct {
|
|
||||||
ID string
|
|
||||||
FunctionCall FunctionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t ToolCall) toRaw() map[string]any {
|
|
||||||
res := map[string]any{
|
|
||||||
"id": t.ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
res["function"] = t.FunctionCall.toRaw()
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t ToolCall) toChatCompletionMessages(_ string) []openai.ChatCompletionMessageParamUnion {
|
|
||||||
return []openai.ChatCompletionMessageParamUnion{{
|
|
||||||
OfAssistant: &openai.ChatCompletionAssistantMessageParam{
|
|
||||||
ToolCalls: []openai.ChatCompletionMessageToolCallParam{
|
|
||||||
{
|
|
||||||
ID: t.ID,
|
|
||||||
Function: openai.ChatCompletionMessageToolCallFunctionParam{
|
|
||||||
Name: t.FunctionCall.Name,
|
|
||||||
Arguments: t.FunctionCall.Arguments,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ToolCallResponse struct {
|
|
||||||
ID string
|
|
||||||
Result any
|
|
||||||
Error error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t ToolCallResponse) toRaw() map[string]any {
|
|
||||||
res := map[string]any{
|
|
||||||
"id": t.ID,
|
|
||||||
"result": t.Result,
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Error != nil {
|
|
||||||
res["error"] = t.Error.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t ToolCallResponse) toChatCompletionMessages(_ string) []openai.ChatCompletionMessageParamUnion {
|
|
||||||
var refusal string
|
|
||||||
if t.Error != nil {
|
|
||||||
refusal = t.Error.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if refusal != "" {
|
|
||||||
if t.Result != "" {
|
|
||||||
t.Result = fmt.Sprint(t.Result) + " (error in execution: " + refusal + ")"
|
|
||||||
} else {
|
|
||||||
t.Result = "error in execution:" + refusal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return []openai.ChatCompletionMessageParamUnion{{
|
|
||||||
OfTool: &openai.ChatCompletionToolMessageParam{
|
|
||||||
ToolCallID: t.ID,
|
|
||||||
Content: openai.ChatCompletionToolMessageParamContentUnion{
|
|
||||||
OfString: openai.String(fmt.Sprint(t.Result)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChatCompletion interface {
|
type ChatCompletion interface {
|
||||||
ChatComplete(ctx context.Context, req Request) (Response, error)
|
ChatComplete(ctx context.Context, req Request) (Response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LLM is the interface for language model providers.
|
||||||
type LLM interface {
|
type LLM interface {
|
||||||
ModelVersion(modelVersion string) (ChatCompletion, error)
|
ModelVersion(modelVersion string) (ChatCompletion, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenAI creates a new OpenAI LLM provider with the given API key.
|
||||||
func OpenAI(key string) LLM {
|
func OpenAI(key string) LLM {
|
||||||
return openaiImpl{key: key}
|
return openaiImpl{key: key}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Anthropic creates a new Anthropic LLM provider with the given API key.
|
||||||
func Anthropic(key string) LLM {
|
func Anthropic(key string) LLM {
|
||||||
return anthropic{key: key}
|
return anthropicImpl{key: key}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Google creates a new Google LLM provider with the given API key.
|
||||||
func Google(key string) LLM {
|
func Google(key string) LLM {
|
||||||
return google{key: key}
|
return googleImpl{key: key}
|
||||||
}
|
}
|
||||||
|
|||||||
115
message.go
Normal file
115
message.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package llm
|
||||||
|
|
||||||
|
// Role represents the role of a message in a conversation.
|
||||||
|
type Role string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoleSystem Role = "system"
|
||||||
|
RoleUser Role = "user"
|
||||||
|
RoleAssistant Role = "assistant"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Image represents an image that can be included in a message.
|
||||||
|
type Image struct {
|
||||||
|
Base64 string
|
||||||
|
ContentType string
|
||||||
|
Url string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Image) toRaw() map[string]any {
|
||||||
|
res := map[string]any{
|
||||||
|
"base64": i.Base64,
|
||||||
|
"contenttype": i.ContentType,
|
||||||
|
"url": i.Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Image) fromRaw(raw map[string]any) Image {
|
||||||
|
var res Image
|
||||||
|
|
||||||
|
res.Base64 = raw["base64"].(string)
|
||||||
|
res.ContentType = raw["contenttype"].(string)
|
||||||
|
res.Url = raw["url"].(string)
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message represents a message in a conversation.
|
||||||
|
type Message struct {
|
||||||
|
Role Role
|
||||||
|
Name string
|
||||||
|
Text string
|
||||||
|
Images []Image
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Message) toRaw() map[string]any {
|
||||||
|
res := map[string]any{
|
||||||
|
"role": m.Role,
|
||||||
|
"name": m.Name,
|
||||||
|
"text": m.Text,
|
||||||
|
}
|
||||||
|
|
||||||
|
images := make([]map[string]any, 0, len(m.Images))
|
||||||
|
for _, img := range m.Images {
|
||||||
|
images = append(images, img.toRaw())
|
||||||
|
}
|
||||||
|
|
||||||
|
res["images"] = images
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) fromRaw(raw map[string]any) Message {
|
||||||
|
var res Message
|
||||||
|
|
||||||
|
res.Role = Role(raw["role"].(string))
|
||||||
|
res.Name = raw["name"].(string)
|
||||||
|
res.Text = raw["text"].(string)
|
||||||
|
|
||||||
|
images := raw["images"].([]map[string]any)
|
||||||
|
for _, img := range images {
|
||||||
|
var i Image
|
||||||
|
|
||||||
|
res.Images = append(res.Images, i.fromRaw(img))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToolCall represents a tool call made by an assistant.
|
||||||
|
type ToolCall struct {
|
||||||
|
ID string
|
||||||
|
FunctionCall FunctionCall
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolCall) toRaw() map[string]any {
|
||||||
|
res := map[string]any{
|
||||||
|
"id": t.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
res["function"] = t.FunctionCall.toRaw()
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToolCallResponse represents the response to a tool call.
|
||||||
|
type ToolCallResponse struct {
|
||||||
|
ID string
|
||||||
|
Result any
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ToolCallResponse) toRaw() map[string]any {
|
||||||
|
res := map[string]any{
|
||||||
|
"id": t.ID,
|
||||||
|
"result": t.Result,
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Error != nil {
|
||||||
|
res["error"] = t.Error.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
210
openai.go
210
openai.go
@@ -1,4 +1,4 @@
|
|||||||
package go_llm
|
package llm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/openai/openai-go"
|
"github.com/openai/openai-go"
|
||||||
"github.com/openai/openai-go/option"
|
"github.com/openai/openai-go/option"
|
||||||
|
"github.com/openai/openai-go/packages/param"
|
||||||
"github.com/openai/openai-go/shared"
|
"github.com/openai/openai-go/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,14 +25,14 @@ func (o openaiImpl) newRequestToOpenAIRequest(request Request) openai.ChatComple
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, i := range request.Conversation {
|
for _, i := range request.Conversation {
|
||||||
res.Messages = append(res.Messages, i.toChatCompletionMessages(o.model)...)
|
res.Messages = append(res.Messages, inputToChatCompletionMessages(i, o.model)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, msg := range request.Messages {
|
for _, msg := range request.Messages {
|
||||||
res.Messages = append(res.Messages, msg.toChatCompletionMessages(o.model)...)
|
res.Messages = append(res.Messages, messageToChatCompletionMessages(msg, o.model)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tool := range request.Toolbox.functions {
|
for _, tool := range request.Toolbox.Functions() {
|
||||||
res.Tools = append(res.Tools, openai.ChatCompletionToolParam{
|
res.Tools = append(res.Tools, openai.ChatCompletionToolParam{
|
||||||
Type: "function",
|
Type: "function",
|
||||||
Function: shared.FunctionDefinitionParam{
|
Function: shared.FunctionDefinitionParam{
|
||||||
@@ -111,10 +112,9 @@ func (o openaiImpl) ChatComplete(ctx context.Context, request Request) (Response
|
|||||||
req := o.newRequestToOpenAIRequest(request)
|
req := o.newRequestToOpenAIRequest(request)
|
||||||
|
|
||||||
resp, err := cl.Chat.Completions.New(ctx, req)
|
resp, err := cl.Chat.Completions.New(ctx, req)
|
||||||
//resp, err := cl.CreateChatCompletion(ctx, req)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Response{}, fmt.Errorf("unhandled openaiImpl error: %w", err)
|
return Response{}, fmt.Errorf("unhandled openai error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return o.responseToLLMResponse(resp), nil
|
return o.responseToLLMResponse(resp), nil
|
||||||
@@ -122,7 +122,201 @@ func (o openaiImpl) ChatComplete(ctx context.Context, request Request) (Response
|
|||||||
|
|
||||||
func (o openaiImpl) ModelVersion(modelVersion string) (ChatCompletion, error) {
|
func (o openaiImpl) ModelVersion(modelVersion string) (ChatCompletion, error) {
|
||||||
return openaiImpl{
|
return openaiImpl{
|
||||||
key: o.key,
|
key: o.key,
|
||||||
model: modelVersion,
|
model: modelVersion,
|
||||||
|
baseUrl: o.baseUrl,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inputToChatCompletionMessages converts an Input to OpenAI chat completion messages.
|
||||||
|
func inputToChatCompletionMessages(input Input, model string) []openai.ChatCompletionMessageParamUnion {
|
||||||
|
switch v := input.(type) {
|
||||||
|
case Message:
|
||||||
|
return messageToChatCompletionMessages(v, model)
|
||||||
|
case ToolCall:
|
||||||
|
return toolCallToChatCompletionMessages(v)
|
||||||
|
case ToolCallResponse:
|
||||||
|
return toolCallResponseToChatCompletionMessages(v)
|
||||||
|
case ResponseChoice:
|
||||||
|
return responseChoiceToChatCompletionMessages(v)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageToChatCompletionMessages(m Message, model string) []openai.ChatCompletionMessageParamUnion {
|
||||||
|
var res openai.ChatCompletionMessageParamUnion
|
||||||
|
|
||||||
|
var arrayOfContentParts []openai.ChatCompletionContentPartUnionParam
|
||||||
|
var textContent param.Opt[string]
|
||||||
|
|
||||||
|
for _, img := range m.Images {
|
||||||
|
if img.Base64 != "" {
|
||||||
|
arrayOfContentParts = append(arrayOfContentParts,
|
||||||
|
openai.ChatCompletionContentPartUnionParam{
|
||||||
|
OfImageURL: &openai.ChatCompletionContentPartImageParam{
|
||||||
|
ImageURL: openai.ChatCompletionContentPartImageImageURLParam{
|
||||||
|
URL: "data:" + img.ContentType + ";base64," + img.Base64,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else if img.Url != "" {
|
||||||
|
arrayOfContentParts = append(arrayOfContentParts,
|
||||||
|
openai.ChatCompletionContentPartUnionParam{
|
||||||
|
OfImageURL: &openai.ChatCompletionContentPartImageParam{
|
||||||
|
ImageURL: openai.ChatCompletionContentPartImageImageURLParam{
|
||||||
|
URL: img.Url,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Text != "" {
|
||||||
|
if len(arrayOfContentParts) > 0 {
|
||||||
|
arrayOfContentParts = append(arrayOfContentParts,
|
||||||
|
openai.ChatCompletionContentPartUnionParam{
|
||||||
|
OfText: &openai.ChatCompletionContentPartTextParam{
|
||||||
|
Text: "\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
textContent = openai.String(m.Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a := strings.Split(model, "-")
|
||||||
|
|
||||||
|
useSystemInsteadOfDeveloper := true
|
||||||
|
if len(a) > 1 && a[0][0] == 'o' {
|
||||||
|
useSystemInsteadOfDeveloper = false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m.Role {
|
||||||
|
case RoleSystem:
|
||||||
|
if useSystemInsteadOfDeveloper {
|
||||||
|
res = openai.ChatCompletionMessageParamUnion{
|
||||||
|
OfSystem: &openai.ChatCompletionSystemMessageParam{
|
||||||
|
Content: openai.ChatCompletionSystemMessageParamContentUnion{
|
||||||
|
OfString: textContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res = openai.ChatCompletionMessageParamUnion{
|
||||||
|
OfDeveloper: &openai.ChatCompletionDeveloperMessageParam{
|
||||||
|
Content: openai.ChatCompletionDeveloperMessageParamContentUnion{
|
||||||
|
OfString: textContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case RoleUser:
|
||||||
|
var name param.Opt[string]
|
||||||
|
if m.Name != "" {
|
||||||
|
name = openai.String(m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
res = openai.ChatCompletionMessageParamUnion{
|
||||||
|
OfUser: &openai.ChatCompletionUserMessageParam{
|
||||||
|
Name: name,
|
||||||
|
Content: openai.ChatCompletionUserMessageParamContentUnion{
|
||||||
|
OfString: textContent,
|
||||||
|
OfArrayOfContentParts: arrayOfContentParts,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
case RoleAssistant:
|
||||||
|
var name param.Opt[string]
|
||||||
|
if m.Name != "" {
|
||||||
|
name = openai.String(m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
res = openai.ChatCompletionMessageParamUnion{
|
||||||
|
OfAssistant: &openai.ChatCompletionAssistantMessageParam{
|
||||||
|
Name: name,
|
||||||
|
Content: openai.ChatCompletionAssistantMessageParamContentUnion{
|
||||||
|
OfString: textContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []openai.ChatCompletionMessageParamUnion{res}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toolCallToChatCompletionMessages(t ToolCall) []openai.ChatCompletionMessageParamUnion {
|
||||||
|
return []openai.ChatCompletionMessageParamUnion{{
|
||||||
|
OfAssistant: &openai.ChatCompletionAssistantMessageParam{
|
||||||
|
ToolCalls: []openai.ChatCompletionMessageToolCallParam{
|
||||||
|
{
|
||||||
|
ID: t.ID,
|
||||||
|
Function: openai.ChatCompletionMessageToolCallFunctionParam{
|
||||||
|
Name: t.FunctionCall.Name,
|
||||||
|
Arguments: t.FunctionCall.Arguments,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toolCallResponseToChatCompletionMessages(t ToolCallResponse) []openai.ChatCompletionMessageParamUnion {
|
||||||
|
var refusal string
|
||||||
|
if t.Error != nil {
|
||||||
|
refusal = t.Error.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
result := t.Result
|
||||||
|
if refusal != "" {
|
||||||
|
if result != "" {
|
||||||
|
result = fmt.Sprint(result) + " (error in execution: " + refusal + ")"
|
||||||
|
} else {
|
||||||
|
result = "error in execution:" + refusal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []openai.ChatCompletionMessageParamUnion{{
|
||||||
|
OfTool: &openai.ChatCompletionToolMessageParam{
|
||||||
|
ToolCallID: t.ID,
|
||||||
|
Content: openai.ChatCompletionToolMessageParamContentUnion{
|
||||||
|
OfString: openai.String(fmt.Sprint(result)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func responseChoiceToChatCompletionMessages(r ResponseChoice) []openai.ChatCompletionMessageParamUnion {
|
||||||
|
var as openai.ChatCompletionAssistantMessageParam
|
||||||
|
|
||||||
|
if r.Name != "" {
|
||||||
|
as.Name = openai.String(r.Name)
|
||||||
|
}
|
||||||
|
if r.Refusal != "" {
|
||||||
|
as.Refusal = openai.String(r.Refusal)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Content != "" {
|
||||||
|
as.Content.OfString = openai.String(r.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, call := range r.Calls {
|
||||||
|
as.ToolCalls = append(as.ToolCalls, openai.ChatCompletionMessageToolCallParam{
|
||||||
|
ID: call.ID,
|
||||||
|
Function: openai.ChatCompletionMessageToolCallFunctionParam{
|
||||||
|
Name: call.FunctionCall.Name,
|
||||||
|
Arguments: call.FunctionCall.Arguments,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return []openai.ChatCompletionMessageParamUnion{
|
||||||
|
{
|
||||||
|
OfAssistant: &as,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
2
parse.go
2
parse.go
@@ -1,4 +1,4 @@
|
|||||||
package go_llm
|
package llm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|||||||
11
provider/anthropic/anthropic.go
Normal file
11
provider/anthropic/anthropic.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Package anthropic provides the Anthropic LLM provider.
|
||||||
|
package anthropic
|
||||||
|
|
||||||
|
import (
|
||||||
|
llm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a new Anthropic LLM provider with the given API key.
|
||||||
|
func New(key string) llm.LLM {
|
||||||
|
return llm.Anthropic(key)
|
||||||
|
}
|
||||||
11
provider/google/google.go
Normal file
11
provider/google/google.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Package google provides the Google LLM provider.
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
llm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a new Google LLM provider with the given API key.
|
||||||
|
func New(key string) llm.LLM {
|
||||||
|
return llm.Google(key)
|
||||||
|
}
|
||||||
11
provider/openai/openai.go
Normal file
11
provider/openai/openai.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Package openai provides the OpenAI LLM provider.
|
||||||
|
package openai
|
||||||
|
|
||||||
|
import (
|
||||||
|
llm "gitea.stevedudenhoeffer.com/steve/go-llm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a new OpenAI LLM provider with the given API key.
|
||||||
|
func New(key string) llm.LLM {
|
||||||
|
return llm.OpenAI(key)
|
||||||
|
}
|
||||||
25
request.go
25
request.go
@@ -1,17 +1,20 @@
|
|||||||
package go_llm
|
package llm
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/openai/openai-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
type rawAble interface {
|
|
||||||
toRaw() map[string]any
|
|
||||||
fromRaw(raw map[string]any) Input
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Input is the interface for conversation inputs.
|
||||||
|
// Types that implement this interface can be part of a conversation:
|
||||||
|
// Message, ToolCall, ToolCallResponse, and ResponseChoice.
|
||||||
type Input interface {
|
type Input interface {
|
||||||
toChatCompletionMessages(model string) []openai.ChatCompletionMessageParamUnion
|
// isInput is a marker method to ensure only valid types implement this interface.
|
||||||
|
isInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implement Input interface for all valid input types.
|
||||||
|
func (Message) isInput() {}
|
||||||
|
func (ToolCall) isInput() {}
|
||||||
|
func (ToolCallResponse) isInput() {}
|
||||||
|
func (ResponseChoice) isInput() {}
|
||||||
|
|
||||||
|
// Request represents a request to a language model.
|
||||||
type Request struct {
|
type Request struct {
|
||||||
Conversation []Input
|
Conversation []Input
|
||||||
Messages []Message
|
Messages []Message
|
||||||
|
|||||||
38
response.go
38
response.go
@@ -1,9 +1,6 @@
|
|||||||
package go_llm
|
package llm
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/openai/openai-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
// ResponseChoice represents a single choice in a response.
|
||||||
type ResponseChoice struct {
|
type ResponseChoice struct {
|
||||||
Index int
|
Index int
|
||||||
Role Role
|
Role Role
|
||||||
@@ -32,36 +29,6 @@ func (r ResponseChoice) toRaw() map[string]any {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r ResponseChoice) toChatCompletionMessages(_ string) []openai.ChatCompletionMessageParamUnion {
|
|
||||||
var as openai.ChatCompletionAssistantMessageParam
|
|
||||||
|
|
||||||
if r.Name != "" {
|
|
||||||
as.Name = openai.String(r.Name)
|
|
||||||
}
|
|
||||||
if r.Refusal != "" {
|
|
||||||
as.Refusal = openai.String(r.Refusal)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Content != "" {
|
|
||||||
as.Content.OfString = openai.String(r.Content)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, call := range r.Calls {
|
|
||||||
as.ToolCalls = append(as.ToolCalls, openai.ChatCompletionMessageToolCallParam{
|
|
||||||
ID: call.ID,
|
|
||||||
Function: openai.ChatCompletionMessageToolCallFunctionParam{
|
|
||||||
Name: call.FunctionCall.Name,
|
|
||||||
Arguments: call.FunctionCall.Arguments,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return []openai.ChatCompletionMessageParamUnion{
|
|
||||||
{
|
|
||||||
OfAssistant: &as,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r ResponseChoice) toInput() []Input {
|
func (r ResponseChoice) toInput() []Input {
|
||||||
var res []Input
|
var res []Input
|
||||||
|
|
||||||
@@ -79,6 +46,7 @@ func (r ResponseChoice) toInput() []Input {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Response represents a response from a language model.
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Choices []ResponseChoice
|
Choices []ResponseChoice
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package go_llm
|
package llm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
Reference in New Issue
Block a user