Skip to content

Commit 8b00a49

Browse files
committed
fix: llm queries
1 parent 5570e22 commit 8b00a49

File tree

11 files changed

+803
-175
lines changed

11 files changed

+803
-175
lines changed

.cursor/mcp.json

Lines changed: 0 additions & 13 deletions
This file was deleted.

.env.example

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# GoGraph Environment Variables
2+
3+
# OpenAI Configuration
4+
# Required for natural language query processing in MCP server
5+
# Get your API key from: https://platform.openai.com/api-keys
6+
OPENAI_API_KEY=your-openai-api-key-here
7+
8+
# Neo4j Configuration (optional - defaults are usually fine for local development)
9+
# NEO4J_URI=bolt://localhost:7687
10+
# NEO4J_USERNAME=neo4j
11+
# NEO4J_PASSWORD=password
12+
# NEO4J_DATABASE=neo4j
13+
14+
# MCP Server Configuration (optional)
15+
# MCP_SERVER_HOST=localhost
16+
# MCP_SERVER_PORT=3333
17+
# MCP_AUTH_ENABLED=false
18+
# MCP_AUTH_TOKEN=your-auth-token-here

cmd/gograph/commands/mcp.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ import (
55
"fmt"
66
"os"
77
"os/signal"
8+
"strings"
89
"syscall"
910

1011
"github.com/compozy/gograph/engine/analyzer"
1112
"github.com/compozy/gograph/engine/graph"
1213
"github.com/compozy/gograph/engine/infra"
14+
"github.com/compozy/gograph/engine/llm"
1315
"github.com/compozy/gograph/engine/mcp"
1416
"github.com/compozy/gograph/engine/parser"
1517
"github.com/compozy/gograph/pkg/logger"
1618
mcpconfig "github.com/compozy/gograph/pkg/mcp"
19+
"github.com/joho/godotenv"
1720
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
1821
"github.com/spf13/cobra"
1922
"github.com/spf13/viper"
@@ -75,6 +78,11 @@ func runServeMCP(cmd *cobra.Command, _ []string) error {
7578
ctx, cancel := context.WithCancel(context.Background())
7679
defer cancel()
7780

81+
// Load .env file if it exists
82+
if err := loadEnvFile(); err != nil {
83+
logger.Debug("Could not load .env file", "error", err)
84+
}
85+
7886
config, err := prepareMCPConfiguration(cmd)
7987
if err != nil {
8088
return err
@@ -182,9 +190,30 @@ func createMCPServer(config *mcpconfig.Config) *mcp.Server {
182190
// Create service adapter
183191
serviceAdapter := mcp.NewServiceAdapter(driver, graphService, parserService, analyzerService)
184192

185-
// Note: For now, pass nils for LLM services since they may not be fully implemented
186-
// The core analysis tools will work without them
187-
return mcp.NewServer(config, serviceAdapter, nil, nil, nil)
193+
// Initialize LLM service if OpenAI API key is available
194+
var llmService llm.CypherTranslator
195+
openAIKey := viper.GetString("openai.api_key")
196+
if openAIKey == "" {
197+
openAIKey = os.Getenv("OPENAI_API_KEY")
198+
}
199+
200+
if openAIKey != "" {
201+
// Validate API key format
202+
if !strings.HasPrefix(openAIKey, "sk-") {
203+
logger.Warn("OpenAI API key does not have the expected 'sk-' prefix. It may be invalid.")
204+
}
205+
logger.Info("Initializing OpenAI LLM service for natural language queries")
206+
llmConfig := llm.CypherTranslatorConfig{
207+
APIKey: openAIKey,
208+
Model: viper.GetString("openai.model"), // Will use default if not set
209+
}
210+
llmService = llm.NewOpenAICypherTranslator(llmConfig, graphService)
211+
} else {
212+
logger.Warn("OpenAI API key not found - natural language queries will use basic fallback")
213+
}
214+
215+
// TODO: Initialize context generator and query builder when implemented
216+
return mcp.NewServer(config, serviceAdapter, llmService, nil, nil)
188217
}
189218

190219
func runMCPServerWithGracefulShutdown(ctx context.Context, cancel context.CancelFunc, server *mcp.Server) {
@@ -253,3 +282,14 @@ func loadMCPConfig() (*mcpconfig.Config, error) {
253282

254283
return config, nil
255284
}
285+
286+
// loadEnvFile loads the .env file from the current directory
287+
func loadEnvFile() error {
288+
err := godotenv.Load()
289+
if err != nil {
290+
// It's acceptable if the file doesn't exist
291+
return fmt.Errorf(".env file not found or failed to load: %w", err)
292+
}
293+
logger.Info("Loaded .env file from current directory")
294+
return nil
295+
}

engine/llm/cypher_translator.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func (t *OpenAICypherTranslator) GetSchema(ctx context.Context, projectID string
9797

9898
// getSystemPrompt returns the system prompt for Cypher translation
9999
func (t *OpenAICypherTranslator) getSystemPrompt() string {
100-
return `You are a expert Neo4j Cypher query generator. ` +
100+
return `You are a expert Neo4j Cypher query generator for Go code analysis. ` +
101101
`Your task is to convert natural language questions into precise Cypher queries.
102102
103103
IMPORTANT RULES:
@@ -108,12 +108,32 @@ IMPORTANT RULES:
108108
5. Always include LIMIT clauses to prevent excessive results (default: 50)
109109
6. Use parameterized queries when possible
110110
7. Focus on performance and accuracy
111+
8. Functions have a 'package' property - use it directly instead of traversing relationships
111112
112-
COMMON PATTERNS:
113-
- Find functions: MATCH (f:Function) WHERE f.name CONTAINS $name RETURN f
114-
- Find dependencies: MATCH (f1:File)-[:DEPENDS_ON]->(f2:File) RETURN f1, f2
115-
- Find callers: MATCH (f1:Function)-[:CALLS]->(f2:Function) WHERE f2.name = $name RETURN f1
116-
- Find implementations: MATCH (s:Struct)-[:IMPLEMENTS]->(i:Interface) RETURN s, i
113+
WORKING QUERY EXAMPLES:
114+
Query: "Show me all handler functions in the mcp package"
115+
Cypher: MATCH (f:Function) WHERE f.package = 'mcp' AND toLower(f.name) CONTAINS 'handle' RETURN f LIMIT 50
116+
117+
Query: "Find functions that handle MCP requests"
118+
Cypher: MATCH (f:Function) WHERE toLower(f.name) CONTAINS 'mcp' RETURN f LIMIT 50
119+
120+
Query: "List all functions in package mcp"
121+
Cypher: MATCH (f:Function) WHERE f.package = 'mcp' RETURN f LIMIT 50
122+
123+
Query: "Show functions with names starting with handle"
124+
Cypher: MATCH (f:Function) WHERE f.name STARTS WITH 'handle' RETURN f LIMIT 50
125+
126+
Query: "Find all structs in the analyzer package"
127+
Cypher: MATCH (s:Struct) WHERE s.package = 'analyzer' RETURN s LIMIT 50
128+
129+
Query: "Show interfaces and their implementations"
130+
Cypher: MATCH (s:Struct)-[:IMPLEMENTS]->(i:Interface) RETURN s.name as struct, i.name as interface LIMIT 50
131+
132+
Query: "Find functions that call a specific function"
133+
Cypher: MATCH (f1:Function)-[:CALLS]->(f2:Function {name: 'Debug'}) RETURN f1.name, f1.package LIMIT 50
134+
135+
Query: "Show all packages and their file count"
136+
Cypher: MATCH (p:Package)-[:CONTAINS]->(f:File) RETURN p.name, count(f) as file_count ORDER BY file_count DESC
117137
118138
Return only the Cypher query without any formatting or explanations.`
119139
}
@@ -147,15 +167,21 @@ func (t *OpenAICypherTranslator) buildSchemaDescription(stats *graph.ProjectStat
147167
schema.WriteString("- Constant: Constants with properties: name, type, value, is_exported\n")
148168
schema.WriteString("- Import: Import statements with properties: path, alias\n")
149169
schema.WriteString("\nRELATIONSHIP TYPES:\n")
150-
schema.WriteString("- CONTAINS: Package->File, File->Function/Struct/Interface\n")
151-
schema.WriteString("- IMPORTS: File->Package\n")
152-
schema.WriteString("- CALLS: Function->Function\n")
153-
schema.WriteString("- IMPLEMENTS: Struct->Interface\n")
154-
schema.WriteString("- HAS_METHOD: Struct->Function\n")
155-
schema.WriteString("- DEPENDS_ON: File->File\n")
156-
schema.WriteString("- HAS_FIELD: Struct->Variable\n")
170+
schema.WriteString("- CONTAINS: Package->File (packages contain files)\n")
171+
schema.WriteString("- DEFINES: File->Function/Struct/Interface/Variable/Constant (files define code elements)\n")
172+
schema.WriteString("- IMPORTS: File->Import (files have imports)\n")
173+
schema.WriteString("- CALLS: Function->Function (function call relationships)\n")
174+
schema.WriteString("- IMPLEMENTS: Struct->Interface (struct implements interface)\n")
175+
schema.WriteString("- HAS_METHOD: Struct->Function (struct has methods)\n")
176+
schema.WriteString("- DEPENDS_ON: File->File (file dependencies)\n")
177+
schema.WriteString("- HAS_FIELD: Struct->Variable (struct has fields)\n")
157178
schema.WriteString(fmt.Sprintf("\nDatabase contains %d nodes total.\n", stats.TotalNodes))
158179
schema.WriteString(fmt.Sprintf("Database contains %d relationships total.\n", stats.TotalRelationships))
180+
schema.WriteString("\nIMPORTANT NOTES:\n")
181+
schema.WriteString("- Functions have a 'package' property that can be queried directly\n")
182+
schema.WriteString("- To find functions in a package, use: MATCH (f:Function) WHERE f.package = 'packagename'\n")
183+
schema.WriteString("- Files DEFINE code elements (not CONTAINS)\n")
184+
schema.WriteString("- All nodes have a 'project_id' property for filtering\n")
159185
return schema.String()
160186
}
161187

0 commit comments

Comments
 (0)