SearchAndFormat 执行向量检索并格式化为 Markdown,用于 RAG 场景。 返回的字符串可以直接注入到 Prompt 中作为上下文。
(ctx context.Context, query string, meta map[string]any, topK int)
| 403 | // SearchAndFormat 执行向量检索并格式化为 Markdown,用于 RAG 场景。 |
| 404 | // 返回的字符串可以直接注入到 Prompt 中作为上下文。 |
| 405 | func (sm *SemanticMemory) SearchAndFormat(ctx context.Context, query string, meta map[string]any, topK int) (string, error) { |
| 406 | if sm == nil || sm.cfg.Store == nil || sm.cfg.Embedder == nil { |
| 407 | return "", nil |
| 408 | } |
| 409 | |
| 410 | // 执行检索 |
| 411 | hits, err := sm.Search(ctx, query, meta, topK) |
| 412 | if err != nil { |
| 413 | return "", fmt.Errorf("search: %w", err) |
| 414 | } |
| 415 | |
| 416 | if len(hits) == 0 { |
| 417 | return "", nil |
| 418 | } |
| 419 | |
| 420 | // 格式化为 Markdown |
| 421 | var result string |
| 422 | result += "## Relevant Context\n\n" |
| 423 | result += fmt.Sprintf("Found %d relevant documents:\n\n", len(hits)) |
| 424 | |
| 425 | var resultSb423 strings.Builder |
| 426 | for i, hit := range hits { |
| 427 | // 获取文本内容 |
| 428 | text := "" |
| 429 | if t, ok := hit.Metadata["text"].(string); ok { |
| 430 | text = t |
| 431 | } |
| 432 | |
| 433 | // 格式化分数(0-1 范围) |
| 434 | score := hit.Score |
| 435 | scorePercent := int(score * 100) |
| 436 | |
| 437 | resultSb423.WriteString(fmt.Sprintf("### %d. (Relevance: %d%%)\n\n", i+1, scorePercent)) |
| 438 | |
| 439 | if text != "" { |
| 440 | resultSb423.WriteString(text + "\n\n") |
| 441 | } |
| 442 | |
| 443 | // 添加元数据(可选) |
| 444 | if len(hit.Metadata) > 1 { // 除了 text 之外还有其他元数据 |
| 445 | resultSb423.WriteString("**Metadata:**\n") |
| 446 | var resultSb443 strings.Builder |
| 447 | for k, v := range hit.Metadata { |
| 448 | if k != "text" { // 跳过已显示的 text |
| 449 | resultSb443.WriteString(fmt.Sprintf("- %s: %v\n", k, v)) |
| 450 | } |
| 451 | } |
| 452 | resultSb423.WriteString(resultSb443.String()) |
| 453 | resultSb423.WriteString("\n") |
| 454 | } |
| 455 | } |
| 456 | result += resultSb423.String() |
| 457 | |
| 458 | return result, nil |
| 459 | } |