* @brief meta.searchDocs: BM25-style doc search via DocSearch singleton. */
| 677 | * @brief meta.searchDocs: BM25-style doc search via DocSearch singleton. |
| 678 | */ |
| 679 | void AI::Conversation::runMetaSearchDocs(const QString& callId, |
| 680 | const QString& name, |
| 681 | const QJsonObject& arguments) |
| 682 | { |
| 683 | appendToolCallCard(callId, name, arguments, CallStatus::Running); |
| 684 | const auto query = arguments.value(QStringLiteral("query")).toString(); |
| 685 | const int k = qBound(1, arguments.value(QStringLiteral("k")).toInt(5), 10); |
| 686 | |
| 687 | const auto hits = AI::DocSearch::instance().search(query, k); |
| 688 | |
| 689 | QJsonArray rows; |
| 690 | for (const auto& h : hits) { |
| 691 | QJsonObject row; |
| 692 | row[QStringLiteral("id")] = h.id; |
| 693 | row[QStringLiteral("source")] = h.source; |
| 694 | row[QStringLiteral("title")] = h.title; |
| 695 | row[QStringLiteral("body")] = h.body; |
| 696 | row[QStringLiteral("score")] = h.score; |
| 697 | rows.append(row); |
| 698 | } |
| 699 | |
| 700 | QJsonObject reply; |
| 701 | reply[QStringLiteral("ok")] = true; |
| 702 | reply[QStringLiteral("query")] = query; |
| 703 | reply[QStringLiteral("hits")] = rows; |
| 704 | reply[QStringLiteral("count")] = rows.size(); |
| 705 | if (rows.isEmpty()) { |
| 706 | reply[QStringLiteral("hint")] = |
| 707 | QStringLiteral("No matches. Try rephrasing with command-shaped terms (e.g. " |
| 708 | "'project.dataset.add' instead of 'add a channel'), or fall back to " |
| 709 | "meta.listCommands{prefix} / meta.fetchHelp{path: 'help.json'}."); |
| 710 | } |
| 711 | |
| 712 | recordToolResult(callId, name, reply); |
| 713 | updateToolCallCard(callId, CallStatus::Done, reply); |
| 714 | releaseOutstandingToolResult(); |
| 715 | } |
| 716 | |
| 717 | /** |
| 718 | * @brief Returns the skill id whose body documents @a commandName, or empty. |