- Updated the search handling to incorporate keyword extraction with weights, improving the relevance of search results. - Refactored the `shouldAutoSearch` method to return a `SearchIntent` struct, allowing for more nuanced control over search execution based on context. - Enhanced the `buildSearchRequests` function to utilize extracted keywords, optimizing search queries based on user input. - Improved the handling of search types and conditions, ensuring that the system can dynamically adjust search behavior based on intent and configuration. - Updated documentation and prompts to reflect changes in keyword extraction and search intent classification, providing clearer guidelines for usage.
137 lines
3.8 KiB
Go
137 lines
3.8 KiB
Go
package keyword
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/yaoapp/gou/mcp"
|
|
gouMCPTypes "github.com/yaoapp/gou/mcp/types"
|
|
agentContext "github.com/yaoapp/yao/agent/context"
|
|
"github.com/yaoapp/yao/agent/search/types"
|
|
)
|
|
|
|
// MCPProvider delegates keyword extraction to an MCP tool
|
|
type MCPProvider struct {
|
|
serverID string // MCP server ID
|
|
toolName string // Tool name to call
|
|
}
|
|
|
|
// NewMCPProvider creates a new MCP-based keyword extractor
|
|
// mcpRef format: "server.tool" (e.g., "nlp.extract_keywords")
|
|
func NewMCPProvider(mcpRef string) (*MCPProvider, error) {
|
|
parts := strings.SplitN(mcpRef, ".", 2)
|
|
if len(parts) != 2 {
|
|
return nil, fmt.Errorf("invalid MCP format, expected 'server.tool', got '%s'", mcpRef)
|
|
}
|
|
return &MCPProvider{
|
|
serverID: parts[0],
|
|
toolName: parts[1],
|
|
}, nil
|
|
}
|
|
|
|
// Extract extracts keywords by calling the MCP tool
|
|
func (p *MCPProvider) Extract(ctx *agentContext.Context, content string, opts *types.KeywordOptions) ([]types.Keyword, error) {
|
|
// Get MCP client
|
|
client, err := mcp.Select(p.serverID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("MCP server '%s' not found: %w", p.serverID, err)
|
|
}
|
|
|
|
// Build arguments for the MCP tool
|
|
arguments := map[string]interface{}{
|
|
"content": content,
|
|
"max_keywords": opts.MaxKeywords,
|
|
"language": opts.Language,
|
|
}
|
|
|
|
// Call the MCP tool (ctx embeds context.Context)
|
|
callResult, err := client.CallTool(ctx, p.toolName, arguments)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("MCP tool call failed: %w", err)
|
|
}
|
|
|
|
// Parse the result
|
|
return p.parseResult(callResult)
|
|
}
|
|
|
|
// parseResult extracts keywords from the MCP tool response
|
|
func (p *MCPProvider) parseResult(result *gouMCPTypes.CallToolResponse) ([]types.Keyword, error) {
|
|
if result == nil {
|
|
return []types.Keyword{}, nil
|
|
}
|
|
|
|
// Check for errors in result
|
|
if result.IsError {
|
|
errMsg := "MCP tool returned error"
|
|
if len(result.Content) > 0 && result.Content[0].Text != "" {
|
|
errMsg = result.Content[0].Text
|
|
}
|
|
return nil, fmt.Errorf("%s", errMsg)
|
|
}
|
|
|
|
// Parse content - expect JSON data with "keywords" field
|
|
if len(result.Content) == 0 {
|
|
return []types.Keyword{}, nil
|
|
}
|
|
|
|
// Try to extract keywords from content
|
|
for _, content := range result.Content {
|
|
// Check text content type
|
|
if content.Type == gouMCPTypes.ToolContentTypeText && content.Text != "" {
|
|
// Try to parse as JSON
|
|
var data map[string]interface{}
|
|
if err := json.Unmarshal([]byte(content.Text), &data); err == nil {
|
|
// Look for "keywords" field
|
|
if kw, ok := data["keywords"]; ok {
|
|
return p.extractKeywordsFromValue(kw)
|
|
}
|
|
}
|
|
|
|
// Try to parse as direct array of keywords
|
|
var keywords []types.Keyword
|
|
if err := json.Unmarshal([]byte(content.Text), &keywords); err == nil {
|
|
return keywords, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return []types.Keyword{}, nil
|
|
}
|
|
|
|
// extractKeywordsFromValue extracts Keyword array from various types
|
|
func (p *MCPProvider) extractKeywordsFromValue(v interface{}) ([]types.Keyword, error) {
|
|
switch kw := v.(type) {
|
|
case []types.Keyword:
|
|
return kw, nil
|
|
case []interface{}:
|
|
keywords := make([]types.Keyword, 0, len(kw))
|
|
for _, item := range kw {
|
|
switch v := item.(type) {
|
|
case map[string]interface{}:
|
|
// Handle {k: "keyword", w: 0.9} format
|
|
k, _ := v["k"].(string)
|
|
w, _ := v["w"].(float64)
|
|
if k != "" {
|
|
if w == 0 {
|
|
w = 0.5 // Default weight
|
|
}
|
|
keywords = append(keywords, types.Keyword{K: k, W: w})
|
|
}
|
|
case string:
|
|
// Plain string, use default weight
|
|
if v != "" {
|
|
keywords = append(keywords, types.Keyword{K: v, W: 0.5})
|
|
}
|
|
}
|
|
}
|
|
return keywords, nil
|
|
case string:
|
|
var keywords []types.Keyword
|
|
if err := json.Unmarshal([]byte(kw), &keywords); err == nil {
|
|
return keywords, nil
|
|
}
|
|
return []types.Keyword{{K: kw, W: 0.5}}, nil
|
|
}
|
|
return []types.Keyword{}, nil
|
|
}
|