Merge branch 'main' into clear-doc
Browse files- README-zh.md +48 -0
- README.md +147 -67
- examples/lightrag_gemini_track_token_demo.py +20 -22
- examples/lightrag_siliconcloud_track_token_demo.py +4 -8
- lightrag/llm/openai.py +101 -26
- lightrag/operate.py +38 -14
- lightrag/utils.py +7 -0
- lightrag_webui/src/hooks/useLightragGraph.tsx +2 -2
README-zh.md
CHANGED
@@ -409,6 +409,54 @@ if __name__ == "__main__":
|
|
409 |
|
410 |
</details>
|
411 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
412 |
### 对话历史
|
413 |
|
414 |
LightRAG现在通过对话历史功能支持多轮对话。以下是使用方法:
|
|
|
409 |
|
410 |
</details>
|
411 |
|
412 |
+
### Token统计功能
|
413 |
+
<details>
|
414 |
+
<summary> <b>概述和使用</b> </summary>
|
415 |
+
|
416 |
+
LightRAG提供了TokenTracker工具来跟踪和管理大模型的token消耗。这个功能对于控制API成本和优化性能特别有用。
|
417 |
+
|
418 |
+
#### 使用方法
|
419 |
+
|
420 |
+
```python
|
421 |
+
from lightrag.utils import TokenTracker
|
422 |
+
|
423 |
+
# 创建TokenTracker实例
|
424 |
+
token_tracker = TokenTracker()
|
425 |
+
|
426 |
+
# 方法1:使用上下文管理器(推荐)
|
427 |
+
# 适用于需要自动跟踪token使用的场景
|
428 |
+
with token_tracker:
|
429 |
+
result1 = await llm_model_func("你的问题1")
|
430 |
+
result2 = await llm_model_func("你的问题2")
|
431 |
+
|
432 |
+
# 方法2:手动添加token使用记录
|
433 |
+
# 适用于需要更精细控制token统计的场景
|
434 |
+
token_tracker.reset()
|
435 |
+
|
436 |
+
rag.insert()
|
437 |
+
|
438 |
+
rag.query("你的问题1", param=QueryParam(mode="naive"))
|
439 |
+
rag.query("你的问题2", param=QueryParam(mode="mix"))
|
440 |
+
|
441 |
+
# 显示总token使用量(包含插入和查询操作)
|
442 |
+
print("Token usage:", token_tracker.get_usage())
|
443 |
+
```
|
444 |
+
|
445 |
+
#### 使用建议
|
446 |
+
- 在长会话或批量操作中使用上下文管理器,可以自动跟踪所有token消耗
|
447 |
+
- 对于需要分段统计的场景,使用手动模式并适时调用reset()
|
448 |
+
- 定期检查token使用情况,有助于及时发现异常消耗
|
449 |
+
- 在开发测试阶段积极使用此功能,以便优化生产环境的成本
|
450 |
+
|
451 |
+
#### 实际应用示例
|
452 |
+
您可以参考以下示例来实现token统计:
|
453 |
+
- `examples/lightrag_gemini_track_token_demo.py`:使用Google Gemini模型的token统计示例
|
454 |
+
- `examples/lightrag_siliconcloud_track_token_demo.py`:使用SiliconCloud模型的token统计示例
|
455 |
+
|
456 |
+
这些示例展示了如何在不同模型和场景下有效地使用TokenTracker功能。
|
457 |
+
|
458 |
+
</details>
|
459 |
+
|
460 |
### 对话历史
|
461 |
|
462 |
LightRAG现在通过对话历史功能支持多轮对话。以下是使用方法:
|
README.md
CHANGED
@@ -440,11 +440,65 @@ if __name__ == "__main__":
|
|
440 |
- [Direct OpenAI Example](examples/lightrag_llamaindex_direct_demo.py)
|
441 |
- [LiteLLM Proxy Example](examples/lightrag_llamaindex_litellm_demo.py)
|
442 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
443 |
### Conversation History Support
|
444 |
|
445 |
|
446 |
LightRAG now supports multi-turn dialogue through the conversation history feature. Here's how to use it:
|
447 |
|
|
|
|
|
|
|
448 |
```python
|
449 |
# Create conversation history
|
450 |
conversation_history = [
|
@@ -467,10 +521,15 @@ response = rag.query(
|
|
467 |
)
|
468 |
```
|
469 |
|
|
|
|
|
470 |
### Custom Prompt Support
|
471 |
|
472 |
LightRAG now supports custom prompts for fine-tuned control over the system's behavior. Here's how to use it:
|
473 |
|
|
|
|
|
|
|
474 |
```python
|
475 |
# Create query parameters
|
476 |
query_param = QueryParam(
|
@@ -505,6 +564,8 @@ response_custom = rag.query(
|
|
505 |
print(response_custom)
|
506 |
```
|
507 |
|
|
|
|
|
508 |
### Separate Keyword Extraction
|
509 |
|
510 |
We've introduced a new function `query_with_separate_keyword_extraction` to enhance the keyword extraction capabilities. This function separates the keyword extraction process from the user's prompt, focusing solely on the query to improve the relevance of extracted keywords.
|
@@ -518,7 +579,8 @@ The function operates by dividing the input into two parts:
|
|
518 |
|
519 |
It then performs keyword extraction exclusively on the `user query`. This separation ensures that the extraction process is focused and relevant, unaffected by any additional language in the `prompt`. It also allows the `prompt` to serve purely for response formatting, maintaining the intent and clarity of the user's original question.
|
520 |
|
521 |
-
|
|
|
522 |
|
523 |
This `example` shows how to tailor the function for educational content, focusing on detailed explanations for older students.
|
524 |
|
@@ -530,67 +592,6 @@ rag.query_with_separate_keyword_extraction(
|
|
530 |
)
|
531 |
```
|
532 |
|
533 |
-
### Insert Custom KG
|
534 |
-
|
535 |
-
```python
|
536 |
-
custom_kg = {
|
537 |
-
"chunks": [
|
538 |
-
{
|
539 |
-
"content": "Alice and Bob are collaborating on quantum computing research.",
|
540 |
-
"source_id": "doc-1"
|
541 |
-
}
|
542 |
-
],
|
543 |
-
"entities": [
|
544 |
-
{
|
545 |
-
"entity_name": "Alice",
|
546 |
-
"entity_type": "person",
|
547 |
-
"description": "Alice is a researcher specializing in quantum physics.",
|
548 |
-
"source_id": "doc-1"
|
549 |
-
},
|
550 |
-
{
|
551 |
-
"entity_name": "Bob",
|
552 |
-
"entity_type": "person",
|
553 |
-
"description": "Bob is a mathematician.",
|
554 |
-
"source_id": "doc-1"
|
555 |
-
},
|
556 |
-
{
|
557 |
-
"entity_name": "Quantum Computing",
|
558 |
-
"entity_type": "technology",
|
559 |
-
"description": "Quantum computing utilizes quantum mechanical phenomena for computation.",
|
560 |
-
"source_id": "doc-1"
|
561 |
-
}
|
562 |
-
],
|
563 |
-
"relationships": [
|
564 |
-
{
|
565 |
-
"src_id": "Alice",
|
566 |
-
"tgt_id": "Bob",
|
567 |
-
"description": "Alice and Bob are research partners.",
|
568 |
-
"keywords": "collaboration research",
|
569 |
-
"weight": 1.0,
|
570 |
-
"source_id": "doc-1"
|
571 |
-
},
|
572 |
-
{
|
573 |
-
"src_id": "Alice",
|
574 |
-
"tgt_id": "Quantum Computing",
|
575 |
-
"description": "Alice conducts research on quantum computing.",
|
576 |
-
"keywords": "research expertise",
|
577 |
-
"weight": 1.0,
|
578 |
-
"source_id": "doc-1"
|
579 |
-
},
|
580 |
-
{
|
581 |
-
"src_id": "Bob",
|
582 |
-
"tgt_id": "Quantum Computing",
|
583 |
-
"description": "Bob researches quantum computing.",
|
584 |
-
"keywords": "research application",
|
585 |
-
"weight": 1.0,
|
586 |
-
"source_id": "doc-1"
|
587 |
-
}
|
588 |
-
]
|
589 |
-
}
|
590 |
-
|
591 |
-
rag.insert_custom_kg(custom_kg)
|
592 |
-
```
|
593 |
-
|
594 |
</details>
|
595 |
|
596 |
## Insert
|
@@ -682,6 +683,70 @@ rag.insert(text_content.decode('utf-8'))
|
|
682 |
|
683 |
</details>
|
684 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
685 |
<details>
|
686 |
<summary><b>Citation Functionality</b></summary>
|
687 |
|
@@ -841,7 +906,8 @@ rag.delete_by_doc_id("doc_id")
|
|
841 |
|
842 |
LightRAG now supports comprehensive knowledge graph management capabilities, allowing you to create, edit, and delete entities and relationships within your knowledge graph.
|
843 |
|
844 |
-
|
|
|
845 |
|
846 |
```python
|
847 |
# Create new entity
|
@@ -864,7 +930,10 @@ relation = rag.create_relation("Google", "Gmail", {
|
|
864 |
})
|
865 |
```
|
866 |
|
867 |
-
|
|
|
|
|
|
|
868 |
|
869 |
```python
|
870 |
# Edit an existing entity
|
@@ -901,6 +970,8 @@ All operations are available in both synchronous and asynchronous versions. The
|
|
901 |
|
902 |
These operations maintain data consistency across both the graph database and vector database components, ensuring your knowledge graph remains coherent.
|
903 |
|
|
|
|
|
904 |
## Data Export Functions
|
905 |
|
906 |
### Overview
|
@@ -909,7 +980,8 @@ LightRAG allows you to export your knowledge graph data in various formats for a
|
|
909 |
|
910 |
### Export Functions
|
911 |
|
912 |
-
|
|
|
913 |
|
914 |
```python
|
915 |
# Basic CSV export (default format)
|
@@ -919,7 +991,10 @@ rag.export_data("knowledge_graph.csv")
|
|
919 |
rag.export_data("output.xlsx", file_format="excel")
|
920 |
```
|
921 |
|
922 |
-
|
|
|
|
|
|
|
923 |
|
924 |
```python
|
925 |
#Export data in CSV format
|
@@ -934,13 +1009,18 @@ rag.export_data("graph_data.md", file_format="md")
|
|
934 |
# Export data in Text
|
935 |
rag.export_data("graph_data.txt", file_format="txt")
|
936 |
```
|
937 |
-
|
|
|
|
|
|
|
938 |
|
939 |
Include vector embeddings in the export (optional):
|
940 |
|
941 |
```python
|
942 |
rag.export_data("complete_data.csv", include_vector_data=True)
|
943 |
```
|
|
|
|
|
944 |
### Data Included in Export
|
945 |
|
946 |
All exports include:
|
|
|
440 |
- [Direct OpenAI Example](examples/lightrag_llamaindex_direct_demo.py)
|
441 |
- [LiteLLM Proxy Example](examples/lightrag_llamaindex_litellm_demo.py)
|
442 |
|
443 |
+
</details>
|
444 |
+
|
445 |
+
### Token Usage Tracking
|
446 |
+
|
447 |
+
<details>
|
448 |
+
<summary> <b>Overview and Usage</b> </summary>
|
449 |
+
|
450 |
+
LightRAG provides a TokenTracker tool to monitor and manage token consumption by large language models. This feature is particularly useful for controlling API costs and optimizing performance.
|
451 |
+
|
452 |
+
#### Usage
|
453 |
+
|
454 |
+
```python
|
455 |
+
from lightrag.utils import TokenTracker
|
456 |
+
|
457 |
+
# Create TokenTracker instance
|
458 |
+
token_tracker = TokenTracker()
|
459 |
+
|
460 |
+
# Method 1: Using context manager (Recommended)
|
461 |
+
# Suitable for scenarios requiring automatic token usage tracking
|
462 |
+
with token_tracker:
|
463 |
+
result1 = await llm_model_func("your question 1")
|
464 |
+
result2 = await llm_model_func("your question 2")
|
465 |
+
|
466 |
+
# Method 2: Manually adding token usage records
|
467 |
+
# Suitable for scenarios requiring more granular control over token statistics
|
468 |
+
token_tracker.reset()
|
469 |
+
|
470 |
+
rag.insert()
|
471 |
+
|
472 |
+
rag.query("your question 1", param=QueryParam(mode="naive"))
|
473 |
+
rag.query("your question 2", param=QueryParam(mode="mix"))
|
474 |
+
|
475 |
+
# Display total token usage (including insert and query operations)
|
476 |
+
print("Token usage:", token_tracker.get_usage())
|
477 |
+
```
|
478 |
+
|
479 |
+
#### Usage Tips
|
480 |
+
- Use context managers for long sessions or batch operations to automatically track all token consumption
|
481 |
+
- For scenarios requiring segmented statistics, use manual mode and call reset() when appropriate
|
482 |
+
- Regular checking of token usage helps detect abnormal consumption early
|
483 |
+
- Actively use this feature during development and testing to optimize production costs
|
484 |
+
|
485 |
+
#### Practical Examples
|
486 |
+
You can refer to these examples for implementing token tracking:
|
487 |
+
- `examples/lightrag_gemini_track_token_demo.py`: Token tracking example using Google Gemini model
|
488 |
+
- `examples/lightrag_siliconcloud_track_token_demo.py`: Token tracking example using SiliconCloud model
|
489 |
+
|
490 |
+
These examples demonstrate how to effectively use the TokenTracker feature with different models and scenarios.
|
491 |
+
|
492 |
+
</details>
|
493 |
+
|
494 |
### Conversation History Support
|
495 |
|
496 |
|
497 |
LightRAG now supports multi-turn dialogue through the conversation history feature. Here's how to use it:
|
498 |
|
499 |
+
<details>
|
500 |
+
<summary> <b> Usage Example </b></summary>
|
501 |
+
|
502 |
```python
|
503 |
# Create conversation history
|
504 |
conversation_history = [
|
|
|
521 |
)
|
522 |
```
|
523 |
|
524 |
+
</details>
|
525 |
+
|
526 |
### Custom Prompt Support
|
527 |
|
528 |
LightRAG now supports custom prompts for fine-tuned control over the system's behavior. Here's how to use it:
|
529 |
|
530 |
+
<details>
|
531 |
+
<summary> <b> Usage Example </b></summary>
|
532 |
+
|
533 |
```python
|
534 |
# Create query parameters
|
535 |
query_param = QueryParam(
|
|
|
564 |
print(response_custom)
|
565 |
```
|
566 |
|
567 |
+
</details>
|
568 |
+
|
569 |
### Separate Keyword Extraction
|
570 |
|
571 |
We've introduced a new function `query_with_separate_keyword_extraction` to enhance the keyword extraction capabilities. This function separates the keyword extraction process from the user's prompt, focusing solely on the query to improve the relevance of extracted keywords.
|
|
|
579 |
|
580 |
It then performs keyword extraction exclusively on the `user query`. This separation ensures that the extraction process is focused and relevant, unaffected by any additional language in the `prompt`. It also allows the `prompt` to serve purely for response formatting, maintaining the intent and clarity of the user's original question.
|
581 |
|
582 |
+
<details>
|
583 |
+
<summary> <b> Usage Example </b></summary>
|
584 |
|
585 |
This `example` shows how to tailor the function for educational content, focusing on detailed explanations for older students.
|
586 |
|
|
|
592 |
)
|
593 |
```
|
594 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
595 |
</details>
|
596 |
|
597 |
## Insert
|
|
|
683 |
|
684 |
</details>
|
685 |
|
686 |
+
<details>
|
687 |
+
<summary> <b> Insert Custom KG </b></summary>
|
688 |
+
|
689 |
+
```python
|
690 |
+
custom_kg = {
|
691 |
+
"chunks": [
|
692 |
+
{
|
693 |
+
"content": "Alice and Bob are collaborating on quantum computing research.",
|
694 |
+
"source_id": "doc-1"
|
695 |
+
}
|
696 |
+
],
|
697 |
+
"entities": [
|
698 |
+
{
|
699 |
+
"entity_name": "Alice",
|
700 |
+
"entity_type": "person",
|
701 |
+
"description": "Alice is a researcher specializing in quantum physics.",
|
702 |
+
"source_id": "doc-1"
|
703 |
+
},
|
704 |
+
{
|
705 |
+
"entity_name": "Bob",
|
706 |
+
"entity_type": "person",
|
707 |
+
"description": "Bob is a mathematician.",
|
708 |
+
"source_id": "doc-1"
|
709 |
+
},
|
710 |
+
{
|
711 |
+
"entity_name": "Quantum Computing",
|
712 |
+
"entity_type": "technology",
|
713 |
+
"description": "Quantum computing utilizes quantum mechanical phenomena for computation.",
|
714 |
+
"source_id": "doc-1"
|
715 |
+
}
|
716 |
+
],
|
717 |
+
"relationships": [
|
718 |
+
{
|
719 |
+
"src_id": "Alice",
|
720 |
+
"tgt_id": "Bob",
|
721 |
+
"description": "Alice and Bob are research partners.",
|
722 |
+
"keywords": "collaboration research",
|
723 |
+
"weight": 1.0,
|
724 |
+
"source_id": "doc-1"
|
725 |
+
},
|
726 |
+
{
|
727 |
+
"src_id": "Alice",
|
728 |
+
"tgt_id": "Quantum Computing",
|
729 |
+
"description": "Alice conducts research on quantum computing.",
|
730 |
+
"keywords": "research expertise",
|
731 |
+
"weight": 1.0,
|
732 |
+
"source_id": "doc-1"
|
733 |
+
},
|
734 |
+
{
|
735 |
+
"src_id": "Bob",
|
736 |
+
"tgt_id": "Quantum Computing",
|
737 |
+
"description": "Bob researches quantum computing.",
|
738 |
+
"keywords": "research application",
|
739 |
+
"weight": 1.0,
|
740 |
+
"source_id": "doc-1"
|
741 |
+
}
|
742 |
+
]
|
743 |
+
}
|
744 |
+
|
745 |
+
rag.insert_custom_kg(custom_kg)
|
746 |
+
```
|
747 |
+
|
748 |
+
</details>
|
749 |
+
|
750 |
<details>
|
751 |
<summary><b>Citation Functionality</b></summary>
|
752 |
|
|
|
906 |
|
907 |
LightRAG now supports comprehensive knowledge graph management capabilities, allowing you to create, edit, and delete entities and relationships within your knowledge graph.
|
908 |
|
909 |
+
<details>
|
910 |
+
<summary> <b> Create Entities and Relations </b></summary>
|
911 |
|
912 |
```python
|
913 |
# Create new entity
|
|
|
930 |
})
|
931 |
```
|
932 |
|
933 |
+
</details>
|
934 |
+
|
935 |
+
<details>
|
936 |
+
<summary> <b> Edit Entities and Relations </b></summary>
|
937 |
|
938 |
```python
|
939 |
# Edit an existing entity
|
|
|
970 |
|
971 |
These operations maintain data consistency across both the graph database and vector database components, ensuring your knowledge graph remains coherent.
|
972 |
|
973 |
+
</details>
|
974 |
+
|
975 |
## Data Export Functions
|
976 |
|
977 |
### Overview
|
|
|
980 |
|
981 |
### Export Functions
|
982 |
|
983 |
+
<details>
|
984 |
+
<summary> <b> Basic Usage </b></summary>
|
985 |
|
986 |
```python
|
987 |
# Basic CSV export (default format)
|
|
|
991 |
rag.export_data("output.xlsx", file_format="excel")
|
992 |
```
|
993 |
|
994 |
+
</details>
|
995 |
+
|
996 |
+
<details>
|
997 |
+
<summary> <b> Different File Formats supported </b></summary>
|
998 |
|
999 |
```python
|
1000 |
#Export data in CSV format
|
|
|
1009 |
# Export data in Text
|
1010 |
rag.export_data("graph_data.txt", file_format="txt")
|
1011 |
```
|
1012 |
+
</details>
|
1013 |
+
|
1014 |
+
<details>
|
1015 |
+
<summary> <b> Additional Options </b></summary>
|
1016 |
|
1017 |
Include vector embeddings in the export (optional):
|
1018 |
|
1019 |
```python
|
1020 |
rag.export_data("complete_data.csv", include_vector_data=True)
|
1021 |
```
|
1022 |
+
</details>
|
1023 |
+
|
1024 |
### Data Included in Export
|
1025 |
|
1026 |
All exports include:
|
examples/lightrag_gemini_track_token_demo.py
CHANGED
@@ -115,38 +115,36 @@ def main():
|
|
115 |
# Initialize RAG instance
|
116 |
rag = asyncio.run(initialize_rag())
|
117 |
|
118 |
-
# Reset tracker before processing queries
|
119 |
-
token_tracker.reset()
|
120 |
-
|
121 |
with open("./book.txt", "r", encoding="utf-8") as f:
|
122 |
rag.insert(f.read())
|
123 |
|
124 |
-
|
125 |
-
|
126 |
-
|
|
|
|
|
|
|
127 |
)
|
128 |
-
)
|
129 |
|
130 |
-
|
131 |
-
|
132 |
-
|
|
|
133 |
)
|
134 |
-
)
|
135 |
|
136 |
-
|
137 |
-
|
138 |
-
|
|
|
|
|
139 |
)
|
140 |
-
)
|
141 |
|
142 |
-
|
143 |
-
|
144 |
-
|
|
|
|
|
145 |
)
|
146 |
-
)
|
147 |
-
|
148 |
-
# Display final token usage after main query
|
149 |
-
print("Token usage:", token_tracker.get_usage())
|
150 |
|
151 |
|
152 |
if __name__ == "__main__":
|
|
|
115 |
# Initialize RAG instance
|
116 |
rag = asyncio.run(initialize_rag())
|
117 |
|
|
|
|
|
|
|
118 |
with open("./book.txt", "r", encoding="utf-8") as f:
|
119 |
rag.insert(f.read())
|
120 |
|
121 |
+
# Context Manager Method
|
122 |
+
with token_tracker:
|
123 |
+
print(
|
124 |
+
rag.query(
|
125 |
+
"What are the top themes in this story?", param=QueryParam(mode="naive")
|
126 |
+
)
|
127 |
)
|
|
|
128 |
|
129 |
+
print(
|
130 |
+
rag.query(
|
131 |
+
"What are the top themes in this story?", param=QueryParam(mode="local")
|
132 |
+
)
|
133 |
)
|
|
|
134 |
|
135 |
+
print(
|
136 |
+
rag.query(
|
137 |
+
"What are the top themes in this story?",
|
138 |
+
param=QueryParam(mode="global"),
|
139 |
+
)
|
140 |
)
|
|
|
141 |
|
142 |
+
print(
|
143 |
+
rag.query(
|
144 |
+
"What are the top themes in this story?",
|
145 |
+
param=QueryParam(mode="hybrid"),
|
146 |
+
)
|
147 |
)
|
|
|
|
|
|
|
|
|
148 |
|
149 |
|
150 |
if __name__ == "__main__":
|
examples/lightrag_siliconcloud_track_token_demo.py
CHANGED
@@ -44,14 +44,10 @@ async def embedding_func(texts: list[str]) -> np.ndarray:
|
|
44 |
|
45 |
# function test
|
46 |
async def test_funcs():
|
47 |
-
#
|
48 |
-
token_tracker
|
49 |
-
|
50 |
-
|
51 |
-
print("llm_model_func: ", result)
|
52 |
-
|
53 |
-
# Display final token usage after main query
|
54 |
-
print("Token usage:", token_tracker.get_usage())
|
55 |
|
56 |
|
57 |
asyncio.run(test_funcs())
|
|
|
44 |
|
45 |
# function test
|
46 |
async def test_funcs():
|
47 |
+
# Context Manager Method
|
48 |
+
with token_tracker:
|
49 |
+
result = await llm_model_func("How are you?")
|
50 |
+
print("llm_model_func: ", result)
|
|
|
|
|
|
|
|
|
51 |
|
52 |
|
53 |
asyncio.run(test_funcs())
|
lightrag/llm/openai.py
CHANGED
@@ -44,6 +44,47 @@ class InvalidResponseError(Exception):
|
|
44 |
pass
|
45 |
|
46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
47 |
@retry(
|
48 |
stop=stop_after_attempt(3),
|
49 |
wait=wait_exponential(multiplier=1, min=4, max=10),
|
@@ -61,29 +102,52 @@ async def openai_complete_if_cache(
|
|
61 |
token_tracker: Any | None = None,
|
62 |
**kwargs: Any,
|
63 |
) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
if history_messages is None:
|
65 |
history_messages = []
|
66 |
-
if not api_key:
|
67 |
-
api_key = os.environ["OPENAI_API_KEY"]
|
68 |
-
|
69 |
-
default_headers = {
|
70 |
-
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_8) LightRAG/{__api_version__}",
|
71 |
-
"Content-Type": "application/json",
|
72 |
-
}
|
73 |
|
74 |
# Set openai logger level to INFO when VERBOSE_DEBUG is off
|
75 |
if not VERBOSE_DEBUG and logger.level == logging.DEBUG:
|
76 |
logging.getLogger("openai").setLevel(logging.INFO)
|
77 |
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
)
|
|
|
|
|
85 |
kwargs.pop("hashing_kv", None)
|
86 |
kwargs.pop("keyword_extraction", None)
|
|
|
|
|
87 |
messages: list[dict[str, Any]] = []
|
88 |
if system_prompt:
|
89 |
messages.append({"role": "system", "content": system_prompt})
|
@@ -272,21 +336,32 @@ async def openai_embed(
|
|
272 |
model: str = "text-embedding-3-small",
|
273 |
base_url: str = None,
|
274 |
api_key: str = None,
|
|
|
275 |
) -> np.ndarray:
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
289 |
)
|
|
|
290 |
response = await openai_async_client.embeddings.create(
|
291 |
model=model, input=texts, encoding_format="float"
|
292 |
)
|
|
|
44 |
pass
|
45 |
|
46 |
|
47 |
+
def create_openai_async_client(
|
48 |
+
api_key: str | None = None,
|
49 |
+
base_url: str | None = None,
|
50 |
+
client_configs: dict[str, Any] = None,
|
51 |
+
) -> AsyncOpenAI:
|
52 |
+
"""Create an AsyncOpenAI client with the given configuration.
|
53 |
+
|
54 |
+
Args:
|
55 |
+
api_key: OpenAI API key. If None, uses the OPENAI_API_KEY environment variable.
|
56 |
+
base_url: Base URL for the OpenAI API. If None, uses the default OpenAI API URL.
|
57 |
+
client_configs: Additional configuration options for the AsyncOpenAI client.
|
58 |
+
These will override any default configurations but will be overridden by
|
59 |
+
explicit parameters (api_key, base_url).
|
60 |
+
|
61 |
+
Returns:
|
62 |
+
An AsyncOpenAI client instance.
|
63 |
+
"""
|
64 |
+
if not api_key:
|
65 |
+
api_key = os.environ["OPENAI_API_KEY"]
|
66 |
+
|
67 |
+
default_headers = {
|
68 |
+
"User-Agent": f"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_8) LightRAG/{__api_version__}",
|
69 |
+
"Content-Type": "application/json",
|
70 |
+
}
|
71 |
+
|
72 |
+
if client_configs is None:
|
73 |
+
client_configs = {}
|
74 |
+
|
75 |
+
# Create a merged config dict with precedence: explicit params > client_configs > defaults
|
76 |
+
merged_configs = {
|
77 |
+
**client_configs,
|
78 |
+
"default_headers": default_headers,
|
79 |
+
"api_key": api_key,
|
80 |
+
}
|
81 |
+
|
82 |
+
if base_url is not None:
|
83 |
+
merged_configs["base_url"] = base_url
|
84 |
+
|
85 |
+
return AsyncOpenAI(**merged_configs)
|
86 |
+
|
87 |
+
|
88 |
@retry(
|
89 |
stop=stop_after_attempt(3),
|
90 |
wait=wait_exponential(multiplier=1, min=4, max=10),
|
|
|
102 |
token_tracker: Any | None = None,
|
103 |
**kwargs: Any,
|
104 |
) -> str:
|
105 |
+
"""Complete a prompt using OpenAI's API with caching support.
|
106 |
+
|
107 |
+
Args:
|
108 |
+
model: The OpenAI model to use.
|
109 |
+
prompt: The prompt to complete.
|
110 |
+
system_prompt: Optional system prompt to include.
|
111 |
+
history_messages: Optional list of previous messages in the conversation.
|
112 |
+
base_url: Optional base URL for the OpenAI API.
|
113 |
+
api_key: Optional OpenAI API key. If None, uses the OPENAI_API_KEY environment variable.
|
114 |
+
**kwargs: Additional keyword arguments to pass to the OpenAI API.
|
115 |
+
Special kwargs:
|
116 |
+
- openai_client_configs: Dict of configuration options for the AsyncOpenAI client.
|
117 |
+
These will be passed to the client constructor but will be overridden by
|
118 |
+
explicit parameters (api_key, base_url).
|
119 |
+
- hashing_kv: Will be removed from kwargs before passing to OpenAI.
|
120 |
+
- keyword_extraction: Will be removed from kwargs before passing to OpenAI.
|
121 |
+
|
122 |
+
Returns:
|
123 |
+
The completed text or an async iterator of text chunks if streaming.
|
124 |
+
|
125 |
+
Raises:
|
126 |
+
InvalidResponseError: If the response from OpenAI is invalid or empty.
|
127 |
+
APIConnectionError: If there is a connection error with the OpenAI API.
|
128 |
+
RateLimitError: If the OpenAI API rate limit is exceeded.
|
129 |
+
APITimeoutError: If the OpenAI API request times out.
|
130 |
+
"""
|
131 |
if history_messages is None:
|
132 |
history_messages = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
133 |
|
134 |
# Set openai logger level to INFO when VERBOSE_DEBUG is off
|
135 |
if not VERBOSE_DEBUG and logger.level == logging.DEBUG:
|
136 |
logging.getLogger("openai").setLevel(logging.INFO)
|
137 |
|
138 |
+
# Extract client configuration options
|
139 |
+
client_configs = kwargs.pop("openai_client_configs", {})
|
140 |
+
|
141 |
+
# Create the OpenAI client
|
142 |
+
openai_async_client = create_openai_async_client(
|
143 |
+
api_key=api_key, base_url=base_url, client_configs=client_configs
|
144 |
)
|
145 |
+
|
146 |
+
# Remove special kwargs that shouldn't be passed to OpenAI
|
147 |
kwargs.pop("hashing_kv", None)
|
148 |
kwargs.pop("keyword_extraction", None)
|
149 |
+
|
150 |
+
# Prepare messages
|
151 |
messages: list[dict[str, Any]] = []
|
152 |
if system_prompt:
|
153 |
messages.append({"role": "system", "content": system_prompt})
|
|
|
336 |
model: str = "text-embedding-3-small",
|
337 |
base_url: str = None,
|
338 |
api_key: str = None,
|
339 |
+
client_configs: dict[str, Any] = None,
|
340 |
) -> np.ndarray:
|
341 |
+
"""Generate embeddings for a list of texts using OpenAI's API.
|
342 |
+
|
343 |
+
Args:
|
344 |
+
texts: List of texts to embed.
|
345 |
+
model: The OpenAI embedding model to use.
|
346 |
+
base_url: Optional base URL for the OpenAI API.
|
347 |
+
api_key: Optional OpenAI API key. If None, uses the OPENAI_API_KEY environment variable.
|
348 |
+
client_configs: Additional configuration options for the AsyncOpenAI client.
|
349 |
+
These will override any default configurations but will be overridden by
|
350 |
+
explicit parameters (api_key, base_url).
|
351 |
+
|
352 |
+
Returns:
|
353 |
+
A numpy array of embeddings, one per input text.
|
354 |
+
|
355 |
+
Raises:
|
356 |
+
APIConnectionError: If there is a connection error with the OpenAI API.
|
357 |
+
RateLimitError: If the OpenAI API rate limit is exceeded.
|
358 |
+
APITimeoutError: If the OpenAI API request times out.
|
359 |
+
"""
|
360 |
+
# Create the OpenAI client
|
361 |
+
openai_async_client = create_openai_async_client(
|
362 |
+
api_key=api_key, base_url=base_url, client_configs=client_configs
|
363 |
)
|
364 |
+
|
365 |
response = await openai_async_client.embeddings.create(
|
366 |
model=model, input=texts, encoding_format="float"
|
367 |
)
|
lightrag/operate.py
CHANGED
@@ -697,8 +697,7 @@ async def kg_query(
|
|
697 |
if cached_response is not None:
|
698 |
return cached_response
|
699 |
|
700 |
-
|
701 |
-
hl_keywords, ll_keywords = await extract_keywords_only(
|
702 |
query, query_param, global_config, hashing_kv
|
703 |
)
|
704 |
|
@@ -794,6 +793,38 @@ async def kg_query(
|
|
794 |
return response
|
795 |
|
796 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
797 |
async def extract_keywords_only(
|
798 |
text: str,
|
799 |
param: QueryParam,
|
@@ -934,8 +965,7 @@ async def mix_kg_vector_query(
|
|
934 |
# 2. Execute knowledge graph and vector searches in parallel
|
935 |
async def get_kg_context():
|
936 |
try:
|
937 |
-
|
938 |
-
hl_keywords, ll_keywords = await extract_keywords_only(
|
939 |
query, query_param, global_config, hashing_kv
|
940 |
)
|
941 |
|
@@ -983,7 +1013,6 @@ async def mix_kg_vector_query(
|
|
983 |
try:
|
984 |
# Reduce top_k for vector search in hybrid mode since we have structured information from KG
|
985 |
mix_topk = min(10, query_param.top_k)
|
986 |
-
# TODO: add ids to the query
|
987 |
results = await chunks_vdb.query(
|
988 |
augmented_query, top_k=mix_topk, ids=query_param.ids
|
989 |
)
|
@@ -1581,9 +1610,7 @@ async def _get_edge_data(
|
|
1581 |
|
1582 |
text_units_section_list = [["id", "content", "file_path"]]
|
1583 |
for i, t in enumerate(use_text_units):
|
1584 |
-
text_units_section_list.append(
|
1585 |
-
[i, t["content"], t.get("file_path", "unknown_source")]
|
1586 |
-
)
|
1587 |
text_units_context = list_of_list_to_csv(text_units_section_list)
|
1588 |
return entities_context, relations_context, text_units_context
|
1589 |
|
@@ -2017,16 +2044,13 @@ async def query_with_keywords(
|
|
2017 |
Query response or async iterator
|
2018 |
"""
|
2019 |
# Extract keywords
|
2020 |
-
hl_keywords, ll_keywords = await
|
2021 |
-
|
2022 |
-
|
2023 |
global_config=global_config,
|
2024 |
hashing_kv=hashing_kv,
|
2025 |
)
|
2026 |
|
2027 |
-
param.hl_keywords = hl_keywords
|
2028 |
-
param.ll_keywords = ll_keywords
|
2029 |
-
|
2030 |
# Create a new string with the prompt and the keywords
|
2031 |
ll_keywords_str = ", ".join(ll_keywords)
|
2032 |
hl_keywords_str = ", ".join(hl_keywords)
|
|
|
697 |
if cached_response is not None:
|
698 |
return cached_response
|
699 |
|
700 |
+
hl_keywords, ll_keywords = await get_keywords_from_query(
|
|
|
701 |
query, query_param, global_config, hashing_kv
|
702 |
)
|
703 |
|
|
|
793 |
return response
|
794 |
|
795 |
|
796 |
+
async def get_keywords_from_query(
|
797 |
+
query: str,
|
798 |
+
query_param: QueryParam,
|
799 |
+
global_config: dict[str, str],
|
800 |
+
hashing_kv: BaseKVStorage | None = None,
|
801 |
+
) -> tuple[list[str], list[str]]:
|
802 |
+
"""
|
803 |
+
Retrieves high-level and low-level keywords for RAG operations.
|
804 |
+
|
805 |
+
This function checks if keywords are already provided in query parameters,
|
806 |
+
and if not, extracts them from the query text using LLM.
|
807 |
+
|
808 |
+
Args:
|
809 |
+
query: The user's query text
|
810 |
+
query_param: Query parameters that may contain pre-defined keywords
|
811 |
+
global_config: Global configuration dictionary
|
812 |
+
hashing_kv: Optional key-value storage for caching results
|
813 |
+
|
814 |
+
Returns:
|
815 |
+
A tuple containing (high_level_keywords, low_level_keywords)
|
816 |
+
"""
|
817 |
+
# Check if pre-defined keywords are already provided
|
818 |
+
if query_param.hl_keywords or query_param.ll_keywords:
|
819 |
+
return query_param.hl_keywords, query_param.ll_keywords
|
820 |
+
|
821 |
+
# Extract keywords using extract_keywords_only function which already supports conversation history
|
822 |
+
hl_keywords, ll_keywords = await extract_keywords_only(
|
823 |
+
query, query_param, global_config, hashing_kv
|
824 |
+
)
|
825 |
+
return hl_keywords, ll_keywords
|
826 |
+
|
827 |
+
|
828 |
async def extract_keywords_only(
|
829 |
text: str,
|
830 |
param: QueryParam,
|
|
|
965 |
# 2. Execute knowledge graph and vector searches in parallel
|
966 |
async def get_kg_context():
|
967 |
try:
|
968 |
+
hl_keywords, ll_keywords = await get_keywords_from_query(
|
|
|
969 |
query, query_param, global_config, hashing_kv
|
970 |
)
|
971 |
|
|
|
1013 |
try:
|
1014 |
# Reduce top_k for vector search in hybrid mode since we have structured information from KG
|
1015 |
mix_topk = min(10, query_param.top_k)
|
|
|
1016 |
results = await chunks_vdb.query(
|
1017 |
augmented_query, top_k=mix_topk, ids=query_param.ids
|
1018 |
)
|
|
|
1610 |
|
1611 |
text_units_section_list = [["id", "content", "file_path"]]
|
1612 |
for i, t in enumerate(use_text_units):
|
1613 |
+
text_units_section_list.append([i, t["content"], t.get("file_path", "unknown")])
|
|
|
|
|
1614 |
text_units_context = list_of_list_to_csv(text_units_section_list)
|
1615 |
return entities_context, relations_context, text_units_context
|
1616 |
|
|
|
2044 |
Query response or async iterator
|
2045 |
"""
|
2046 |
# Extract keywords
|
2047 |
+
hl_keywords, ll_keywords = await get_keywords_from_query(
|
2048 |
+
query=query,
|
2049 |
+
query_param=param,
|
2050 |
global_config=global_config,
|
2051 |
hashing_kv=hashing_kv,
|
2052 |
)
|
2053 |
|
|
|
|
|
|
|
2054 |
# Create a new string with the prompt and the keywords
|
2055 |
ll_keywords_str = ", ".join(ll_keywords)
|
2056 |
hl_keywords_str = ", ".join(hl_keywords)
|
lightrag/utils.py
CHANGED
@@ -962,6 +962,13 @@ class TokenTracker:
|
|
962 |
def __init__(self):
|
963 |
self.reset()
|
964 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
965 |
def reset(self):
|
966 |
self.prompt_tokens = 0
|
967 |
self.completion_tokens = 0
|
|
|
962 |
def __init__(self):
|
963 |
self.reset()
|
964 |
|
965 |
+
def __enter__(self):
|
966 |
+
self.reset()
|
967 |
+
return self
|
968 |
+
|
969 |
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
970 |
+
print(self)
|
971 |
+
|
972 |
def reset(self):
|
973 |
self.prompt_tokens = 0
|
974 |
self.completion_tokens = 0
|
lightrag_webui/src/hooks/useLightragGraph.tsx
CHANGED
@@ -205,7 +205,7 @@ const createSigmaGraph = (rawGraph: RawGraph | null) => {
|
|
205 |
// Add edges from raw graph data
|
206 |
for (const rawEdge of rawGraph?.edges ?? []) {
|
207 |
rawEdge.dynamicId = graph.addDirectedEdge(rawEdge.source, rawEdge.target, {
|
208 |
-
label: rawEdge.
|
209 |
})
|
210 |
}
|
211 |
|
@@ -666,7 +666,7 @@ const useLightrangeGraph = () => {
|
|
666 |
|
667 |
// Add the edge to the sigma graph
|
668 |
newEdge.dynamicId = sigmaGraph.addDirectedEdge(newEdge.source, newEdge.target, {
|
669 |
-
label: newEdge.
|
670 |
});
|
671 |
|
672 |
// Add the edge to the raw graph
|
|
|
205 |
// Add edges from raw graph data
|
206 |
for (const rawEdge of rawGraph?.edges ?? []) {
|
207 |
rawEdge.dynamicId = graph.addDirectedEdge(rawEdge.source, rawEdge.target, {
|
208 |
+
label: rawEdge.properties?.keywords || undefined
|
209 |
})
|
210 |
}
|
211 |
|
|
|
666 |
|
667 |
// Add the edge to the sigma graph
|
668 |
newEdge.dynamicId = sigmaGraph.addDirectedEdge(newEdge.source, newEdge.target, {
|
669 |
+
label: newEdge.properties?.keywords || undefined
|
670 |
});
|
671 |
|
672 |
// Add the edge to the raw graph
|