feat: add Go client SDK with ogen code generation (#375)
Add a Go client for the Hindsight API using ogen for strongly-typed code generation from the OpenAPI 3.1 spec. The client provides a high-level wrapper with functional options around the generated code, covering all core operations (retain, recall, reflect, bank management). Includes: - ogen-based code generation with OpenAPI 3.1 spec preprocessing - High-level Client wrapper with idiomatic Go API - Functional options for all operations (WithBudget, WithTags, etc.) - OgenClient() escape hatch for advanced operations - Integration tests and godoc examples - Go SDK reference docs and cookbook entries (quickstart, concurrent pipeline, memory-augmented API service) - Updated generate-clients.sh with Go generation step Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b4b5c44a87
commit
2a47389f2c
42 changed files with 31851 additions and 5 deletions
178
hindsight-clients/go/README.md
Normal file
178
hindsight-clients/go/README.md
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
# Hindsight Go Client
|
||||
|
||||
Go client for the [Hindsight](https://github.com/vectorize-io/hindsight) agent memory API.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/vectorize-io/hindsight-client-go
|
||||
```
|
||||
|
||||
Requires Go 1.25+.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := hindsight.New("http://localhost:8888")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Store a memory
|
||||
_, err = client.Retain(ctx, "my-bank", "The user prefers dark mode")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Recall memories
|
||||
resp, err := client.Recall(ctx, "my-bank", "What are the user's preferences?")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, r := range resp.Results {
|
||||
fmt.Println(r.Text)
|
||||
}
|
||||
|
||||
// Reflect with reasoning
|
||||
ref, err := client.Reflect(ctx, "my-bank", "Summarize what you know about the user")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(ref.Text)
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
```go
|
||||
client, err := hindsight.New("http://localhost:8888", hindsight.WithAPIKey("your-key"))
|
||||
```
|
||||
|
||||
## Core Operations
|
||||
|
||||
### Retain (Store Memories)
|
||||
|
||||
```go
|
||||
// Single memory
|
||||
_, err := client.Retain(ctx, "bank-id", "Alice loves Python",
|
||||
hindsight.WithContext("programming discussion"),
|
||||
hindsight.WithTags([]string{"tech"}),
|
||||
hindsight.WithDocumentID("conv-123"),
|
||||
)
|
||||
|
||||
// Batch
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "First memory"},
|
||||
{Content: "Second memory"},
|
||||
}
|
||||
_, err := client.RetainBatch(ctx, "bank-id", items,
|
||||
hindsight.WithDocumentTags([]string{"import"}),
|
||||
hindsight.WithAsync(true),
|
||||
)
|
||||
```
|
||||
|
||||
### Recall (Retrieve Memories)
|
||||
|
||||
```go
|
||||
resp, err := client.Recall(ctx, "bank-id", "What does Alice like?",
|
||||
hindsight.WithBudget(hindsight.BudgetHigh),
|
||||
hindsight.WithMaxTokens(4096),
|
||||
hindsight.WithTypes([]string{"world", "experience"}),
|
||||
hindsight.WithTrace(true),
|
||||
hindsight.WithRecallTags([]string{"tech"}),
|
||||
)
|
||||
|
||||
for _, r := range resp.Results {
|
||||
fmt.Printf("[%s] %s\n", r.Type.Or("unknown"), r.Text)
|
||||
}
|
||||
```
|
||||
|
||||
### Reflect (Reason with Memories)
|
||||
|
||||
```go
|
||||
resp, err := client.Reflect(ctx, "bank-id", "What are the user's interests?",
|
||||
hindsight.WithReflectBudget(hindsight.BudgetMid),
|
||||
hindsight.WithReflectMaxTokens(2048),
|
||||
hindsight.WithResponseSchema(map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"interests": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
fmt.Println(resp.Text)
|
||||
```
|
||||
|
||||
### Bank Management
|
||||
|
||||
```go
|
||||
// Create bank with personality
|
||||
_, err := client.CreateBank(ctx, "my-bank",
|
||||
hindsight.WithBankName("My Agent"),
|
||||
hindsight.WithMission("Help users with coding tasks"),
|
||||
hindsight.WithDisposition(hindsight.DispositionTraits{
|
||||
Skepticism: 3,
|
||||
Literalism: 2,
|
||||
Empathy: 4,
|
||||
}),
|
||||
)
|
||||
|
||||
// Update mission
|
||||
_, err = client.SetMission(ctx, "my-bank", "New mission statement")
|
||||
|
||||
// List all banks
|
||||
banks, err := client.ListBanks(ctx)
|
||||
|
||||
// Delete bank
|
||||
err = client.DeleteBank(ctx, "my-bank")
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
For operations not covered by the high-level wrapper (documents, entities, operations, mental models, directives), access the ogen-generated client directly:
|
||||
|
||||
```go
|
||||
ogen := client.OgenClient()
|
||||
|
||||
// List entities
|
||||
resp, err := ogen.ListEntities(ctx, ogenapi.ListEntitiesParams{
|
||||
BankID: "my-bank",
|
||||
})
|
||||
|
||||
// Create mental model
|
||||
resp, err := ogen.CreateMentalModel(ctx, &ogenapi.CreateMentalModelRequest{
|
||||
Name: "user-preferences",
|
||||
SourceQuery: "What are the user's preferences?",
|
||||
}, ogenapi.CreateMentalModelParams{BankID: "my-bank"})
|
||||
```
|
||||
|
||||
## Code Generation
|
||||
|
||||
The client is built on [ogen](https://github.com/ogen-go/ogen), generating strongly-typed Go code from the OpenAPI 3.1 spec. To regenerate after API changes:
|
||||
|
||||
```bash
|
||||
cd hindsight-clients/go
|
||||
go generate ./...
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
Integration tests require a running Hindsight API server:
|
||||
|
||||
```bash
|
||||
HINDSIGHT_API_URL=http://localhost:8888 go test -v -tags=integration ./...
|
||||
```
|
||||
118
hindsight-clients/go/banks.go
Normal file
118
hindsight-clients/go/banks.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package hindsight
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
|
||||
)
|
||||
|
||||
// CreateBank creates a new memory bank or updates an existing one.
|
||||
func (c *Client) CreateBank(ctx context.Context, bankID string, opts ...CreateBankOption) (*BankProfileResponse, error) {
|
||||
var cfg createBankConfig
|
||||
for _, o := range opts {
|
||||
o(&cfg)
|
||||
}
|
||||
|
||||
req := &ogenapi.CreateBankRequest{}
|
||||
if cfg.name != nil {
|
||||
req.Name = ogenapi.NewOptString(*cfg.name)
|
||||
}
|
||||
if cfg.mission != nil {
|
||||
req.Mission = ogenapi.NewOptString(*cfg.mission)
|
||||
}
|
||||
if cfg.disposition != nil {
|
||||
req.Disposition = ogenapi.NewOptDispositionTraits(*cfg.disposition)
|
||||
}
|
||||
|
||||
res, err := c.api.CreateOrUpdateBank(ctx, req, ogenapi.CreateOrUpdateBankParams{
|
||||
BankID: bankID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, ok := res.(*ogenapi.BankProfileResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetBankProfile retrieves the profile for a memory bank.
|
||||
func (c *Client) GetBankProfile(ctx context.Context, bankID string) (*BankProfileResponse, error) {
|
||||
res, err := c.api.GetBankProfile(ctx, ogenapi.GetBankProfileParams{
|
||||
BankID: bankID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, ok := res.(*ogenapi.BankProfileResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListBanks returns all memory banks.
|
||||
func (c *Client) ListBanks(ctx context.Context) (*BankListResponse, error) {
|
||||
res, err := c.api.ListBanks(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, ok := res.(*ogenapi.BankListResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeleteBank permanently deletes a memory bank and all its data.
|
||||
func (c *Client) DeleteBank(ctx context.Context, bankID string) error {
|
||||
_, err := c.api.DeleteBank(ctx, ogenapi.DeleteBankParams{
|
||||
BankID: bankID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// SetMission updates the mission for a memory bank.
|
||||
func (c *Client) SetMission(ctx context.Context, bankID, mission string) (*BankProfileResponse, error) {
|
||||
req := &ogenapi.CreateBankRequest{
|
||||
Mission: ogenapi.NewOptString(mission),
|
||||
}
|
||||
|
||||
res, err := c.api.CreateOrUpdateBank(ctx, req, ogenapi.CreateOrUpdateBankParams{
|
||||
BankID: bankID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, ok := res.(*ogenapi.BankProfileResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// UpdateDisposition updates the personality traits for a memory bank.
|
||||
func (c *Client) UpdateDisposition(ctx context.Context, bankID string, traits DispositionTraits) (*BankProfileResponse, error) {
|
||||
req := &ogenapi.UpdateDispositionRequest{
|
||||
Disposition: traits,
|
||||
}
|
||||
|
||||
res, err := c.api.UpdateBankDisposition(ctx, req, ogenapi.UpdateBankDispositionParams{
|
||||
BankID: bankID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, ok := res.(*ogenapi.BankProfileResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
40
hindsight-clients/go/doc.go
Normal file
40
hindsight-clients/go/doc.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Package hindsight provides a Go client for the Hindsight agent memory API.
|
||||
//
|
||||
// Hindsight is a long-term memory system for AI agents. This client wraps the
|
||||
// auto-generated ogen API client with a simpler, Go-idiomatic interface for the
|
||||
// core operations: retain (store), recall (retrieve), and reflect (reason).
|
||||
//
|
||||
// # Quick Start
|
||||
//
|
||||
// client, err := hindsight.New("http://localhost:8888")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// // Store a memory
|
||||
// _, err = client.Retain(ctx, "my-bank", "The user prefers dark mode")
|
||||
//
|
||||
// // Recall memories
|
||||
// resp, err := client.Recall(ctx, "my-bank", "What are the user's preferences?")
|
||||
// for _, r := range resp.Results {
|
||||
// fmt.Println(r.Text)
|
||||
// }
|
||||
//
|
||||
// // Reflect with reasoning
|
||||
// ref, err := client.Reflect(ctx, "my-bank", "Summarize what you know about the user")
|
||||
// fmt.Println(ref.Text)
|
||||
//
|
||||
// # Authentication
|
||||
//
|
||||
// For authenticated deployments, pass an API key:
|
||||
//
|
||||
// client, err := hindsight.New("http://localhost:8888", hindsight.WithAPIKey("your-key"))
|
||||
//
|
||||
// # Advanced Usage
|
||||
//
|
||||
// For operations not covered by the high-level wrapper (documents, entities,
|
||||
// operations, mental models, directives), access the ogen-generated client:
|
||||
//
|
||||
// ogen := client.OgenClient()
|
||||
// resp, err := ogen.ListEntities(ctx, ogenapi.ListEntitiesParams{BankID: "my-bank"})
|
||||
package hindsight
|
||||
124
hindsight-clients/go/example_test.go
Normal file
124
hindsight-clients/go/example_test.go
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
package hindsight_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
client, err := hindsight.New("http://localhost:8888")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Store a memory
|
||||
_, err = client.Retain(ctx, "my-bank", "The user prefers dark mode")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Recall memories
|
||||
resp, err := client.Recall(ctx, "my-bank", "What are the user's preferences?")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, r := range resp.Results {
|
||||
fmt.Println(r.Text)
|
||||
}
|
||||
|
||||
// Reflect with reasoning
|
||||
ref, err := client.Reflect(ctx, "my-bank", "Summarize what you know about the user")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(ref.Text)
|
||||
}
|
||||
|
||||
func ExampleNew_withAPIKey() {
|
||||
_, err := hindsight.New(
|
||||
"http://localhost:8888",
|
||||
hindsight.WithAPIKey("your-api-key"),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_Retain() {
|
||||
client, _ := hindsight.New("http://localhost:8888")
|
||||
ctx := context.Background()
|
||||
|
||||
// Simple retain
|
||||
_, _ = client.Retain(ctx, "my-bank", "Alice loves Python programming")
|
||||
|
||||
// Retain with options
|
||||
_, _ = client.Retain(ctx, "my-bank", "Bob went hiking",
|
||||
hindsight.WithContext("outdoor activities"),
|
||||
hindsight.WithTags([]string{"hobbies"}),
|
||||
hindsight.WithDocumentID("conversation-123"),
|
||||
)
|
||||
}
|
||||
|
||||
func ExampleClient_RetainBatch() {
|
||||
client, _ := hindsight.New("http://localhost:8888")
|
||||
ctx := context.Background()
|
||||
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "Alice completed the project"},
|
||||
{Content: "Bob started learning Go"},
|
||||
{Content: "Charlie presented at the conference"},
|
||||
}
|
||||
|
||||
_, _ = client.RetainBatch(ctx, "my-bank", items,
|
||||
hindsight.WithDocumentTags([]string{"team-updates"}),
|
||||
)
|
||||
}
|
||||
|
||||
func ExampleClient_Recall() {
|
||||
client, _ := hindsight.New("http://localhost:8888")
|
||||
ctx := context.Background()
|
||||
|
||||
resp, _ := client.Recall(ctx, "my-bank", "What does Alice like?",
|
||||
hindsight.WithBudget(hindsight.BudgetHigh),
|
||||
hindsight.WithMaxTokens(4096),
|
||||
hindsight.WithTypes([]string{"world", "experience"}),
|
||||
hindsight.WithTrace(true),
|
||||
)
|
||||
|
||||
for _, r := range resp.Results {
|
||||
fmt.Printf("[%s] %s\n", r.Type.Or("unknown"), r.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_Reflect() {
|
||||
client, _ := hindsight.New("http://localhost:8888")
|
||||
ctx := context.Background()
|
||||
|
||||
resp, _ := client.Reflect(ctx, "my-bank",
|
||||
"What are the user's professional interests?",
|
||||
hindsight.WithReflectBudget(hindsight.BudgetMid),
|
||||
hindsight.WithReflectMaxTokens(2048),
|
||||
)
|
||||
|
||||
fmt.Println(resp.Text)
|
||||
}
|
||||
|
||||
func ExampleClient_CreateBank() {
|
||||
client, _ := hindsight.New("http://localhost:8888")
|
||||
ctx := context.Background()
|
||||
|
||||
_, _ = client.CreateBank(ctx, "my-bank",
|
||||
hindsight.WithBankName("My Agent"),
|
||||
hindsight.WithMission("Help users with programming tasks"),
|
||||
hindsight.WithDisposition(hindsight.DispositionTraits{
|
||||
Skepticism: 3,
|
||||
Literalism: 2,
|
||||
Empathy: 4,
|
||||
}),
|
||||
)
|
||||
}
|
||||
4
hindsight-clients/go/generate.go
Normal file
4
hindsight-clients/go/generate.go
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
package hindsight
|
||||
|
||||
//go:generate go run ./internal/cmd/preprocess ../../hindsight-docs/static/openapi.json internal/ogenapi/openapi.json
|
||||
//go:generate go run github.com/ogen-go/ogen/cmd/ogen --target internal/ogenapi -package ogenapi --clean --config ogen.yml internal/ogenapi/openapi.json
|
||||
29
hindsight-clients/go/go.mod
Normal file
29
hindsight-clients/go/go.mod
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
module github.com/vectorize-io/hindsight-client-go
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/go-faster/errors v0.7.1
|
||||
github.com/go-faster/jx v1.2.0
|
||||
github.com/ogen-go/ogen v1.19.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-faster/yaml v0.4.6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/segmentio/asm v1.2.1 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
60
hindsight-clients/go/go.sum
Normal file
60
hindsight-clients/go/go.sum
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
||||
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
|
||||
github.com/go-faster/jx v1.2.0 h1:T2YHJPrFaYu21fJtUxC9GzmluKu8rVIFDwwGBKTDseI=
|
||||
github.com/go-faster/jx v1.2.0/go.mod h1:UWLOVDmMG597a5tBFPLIWJdUxz5/2emOpfsj9Neg0PE=
|
||||
github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I=
|
||||
github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/ogen-go/ogen v1.19.0 h1:YvdNpeQJ8A8dLLpS6Vs4WxXL53BT6tBPxH0VSjfALhA=
|
||||
github.com/ogen-go/ogen v1.19.0/go.mod h1:DeShwO+TEpLYXNCuZliSAedphphXsJaTGGbmSomWUjE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
|
||||
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
66
hindsight-clients/go/hindsight.go
Normal file
66
hindsight-clients/go/hindsight.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package hindsight
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
|
||||
)
|
||||
|
||||
// Client is the high-level Hindsight API client. It wraps the ogen-generated
|
||||
// client with convenience methods for core operations.
|
||||
type Client struct {
|
||||
api *ogenapi.Client
|
||||
}
|
||||
|
||||
// New creates a new Hindsight client for the given base URL.
|
||||
//
|
||||
// By default, no authentication is configured. Use [WithAPIKey] to set a
|
||||
// Bearer token, or [WithHTTPClient] for full control over the HTTP transport.
|
||||
func New(baseURL string, opts ...Option) (*Client, error) {
|
||||
cfg := clientConfig{}
|
||||
for _, o := range opts {
|
||||
o(&cfg)
|
||||
}
|
||||
|
||||
var httpClient http.Client
|
||||
if cfg.httpClient != nil {
|
||||
httpClient = *cfg.httpClient
|
||||
}
|
||||
|
||||
if cfg.apiKey != "" {
|
||||
base := httpClient.Transport
|
||||
if base == nil {
|
||||
base = http.DefaultTransport
|
||||
}
|
||||
httpClient.Transport = &authTransport{
|
||||
base: base,
|
||||
token: cfg.apiKey,
|
||||
}
|
||||
}
|
||||
|
||||
api, err := ogenapi.NewClient(baseURL, ogenapi.WithClient(&httpClient))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{api: api}, nil
|
||||
}
|
||||
|
||||
// OgenClient returns the underlying ogen-generated client for advanced
|
||||
// operations not covered by the high-level wrapper (documents, entities,
|
||||
// operations, mental models, directives).
|
||||
func (c *Client) OgenClient() *ogenapi.Client {
|
||||
return c.api
|
||||
}
|
||||
|
||||
// authTransport injects a Bearer token into every request.
|
||||
type authTransport struct {
|
||||
base http.RoundTripper
|
||||
token string
|
||||
}
|
||||
|
||||
func (t *authTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
r = r.Clone(r.Context())
|
||||
r.Header.Set("Authorization", "Bearer "+t.token)
|
||||
return t.base.RoundTrip(r)
|
||||
}
|
||||
399
hindsight-clients/go/integration_test.go
Normal file
399
hindsight-clients/go/integration_test.go
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
//go:build integration
|
||||
|
||||
package hindsight_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
func apiURL(t *testing.T) string {
|
||||
t.Helper()
|
||||
u := os.Getenv("HINDSIGHT_API_URL")
|
||||
if u == "" {
|
||||
u = "http://localhost:8888"
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func newClient(t *testing.T) *hindsight.Client {
|
||||
t.Helper()
|
||||
c, err := hindsight.New(apiURL(t))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func uniqueBank(t *testing.T) string {
|
||||
t.Helper()
|
||||
return fmt.Sprintf("go_test_%d", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// --- Retain tests ---
|
||||
|
||||
func TestRetainSingle(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
|
||||
resp, err := c.Retain(ctx, uniqueBank(t), "Alice loves artificial intelligence and machine learning")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !resp.Success {
|
||||
t.Error("expected success=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetainWithContext(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
|
||||
resp, err := c.Retain(ctx, uniqueBank(t), "Bob went hiking in the mountains",
|
||||
hindsight.WithTimestamp(time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC)),
|
||||
hindsight.WithContext("outdoor activities"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !resp.Success {
|
||||
t.Error("expected success=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetainBatch(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "Charlie enjoys reading science fiction books"},
|
||||
{Content: "Diana is learning to play the guitar"},
|
||||
{Content: "Eve completed a marathon last month"},
|
||||
}
|
||||
|
||||
resp, err := c.RetainBatch(ctx, uniqueBank(t), items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !resp.Success {
|
||||
t.Error("expected success=true")
|
||||
}
|
||||
if resp.ItemsCount != 3 {
|
||||
t.Errorf("expected items_count=3, got %d", resp.ItemsCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetainWithTags(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
|
||||
resp, err := c.Retain(ctx, uniqueBank(t), "New feature implementation for project Z",
|
||||
hindsight.WithTags([]string{"project_z", "features"}),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !resp.Success {
|
||||
t.Error("expected success=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetainBatchWithDocumentTags(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "First item in batch"},
|
||||
{Content: "Second item in batch"},
|
||||
}
|
||||
|
||||
resp, err := c.RetainBatch(ctx, uniqueBank(t), items,
|
||||
hindsight.WithDocumentTags([]string{"batch_import", "test_data"}),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !resp.Success {
|
||||
t.Error("expected success=true")
|
||||
}
|
||||
if resp.ItemsCount != 2 {
|
||||
t.Errorf("expected items_count=2, got %d", resp.ItemsCount)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Recall tests ---
|
||||
|
||||
func setupRecallBank(t *testing.T, c *hindsight.Client, bankID string) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "Alice loves programming in Python"},
|
||||
{Content: "Bob enjoys hiking and outdoor adventures"},
|
||||
{Content: "Charlie is interested in quantum physics"},
|
||||
{Content: "Diana plays the violin beautifully"},
|
||||
}
|
||||
|
||||
_, err := c.RetainBatch(ctx, bankID, items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecallBasic(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
bankID := uniqueBank(t)
|
||||
setupRecallBank(t, c, bankID)
|
||||
|
||||
resp, err := c.Recall(ctx, bankID, "What does Alice like?")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resp.Results) == 0 {
|
||||
t.Error("expected at least one result")
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, r := range resp.Results {
|
||||
if contains(r.Text, "Alice") || contains(r.Text, "Python") || contains(r.Text, "programming") {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("expected a result mentioning Alice or Python")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecallWithMaxTokens(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
bankID := uniqueBank(t)
|
||||
setupRecallBank(t, c, bankID)
|
||||
|
||||
resp, err := c.Recall(ctx, bankID, "outdoor activities",
|
||||
hindsight.WithMaxTokens(1024),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Results == nil {
|
||||
t.Error("expected results, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecallFullFeatured(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
bankID := uniqueBank(t)
|
||||
setupRecallBank(t, c, bankID)
|
||||
|
||||
resp, err := c.Recall(ctx, bankID, "What are people's hobbies?",
|
||||
hindsight.WithTypes([]string{"world"}),
|
||||
hindsight.WithMaxTokens(2048),
|
||||
hindsight.WithTrace(true),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Results == nil {
|
||||
t.Error("expected results, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// --- Reflect tests ---
|
||||
|
||||
func setupReflectBank(t *testing.T, c *hindsight.Client, bankID string) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := c.CreateBank(ctx, bankID,
|
||||
hindsight.WithMission("I am a helpful AI assistant interested in technology and science."),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "The Python programming language is great for data science"},
|
||||
{Content: "Machine learning models can recognize patterns in data"},
|
||||
{Content: "Neural networks are inspired by biological neurons"},
|
||||
}
|
||||
|
||||
_, err = c.RetainBatch(ctx, bankID, items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReflectBasic(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
bankID := uniqueBank(t)
|
||||
setupReflectBank(t, c, bankID)
|
||||
|
||||
resp, err := c.Reflect(ctx, bankID, "What do you think about artificial intelligence?")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Text == "" {
|
||||
t.Error("expected non-empty response text")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReflectWithMaxTokens(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
bankID := uniqueBank(t)
|
||||
setupReflectBank(t, c, bankID)
|
||||
|
||||
resp, err := c.Reflect(ctx, bankID, "What do you think about Python?",
|
||||
hindsight.WithReflectMaxTokens(500),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Text == "" {
|
||||
t.Error("expected non-empty response text")
|
||||
}
|
||||
}
|
||||
|
||||
// --- Bank tests ---
|
||||
|
||||
func TestCreateBank(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
|
||||
bankID := uniqueBank(t)
|
||||
resp, err := c.CreateBank(ctx, bankID,
|
||||
hindsight.WithBankName("Test Bank"),
|
||||
hindsight.WithMission("A test bank for Go client"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.BankID != bankID {
|
||||
t.Errorf("expected bank_id=%q, got %q", bankID, resp.BankID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetMission(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
|
||||
bankID := uniqueBank(t)
|
||||
resp, err := c.SetMission(ctx, bankID, "Be a helpful PM tracking sprint progress")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.BankID != bankID {
|
||||
t.Errorf("expected bank_id=%q, got %q", bankID, resp.BankID)
|
||||
}
|
||||
if resp.Mission != "Be a helpful PM tracking sprint progress" {
|
||||
t.Errorf("expected mission=%q, got %q", "Be a helpful PM tracking sprint progress", resp.Mission)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListBanks(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a bank first
|
||||
bankID := uniqueBank(t)
|
||||
_, err := c.CreateBank(ctx, bankID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := c.ListBanks(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(resp.Banks) == 0 {
|
||||
t.Error("expected at least one bank")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteBank(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
|
||||
bankID := uniqueBank(t)
|
||||
_, err := c.CreateBank(ctx, bankID, hindsight.WithMission("will be deleted"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = c.DeleteBank(ctx, bankID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- End-to-end workflow ---
|
||||
|
||||
func TestCompleteWorkflow(t *testing.T) {
|
||||
c := newClient(t)
|
||||
ctx := context.Background()
|
||||
bankID := uniqueBank(t)
|
||||
|
||||
// 1. Create bank
|
||||
_, err := c.CreateBank(ctx, bankID,
|
||||
hindsight.WithMission("I am a software engineer who loves Python programming."),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// 2. Store memories
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "I completed a project using FastAPI"},
|
||||
{Content: "I learned about async programming in Python"},
|
||||
{Content: "I enjoy working on open source projects"},
|
||||
}
|
||||
storeResp, err := c.RetainBatch(ctx, bankID, items)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !storeResp.Success {
|
||||
t.Error("expected retain success")
|
||||
}
|
||||
|
||||
// 3. Search for relevant memories
|
||||
recallResp, err := c.Recall(ctx, bankID, "What programming technologies do I use?")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(recallResp.Results) == 0 {
|
||||
t.Error("expected recall results")
|
||||
}
|
||||
|
||||
// 4. Generate contextual answer
|
||||
reflectResp, err := c.Reflect(ctx, bankID, "What are my professional interests?")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if reflectResp.Text == "" {
|
||||
t.Error("expected non-empty reflect response")
|
||||
}
|
||||
}
|
||||
|
||||
// contains checks if s contains substr (case-sensitive).
|
||||
func contains(s, substr string) bool {
|
||||
return len(s) >= len(substr) && searchString(s, substr)
|
||||
}
|
||||
|
||||
func searchString(s, substr string) bool {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
111
hindsight-clients/go/internal/cmd/preprocess/main.go
Normal file
111
hindsight-clients/go/internal/cmd/preprocess/main.go
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// Command preprocess converts an OpenAPI 3.1 spec to be ogen-compatible.
|
||||
//
|
||||
// Hindsight's OpenAPI spec uses anyOf: [{type: T}, {type: null}] for optional
|
||||
// fields. ogen cannot handle the null type in header/query parameter schemas
|
||||
// with style:simple. This tool rewrites such patterns to plain {type: T},
|
||||
// making the spec consumable by ogen while preserving semantic meaning (the
|
||||
// fields are already marked as required: false).
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 3 {
|
||||
fmt.Fprintf(os.Stderr, "usage: preprocess <input.json> <output.json>\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(os.Args[1])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "read: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var spec map[string]any
|
||||
if err := json.Unmarshal(data, &spec); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "parse: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
convertAnyOfNull(spec)
|
||||
|
||||
out, err := json.MarshalIndent(spec, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "marshal: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(os.Args[2], out, 0o644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "write: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// convertAnyOfNull recursively walks the spec and converts
|
||||
// anyOf: [{type: T}, {type: null}] → {type: T} (or just the non-null schema).
|
||||
// For component schema properties, it also does the conversion but additionally
|
||||
// handles cases where the non-null branch is a $ref.
|
||||
func convertAnyOfNull(v any) {
|
||||
switch val := v.(type) {
|
||||
case map[string]any:
|
||||
// Check if this object has an "anyOf" with exactly a non-null + null pair.
|
||||
if tryConvertAnyOf(val) {
|
||||
// Converted in place; recurse into the result.
|
||||
convertAnyOfNull(val)
|
||||
return
|
||||
}
|
||||
// Recurse into all values.
|
||||
for _, child := range val {
|
||||
convertAnyOfNull(child)
|
||||
}
|
||||
case []any:
|
||||
for _, child := range val {
|
||||
convertAnyOfNull(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tryConvertAnyOf checks if m has anyOf: [{...}, {type: null}] and converts
|
||||
// it in-place. Returns true if conversion happened.
|
||||
func tryConvertAnyOf(m map[string]any) bool {
|
||||
anyOf, ok := m["anyOf"].([]any)
|
||||
if !ok || len(anyOf) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Identify which branch is null and which is the real type.
|
||||
var realIdx int = -1
|
||||
for i, branch := range anyOf {
|
||||
branchMap, ok := branch.(map[string]any)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if branchMap["type"] == "null" {
|
||||
continue
|
||||
}
|
||||
realIdx = i
|
||||
}
|
||||
|
||||
if realIdx == -1 {
|
||||
return false // Both are null? Skip.
|
||||
}
|
||||
|
||||
realBranch, ok := anyOf[realIdx].(map[string]any)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove the anyOf key.
|
||||
delete(m, "anyOf")
|
||||
|
||||
// Copy all properties from the real branch into the parent.
|
||||
for k, v := range realBranch {
|
||||
m[k] = v
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
2
hindsight-clients/go/internal/ogenapi/.gitignore
vendored
Normal file
2
hindsight-clients/go/internal/ogenapi/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Preprocessed OpenAPI spec (intermediate artifact, regenerated by go generate)
|
||||
openapi.json
|
||||
61
hindsight-clients/go/internal/ogenapi/oas_cfg_gen.go
Normal file
61
hindsight-clients/go/internal/ogenapi/oas_cfg_gen.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package ogenapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
ht "github.com/ogen-go/ogen/http"
|
||||
)
|
||||
|
||||
type (
|
||||
optionFunc[C any] func(*C)
|
||||
)
|
||||
|
||||
type clientConfig struct {
|
||||
Client ht.Client
|
||||
}
|
||||
|
||||
// ClientOption is client config option.
|
||||
type ClientOption interface {
|
||||
applyClient(*clientConfig)
|
||||
}
|
||||
|
||||
var _ ClientOption = (optionFunc[clientConfig])(nil)
|
||||
|
||||
func (o optionFunc[C]) applyClient(c *C) {
|
||||
o(c)
|
||||
}
|
||||
|
||||
func newClientConfig(opts ...ClientOption) clientConfig {
|
||||
cfg := clientConfig{
|
||||
Client: http.DefaultClient,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.applyClient(&cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
type baseClient struct {
|
||||
cfg clientConfig
|
||||
}
|
||||
|
||||
func (cfg clientConfig) baseClient() (c baseClient, err error) {
|
||||
c = baseClient{cfg: cfg}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Option is config option.
|
||||
type Option interface {
|
||||
ClientOption
|
||||
}
|
||||
|
||||
// WithClient specifies http client to use.
|
||||
func WithClient(client ht.Client) ClientOption {
|
||||
return optionFunc[clientConfig](func(cfg *clientConfig) {
|
||||
if client != nil {
|
||||
cfg.Client = client
|
||||
}
|
||||
})
|
||||
}
|
||||
3638
hindsight-clients/go/internal/ogenapi/oas_client_gen.go
Normal file
3638
hindsight-clients/go/internal/ogenapi/oas_client_gen.go
Normal file
File diff suppressed because it is too large
Load diff
179
hindsight-clients/go/internal/ogenapi/oas_defaults_gen.go
Normal file
179
hindsight-clients/go/internal/ogenapi/oas_defaults_gen.go
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package ogenapi
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *AddBackgroundRequest) setDefaults() {
|
||||
{
|
||||
val := bool(true)
|
||||
s.UpdateDisposition.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *BankStatsResponse) setDefaults() {
|
||||
{
|
||||
val := int(0)
|
||||
s.PendingConsolidation.SetTo(val)
|
||||
}
|
||||
{
|
||||
val := int(0)
|
||||
s.TotalObservations.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *ChunkData) setDefaults() {
|
||||
{
|
||||
val := bool(false)
|
||||
s.Truncated.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *ChunkIncludeOptions) setDefaults() {
|
||||
{
|
||||
val := int(8192)
|
||||
s.MaxTokens.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *ConsolidationResponse) setDefaults() {
|
||||
{
|
||||
val := bool(false)
|
||||
s.Deduplicated.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *CreateDirectiveRequest) setDefaults() {
|
||||
{
|
||||
val := bool(true)
|
||||
s.IsActive.SetTo(val)
|
||||
}
|
||||
{
|
||||
val := int(0)
|
||||
s.Priority.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *CreateMentalModelRequest) setDefaults() {
|
||||
{
|
||||
val := int(2048)
|
||||
s.MaxTokens.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *DirectiveResponse) setDefaults() {
|
||||
{
|
||||
val := bool(true)
|
||||
s.IsActive.SetTo(val)
|
||||
}
|
||||
{
|
||||
val := int(0)
|
||||
s.Priority.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *EntityIncludeOptions) setDefaults() {
|
||||
{
|
||||
val := int(500)
|
||||
s.MaxTokens.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *MentalModelResponse) setDefaults() {
|
||||
{
|
||||
val := int(2048)
|
||||
s.MaxTokens.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *MentalModelTrigger) setDefaults() {
|
||||
{
|
||||
val := bool(false)
|
||||
s.RefreshAfterConsolidation.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *RecallRequest) setDefaults() {
|
||||
{
|
||||
val := Budget("low")
|
||||
s.Budget.SetTo(val)
|
||||
}
|
||||
{
|
||||
val := int(4096)
|
||||
s.MaxTokens.SetTo(val)
|
||||
}
|
||||
{
|
||||
val := RecallRequestTagsMatch("any")
|
||||
s.TagsMatch.SetTo(val)
|
||||
}
|
||||
{
|
||||
val := bool(false)
|
||||
s.Trace.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *ReflectRequest) setDefaults() {
|
||||
{
|
||||
val := Budget("low")
|
||||
s.Budget.SetTo(val)
|
||||
}
|
||||
{
|
||||
val := int(4096)
|
||||
s.MaxTokens.SetTo(val)
|
||||
}
|
||||
{
|
||||
val := ReflectRequestTagsMatch("any")
|
||||
s.TagsMatch.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *ReflectToolCall) setDefaults() {
|
||||
{
|
||||
val := int(0)
|
||||
s.Iteration.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *RetainRequest) setDefaults() {
|
||||
{
|
||||
val := bool(false)
|
||||
s.Async.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *TokenUsage) setDefaults() {
|
||||
{
|
||||
val := int(0)
|
||||
s.InputTokens.SetTo(val)
|
||||
}
|
||||
{
|
||||
val := int(0)
|
||||
s.OutputTokens.SetTo(val)
|
||||
}
|
||||
{
|
||||
val := int(0)
|
||||
s.TotalTokens.SetTo(val)
|
||||
}
|
||||
}
|
||||
|
||||
// setDefaults set default value of fields.
|
||||
func (s *ToolCallsIncludeOptions) setDefaults() {
|
||||
{
|
||||
val := bool(true)
|
||||
s.Output.SetTo(val)
|
||||
}
|
||||
}
|
||||
170
hindsight-clients/go/internal/ogenapi/oas_interfaces_gen.go
Normal file
170
hindsight-clients/go/internal/ogenapi/oas_interfaces_gen.go
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
// Code generated by ogen, DO NOT EDIT.
|
||||
package ogenapi
|
||||
|
||||
type AddBankBackgroundRes interface {
|
||||
addBankBackgroundRes()
|
||||
}
|
||||
|
||||
type CancelOperationRes interface {
|
||||
cancelOperationRes()
|
||||
}
|
||||
|
||||
type ClearBankMemoriesRes interface {
|
||||
clearBankMemoriesRes()
|
||||
}
|
||||
|
||||
type ClearObservationsRes interface {
|
||||
clearObservationsRes()
|
||||
}
|
||||
|
||||
type CreateDirectiveRes interface {
|
||||
createDirectiveRes()
|
||||
}
|
||||
|
||||
type CreateMentalModelRes interface {
|
||||
createMentalModelRes()
|
||||
}
|
||||
|
||||
type CreateOrUpdateBankRes interface {
|
||||
createOrUpdateBankRes()
|
||||
}
|
||||
|
||||
type DeleteBankRes interface {
|
||||
deleteBankRes()
|
||||
}
|
||||
|
||||
type DeleteDirectiveRes interface {
|
||||
deleteDirectiveRes()
|
||||
}
|
||||
|
||||
type DeleteDocumentRes interface {
|
||||
deleteDocumentRes()
|
||||
}
|
||||
|
||||
type DeleteMentalModelRes interface {
|
||||
deleteMentalModelRes()
|
||||
}
|
||||
|
||||
type GetAgentStatsRes interface {
|
||||
getAgentStatsRes()
|
||||
}
|
||||
|
||||
type GetBankConfigRes interface {
|
||||
getBankConfigRes()
|
||||
}
|
||||
|
||||
type GetBankProfileRes interface {
|
||||
getBankProfileRes()
|
||||
}
|
||||
|
||||
type GetChunkRes interface {
|
||||
getChunkRes()
|
||||
}
|
||||
|
||||
type GetDirectiveRes interface {
|
||||
getDirectiveRes()
|
||||
}
|
||||
|
||||
type GetDocumentRes interface {
|
||||
getDocumentRes()
|
||||
}
|
||||
|
||||
type GetEntityRes interface {
|
||||
getEntityRes()
|
||||
}
|
||||
|
||||
type GetGraphRes interface {
|
||||
getGraphRes()
|
||||
}
|
||||
|
||||
type GetMemoryRes interface {
|
||||
getMemoryRes()
|
||||
}
|
||||
|
||||
type GetMentalModelRes interface {
|
||||
getMentalModelRes()
|
||||
}
|
||||
|
||||
type GetOperationStatusRes interface {
|
||||
getOperationStatusRes()
|
||||
}
|
||||
|
||||
type ListBanksRes interface {
|
||||
listBanksRes()
|
||||
}
|
||||
|
||||
type ListDirectivesRes interface {
|
||||
listDirectivesRes()
|
||||
}
|
||||
|
||||
type ListDocumentsRes interface {
|
||||
listDocumentsRes()
|
||||
}
|
||||
|
||||
type ListEntitiesRes interface {
|
||||
listEntitiesRes()
|
||||
}
|
||||
|
||||
type ListMemoriesRes interface {
|
||||
listMemoriesRes()
|
||||
}
|
||||
|
||||
type ListMentalModelsRes interface {
|
||||
listMentalModelsRes()
|
||||
}
|
||||
|
||||
type ListOperationsRes interface {
|
||||
listOperationsRes()
|
||||
}
|
||||
|
||||
type ListTagsRes interface {
|
||||
listTagsRes()
|
||||
}
|
||||
|
||||
type RecallMemoriesRes interface {
|
||||
recallMemoriesRes()
|
||||
}
|
||||
|
||||
type ReflectRes interface {
|
||||
reflectRes()
|
||||
}
|
||||
|
||||
type RefreshMentalModelRes interface {
|
||||
refreshMentalModelRes()
|
||||
}
|
||||
|
||||
type RegenerateEntityObservationsRes interface {
|
||||
regenerateEntityObservationsRes()
|
||||
}
|
||||
|
||||
type ResetBankConfigRes interface {
|
||||
resetBankConfigRes()
|
||||
}
|
||||
|
||||
type RetainMemoriesRes interface {
|
||||
retainMemoriesRes()
|
||||
}
|
||||
|
||||
type TriggerConsolidationRes interface {
|
||||
triggerConsolidationRes()
|
||||
}
|
||||
|
||||
type UpdateBankConfigRes interface {
|
||||
updateBankConfigRes()
|
||||
}
|
||||
|
||||
type UpdateBankDispositionRes interface {
|
||||
updateBankDispositionRes()
|
||||
}
|
||||
|
||||
type UpdateBankRes interface {
|
||||
updateBankRes()
|
||||
}
|
||||
|
||||
type UpdateDirectiveRes interface {
|
||||
updateDirectiveRes()
|
||||
}
|
||||
|
||||
type UpdateMentalModelRes interface {
|
||||
updateMentalModelRes()
|
||||
}
|
||||
12849
hindsight-clients/go/internal/ogenapi/oas_json_gen.go
Normal file
12849
hindsight-clients/go/internal/ogenapi/oas_json_gen.go
Normal file
File diff suppressed because it is too large
Load diff
54
hindsight-clients/go/internal/ogenapi/oas_operations_gen.go
Normal file
54
hindsight-clients/go/internal/ogenapi/oas_operations_gen.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package ogenapi
|
||||
|
||||
// OperationName is the ogen operation name
|
||||
type OperationName = string
|
||||
|
||||
const (
|
||||
AddBankBackgroundOperation OperationName = "AddBankBackground"
|
||||
CancelOperationOperation OperationName = "CancelOperation"
|
||||
ClearBankMemoriesOperation OperationName = "ClearBankMemories"
|
||||
ClearObservationsOperation OperationName = "ClearObservations"
|
||||
CreateDirectiveOperation OperationName = "CreateDirective"
|
||||
CreateMentalModelOperation OperationName = "CreateMentalModel"
|
||||
CreateOrUpdateBankOperation OperationName = "CreateOrUpdateBank"
|
||||
DeleteBankOperation OperationName = "DeleteBank"
|
||||
DeleteDirectiveOperation OperationName = "DeleteDirective"
|
||||
DeleteDocumentOperation OperationName = "DeleteDocument"
|
||||
DeleteMentalModelOperation OperationName = "DeleteMentalModel"
|
||||
GetAgentStatsOperation OperationName = "GetAgentStats"
|
||||
GetBankConfigOperation OperationName = "GetBankConfig"
|
||||
GetBankProfileOperation OperationName = "GetBankProfile"
|
||||
GetChunkOperation OperationName = "GetChunk"
|
||||
GetDirectiveOperation OperationName = "GetDirective"
|
||||
GetDocumentOperation OperationName = "GetDocument"
|
||||
GetEntityOperation OperationName = "GetEntity"
|
||||
GetGraphOperation OperationName = "GetGraph"
|
||||
GetMemoryOperation OperationName = "GetMemory"
|
||||
GetMentalModelOperation OperationName = "GetMentalModel"
|
||||
GetOperationStatusOperation OperationName = "GetOperationStatus"
|
||||
GetVersionOperation OperationName = "GetVersion"
|
||||
HealthEndpointHealthGetOperation OperationName = "HealthEndpointHealthGet"
|
||||
ListBanksOperation OperationName = "ListBanks"
|
||||
ListDirectivesOperation OperationName = "ListDirectives"
|
||||
ListDocumentsOperation OperationName = "ListDocuments"
|
||||
ListEntitiesOperation OperationName = "ListEntities"
|
||||
ListMemoriesOperation OperationName = "ListMemories"
|
||||
ListMentalModelsOperation OperationName = "ListMentalModels"
|
||||
ListOperationsOperation OperationName = "ListOperations"
|
||||
ListTagsOperation OperationName = "ListTags"
|
||||
MetricsEndpointMetricsGetOperation OperationName = "MetricsEndpointMetricsGet"
|
||||
RecallMemoriesOperation OperationName = "RecallMemories"
|
||||
ReflectOperation OperationName = "Reflect"
|
||||
RefreshMentalModelOperation OperationName = "RefreshMentalModel"
|
||||
RegenerateEntityObservationsOperation OperationName = "RegenerateEntityObservations"
|
||||
ResetBankConfigOperation OperationName = "ResetBankConfig"
|
||||
RetainMemoriesOperation OperationName = "RetainMemories"
|
||||
TriggerConsolidationOperation OperationName = "TriggerConsolidation"
|
||||
UpdateBankOperation OperationName = "UpdateBank"
|
||||
UpdateBankConfigOperation OperationName = "UpdateBankConfig"
|
||||
UpdateBankDispositionOperation OperationName = "UpdateBankDisposition"
|
||||
UpdateDirectiveOperation OperationName = "UpdateDirective"
|
||||
UpdateMentalModelOperation OperationName = "UpdateMentalModel"
|
||||
)
|
||||
264
hindsight-clients/go/internal/ogenapi/oas_parameters_gen.go
Normal file
264
hindsight-clients/go/internal/ogenapi/oas_parameters_gen.go
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package ogenapi
|
||||
|
||||
// AddBankBackgroundParams is parameters of add_bank_background operation.
|
||||
type AddBankBackgroundParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// CancelOperationParams is parameters of cancel_operation operation.
|
||||
type CancelOperationParams struct {
|
||||
BankID string
|
||||
OperationID string
|
||||
}
|
||||
|
||||
// ClearBankMemoriesParams is parameters of clear_bank_memories operation.
|
||||
type ClearBankMemoriesParams struct {
|
||||
BankID string
|
||||
// Optional fact type filter (world, experience, opinion).
|
||||
Type OptString `json:",omitempty,omitzero"`
|
||||
}
|
||||
|
||||
// ClearObservationsParams is parameters of clear_observations operation.
|
||||
type ClearObservationsParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// CreateDirectiveParams is parameters of create_directive operation.
|
||||
type CreateDirectiveParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// CreateMentalModelParams is parameters of create_mental_model operation.
|
||||
type CreateMentalModelParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// CreateOrUpdateBankParams is parameters of create_or_update_bank operation.
|
||||
type CreateOrUpdateBankParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// DeleteBankParams is parameters of delete_bank operation.
|
||||
type DeleteBankParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// DeleteDirectiveParams is parameters of delete_directive operation.
|
||||
type DeleteDirectiveParams struct {
|
||||
BankID string
|
||||
DirectiveID string
|
||||
}
|
||||
|
||||
// DeleteDocumentParams is parameters of delete_document operation.
|
||||
type DeleteDocumentParams struct {
|
||||
BankID string
|
||||
DocumentID string
|
||||
}
|
||||
|
||||
// DeleteMentalModelParams is parameters of delete_mental_model operation.
|
||||
type DeleteMentalModelParams struct {
|
||||
BankID string
|
||||
MentalModelID string
|
||||
}
|
||||
|
||||
// GetAgentStatsParams is parameters of get_agent_stats operation.
|
||||
type GetAgentStatsParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// GetBankConfigParams is parameters of get_bank_config operation.
|
||||
type GetBankConfigParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// GetBankProfileParams is parameters of get_bank_profile operation.
|
||||
type GetBankProfileParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// GetChunkParams is parameters of get_chunk operation.
|
||||
type GetChunkParams struct {
|
||||
ChunkID string
|
||||
}
|
||||
|
||||
// GetDirectiveParams is parameters of get_directive operation.
|
||||
type GetDirectiveParams struct {
|
||||
BankID string
|
||||
DirectiveID string
|
||||
}
|
||||
|
||||
// GetDocumentParams is parameters of get_document operation.
|
||||
type GetDocumentParams struct {
|
||||
BankID string
|
||||
DocumentID string
|
||||
}
|
||||
|
||||
// GetEntityParams is parameters of get_entity operation.
|
||||
type GetEntityParams struct {
|
||||
BankID string
|
||||
EntityID string
|
||||
}
|
||||
|
||||
// GetGraphParams is parameters of get_graph operation.
|
||||
type GetGraphParams struct {
|
||||
BankID string
|
||||
Type OptString `json:",omitempty,omitzero"`
|
||||
Limit OptInt `json:",omitempty,omitzero"`
|
||||
}
|
||||
|
||||
// GetMemoryParams is parameters of get_memory operation.
|
||||
type GetMemoryParams struct {
|
||||
BankID string
|
||||
MemoryID string
|
||||
}
|
||||
|
||||
// GetMentalModelParams is parameters of get_mental_model operation.
|
||||
type GetMentalModelParams struct {
|
||||
BankID string
|
||||
MentalModelID string
|
||||
}
|
||||
|
||||
// GetOperationStatusParams is parameters of get_operation_status operation.
|
||||
type GetOperationStatusParams struct {
|
||||
BankID string
|
||||
OperationID string
|
||||
}
|
||||
|
||||
// ListDirectivesParams is parameters of list_directives operation.
|
||||
type ListDirectivesParams struct {
|
||||
BankID string
|
||||
// Filter by tags.
|
||||
Tags []string `json:",omitempty"`
|
||||
// How to match tags.
|
||||
TagsMatch OptListDirectivesTagsMatch `json:",omitempty,omitzero"`
|
||||
// Only return active directives.
|
||||
ActiveOnly OptBool `json:",omitempty,omitzero"`
|
||||
Limit OptInt `json:",omitempty,omitzero"`
|
||||
Offset OptInt `json:",omitempty,omitzero"`
|
||||
}
|
||||
|
||||
// ListDocumentsParams is parameters of list_documents operation.
|
||||
type ListDocumentsParams struct {
|
||||
BankID string
|
||||
Q OptString `json:",omitempty,omitzero"`
|
||||
Limit OptInt `json:",omitempty,omitzero"`
|
||||
Offset OptInt `json:",omitempty,omitzero"`
|
||||
}
|
||||
|
||||
// ListEntitiesParams is parameters of list_entities operation.
|
||||
type ListEntitiesParams struct {
|
||||
BankID string
|
||||
// Maximum number of entities to return.
|
||||
Limit OptInt `json:",omitempty,omitzero"`
|
||||
// Offset for pagination.
|
||||
Offset OptInt `json:",omitempty,omitzero"`
|
||||
}
|
||||
|
||||
// ListMemoriesParams is parameters of list_memories operation.
|
||||
type ListMemoriesParams struct {
|
||||
BankID string
|
||||
Type OptString `json:",omitempty,omitzero"`
|
||||
Q OptString `json:",omitempty,omitzero"`
|
||||
Limit OptInt `json:",omitempty,omitzero"`
|
||||
Offset OptInt `json:",omitempty,omitzero"`
|
||||
}
|
||||
|
||||
// ListMentalModelsParams is parameters of list_mental_models operation.
|
||||
type ListMentalModelsParams struct {
|
||||
BankID string
|
||||
// Filter by tags.
|
||||
Tags []string `json:",omitempty"`
|
||||
// How to match tags.
|
||||
TagsMatch OptListMentalModelsTagsMatch `json:",omitempty,omitzero"`
|
||||
Limit OptInt `json:",omitempty,omitzero"`
|
||||
Offset OptInt `json:",omitempty,omitzero"`
|
||||
}
|
||||
|
||||
// ListOperationsParams is parameters of list_operations operation.
|
||||
type ListOperationsParams struct {
|
||||
BankID string
|
||||
// Filter by status: pending, completed, or failed.
|
||||
Status OptString `json:",omitempty,omitzero"`
|
||||
// Maximum number of operations to return.
|
||||
Limit OptInt `json:",omitempty,omitzero"`
|
||||
// Number of operations to skip.
|
||||
Offset OptInt `json:",omitempty,omitzero"`
|
||||
}
|
||||
|
||||
// ListTagsParams is parameters of list_tags operation.
|
||||
type ListTagsParams struct {
|
||||
BankID string
|
||||
// Wildcard pattern to filter tags (e.g., 'user:*' for user:alice, '*-admin' for role-admin). Use '*'
|
||||
// as wildcard. Case-insensitive.
|
||||
Q OptString `json:",omitempty,omitzero"`
|
||||
// Maximum number of tags to return.
|
||||
Limit OptInt `json:",omitempty,omitzero"`
|
||||
// Offset for pagination.
|
||||
Offset OptInt `json:",omitempty,omitzero"`
|
||||
}
|
||||
|
||||
// RecallMemoriesParams is parameters of recall_memories operation.
|
||||
type RecallMemoriesParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// ReflectParams is parameters of reflect operation.
|
||||
type ReflectParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// RefreshMentalModelParams is parameters of refresh_mental_model operation.
|
||||
type RefreshMentalModelParams struct {
|
||||
BankID string
|
||||
MentalModelID string
|
||||
}
|
||||
|
||||
// RegenerateEntityObservationsParams is parameters of regenerate_entity_observations operation.
|
||||
type RegenerateEntityObservationsParams struct {
|
||||
BankID string
|
||||
EntityID string
|
||||
}
|
||||
|
||||
// ResetBankConfigParams is parameters of reset_bank_config operation.
|
||||
type ResetBankConfigParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// RetainMemoriesParams is parameters of retain_memories operation.
|
||||
type RetainMemoriesParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// TriggerConsolidationParams is parameters of trigger_consolidation operation.
|
||||
type TriggerConsolidationParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// UpdateBankParams is parameters of update_bank operation.
|
||||
type UpdateBankParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// UpdateBankConfigParams is parameters of update_bank_config operation.
|
||||
type UpdateBankConfigParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// UpdateBankDispositionParams is parameters of update_bank_disposition operation.
|
||||
type UpdateBankDispositionParams struct {
|
||||
BankID string
|
||||
}
|
||||
|
||||
// UpdateDirectiveParams is parameters of update_directive operation.
|
||||
type UpdateDirectiveParams struct {
|
||||
BankID string
|
||||
DirectiveID string
|
||||
}
|
||||
|
||||
// UpdateMentalModelParams is parameters of update_mental_model operation.
|
||||
type UpdateMentalModelParams struct {
|
||||
BankID string
|
||||
MentalModelID string
|
||||
}
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package ogenapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-faster/jx"
|
||||
ht "github.com/ogen-go/ogen/http"
|
||||
)
|
||||
|
||||
func encodeAddBankBackgroundRequest(
|
||||
req *AddBackgroundRequest,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateDirectiveRequest(
|
||||
req *CreateDirectiveRequest,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateMentalModelRequest(
|
||||
req *CreateMentalModelRequest,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeCreateOrUpdateBankRequest(
|
||||
req *CreateBankRequest,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeRecallMemoriesRequest(
|
||||
req *RecallRequest,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeReflectRequest(
|
||||
req *ReflectRequest,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeRetainMemoriesRequest(
|
||||
req *RetainRequest,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeUpdateBankRequest(
|
||||
req *CreateBankRequest,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeUpdateBankConfigRequest(
|
||||
req *BankConfigUpdate,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeUpdateBankDispositionRequest(
|
||||
req *UpdateDispositionRequest,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeUpdateDirectiveRequest(
|
||||
req *UpdateDirectiveRequest,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeUpdateMentalModelRequest(
|
||||
req *UpdateMentalModelRequest,
|
||||
r *http.Request,
|
||||
) error {
|
||||
const contentType = "application/json"
|
||||
e := new(jx.Encoder)
|
||||
{
|
||||
req.Encode(e)
|
||||
}
|
||||
encoded := e.Bytes()
|
||||
ht.SetBody(r, bytes.NewReader(encoded), contentType)
|
||||
return nil
|
||||
}
|
||||
3873
hindsight-clients/go/internal/ogenapi/oas_response_decoders_gen.go
Normal file
3873
hindsight-clients/go/internal/ogenapi/oas_response_decoders_gen.go
Normal file
File diff suppressed because it is too large
Load diff
5666
hindsight-clients/go/internal/ogenapi/oas_schemas_gen.go
Normal file
5666
hindsight-clients/go/internal/ogenapi/oas_schemas_gen.go
Normal file
File diff suppressed because it is too large
Load diff
935
hindsight-clients/go/internal/ogenapi/oas_validators_gen.go
Normal file
935
hindsight-clients/go/internal/ogenapi/oas_validators_gen.go
Normal file
|
|
@ -0,0 +1,935 @@
|
|||
// Code generated by ogen, DO NOT EDIT.
|
||||
|
||||
package ogenapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-faster/errors"
|
||||
"github.com/ogen-go/ogen/validate"
|
||||
)
|
||||
|
||||
func (s *BackgroundResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if value, ok := s.Disposition.Get(); ok {
|
||||
if err := func() error {
|
||||
if err := value.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "disposition",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BankListItem) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if err := s.Disposition.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "disposition",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BankListResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Banks == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
var failures []validate.FieldError
|
||||
for i, elem := range s.Banks {
|
||||
if err := func() error {
|
||||
if err := elem.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: fmt.Sprintf("[%d]", i),
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "banks",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BankProfileResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if err := s.Disposition.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "disposition",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Budget) Validate() error {
|
||||
switch s {
|
||||
case "low":
|
||||
return nil
|
||||
case "mid":
|
||||
return nil
|
||||
case "high":
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("invalid value: %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CreateBankRequest) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if value, ok := s.Disposition.Get(); ok {
|
||||
if err := func() error {
|
||||
if err := value.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "disposition",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CreateMentalModelRequest) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if value, ok := s.MaxTokens.Get(); ok {
|
||||
if err := func() error {
|
||||
if err := (validate.Int{
|
||||
MinSet: true,
|
||||
Min: 256,
|
||||
MaxSet: true,
|
||||
Max: 8192,
|
||||
MinExclusive: false,
|
||||
MaxExclusive: false,
|
||||
MultipleOfSet: false,
|
||||
MultipleOf: 0,
|
||||
Pattern: nil,
|
||||
}).Validate(int64(value)); err != nil {
|
||||
return errors.Wrap(err, "int")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "max_tokens",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DirectiveListResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Items == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "items",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DispositionTraits) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if err := (validate.Int{
|
||||
MinSet: true,
|
||||
Min: 1,
|
||||
MaxSet: true,
|
||||
Max: 5,
|
||||
MinExclusive: false,
|
||||
MaxExclusive: false,
|
||||
MultipleOfSet: false,
|
||||
MultipleOf: 0,
|
||||
Pattern: nil,
|
||||
}).Validate(int64(s.Empathy)); err != nil {
|
||||
return errors.Wrap(err, "int")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "empathy",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if err := (validate.Int{
|
||||
MinSet: true,
|
||||
Min: 1,
|
||||
MaxSet: true,
|
||||
Max: 5,
|
||||
MinExclusive: false,
|
||||
MaxExclusive: false,
|
||||
MultipleOfSet: false,
|
||||
MultipleOf: 0,
|
||||
Pattern: nil,
|
||||
}).Validate(int64(s.Literalism)); err != nil {
|
||||
return errors.Wrap(err, "int")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "literalism",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if err := (validate.Int{
|
||||
MinSet: true,
|
||||
Min: 1,
|
||||
MaxSet: true,
|
||||
Max: 5,
|
||||
MinExclusive: false,
|
||||
MaxExclusive: false,
|
||||
MultipleOfSet: false,
|
||||
MultipleOf: 0,
|
||||
Pattern: nil,
|
||||
}).Validate(int64(s.Skepticism)); err != nil {
|
||||
return errors.Wrap(err, "int")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "skepticism",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EntityDetailResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Observations == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "observations",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EntityListResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Items == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "items",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EntityStateResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Observations == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "observations",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *GraphDataResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Edges == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "edges",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if s.Nodes == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "nodes",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if s.TableRows == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "table_rows",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HTTPValidationError) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
var failures []validate.FieldError
|
||||
for i, elem := range s.Detail {
|
||||
if err := func() error {
|
||||
if err := elem.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: fmt.Sprintf("[%d]", i),
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "detail",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s ListDirectivesTagsMatch) Validate() error {
|
||||
switch s {
|
||||
case "any":
|
||||
return nil
|
||||
case "all":
|
||||
return nil
|
||||
case "exact":
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("invalid value: %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ListDocumentsResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Items == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "items",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ListMemoryUnitsResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Items == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "items",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s ListMentalModelsTagsMatch) Validate() error {
|
||||
switch s {
|
||||
case "any":
|
||||
return nil
|
||||
case "all":
|
||||
return nil
|
||||
case "exact":
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("invalid value: %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ListTagsResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Items == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "items",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MentalModelListResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Items == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "items",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OperationStatusResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if err := s.Status.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "status",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s OperationStatusResponseStatus) Validate() error {
|
||||
switch s {
|
||||
case "pending":
|
||||
return nil
|
||||
case "completed":
|
||||
return nil
|
||||
case "failed":
|
||||
return nil
|
||||
case "not_found":
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("invalid value: %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *OperationsListResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Operations == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "operations",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RecallRequest) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if value, ok := s.Budget.Get(); ok {
|
||||
if err := func() error {
|
||||
if err := value.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "budget",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if value, ok := s.TagsMatch.Get(); ok {
|
||||
if err := func() error {
|
||||
if err := value.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "tags_match",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s RecallRequestTagsMatch) Validate() error {
|
||||
switch s {
|
||||
case "any":
|
||||
return nil
|
||||
case "all":
|
||||
return nil
|
||||
case "any_strict":
|
||||
return nil
|
||||
case "all_strict":
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("invalid value: %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RecallResponse) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if value, ok := s.Entities.Get(); ok {
|
||||
if err := func() error {
|
||||
if err := value.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "entities",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if s.Results == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "results",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s RecallResponseEntities) Validate() error {
|
||||
var failures []validate.FieldError
|
||||
for key, elem := range s {
|
||||
if err := func() error {
|
||||
if err := elem.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: key,
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ReflectRequest) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if value, ok := s.Budget.Get(); ok {
|
||||
if err := func() error {
|
||||
if err := value.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "budget",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if err := func() error {
|
||||
if value, ok := s.TagsMatch.Get(); ok {
|
||||
if err := func() error {
|
||||
if err := value.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "tags_match",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s ReflectRequestTagsMatch) Validate() error {
|
||||
switch s {
|
||||
case "any":
|
||||
return nil
|
||||
case "all":
|
||||
return nil
|
||||
case "any_strict":
|
||||
return nil
|
||||
case "all_strict":
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("invalid value: %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RetainRequest) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Items == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "items",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *UpdateDispositionRequest) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if err := s.Disposition.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "disposition",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *UpdateMentalModelRequest) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if value, ok := s.MaxTokens.Get(); ok {
|
||||
if err := func() error {
|
||||
if err := (validate.Int{
|
||||
MinSet: true,
|
||||
Min: 256,
|
||||
MaxSet: true,
|
||||
Max: 8192,
|
||||
MinExclusive: false,
|
||||
MaxExclusive: false,
|
||||
MultipleOfSet: false,
|
||||
MultipleOf: 0,
|
||||
Pattern: nil,
|
||||
}).Validate(int64(value)); err != nil {
|
||||
return errors.Wrap(err, "int")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "max_tokens",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ValidationError) Validate() error {
|
||||
if s == nil {
|
||||
return validate.ErrNilPointer
|
||||
}
|
||||
|
||||
var failures []validate.FieldError
|
||||
if err := func() error {
|
||||
if s.Loc == nil {
|
||||
return errors.New("nil is invalid value")
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
failures = append(failures, validate.FieldError{
|
||||
Name: "loc",
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
return &validate.Error{Fields: failures}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
9
hindsight-clients/go/ogen.yml
Normal file
9
hindsight-clients/go/ogen.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
generator:
|
||||
ignore_not_implemented: ["all"]
|
||||
features:
|
||||
disable:
|
||||
- paths/server
|
||||
- webhooks/server
|
||||
- webhooks/client
|
||||
- ogen/otel
|
||||
- ogen/unimplemented
|
||||
321
hindsight-clients/go/options.go
Normal file
321
hindsight-clients/go/options.go
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
package hindsight
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
|
||||
)
|
||||
|
||||
// --- Client options ---
|
||||
|
||||
type clientConfig struct {
|
||||
apiKey string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// Option configures a [Client].
|
||||
type Option func(*clientConfig)
|
||||
|
||||
// WithAPIKey sets the Bearer token used for authentication.
|
||||
func WithAPIKey(key string) Option {
|
||||
return func(c *clientConfig) { c.apiKey = key }
|
||||
}
|
||||
|
||||
// WithHTTPClient sets a custom [http.Client] for all requests.
|
||||
// If combined with [WithAPIKey], the API key transport wraps this client's transport.
|
||||
func WithHTTPClient(hc *http.Client) Option {
|
||||
return func(c *clientConfig) { c.httpClient = hc }
|
||||
}
|
||||
|
||||
// --- Re-exported types ---
|
||||
|
||||
// Budget controls the computation budget for recall and reflect operations.
|
||||
type Budget = ogenapi.Budget
|
||||
|
||||
const (
|
||||
BudgetLow = ogenapi.BudgetLow
|
||||
BudgetMid = ogenapi.BudgetMid
|
||||
BudgetHigh = ogenapi.BudgetHigh
|
||||
)
|
||||
|
||||
// TagsMatch controls how tag filtering works.
|
||||
type TagsMatch string
|
||||
|
||||
const (
|
||||
TagsMatchAny TagsMatch = "any"
|
||||
TagsMatchAll TagsMatch = "all"
|
||||
TagsMatchAnyStrict TagsMatch = "any_strict"
|
||||
TagsMatchAllStrict TagsMatch = "all_strict"
|
||||
)
|
||||
|
||||
type (
|
||||
// RetainResponse is the response from a retain operation.
|
||||
RetainResponse = ogenapi.RetainResponse
|
||||
|
||||
// RecallResponse is the response from a recall operation.
|
||||
RecallResponse = ogenapi.RecallResponse
|
||||
|
||||
// RecallResult is a single memory result from recall.
|
||||
RecallResult = ogenapi.RecallResult
|
||||
|
||||
// ReflectResponse is the response from a reflect operation.
|
||||
ReflectResponse = ogenapi.ReflectResponse
|
||||
|
||||
// MemoryItem is a single memory item for retain operations.
|
||||
MemoryItem = ogenapi.MemoryItem
|
||||
|
||||
// EntityInput provides entity hints for retain.
|
||||
EntityInput = ogenapi.EntityInput
|
||||
|
||||
// BankProfileResponse is the response for bank profile operations.
|
||||
BankProfileResponse = ogenapi.BankProfileResponse
|
||||
|
||||
// BankListResponse is the response for listing banks.
|
||||
BankListResponse = ogenapi.BankListResponse
|
||||
|
||||
// DispositionTraits configures personality traits for a bank.
|
||||
DispositionTraits = ogenapi.DispositionTraits
|
||||
|
||||
// TokenUsage reports LLM token consumption.
|
||||
TokenUsage = ogenapi.TokenUsage
|
||||
|
||||
// ReflectBasedOn contains the evidence used for a reflect response.
|
||||
ReflectBasedOn = ogenapi.ReflectBasedOn
|
||||
|
||||
// IncludeOptions controls what extra data is returned from recall.
|
||||
IncludeOptions = ogenapi.IncludeOptions
|
||||
|
||||
// ReflectIncludeOptions controls what extra data is returned from reflect.
|
||||
ReflectIncludeOptions = ogenapi.ReflectIncludeOptions
|
||||
)
|
||||
|
||||
// --- Retain options ---
|
||||
|
||||
// RetainOption configures a [Client.Retain] call.
|
||||
type RetainOption func(*retainConfig)
|
||||
|
||||
type retainConfig struct {
|
||||
timestamp *time.Time
|
||||
context *string
|
||||
documentID *string
|
||||
metadata map[string]string
|
||||
entities []EntityInput
|
||||
tags []string
|
||||
}
|
||||
|
||||
// WithTimestamp sets the timestamp for a retained memory.
|
||||
func WithTimestamp(t time.Time) RetainOption {
|
||||
return func(c *retainConfig) { c.timestamp = &t }
|
||||
}
|
||||
|
||||
// WithContext sets additional context for a retained memory.
|
||||
func WithContext(ctx string) RetainOption {
|
||||
return func(c *retainConfig) { c.context = &ctx }
|
||||
}
|
||||
|
||||
// WithDocumentID groups retained memories under a document.
|
||||
func WithDocumentID(id string) RetainOption {
|
||||
return func(c *retainConfig) { c.documentID = &id }
|
||||
}
|
||||
|
||||
// WithMetadata attaches key-value metadata to a retained memory.
|
||||
func WithMetadata(m map[string]string) RetainOption {
|
||||
return func(c *retainConfig) { c.metadata = m }
|
||||
}
|
||||
|
||||
// WithEntities provides entity hints for a retained memory.
|
||||
func WithEntities(e []EntityInput) RetainOption {
|
||||
return func(c *retainConfig) { c.entities = e }
|
||||
}
|
||||
|
||||
// WithTags attaches tags to a retained memory for filtering.
|
||||
func WithTags(tags []string) RetainOption {
|
||||
return func(c *retainConfig) { c.tags = tags }
|
||||
}
|
||||
|
||||
// RetainBatchOption configures a [Client.RetainBatch] call.
|
||||
type RetainBatchOption func(*retainBatchConfig)
|
||||
|
||||
type retainBatchConfig struct {
|
||||
documentTags []string
|
||||
async bool
|
||||
}
|
||||
|
||||
// WithDocumentTags sets tags applied to all items in a batch retain.
|
||||
func WithDocumentTags(tags []string) RetainBatchOption {
|
||||
return func(c *retainBatchConfig) { c.documentTags = tags }
|
||||
}
|
||||
|
||||
// WithAsync processes the retain batch asynchronously.
|
||||
func WithAsync(async bool) RetainBatchOption {
|
||||
return func(c *retainBatchConfig) { c.async = async }
|
||||
}
|
||||
|
||||
// --- Recall options ---
|
||||
|
||||
// RecallOption configures a [Client.Recall] call.
|
||||
type RecallOption func(*recallConfig)
|
||||
|
||||
type recallConfig struct {
|
||||
types []string
|
||||
maxTokens *int
|
||||
budget *Budget
|
||||
trace *bool
|
||||
queryTimestamp *string
|
||||
includeOpts *IncludeOptions
|
||||
tags []string
|
||||
tagsMatch *TagsMatch
|
||||
}
|
||||
|
||||
// WithTypes filters recalled memories by type (e.g., "world", "experience").
|
||||
func WithTypes(types []string) RecallOption {
|
||||
return func(c *recallConfig) { c.types = types }
|
||||
}
|
||||
|
||||
// WithMaxTokens sets the maximum tokens for recall results.
|
||||
func WithMaxTokens(n int) RecallOption {
|
||||
return func(c *recallConfig) { c.maxTokens = &n }
|
||||
}
|
||||
|
||||
// WithBudget sets the computation budget for recall.
|
||||
func WithBudget(b Budget) RecallOption {
|
||||
return func(c *recallConfig) { c.budget = &b }
|
||||
}
|
||||
|
||||
// WithTrace enables the execution trace in recall results.
|
||||
func WithTrace(enabled bool) RecallOption {
|
||||
return func(c *recallConfig) { c.trace = &enabled }
|
||||
}
|
||||
|
||||
// WithQueryTimestamp sets the temporal context for recall (ISO 8601 format).
|
||||
func WithQueryTimestamp(ts string) RecallOption {
|
||||
return func(c *recallConfig) { c.queryTimestamp = &ts }
|
||||
}
|
||||
|
||||
// WithInclude configures which additional data to include in recall results.
|
||||
func WithInclude(opts IncludeOptions) RecallOption {
|
||||
return func(c *recallConfig) { c.includeOpts = &opts }
|
||||
}
|
||||
|
||||
// WithRecallTags filters recalled memories by tags.
|
||||
func WithRecallTags(tags []string) RecallOption {
|
||||
return func(c *recallConfig) { c.tags = tags }
|
||||
}
|
||||
|
||||
// WithRecallTagsMatch sets how tags are matched during recall.
|
||||
func WithRecallTagsMatch(m TagsMatch) RecallOption {
|
||||
return func(c *recallConfig) { c.tagsMatch = &m }
|
||||
}
|
||||
|
||||
// --- Reflect options ---
|
||||
|
||||
// ReflectOption configures a [Client.Reflect] call.
|
||||
type ReflectOption func(*reflectConfig)
|
||||
|
||||
type reflectConfig struct {
|
||||
budget *Budget
|
||||
maxTokens *int
|
||||
includeOpts *ReflectIncludeOptions
|
||||
responseSchema map[string]any
|
||||
tags []string
|
||||
tagsMatch *TagsMatch
|
||||
}
|
||||
|
||||
// WithReflectBudget sets the computation budget for reflect.
|
||||
func WithReflectBudget(b Budget) ReflectOption {
|
||||
return func(c *reflectConfig) { c.budget = &b }
|
||||
}
|
||||
|
||||
// WithReflectMaxTokens sets the maximum tokens for the reflect response.
|
||||
func WithReflectMaxTokens(n int) ReflectOption {
|
||||
return func(c *reflectConfig) { c.maxTokens = &n }
|
||||
}
|
||||
|
||||
// WithReflectInclude configures which additional data to include in reflect results.
|
||||
func WithReflectInclude(opts ReflectIncludeOptions) ReflectOption {
|
||||
return func(c *reflectConfig) { c.includeOpts = &opts }
|
||||
}
|
||||
|
||||
// WithResponseSchema sets a JSON Schema for structured output from reflect.
|
||||
func WithResponseSchema(schema map[string]any) ReflectOption {
|
||||
return func(c *reflectConfig) { c.responseSchema = schema }
|
||||
}
|
||||
|
||||
// WithReflectTags filters memories by tags during reflect.
|
||||
func WithReflectTags(tags []string) ReflectOption {
|
||||
return func(c *reflectConfig) { c.tags = tags }
|
||||
}
|
||||
|
||||
// WithReflectTagsMatch sets how tags are matched during reflect.
|
||||
func WithReflectTagsMatch(m TagsMatch) ReflectOption {
|
||||
return func(c *reflectConfig) { c.tagsMatch = &m }
|
||||
}
|
||||
|
||||
// --- Bank options ---
|
||||
|
||||
// CreateBankOption configures a [Client.CreateBank] call.
|
||||
type CreateBankOption func(*createBankConfig)
|
||||
|
||||
type createBankConfig struct {
|
||||
name *string
|
||||
mission *string
|
||||
disposition *DispositionTraits
|
||||
}
|
||||
|
||||
// WithBankName sets the display name for a bank.
|
||||
func WithBankName(name string) CreateBankOption {
|
||||
return func(c *createBankConfig) { c.name = &name }
|
||||
}
|
||||
|
||||
// WithMission sets the mission for a bank.
|
||||
func WithMission(mission string) CreateBankOption {
|
||||
return func(c *createBankConfig) { c.mission = &mission }
|
||||
}
|
||||
|
||||
// WithDisposition sets the personality traits for a bank.
|
||||
func WithDisposition(d DispositionTraits) CreateBankOption {
|
||||
return func(c *createBankConfig) { c.disposition = &d }
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
func optString(s string) ogenapi.OptString {
|
||||
return ogenapi.NewOptString(s)
|
||||
}
|
||||
|
||||
func optStringPtr(s *string) ogenapi.OptString {
|
||||
if s == nil {
|
||||
return ogenapi.OptString{}
|
||||
}
|
||||
return ogenapi.NewOptString(*s)
|
||||
}
|
||||
|
||||
func optInt(n int) ogenapi.OptInt {
|
||||
return ogenapi.NewOptInt(n)
|
||||
}
|
||||
|
||||
func optIntPtr(n *int) ogenapi.OptInt {
|
||||
if n == nil {
|
||||
return ogenapi.OptInt{}
|
||||
}
|
||||
return ogenapi.NewOptInt(*n)
|
||||
}
|
||||
|
||||
func optBool(b bool) ogenapi.OptBool {
|
||||
return ogenapi.NewOptBool(b)
|
||||
}
|
||||
|
||||
func optBoolPtr(b *bool) ogenapi.OptBool {
|
||||
if b == nil {
|
||||
return ogenapi.OptBool{}
|
||||
}
|
||||
return ogenapi.NewOptBool(*b)
|
||||
}
|
||||
|
||||
func optBudget(b *Budget) ogenapi.OptBudget {
|
||||
if b == nil {
|
||||
return ogenapi.OptBudget{}
|
||||
}
|
||||
return ogenapi.NewOptBudget(*b)
|
||||
}
|
||||
59
hindsight-clients/go/recall.go
Normal file
59
hindsight-clients/go/recall.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package hindsight
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
|
||||
)
|
||||
|
||||
// Recall retrieves memories from the given bank that match the query.
|
||||
func (c *Client) Recall(ctx context.Context, bankID, query string, opts ...RecallOption) (*RecallResponse, error) {
|
||||
var cfg recallConfig
|
||||
for _, o := range opts {
|
||||
o(&cfg)
|
||||
}
|
||||
|
||||
req := &ogenapi.RecallRequest{
|
||||
Query: query,
|
||||
}
|
||||
if cfg.budget != nil {
|
||||
req.Budget = ogenapi.NewOptBudget(*cfg.budget)
|
||||
}
|
||||
if cfg.maxTokens != nil {
|
||||
req.MaxTokens = ogenapi.NewOptInt(*cfg.maxTokens)
|
||||
}
|
||||
if cfg.trace != nil {
|
||||
req.Trace = ogenapi.NewOptBool(*cfg.trace)
|
||||
}
|
||||
if cfg.queryTimestamp != nil {
|
||||
req.QueryTimestamp = ogenapi.NewOptString(*cfg.queryTimestamp)
|
||||
}
|
||||
if cfg.types != nil {
|
||||
req.Types = cfg.types
|
||||
}
|
||||
if cfg.includeOpts != nil {
|
||||
req.Include = ogenapi.NewOptIncludeOptions(*cfg.includeOpts)
|
||||
}
|
||||
if cfg.tags != nil {
|
||||
req.Tags = cfg.tags
|
||||
}
|
||||
if cfg.tagsMatch != nil {
|
||||
req.TagsMatch = ogenapi.NewOptRecallRequestTagsMatch(
|
||||
ogenapi.RecallRequestTagsMatch(*cfg.tagsMatch),
|
||||
)
|
||||
}
|
||||
|
||||
res, err := c.api.RecallMemories(ctx, req, ogenapi.RecallMemoriesParams{
|
||||
BankID: bankID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, ok := res.(*ogenapi.RecallResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
74
hindsight-clients/go/reflect.go
Normal file
74
hindsight-clients/go/reflect.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package hindsight
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-faster/jx"
|
||||
|
||||
"github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
|
||||
)
|
||||
|
||||
// Reflect performs disposition-aware reasoning using the bank's memories and
|
||||
// mental models. Returns a markdown-formatted response.
|
||||
func (c *Client) Reflect(ctx context.Context, bankID, query string, opts ...ReflectOption) (*ReflectResponse, error) {
|
||||
var cfg reflectConfig
|
||||
for _, o := range opts {
|
||||
o(&cfg)
|
||||
}
|
||||
|
||||
req := &ogenapi.ReflectRequest{
|
||||
Query: query,
|
||||
}
|
||||
if cfg.budget != nil {
|
||||
req.Budget = ogenapi.NewOptBudget(*cfg.budget)
|
||||
}
|
||||
if cfg.maxTokens != nil {
|
||||
req.MaxTokens = ogenapi.NewOptInt(*cfg.maxTokens)
|
||||
}
|
||||
if cfg.includeOpts != nil {
|
||||
req.Include = ogenapi.NewOptReflectIncludeOptions(*cfg.includeOpts)
|
||||
}
|
||||
if cfg.responseSchema != nil {
|
||||
schema, err := toResponseSchema(cfg.responseSchema)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hindsight: marshal response_schema: %w", err)
|
||||
}
|
||||
req.ResponseSchema = ogenapi.NewOptReflectRequestResponseSchema(schema)
|
||||
}
|
||||
if cfg.tags != nil {
|
||||
req.Tags = cfg.tags
|
||||
}
|
||||
if cfg.tagsMatch != nil {
|
||||
req.TagsMatch = ogenapi.NewOptReflectRequestTagsMatch(
|
||||
ogenapi.ReflectRequestTagsMatch(*cfg.tagsMatch),
|
||||
)
|
||||
}
|
||||
|
||||
res, err := c.api.Reflect(ctx, req, ogenapi.ReflectParams{
|
||||
BankID: bankID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, ok := res.(*ogenapi.ReflectResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// toResponseSchema converts a map[string]any JSON schema to the ogen type.
|
||||
func toResponseSchema(schema map[string]any) (ogenapi.ReflectRequestResponseSchema, error) {
|
||||
out := make(ogenapi.ReflectRequestResponseSchema, len(schema))
|
||||
for k, v := range schema {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[k] = jx.Raw(data)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
73
hindsight-clients/go/retain.go
Normal file
73
hindsight-clients/go/retain.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package hindsight
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
|
||||
)
|
||||
|
||||
// Retain stores a single memory in the given bank.
|
||||
// It wraps [Client.RetainBatch] for convenience.
|
||||
func (c *Client) Retain(ctx context.Context, bankID, content string, opts ...RetainOption) (*RetainResponse, error) {
|
||||
var cfg retainConfig
|
||||
for _, o := range opts {
|
||||
o(&cfg)
|
||||
}
|
||||
|
||||
item := ogenapi.MemoryItem{
|
||||
Content: content,
|
||||
}
|
||||
if cfg.timestamp != nil {
|
||||
item.Timestamp = ogenapi.NewOptDateTime(*cfg.timestamp)
|
||||
}
|
||||
if cfg.context != nil {
|
||||
item.Context = ogenapi.NewOptString(*cfg.context)
|
||||
}
|
||||
if cfg.documentID != nil {
|
||||
item.DocumentID = ogenapi.NewOptString(*cfg.documentID)
|
||||
}
|
||||
if cfg.metadata != nil {
|
||||
m := ogenapi.MemoryItemMetadata(cfg.metadata)
|
||||
item.Metadata = ogenapi.NewOptMemoryItemMetadata(m)
|
||||
}
|
||||
if cfg.entities != nil {
|
||||
item.Entities = cfg.entities
|
||||
}
|
||||
if cfg.tags != nil {
|
||||
item.Tags = cfg.tags
|
||||
}
|
||||
|
||||
return c.RetainBatch(ctx, bankID, []MemoryItem{item})
|
||||
}
|
||||
|
||||
// RetainBatch stores multiple memories in the given bank.
|
||||
func (c *Client) RetainBatch(ctx context.Context, bankID string, items []MemoryItem, opts ...RetainBatchOption) (*RetainResponse, error) {
|
||||
var cfg retainBatchConfig
|
||||
for _, o := range opts {
|
||||
o(&cfg)
|
||||
}
|
||||
|
||||
req := &ogenapi.RetainRequest{
|
||||
Items: items,
|
||||
}
|
||||
if cfg.async {
|
||||
req.Async = ogenapi.NewOptBool(true)
|
||||
}
|
||||
if cfg.documentTags != nil {
|
||||
req.DocumentTags = cfg.documentTags
|
||||
}
|
||||
|
||||
res, err := c.api.RetainMemories(ctx, req, ogenapi.RetainMemoriesParams{
|
||||
BankID: bankID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, ok := res.(*ogenapi.RetainResponse)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("hindsight: unexpected response type %T", res)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
345
hindsight-docs/docs/cookbook/applications/go-memory-service.md
Normal file
345
hindsight-docs/docs/cookbook/applications/go-memory-service.md
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
---
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
# Go Memory-Augmented API
|
||||
|
||||
:::info Complete Application
|
||||
This is a complete, runnable Go application demonstrating Hindsight integration.
|
||||
:::
|
||||
|
||||
A Go HTTP microservice that combines a domain API with Hindsight memory. Each API user gets a personal memory bank. The service remembers past interactions and uses them to provide personalized responses.
|
||||
|
||||
## Use Case
|
||||
|
||||
A **developer knowledge assistant** that remembers what technologies each user works with, what problems they've solved, and provides personalized recommendations.
|
||||
|
||||
## Features
|
||||
|
||||
- Per-user memory banks (created on first interaction)
|
||||
- Conversation history stored via retain
|
||||
- Context-aware responses via recall + reflect
|
||||
- Structured output for programmatic consumption
|
||||
- Health check and bank stats endpoints
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
go-memory-service/
|
||||
├── main.go # HTTP server, routes, handlers
|
||||
├── go.mod
|
||||
└── go.sum
|
||||
```
|
||||
|
||||
## The Code
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
var client *hindsight.Client
|
||||
|
||||
func main() {
|
||||
apiURL := envOr("HINDSIGHT_API_URL", "http://localhost:8888")
|
||||
|
||||
var err error
|
||||
client, err = hindsight.New(apiURL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /ask", handleAsk)
|
||||
mux.HandleFunc("POST /learn", handleLearn)
|
||||
mux.HandleFunc("GET /recall/{userID}", handleRecall)
|
||||
mux.HandleFunc("GET /health", handleHealth)
|
||||
|
||||
addr := envOr("ADDR", ":8080")
|
||||
log.Printf("listening on %s (hindsight: %s)", addr, apiURL)
|
||||
log.Fatal(http.ListenAndServe(addr, mux))
|
||||
}
|
||||
|
||||
// --- Request/Response types ---
|
||||
|
||||
type AskRequest struct {
|
||||
UserID string `json:"user_id"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
type AskResponse struct {
|
||||
Answer string `json:"answer"`
|
||||
Facts []string `json:"facts,omitempty"`
|
||||
}
|
||||
|
||||
type LearnRequest struct {
|
||||
UserID string `json:"user_id"`
|
||||
Content string `json:"content"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
type RecallResponse struct {
|
||||
Results []RecallFact `json:"results"`
|
||||
}
|
||||
|
||||
type RecallFact struct {
|
||||
Text string `json:"text"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// --- Handlers ---
|
||||
|
||||
// handleLearn stores new information for a user.
|
||||
func handleLearn(w http.ResponseWriter, r *http.Request) {
|
||||
var req LearnRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
bankID := bankFor(req.UserID)
|
||||
|
||||
// Ensure bank exists
|
||||
ensureBank(ctx, bankID, req.UserID)
|
||||
|
||||
// Store the memory
|
||||
var opts []hindsight.RetainOption
|
||||
if len(req.Tags) > 0 {
|
||||
opts = append(opts, hindsight.WithTags(req.Tags))
|
||||
}
|
||||
|
||||
resp, err := client.Retain(ctx, bankID, req.Content, opts...)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]any{
|
||||
"success": resp.Success,
|
||||
"bank_id": bankID,
|
||||
})
|
||||
}
|
||||
|
||||
// handleAsk answers a question using the user's memories.
|
||||
func handleAsk(w http.ResponseWriter, r *http.Request) {
|
||||
var req AskRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
bankID := bankFor(req.UserID)
|
||||
|
||||
// Ensure bank exists
|
||||
ensureBank(ctx, bankID, req.UserID)
|
||||
|
||||
// Recall relevant facts
|
||||
recallResp, err := client.Recall(ctx, bankID, req.Query,
|
||||
hindsight.WithBudget(hindsight.BudgetMid),
|
||||
hindsight.WithMaxTokens(2048),
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var facts []string
|
||||
for _, result := range recallResp.Results {
|
||||
facts = append(facts, result.Text)
|
||||
}
|
||||
|
||||
// Reflect to generate an answer
|
||||
reflectResp, err := client.Reflect(ctx, bankID, req.Query,
|
||||
hindsight.WithReflectBudget(hindsight.BudgetMid),
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Store this interaction as a new memory
|
||||
interaction := fmt.Sprintf("User asked: %q\nAssistant answered: %s", req.Query, reflectResp.Text)
|
||||
go func() {
|
||||
bgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
client.Retain(bgCtx, bankID, interaction,
|
||||
hindsight.WithContext("Q&A interaction"),
|
||||
)
|
||||
}()
|
||||
|
||||
writeJSON(w, AskResponse{
|
||||
Answer: reflectResp.Text,
|
||||
Facts: facts,
|
||||
})
|
||||
}
|
||||
|
||||
// handleRecall returns raw memories for a user.
|
||||
func handleRecall(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.PathValue("userID")
|
||||
query := r.URL.Query().Get("q")
|
||||
if query == "" {
|
||||
query = "What do you know?"
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
bankID := bankFor(userID)
|
||||
|
||||
resp, err := client.Recall(ctx, bankID, query,
|
||||
hindsight.WithBudget(hindsight.BudgetHigh),
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var results []RecallFact
|
||||
for _, result := range resp.Results {
|
||||
results = append(results, RecallFact{
|
||||
Text: result.Text,
|
||||
Type: result.Type.Or("unknown"),
|
||||
})
|
||||
}
|
||||
|
||||
writeJSON(w, RecallResponse{Results: results})
|
||||
}
|
||||
|
||||
func handleHealth(w http.ResponseWriter, _ *http.Request) {
|
||||
writeJSON(w, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
func bankFor(userID string) string {
|
||||
return "user-" + strings.ToLower(userID)
|
||||
}
|
||||
|
||||
func ensureBank(ctx context.Context, bankID, userID string) {
|
||||
client.CreateBank(ctx, bankID,
|
||||
hindsight.WithBankName(fmt.Sprintf("Memory for %s", userID)),
|
||||
hindsight.WithMission("Developer knowledge assistant. Remember technologies, problems solved, and preferences."),
|
||||
)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, v any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
func envOr(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
### 1. Start Hindsight
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=your-key
|
||||
|
||||
docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
|
||||
-e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \
|
||||
-e HINDSIGHT_API_LLM_MODEL=o3-mini \
|
||||
-v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
|
||||
ghcr.io/vectorize-io/hindsight:latest
|
||||
```
|
||||
|
||||
### 2. Start the service
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
### 3. Try it out
|
||||
|
||||
```bash
|
||||
# Teach it something
|
||||
curl -s localhost:8080/learn -d '{
|
||||
"user_id": "alice",
|
||||
"content": "I am building a Go microservice that uses gRPC and PostgreSQL",
|
||||
"tags": ["project"]
|
||||
}' | jq .
|
||||
|
||||
# Teach it more
|
||||
curl -s localhost:8080/learn -d '{
|
||||
"user_id": "alice",
|
||||
"content": "I solved a connection pooling issue by switching from pgx to pgxpool",
|
||||
"tags": ["debugging"]
|
||||
}'
|
||||
|
||||
curl -s localhost:8080/learn -d '{
|
||||
"user_id": "alice",
|
||||
"content": "I prefer structured logging with slog over zerolog",
|
||||
"tags": ["preferences"]
|
||||
}'
|
||||
|
||||
# Ask a question (uses recall + reflect)
|
||||
curl -s localhost:8080/ask -d '{
|
||||
"user_id": "alice",
|
||||
"query": "What tech stack am I using?"
|
||||
}' | jq .
|
||||
|
||||
# Raw recall
|
||||
curl -s "localhost:8080/recall/alice?q=database" | jq .
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **`/learn`** - Stores information using `Retain`. Each piece of info becomes searchable facts, entities, and relationships.
|
||||
|
||||
2. **`/ask`** - Two-phase retrieval:
|
||||
- **Recall**: Finds relevant facts from the user's memory bank
|
||||
- **Reflect**: Synthesizes a response using those facts plus disposition-aware reasoning
|
||||
- The Q&A interaction itself is stored as a new memory (fire-and-forget goroutine)
|
||||
|
||||
3. **`/recall/{userID}`** - Direct access to raw recalled facts for debugging or building custom UIs.
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### Per-User Isolation
|
||||
|
||||
Each user gets their own bank (`user-alice`, `user-bob`). Banks are created lazily on first interaction via `CreateBank` (idempotent - safe to call repeatedly).
|
||||
|
||||
### Fire-and-Forget Memory
|
||||
|
||||
The `/ask` handler stores the interaction in a background goroutine so the response isn't delayed by the retain call:
|
||||
|
||||
```go
|
||||
go func() {
|
||||
bgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
client.Retain(bgCtx, bankID, interaction)
|
||||
}()
|
||||
```
|
||||
|
||||
### Tag-Based Scoping
|
||||
|
||||
Tags partition memories within a bank. Query only `debugging` memories, or only `preferences`:
|
||||
|
||||
```bash
|
||||
# The /recall endpoint could be extended to support tag filtering:
|
||||
curl "localhost:8080/recall/alice?q=issues&tags=debugging"
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Go Quickstart](/cookbook/recipes/go-quickstart) - Core operations walkthrough
|
||||
- [Go Concurrent Pipeline](/cookbook/recipes/go-concurrent-pipeline) - Bulk data ingestion
|
||||
- [Per-User Memory](/cookbook/recipes/per-user-memory) - The pattern in depth
|
||||
- [Go SDK Reference](/sdks/go) - Full API documentation
|
||||
|
|
@ -86,6 +86,18 @@ Learn how to build with Hindsight through practical examples:
|
|||
href: "/cookbook/recipes/study_buddy",
|
||||
description: "Track study sessions, identify knowledge gaps, and get personalized review suggestions",
|
||||
tags: { sdk: "hindsight-client", topic: "Learning" }
|
||||
},
|
||||
{
|
||||
title: "Go Quickstart",
|
||||
href: "/cookbook/recipes/go-quickstart",
|
||||
description: "Get started with the Go client: retain, recall, and reflect",
|
||||
tags: { sdk: "hindsight-go", topic: "Quick Start" }
|
||||
},
|
||||
{
|
||||
title: "Go Concurrent Pipeline",
|
||||
href: "/cookbook/recipes/go-concurrent-pipeline",
|
||||
description: "Build a concurrent memory ingestion pipeline with goroutines",
|
||||
tags: { sdk: "hindsight-go", topic: "Learning" }
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
|
@ -140,6 +152,12 @@ Learn how to build with Hindsight through practical examples:
|
|||
href: "/cookbook/applications/taste-ai",
|
||||
description: "Personal food assistant with AI SDK v6 showcasing recall, mental models, and directives",
|
||||
tags: { sdk: "@vectorize-io/hindsight-ai-sdk", topic: "Recommendation" }
|
||||
},
|
||||
{
|
||||
title: "Go Memory-Augmented API",
|
||||
href: "/cookbook/applications/go-memory-service",
|
||||
description: "Go HTTP microservice with per-user memory banks for a developer knowledge assistant",
|
||||
tags: { sdk: "hindsight-go", topic: "Learning" }
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
|
|
|||
272
hindsight-docs/docs/cookbook/recipes/go-concurrent-pipeline.md
Normal file
272
hindsight-docs/docs/cookbook/recipes/go-concurrent-pipeline.md
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
---
|
||||
sidebar_position: 13
|
||||
---
|
||||
|
||||
# Go Concurrent Pipeline
|
||||
|
||||
Build a concurrent memory ingestion pipeline in Go. This recipe demonstrates how to use Go's concurrency primitives with the Hindsight client to ingest large datasets efficiently, then query them with recall and reflect.
|
||||
|
||||
## The Problem
|
||||
|
||||
You have a large dataset (log files, chat transcripts, documents) that needs to be ingested into Hindsight. Sequential ingestion is slow. Go's goroutines make it straightforward to parallelize.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌──────────┐
|
||||
│ Source │
|
||||
│ (files, │
|
||||
│ API, DB) │
|
||||
└────┬─────┘
|
||||
│
|
||||
┌────▼─────┐
|
||||
│ Producer │ reads data, sends to channel
|
||||
└────┬─────┘
|
||||
│
|
||||
┌──────────┼──────────┐
|
||||
│ │ │
|
||||
┌────▼───┐ ┌───▼────┐ ┌──▼─────┐
|
||||
│Worker 1│ │Worker 2│ │Worker N│ concurrent RetainBatch
|
||||
└────┬───┘ └───┬────┘ └──┬─────┘
|
||||
│ │ │
|
||||
└─────────┼─────────┘
|
||||
│
|
||||
┌─────▼─────┐
|
||||
│ Hindsight │
|
||||
│ API │
|
||||
└───────────┘
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Hindsight server running (see [Go Quickstart](/cookbook/recipes/go-quickstart) for setup).
|
||||
|
||||
## The Pipeline
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
const (
|
||||
batchSize = 10 // memories per batch
|
||||
numWorkers = 4 // concurrent workers
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := hindsight.New("http://localhost:8888")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
bankID := "pipeline-demo"
|
||||
|
||||
// Create the bank
|
||||
client.CreateBank(ctx, bankID,
|
||||
hindsight.WithBankName("Pipeline Demo"),
|
||||
hindsight.WithMission("Knowledge base ingested from documents"),
|
||||
)
|
||||
|
||||
// Ingest from a file (one line = one memory)
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("usage: pipeline <file>")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
count := ingest(ctx, client, bankID, os.Args[1])
|
||||
elapsed := time.Since(start)
|
||||
|
||||
fmt.Printf("\nIngested %d memories in %s (%.1f/sec)\n",
|
||||
count, elapsed, float64(count)/elapsed.Seconds())
|
||||
|
||||
// Query the ingested data
|
||||
fmt.Println("\n--- Recall ---")
|
||||
resp, _ := client.Recall(ctx, bankID, "What are the key topics?",
|
||||
hindsight.WithBudget(hindsight.BudgetHigh),
|
||||
)
|
||||
for _, r := range resp.Results {
|
||||
fmt.Printf(" - %s\n", r.Text)
|
||||
}
|
||||
|
||||
fmt.Println("\n--- Reflect ---")
|
||||
answer, _ := client.Reflect(ctx, bankID, "Summarize everything you know")
|
||||
fmt.Println(answer.Text)
|
||||
}
|
||||
|
||||
func ingest(ctx context.Context, client *hindsight.Client, bankID, filename string) int64 {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Channel for batches
|
||||
batches := make(chan []hindsight.MemoryItem, numWorkers*2)
|
||||
var ingested atomic.Int64
|
||||
|
||||
// Start workers
|
||||
var wg sync.WaitGroup
|
||||
for i := range numWorkers {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for batch := range batches {
|
||||
_, err := client.RetainBatch(ctx, bankID, batch)
|
||||
if err != nil {
|
||||
log.Printf("worker %d: batch failed: %v", id, err)
|
||||
continue
|
||||
}
|
||||
n := ingested.Add(int64(len(batch)))
|
||||
if n%100 == 0 {
|
||||
fmt.Printf(" ingested %d memories...\n", n)
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Producer: read lines and batch them
|
||||
scanner := bufio.NewScanner(f)
|
||||
var batch []hindsight.MemoryItem
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
batch = append(batch, hindsight.MemoryItem{Content: line})
|
||||
|
||||
if len(batch) >= batchSize {
|
||||
batches <- batch
|
||||
batch = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Flush remaining
|
||||
if len(batch) > 0 {
|
||||
batches <- batch
|
||||
}
|
||||
|
||||
close(batches)
|
||||
wg.Wait()
|
||||
|
||||
return ingested.Load()
|
||||
}
|
||||
```
|
||||
|
||||
## Running It
|
||||
|
||||
Create a sample data file:
|
||||
|
||||
```bash
|
||||
cat > sample_data.txt << 'EOF'
|
||||
The Go programming language was created at Google in 2007
|
||||
Go's concurrency model is based on CSP (Communicating Sequential Processes)
|
||||
Goroutines are lightweight threads managed by the Go runtime
|
||||
Channels provide typed conduits for communication between goroutines
|
||||
The sync package provides mutexes, wait groups, and other synchronization primitives
|
||||
Go modules were introduced in Go 1.11 for dependency management
|
||||
The context package provides cancellation and deadline propagation
|
||||
Go compiles to a single static binary with no external dependencies
|
||||
The standard library includes an HTTP server, JSON parser, and crypto packages
|
||||
Go 1.18 introduced generics with type parameters
|
||||
EOF
|
||||
```
|
||||
|
||||
```bash
|
||||
go run . sample_data.txt
|
||||
```
|
||||
|
||||
## Adding Tags for Partitioning
|
||||
|
||||
For larger datasets, use tags to partition memories by source or topic:
|
||||
|
||||
```go
|
||||
func ingestWithTags(ctx context.Context, client *hindsight.Client, bankID, source string, lines []string) {
|
||||
var items []hindsight.MemoryItem
|
||||
for _, line := range lines {
|
||||
items = append(items, hindsight.MemoryItem{
|
||||
Content: line,
|
||||
Tags: []string{source},
|
||||
})
|
||||
}
|
||||
|
||||
_, err := client.RetainBatch(ctx, bankID, items,
|
||||
hindsight.WithDocumentTags([]string{"bulk-import", source}),
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("ingest %s failed: %v", source, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Later, recall only from a specific source
|
||||
resp, _ := client.Recall(ctx, bankID, "What are the key concepts?",
|
||||
hindsight.WithRecallTags([]string{"go-docs"}),
|
||||
hindsight.WithRecallTagsMatch(hindsight.TagsMatchAnyStrict),
|
||||
)
|
||||
```
|
||||
|
||||
## Async Ingestion
|
||||
|
||||
For very large datasets, use async mode to avoid waiting for fact extraction:
|
||||
|
||||
```go
|
||||
// Async retain returns immediately; processing happens in background
|
||||
_, err := client.RetainBatch(ctx, bankID, items,
|
||||
hindsight.WithAsync(true),
|
||||
)
|
||||
|
||||
// Check operation status via the ogen client
|
||||
ogen := client.OgenClient()
|
||||
ops, _ := ogen.ListOperations(ctx, ogenapi.ListOperationsParams{
|
||||
BankID: bankID,
|
||||
})
|
||||
```
|
||||
|
||||
## Graceful Shutdown
|
||||
|
||||
Use `context.WithCancel` for clean cancellation:
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// In a signal handler:
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, os.Interrupt)
|
||||
go func() {
|
||||
<-sigCh
|
||||
fmt.Println("\nShutting down gracefully...")
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// Workers will stop when ctx is cancelled
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
| Tip | Why |
|
||||
|-----|-----|
|
||||
| Use `RetainBatch` over individual `Retain` | Fewer HTTP round trips |
|
||||
| Set `batchSize` to 10-50 | Balances throughput vs. memory |
|
||||
| Use 4-8 workers | Matches typical API concurrency limits |
|
||||
| Use `WithAsync(true)` for bulk imports | Returns immediately, processes in background |
|
||||
| Tag your data | Enables scoped recall without reingesting |
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Go Quickstart](/cookbook/recipes/go-quickstart) - Basic retain, recall, reflect
|
||||
- [Per-User Memory](/cookbook/recipes/per-user-memory) - One bank per user pattern
|
||||
- [Go SDK Reference](/sdks/go) - Full API reference
|
||||
216
hindsight-docs/docs/cookbook/recipes/go-quickstart.md
Normal file
216
hindsight-docs/docs/cookbook/recipes/go-quickstart.md
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
---
|
||||
sidebar_position: 12
|
||||
---
|
||||
|
||||
# Go Quickstart
|
||||
|
||||
Get started with the Hindsight Go client in under 5 minutes. This recipe covers the three core operations: **retain**, **recall**, and **reflect**.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Make sure you have Hindsight running. The easiest way is via Docker:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=your-key
|
||||
|
||||
docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
|
||||
-e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \
|
||||
-e HINDSIGHT_API_LLM_MODEL=o3-mini \
|
||||
-v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
|
||||
ghcr.io/vectorize-io/hindsight:latest
|
||||
```
|
||||
|
||||
- API: http://localhost:8888
|
||||
- UI: http://localhost:9999
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/vectorize-io/hindsight-client-go
|
||||
```
|
||||
|
||||
## Connect to Hindsight
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := hindsight.New("http://localhost:8888")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
bankID := "go-quickstart"
|
||||
```
|
||||
|
||||
## Retain: Store Information
|
||||
|
||||
The `Retain` operation pushes new memories into Hindsight. Behind the scenes, an LLM extracts key facts, temporal data, entities, and relationships.
|
||||
|
||||
```go
|
||||
// Simple retain
|
||||
_, err = client.Retain(ctx, bankID,
|
||||
"Alice works at Google as a software engineer",
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("Stored memory about Alice's job")
|
||||
|
||||
// Retain with context and timestamp
|
||||
_, err = client.Retain(ctx, bankID,
|
||||
"Alice got promoted to senior engineer",
|
||||
hindsight.WithContext("career update"),
|
||||
hindsight.WithTimestamp(time.Date(2025, 6, 15, 10, 0, 0, 0, time.UTC)),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("Stored memory about Alice's promotion")
|
||||
```
|
||||
|
||||
## Retain Batch: Store Multiple Memories
|
||||
|
||||
```go
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "Bob is a data scientist who works with Alice"},
|
||||
{Content: "Charlie manages the team and reports to the VP of Engineering"},
|
||||
{Content: "The team is working on a recommendation engine using Go"},
|
||||
}
|
||||
|
||||
resp, err := client.RetainBatch(ctx, bankID, items)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Stored %d memories\n", resp.ItemsCount)
|
||||
```
|
||||
|
||||
## Recall: Retrieve Memories
|
||||
|
||||
`Recall` retrieves memories matching a query using four parallel strategies: semantic similarity, keyword matching, entity/relationship graph traversal, and temporal filtering.
|
||||
|
||||
```go
|
||||
// Simple recall
|
||||
results, err := client.Recall(ctx, bankID, "What does Alice do?")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("\nMemories about Alice:")
|
||||
for _, r := range results.Results {
|
||||
fmt.Printf(" - %s\n", r.Text)
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// Recall with options
|
||||
results, err = client.Recall(ctx, bankID, "Who works on the team?",
|
||||
hindsight.WithBudget(hindsight.BudgetHigh),
|
||||
hindsight.WithMaxTokens(2048),
|
||||
hindsight.WithTypes([]string{"world"}),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("\nTeam memories (world facts only):")
|
||||
for _, r := range results.Results {
|
||||
fmt.Printf(" - [%s] %s\n", r.Type.Or("?"), r.Text)
|
||||
}
|
||||
```
|
||||
|
||||
## Reflect: Generate Insights
|
||||
|
||||
`Reflect` performs disposition-aware reasoning over stored memories. It retrieves relevant context, then uses an LLM to synthesize a response. Great for summarization, analysis, and Q&A.
|
||||
|
||||
```go
|
||||
answer, err := client.Reflect(ctx, bankID,
|
||||
"What should I know about this team?",
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("\nReflection:")
|
||||
fmt.Println(answer.Text)
|
||||
```
|
||||
|
||||
## Full Program
|
||||
|
||||
Here's the complete program:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := hindsight.New("http://localhost:8888")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
bankID := "go-quickstart"
|
||||
|
||||
// 1. Store memories
|
||||
client.Retain(ctx, bankID, "Alice works at Google as a software engineer")
|
||||
client.Retain(ctx, bankID, "Alice got promoted to senior engineer",
|
||||
hindsight.WithContext("career update"),
|
||||
hindsight.WithTimestamp(time.Date(2025, 6, 15, 10, 0, 0, 0, time.UTC)),
|
||||
)
|
||||
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "Bob is a data scientist who works with Alice"},
|
||||
{Content: "Charlie manages the team and reports to the VP of Engineering"},
|
||||
{Content: "The team is working on a recommendation engine using Go"},
|
||||
}
|
||||
client.RetainBatch(ctx, bankID, items)
|
||||
|
||||
// 2. Recall memories
|
||||
results, _ := client.Recall(ctx, bankID, "What does Alice do?")
|
||||
fmt.Println("Memories about Alice:")
|
||||
for _, r := range results.Results {
|
||||
fmt.Printf(" - %s\n", r.Text)
|
||||
}
|
||||
|
||||
// 3. Reflect
|
||||
answer, _ := client.Reflect(ctx, bankID, "What should I know about this team?")
|
||||
fmt.Printf("\nReflection:\n%s\n", answer.Text)
|
||||
|
||||
// 4. Cleanup
|
||||
client.DeleteBank(ctx, bankID)
|
||||
}
|
||||
```
|
||||
|
||||
## Memory Types
|
||||
|
||||
Hindsight organizes memory into distinct networks:
|
||||
|
||||
| Type | Description | Example |
|
||||
|------|-------------|---------|
|
||||
| **World** | Facts about the world | "Alice works at Google" |
|
||||
| **Experience** | Agent's own experiences | "I helped Alice debug her code" |
|
||||
| **Observation** | Complex models from reflection | "Alice is a senior IC focused on ML" |
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Go Concurrent Pipeline](/cookbook/recipes/go-concurrent-pipeline) - Build a concurrent ingestion pipeline
|
||||
- [Per-User Memory](/cookbook/recipes/per-user-memory) - One bank per user pattern
|
||||
- [Go SDK Reference](/sdks/go) - Full API reference
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# CLI Reference
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Embedded SDK (hindsight-embed)
|
||||
|
|
|
|||
294
hindsight-docs/docs/sdks/go.md
Normal file
294
hindsight-docs/docs/sdks/go.md
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Go Client
|
||||
|
||||
Official Go client for the Hindsight API, built on [ogen](https://github.com/ogen-go/ogen) for strongly-typed code generation from the OpenAPI 3.1 spec.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/vectorize-io/hindsight-client-go
|
||||
```
|
||||
|
||||
Requires Go 1.25+.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := hindsight.New("http://localhost:8888")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Retain a memory
|
||||
client.Retain(ctx, "my-bank", "Alice works at Google")
|
||||
|
||||
// Recall memories
|
||||
resp, _ := client.Recall(ctx, "my-bank", "What does Alice do?")
|
||||
for _, r := range resp.Results {
|
||||
fmt.Println(r.Text)
|
||||
}
|
||||
|
||||
// Reflect - generate response with disposition
|
||||
answer, _ := client.Reflect(ctx, "my-bank", "Tell me about Alice")
|
||||
fmt.Println(answer.Text)
|
||||
}
|
||||
```
|
||||
|
||||
## Client Initialization
|
||||
|
||||
```go
|
||||
import hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
|
||||
// Default client
|
||||
client, err := hindsight.New("http://localhost:8888")
|
||||
|
||||
// With API key authentication
|
||||
client, err := hindsight.New("http://localhost:8888",
|
||||
hindsight.WithAPIKey("your-api-key"),
|
||||
)
|
||||
|
||||
// With custom HTTP client
|
||||
client, err := hindsight.New("http://localhost:8888",
|
||||
hindsight.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
|
||||
)
|
||||
```
|
||||
|
||||
## Core Operations
|
||||
|
||||
### Retain (Store Memory)
|
||||
|
||||
```go
|
||||
// Simple
|
||||
_, err := client.Retain(ctx, "my-bank", "Alice works at Google as a software engineer")
|
||||
|
||||
// With options
|
||||
_, err := client.Retain(ctx, "my-bank", "Alice got promoted",
|
||||
hindsight.WithContext("career update"),
|
||||
hindsight.WithTimestamp(time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)),
|
||||
hindsight.WithDocumentID("conversation_001"),
|
||||
hindsight.WithMetadata(map[string]string{"source": "slack"}),
|
||||
hindsight.WithTags([]string{"career", "updates"}),
|
||||
)
|
||||
```
|
||||
|
||||
### Retain Batch
|
||||
|
||||
```go
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "Alice works at Google"},
|
||||
{Content: "Bob is a data scientist"},
|
||||
}
|
||||
|
||||
_, err := client.RetainBatch(ctx, "my-bank", items,
|
||||
hindsight.WithDocumentTags([]string{"team-info"}),
|
||||
hindsight.WithAsync(false), // Set true for background processing
|
||||
)
|
||||
```
|
||||
|
||||
### Recall (Search)
|
||||
|
||||
```go
|
||||
// Simple
|
||||
resp, err := client.Recall(ctx, "my-bank", "What does Alice do?")
|
||||
|
||||
for _, r := range resp.Results {
|
||||
fmt.Printf(" %s (type: %s)\n", r.Text, r.Type.Or("unknown"))
|
||||
}
|
||||
|
||||
// With options
|
||||
resp, err := client.Recall(ctx, "my-bank", "What does Alice do?",
|
||||
hindsight.WithTypes([]string{"world", "experience"}), // Filter by fact type
|
||||
hindsight.WithMaxTokens(4096),
|
||||
hindsight.WithBudget(hindsight.BudgetHigh), // BudgetLow, BudgetMid, BudgetHigh
|
||||
hindsight.WithTrace(true), // Include execution trace
|
||||
hindsight.WithRecallTags([]string{"career"}),
|
||||
hindsight.WithRecallTagsMatch(hindsight.TagsMatchAnyStrict),
|
||||
)
|
||||
```
|
||||
|
||||
### Reflect (Generate Response)
|
||||
|
||||
```go
|
||||
resp, err := client.Reflect(ctx, "my-bank", "What should I know about Alice?",
|
||||
hindsight.WithReflectBudget(hindsight.BudgetMid),
|
||||
hindsight.WithReflectMaxTokens(2048),
|
||||
)
|
||||
|
||||
fmt.Println(resp.Text) // Generated markdown response
|
||||
```
|
||||
|
||||
### Reflect with Structured Output
|
||||
|
||||
```go
|
||||
resp, err := client.Reflect(ctx, "my-bank",
|
||||
"What programming language should I learn for data science?",
|
||||
hindsight.WithResponseSchema(map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"recommendation": map[string]any{"type": "string"},
|
||||
"reasons": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
|
||||
"confidence": map[string]any{"type": "string"},
|
||||
},
|
||||
"required": []any{"recommendation", "reasons"},
|
||||
}),
|
||||
hindsight.WithReflectMaxTokens(4096),
|
||||
)
|
||||
|
||||
// resp.StructuredOutput contains the parsed JSON schema result
|
||||
```
|
||||
|
||||
## Bank Management
|
||||
|
||||
### Create Bank
|
||||
|
||||
```go
|
||||
_, err := client.CreateBank(ctx, "my-bank",
|
||||
hindsight.WithBankName("Assistant"),
|
||||
hindsight.WithMission("Helpful AI assistant tracking user preferences."),
|
||||
hindsight.WithDisposition(hindsight.DispositionTraits{
|
||||
Skepticism: 3, // 1-5: trusting to skeptical
|
||||
Literalism: 3, // 1-5: flexible to literal
|
||||
Empathy: 3, // 1-5: detached to empathetic
|
||||
}),
|
||||
)
|
||||
```
|
||||
|
||||
### Other Bank Operations
|
||||
|
||||
```go
|
||||
// Get bank profile
|
||||
profile, err := client.GetBankProfile(ctx, "my-bank")
|
||||
fmt.Println(profile.Mission)
|
||||
|
||||
// List all banks
|
||||
banks, err := client.ListBanks(ctx)
|
||||
for _, b := range banks.Banks {
|
||||
fmt.Println(b.BankID)
|
||||
}
|
||||
|
||||
// Update mission
|
||||
_, err = client.SetMission(ctx, "my-bank", "New mission statement")
|
||||
|
||||
// Update disposition
|
||||
_, err = client.UpdateDisposition(ctx, "my-bank", hindsight.DispositionTraits{
|
||||
Skepticism: 4,
|
||||
Literalism: 2,
|
||||
Empathy: 5,
|
||||
})
|
||||
|
||||
// Delete bank (destructive, cannot be undone)
|
||||
err = client.DeleteBank(ctx, "my-bank")
|
||||
```
|
||||
|
||||
## Tag Filtering
|
||||
|
||||
Tags provide visibility scoping for memories. Use them to partition memories within a bank.
|
||||
|
||||
```go
|
||||
// Store tagged memories
|
||||
client.Retain(ctx, "my-bank", "Project X meeting notes",
|
||||
hindsight.WithTags([]string{"project_x", "meetings"}),
|
||||
)
|
||||
|
||||
// Recall only project_x memories (strict - excludes untagged)
|
||||
resp, _ := client.Recall(ctx, "my-bank", "What happened in meetings?",
|
||||
hindsight.WithRecallTags([]string{"project_x"}),
|
||||
hindsight.WithRecallTagsMatch(hindsight.TagsMatchAnyStrict),
|
||||
)
|
||||
|
||||
// Reflect scoped to tags
|
||||
resp, _ := client.Reflect(ctx, "my-bank", "Summarize project X",
|
||||
hindsight.WithReflectTags([]string{"project_x"}),
|
||||
hindsight.WithReflectTagsMatch(hindsight.TagsMatchAnyStrict),
|
||||
)
|
||||
```
|
||||
|
||||
| Match Mode | Behavior |
|
||||
|-----------|----------|
|
||||
| `TagsMatchAny` | OR matching, includes untagged memories |
|
||||
| `TagsMatchAll` | AND matching, includes untagged memories |
|
||||
| `TagsMatchAnyStrict` | OR matching, excludes untagged memories |
|
||||
| `TagsMatchAllStrict` | AND matching, excludes untagged memories |
|
||||
|
||||
## Advanced Usage (ogen Client)
|
||||
|
||||
For operations not covered by the high-level wrapper (documents, entities, mental models, directives, operations), access the generated ogen client directly:
|
||||
|
||||
```go
|
||||
import "github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
|
||||
|
||||
ogen := client.OgenClient()
|
||||
|
||||
// List entities
|
||||
entities, err := ogen.ListEntities(ctx, ogenapi.ListEntitiesParams{
|
||||
BankID: "my-bank",
|
||||
})
|
||||
|
||||
// Create a mental model
|
||||
model, err := ogen.CreateMentalModel(ctx,
|
||||
&ogenapi.CreateMentalModelRequest{
|
||||
Name: ogenapi.NewOptString("User Preferences"),
|
||||
SourceQuery: "What are this user's preferences and habits?",
|
||||
},
|
||||
ogenapi.CreateMentalModelParams{BankID: "my-bank"},
|
||||
)
|
||||
|
||||
// Create a directive
|
||||
directive, err := ogen.CreateDirective(ctx,
|
||||
&ogenapi.CreateDirectiveRequest{
|
||||
Name: "Response Style",
|
||||
Content: "Always respond in a friendly, concise manner",
|
||||
},
|
||||
ogenapi.CreateDirectiveParams{BankID: "my-bank"},
|
||||
)
|
||||
|
||||
// List operations (async tasks)
|
||||
ops, err := ogen.ListOperations(ctx, ogenapi.ListOperationsParams{
|
||||
BankID: "my-bank",
|
||||
})
|
||||
|
||||
// Get bank stats
|
||||
stats, err := ogen.GetAgentStats(ctx, ogenapi.GetAgentStatsParams{
|
||||
BankID: "my-bank",
|
||||
})
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The client returns standard Go errors. HTTP errors from the API are returned as ogen error types:
|
||||
|
||||
```go
|
||||
resp, err := client.Recall(ctx, "nonexistent-bank", "query")
|
||||
if err != nil {
|
||||
// Handle error - could be network, HTTP 4xx/5xx, etc.
|
||||
log.Printf("recall failed: %v", err)
|
||||
}
|
||||
```
|
||||
|
||||
## Code Generation
|
||||
|
||||
The Go client is built on [ogen](https://github.com/ogen-go/ogen). The generated code lives in `internal/ogenapi/` and provides full type safety with no `interface{}` or reflection.
|
||||
|
||||
To regenerate after API changes:
|
||||
|
||||
```bash
|
||||
cd hindsight-clients/go
|
||||
go generate ./...
|
||||
go build ./...
|
||||
```
|
||||
|
|
@ -0,0 +1,345 @@
|
|||
---
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
# Go Memory-Augmented API
|
||||
|
||||
:::info Complete Application
|
||||
This is a complete, runnable Go application demonstrating Hindsight integration.
|
||||
:::
|
||||
|
||||
A Go HTTP microservice that combines a domain API with Hindsight memory. Each API user gets a personal memory bank. The service remembers past interactions and uses them to provide personalized responses.
|
||||
|
||||
## Use Case
|
||||
|
||||
A **developer knowledge assistant** that remembers what technologies each user works with, what problems they've solved, and provides personalized recommendations.
|
||||
|
||||
## Features
|
||||
|
||||
- Per-user memory banks (created on first interaction)
|
||||
- Conversation history stored via retain
|
||||
- Context-aware responses via recall + reflect
|
||||
- Structured output for programmatic consumption
|
||||
- Health check and bank stats endpoints
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
go-memory-service/
|
||||
├── main.go # HTTP server, routes, handlers
|
||||
├── go.mod
|
||||
└── go.sum
|
||||
```
|
||||
|
||||
## The Code
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
var client *hindsight.Client
|
||||
|
||||
func main() {
|
||||
apiURL := envOr("HINDSIGHT_API_URL", "http://localhost:8888")
|
||||
|
||||
var err error
|
||||
client, err = hindsight.New(apiURL)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /ask", handleAsk)
|
||||
mux.HandleFunc("POST /learn", handleLearn)
|
||||
mux.HandleFunc("GET /recall/{userID}", handleRecall)
|
||||
mux.HandleFunc("GET /health", handleHealth)
|
||||
|
||||
addr := envOr("ADDR", ":8080")
|
||||
log.Printf("listening on %s (hindsight: %s)", addr, apiURL)
|
||||
log.Fatal(http.ListenAndServe(addr, mux))
|
||||
}
|
||||
|
||||
// --- Request/Response types ---
|
||||
|
||||
type AskRequest struct {
|
||||
UserID string `json:"user_id"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
type AskResponse struct {
|
||||
Answer string `json:"answer"`
|
||||
Facts []string `json:"facts,omitempty"`
|
||||
}
|
||||
|
||||
type LearnRequest struct {
|
||||
UserID string `json:"user_id"`
|
||||
Content string `json:"content"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
type RecallResponse struct {
|
||||
Results []RecallFact `json:"results"`
|
||||
}
|
||||
|
||||
type RecallFact struct {
|
||||
Text string `json:"text"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// --- Handlers ---
|
||||
|
||||
// handleLearn stores new information for a user.
|
||||
func handleLearn(w http.ResponseWriter, r *http.Request) {
|
||||
var req LearnRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
bankID := bankFor(req.UserID)
|
||||
|
||||
// Ensure bank exists
|
||||
ensureBank(ctx, bankID, req.UserID)
|
||||
|
||||
// Store the memory
|
||||
var opts []hindsight.RetainOption
|
||||
if len(req.Tags) > 0 {
|
||||
opts = append(opts, hindsight.WithTags(req.Tags))
|
||||
}
|
||||
|
||||
resp, err := client.Retain(ctx, bankID, req.Content, opts...)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, map[string]any{
|
||||
"success": resp.Success,
|
||||
"bank_id": bankID,
|
||||
})
|
||||
}
|
||||
|
||||
// handleAsk answers a question using the user's memories.
|
||||
func handleAsk(w http.ResponseWriter, r *http.Request) {
|
||||
var req AskRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
bankID := bankFor(req.UserID)
|
||||
|
||||
// Ensure bank exists
|
||||
ensureBank(ctx, bankID, req.UserID)
|
||||
|
||||
// Recall relevant facts
|
||||
recallResp, err := client.Recall(ctx, bankID, req.Query,
|
||||
hindsight.WithBudget(hindsight.BudgetMid),
|
||||
hindsight.WithMaxTokens(2048),
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var facts []string
|
||||
for _, result := range recallResp.Results {
|
||||
facts = append(facts, result.Text)
|
||||
}
|
||||
|
||||
// Reflect to generate an answer
|
||||
reflectResp, err := client.Reflect(ctx, bankID, req.Query,
|
||||
hindsight.WithReflectBudget(hindsight.BudgetMid),
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Store this interaction as a new memory
|
||||
interaction := fmt.Sprintf("User asked: %q\nAssistant answered: %s", req.Query, reflectResp.Text)
|
||||
go func() {
|
||||
bgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
client.Retain(bgCtx, bankID, interaction,
|
||||
hindsight.WithContext("Q&A interaction"),
|
||||
)
|
||||
}()
|
||||
|
||||
writeJSON(w, AskResponse{
|
||||
Answer: reflectResp.Text,
|
||||
Facts: facts,
|
||||
})
|
||||
}
|
||||
|
||||
// handleRecall returns raw memories for a user.
|
||||
func handleRecall(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.PathValue("userID")
|
||||
query := r.URL.Query().Get("q")
|
||||
if query == "" {
|
||||
query = "What do you know?"
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
bankID := bankFor(userID)
|
||||
|
||||
resp, err := client.Recall(ctx, bankID, query,
|
||||
hindsight.WithBudget(hindsight.BudgetHigh),
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var results []RecallFact
|
||||
for _, result := range resp.Results {
|
||||
results = append(results, RecallFact{
|
||||
Text: result.Text,
|
||||
Type: result.Type.Or("unknown"),
|
||||
})
|
||||
}
|
||||
|
||||
writeJSON(w, RecallResponse{Results: results})
|
||||
}
|
||||
|
||||
func handleHealth(w http.ResponseWriter, _ *http.Request) {
|
||||
writeJSON(w, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
func bankFor(userID string) string {
|
||||
return "user-" + strings.ToLower(userID)
|
||||
}
|
||||
|
||||
func ensureBank(ctx context.Context, bankID, userID string) {
|
||||
client.CreateBank(ctx, bankID,
|
||||
hindsight.WithBankName(fmt.Sprintf("Memory for %s", userID)),
|
||||
hindsight.WithMission("Developer knowledge assistant. Remember technologies, problems solved, and preferences."),
|
||||
)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, v any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
func envOr(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
### 1. Start Hindsight
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=your-key
|
||||
|
||||
docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
|
||||
-e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \
|
||||
-e HINDSIGHT_API_LLM_MODEL=o3-mini \
|
||||
-v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
|
||||
ghcr.io/vectorize-io/hindsight:latest
|
||||
```
|
||||
|
||||
### 2. Start the service
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
### 3. Try it out
|
||||
|
||||
```bash
|
||||
# Teach it something
|
||||
curl -s localhost:8080/learn -d '{
|
||||
"user_id": "alice",
|
||||
"content": "I am building a Go microservice that uses gRPC and PostgreSQL",
|
||||
"tags": ["project"]
|
||||
}' | jq .
|
||||
|
||||
# Teach it more
|
||||
curl -s localhost:8080/learn -d '{
|
||||
"user_id": "alice",
|
||||
"content": "I solved a connection pooling issue by switching from pgx to pgxpool",
|
||||
"tags": ["debugging"]
|
||||
}'
|
||||
|
||||
curl -s localhost:8080/learn -d '{
|
||||
"user_id": "alice",
|
||||
"content": "I prefer structured logging with slog over zerolog",
|
||||
"tags": ["preferences"]
|
||||
}'
|
||||
|
||||
# Ask a question (uses recall + reflect)
|
||||
curl -s localhost:8080/ask -d '{
|
||||
"user_id": "alice",
|
||||
"query": "What tech stack am I using?"
|
||||
}' | jq .
|
||||
|
||||
# Raw recall
|
||||
curl -s "localhost:8080/recall/alice?q=database" | jq .
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **`/learn`** - Stores information using `Retain`. Each piece of info becomes searchable facts, entities, and relationships.
|
||||
|
||||
2. **`/ask`** - Two-phase retrieval:
|
||||
- **Recall**: Finds relevant facts from the user's memory bank
|
||||
- **Reflect**: Synthesizes a response using those facts plus disposition-aware reasoning
|
||||
- The Q&A interaction itself is stored as a new memory (fire-and-forget goroutine)
|
||||
|
||||
3. **`/recall/{userID}`** - Direct access to raw recalled facts for debugging or building custom UIs.
|
||||
|
||||
## Key Patterns
|
||||
|
||||
### Per-User Isolation
|
||||
|
||||
Each user gets their own bank (`user-alice`, `user-bob`). Banks are created lazily on first interaction via `CreateBank` (idempotent - safe to call repeatedly).
|
||||
|
||||
### Fire-and-Forget Memory
|
||||
|
||||
The `/ask` handler stores the interaction in a background goroutine so the response isn't delayed by the retain call:
|
||||
|
||||
```go
|
||||
go func() {
|
||||
bgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
client.Retain(bgCtx, bankID, interaction)
|
||||
}()
|
||||
```
|
||||
|
||||
### Tag-Based Scoping
|
||||
|
||||
Tags partition memories within a bank. Query only `debugging` memories, or only `preferences`:
|
||||
|
||||
```bash
|
||||
# The /recall endpoint could be extended to support tag filtering:
|
||||
curl "localhost:8080/recall/alice?q=issues&tags=debugging"
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Go Quickstart](/cookbook/recipes/go-quickstart) - Core operations walkthrough
|
||||
- [Go Concurrent Pipeline](/cookbook/recipes/go-concurrent-pipeline) - Bulk data ingestion
|
||||
- [Per-User Memory](/cookbook/recipes/per-user-memory) - The pattern in depth
|
||||
- [Go SDK Reference](/sdks/go) - Full API documentation
|
||||
|
|
@ -86,6 +86,18 @@ Learn how to build with Hindsight through practical examples:
|
|||
href: "/cookbook/recipes/study_buddy",
|
||||
description: "Track study sessions, identify knowledge gaps, and get personalized review suggestions",
|
||||
tags: { sdk: "hindsight-client", topic: "Learning" }
|
||||
},
|
||||
{
|
||||
title: "Go Quickstart",
|
||||
href: "/cookbook/recipes/go-quickstart",
|
||||
description: "Get started with the Go client: retain, recall, and reflect",
|
||||
tags: { sdk: "hindsight-go", topic: "Quick Start" }
|
||||
},
|
||||
{
|
||||
title: "Go Concurrent Pipeline",
|
||||
href: "/cookbook/recipes/go-concurrent-pipeline",
|
||||
description: "Build a concurrent memory ingestion pipeline with goroutines",
|
||||
tags: { sdk: "hindsight-go", topic: "Learning" }
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
|
@ -140,6 +152,12 @@ Learn how to build with Hindsight through practical examples:
|
|||
href: "/cookbook/applications/taste-ai",
|
||||
description: "Personal food assistant with AI SDK v6 showcasing recall, mental models, and directives",
|
||||
tags: { sdk: "@vectorize-io/hindsight-ai-sdk", topic: "Recommendation" }
|
||||
},
|
||||
{
|
||||
title: "Go Memory-Augmented API",
|
||||
href: "/cookbook/applications/go-memory-service",
|
||||
description: "Go HTTP microservice with per-user memory banks for a developer knowledge assistant",
|
||||
tags: { sdk: "hindsight-go", topic: "Learning" }
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,272 @@
|
|||
---
|
||||
sidebar_position: 13
|
||||
---
|
||||
|
||||
# Go Concurrent Pipeline
|
||||
|
||||
Build a concurrent memory ingestion pipeline in Go. This recipe demonstrates how to use Go's concurrency primitives with the Hindsight client to ingest large datasets efficiently, then query them with recall and reflect.
|
||||
|
||||
## The Problem
|
||||
|
||||
You have a large dataset (log files, chat transcripts, documents) that needs to be ingested into Hindsight. Sequential ingestion is slow. Go's goroutines make it straightforward to parallelize.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌──────────┐
|
||||
│ Source │
|
||||
│ (files, │
|
||||
│ API, DB) │
|
||||
└────┬─────┘
|
||||
│
|
||||
┌────▼─────┐
|
||||
│ Producer │ reads data, sends to channel
|
||||
└────┬─────┘
|
||||
│
|
||||
┌──────────┼──────────┐
|
||||
│ │ │
|
||||
┌────▼───┐ ┌───▼────┐ ┌──▼─────┐
|
||||
│Worker 1│ │Worker 2│ │Worker N│ concurrent RetainBatch
|
||||
└────┬───┘ └───┬────┘ └──┬─────┘
|
||||
│ │ │
|
||||
└─────────┼─────────┘
|
||||
│
|
||||
┌─────▼─────┐
|
||||
│ Hindsight │
|
||||
│ API │
|
||||
└───────────┘
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Hindsight server running (see [Go Quickstart](/cookbook/recipes/go-quickstart) for setup).
|
||||
|
||||
## The Pipeline
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
const (
|
||||
batchSize = 10 // memories per batch
|
||||
numWorkers = 4 // concurrent workers
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := hindsight.New("http://localhost:8888")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
bankID := "pipeline-demo"
|
||||
|
||||
// Create the bank
|
||||
client.CreateBank(ctx, bankID,
|
||||
hindsight.WithBankName("Pipeline Demo"),
|
||||
hindsight.WithMission("Knowledge base ingested from documents"),
|
||||
)
|
||||
|
||||
// Ingest from a file (one line = one memory)
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("usage: pipeline <file>")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
count := ingest(ctx, client, bankID, os.Args[1])
|
||||
elapsed := time.Since(start)
|
||||
|
||||
fmt.Printf("\nIngested %d memories in %s (%.1f/sec)\n",
|
||||
count, elapsed, float64(count)/elapsed.Seconds())
|
||||
|
||||
// Query the ingested data
|
||||
fmt.Println("\n--- Recall ---")
|
||||
resp, _ := client.Recall(ctx, bankID, "What are the key topics?",
|
||||
hindsight.WithBudget(hindsight.BudgetHigh),
|
||||
)
|
||||
for _, r := range resp.Results {
|
||||
fmt.Printf(" - %s\n", r.Text)
|
||||
}
|
||||
|
||||
fmt.Println("\n--- Reflect ---")
|
||||
answer, _ := client.Reflect(ctx, bankID, "Summarize everything you know")
|
||||
fmt.Println(answer.Text)
|
||||
}
|
||||
|
||||
func ingest(ctx context.Context, client *hindsight.Client, bankID, filename string) int64 {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Channel for batches
|
||||
batches := make(chan []hindsight.MemoryItem, numWorkers*2)
|
||||
var ingested atomic.Int64
|
||||
|
||||
// Start workers
|
||||
var wg sync.WaitGroup
|
||||
for i := range numWorkers {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for batch := range batches {
|
||||
_, err := client.RetainBatch(ctx, bankID, batch)
|
||||
if err != nil {
|
||||
log.Printf("worker %d: batch failed: %v", id, err)
|
||||
continue
|
||||
}
|
||||
n := ingested.Add(int64(len(batch)))
|
||||
if n%100 == 0 {
|
||||
fmt.Printf(" ingested %d memories...\n", n)
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Producer: read lines and batch them
|
||||
scanner := bufio.NewScanner(f)
|
||||
var batch []hindsight.MemoryItem
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
batch = append(batch, hindsight.MemoryItem{Content: line})
|
||||
|
||||
if len(batch) >= batchSize {
|
||||
batches <- batch
|
||||
batch = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Flush remaining
|
||||
if len(batch) > 0 {
|
||||
batches <- batch
|
||||
}
|
||||
|
||||
close(batches)
|
||||
wg.Wait()
|
||||
|
||||
return ingested.Load()
|
||||
}
|
||||
```
|
||||
|
||||
## Running It
|
||||
|
||||
Create a sample data file:
|
||||
|
||||
```bash
|
||||
cat > sample_data.txt << 'EOF'
|
||||
The Go programming language was created at Google in 2007
|
||||
Go's concurrency model is based on CSP (Communicating Sequential Processes)
|
||||
Goroutines are lightweight threads managed by the Go runtime
|
||||
Channels provide typed conduits for communication between goroutines
|
||||
The sync package provides mutexes, wait groups, and other synchronization primitives
|
||||
Go modules were introduced in Go 1.11 for dependency management
|
||||
The context package provides cancellation and deadline propagation
|
||||
Go compiles to a single static binary with no external dependencies
|
||||
The standard library includes an HTTP server, JSON parser, and crypto packages
|
||||
Go 1.18 introduced generics with type parameters
|
||||
EOF
|
||||
```
|
||||
|
||||
```bash
|
||||
go run . sample_data.txt
|
||||
```
|
||||
|
||||
## Adding Tags for Partitioning
|
||||
|
||||
For larger datasets, use tags to partition memories by source or topic:
|
||||
|
||||
```go
|
||||
func ingestWithTags(ctx context.Context, client *hindsight.Client, bankID, source string, lines []string) {
|
||||
var items []hindsight.MemoryItem
|
||||
for _, line := range lines {
|
||||
items = append(items, hindsight.MemoryItem{
|
||||
Content: line,
|
||||
Tags: []string{source},
|
||||
})
|
||||
}
|
||||
|
||||
_, err := client.RetainBatch(ctx, bankID, items,
|
||||
hindsight.WithDocumentTags([]string{"bulk-import", source}),
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("ingest %s failed: %v", source, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Later, recall only from a specific source
|
||||
resp, _ := client.Recall(ctx, bankID, "What are the key concepts?",
|
||||
hindsight.WithRecallTags([]string{"go-docs"}),
|
||||
hindsight.WithRecallTagsMatch(hindsight.TagsMatchAnyStrict),
|
||||
)
|
||||
```
|
||||
|
||||
## Async Ingestion
|
||||
|
||||
For very large datasets, use async mode to avoid waiting for fact extraction:
|
||||
|
||||
```go
|
||||
// Async retain returns immediately; processing happens in background
|
||||
_, err := client.RetainBatch(ctx, bankID, items,
|
||||
hindsight.WithAsync(true),
|
||||
)
|
||||
|
||||
// Check operation status via the ogen client
|
||||
ogen := client.OgenClient()
|
||||
ops, _ := ogen.ListOperations(ctx, ogenapi.ListOperationsParams{
|
||||
BankID: bankID,
|
||||
})
|
||||
```
|
||||
|
||||
## Graceful Shutdown
|
||||
|
||||
Use `context.WithCancel` for clean cancellation:
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// In a signal handler:
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, os.Interrupt)
|
||||
go func() {
|
||||
<-sigCh
|
||||
fmt.Println("\nShutting down gracefully...")
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// Workers will stop when ctx is cancelled
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
| Tip | Why |
|
||||
|-----|-----|
|
||||
| Use `RetainBatch` over individual `Retain` | Fewer HTTP round trips |
|
||||
| Set `batchSize` to 10-50 | Balances throughput vs. memory |
|
||||
| Use 4-8 workers | Matches typical API concurrency limits |
|
||||
| Use `WithAsync(true)` for bulk imports | Returns immediately, processes in background |
|
||||
| Tag your data | Enables scoped recall without reingesting |
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Go Quickstart](/cookbook/recipes/go-quickstart) - Basic retain, recall, reflect
|
||||
- [Per-User Memory](/cookbook/recipes/per-user-memory) - One bank per user pattern
|
||||
- [Go SDK Reference](/sdks/go) - Full API reference
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
---
|
||||
sidebar_position: 12
|
||||
---
|
||||
|
||||
# Go Quickstart
|
||||
|
||||
Get started with the Hindsight Go client in under 5 minutes. This recipe covers the three core operations: **retain**, **recall**, and **reflect**.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Make sure you have Hindsight running. The easiest way is via Docker:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=your-key
|
||||
|
||||
docker run --rm -it --pull always -p 8888:8888 -p 9999:9999 \
|
||||
-e HINDSIGHT_API_LLM_API_KEY=$OPENAI_API_KEY \
|
||||
-e HINDSIGHT_API_LLM_MODEL=o3-mini \
|
||||
-v $HOME/.hindsight-docker:/home/hindsight/.pg0 \
|
||||
ghcr.io/vectorize-io/hindsight:latest
|
||||
```
|
||||
|
||||
- API: http://localhost:8888
|
||||
- UI: http://localhost:9999
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/vectorize-io/hindsight-client-go
|
||||
```
|
||||
|
||||
## Connect to Hindsight
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := hindsight.New("http://localhost:8888")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
bankID := "go-quickstart"
|
||||
```
|
||||
|
||||
## Retain: Store Information
|
||||
|
||||
The `Retain` operation pushes new memories into Hindsight. Behind the scenes, an LLM extracts key facts, temporal data, entities, and relationships.
|
||||
|
||||
```go
|
||||
// Simple retain
|
||||
_, err = client.Retain(ctx, bankID,
|
||||
"Alice works at Google as a software engineer",
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("Stored memory about Alice's job")
|
||||
|
||||
// Retain with context and timestamp
|
||||
_, err = client.Retain(ctx, bankID,
|
||||
"Alice got promoted to senior engineer",
|
||||
hindsight.WithContext("career update"),
|
||||
hindsight.WithTimestamp(time.Date(2025, 6, 15, 10, 0, 0, 0, time.UTC)),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("Stored memory about Alice's promotion")
|
||||
```
|
||||
|
||||
## Retain Batch: Store Multiple Memories
|
||||
|
||||
```go
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "Bob is a data scientist who works with Alice"},
|
||||
{Content: "Charlie manages the team and reports to the VP of Engineering"},
|
||||
{Content: "The team is working on a recommendation engine using Go"},
|
||||
}
|
||||
|
||||
resp, err := client.RetainBatch(ctx, bankID, items)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Stored %d memories\n", resp.ItemsCount)
|
||||
```
|
||||
|
||||
## Recall: Retrieve Memories
|
||||
|
||||
`Recall` retrieves memories matching a query using four parallel strategies: semantic similarity, keyword matching, entity/relationship graph traversal, and temporal filtering.
|
||||
|
||||
```go
|
||||
// Simple recall
|
||||
results, err := client.Recall(ctx, bankID, "What does Alice do?")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("\nMemories about Alice:")
|
||||
for _, r := range results.Results {
|
||||
fmt.Printf(" - %s\n", r.Text)
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// Recall with options
|
||||
results, err = client.Recall(ctx, bankID, "Who works on the team?",
|
||||
hindsight.WithBudget(hindsight.BudgetHigh),
|
||||
hindsight.WithMaxTokens(2048),
|
||||
hindsight.WithTypes([]string{"world"}),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("\nTeam memories (world facts only):")
|
||||
for _, r := range results.Results {
|
||||
fmt.Printf(" - [%s] %s\n", r.Type.Or("?"), r.Text)
|
||||
}
|
||||
```
|
||||
|
||||
## Reflect: Generate Insights
|
||||
|
||||
`Reflect` performs disposition-aware reasoning over stored memories. It retrieves relevant context, then uses an LLM to synthesize a response. Great for summarization, analysis, and Q&A.
|
||||
|
||||
```go
|
||||
answer, err := client.Reflect(ctx, bankID,
|
||||
"What should I know about this team?",
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("\nReflection:")
|
||||
fmt.Println(answer.Text)
|
||||
```
|
||||
|
||||
## Full Program
|
||||
|
||||
Here's the complete program:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := hindsight.New("http://localhost:8888")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
bankID := "go-quickstart"
|
||||
|
||||
// 1. Store memories
|
||||
client.Retain(ctx, bankID, "Alice works at Google as a software engineer")
|
||||
client.Retain(ctx, bankID, "Alice got promoted to senior engineer",
|
||||
hindsight.WithContext("career update"),
|
||||
hindsight.WithTimestamp(time.Date(2025, 6, 15, 10, 0, 0, 0, time.UTC)),
|
||||
)
|
||||
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "Bob is a data scientist who works with Alice"},
|
||||
{Content: "Charlie manages the team and reports to the VP of Engineering"},
|
||||
{Content: "The team is working on a recommendation engine using Go"},
|
||||
}
|
||||
client.RetainBatch(ctx, bankID, items)
|
||||
|
||||
// 2. Recall memories
|
||||
results, _ := client.Recall(ctx, bankID, "What does Alice do?")
|
||||
fmt.Println("Memories about Alice:")
|
||||
for _, r := range results.Results {
|
||||
fmt.Printf(" - %s\n", r.Text)
|
||||
}
|
||||
|
||||
// 3. Reflect
|
||||
answer, _ := client.Reflect(ctx, bankID, "What should I know about this team?")
|
||||
fmt.Printf("\nReflection:\n%s\n", answer.Text)
|
||||
|
||||
// 4. Cleanup
|
||||
client.DeleteBank(ctx, bankID)
|
||||
}
|
||||
```
|
||||
|
||||
## Memory Types
|
||||
|
||||
Hindsight organizes memory into distinct networks:
|
||||
|
||||
| Type | Description | Example |
|
||||
|------|-------------|---------|
|
||||
| **World** | Facts about the world | "Alice works at Google" |
|
||||
| **Experience** | Agent's own experiences | "I helped Alice debug her code" |
|
||||
| **Observation** | Complex models from reflection | "Alice is a senior IC focused on ML" |
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Go Concurrent Pipeline](/cookbook/recipes/go-concurrent-pipeline) - Build a concurrent ingestion pipeline
|
||||
- [Per-User Memory](/cookbook/recipes/per-user-memory) - One bank per user pattern
|
||||
- [Go SDK Reference](/sdks/go) - Full API reference
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# CLI Reference
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Embedded SDK (hindsight-embed)
|
||||
|
|
|
|||
294
hindsight-docs/versioned_docs/version-0.4/sdks/go.md
Normal file
294
hindsight-docs/versioned_docs/version-0.4/sdks/go.md
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Go Client
|
||||
|
||||
Official Go client for the Hindsight API, built on [ogen](https://github.com/ogen-go/ogen) for strongly-typed code generation from the OpenAPI 3.1 spec.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/vectorize-io/hindsight-client-go
|
||||
```
|
||||
|
||||
Requires Go 1.25+.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, err := hindsight.New("http://localhost:8888")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Retain a memory
|
||||
client.Retain(ctx, "my-bank", "Alice works at Google")
|
||||
|
||||
// Recall memories
|
||||
resp, _ := client.Recall(ctx, "my-bank", "What does Alice do?")
|
||||
for _, r := range resp.Results {
|
||||
fmt.Println(r.Text)
|
||||
}
|
||||
|
||||
// Reflect - generate response with disposition
|
||||
answer, _ := client.Reflect(ctx, "my-bank", "Tell me about Alice")
|
||||
fmt.Println(answer.Text)
|
||||
}
|
||||
```
|
||||
|
||||
## Client Initialization
|
||||
|
||||
```go
|
||||
import hindsight "github.com/vectorize-io/hindsight-client-go"
|
||||
|
||||
// Default client
|
||||
client, err := hindsight.New("http://localhost:8888")
|
||||
|
||||
// With API key authentication
|
||||
client, err := hindsight.New("http://localhost:8888",
|
||||
hindsight.WithAPIKey("your-api-key"),
|
||||
)
|
||||
|
||||
// With custom HTTP client
|
||||
client, err := hindsight.New("http://localhost:8888",
|
||||
hindsight.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
|
||||
)
|
||||
```
|
||||
|
||||
## Core Operations
|
||||
|
||||
### Retain (Store Memory)
|
||||
|
||||
```go
|
||||
// Simple
|
||||
_, err := client.Retain(ctx, "my-bank", "Alice works at Google as a software engineer")
|
||||
|
||||
// With options
|
||||
_, err := client.Retain(ctx, "my-bank", "Alice got promoted",
|
||||
hindsight.WithContext("career update"),
|
||||
hindsight.WithTimestamp(time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)),
|
||||
hindsight.WithDocumentID("conversation_001"),
|
||||
hindsight.WithMetadata(map[string]string{"source": "slack"}),
|
||||
hindsight.WithTags([]string{"career", "updates"}),
|
||||
)
|
||||
```
|
||||
|
||||
### Retain Batch
|
||||
|
||||
```go
|
||||
items := []hindsight.MemoryItem{
|
||||
{Content: "Alice works at Google"},
|
||||
{Content: "Bob is a data scientist"},
|
||||
}
|
||||
|
||||
_, err := client.RetainBatch(ctx, "my-bank", items,
|
||||
hindsight.WithDocumentTags([]string{"team-info"}),
|
||||
hindsight.WithAsync(false), // Set true for background processing
|
||||
)
|
||||
```
|
||||
|
||||
### Recall (Search)
|
||||
|
||||
```go
|
||||
// Simple
|
||||
resp, err := client.Recall(ctx, "my-bank", "What does Alice do?")
|
||||
|
||||
for _, r := range resp.Results {
|
||||
fmt.Printf(" %s (type: %s)\n", r.Text, r.Type.Or("unknown"))
|
||||
}
|
||||
|
||||
// With options
|
||||
resp, err := client.Recall(ctx, "my-bank", "What does Alice do?",
|
||||
hindsight.WithTypes([]string{"world", "experience"}), // Filter by fact type
|
||||
hindsight.WithMaxTokens(4096),
|
||||
hindsight.WithBudget(hindsight.BudgetHigh), // BudgetLow, BudgetMid, BudgetHigh
|
||||
hindsight.WithTrace(true), // Include execution trace
|
||||
hindsight.WithRecallTags([]string{"career"}),
|
||||
hindsight.WithRecallTagsMatch(hindsight.TagsMatchAnyStrict),
|
||||
)
|
||||
```
|
||||
|
||||
### Reflect (Generate Response)
|
||||
|
||||
```go
|
||||
resp, err := client.Reflect(ctx, "my-bank", "What should I know about Alice?",
|
||||
hindsight.WithReflectBudget(hindsight.BudgetMid),
|
||||
hindsight.WithReflectMaxTokens(2048),
|
||||
)
|
||||
|
||||
fmt.Println(resp.Text) // Generated markdown response
|
||||
```
|
||||
|
||||
### Reflect with Structured Output
|
||||
|
||||
```go
|
||||
resp, err := client.Reflect(ctx, "my-bank",
|
||||
"What programming language should I learn for data science?",
|
||||
hindsight.WithResponseSchema(map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"recommendation": map[string]any{"type": "string"},
|
||||
"reasons": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
|
||||
"confidence": map[string]any{"type": "string"},
|
||||
},
|
||||
"required": []any{"recommendation", "reasons"},
|
||||
}),
|
||||
hindsight.WithReflectMaxTokens(4096),
|
||||
)
|
||||
|
||||
// resp.StructuredOutput contains the parsed JSON schema result
|
||||
```
|
||||
|
||||
## Bank Management
|
||||
|
||||
### Create Bank
|
||||
|
||||
```go
|
||||
_, err := client.CreateBank(ctx, "my-bank",
|
||||
hindsight.WithBankName("Assistant"),
|
||||
hindsight.WithMission("Helpful AI assistant tracking user preferences."),
|
||||
hindsight.WithDisposition(hindsight.DispositionTraits{
|
||||
Skepticism: 3, // 1-5: trusting to skeptical
|
||||
Literalism: 3, // 1-5: flexible to literal
|
||||
Empathy: 3, // 1-5: detached to empathetic
|
||||
}),
|
||||
)
|
||||
```
|
||||
|
||||
### Other Bank Operations
|
||||
|
||||
```go
|
||||
// Get bank profile
|
||||
profile, err := client.GetBankProfile(ctx, "my-bank")
|
||||
fmt.Println(profile.Mission)
|
||||
|
||||
// List all banks
|
||||
banks, err := client.ListBanks(ctx)
|
||||
for _, b := range banks.Banks {
|
||||
fmt.Println(b.BankID)
|
||||
}
|
||||
|
||||
// Update mission
|
||||
_, err = client.SetMission(ctx, "my-bank", "New mission statement")
|
||||
|
||||
// Update disposition
|
||||
_, err = client.UpdateDisposition(ctx, "my-bank", hindsight.DispositionTraits{
|
||||
Skepticism: 4,
|
||||
Literalism: 2,
|
||||
Empathy: 5,
|
||||
})
|
||||
|
||||
// Delete bank (destructive, cannot be undone)
|
||||
err = client.DeleteBank(ctx, "my-bank")
|
||||
```
|
||||
|
||||
## Tag Filtering
|
||||
|
||||
Tags provide visibility scoping for memories. Use them to partition memories within a bank.
|
||||
|
||||
```go
|
||||
// Store tagged memories
|
||||
client.Retain(ctx, "my-bank", "Project X meeting notes",
|
||||
hindsight.WithTags([]string{"project_x", "meetings"}),
|
||||
)
|
||||
|
||||
// Recall only project_x memories (strict - excludes untagged)
|
||||
resp, _ := client.Recall(ctx, "my-bank", "What happened in meetings?",
|
||||
hindsight.WithRecallTags([]string{"project_x"}),
|
||||
hindsight.WithRecallTagsMatch(hindsight.TagsMatchAnyStrict),
|
||||
)
|
||||
|
||||
// Reflect scoped to tags
|
||||
resp, _ := client.Reflect(ctx, "my-bank", "Summarize project X",
|
||||
hindsight.WithReflectTags([]string{"project_x"}),
|
||||
hindsight.WithReflectTagsMatch(hindsight.TagsMatchAnyStrict),
|
||||
)
|
||||
```
|
||||
|
||||
| Match Mode | Behavior |
|
||||
|-----------|----------|
|
||||
| `TagsMatchAny` | OR matching, includes untagged memories |
|
||||
| `TagsMatchAll` | AND matching, includes untagged memories |
|
||||
| `TagsMatchAnyStrict` | OR matching, excludes untagged memories |
|
||||
| `TagsMatchAllStrict` | AND matching, excludes untagged memories |
|
||||
|
||||
## Advanced Usage (ogen Client)
|
||||
|
||||
For operations not covered by the high-level wrapper (documents, entities, mental models, directives, operations), access the generated ogen client directly:
|
||||
|
||||
```go
|
||||
import "github.com/vectorize-io/hindsight-client-go/internal/ogenapi"
|
||||
|
||||
ogen := client.OgenClient()
|
||||
|
||||
// List entities
|
||||
entities, err := ogen.ListEntities(ctx, ogenapi.ListEntitiesParams{
|
||||
BankID: "my-bank",
|
||||
})
|
||||
|
||||
// Create a mental model
|
||||
model, err := ogen.CreateMentalModel(ctx,
|
||||
&ogenapi.CreateMentalModelRequest{
|
||||
Name: ogenapi.NewOptString("User Preferences"),
|
||||
SourceQuery: "What are this user's preferences and habits?",
|
||||
},
|
||||
ogenapi.CreateMentalModelParams{BankID: "my-bank"},
|
||||
)
|
||||
|
||||
// Create a directive
|
||||
directive, err := ogen.CreateDirective(ctx,
|
||||
&ogenapi.CreateDirectiveRequest{
|
||||
Name: "Response Style",
|
||||
Content: "Always respond in a friendly, concise manner",
|
||||
},
|
||||
ogenapi.CreateDirectiveParams{BankID: "my-bank"},
|
||||
)
|
||||
|
||||
// List operations (async tasks)
|
||||
ops, err := ogen.ListOperations(ctx, ogenapi.ListOperationsParams{
|
||||
BankID: "my-bank",
|
||||
})
|
||||
|
||||
// Get bank stats
|
||||
stats, err := ogen.GetAgentStats(ctx, ogenapi.GetAgentStatsParams{
|
||||
BankID: "my-bank",
|
||||
})
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The client returns standard Go errors. HTTP errors from the API are returned as ogen error types:
|
||||
|
||||
```go
|
||||
resp, err := client.Recall(ctx, "nonexistent-bank", "query")
|
||||
if err != nil {
|
||||
// Handle error - could be network, HTTP 4xx/5xx, etc.
|
||||
log.Printf("recall failed: %v", err)
|
||||
}
|
||||
```
|
||||
|
||||
## Code Generation
|
||||
|
||||
The Go client is built on [ogen](https://github.com/ogen-go/ogen). The generated code lives in `internal/ogenapi/` and provides full type safety with no `interface{}` or reflection.
|
||||
|
||||
To regenerate after API changes:
|
||||
|
||||
```bash
|
||||
cd hindsight-clients/go
|
||||
go generate ./...
|
||||
go build ./...
|
||||
```
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# Script to generate Python and TypeScript clients from OpenAPI spec using openapi-generator
|
||||
# Script to generate Python, TypeScript, and Go clients from OpenAPI spec
|
||||
# Note: Rust client is auto-generated at build time via build.rs (uses progenitor)
|
||||
# Usage: ./scripts/generate-clients.sh
|
||||
|
||||
|
|
@ -24,6 +24,7 @@ echo "This script generates clients for:"
|
|||
echo " - Rust (via progenitor in build.rs)"
|
||||
echo " - Python (via openapi-generator)"
|
||||
echo " - TypeScript (via @hey-api/openapi-ts)"
|
||||
echo " - Go (via ogen)"
|
||||
echo ""
|
||||
|
||||
# Check if OpenAPI spec exists
|
||||
|
|
@ -333,6 +334,25 @@ npm run generate
|
|||
echo "✓ TypeScript client generated at $TYPESCRIPT_CLIENT_DIR"
|
||||
echo ""
|
||||
|
||||
# Generate Go client
|
||||
echo "=================================================="
|
||||
echo "Generating Go client..."
|
||||
echo "=================================================="
|
||||
|
||||
GO_CLIENT_DIR="$CLIENTS_DIR/go"
|
||||
|
||||
if ! command -v go &> /dev/null; then
|
||||
echo "⚠ Go not found, skipping Go client generation"
|
||||
echo " Install Go 1.25+ from https://go.dev/dl/"
|
||||
else
|
||||
echo "Regenerating Go client (via ogen)..."
|
||||
cd "$GO_CLIENT_DIR"
|
||||
go generate ./...
|
||||
go build ./...
|
||||
echo "✓ Go client generated at $GO_CLIENT_DIR"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "=================================================="
|
||||
echo "✅ Client generation complete!"
|
||||
echo "=================================================="
|
||||
|
|
@ -340,6 +360,7 @@ echo ""
|
|||
echo "Rust client: $RUST_CLIENT_DIR"
|
||||
echo "Python client: $PYTHON_CLIENT_DIR"
|
||||
echo "TypeScript client: $TYPESCRIPT_CLIENT_DIR"
|
||||
echo "Go client: $GO_CLIENT_DIR"
|
||||
echo ""
|
||||
echo "⚠️ Important: The maintained wrapper hindsight_client.py and README.md were preserved"
|
||||
echo ""
|
||||
|
|
|
|||
Loading…
Reference in a new issue