# How the LifeTrace Natural Language To‑Do Query Parser Works

> Discover how the LifeTrace Natural Language To-Do Query Parser transforms Chinese queries into structured conditions. Learn about its dual-strategy LLM and rule-based approach.

- Repository: [FreeU-group/lifetrace](https://github.com/freeu-group/lifetrace)
- Tags: deep-dive
- Published: 2026-03-02

---

**The `QueryParser` class in [`lifetrace/util/query_parser.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/util/query_parser.py) converts free‑form Chinese queries like "搜索昨天在微信上的聊天记录" into structured `QueryConditions` using a dual‑strategy approach that prioritizes LLM extraction and falls back to deterministic rule‑based parsing.**

The freeu‑group/lifetrace repository implements a sophisticated natural language todo query parser that bridges human intent and database filters. Located in [`lifetrace/util/query_parser.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/util/query_parser.py), this component transforms unstructured Chinese search requests into UTC‑normalized timestamps, resolved process names, and cleaned keyword lists, enabling downstream services such as [`lifetrace/llm/retrieval_service.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/llm/retrieval_service.py) to execute precise vector and SQL searches without requiring users to learn complex query syntax.

## Dual‑Strategy Architecture: LLM First, Rules as Fallback

The parser implements a resilient two‑tier system designed to maximize accuracy while maintaining offline capability. When instantiated with an LLM client, it attempts semantic understanding first; otherwise, it immediately engages deterministic extraction logic.

### Entry Point and LLM Integration (`parse_query`)

The `parse_query` method serves as the orchestration layer. It first checks for the presence of `self.llm_client` and attempts `self.llm_client.parse_query` (lines 34‑72). The parser validates the LLM response to ensure at least one of **keywords**, **app_names**, or a time range is present. Valid results flow into `_build_query_conditions` (lines 72‑92), while failures, empty returns, or missing LLM configurations trigger the rule‑based fallback at line 34.

### Fallback Rule‑Based Engine (`_parse_with_rules`)

When LLM extraction fails or is unavailable, `_parse_with_rules` instantiates an empty `QueryConditions` object and sequentially invokes three specialized extraction methods. This deterministic path ensures consistent behavior without API dependencies and reduces token costs for common query patterns.

## Rule‑Based Extraction Pipeline

The fallback mechanism decomposes natural language todo queries into three structured dimensions: temporal boundaries, application context, and search intent.

### Time Range Recognition (`_extract_time_range`)

This method scans for Chinese temporal keywords stored in `self.time_keywords` (lines 86‑94), including expressions like `今天`, `昨天`, and `本周`. Relative offsets are converted to UTC boundaries using `get_utc_now` and `to_utc` from [`lifetrace/util/time_utils.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/util/time_utils.py). Explicit dates in `YYYY‑MM‑DD` format are captured via regex and parsed with `datetime.strptime`, ensuring all output is UTC‑normalized (lines 94‑97).

### Application Name Resolution (`_extract_app_names`)

Application identification operates through a two‑stage mapping process. First, the method queries the global `app_mapper` (the `AppMapper` defined in [`lifetrace/util/app_utils.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/util/app_utils.py)) for known process names (lines 99‑102). Second, it consults the internal alias table `self.app_name_mapping` (lines 65‑84) and applies regex patterns such as "在 XX 上" or "XX 应用" via `_find_app_names_from_patterns`. Friendly names are ultimately converted to actual process identifiers through `_convert_to_process_names`, enabling precise database filtering.

### Keyword Intent Detection (`_extract_keywords`)

The parser identifies search intent by detecting indicator verbs including `["搜索","查找","包含","关于","找到","寻找"]`. When present, the method strips temporal expressions and app aliases, then tokenizes the remaining text while filtering stop‑words and functional verbs. This yields a cleaned list of meaningful search terms (lines 55‑78) that drive vector‑search relevance in RAG pipelines.

## LLM‑Driven Structured Extraction (`_build_query_conditions`)

When the LLM returns a JSON payload, `_build_query_conditions` handles normalization. It extracts temporal boundaries via `_extract_time_from_parsed_data` (supporting both `time_range` objects and legacy `start_date`/`end_date` fields), converts friendly app names to process names using `_convert_to_process_names`, and passes keywords through verbatim. The populated `QueryConditions` dataclass provides a `to_dict` method (lines 39‑56) that generates clean dictionary representations for immediate SQL or vector‑DB consumption.

## Practical Usage Examples

The parser supports both offline rule‑based operation and LLM‑enhanced extraction.

### Rule‑Based Parsing Without LLM

```python
from lifetrace.util.query_parser import QueryParser

parser = QueryParser()
cond = parser.parse_query("搜索上周在 Chrome 上打开的网页")
print(cond.to_dict())

# → {'start_date': datetime(...), 'end_date': datetime(...),

#    'app_names': ['chrome.exe'], 'keywords': ['网页']}

```

### LLM‑Enhanced Extraction

```python
class DummyLLM:
    def parse_query(self, q):
        return {
            "time_range": {"start": "2024-04-01 00:00:00", "end": "2024-04-07 23:59:59"},
            "app_names": ["微信"],
            "keywords": ["会议记录"]
        }

parser_llm = QueryParser(llm_client=DummyLLM())
cond_llm = parser_llm.parse_query("帮我找一下微信上这周的会议记录")
print(cond_llm.to_dict())

# → {'start_date': datetime(...), 'end_date': datetime(...),

#    'app_names': ['WeChat.exe', 'Weixin.exe', '微信.exe'],

#    'keywords': ['会议记录']}

```

## Summary

- **Dual‑strategy design**: The parser attempts LLM extraction first, then falls back to deterministic rule‑based parsing via `_parse_with_rules` if the LLM fails or is unavailable.
- **UTC normalization**: All temporal expressions are converted to UTC boundaries using `get_utc_now` and `to_utc` from [`lifetrace/util/time_utils.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/util/time_utils.py).
- **App resolution**: Friendly Chinese app names map to actual process identifiers via `AppMapper` in [`lifetrace/util/app_utils.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/util/app_utils.py) and `_convert_to_process_names`.
- **Intent detection**: Keyword extraction requires explicit search indicators (搜索, 查找, etc.) to distinguish search terms from contextual noise.
- **Structured output**: Both paths return a `QueryConditions` dataclass with a `to_dict()` method for immediate consumption by [`retrieval_service.py`](https://github.com/freeu-group/lifetrace/blob/main/retrieval_service.py) and [`rag_service.py`](https://github.com/freeu-group/lifetrace/blob/main/rag_service.py).

## Frequently Asked Questions

### What happens if the LLM client returns malformed data?

The `parse_query` method validates LLM responses to ensure at least one of **keywords**, **app_names**, or a time range is present. If validation fails or the LLM returns empty, the parser automatically falls back to `_parse_with_rules` at line 34, ensuring robust operation even with unreliable LLM outputs.

### How does the parser handle ambiguous time expressions like "recently"?

The rule‑based engine relies on predefined Chinese keywords stored in `self.time_keywords` (lines 86‑94). Ambiguous terms not matching `今天`, `昨天`, `本周`, or explicit date patterns (YYYY‑MM‑DD) may be omitted unless the LLM path successfully interprets them. Explicit dates are captured via regex and parsed with `datetime.strptime` before UTC conversion via `to_utc`.

### Can the parser recognize English application names?

While the primary implementation targets Chinese queries, the `AppMapper` in [`lifetrace/util/app_utils.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/util/app_utils.py) maintains process name mappings that may include English identifiers. The regex patterns in `_find_app_names_from_patterns` focus on Chinese grammar structures ("在 XX 上"), but the underlying mapping system can resolve any friendly name registered in the global `app_mapper` or `self.app_name_mapping` tables.

### Why are keywords only extracted when search indicators are present?

The `_extract_keywords` method requires explicit intent signals such as `["搜索","查找","包含","关于","找到","寻找"]` to avoid conflating descriptive text with search terms. This prevents casual mentions of applications or dates from being treated as search keywords, ensuring that `QueryConditions.keywords` contains only terms explicitly marked for content retrieval.