Skip to content

Commit f5e6f06

Browse files
authored
feat: add prompt version API with caching (#16)
# Add Prompt Version API Support with Caching This PR adds support for retrieving prompt versions from the Maxim API with an efficient caching mechanism. The implementation includes: - New `GetPromptVersion` method to fetch prompt configurations with their associated messages and parameters - In-memory caching using a thread-safe map to improve performance for repeated requests - Comprehensive data structures to represent prompt versions, messages, and model parameters - Support for both string and structured content formats in prompt messages - Extensive test coverage including integration tests for API interaction and cache verification The PR also refactors the existing code by: - Moving the `MaximError` type to a new `utils.go` file - Renaming `maximApis.go` to `logging.go` to better reflect its purpose This enhancement allows developers to retrieve prompt configurations programmatically, enabling dynamic prompt management in applications.
2 parents f8c96db + 92ffade commit f5e6f06

File tree

6 files changed

+541
-8
lines changed

6 files changed

+541
-8
lines changed

apis/maximApis.go renamed to apis/logging.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,12 @@ import (
77
"strings"
88
)
99

10-
type MaximError struct {
11-
Message string `json:"message"`
12-
}
13-
1410
// MaximApiResponse represents the structure of the response from the Maxim API.
1511
// It contains an optional Error field which, if present, includes a message describing the error.
1612
type MaximApiResponse struct {
1713
Error *MaximError `json:"error,omitempty"`
1814
}
1915

20-
func newMaximError(err error) *MaximError {
21-
return &MaximError{Message: err.Error()}
22-
}
23-
2416
// PushLogs sends logs to the specified repository.
2517
//
2618
// Parameters:

apis/prompt.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package apis
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"time"
8+
)
9+
10+
// PromptVersionResponse is the wrapper for the API response
11+
type PromptVersionResponse struct {
12+
Data PromptVersion `json:"data"`
13+
}
14+
15+
// PromptVersion represents a version of a prompt with its configuration
16+
type PromptVersion struct {
17+
ID string `json:"id"`
18+
Version int `json:"version"`
19+
Description string `json:"description"`
20+
PromptID string `json:"promptId"`
21+
Config PromptConfig `json:"config"`
22+
CreatedAt string `json:"createdAt"`
23+
UpdatedAt string `json:"updatedAt"`
24+
DeletedAt string `json:"deletedAt,omitempty"`
25+
}
26+
27+
// PromptConfig contains the configuration settings for a prompt version
28+
type PromptConfig struct {
29+
Tags map[string]interface{} `json:"tags"`
30+
Model string `json:"model"`
31+
Author Author `json:"author"`
32+
ModelID string `json:"modelId"`
33+
Messages []Message `json:"messages"`
34+
Provider string `json:"provider"`
35+
ModelParameters ModelParameters `json:"modelParameters"`
36+
}
37+
38+
// Author represents the author information
39+
type Author struct {
40+
ID string `json:"id"`
41+
Name string `json:"name"`
42+
Email string `json:"email"`
43+
Image string `json:"image"`
44+
}
45+
46+
// Message represents a message in the prompt
47+
type Message struct {
48+
ID string `json:"id"`
49+
Index int `json:"index"`
50+
Payload MessagePayload `json:"payload"`
51+
CurrentType string `json:"currentType"`
52+
OriginalType string `json:"originalType"`
53+
}
54+
55+
// MessagePayload contains the role and content of a message
56+
type MessagePayload struct {
57+
Role string `json:"role"`
58+
Content MessagePayloadContent `json:"content"`
59+
}
60+
61+
type MessagePayloadContent struct {
62+
MessagePayloadContentStr *string
63+
MessagePayloadContentArray []MessagePayloadContentBlock
64+
}
65+
66+
type MessagePayloadContentBlock struct {
67+
Type string `json:"type"`
68+
Text string `json:"text"`
69+
}
70+
71+
func (m *MessagePayloadContent) UnmarshalJSON(data []byte) error {
72+
var messageStr string
73+
if err := json.Unmarshal(data, &messageStr); err == nil {
74+
m.MessagePayloadContentStr = &messageStr
75+
return nil
76+
}
77+
var messageArray []MessagePayloadContentBlock
78+
if err := json.Unmarshal(data, &messageArray); err == nil {
79+
m.MessagePayloadContentArray = messageArray
80+
return nil
81+
}
82+
return fmt.Errorf("failed to unmarshal MessagePayloadContent")
83+
}
84+
85+
// ModelParameters contains the model configuration parameters
86+
type ModelParameters struct {
87+
N int `json:"n"`
88+
TopP float64 `json:"top_p"`
89+
Logprobs bool `json:"logprobs"`
90+
MaxTokens int `json:"max_tokens"`
91+
PromptTools []string `json:"promptTools"`
92+
Temperature float64 `json:"temperature"`
93+
PresencePenalty float64 `json:"presence_penalty"`
94+
FrequencyPenalty float64 `json:"frequency_penalty"`
95+
}
96+
97+
// GetPromptVersion fetches a specific version of a prompt.
98+
//
99+
// Parameters:
100+
// - baseUrl: The base URL of the API endpoint.
101+
// - apiKey: The API key for authentication.
102+
// - versionId: The version ID you want to query.
103+
// - promptId: The prompt ID whose versions you want to query.
104+
//
105+
// Returns:
106+
// - *PromptVersion: The prompt version if successful, or nil with an error.
107+
// - *MaximError: An error if the request fails.
108+
func GetPromptVersion(baseUrl, apiKey, versionId, promptId string) (*PromptVersion, *MaximError) {
109+
url := fmt.Sprintf("%s/api/public/v1/prompts/versions?id=%s&promptId=%s", baseUrl, versionId, promptId)
110+
client := &http.Client{
111+
Timeout: 15 * time.Second,
112+
}
113+
req, err := http.NewRequest("GET", url, nil)
114+
if err != nil {
115+
return nil, newMaximError(err)
116+
}
117+
req.Header.Set("Content-Type", "application/json")
118+
req.Header.Set("Accept", "application/json")
119+
req.Header.Set("x-maxim-api-key", apiKey)
120+
resp, err := client.Do(req)
121+
if err != nil {
122+
return nil, newMaximError(err)
123+
}
124+
defer resp.Body.Close()
125+
if resp.StatusCode != http.StatusOK {
126+
return nil, newMaximError(fmt.Errorf("unexpected status code: %d", resp.StatusCode))
127+
}
128+
129+
var response PromptVersionResponse
130+
err = json.NewDecoder(resp.Body).Decode(&response)
131+
if err != nil {
132+
return nil, newMaximError(err)
133+
}
134+
135+
return &response.Data, nil
136+
}

apis/utils.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package apis
2+
3+
type MaximError struct {
4+
Message string `json:"message"`
5+
}
6+
7+
func newMaximError(err error) *MaximError {
8+
return &MaximError{Message: err.Error()}
9+
}

maxim.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/maximhq/maxim-go/apis"
99
"github.com/maximhq/maxim-go/logging"
10+
"github.com/maximhq/maxim-go/prompt"
1011
)
1112

1213
type MaximSDKConfig struct {
@@ -96,6 +97,29 @@ func (m *Maxim) GetLogger(c *logging.LoggerConfig) (*logging.Logger, error) {
9697
return m.loggers[c.Id], nil
9798
}
9899

100+
// GetPromptVersion fetches a specific version of a prompt.
101+
//
102+
// Parameters:
103+
// - versionId: The version ID you want to query.
104+
// - promptId: The prompt ID whose versions you want to query.
105+
//
106+
// Returns:
107+
// - *apis.PromptVersion: The prompt version if successful, or nil with an error.
108+
// - error: An error if the request fails.
109+
//
110+
// Example usage:
111+
//
112+
// client := maxim.Init(&maxim.MaximSDKConfig{
113+
// ApiKey: "your-api-key",
114+
// })
115+
// promptVersion, err := client.GetPromptVersion("version-id", "prompt-id")
116+
// if err != nil {
117+
// // handle error
118+
// }
119+
func (m *Maxim) GetPromptVersion(versionId, promptId string) (*apis.PromptVersion, error) {
120+
return prompt.GetPromptVersion(m.baseUrl, m.apiKey, versionId, promptId)
121+
}
122+
99123
// Cleanup Maxim SDK state and flushes all logs in all the loggers.
100124
// It should be called when the application is shutting down to ensure all logs are sent to the server.
101125
//

prompt/promptversion.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package prompt
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/hex"
6+
"fmt"
7+
"sync"
8+
9+
"github.com/maximhq/maxim-go/apis"
10+
)
11+
12+
var (
13+
promptVersionCache sync.Map = sync.Map{} // key: sha256 of "baseUrl:apiKey:versionId:promptId", value: *apis.PromptVersion
14+
)
15+
16+
// GetPromptVersion fetches a specific version of a prompt with caching.
17+
//
18+
// Parameters:
19+
// - baseUrl: The base URL of the API endpoint.
20+
// - apiKey: The API key for authentication.
21+
// - versionId: The version ID you want to query.
22+
// - promptId: The prompt ID whose versions you want to query.
23+
//
24+
// Returns:
25+
// - *apis.PromptVersion: The prompt version if successful, or nil with an error.
26+
// - error: An error if the request fails.
27+
func GetPromptVersion(baseUrl, apiKey, versionId, promptId string) (*apis.PromptVersion, error) {
28+
// Create cache key from both IDs
29+
hashInput := fmt.Sprintf("%q|%q|%q|%q", baseUrl, apiKey, versionId, promptId)
30+
hash := sha256.Sum256([]byte(hashInput))
31+
cacheKey := hex.EncodeToString(hash[:])
32+
33+
// Check cache first
34+
if cached, ok := promptVersionCache.Load(cacheKey); ok {
35+
return cached.(*apis.PromptVersion), nil
36+
}
37+
38+
// Fetch from API if not cached
39+
resp, err := apis.GetPromptVersion(baseUrl, apiKey, versionId, promptId)
40+
if err != nil {
41+
return nil, fmt.Errorf("failed to get prompt version: %s", err.Message)
42+
}
43+
44+
// Store in cache
45+
promptVersionCache.Store(cacheKey, resp)
46+
return resp, nil
47+
}

0 commit comments

Comments
 (0)