formal-ai 0.165.0

Formal symbolic AI implementation with OpenAI-compatible APIs
Documentation
//! Scoped documentation-answer handlers for known project APIs.

use crate::engine::SymbolicAnswer;
use crate::event_log::EventLog;
use crate::language::{detect as detect_language, Language};
use crate::solver_handlers::finalize_simple;

const PANDAS_DATAFRAME_JOIN_DOCS_URL: &str =
    "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.join.html";

/// Handles project-method documentation prompts such as
/// "how the join method works in pandas" with a narrow official-docs summary.
pub fn try_docs_method_explanation(
    prompt: &str,
    normalized: &str,
    log: &mut EventLog,
) -> Option<SymbolicAnswer> {
    if !is_pandas_dataframe_join_prompt(prompt, normalized) {
        return None;
    }

    log.append("docs_method:request", prompt.to_owned());
    log.append("docs_method:project", "pandas".to_owned());
    log.append("docs_method:method", "pandas.DataFrame.join".to_owned());
    log.append("docs_method:source_kind", "official-docs".to_owned());
    log.append("docs_method:source", PANDAS_DATAFRAME_JOIN_DOCS_URL);

    let body = render_pandas_dataframe_join_answer(detect_language(prompt));

    Some(finalize_simple(
        prompt,
        log,
        "docs_method_explanation",
        "response:docs_method_explanation",
        &body,
        0.92,
    ))
}

fn is_pandas_dataframe_join_prompt(prompt: &str, normalized: &str) -> bool {
    let lower = prompt.to_lowercase();
    let normalized = normalized.trim();
    if is_explicit_web_search(normalized) {
        return false;
    }
    if !has_word(normalized, "pandas") {
        return false;
    }
    if !is_explanation_request(normalized) {
        return false;
    }

    lower.contains("dataframe.join")
        || lower.contains("df.join")
        || normalized.contains("join method")
        || normalized.contains("method join")
        || (has_word(normalized, "join") && has_word(normalized, "метод"))
        || (has_word(normalized, "join") && normalized.contains("विधि"))
        || (has_word(normalized, "join") && normalized.contains("方法"))
        || (has_word(normalized, "join")
            && (has_word(normalized, "method") || has_word(normalized, "dataframe")))
}

fn is_explicit_web_search(normalized: &str) -> bool {
    let requests_search = normalized.starts_with("search ")
        || normalized.starts_with("find ")
        || normalized.starts_with("look up ")
        || normalized.starts_with("lookup ");
    requests_search
        && (has_word(normalized, "web")
            || has_word(normalized, "internet")
            || has_word(normalized, "online"))
}

fn is_explanation_request(normalized: &str) -> bool {
    normalized.starts_with("how ")
        || normalized.contains(" how ")
        || normalized.starts_with("explain ")
        || normalized.starts_with("describe ")
        || normalized.starts_with("what does ")
        || normalized.starts_with("what is ")
        || normalized.starts_with("tell me about ")
        || normalized.starts_with("how to use ")
        || normalized.starts_with("как ")
        || normalized.contains(" как ")
        || normalized.starts_with("объясни ")
        || normalized.starts_with("расскажи ")
        || normalized.starts_with("что такое ")
        || normalized.contains("कैसे काम")
        || normalized.starts_with("समझाओ")
        || normalized.starts_with("क्या है ")
        || normalized.contains("如何工作")
        || normalized.contains("怎么工作")
        || normalized.starts_with("解释")
        || normalized.contains("是什么")
}

fn has_word(normalized: &str, word: &str) -> bool {
    normalized.split_whitespace().any(|token| token == word)
}

fn render_pandas_dataframe_join_answer(language: Language) -> String {
    match language {
        Language::Russian => format!(
            "pandas `DataFrame.join` добавляет столбцы из `other` DataFrame \
             или именованной Series к вызывающему DataFrame и возвращает новый \
             DataFrame.\n\n\
             В рамках этого метода: по умолчанию это left join по индексу \
             вызывающего DataFrame. Если задан `on`, pandas сопоставляет этот \
             столбец или уровень индекса с индексом объекта `other`. Параметр \
             `how` управляет объединением ключей (`left`, `right`, `outer`, \
             `inner`, `cross`, `left_anti` или `right_anti`). `lsuffix` и \
             `rsuffix` нужны при совпадающих именах столбцов, `sort` сортирует \
             ключи join, а `validate` проверяет связи one-to-one, one-to-many, \
             many-to-one или many-to-many. Для join столбец-к-столбцу \
             документация pandas указывает на `DataFrame.merge`.\n\n\
             Источник: [pandas.DataFrame.join]({PANDAS_DATAFRAME_JOIN_DOCS_URL}) \
             (официальная документация pandas)."
        ),
        Language::Hindi => format!(
            "pandas `DataFrame.join` कॉल करने वाले DataFrame में `other` \
             DataFrame या named Series के columns जोड़ता है और नया DataFrame \
             लौटाता है.\n\n\
             इस method के दायरे में: default रूप से यह caller के index पर left \
             join करता है. `on` देने पर pandas caller के उस column या index \
             level को `other` object के index से मिलाता है. `how` parameter \
             keys को मिलाने का तरीका चुनता है (`left`, `right`, `outer`, \
             `inner`, `cross`, `left_anti`, या `right_anti`). Column नाम टकराने \
             पर `lsuffix` और `rsuffix`, join keys को sort करने के लिए `sort`, \
             और one-to-one, one-to-many, many-to-one, या many-to-many संबंध \
             जांचने के लिए `validate` इस्तेमाल करें. Column-on-column joins \
             के लिए pandas docs `DataFrame.merge` की ओर भेजते हैं.\n\n\
             Source: [pandas.DataFrame.join]({PANDAS_DATAFRAME_JOIN_DOCS_URL}) \
             (official pandas docs)."
        ),
        Language::Chinese => format!(
            "pandas `DataFrame.join` 会把 `other` DataFrame 或具名 Series 的列加入调用方,并返回新的 DataFrame。\n\n\
             只看这个方法:默认情况下,它使用调用方的 index 执行 left join。设置 `on` 时,pandas 会把调用方的列或索引层级与 `other` 对象的 index 匹配。`how` 参数控制键的组合方式(`left`、`right`、`outer`、`inner`、`cross`、`left_anti` 或 `right_anti`)。列名冲突时使用 `lsuffix` 和 `rsuffix`,用 `sort` 排序 join keys,用 `validate` 检查 one-to-one、one-to-many、many-to-one 或 many-to-many 关系。对于列到列的 join,pandas 文档指向 `DataFrame.merge`。\n\n\
             Source: [pandas.DataFrame.join]({PANDAS_DATAFRAME_JOIN_DOCS_URL}) \
             (official pandas docs)."
        ),
        Language::English | Language::Unknown => format!(
            "pandas `DataFrame.join` joins columns from the `other` DataFrame or \
             named Series into the caller and returns a new DataFrame.\n\n\
             Scoped to this method: by default, it performs a left join using the \
             caller's index. If `on` is set, pandas matches that caller column or \
             index level against the `other` object's index. The `how` parameter \
             controls key handling (`left`, `right`, `outer`, `inner`, `cross`, \
             `left_anti`, or `right_anti`). Use `lsuffix` and `rsuffix` when \
             column names overlap, `sort` to order join keys, and `validate` to \
             check one-to-one, one-to-many, many-to-one, or many-to-many \
             relationships. For column-on-column joins, the pandas docs point to \
             `DataFrame.merge`.\n\n\
             Source: [pandas.DataFrame.join]({PANDAS_DATAFRAME_JOIN_DOCS_URL}) \
             (official pandas docs)."
        ),
    }
}