mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-31 13:03:49 +08:00
Refine handling of POST /api/v1/datasets/search in GO (#15583)
### What problem does this PR solve? Refine handling of POST /api/v1/datasets/search in GO ### Type of change - [x] Refactoring
This commit is contained in:
@@ -6,6 +6,13 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func tokenTypeDescription(t int, tok Token) string {
|
||||
if tok.Type == t && tok.Value != "" {
|
||||
return fmt.Sprintf("%s %q", tokenTypeToString(t), tok.Value)
|
||||
}
|
||||
return tokenTypeToString(t)
|
||||
}
|
||||
|
||||
// Command parsers
|
||||
func (p *Parser) parseLogout() (*Command, error) {
|
||||
cmd := NewCommand("logout")
|
||||
@@ -138,8 +145,6 @@ func (p *Parser) parseListCommand() (*Command, error) {
|
||||
return p.parseListDatasets()
|
||||
case TokenDocuments:
|
||||
return p.parseListDatasetDocuments()
|
||||
case TokenMetadata:
|
||||
return p.parseListMetadata()
|
||||
case TokenAgents:
|
||||
return p.parseListAgents()
|
||||
case TokenTokens:
|
||||
@@ -210,7 +215,7 @@ func (p *Parser) parseListDatasetDocuments() (*Command, error) {
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseListMetadata() (*Command, error) {
|
||||
func (p *Parser) parseGetMetadata() (*Command, error) {
|
||||
p.nextToken() // consume METADATA
|
||||
|
||||
if p.curToken.Type != TokenOf {
|
||||
@@ -233,6 +238,10 @@ func (p *Parser) parseListMetadata() (*Command, error) {
|
||||
datasetNames = append(datasetNames, name)
|
||||
|
||||
p.nextToken()
|
||||
|
||||
if p.curToken.Type == TokenComma {
|
||||
return nil, fmt.Errorf("syntax error: dataset names must be space-separated, not comma-separated (got %q after %q)", "'", name)
|
||||
}
|
||||
// Stop at semicolon or non-quoted (dataset name must be quoted)
|
||||
if p.curToken.Type == TokenSemicolon {
|
||||
break
|
||||
@@ -243,7 +252,7 @@ func (p *Parser) parseListMetadata() (*Command, error) {
|
||||
}
|
||||
}
|
||||
|
||||
cmd := NewCommand("list_metadata")
|
||||
cmd := NewCommand("get_metadata")
|
||||
cmd.Params["dataset_names"] = datasetNames
|
||||
|
||||
// Semicolon is optional
|
||||
@@ -2379,6 +2388,27 @@ func (p *Parser) parseInsertMetadataFromFile() (*Command, error) {
|
||||
func (p *Parser) parseSearchCommand() (*Command, error) {
|
||||
p.nextToken() // consume SEARCH
|
||||
|
||||
// Handle help flag: -h / --help. The lexer tokenizes each leading
|
||||
// `-` as a separate `TokenDash` and then the rest of the flag name
|
||||
// (e.g. "help") as a `TokenIdentifier`. We collect any leading
|
||||
// dashes before checking the identifier value. Short-circuit with
|
||||
// a dedicated command type so the dispatcher can print the search
|
||||
// usage instead of erroring out on the missing question.
|
||||
if p.curToken.Type == TokenDash {
|
||||
dashCount := 0
|
||||
for p.curToken.Type == TokenDash {
|
||||
dashCount++
|
||||
p.nextToken()
|
||||
}
|
||||
if dashCount > 0 && p.curToken.Type == TokenIdentifier {
|
||||
switch strings.ToLower(p.curToken.Value) {
|
||||
case "h", "help":
|
||||
return NewCommand("search_help"), nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("expected quoted string or identifier")
|
||||
}
|
||||
|
||||
var err error
|
||||
var question string
|
||||
if p.curToken.Type == TokenQuotedString {
|
||||
@@ -2415,7 +2445,119 @@ func (p *Parser) parseSearchCommand() (*Command, error) {
|
||||
cmd.Params["datasets"] = datasets
|
||||
|
||||
p.nextToken()
|
||||
// Semicolon is optional for UNSET TOKEN
|
||||
|
||||
// Parse optional WITH clause for additional parameters
|
||||
if p.curToken.Type == TokenWith || (p.curToken.Type == TokenIdentifier && strings.ToLower(p.curToken.Value) == "with") {
|
||||
if p.curToken.Type == TokenWith {
|
||||
p.nextToken()
|
||||
} else {
|
||||
p.nextToken() // skip "with" identifier
|
||||
}
|
||||
|
||||
for p.curToken.Type != TokenEOF && p.curToken.Type != TokenSemicolon {
|
||||
if p.curToken.Type == TokenComma {
|
||||
return nil, fmt.Errorf("syntax error: WITH options must be space-separated, not comma-separated")
|
||||
}
|
||||
// Parse parameter name
|
||||
if p.curToken.Type != TokenIdentifier {
|
||||
break
|
||||
}
|
||||
paramName := strings.ToLower(p.curToken.Value)
|
||||
p.nextToken()
|
||||
|
||||
// Parse parameter value
|
||||
var paramValue interface{}
|
||||
valueToken := p.curToken.Type
|
||||
var valueErr error
|
||||
switch p.curToken.Type {
|
||||
case TokenInteger:
|
||||
paramValue, valueErr = p.parseNumber()
|
||||
if valueErr != nil {
|
||||
return nil, valueErr
|
||||
}
|
||||
p.nextToken() // step past the integer
|
||||
case TokenFloat:
|
||||
paramValue, valueErr = p.parseFloat()
|
||||
if valueErr != nil {
|
||||
return nil, valueErr
|
||||
}
|
||||
p.nextToken() // step past the float
|
||||
case TokenQuotedString:
|
||||
paramValue, valueErr = p.parseQuotedString()
|
||||
if valueErr != nil {
|
||||
return nil, valueErr
|
||||
}
|
||||
p.nextToken() // step past the closing quote
|
||||
case TokenIdentifier:
|
||||
// Bare identifiers are only meaningful for the
|
||||
// boolean keys (keyword / use_kg = true|false);
|
||||
// everything else rejects them.
|
||||
paramValue = p.curToken.Value
|
||||
p.nextToken()
|
||||
case TokenLBracket:
|
||||
// List value: parsed inside the switch below by
|
||||
// cross_languages / doc_ids. No value is captured here.
|
||||
paramValue = nil
|
||||
default:
|
||||
// EOF, ';', or any other non-value token: the option
|
||||
// is missing a value, which is a hard error rather
|
||||
// than a silent drop.
|
||||
return nil, fmt.Errorf("WITH option %q is missing a value", paramName)
|
||||
}
|
||||
|
||||
switch paramName {
|
||||
case "top_k", "page_size", "page":
|
||||
if valueToken != TokenInteger {
|
||||
return nil, fmt.Errorf("WITH option %q must be an integer, got %s", paramName, tokenTypeDescription(valueToken, p.curToken))
|
||||
}
|
||||
cmd.Params[paramName] = paramValue
|
||||
case "similarity_threshold", "vector_similarity_weight":
|
||||
switch n := paramValue.(type) {
|
||||
case int:
|
||||
cmd.Params[paramName] = float64(n)
|
||||
case float64:
|
||||
cmd.Params[paramName] = n
|
||||
default:
|
||||
return nil, fmt.Errorf("WITH option %q must be a number, got %s", paramName, tokenTypeDescription(valueToken, p.curToken))
|
||||
}
|
||||
case "keyword", "use_kg":
|
||||
s, ok := paramValue.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("WITH option %q must be true or false, got %s", paramName, tokenTypeDescription(valueToken, p.curToken))
|
||||
}
|
||||
switch strings.ToLower(s) {
|
||||
case "true":
|
||||
cmd.Params[paramName] = true
|
||||
case "false":
|
||||
cmd.Params[paramName] = false
|
||||
default:
|
||||
return nil, fmt.Errorf("WITH option %q must be true or false, got %q", paramName, s)
|
||||
}
|
||||
case "rerank_id", "tenant_rerank_id", "search_id", "meta_data_filter":
|
||||
if valueToken != TokenQuotedString {
|
||||
return nil, fmt.Errorf("WITH option %q must be a quoted string, got %s", paramName, tokenTypeDescription(valueToken, p.curToken))
|
||||
}
|
||||
// meta_data_filter JSON string is decoded into a map in
|
||||
// the SearchOnDatasets handler; parser stores the raw
|
||||
// string so the handler can surface a clean error on
|
||||
// invalid JSON.
|
||||
cmd.Params[paramName] = paramValue
|
||||
case "cross_languages", "doc_ids":
|
||||
if p.curToken.Type != TokenLBracket {
|
||||
return nil, fmt.Errorf("WITH option %q must be a list, e.g. %q ['a', 'b']", paramName, paramName)
|
||||
}
|
||||
list, err := p.parseQuotedStringList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd.Params[paramName] = list
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown WITH option %q", paramName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Semicolon is optional
|
||||
if p.curToken.Type == TokenSemicolon {
|
||||
p.nextToken()
|
||||
}
|
||||
@@ -3599,13 +3741,16 @@ func (p *Parser) parseUnsetCommand() (*Command, error) {
|
||||
return NewCommand("unset_token"), nil
|
||||
}
|
||||
|
||||
// parseGetCommand parses: GET CHUNK 'chunk_id'
|
||||
// parseGetCommand parses: GET CHUNK or GET METADATA
|
||||
func (p *Parser) parseGetCommand() (*Command, error) {
|
||||
p.nextToken() // consume GET
|
||||
|
||||
if p.curToken.Type == TokenChunk {
|
||||
return p.parseGetChunk()
|
||||
}
|
||||
if p.curToken.Type == TokenMetadata {
|
||||
return p.parseGetMetadata()
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown GET target: %s", p.curToken.Value)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user