From 67c3f475b5fcad30c1d5ae2100276bd5d27353b0 Mon Sep 17 00:00:00 2001 From: git-hulk Date: Tue, 16 Sep 2025 14:56:57 +0800 Subject: [PATCH 1/2] feat: implement Langfuse Prompt client with CRUD operations - Add prompt management client with list, get, create, update operations - Include comprehensive test coverage for all prompt operations - Support chat message placeholders and prompt compilation --- libs/acl/langfuse/langfuse.go | 17 +- libs/acl/langfuse/prompt.go | 343 ++++++++++++++++++++++++++++++ libs/acl/langfuse/prompt_test.go | 350 +++++++++++++++++++++++++++++++ 3 files changed, 707 insertions(+), 3 deletions(-) create mode 100644 libs/acl/langfuse/prompt.go create mode 100644 libs/acl/langfuse/prompt_test.go diff --git a/libs/acl/langfuse/langfuse.go b/libs/acl/langfuse/langfuse.go index 8211d7d67..86db67589 100644 --- a/libs/acl/langfuse/langfuse.go +++ b/libs/acl/langfuse/langfuse.go @@ -38,6 +38,7 @@ type Langfuse interface { EndGeneration(body *GenerationEventBody) error CreateEvent(body *EventEventBody) (string, error) Flush() + Prompt() *promptClient } // NewLangfuse creates a Langfuse client instance @@ -68,9 +69,10 @@ func NewLangfuse( opt(o) } + httpCli := &http.Client{Timeout: o.timeout} tm := newTaskManager( o.threads, - &http.Client{Timeout: o.timeout}, + httpCli, host, o.maxTaskQueueSize, o.flushAt, @@ -85,11 +87,14 @@ func NewLangfuse( secretKey, o.maxRetry, ) - return &langfuseIns{tm: tm} + langfuseCli := newClient(httpCli, host, publicKey, secretKey, sdkVersion) + promptCli := &promptClient{cli: langfuseCli} + return &langfuseIns{tm: tm, promptCli: promptCli} } type langfuseIns struct { - tm *taskManager + tm *taskManager + promptCli *promptClient } // CreateTrace creates a new trace in Langfuse @@ -215,3 +220,9 @@ func (l *langfuseIns) CreateEvent(body *EventEventBody) (string, error) { func (l *langfuseIns) Flush() { l.tm.flush() } + +// Prompt provides access to the prompt client for managing prompts. +// Use the returned promptClient to create, retrieve and list prompts. +func (l *langfuseIns) Prompt() *promptClient { + return l.promptCli +} diff --git a/libs/acl/langfuse/prompt.go b/libs/acl/langfuse/prompt.go new file mode 100644 index 000000000..01c0d5eed --- /dev/null +++ b/libs/acl/langfuse/prompt.go @@ -0,0 +1,343 @@ +package langfuse + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" +) + +const ( + promptsPath = "/api/public/v2/prompts" + promptByNamePath = "/api/public/v2/prompts/" +) + +type ListMetadata struct { + Page int `json:"page"` + Limit int `json:"limit"` + TotalItems int `json:"totalItems"` + TotalPages int `json:"totalPages"` +} + +// ChatMessageWithPlaceHolder represents a chat message that can include placeholders for dynamic content. +// +// Placeholders in the content can be replaced with actual values when using the prompt. +// The Role field specifies the message role (e.g., "system", "user", "assistant"), +// Type specifies the content type, and Content contains the message text with optional placeholders. +type ChatMessageWithPlaceHolder struct { + Role string `json:"role"` + Type string `json:"type"` + Content string `json:"content"` +} + +func (c *ChatMessageWithPlaceHolder) validate() error { + if c.Role == "" { + return errors.New("'role' is required") + } + if c.Content == "" { + return errors.New("'content' is required") + } + return nil +} + +// PromptEntry represents a complete prompt template with its configuration and messages. +// +// A prompt entry contains the prompt name, which can be either a string (when Type is "text") +// or an array of chat messages with placeholders (for other types). +// The Type field determines the expected structure of the Prompt field. +// The Config field can contain model-specific configuration parameters. +type PromptEntry struct { + Name string `json:"name"` + Prompt any `json:"prompt"` + Type string `json:"type"` + Version int `json:"version,omitempty"` + Tags []string `json:"tags,omitempty"` + Labels []string `json:"labels,omitempty"` + Config any `json:"config,omitempty"` +} + +// UnmarshalJSON implements custom JSON unmarshalling for PromptEntry. +// It correctly unmarshal the Prompt field as either a string (for "text" type) +// or []ChatMessageWithPlaceHolder (for other types) based on the Type field. +func (p *PromptEntry) UnmarshalJSON(data []byte) error { + // Define an alias to avoid infinite recursion during unmarshalling + type Alias PromptEntry + + // First, unmarshal into a temporary struct with raw JSON for the Prompt field + temp := &struct { + *Alias + Prompt json.RawMessage `json:"prompt"` + }{ + Alias: (*Alias)(p), + } + + if err := json.Unmarshal(data, temp); err != nil { + return err + } + + if p.Type == "text" { + var promptStr string + if err := json.Unmarshal(temp.Prompt, &promptStr); err != nil { + return fmt.Errorf("failed to unmarshal prompt as string for type 'text': %w", err) + } + p.Prompt = promptStr + } else { + var promptMessages []ChatMessageWithPlaceHolder + if err := json.Unmarshal(temp.Prompt, &promptMessages); err != nil { + return fmt.Errorf("failed to unmarshal prompt as []ChatMessageWithPlaceHolder for type '%s': %w", p.Type, err) + } + p.Prompt = promptMessages + } + + return nil +} + +func (p *PromptEntry) validate() error { + if p.Name == "" { + return errors.New("'name' is required") + } + if p.Prompt == nil { + return errors.New("'prompt' cannot be nil") + } + + // Validate based on Type field + if p.Type == "text" { + // For text type, prompt should be a string + if str, ok := p.Prompt.(string); !ok || str == "" { + return errors.New("'prompt' must be a non-empty string when type is 'text'") + } + } else { + // For other types, prompt should be []ChatMessageWithPlaceHolder + messages, ok := p.Prompt.([]ChatMessageWithPlaceHolder) + if !ok { + return errors.New("'prompt' must be []ChatMessageWithPlaceHolder when type is not 'text'") + } + if len(messages) == 0 { + return errors.New("'prompt' cannot be empty") + } + for _, msg := range messages { + if err := msg.validate(); err != nil { + return fmt.Errorf("invalid prompts message: %w", err) + } + } + } + return nil +} + +// ListParams defines the query parameters for filtering and paginating prompt listings. +// +// Use these parameters to filter prompts by name, labels, tags, and update timestamps, +// as well as to control pagination with Page and Limit fields. +type ListParams struct { + Name string + Label string + Tag string + Page int + Limit int + FromUpdatedAt time.Time + ToUpdatedAt time.Time +} + +// ToQueryString converts the ListParams to a URL query string. +func (query *ListParams) ToQueryString() string { + parts := make([]string, 0) + if query.Name != "" { + parts = append(parts, "name="+query.Name+"") + } + if query.Label != "" { + parts = append(parts, "label="+query.Label) + } + if query.Tag != "" { + parts = append(parts, "tag="+query.Tag) + } + if query.Page != 0 { + parts = append(parts, "page="+strconv.Itoa(query.Page)) + } + if query.Limit != 0 { + parts = append(parts, "limit="+strconv.Itoa(query.Limit)) + } + if !query.FromUpdatedAt.IsZero() { + // format with ios8601 + parts = append(parts, "fromUpdatedAt="+query.FromUpdatedAt.Format(time.RFC3339)) + } + if !query.ToUpdatedAt.IsZero() { + parts = append(parts, "toUpdatedAt="+query.ToUpdatedAt.Format(time.RFC3339)) + } + return strings.Join(parts, "&") +} + +// GetParams defines the parameters for retrieving a specific prompt. +// +// Use Name to specify the prompt name, Label for a specific label, +// and Version for a specific version. If Version is 0, the latest version is returned. +type GetParams struct { + Name string + Label string + Version int +} + +type PromptMeta struct { + Name string `json:"name"` + Labels []string `json:"labels"` + Tags []string `json:"tags"` + Versions []int `json:"versions"` + LastConfig any `json:"lastConfig,omitempty"` + LastUpdatedAt time.Time `json:"lastUpdatedAt"` +} + +// ListPrompts represents the response structure for prompt listing operations. +// +// It contains pagination metadata and an array of prompt entries matching the query parameters. +type ListPrompts struct { + Metadata ListMetadata `json:"meta"` + Data []PromptMeta `json:"data"` +} + +// promptClient provides methods for interacting with the Langfuse prompts API. +// +// The client handles HTTP communication with the Langfuse API for prompt management +// operations including creating, retrieving, and listing prompt templates. +type promptClient struct { + cli *client +} + +// GetPrompt retrieves a specific prompt by name, version, and label. +func (c *promptClient) GetPrompt(ctx context.Context, params GetParams) (*PromptEntry, error) { + if params.Name == "" { + return nil, errors.New("'name' is required") + } + + url := c.cli.host + promptByNamePath + params.Name + + // Build query parameters + queryParts := make([]string, 0) + if params.Version > 0 { + queryParts = append(queryParts, "version="+strconv.Itoa(params.Version)) + } + if params.Label != "" { + queryParts = append(queryParts, "label="+params.Label) + } + if len(queryParts) > 0 { + url += "?" + strings.Join(queryParts, "&") + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create get prompt request: %w", err) + } + + c.cli.addBaseHeaders(req) + + resp, err := c.cli.cli.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to do get prompt request: %v", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, &apiError{Status: resp.StatusCode, Message: fmt.Sprintf("failed to read get prompt response: %v", err)} + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, &apiError{Status: resp.StatusCode, Message: string(body)} + } + + var prompt PromptEntry + if err := json.Unmarshal(body, &prompt); err != nil { + return nil, fmt.Errorf("failed to unmarshal get prompt response: %w", err) + } + + return &prompt, nil +} + +// ListPrompt retrieves a list of prompts based on the provided parameters. +func (c promptClient) ListPrompt(ctx context.Context, params ListParams) (*ListPrompts, error) { + url := c.cli.host + promptsPath + + queryString := params.ToQueryString() + if queryString != "" { + url += "?" + queryString + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create list prompts request: %w", err) + } + + c.cli.addBaseHeaders(req) + + resp, err := c.cli.cli.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to do list prompts request: %v", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, &apiError{Status: resp.StatusCode, Message: fmt.Sprintf("failed to read list prompts response: %v", err)} + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, &apiError{Status: resp.StatusCode, Message: string(body)} + } + + var listResponse ListPrompts + if err := json.Unmarshal(body, &listResponse); err != nil { + return nil, fmt.Errorf("failed to unmarshal list prompts response: %w", err) + } + + return &listResponse, nil +} + +// CreatePrompt creates a new prompt. +func (c *promptClient) CreatePrompt(ctx context.Context, createPrompt *PromptEntry) (*PromptEntry, error) { + if err := createPrompt.validate(); err != nil { + return nil, err + } + + // For reset the prompt version because it's not supported in the creating API. + createPrompt.Version = 0 + + body, err := json.Marshal(createPrompt) + if err != nil { + return nil, fmt.Errorf("failed to marshal create prompt request body: %w", err) + } + + url := c.cli.host + promptsPath + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(body)) + if err != nil { + return nil, fmt.Errorf("failed to create create prompt request: %w", err) + } + + req.Header.Add("Content-Type", "application/json") + c.cli.addBaseHeaders(req) + + resp, err := c.cli.cli.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to do create prompt request: %v", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, &apiError{Status: resp.StatusCode, Message: fmt.Sprintf("failed to read create prompt response: %v", err)} + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, &apiError{Status: resp.StatusCode, Message: string(respBody)} + } + + var createdPrompt PromptEntry + if err := json.Unmarshal(respBody, &createdPrompt); err != nil { + return nil, fmt.Errorf("failed to unmarshal create prompt response: %w", err) + } + + return &createdPrompt, nil +} diff --git a/libs/acl/langfuse/prompt_test.go b/libs/acl/langfuse/prompt_test.go new file mode 100644 index 000000000..83b405ec8 --- /dev/null +++ b/libs/acl/langfuse/prompt_test.go @@ -0,0 +1,350 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package langfuse + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestChatMessageWithPlaceHolder_validate(t *testing.T) { + tests := []struct { + name string + message ChatMessageWithPlaceHolder + wantErr bool + }{ + { + name: "valid message", + message: ChatMessageWithPlaceHolder{ + Role: "user", + Type: "text", + Content: "Hello world", + }, + wantErr: false, + }, + { + name: "missing role", + message: ChatMessageWithPlaceHolder{ + Type: "text", + Content: "Hello world", + }, + wantErr: true, + }, + { + name: "missing content", + message: ChatMessageWithPlaceHolder{ + Role: "user", + Type: "text", + }, + wantErr: true, + }, + { + name: "empty role", + message: ChatMessageWithPlaceHolder{ + Role: "", + Type: "text", + Content: "Hello world", + }, + wantErr: true, + }, + { + name: "empty content", + message: ChatMessageWithPlaceHolder{ + Role: "user", + Type: "text", + Content: "", + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.message.validate() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestPromptEntry_validate(t *testing.T) { + tests := []struct { + name string + prompt PromptEntry + wantErr bool + }{ + { + name: "valid text prompt", + prompt: PromptEntry{ + Name: "test-prompt", + Type: "text", + Prompt: "This is a test prompt", + }, + wantErr: false, + }, + { + name: "valid chat prompt", + prompt: PromptEntry{ + Name: "test-chat-prompt", + Type: "chat", + Prompt: []ChatMessageWithPlaceHolder{ + { + Role: "system", + Type: "text", + Content: "You are a helpful assistant", + }, + { + Role: "user", + Type: "text", + Content: "Hello {{name}}", + }, + }, + }, + wantErr: false, + }, + { + name: "missing name", + prompt: PromptEntry{ + Type: "text", + Prompt: "This is a test prompt", + }, + wantErr: true, + }, + { + name: "empty name", + prompt: PromptEntry{ + Name: "", + Type: "text", + Prompt: "This is a test prompt", + }, + wantErr: true, + }, + { + name: "nil prompt", + prompt: PromptEntry{ + Name: "test-prompt", + Type: "text", + Prompt: nil, + }, + wantErr: true, + }, + { + name: "empty string for text type", + prompt: PromptEntry{ + Name: "test-prompt", + Type: "text", + Prompt: "", + }, + wantErr: true, + }, + { + name: "wrong type for text prompt", + prompt: PromptEntry{ + Name: "test-prompt", + Type: "text", + Prompt: []ChatMessageWithPlaceHolder{}, + }, + wantErr: true, + }, + { + name: "empty messages for chat prompt", + prompt: PromptEntry{ + Name: "test-prompt", + Type: "chat", + Prompt: []ChatMessageWithPlaceHolder{}, + }, + wantErr: true, + }, + { + name: "wrong type for chat prompt", + prompt: PromptEntry{ + Name: "test-prompt", + Type: "chat", + Prompt: "This should be messages", + }, + wantErr: true, + }, + { + name: "invalid message in chat prompt", + prompt: PromptEntry{ + Name: "test-prompt", + Type: "chat", + Prompt: []ChatMessageWithPlaceHolder{ + { + Role: "", + Content: "Invalid message", + }, + }, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.prompt.validate() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestListParams_ToQueryString(t *testing.T) { + tests := []struct { + name string + params ListParams + want string + }{ + { + name: "empty params", + params: ListParams{}, + want: "", + }, + { + name: "all params", + params: ListParams{ + Name: "test-prompt", + Label: "production", + Tag: "v1.0", + Page: 1, + Limit: 10, + FromUpdatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + ToUpdatedAt: time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC), + }, + want: "name=test-prompt&label=production&tag=v1.0&page=1&limit=10&fromUpdatedAt=2024-01-01T00:00:00Z&toUpdatedAt=2024-12-31T23:59:59Z", + }, + { + name: "name only", + params: ListParams{ + Name: "test-prompt", + }, + want: "name=test-prompt", + }, + { + name: "pagination only", + params: ListParams{ + Page: 2, + Limit: 20, + }, + want: "page=2&limit=20", + }, + { + name: "time range only", + params: ListParams{ + FromUpdatedAt: time.Date(2024, 6, 1, 12, 0, 0, 0, time.UTC), + ToUpdatedAt: time.Date(2024, 6, 30, 12, 0, 0, 0, time.UTC), + }, + want: "fromUpdatedAt=2024-06-01T12:00:00Z&toUpdatedAt=2024-06-30T12:00:00Z", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.params.ToQueryString() + assert.Equal(t, tt.want, got) + }) + } +} + +func TestPromptEntry_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + want PromptEntry + wantErr bool + }{ + { + name: "text type prompt", + input: `{"name":"test","type":"text","prompt":"Hello world"}`, + want: PromptEntry{ + Name: "test", + Type: "text", + Prompt: "Hello world", + }, + wantErr: false, + }, + { + name: "chat type prompt", + input: `{"name":"test","type":"chat","prompt":[{"role":"user","type":"text","content":"Hello"}]}`, + want: PromptEntry{ + Name: "test", + Type: "chat", + Prompt: []ChatMessageWithPlaceHolder{ + { + Role: "user", + Type: "text", + Content: "Hello", + }, + }, + }, + wantErr: false, + }, + { + name: "prompt with version and tags", + input: `{"name":"test","type":"text","prompt":"Hello","version":1,"tags":["tag1","tag2"],"labels":["label1"]}`, + want: PromptEntry{ + Name: "test", + Type: "text", + Prompt: "Hello", + Version: 1, + Tags: []string{"tag1", "tag2"}, + Labels: []string{"label1"}, + }, + wantErr: false, + }, + { + name: "invalid json", + input: `{"name":"test","type":"text"`, + want: PromptEntry{}, + wantErr: true, + }, + { + name: "invalid prompt for text type", + input: `{"name":"test","type":"text","prompt":123}`, + want: PromptEntry{}, + wantErr: true, + }, + { + name: "invalid prompt for chat type", + input: `{"name":"test","type":"chat","prompt":"should be array"}`, + want: PromptEntry{}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got PromptEntry + err := json.Unmarshal([]byte(tt.input), &got) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + } + }) + } +} From e34acb1a89145407b9fa9330cb47dfc683de1c9a Mon Sep 17 00:00:00 2001 From: git-hulk Date: Tue, 16 Sep 2025 20:10:58 +0800 Subject: [PATCH 2/2] Fix test error --- libs/acl/langfuse/langfuse.go | 10 +++++----- libs/acl/langfuse/mock/langfuse_mock.go | 14 ++++++++++++++ libs/acl/langfuse/prompt.go | 10 +++++----- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/libs/acl/langfuse/langfuse.go b/libs/acl/langfuse/langfuse.go index 86db67589..1b6d50d2d 100644 --- a/libs/acl/langfuse/langfuse.go +++ b/libs/acl/langfuse/langfuse.go @@ -38,7 +38,7 @@ type Langfuse interface { EndGeneration(body *GenerationEventBody) error CreateEvent(body *EventEventBody) (string, error) Flush() - Prompt() *promptClient + Prompt() *PromptClient } // NewLangfuse creates a Langfuse client instance @@ -88,13 +88,13 @@ func NewLangfuse( o.maxRetry, ) langfuseCli := newClient(httpCli, host, publicKey, secretKey, sdkVersion) - promptCli := &promptClient{cli: langfuseCli} + promptCli := &PromptClient{cli: langfuseCli} return &langfuseIns{tm: tm, promptCli: promptCli} } type langfuseIns struct { tm *taskManager - promptCli *promptClient + promptCli *PromptClient } // CreateTrace creates a new trace in Langfuse @@ -222,7 +222,7 @@ func (l *langfuseIns) Flush() { } // Prompt provides access to the prompt client for managing prompts. -// Use the returned promptClient to create, retrieve and list prompts. -func (l *langfuseIns) Prompt() *promptClient { +// Use the returned PromptClient to create, retrieve and list prompts. +func (l *langfuseIns) Prompt() *PromptClient { return l.promptCli } diff --git a/libs/acl/langfuse/mock/langfuse_mock.go b/libs/acl/langfuse/mock/langfuse_mock.go index da76075aa..d042210d4 100644 --- a/libs/acl/langfuse/mock/langfuse_mock.go +++ b/libs/acl/langfuse/mock/langfuse_mock.go @@ -149,3 +149,17 @@ func (mr *MockLangfuseMockRecorder) Flush() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Flush", reflect.TypeOf((*MockLangfuse)(nil).Flush)) } + +// Prompt mocks base method. +func (m *MockLangfuse) Prompt() *langfuse.PromptClient { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Prompt") + ret0, _ := ret[0].(*langfuse.PromptClient) + return ret0 +} + +// Prompt indicates an expected call of Prompt. +func (mr *MockLangfuseMockRecorder) Prompt() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Prompt", reflect.TypeOf((*MockLangfuse)(nil).Prompt)) +} diff --git a/libs/acl/langfuse/prompt.go b/libs/acl/langfuse/prompt.go index 01c0d5eed..725133dd9 100644 --- a/libs/acl/langfuse/prompt.go +++ b/libs/acl/langfuse/prompt.go @@ -199,16 +199,16 @@ type ListPrompts struct { Data []PromptMeta `json:"data"` } -// promptClient provides methods for interacting with the Langfuse prompts API. +// PromptClient provides methods for interacting with the Langfuse prompts API. // // The client handles HTTP communication with the Langfuse API for prompt management // operations including creating, retrieving, and listing prompt templates. -type promptClient struct { +type PromptClient struct { cli *client } // GetPrompt retrieves a specific prompt by name, version, and label. -func (c *promptClient) GetPrompt(ctx context.Context, params GetParams) (*PromptEntry, error) { +func (c *PromptClient) GetPrompt(ctx context.Context, params GetParams) (*PromptEntry, error) { if params.Name == "" { return nil, errors.New("'name' is required") } @@ -258,7 +258,7 @@ func (c *promptClient) GetPrompt(ctx context.Context, params GetParams) (*Prompt } // ListPrompt retrieves a list of prompts based on the provided parameters. -func (c promptClient) ListPrompt(ctx context.Context, params ListParams) (*ListPrompts, error) { +func (c PromptClient) ListPrompt(ctx context.Context, params ListParams) (*ListPrompts, error) { url := c.cli.host + promptsPath queryString := params.ToQueryString() @@ -297,7 +297,7 @@ func (c promptClient) ListPrompt(ctx context.Context, params ListParams) (*ListP } // CreatePrompt creates a new prompt. -func (c *promptClient) CreatePrompt(ctx context.Context, createPrompt *PromptEntry) (*PromptEntry, error) { +func (c *PromptClient) CreatePrompt(ctx context.Context, createPrompt *PromptEntry) (*PromptEntry, error) { if err := createPrompt.validate(); err != nil { return nil, err }