diff --git a/README.md b/README.md index cbac38f..fd9e201 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,14 @@ A Go client library for the Graphiti HTTP API. ## Features - Full coverage of Graphiti HTTP API endpoints +- **7 specialized advanced search methods** for different query patterns: + - Temporal Window Search - search within time ranges + - Entity Relationships Search - explore entity connections + - Diverse Results Search - get non-redundant results using MMR + - Episode Context Search - search conversation history + - Successful Tools Search - find frequently mentioned techniques + - Recent Context Search - get recent information with recency bias + - Entity By Label Search - filter entities by type/label - Optional Langfuse observation tracking for monitoring and debugging - Configurable HTTP client and timeouts - Type-safe request and response structures @@ -15,6 +23,12 @@ A Go client library for the Graphiti HTTP API. go get github.com/vxcontrol/graphiti-go-client ``` +## Quick Start + +See the complete working examples: +- **[Base Usage Example](./examples/base-usage-example/main.go)** - Basic operations and common patterns +- **[Advanced Search Example](./examples/advanced-search-example/main.go)** - All 7 advanced search methods + ## Usage ### Creating a Client @@ -253,6 +267,108 @@ if err != nil { fmt.Printf("Fact: %s\n", fact.Fact) ``` +### Advanced Search Methods + +The client provides specialized search methods for different use cases: + +#### Temporal Window Search + +Search for context within a specific time window: + +```go +result, err := client.TemporalWindowSearch(graphiti.TemporalSearchRequest{ + Query: "user activities", + GroupID: &groupID, + TimeStart: time.Now().Add(-24 * time.Hour), + TimeEnd: time.Now(), + MaxResults: 10, +}) +if err != nil { + log.Fatal(err) +} +fmt.Printf("Found %d edges, %d nodes, %d episodes\n", + len(result.Edges), len(result.Nodes), len(result.Episodes)) +``` + +#### Entity Relationships Search + +Find relationships and related entities from a center node: + +```go +result, err := client.EntityRelationshipsSearch(graphiti.EntityRelationshipSearchRequest{ + Query: "related entities", + GroupID: &groupID, + CenterNodeUUID: "entity-uuid-123", + MaxDepth: 2, + NodeLabels: &[]string{"PERSON", "ORGANIZATION"}, + MaxResults: 20, +}) +``` + +#### Diverse Results Search + +Get diverse, non-redundant results using Maximal Marginal Relevance (MMR): + +```go +result, err := client.DiverseResultsSearch(graphiti.DiverseSearchRequest{ + Query: "user interests and hobbies", + GroupID: &groupID, + DiversityLevel: "medium", // "low", "medium", or "high" + MaxResults: 10, +}) +``` + +#### Episode Context Search + +Search through agent responses and conversation context: + +```go +result, err := client.EpisodeContextSearch(graphiti.EpisodeContextSearchRequest{ + Query: "tool execution results", + GroupID: &groupID, + MaxResults: 5, +}) +``` + +#### Successful Tools Search + +Find frequently mentioned successful tools or techniques: + +```go +result, err := client.SuccessfulToolsSearch(graphiti.SuccessfulToolsSearchRequest{ + Query: "successful exploits", + GroupID: &groupID, + MinMentions: 2, + MaxResults: 10, +}) +``` + +#### Recent Context Search + +Get most recent relevant context with recency bias: + +```go +result, err := client.RecentContextSearch(graphiti.RecentContextSearchRequest{ + Query: "recent discoveries", + GroupID: &groupID, + RecencyWindow: "6h", // e.g., "1h", "24h", "7d" + MaxResults: 10, +}) +``` + +#### Entity By Label Search + +Search for entities by their labels/types: + +```go +result, err := client.EntityByLabelSearch(graphiti.EntityByLabelSearchRequest{ + Query: "vulnerable services", + GroupID: &groupID, + NodeLabels: []string{"SERVICE", "VULNERABILITY"}, + MaxResults: 20, +}) +``` + ### Delete Operations ```go @@ -269,161 +385,6 @@ result, err := client.DeleteGroup("group-id-123") result, err := client.Clear() ``` -## Advanced Search Methods - -The client provides specialized search methods for advanced querying and analysis. - -### Temporal Window Search - -Search for all relevant context (facts, entities, and agent responses) from a specific time window: - -```go -result, err := client.TemporalWindowSearch(graphiti.TemporalSearchRequest{ - Query: "What attacks were performed?", - GroupID: &groupID, - TimeStart: time.Now().Add(-24 * time.Hour), - TimeEnd: time.Now(), - MaxResults: 15, -}) -if err != nil { - log.Fatal(err) -} - -for _, edge := range result.Edges { - fmt.Printf("Fact: %s (score: %.2f)\n", edge.Fact, result.EdgeScores[i]) -} -``` - -### Entity Relationships Search - -Find all relationships and related entities starting from a specific discovered entity using graph traversal: - -```go -result, err := client.EntityRelationshipsSearch(graphiti.EntityRelationshipSearchRequest{ - Query: "What vulnerabilities are related to this service?", - GroupID: &groupID, - CenterNodeUUID: "service-uuid-123", - MaxDepth: 2, - NodeLabels: &[]string{"VULNERABILITY", "EXPLOIT"}, - MaxResults: 20, -}) -if err != nil { - log.Fatal(err) -} - -if result.CenterNode != nil { - fmt.Printf("Center: %s\n", result.CenterNode.Name) -} -for i, node := range result.Nodes { - fmt.Printf("Related: %s (distance: %.2f)\n", node.Name, result.NodeDistances[i]) -} -``` - -### Diverse Results Search - -Get diverse, non-redundant results using Maximal Marginal Relevance (MMR) to prevent receiving repetitive information: - -```go -result, err := client.DiverseResultsSearch(graphiti.DiverseSearchRequest{ - Query: "Find different attack vectors", - GroupID: &groupID, - DiversityLevel: "high", // "low", "medium", or "high" - MaxResults: 10, -}) -if err != nil { - log.Fatal(err) -} - -for i, edge := range result.Edges { - fmt.Printf("Fact: %s (MMR score: %.2f)\n", edge.Fact, result.EdgeMMRScores[i]) -} -``` - -### Episode Context Search - -Search through complete agent responses, reasoning, and tool execution records: - -```go -result, err := client.EpisodeContextSearch(graphiti.EpisodeContextSearchRequest{ - Query: "Show me nmap scan results", - GroupID: &groupID, - AgentTypes: &[]string{"pentester"}, - IncludeToolCalls: true, - MaxResults: 10, -}) -if err != nil { - log.Fatal(err) -} - -for i, episode := range result.Episodes { - fmt.Printf("Episode: %s (score: %.2f)\n", episode.Content, result.RerankerScores[i]) -} -``` - -### Successful Tools Search - -Find successful tool executions and attack patterns, prioritizing facts that led to successful exploitation: - -```go -result, err := client.SuccessfulToolsSearch(graphiti.SuccessfulToolsSearchRequest{ - Query: "Find successful exploits", - GroupID: &groupID, - ToolNames: &[]string{"metasploit", "sqlmap"}, - MinMentions: 2, - MaxResults: 15, -}) -if err != nil { - log.Fatal(err) -} - -for i, edge := range result.Edges { - fmt.Printf("Fact: %s (mentions: %.0f)\n", edge.Fact, result.EdgeMentionCounts[i]) -} -``` - -### Recent Context Search - -Retrieve the most recent relevant context, biased toward recent actions and discoveries: - -```go -result, err := client.RecentContextSearch(graphiti.RecentContextSearchRequest{ - Query: "What was discovered recently?", - GroupID: &groupID, - RecencyWindow: "24h", // "1h", "6h", "24h", or "7d" - MaxResults: 10, -}) -if err != nil { - log.Fatal(err) -} - -fmt.Printf("Searching from %s to %s\n", result.TimeWindow.Start, result.TimeWindow.End) -for i, edge := range result.Edges { - fmt.Printf("Recent fact: %s (score: %.2f)\n", edge.Fact, result.EdgeScores[i]) -} -``` - -### Entity By Label Search - -Search for specific entity types (IPs, services, vulnerabilities, tools, etc.) with label-based filtering: - -```go -result, err := client.EntityByLabelSearch(graphiti.EntityByLabelSearchRequest{ - Query: "Find all vulnerable services", - GroupID: &groupID, - NodeLabels: []string{"SERVICE", "VULNERABILITY"}, - EdgeTypes: &[]string{"HAS_VULNERABILITY", "EXPLOITS"}, - MaxResults: 25, -}) -if err != nil { - log.Fatal(err) -} - -for i, node := range result.Nodes { - fmt.Printf("Entity: %s [%s] (score: %.2f)\n", - node.Name, strings.Join(node.Labels, ", "), result.NodeScores[i]) -} -``` - ## Types ### Observation @@ -580,5 +541,27 @@ if err != nil { ## Examples -- **[Basic Example](./example/main.go)**: Complete working demonstration of the client, including proper handling of asynchronous operations and data verification. -- **[Advanced Search Example](./advanced_search_example/advanced_search_example.go)**: Comprehensive demonstration of all advanced search methods including temporal queries, entity relationships, diverse results, episode context, successful tools, recent context, and entity label filtering. +Two complete working examples are available: + +### [Base Usage Example](./examples/base-usage-example/main.go) + +Demonstrates basic operations: +- Client initialization and health checks +- Adding messages with Langfuse observation tracking +- Basic search and memory retrieval +- Entity node creation +- Episode management +- Proper handling of asynchronous operations + +### [Advanced Search Example](./examples/advanced-search-example/main.go) + +Comprehensive demonstration of all 7 advanced search methods: +- **Temporal Window Search** - query within specific time ranges +- **Entity Relationships Search** - explore entity connections and graph traversal +- **Diverse Results Search** - get non-redundant results using MMR +- **Episode Context Search** - search through conversation history and tool outputs +- **Successful Tools Search** - find frequently mentioned successful techniques +- **Recent Context Search** - retrieve recent information with recency bias +- **Entity By Label Search** - filter entities by type/label + +This example uses a realistic penetration testing scenario with detailed test data to demonstrate each search method's capabilities. diff --git a/advanced_search_example/advanced_search_example b/advanced_search_example/advanced_search_example deleted file mode 100755 index 7df6608..0000000 Binary files a/advanced_search_example/advanced_search_example and /dev/null differ diff --git a/advanced_search_example/go.mod b/advanced_search_example/go.mod deleted file mode 100644 index 21a1232..0000000 --- a/advanced_search_example/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module advanced_search_example - -go 1.25.3 - -require github.com/vxcontrol/graphiti-go-client v0.0.0 - -require github.com/google/uuid v1.6.0 - -replace github.com/vxcontrol/graphiti-go-client => ../ diff --git a/example/advanced_search_example.go b/example/advanced_search_example.go deleted file mode 100644 index 1cf072a..0000000 --- a/example/advanced_search_example.go +++ /dev/null @@ -1,275 +0,0 @@ -package main - -import ( - "fmt" - "log" - "time" - - "github.com/google/uuid" - graphiti "github.com/vxcontrol/graphiti-go-client" -) - -// This example demonstrates the advanced search capabilities of the Graphiti Go client. -// It shows how to use specialized search methods for temporal queries, entity relationships, -// diverse results, episodes, successful tools, recent context, and entity label filtering. - -func main() { - // Create a client - client := graphiti.NewClient("http://localhost:8000", graphiti.WithTimeout(60*time.Second)) - - // Health check - fmt.Println("=== Health Check ===") - health, err := client.HealthCheck() - if err != nil { - log.Fatalf("Health check failed: %v", err) - } - fmt.Printf("Status: %s\n\n", health.Status) - - // Create a unique group ID for this example - groupID := uuid.New().String() - fmt.Printf("Using group ID: %s\n\n", groupID) - - // Add some security test messages to demonstrate advanced search - fmt.Println("=== Adding Security Test Messages ===") - messages := []graphiti.Message{ - { - Content: "Performed nmap scan on target 192.168.1.100. Found open ports: 22 (SSH), 80 (HTTP), 443 (HTTPS).", - Author: "pentester", - Timestamp: time.Now().Add(-2 * time.Hour), - }, - { - Content: "Detected Apache 2.4.41 running on port 80 with potential CVE-2021-41773 vulnerability.", - Author: "scanner", - Timestamp: time.Now().Add(-90 * time.Minute), - }, - { - Content: "Successfully exploited CVE-2021-41773 using metasploit. Gained shell access.", - Author: "exploit_tool", - Timestamp: time.Now().Add(-60 * time.Minute), - }, - { - Content: "Performed privilege escalation using CVE-2021-3156 (sudo vulnerability).", - Author: "exploit_tool", - Timestamp: time.Now().Add(-45 * time.Minute), - }, - } - - addResult, err := client.AddMessages(graphiti.AddMessagesRequest{ - GroupID: groupID, - Messages: messages, - }) - if err != nil { - log.Fatalf("Failed to add messages: %v", err) - } - fmt.Printf("%s: %v\n\n", addResult.Message, addResult.Success) - - // Wait for processing - fmt.Println("Waiting for messages to be processed...") - maxAttempts := 12 - pollInterval := 5 * time.Second - - for attempt := 1; attempt <= maxAttempts; attempt++ { - fmt.Printf(" Polling (attempt %d/%d)...\n", attempt, maxAttempts) - episodes, err := client.GetEpisodes(groupID, 10) - if err != nil { - log.Printf(" Warning: Failed to get episodes: %v", err) - } else if len(episodes) > 0 { - fmt.Printf(" ✓ Found %d episodes, ready for advanced search!\n\n", len(episodes)) - break - } - - if attempt == maxAttempts { - log.Printf("Warning: No episodes found after waiting. Continuing anyway...\n\n") - } - time.Sleep(pollInterval) - } - - // Example 1: Temporal Window Search - fmt.Println("=== Example 1: Temporal Window Search ===") - fmt.Println("Searching for activities in the last 3 hours...") - temporalResult, err := client.TemporalWindowSearch(graphiti.TemporalSearchRequest{ - Query: "What security tests were performed?", - GroupID: &groupID, - TimeStart: time.Now().Add(-3 * time.Hour), - TimeEnd: time.Now(), - MaxResults: 10, - }) - if err != nil { - log.Printf("Temporal search failed: %v\n", err) - } else { - fmt.Printf("Found %d edges, %d nodes, %d episodes\n", - len(temporalResult.Edges), len(temporalResult.Nodes), len(temporalResult.Episodes)) - fmt.Printf("Time window: %s to %s\n", - temporalResult.TimeWindow.Start.Format(time.RFC3339), - temporalResult.TimeWindow.End.Format(time.RFC3339)) - for i, episode := range temporalResult.Episodes { - score := 0.0 - if i < len(temporalResult.EpisodeScores) { - score = temporalResult.EpisodeScores[i] - } - fmt.Printf(" - Episode (score: %.2f): %s\n", score, episode.Content[:min(80, len(episode.Content))]) - } - } - fmt.Println() - - // Example 2: Diverse Results Search - fmt.Println("=== Example 2: Diverse Results Search ===") - fmt.Println("Getting diverse attack vectors with high diversity...") - diverseResult, err := client.DiverseResultsSearch(graphiti.DiverseSearchRequest{ - Query: "Show different attack methods and vulnerabilities", - GroupID: &groupID, - DiversityLevel: "high", - MaxResults: 10, - }) - if err != nil { - log.Printf("Diverse search failed: %v\n", err) - } else { - fmt.Printf("Found %d diverse edges, %d nodes, %d episodes\n", - len(diverseResult.Edges), len(diverseResult.Nodes), len(diverseResult.Episodes)) - for i, edge := range diverseResult.Edges { - score := 0.0 - if i < len(diverseResult.EdgeMMRScores) { - score = diverseResult.EdgeMMRScores[i] - } - fmt.Printf(" - Edge (MMR: %.2f): %s\n", score, edge.Fact[:min(80, len(edge.Fact))]) - } - } - fmt.Println() - - // Example 3: Episode Context Search - fmt.Println("=== Example 3: Episode Context Search ===") - fmt.Println("Searching for tool execution records...") - episodeResult, err := client.EpisodeContextSearch(graphiti.EpisodeContextSearchRequest{ - Query: "Show tool execution results", - GroupID: &groupID, - IncludeToolCalls: true, - MaxResults: 10, - }) - if err != nil { - log.Printf("Episode search failed: %v\n", err) - } else { - fmt.Printf("Found %d episodes with %d mentioned nodes\n", - len(episodeResult.Episodes), len(episodeResult.MentionedNodes)) - for i, episode := range episodeResult.Episodes { - score := 0.0 - if i < len(episodeResult.RerankerScores) { - score = episodeResult.RerankerScores[i] - } - fmt.Printf(" - Episode (score: %.2f) from %s: %s\n", - score, episode.Source, episode.Content[:min(80, len(episode.Content))]) - } - } - fmt.Println() - - // Example 4: Successful Tools Search - fmt.Println("=== Example 4: Successful Tools Search ===") - fmt.Println("Finding successful exploits with min 1 mention...") - toolsResult, err := client.SuccessfulToolsSearch(graphiti.SuccessfulToolsSearchRequest{ - Query: "Find successful exploits", - GroupID: &groupID, - MinMentions: 1, - MaxResults: 10, - }) - if err != nil { - log.Printf("Successful tools search failed: %v\n", err) - } else { - fmt.Printf("Found %d edges with sufficient mentions\n", len(toolsResult.Edges)) - for i, edge := range toolsResult.Edges { - mentions := 0.0 - if i < len(toolsResult.EdgeMentionCounts) { - mentions = toolsResult.EdgeMentionCounts[i] - } - fmt.Printf(" - Edge (mentions: %.0f): %s\n", mentions, edge.Fact[:min(80, len(edge.Fact))]) - } - } - fmt.Println() - - // Example 5: Recent Context Search - fmt.Println("=== Example 5: Recent Context Search ===") - fmt.Println("Getting recent discoveries from the last 6 hours...") - recentResult, err := client.RecentContextSearch(graphiti.RecentContextSearchRequest{ - Query: "What was discovered recently?", - GroupID: &groupID, - RecencyWindow: "6h", - MaxResults: 10, - }) - if err != nil { - log.Printf("Recent context search failed: %v\n", err) - } else { - fmt.Printf("Found %d recent edges, %d nodes, %d episodes\n", - len(recentResult.Edges), len(recentResult.Nodes), len(recentResult.Episodes)) - fmt.Printf("Recency window: %s to %s\n", - recentResult.TimeWindow.Start.Format(time.RFC3339), - recentResult.TimeWindow.End.Format(time.RFC3339)) - for i, edge := range recentResult.Edges { - score := 0.0 - if i < len(recentResult.EdgeScores) { - score = recentResult.EdgeScores[i] - } - fmt.Printf(" - Recent edge (score: %.2f): %s\n", score, edge.Fact[:min(80, len(edge.Fact))]) - } - } - fmt.Println() - - // Example 6: Entity By Label Search - fmt.Println("=== Example 6: Entity By Label Search ===") - fmt.Println("Searching for SERVICE and VULNERABILITY entities...") - labelResult, err := client.EntityByLabelSearch(graphiti.EntityByLabelSearchRequest{ - Query: "Find vulnerable services", - GroupID: &groupID, - NodeLabels: []string{"SERVICE", "VULNERABILITY", "IP_ADDRESS"}, - MaxResults: 20, - }) - if err != nil { - log.Printf("Entity label search failed: %v\n", err) - } else { - fmt.Printf("Found %d nodes, %d edges\n", len(labelResult.Nodes), len(labelResult.Edges)) - for i, node := range labelResult.Nodes { - score := 0.0 - if i < len(labelResult.NodeScores) { - score = labelResult.NodeScores[i] - } - fmt.Printf(" - Node (score: %.2f): %s [labels: %v]\n", - score, node.Name, node.Labels) - } - } - fmt.Println() - - // Optional: Entity Relationships Search (requires a known entity UUID) - // This example shows the pattern, but won't execute without a real entity UUID - fmt.Println("=== Example 7: Entity Relationships Search (Pattern) ===") - fmt.Println("To use EntityRelationshipsSearch, you need a center node UUID.") - fmt.Println("Example usage:") - fmt.Println(` - result, err := client.EntityRelationshipsSearch(graphiti.EntityRelationshipSearchRequest{ - Query: "What is connected to this service?", - GroupID: &groupID, - CenterNodeUUID: "your-entity-uuid-here", - MaxDepth: 2, - NodeLabels: &[]string{"VULNERABILITY", "EXPLOIT"}, - MaxResults: 20, - }) - if err != nil { - log.Printf("Entity relationships search failed: %v\n", err) - } else { - fmt.Printf("Found %d related nodes at various distances\n", len(result.Nodes)) - } - `) - fmt.Println() - - // Cleanup: delete the group - fmt.Println("=== Cleanup ===") - deleteResult, err := client.DeleteGroup(groupID) - if err != nil { - log.Printf("Warning: Failed to delete group: %v", err) - } else { - fmt.Printf("%s: %v\n", deleteResult.Message, deleteResult.Success) - } -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} diff --git a/example/example b/example/example deleted file mode 100755 index 3a0653b..0000000 Binary files a/example/example and /dev/null differ diff --git a/example/go.mod b/example/go.mod deleted file mode 100644 index 43b5c25..0000000 --- a/example/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/vxcontrol/graphiti-go-client/example - -go 1.23 - -replace github.com/vxcontrol/graphiti-go-client => ../ - -require ( - github.com/google/uuid v1.6.0 - github.com/vxcontrol/graphiti-go-client v0.0.0-00010101000000-000000000000 -) - diff --git a/examples/advanced-search-example/go.mod b/examples/advanced-search-example/go.mod new file mode 100644 index 0000000..24b71cc --- /dev/null +++ b/examples/advanced-search-example/go.mod @@ -0,0 +1,10 @@ +module github.com/vxcontrol/graphiti-go-client/examples/advanced-search-example + +go 1.23 + +replace github.com/vxcontrol/graphiti-go-client => ../../ + +require ( + github.com/google/uuid v1.6.0 + github.com/vxcontrol/graphiti-go-client v0.0.0-00010101000000-000000000000 +) \ No newline at end of file diff --git a/advanced_search_example/go.sum b/examples/advanced-search-example/go.sum similarity index 100% rename from advanced_search_example/go.sum rename to examples/advanced-search-example/go.sum diff --git a/advanced_search_example/advanced_search_example.go b/examples/advanced-search-example/main.go similarity index 90% rename from advanced_search_example/advanced_search_example.go rename to examples/advanced-search-example/main.go index a98f768..b9f0b99 100644 --- a/advanced_search_example/advanced_search_example.go +++ b/examples/advanced-search-example/main.go @@ -7,6 +7,8 @@ import ( "time" graphiti "github.com/vxcontrol/graphiti-go-client" + + "github.com/google/uuid" ) // This example demonstrates the advanced search capabilities of the Graphiti Go client. @@ -35,19 +37,25 @@ func main() { } fmt.Printf("✓ Server is running (status: %s)\n\n", health.Status) + observation := &graphiti.Observation{ + ID: uuid.New().String(), + TraceID: uuid.New().String(), + Time: time.Now(), + } + // Add test data if !addTestData(client) { log.Fatal("Failed to add test data") } // Run all search tests - testTemporalWindowSearch(client) - testEntityRelationshipsSearch(client) - testDiverseResultsSearch(client) - testEpisodeContextSearch(client) - testSuccessfulToolsSearch(client) - testRecentContextSearch(client) - testEntityByLabelSearch(client) + testTemporalWindowSearch(client, observation) + testEntityRelationshipsSearch(client, observation) + testDiverseResultsSearch(client, observation) + testEpisodeContextSearch(client, observation) + testSuccessfulToolsSearch(client, observation) + testRecentContextSearch(client, observation) + testEntityByLabelSearch(client, observation) fmt.Println("\n" + strings.Repeat("=", 80)) fmt.Println("Test Suite Complete") @@ -282,7 +290,7 @@ RECOMMENDATIONS: return true } -func testTemporalWindowSearch(client *graphiti.Client) { +func testTemporalWindowSearch(client *graphiti.Client, observation *graphiti.Observation) { fmt.Println("\n" + strings.Repeat("=", 80)) fmt.Println("TEST 1: Temporal Window Search") fmt.Println(strings.Repeat("=", 80) + "\n") @@ -293,11 +301,12 @@ func testTemporalWindowSearch(client *graphiti.Client) { timeEnd := now.Add(-2 * time.Hour) result, err := client.TemporalWindowSearch(graphiti.TemporalSearchRequest{ - Query: "vulnerability exploitation attempts", - GroupID: stringPtr(groupID), - TimeStart: timeStart, - TimeEnd: timeEnd, - MaxResults: 10, + Query: "vulnerability exploitation attempts", + GroupID: stringPtr(groupID), + TimeStart: timeStart, + TimeEnd: timeEnd, + MaxResults: 10, + Observation: observation, }) if err != nil { @@ -324,7 +333,7 @@ func testTemporalWindowSearch(client *graphiti.Client) { } } -func testEntityRelationshipsSearch(client *graphiti.Client) { +func testEntityRelationshipsSearch(client *graphiti.Client, observation *graphiti.Observation) { fmt.Println("\n" + strings.Repeat("=", 80)) fmt.Println("TEST 2: Entity Relationships Search") fmt.Println(strings.Repeat("=", 80) + "\n") @@ -336,11 +345,12 @@ func testEntityRelationshipsSearch(client *graphiti.Client) { // First, do a temporal window search to get edges with node UUIDs tempResult, err := client.TemporalWindowSearch(graphiti.TemporalSearchRequest{ - Query: "192.168.1.10 Linux server", - GroupID: stringPtr(groupID), - TimeStart: timeStart, - TimeEnd: timeEnd, - MaxResults: 5, + Query: "192.168.1.10 Linux server", + GroupID: stringPtr(groupID), + TimeStart: timeStart, + TimeEnd: timeEnd, + MaxResults: 5, + Observation: observation, }) if err != nil { @@ -374,6 +384,7 @@ func testEntityRelationshipsSearch(client *graphiti.Client) { CenterNodeUUID: centerNodeUUID, MaxDepth: 2, MaxResults: 20, + Observation: observation, }) if err != nil { @@ -406,7 +417,7 @@ func testEntityRelationshipsSearch(client *graphiti.Client) { } } -func testDiverseResultsSearch(client *graphiti.Client) { +func testDiverseResultsSearch(client *graphiti.Client, observation *graphiti.Observation) { fmt.Println("\n" + strings.Repeat("=", 80)) fmt.Println("TEST 3: Diverse Results Search (MMR)") fmt.Println(strings.Repeat("=", 80) + "\n") @@ -417,6 +428,7 @@ func testDiverseResultsSearch(client *graphiti.Client) { GroupID: stringPtr(groupID), DiversityLevel: "medium", MaxResults: 10, + Observation: observation, }) if err != nil { @@ -445,16 +457,17 @@ func testDiverseResultsSearch(client *graphiti.Client) { } } -func testEpisodeContextSearch(client *graphiti.Client) { +func testEpisodeContextSearch(client *graphiti.Client, observation *graphiti.Observation) { fmt.Println("\n" + strings.Repeat("=", 80)) fmt.Println("TEST 4: Episode Context Search") fmt.Println(strings.Repeat("=", 80) + "\n") fmt.Println("ℹ Searching for full agent responses about Metasploit...") result, err := client.EpisodeContextSearch(graphiti.EpisodeContextSearchRequest{ - Query: "Metasploit EternalBlue exploitation", - GroupID: stringPtr(groupID), - MaxResults: 5, + Query: "Metasploit EternalBlue exploitation", + GroupID: stringPtr(groupID), + MaxResults: 5, + Observation: observation, }) if err != nil { @@ -478,7 +491,7 @@ func testEpisodeContextSearch(client *graphiti.Client) { } } -func testSuccessfulToolsSearch(client *graphiti.Client) { +func testSuccessfulToolsSearch(client *graphiti.Client, observation *graphiti.Observation) { fmt.Println("\n" + strings.Repeat("=", 80)) fmt.Println("TEST 5: Successful Tools Search") fmt.Println(strings.Repeat("=", 80) + "\n") @@ -489,6 +502,7 @@ func testSuccessfulToolsSearch(client *graphiti.Client) { GroupID: stringPtr(groupID), MinMentions: 1, MaxResults: 15, + Observation: observation, }) if err != nil { @@ -511,7 +525,7 @@ func testSuccessfulToolsSearch(client *graphiti.Client) { fmt.Printf(" - top_mention_count: %.0f\n", topMentionCount) } -func testRecentContextSearch(client *graphiti.Client) { +func testRecentContextSearch(client *graphiti.Client, observation *graphiti.Observation) { fmt.Println("\n" + strings.Repeat("=", 80)) fmt.Println("TEST 6: Recent Context Search") fmt.Println(strings.Repeat("=", 80) + "\n") @@ -522,6 +536,7 @@ func testRecentContextSearch(client *graphiti.Client) { GroupID: stringPtr(groupID), RecencyWindow: "24h", MaxResults: 10, + Observation: observation, }) if err != nil { @@ -543,7 +558,7 @@ func testRecentContextSearch(client *graphiti.Client) { } } -func testEntityByLabelSearch(client *graphiti.Client) { +func testEntityByLabelSearch(client *graphiti.Client, observation *graphiti.Observation) { fmt.Println("\n" + strings.Repeat("=", 80)) fmt.Println("TEST 7: Entity By Label Search") fmt.Println(strings.Repeat("=", 80) + "\n") @@ -555,11 +570,12 @@ func testEntityByLabelSearch(client *graphiti.Client) { // First, use temporal search to get some nodes and discover their labels tempResult, err := client.TemporalWindowSearch(graphiti.TemporalSearchRequest{ - Query: "all entities", - GroupID: stringPtr(groupID), - TimeStart: timeStart, - TimeEnd: timeEnd, - MaxResults: 15, + Query: "all entities", + GroupID: stringPtr(groupID), + TimeStart: timeStart, + TimeEnd: timeEnd, + MaxResults: 15, + Observation: observation, }) if err != nil { @@ -615,10 +631,11 @@ func testEntityByLabelSearch(client *graphiti.Client) { fmt.Printf("ℹ Testing entity-by-label search with labels: %v\n", searchLabels) result, err := client.EntityByLabelSearch(graphiti.EntityByLabelSearchRequest{ - Query: "tools and systems", - GroupID: stringPtr(groupID), - NodeLabels: searchLabels, - MaxResults: 15, + Query: "tools and systems", + GroupID: stringPtr(groupID), + NodeLabels: searchLabels, + MaxResults: 15, + Observation: observation, }) if err != nil { diff --git a/examples/base-usage-example/go.mod b/examples/base-usage-example/go.mod new file mode 100644 index 0000000..666af93 --- /dev/null +++ b/examples/base-usage-example/go.mod @@ -0,0 +1,11 @@ +module github.com/vxcontrol/graphiti-go-client/examples/base-usage-example + +go 1.23 + +replace github.com/vxcontrol/graphiti-go-client => ../../ + +require ( + github.com/google/uuid v1.6.0 + github.com/vxcontrol/graphiti-go-client v0.0.0-00010101000000-000000000000 +) + diff --git a/example/go.sum b/examples/base-usage-example/go.sum similarity index 100% rename from example/go.sum rename to examples/base-usage-example/go.sum diff --git a/example/main.go b/examples/base-usage-example/main.go similarity index 72% rename from example/main.go rename to examples/base-usage-example/main.go index 2243e9c..37cf287 100644 --- a/example/main.go +++ b/examples/base-usage-example/main.go @@ -10,7 +10,15 @@ import ( "github.com/google/uuid" ) -// This example demonstrates how to use the Graphiti Go client. +// This example demonstrates basic usage of the Graphiti Go client. +// +// It covers: +// - Basic operations (health check, add messages, search, get memory) +// - Entity node management +// - Episode retrieval +// - Langfuse observation tracking (optional) +// +// For advanced search methods, see ../advanced-search-example/main.go // // Important: The /messages endpoint processes data asynchronously. This example // polls for episodes to verify data was successfully created before searching. @@ -37,32 +45,45 @@ func main() { groupID := uuid.New().String() fmt.Printf("Using group ID: %s\n\n", groupID) - // Add messages - fmt.Println("=== Adding Messages ===") - messages := []graphiti.Message{ - { - Content: "I love hiking in the mountains on weekends.", - Author: "Alice", - Timestamp: time.Now().Add(-2 * time.Hour), - }, - { - Content: "That sounds great! Do you have a favorite trail?", - Author: "Assistant", - Timestamp: time.Now().Add(-90 * time.Minute), - }, - { - Content: "Yes, I particularly enjoy the Pacific Crest Trail. I try to go there every summer.", - Author: "Alice", - Timestamp: time.Now().Add(-60 * time.Minute), - }, - } - + // Optional: Create Langfuse observation for tracking + // Note: In production, these IDs should come from your Langfuse instance observation := &graphiti.Observation{ ID: uuid.New().String(), TraceID: uuid.New().String(), Time: time.Now(), } + // Add messages with rich context for better demonstration + fmt.Println("=== Adding Messages ===") + now := time.Now() + messages := []graphiti.Message{ + { + Content: "I love hiking in the mountains on weekends. My favorite trail is the Pacific Crest Trail.", + Author: "Alice", + Timestamp: now.Add(-5 * time.Hour), + }, + { + Content: "That sounds amazing! Do you go camping as well?", + Author: "Assistant", + Timestamp: now.Add(-4 * time.Hour), + }, + { + Content: "Yes! I usually camp near the trail. I also enjoy photography, especially nature and landscape shots.", + Author: "Alice", + Timestamp: now.Add(-3 * time.Hour), + }, + { + Content: "I recently bought a new DSLR camera for my trips. It's a Canon EOS R5.", + Author: "Alice", + Timestamp: now.Add(-2 * time.Hour), + }, + { + Content: "Last summer, I hiked the John Muir Trail and took some incredible photos of Yosemite.", + Author: "Alice", + Timestamp: now.Add(-1 * time.Hour), + }, + } + addResult, err := client.AddMessages(graphiti.AddMessagesRequest{ GroupID: groupID, Messages: messages, @@ -75,7 +96,7 @@ func main() { // Wait for processing and verify data exists (poll for episodes) fmt.Println("Waiting for messages to be processed...") - maxAttempts := 10 + maxAttempts := 12 pollInterval := 5 * time.Second var episodes []graphiti.Episode @@ -98,8 +119,8 @@ func main() { log.Fatalf("Timeout: No episodes were created after %v. The async job may have failed.", time.Duration(maxAttempts)*pollInterval) } - // Search for facts - fmt.Println("=== Searching for Facts ===") + // Basic Search + fmt.Println("=== Basic Search ===") searchResult, err := client.Search(graphiti.SearchQuery{ Query: "What does the user like to do?", MaxFacts: 5, @@ -120,7 +141,7 @@ func main() { fmt.Println("=== Getting Memory ===") memoryMessages := []graphiti.Message{ { - Content: "What hobbies does the user have?", + Content: "What hobbies and equipment does the user have?", Author: "User", Timestamp: time.Now(), }, @@ -147,7 +168,7 @@ func main() { UUID: entityUUID, GroupID: groupID, Name: "User Interests", - Summary: "The user's hobbies and interests", + Summary: "The user's hobbies and interests including hiking, camping, and photography", Observation: observation, }) if err != nil { @@ -155,11 +176,15 @@ func main() { } fmt.Printf("Created entity node: %s (UUID: %s)\n\n", node.Name, node.UUID) - // Display episodes (already fetched during polling) + // Display episodes fmt.Println("=== Episodes Summary ===") fmt.Printf("Total episodes: %d\n", len(episodes)) for i, episode := range episodes { - fmt.Printf("%d. %s: %s\n", i+1, episode.Name, episode.Content) + preview := episode.Content + if len(preview) > 80 { + preview = preview[:80] + "..." + } + fmt.Printf("%d. %s: %s\n", i+1, episode.Name, preview) } fmt.Println() diff --git a/types.go b/types.go index dc0e8a9..ea41e28 100644 --- a/types.go +++ b/types.go @@ -159,11 +159,12 @@ type TimeWindow struct { // TemporalSearchRequest represents a temporal window search request type TemporalSearchRequest struct { - Query string `json:"query"` - GroupID *string `json:"group_id,omitempty"` - TimeStart time.Time `json:"time_start"` - TimeEnd time.Time `json:"time_end"` - MaxResults int `json:"max_results,omitempty"` + Query string `json:"query"` + GroupID *string `json:"group_id,omitempty"` + TimeStart time.Time `json:"time_start"` + TimeEnd time.Time `json:"time_end"` + MaxResults int `json:"max_results,omitempty"` + Observation *Observation `json:"observation,omitempty"` } // TemporalSearchResponse represents a temporal window search response @@ -179,13 +180,14 @@ type TemporalSearchResponse struct { // EntityRelationshipSearchRequest represents an entity relationships search request type EntityRelationshipSearchRequest struct { - Query string `json:"query"` - GroupID *string `json:"group_id,omitempty"` - CenterNodeUUID string `json:"center_node_uuid"` - MaxDepth int `json:"max_depth,omitempty"` - NodeLabels *[]string `json:"node_labels,omitempty"` - EdgeTypes *[]string `json:"edge_types,omitempty"` - MaxResults int `json:"max_results,omitempty"` + Query string `json:"query"` + GroupID *string `json:"group_id,omitempty"` + CenterNodeUUID string `json:"center_node_uuid"` + MaxDepth int `json:"max_depth,omitempty"` + NodeLabels *[]string `json:"node_labels,omitempty"` + EdgeTypes *[]string `json:"edge_types,omitempty"` + MaxResults int `json:"max_results,omitempty"` + Observation *Observation `json:"observation,omitempty"` } // EntityRelationshipSearchResponse represents an entity relationships search response @@ -199,10 +201,11 @@ type EntityRelationshipSearchResponse struct { // DiverseSearchRequest represents a diverse results search request type DiverseSearchRequest struct { - Query string `json:"query"` - GroupID *string `json:"group_id,omitempty"` - DiversityLevel string `json:"diversity_level,omitempty"` - MaxResults int `json:"max_results,omitempty"` + Query string `json:"query"` + GroupID *string `json:"group_id,omitempty"` + DiversityLevel string `json:"diversity_level,omitempty"` + MaxResults int `json:"max_results,omitempty"` + Observation *Observation `json:"observation,omitempty"` } // DiverseSearchResponse represents a diverse results search response @@ -219,9 +222,10 @@ type DiverseSearchResponse struct { // EpisodeContextSearchRequest represents an episode context search request type EpisodeContextSearchRequest struct { - Query string `json:"query"` - GroupID *string `json:"group_id,omitempty"` - MaxResults int `json:"max_results,omitempty"` + Query string `json:"query"` + GroupID *string `json:"group_id,omitempty"` + MaxResults int `json:"max_results,omitempty"` + Observation *Observation `json:"observation,omitempty"` } // EpisodeContextSearchResponse represents an episode context search response @@ -234,10 +238,11 @@ type EpisodeContextSearchResponse struct { // SuccessfulToolsSearchRequest represents a successful tools search request type SuccessfulToolsSearchRequest struct { - Query string `json:"query"` - GroupID *string `json:"group_id,omitempty"` - MinMentions int `json:"min_mentions,omitempty"` - MaxResults int `json:"max_results,omitempty"` + Query string `json:"query"` + GroupID *string `json:"group_id,omitempty"` + MinMentions int `json:"min_mentions,omitempty"` + MaxResults int `json:"max_results,omitempty"` + Observation *Observation `json:"observation,omitempty"` } // SuccessfulToolsSearchResponse represents a successful tools search response @@ -252,10 +257,11 @@ type SuccessfulToolsSearchResponse struct { // RecentContextSearchRequest represents a recent context search request type RecentContextSearchRequest struct { - Query string `json:"query"` - GroupID *string `json:"group_id,omitempty"` - RecencyWindow string `json:"recency_window,omitempty"` - MaxResults int `json:"max_results,omitempty"` + Query string `json:"query"` + GroupID *string `json:"group_id,omitempty"` + RecencyWindow string `json:"recency_window,omitempty"` + MaxResults int `json:"max_results,omitempty"` + Observation *Observation `json:"observation,omitempty"` } // RecentContextSearchResponse represents a recent context search response @@ -271,11 +277,12 @@ type RecentContextSearchResponse struct { // EntityByLabelSearchRequest represents an entity by label search request type EntityByLabelSearchRequest struct { - Query string `json:"query"` - GroupID *string `json:"group_id,omitempty"` - NodeLabels []string `json:"node_labels"` - EdgeTypes *[]string `json:"edge_types,omitempty"` - MaxResults int `json:"max_results,omitempty"` + Query string `json:"query"` + GroupID *string `json:"group_id,omitempty"` + NodeLabels []string `json:"node_labels"` + EdgeTypes *[]string `json:"edge_types,omitempty"` + MaxResults int `json:"max_results,omitempty"` + Observation *Observation `json:"observation,omitempty"` } // EntityByLabelSearchResponse represents an entity by label search response