Repository: AIDotNet/GraphRag.Net
Branch: main
Commit: ca82ac4dbf6b
Files: 126
Total size: 681.4 KB
Directory structure:
gitextract_nfiuwx4k/
├── .github/
│ └── workflows/
│ └── nuget-publish.yml
├── .gitignore
├── Directory.Build.props
├── GraphRag.Net.sln
├── LICENSE
├── README.en.md
├── README.md
└── src/
├── GraphRag.Net/
│ ├── Common/
│ │ ├── Options/
│ │ │ ├── GraphDBConnectionOption.cs
│ │ │ ├── GraphOpenAIOption.cs
│ │ │ ├── GraphSearchOption.cs
│ │ │ ├── GraphSysOption.cs
│ │ │ └── TextChunkerOption.cs
│ │ └── ServiceDescriptionAttribute.cs
│ ├── Domain/
│ │ ├── Interface/
│ │ │ ├── ICommunityDetectionService.cs
│ │ │ ├── IGraphService.cs
│ │ │ └── ISemanticService.cs
│ │ ├── Model/
│ │ │ ├── Graph/
│ │ │ │ ├── Graph.cs
│ │ │ │ ├── GraphModel.cs
│ │ │ │ ├── GraphViewModel.cs
│ │ │ │ ├── RelationShipModel.cs
│ │ │ │ └── TextMemModel.cs
│ │ │ └── PageList.cs
│ │ └── Service/
│ │ ├── CommunityDetectionService.cs
│ │ ├── GraphService.cs
│ │ └── SemanticService.cs
│ ├── Extensions/
│ │ └── ServiceCollectionExtensions.cs
│ ├── GraphRag.Net.csproj
│ ├── Repositories/
│ │ ├── Base/
│ │ │ ├── IRepository.cs
│ │ │ ├── Repository.cs
│ │ │ └── SqlSugarHelper.cs
│ │ └── Graph/
│ │ ├── CommunitieNodes/
│ │ │ ├── CommunitieNodes.cs
│ │ │ ├── CommunitieNodes_Repositories.cs
│ │ │ └── ICommunitieNodes_Repositories.cs
│ │ ├── Communities/
│ │ │ ├── Communities.cs
│ │ │ ├── Communities_Repositories.cs
│ │ │ └── ICommunities_Repositories.cs
│ │ ├── Edges/
│ │ │ ├── Edges.cs
│ │ │ ├── Edges_Repositories.cs
│ │ │ └── IEdges_Repositories.cs
│ │ ├── Global/
│ │ │ ├── Globals.cs
│ │ │ ├── Globals_Repositories.cs
│ │ │ └── IGlobals_Repositories.cs
│ │ └── Nodes/
│ │ ├── INodes_Repositories.cs
│ │ ├── Nodes.cs
│ │ └── Nodes_Repositories.cs
│ └── Utils/
│ ├── ConvertUtils.cs
│ ├── OpenAIHttpClientHandler.cs
│ └── RepoUtils/
│ ├── AppException.cs
│ ├── ObjectExtensions.cs
│ └── RepoFiles.cs
└── GraphRag.Net.Web/
├── .config/
│ └── dotnet-tools.json
├── App.razor
├── Components/
│ └── GlobalHeader/
│ ├── RightContent.razor
│ └── RightContent.razor.cs
├── Controllers/
│ └── GraphController.cs
├── Extensions/
│ └── DateTimeExtension.cs
├── GraphRag.Net.Web.csproj
├── GraphRag.Net.Web.xml
├── Layouts/
│ ├── BasicLayout.razor
│ ├── UserLayout.razor
│ └── UserLayout.razor.css
├── Mock/
│ ├── MockChatCompletion.cs
│ ├── MockTextCompletion.cs
│ └── MockTextEmbeddingGeneratorService.cs
├── Models/
│ ├── ActivitiesType.cs
│ ├── ActivityGroup.cs
│ ├── ActivityProject.cs
│ ├── ActivityUser.cs
│ ├── AdvancedOperation.cs
│ ├── AdvancedProfileData.cs
│ ├── BasicGood.cs
│ ├── BasicProfileDataType.cs
│ ├── BasicProgress.cs
│ ├── ChartData.cs
│ ├── ChartDataItem.cs
│ ├── ChatMessage.cs
│ ├── CurrentUser.cs
│ ├── FormItemLayout.cs
│ ├── FormModel.cs
│ ├── LayoutModel.cs
│ ├── ListFormModel.cs
│ ├── ListItemDataType.cs
│ ├── LoginParamsType.cs
│ ├── NoticeItem.cs
│ ├── NoticeType.cs
│ ├── OfflineChartDataItem.cs
│ ├── OfflineDataItem.cs
│ ├── RadarDataItem.cs
│ └── SearchDataItem.cs
├── Pages/
│ ├── Graph/
│ │ ├── Chat.razor
│ │ ├── Chat.razor.cs
│ │ └── Graph.razor
│ ├── Index.razor
│ └── _Host.cshtml
├── Program.cs
├── Utils/
│ └── LongToDateTimeConverter.cs
├── _Imports.razor
├── appsettings.json
├── graphPlugins/
│ └── graph/
│ ├── community_search/
│ │ ├── config.json
│ │ └── skprompt.txt
│ ├── community_summaries/
│ │ ├── config.json
│ │ └── skprompt.txt
│ ├── create/
│ │ ├── config.json
│ │ └── skprompt.txt
│ ├── global_summaries/
│ │ ├── config.json
│ │ └── skprompt.txt
│ ├── mergedesc/
│ │ ├── config.json
│ │ └── skprompt.txt
│ ├── relationship/
│ │ ├── config.json
│ │ └── skprompt.txt
│ └── search/
│ ├── config.json
│ └── skprompt.txt
└── wwwroot/
├── appsettings.json
├── css/
│ └── site.css
├── data/
│ ├── activities.json
│ ├── advanced.json
│ ├── basic.json
│ ├── current_user.json
│ ├── fake_chart_data.json
│ ├── fake_list.json
│ ├── menu.json
│ ├── notice.json
│ └── notices.json
├── index.html
└── js/
├── relation-graph.umd.js
└── vue2.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/nuget-publish.yml
================================================
name: main
on:
push:
branches:
- main
paths:
- 'Directory.Build.props'
jobs:
deploy-nuget:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: dotnet build --configuration Release
- name: Pack
run: dotnet pack --no-build --configuration Release
- name: Push NuGet package
run: |
dotnet nuget push **/*.nupkg --skip-duplicate --source https://api.nuget.org/v3/index.json --api-key ${{ vars.XUZEYU }}
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.bin
*.Debug
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# MFractors (Xamarin productivity tool) working folder
.mfractor/
**/bin/
**/obj/
**/.vs/
/src/GraphRag.Net.Web/appsettings.Development.json
**/tmp-memory-files/
**/tmp-memory-vectors/
/src/GraphRag.Net.Web/graph.db
/src/GraphRag.Net.Web/graphmem.db
/db
/src/GraphRag.Net/GraphRag.Net.xml
================================================
FILE: Directory.Build.props
================================================
0.2.0
1.17.1
================================================
FILE: GraphRag.Net.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.35027.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphRag.Net.Web", "src\GraphRag.Net.Web\GraphRag.Net.Web.csproj", "{8C4B0AA1-7083-4BEA-9F12-2C20CDDB8426}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphRag.Net", "src\GraphRag.Net\GraphRag.Net.csproj", "{214FF6B8-E291-4CC8-94BC-16C1CD9DB3B8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "解决方案项", "解决方案项", "{A35F9835-DFCF-478A-9F24-E0B350161746}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
.github\workflows\nuget-publish.yml = .github\workflows\nuget-publish.yml
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8C4B0AA1-7083-4BEA-9F12-2C20CDDB8426}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C4B0AA1-7083-4BEA-9F12-2C20CDDB8426}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C4B0AA1-7083-4BEA-9F12-2C20CDDB8426}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C4B0AA1-7083-4BEA-9F12-2C20CDDB8426}.Release|Any CPU.Build.0 = Release|Any CPU
{214FF6B8-E291-4CC8-94BC-16C1CD9DB3B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{214FF6B8-E291-4CC8-94BC-16C1CD9DB3B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{214FF6B8-E291-4CC8-94BC-16C1CD9DB3B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{214FF6B8-E291-4CC8-94BC-16C1CD9DB3B8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7892D425-42B4-4F17-B17A-38825D88FA7B}
EndGlobalSection
EndGlobal
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2024] [许泽宇]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.en.md
================================================
[简体中文](./README.md) | English
## This is a simple implementation of dotnet referring to GraphRag
Based on the implementation idea mentioned by Microsoft in the paper, GraphRAG mainly implements the following functions in the execution process:
- Source Documents → Text Chunks: divide the source document into text blocks.
- Text Chunks → Element Instances: Extract instances of graph nodes and edges from each text block.
- Element Instances → Element Summaries: generate summaries for each diagram element.
- Element Summaries → Graph Communities: use the community detection algorithm to divide the graph into communities.
- Graph Communities → Community Summaries: generate summaries for each community.
- Community Summaries → Community Answers → Global Answer: use community summaries to generate local answers, and then summarize these local answers to generate global answers.
This project is a demo example, which is only used to learn GraphRAG ideas.
## Core Business Process
### 1. Overall Architecture Flow
```mermaid
graph LR
A["Document Import"] --> B["Graph Construction"]
B --> C["Community Detection"]
C --> D["Summary Generation"]
D --> E["Query Retrieval"]
subgraph "Data Flow"
F["Raw Documents"] --> G["Text Chunks"]
G --> H["Graph Data
Nodes+Edges"]
H --> I["Community Structure"]
I --> J["Multi-level Summaries"]
J --> K["Intelligent Q&A"]
end
subgraph "Storage Layer"
L["Vector Database
TextMemory"]
M["Relational Database
Nodes/Edges/Communities/Globals"]
end
B -.-> L
B -.-> M
C -.-> M
D -.-> M
E -.-> L
E -.-> M
style A fill:#e1f5fe
style E fill:#c8e6c9
style L fill:#fce4ec
style M fill:#fff9c4
```
### 2. Document Import and Graph Construction Flow
```mermaid
graph TD
A["Document Input
Raw Documents/Text"] --> B["Text Chunking
TextChunker.SplitPlainTextLines
TextChunker.SplitPlainTextParagraphs"]
B --> C["Overlapping Text Chunks
CreateOverlappingChunks
3 paragraphs/chunk, 1 paragraph overlap"]
C --> D["LLM Extract Graph Data
SemanticService.CreateGraphAsync"]
D --> E["Node Extraction
Entity Recognition+Type Classification"]
D --> F["Relationship Extraction
Edges and Relationship Description"]
E --> G["Node Deduplication
Vector Similarity Detection"]
F --> H["Relationship Deduplication
Duplicate Edge Processing"]
G --> I["Store to Database
Nodes Table"]
H --> J["Store to Database
Edges Table"]
I --> K["Vector Storage
TextMemory.SaveInformationAsync"]
J --> L["Orphan Node Detection
ProcessOrphanNodesAsync"]
L --> M["Graph Construction Complete"]
style A fill:#e1f5fe
style M fill:#c8e6c9
style D fill:#fff3e0
style K fill:#fce4ec
```
### 3. Community Detection and Summary Generation Flow
```mermaid
graph TD
A["Graph Data
Nodes + Edges"] --> B["Build Graph Structure
Graph.AddEdge"]
B --> C["Label Propagation Algorithm
FastLabelPropagationAlgorithm
10 iterations"]
C --> D["Community Detection Results
Node→Community ID Mapping"]
D --> E["Store Community Node Relations
CommunitieNodes Table"]
E --> F["Group Nodes by Community
Extract Node Description"]
F --> G["LLM Generate Community Summary
SemanticService.CommunitySummaries"]
G --> H["Store Community Summary
Communities Table"]
H --> I["Collect All Community Summaries"]
I --> J["LLM Generate Global Summary
SemanticService.GlobalSummaries"]
J --> K["Store Global Summary
Globals Table"]
style A fill:#e1f5fe
style C fill:#fff3e0
style G fill:#fff3e0
style J fill:#fff3e0
style K fill:#c8e6c9
```
### 4. Direct Graph Query Flow
```mermaid
graph TD
A["User Query
Question Input"] --> B["Vector Search
TextMemory.SearchAsync
Relevance Threshold 0.5"]
B --> C{"Nodes Matched?"}
C -->|Yes| D["Get Related Nodes
RetrieveTextMemModelList"]
C -->|No| E["Retry with Lower Threshold
Threshold 0.3, Expand Search"]
E --> D
D --> F["Recursive Graph Expansion
GetGraphAllRecursion
Depth Limit+Node Count Limit"]
F --> G["Token Count Estimation
EstimateTokenCount"]
G --> H{"Exceed Token Limit?"}
H -->|Yes| I["Trim Nodes by Weight
LimitGraphByTokenCount"]
H -->|No| J["Build Query Graph
GraphModel"]
I --> J
J --> K["LLM Generate Answer
SemanticService.GetGraphAnswerAsync"]
K --> L["Return Result"]
style A fill:#e1f5fe
style B fill:#fce4ec
style F fill:#fff3e0
style K fill:#fff3e0
style L fill:#c8e6c9
```
### 5. Community Algorithm Query Flow
```mermaid
graph TD
A["User Query
Question Input"] --> B["Vector Search
Find Related Nodes"]
B --> C{"Nodes Matched?"}
C -->|Yes| D["Find Node Communities
GetGraphAllCommunitiesRecursion"]
C -->|No| E["Use Global Summary
Globals Table"]
D --> F["Get All Nodes in Community"]
F --> G["Build Community Subgraph
Nodes+Edges"]
G --> H["Get Related Community Summary
Communities Table"]
H --> I["Get Global Summary
Globals Table"]
I --> J["LLM Comprehensive Analysis
Graph+Community Summary+Global Summary"]
E --> K["Answer Based on Global Summary Only"]
J --> L["Return Answer"]
K --> L
style A fill:#e1f5fe
style B fill:#fce4ec
style D fill:#fff3e0
style J fill:#fff3e0
style K fill:#fff3e0
style L fill:#c8e6c9
```
### Core Algorithm Description
1. **Text Chunking Algorithm**: Uses overlapping window technique, each text chunk contains 3 paragraphs, adjacent chunks overlap by 1 paragraph to ensure continuity of relationship information.
2. **Community Detection Algorithm**: Adopts Fast Label Propagation Algorithm with 10 iterations to discover community structure in the graph.
3. **Vector Search Strategy**: First uses 0.5 relevance threshold for search, if results are insufficient, lowers to 0.3 for retry to ensure finding enough related nodes.
4. **Token Optimization Mechanism**: Real-time estimation of token usage, intelligently trims nodes by weight when exceeding limits to ensure LLM input effectiveness.
5. **Orphan Node Processing**: Automatically detects orphan nodes without relationship connections, attempts to establish relationships with other nodes through semantic search.
## You can directly reference the NuGet package in the project, or directly use the project to provide API services
For convenience, the LLM interface is currently only compatible with the openai specification, and other large models can consider using one api class integration products
Configure in appsettings.json
```
"GraphOpenAI": {
"Key": "sk-xxx",
"EndPoint": "https://api.antsk.cn/",
"ChatModel": "gpt-4o-mini",
"EmbeddingModel": "text-embedding-ada-002"
},
"TextChunker": {
"LinesToken": 100,
"ParagraphsToken": 1000
},
"GraphDBConnection": {
"DbType": "Sqlite", //PostgreSQL
"DBConnection": "Data Source=graph.db",
"VectorConnection": "graphmem.db", //If PostgreSQL is used, it can be consistent with DBConnection
"VectorSize": 1536 //DbType=PostgreSQL needs to be set, sqlite can not be set
},
"GraphSearch": {
"SearchMinRelevance": 0.5, //Search for minimum relevance
"SearchLimit": 3, //Limit the number of vector search nodes
"NodeDepth": 3 ,//Retrieve node depth
"MaxNodes": 100 //Retrieve the maximum number of nodes
},
"GraphSys": {
"RetryCounnt": 2 //Number of retries. Using the domestic model may cause json extraction failure. Increasing the number of retries can improve availability
}
```
## Startup project
```
dotnet run --project GraphRag.Net.Web.csproj
```
## After starting the project, you can use the
```
http://localhost:5000/swagger
```
## Open the swagger view interface

### You can also use the interface
```
http://localhost:5000/
```
Open the UI interface of blazer. The page provides functions such as text import, file import, question and answer dialogue, and view knowledge map

## Nuget Package Usage
```
dotnet add package GraphRag.Net
```
## In order to facilitate the adjustment and modification of prompt words, SK Plugin has separated the project. You need to put GraphRag Copy the graphPlugins directory in the Net. Web project to your project, and set
[graphPlugins](https://github.com/AIDotNet/GraphRag.Net/tree/main/src/GraphRag.Net.Web/graphPlugins)
```
PreserveNewest
```
### The default configuration uses the OpenAI standard interface. After configuring the OpenAI app settings, you can use the following code to inject
After adding a package, you need to set the configuration file and dependency injection
```
//OpenAI configuration
builder.Configuration.GetSection("GraphOpenAI").Get();
//Document Slicing Configuration
builder.Configuration.GetSection("TextChunker").Get();
//Configure Database Links
builder.Configuration.GetSection("GraphDBConnection").Get();
//System settings
builder.Configuration.GetSection("GraphSys").Get();
//Inject AddGraphRagNet. Note that you need to inject the configuration file first, and then inject GraphRagNet
builder.Services.AddGraphRagNet();
```
### If you want to access other models, you can refer to the following code, which abstracts the implementation of Kernel. You can customize the implementation
```
var kernelBuild = Kernel.CreateBuilder();
kernelBuild.Services.AddKeyedSingleton("mock-text", new MockTextCompletion());
kernelBuild.Services.AddKeyedSingleton("mock-chat", new MockChatCompletion());
kernelBuild.Services.AddSingleton((ITextEmbeddingGenerationService)new MockTextEmbeddingGeneratorService());
kernelBuild.Services.AddKeyedSingleton("mock-embedding", new MockTextEmbeddingGeneratorService());
builder.Services.AddGraphRagNet(kernelBuild.Build());
```
#### It should be noted here that since the import may be carried out several times, the generated community and global information is not automatically called during import, so you need to call the generated community and global information according to the actual situation
```
await _graphService.GraphCommunitiesAsync(index);
await _graphService.GraphGlobalAsync(index);
```
Inject IGraphService service when using. The following is the reference sample code
```
namespace GraphRag.Net.Api.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class GraphDemoController(IGraphService _graphService) : ControllerBase
{
///
/// 获取所有的索引数据
///
///
[HttpGet]
public async Task GetAllIndex()
{
var graphModel = _graphService.GetAllIndex();
return Ok(graphModel);
}
///
/// 获取所有的图谱数据
///
///
///
[HttpGet]
public async Task GetAllGraphs(string index)
{
if (string.IsNullOrEmpty(index))
{
return Ok(new GraphViewModel());
}
var graphModel = _graphService.GetAllGraphs(index);
return Ok(graphModel);
}
///
/// 插入文本数据
///
///
///
[HttpPost]
public async Task InsertGraphData(InputModel model)
{
await _graphService.InsertGraphDataAsync(model.Index, model.Input);
return Ok();
}
///
/// 搜索递归获取节点相关的所有边和节点进行图谱对话
///
///
///
[HttpPost]
public async Task SearchGraph(InputModel model)
{
var result = await _graphService.SearchGraphAsync(model.Index, model.Input);
return Ok(result);
}
///
/// 通过社区算法检索社区节点进行对话
///
///
///
[HttpPost]
public async Task SearchGraphCommunity(InputModel model)
{
var result = await _graphService.SearchGraphCommunityAsync(model.Index, model.Input);
return Ok(result);
}
///
/// 导入txt文档
///
///
///
///
[HttpPost]
public async Task ImportTxt(string index,IFormFile file)
{
var forms = await Request.ReadFormAsync();
using (var stream = new StreamReader(file.OpenReadStream()))
{
var txt = await stream.ReadToEndAsync();
await _graphService.InsertTextChunkAsync(index,txt);
return Ok();
}
}
///
/// 通过社区检测生成社区和摘要
///
///
///
[HttpGet]
public async Task GraphCommunities(string index)
{
await _graphService.GraphCommunitiesAsync(index);
return Ok();
}
///
/// 通过社区摘要生成全局摘要
///
///
///
[HttpGet]
public async Task GraphGlobal(string index)
{
await _graphService.GraphGlobalAsync(index);
return Ok();
}
///
/// 删除图谱数据
///
///
///
[HttpGet]
public async Task DeleteGraph(string index)
{
await _graphService.DeleteGraph(index);
return Ok();
}
}
public class InputModel
{
public string Index { get; set; }
public string Input { get; set; }
}
}
```
## Test DB. Some community friends pre trained some data in advance. The link is as follows. After downloading, it can be directly put into the project directory for replacement to test the experience
```
https://pan.quark.cn/s/bf2d21f29f85
```
## See AntSK for more Rag scenarios
Project address:[AntSK](https://github.com/AIDotNet/AntSK)
Experience environment:
[Demo地址](https://demo.antsk.cn)
User:test
Pwd:test
You are also welcome to join our WeChat communication group. You can add my WeChat: **xuzeyu91** and send it to the group
================================================
FILE: README.md
================================================
简体中文 | [English](./README.en.md)
## 这是一个参考GraphRag的dotnet简易实现
基于微软在论文中提到的实现思路,执行过程GraphRAG主要实现了如下功能:
- Source Documents → Text Chunks:将源文档分割成文本块。
- Text Chunks → Element Instances:从每个文本块中提取图节点和边的实例。
- Element Instances → Element Summaries:为每个图元素生成摘要。
- Element Summaries → Graph Communities:使用社区检测算法将图划分为社区。
- Graph Communities → Community Summaries:为每个社区生成摘要。
- Community Summaries → Community Answers → Global Answer:使用社区摘要生成局部答案,然后汇总这些局部答案以生成全局答案。
本项目为demo示例,仅用于学习GraphRAG思路。
## 核心业务流程
### 1. 整体架构流程
```mermaid
graph LR
A["文档导入"] --> B["图谱构建"]
B --> C["社区检测"]
C --> D["摘要生成"]
D --> E["查询检索"]
subgraph "数据流转"
F["原始文档"] --> G["文本块"]
G --> H["图谱数据
节点+边"]
H --> I["社区结构"]
I --> J["多层摘要"]
J --> K["智能问答"]
end
subgraph "存储层"
L["向量数据库
TextMemory"]
M["关系数据库
Nodes/Edges/Communities/Globals"]
end
B -.-> L
B -.-> M
C -.-> M
D -.-> M
E -.-> L
E -.-> M
style A fill:#e1f5fe
style E fill:#c8e6c9
style L fill:#fce4ec
style M fill:#fff9c4
```
### 2. 文档导入与图谱构建流程
```mermaid
graph TD
A["文档输入
原始文档/文本"] --> B["文本切片
TextChunker.SplitPlainTextLines
TextChunker.SplitPlainTextParagraphs"]
B --> C["重叠文本块
CreateOverlappingChunks
3段落/块,1段落重叠"]
C --> D["LLM提取图数据
SemanticService.CreateGraphAsync"]
D --> E["节点提取
实体识别+类型分类"]
D --> F["关系提取
边和关系描述"]
E --> G["节点去重合并
向量相似度检测"]
F --> H["关系去重合并
重复边处理"]
G --> I["存储到数据库
Nodes表"]
H --> J["存储到数据库
Edges表"]
I --> K["向量化存储
TextMemory.SaveInformationAsync"]
J --> L["孤立节点检测
ProcessOrphanNodesAsync"]
L --> M["图谱构建完成"]
style A fill:#e1f5fe
style M fill:#c8e6c9
style D fill:#fff3e0
style K fill:#fce4ec
```
### 3. 社区检测与摘要生成流程
```mermaid
graph TD
A["图谱数据
Nodes + Edges"] --> B["构建图结构
Graph.AddEdge"]
B --> C["标签传播算法
FastLabelPropagationAlgorithm
10次迭代"]
C --> D["社区检测结果
节点→社区ID映射"]
D --> E["存储社区节点关系
CommunitieNodes表"]
E --> F["按社区分组节点
提取节点描述信息"]
F --> G["LLM生成社区摘要
SemanticService.CommunitySummaries"]
G --> H["存储社区摘要
Communities表"]
H --> I["收集所有社区摘要"]
I --> J["LLM生成全局摘要
SemanticService.GlobalSummaries"]
J --> K["存储全局摘要
Globals表"]
style A fill:#e1f5fe
style C fill:#fff3e0
style G fill:#fff3e0
style J fill:#fff3e0
style K fill:#c8e6c9
```
### 4. 直接图谱查询流程
```mermaid
graph TD
A["用户查询
问题输入"] --> B["向量搜索
TextMemory.SearchAsync
相关度阈值0.5"]
B --> C{"匹配到节点?"}
C -->|是| D["获取相关节点
RetrieveTextMemModelList"]
C -->|否| E["降低阈值重试
阈值0.3,扩大搜索"]
E --> D
D --> F["递归扩展图谱
GetGraphAllRecursion
深度限制+节点数限制"]
F --> G["Token数量估算
EstimateTokenCount"]
G --> H{"超过Token限制?"}
H -->|是| I["按权重裁剪节点
LimitGraphByTokenCount"]
H -->|否| J["构建查询图谱
GraphModel"]
I --> J
J --> K["LLM生成答案
SemanticService.GetGraphAnswerAsync"]
K --> L["返回结果"]
style A fill:#e1f5fe
style B fill:#fce4ec
style F fill:#fff3e0
style K fill:#fff3e0
style L fill:#c8e6c9
```
### 5. 社区算法查询流程
```mermaid
graph TD
A["用户查询
问题输入"] --> B["向量搜索
找到相关节点"]
B --> C{"匹配到节点?"}
C -->|是| D["查找节点所属社区
GetGraphAllCommunitiesRecursion"]
C -->|否| E["使用全局摘要
Globals表"]
D --> F["获取社区内所有节点"]
F --> G["构建社区子图
节点+边"]
G --> H["获取相关社区摘要
Communities表"]
H --> I["获取全局摘要
Globals表"]
I --> J["LLM综合分析
图谱+社区摘要+全局摘要"]
E --> K["仅基于全局摘要回答"]
J --> L["返回答案"]
K --> L
style A fill:#e1f5fe
style B fill:#fce4ec
style D fill:#fff3e0
style J fill:#fff3e0
style K fill:#fff3e0
style L fill:#c8e6c9
```
### 核心算法说明
1. **文本切片算法**:使用重叠窗口技术,每个文本块包含3个段落,相邻块之间重叠1个段落,确保关系信息的连续性。
2. **社区检测算法**:采用快速标签传播算法(Fast Label Propagation Algorithm),通过10次迭代来发现图中的社区结构。
3. **向量搜索策略**:首先使用0.5的相关度阈值进行搜索,如果结果不足则降低至0.3重试,确保找到足够的相关节点。
4. **Token优化机制**:实时估算token使用量,当超过限制时按节点权重进行智能裁剪,保证LLM输入的有效性。
5. **孤立节点处理**:自动检测没有关系连接的孤立节点,通过语义搜索尝试为其建立与其他节点的关系。
## 您可以直接在项目中引用NuGet包,或者直接使用本项目提供API服务
出于方便,LLM接口目前只兼容了openai的规范,其他大模型可以考虑使用one-api类的集成产品
在appsettings.json配置
```
"GraphOpenAI": {
"Key": "sk-xxx",
"EndPoint": "https://api.antsk.cn/",
"ChatModel": "gpt-4o-mini",
"EmbeddingModel": "text-embedding-ada-002"
},
"TextChunker": {
"LinesToken": 100,
"ParagraphsToken": 1000
},
"GraphDBConnection": {
"DbType": "Sqlite", //PostgreSQL
"DBConnection": "Data Source=graph.db",
"VectorConnection": "graphmem.db", //如果用PostgreSQL,可以和DBConnection一致
"VectorSize": 1536 //DbType=PostgreSQL时需要设置,sqlite可以不设置
},
"GraphSearch": {
"SearchMinRelevance": 0.5, //搜索最小相关性
"SearchLimit": 3, //向量搜索节点限制个数
"NodeDepth": 3 ,//检索节点深度
"MaxNodes": 100 //检索最大节点数
},
"GraphSys": {
"RetryCounnt": 2 //重试次数,使用国产模型可能会出现json提取失败,增加重试次数可提高可用性
}
```
## 启动项目
```
dotnet run --project GraphRag.Net.Web.csproj
```
## 启动项目后可以通过
```
http://localhost:5000/swagger
```
## 打开swagger查看接口

### 也可以使用界面
```
http://localhost:5000/
```
打开blazor的UI界面,页面提供了文本导入、文件导入,和问答对话,查看知识图谱等功能

## Nuget包使用
```
dotnet add package GraphRag.Net
```
## 为了方便进行提示词调整与修改,SK Plugin我们剥离出了项目,您需要把GraphRag.Net.Web项目中的 graphPlugins目录拷贝到你的项目中,并设置:
[graphPlugins](https://github.com/AIDotNet/GraphRag.Net/tree/main/src/GraphRag.Net.Web/graphPlugins)
```
PreserveNewest
```
### 默认配置,使用OpenAI标准接口,在配置了OpenAI的appsettings后可以使用下面代码进行注入
添加包以后,需要进行配置文件的设置以及依赖注入
```
//OpenAI配置
builder.Configuration.GetSection("GraphOpenAI").Get();
//文档切片配置
builder.Configuration.GetSection("TextChunker").Get();
//配置数据库链接
builder.Configuration.GetSection("GraphDBConnection").Get();
//系统设置
builder.Configuration.GetSection("GraphSys").Get();
//注入AddGraphRagNet,注意,需要先注入配置文件,然后再注入GraphRagNet
builder.Services.AddGraphRagNet();
```
### 如果你想接入其他模型,可以参考以下代码,这里抽象了Kernel的实现,你可以自定义实现
```
var kernelBuild = Kernel.CreateBuilder();
kernelBuild.Services.AddKeyedSingleton("mock-text", new MockTextCompletion());
kernelBuild.Services.AddKeyedSingleton("mock-chat", new MockChatCompletion());
kernelBuild.Services.AddSingleton((ITextEmbeddingGenerationService)new MockTextEmbeddingGeneratorService());
kernelBuild.Services.AddKeyedSingleton("mock-embedding", new MockTextEmbeddingGeneratorService());
builder.Services.AddGraphRagNet(kernelBuild.Build());
```
#### 此处需要注意,由于导入可能分多次导入,没有在导入时自动调用生成社区和全局信息,需要自己根据实际情况调用生成社区和全局信息
```
await _graphService.GraphCommunitiesAsync(index);
await _graphService.GraphGlobalAsync(index);
```
使用时注入 IGraphService 服务,以下为参考示例代码
```
namespace GraphRag.Net.Api.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class GraphController(IGraphService _graphService) : ControllerBase
{
///
/// 获取所有的索引数据
///
///
[HttpGet]
public async Task GetAllIndex()
{
var graphModel = _graphService.GetAllIndex();
return Ok(graphModel);
}
///
/// 获取所有的图谱数据
///
///
///
[HttpGet]
public async Task GetAllGraphs(string index)
{
if (string.IsNullOrEmpty(index))
{
return Ok(new GraphViewModel());
}
var graphModel = _graphService.GetAllGraphs(index);
return Ok(graphModel);
}
///
/// 插入文本数据
///
///
///
[HttpPost]
public async Task InsertGraphData(InputModel model)
{
await _graphService.InsertGraphDataAsync(model.Index, model.Input);
return Ok();
}
///
/// 搜索递归获取节点相关的所有边和节点进行图谱对话
///
///
///
[HttpPost]
public async Task SearchGraph(InputModel model)
{
var result = await _graphService.SearchGraphAsync(model.Index, model.Input);
return Ok(result);
}
///
/// 通过社区算法检索社区节点进行对话
///
///
///
[HttpPost]
public async Task SearchGraphCommunity(InputModel model)
{
var result = await _graphService.SearchGraphCommunityAsync(model.Index, model.Input);
return Ok(result);
}
///
/// 导入txt文档
///
///
///
///
[HttpPost]
public async Task ImportTxt(string index,IFormFile file)
{
var forms = await Request.ReadFormAsync();
using (var stream = new StreamReader(file.OpenReadStream()))
{
var txt = await stream.ReadToEndAsync();
await _graphService.InsertTextChunkAsync(index,txt);
return Ok();
}
}
///
/// 通过社区检测生成社区和摘要
///
///
///
[HttpGet]
public async Task GraphCommunities(string index)
{
await _graphService.GraphCommunitiesAsync(index);
return Ok();
}
///
/// 通过社区摘要生成全局摘要
///
///
///
[HttpGet]
public async Task GraphGlobal(string index)
{
await _graphService.GraphGlobalAsync(index);
return Ok();
}
///
/// 删除图谱数据
///
///
///
[HttpGet]
public async Task DeleteGraph(string index)
{
await _graphService.DeleteGraph(index);
return Ok();
}
}
public class InputModel
{
public string Index { get; set; }
public string Input { get; set; }
}
}
```
## 测试DB,有社区朋友提前预训练了一些数据,链接如下,下载后直接放进项目目录替换即可测试体验
```
https://pan.quark.cn/s/bf2d21f29f85
```
## 更多Rag场景可查看 AntSK
项目地址:[AntSK](https://github.com/AIDotNet/AntSK)
体验环境:
[Demo地址](https://demo.antsk.cn)
账号:test
密码:test
================================================
FILE: src/GraphRag.Net/Common/Options/GraphDBConnectionOption.cs
================================================
namespace GraphRag.Net.Options
{
public class GraphDBConnectionOption
{
///
/// sqlite连接字符串
///
public static string DbType { get; set; } = "Sqlite";
///
/// 业务数据链接字符串
///
public static string DBConnection { get; set; } = $"Data Source=graph.db";
///
/// 向量数据连接字符串
///
public static string VectorConnection { get; set; } = "graphmem.db";
///
/// 向量数据维度,PG需要设置
///
public static int VectorSize { get; set; } = 1536;
}
}
================================================
FILE: src/GraphRag.Net/Common/Options/GraphOpenAIOption.cs
================================================
namespace GraphRag.Net.Options
{
public class GraphOpenAIOption
{
public static string EndPoint { get; set; }
public static string Key { get; set; }
public static string ChatModel { get; set; }
public static string EmbeddingModel { get; set; }
}
}
================================================
FILE: src/GraphRag.Net/Common/Options/GraphSearchOption.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GraphRag.Net.Options
{
public class GraphSearchOption
{
///
/// 社区检索搜索最小向量相似度
///
public static double SearchMinRelevance { get; set; } = 0.5;
///
/// 社区检索搜索向量节点数量
///
public static int SearchLimit { get; set; } = 3;
///
/// 节点关系检索深度
///
public static int NodeDepth { get; set; } = 3;
///
/// 节点检索最多节点数量
///
public static int MaxNodes { get; set; } = 300;
///
/// 最大Token数量限制(32K)
///
public static int MaxTokens { get; set; } = 32000;
}
}
================================================
FILE: src/GraphRag.Net/Common/Options/GraphSysOption.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GraphRag.Net.Common.Options
{
public class GraphSysOption
{
///
/// 重试次数
///
public static int RetryCounnt { get; set; } = 2;
}
}
================================================
FILE: src/GraphRag.Net/Common/Options/TextChunkerOption.cs
================================================
namespace GraphRag.Net.Options
{
public class TextChunkerOption
{
///
/// 行切片token
///
public static int LinesToken { get; set; } = 100;
///
/// 段落切片token
///
public static int ParagraphsToken { get; set; } = 1000;
}
}
================================================
FILE: src/GraphRag.Net/Common/ServiceDescriptionAttribute.cs
================================================
using Microsoft.Extensions.DependencyInjection;
namespace GraphRag.Net
{
public class ServiceDescriptionAttribute : Attribute
{
public ServiceDescriptionAttribute(Type serviceType, ServiceLifetime lifetime)
{
ServiceType = serviceType;
Lifetime = lifetime;
}
public Type ServiceType { get; set; }
public ServiceLifetime Lifetime { get; set; }
}
}
================================================
FILE: src/GraphRag.Net/Domain/Interface/ICommunityDetectionService.cs
================================================
using GraphRag.Net.Domain.Model.Graph;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GraphRag.Net.Domain.Interface
{
public interface ICommunityDetectionService
{
Dictionary FastLabelPropagationAlgorithm(Graph graph, int iterations = 10);
}
}
================================================
FILE: src/GraphRag.Net/Domain/Interface/IGraphService.cs
================================================
using GraphRag.Net.Domain.Model.Graph;
using Microsoft.SemanticKernel;
namespace GraphRag.Net.Domain.Interface
{
public interface IGraphService
{
///
/// 获取所有索引信息
///
///
List GetAllIndex();
///
/// 获取Graph数据
///
///
///
GraphViewModel GetAllGraphs(string index);
///
/// 切片导入文本数据
///
///
///
///
Task InsertTextChunkAsync(string index, string input);
///
/// 生成图谱数据
///
///
///
///
Task InsertGraphDataAsync(string index, string input);
///
/// 搜索递归获取节点相关的所有边和节点
///
///
///
///
Task SearchGraphModel(string index, string input);
///
/// 搜索递归获取节点相关的所有边和节点进行图谱对话
///
///
///
///
Task SearchGraphAsync(string index, string input);
///
/// 通过社区算法匹配相关节点信息
///
///
///
///
Task SearchGraphCommunityModel(string index, string input);
///
/// 搜索递归获取节点相关的所有边和节点进行图谱对话,流式返回
///
///
///
///
IAsyncEnumerable SearchGraphStreamAsync(string index, string input);
///
/// 通过社区算法检索社区节点进行对话
///
///
///
///
Task SearchGraphCommunityAsync(string index, string input);
///
/// 通过社区算法检索社区节点进行对话,流式返回
///
///
///
///
IAsyncEnumerable SearchGraphCommunityStreamAsync(string index, string input);
///
/// 社区摘要
///
///
///
Task GraphCommunitiesAsync(string index);
///
/// 全局摘要
///
///
///
Task GraphGlobalAsync(string index);
///
/// 删除图谱数据
///
///
///
Task DeleteGraph(string index);
}
}
================================================
FILE: src/GraphRag.Net/Domain/Interface/ISemanticService.cs
================================================
using GraphRag.Net.Domain.Model.Graph;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Memory;
namespace GraphRag.Net.Domain.Interface
{
public interface ISemanticService
{
Task CreateGraphAsync(string input);
Task GetGraphAnswerAsync(string graph, string input);
IAsyncEnumerable GetGraphAnswerStreamAsync(string graph, string input);
Task GetGraphCommunityAnswerAsync(string graph, string community, string global, string input);
IAsyncEnumerable GetGraphCommunityAnswerStreamAsync(string graph, string community, string global, string input);
Task GetRelationship(string node1, string node2);
Task MergeDesc(string desc1, string desc2);
Task CommunitySummaries(string nodes);
Task GlobalSummaries(string community);
Task GetTextMemory();
}
}
================================================
FILE: src/GraphRag.Net/Domain/Model/Graph/Graph.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GraphRag.Net.Domain.Model.Graph
{
public class Graph
{
public Dictionary> AdjacencyList { get; }
public Graph()
{
AdjacencyList = new Dictionary>();
}
public void AddEdge(string node1, string node2)
{
if (!AdjacencyList.ContainsKey(node1))
{
AdjacencyList[node1] = new List();
}
if (!AdjacencyList.ContainsKey(node2))
{
AdjacencyList[node2] = new List();
}
AdjacencyList[node1].Add(node2);
AdjacencyList[node2].Add(node1);
}
}
}
================================================
FILE: src/GraphRag.Net/Domain/Model/Graph/GraphModel.cs
================================================
using GraphRag.Net.Repositories;
namespace GraphRag.Net.Domain.Model.Graph
{
public class GraphModel
{
public List Nodes { get; set; }
public List Edges { get; set; }
}
}
================================================
FILE: src/GraphRag.Net/Domain/Model/Graph/GraphViewModel.cs
================================================
namespace GraphRag.Net.Domain.Model.Graph
{
public class GraphViewModel
{
public string rootId { get; set; }
public List nodes { get; set; } = new List();
public List lines { get; set; } = new List();
}
public class NodesViewModel
{
public string id { get; set; }
public string text { get; set; }
public string color { get; set; } = "#43a2f1";
public NodesDataModel data { get; set; }=new NodesDataModel();
}
public class NodesDataModel
{
public string desc { get; set; }
}
public class LinesViewModel
{
public string from { get; set; }
public string to { get; set; }
public string text { get; set; }
public string color { get; set; } = "#000";
}
}
================================================
FILE: src/GraphRag.Net/Domain/Model/Graph/RelationShipModel.cs
================================================
using GraphRag.Net.Repositories;
namespace GraphRag.Net.Domain.Model.Graph
{
public class RelationShipModel
{
public bool IsRelationship { get; set; }
public Edges Edge { get; set; }
}
}
================================================
FILE: src/GraphRag.Net/Domain/Model/Graph/TextMemModel.cs
================================================
namespace GraphRag.Net.Domain.Model.Graph
{
public class TextMemModel
{
public string Id { get; set; }
public string Text { get; set; }
public double Relevance { get; set; }
}
}
================================================
FILE: src/GraphRag.Net/Domain/Model/PageList.cs
================================================
namespace GraphRag.Net.Model
{
public class PageList
{
//查询结果
public List List { get; set; }
///
/// 当前页,从1开始
///
public int PageIndex { get; set; }
///
/// 每页数量
///
public int PageSize { get; set; }
///
/// 总数
///
public int TotalCount { get; set; }
}
}
================================================
FILE: src/GraphRag.Net/Domain/Service/CommunityDetectionService.cs
================================================
using GraphRag.Net.Domain.Interface;
using GraphRag.Net.Domain.Model.Graph;
using Microsoft.Extensions.DependencyInjection;
namespace GraphRag.Net.Domain.Service
{
[ServiceDescription(typeof(ICommunityDetectionService), ServiceLifetime.Scoped)]
public class CommunityDetectionService : ICommunityDetectionService
{
///
/// 标签传播算法
///
///
///
///
public Dictionary FastLabelPropagationAlgorithm(Graph graph, int iterations = 10)
{
// Initialize labels
var labels = graph.AdjacencyList.Keys.ToDictionary(node => node, node => node);
for (int iter = 0; iter < iterations; iter++)
{
// Shuffle nodes
var nodes = graph.AdjacencyList.Keys.OrderBy(a => Guid.NewGuid()).ToList();
foreach (var node in nodes)
{
// Count neighbor labels
var labelCounts = new Dictionary();
foreach (var neighbor in graph.AdjacencyList[node])
{
if (!labelCounts.ContainsKey(labels[neighbor]))
{
labelCounts[labels[neighbor]] = 0;
}
labelCounts[labels[neighbor]]++;
}
// Find the label of highest frequency
var maxCount = labelCounts.Values.Max();
var bestLabels = labelCounts.Where(x => x.Value == maxCount).Select(x => x.Key).ToList();
// Pick a random label among the smallest lexicographically in case of tie
var newLabel = bestLabels.OrderBy(x => x).First();
if (labels[node] != newLabel)
{
labels[node] = newLabel;
}
}
}
return labels;
}
}
}
================================================
FILE: src/GraphRag.Net/Domain/Service/GraphService.cs
================================================
using GraphRag.Net.Domain.Interface;
using GraphRag.Net.Domain.Model.Graph;
using GraphRag.Net.Options;
using GraphRag.Net.Repositories;
using GraphRag.Net.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.Text;
using Newtonsoft.Json;
using SqlSugar;
namespace GraphRag.Net.Domain.Service
{
[ServiceDescription(typeof(IGraphService), ServiceLifetime.Scoped)]
public class GraphService(
INodes_Repositories _nodes_Repositories,
IEdges_Repositories _edges_Repositories,
ISemanticService _semanticService,
ICommunities_Repositories _communities_Repositories,
ICommunitieNodes_Repositories _communitieNodes_Repositories,
IGlobals_Repositories _globals_Repositories,
ICommunityDetectionService _communityDetectionService
) : IGraphService
{
///
/// 获取所有索引信息
///
///
public List GetAllIndex()
{
var indexs = _nodes_Repositories.GetDB().Queryable().GroupBy(p => p.Index).Select(p => p.Index).ToList();
return indexs;
}
///
/// 获取Graph数据
///
///
public GraphViewModel GetAllGraphs(string index)
{
if (string.IsNullOrWhiteSpace(index))
{
throw new ArgumentException("Index required value cannot be null.");
}
GraphViewModel graphViewModel = new GraphViewModel();
var nodes = _nodes_Repositories.GetList(p => p.Index == index);
var edges = _edges_Repositories.GetList(p => p.Index == index);
Dictionary TypeColor = new Dictionary();
Random random = new Random();
foreach (var n in nodes)
{
NodesViewModel nodesViewModel = new NodesViewModel()
{
id = n.Id,
text = n.Name,
data = new NodesDataModel()
{
desc = n.Desc.ConvertToString()
}
};
//处理相同的Type用相同的颜色
if (TypeColor.ContainsKey(n.Type))
{
nodesViewModel.color = TypeColor[n.Type];
}
else
{
nodesViewModel.color = $"#{random.Next(0x1000000):X6}";
TypeColor.Add(n.Type, nodesViewModel.color);
}
graphViewModel.nodes.Add(nodesViewModel);
}
foreach (var e in edges)
{
LinesViewModel linesViewModel = new LinesViewModel()
{
from = e.Source,
to = e.Target,
text = e.Relationship
};
graphViewModel.lines.Add(linesViewModel);
}
return graphViewModel;
}
///
/// 切片导入文本数据
///
///
///
///
public async Task InsertTextChunkAsync(string index, string input)
{
if (string.IsNullOrWhiteSpace(index) || string.IsNullOrWhiteSpace(input))
{
throw new ArgumentException("Values required for index and input cannot be null.");
}
var lines = TextChunker.SplitPlainTextLines(input, TextChunkerOption.LinesToken);
var paragraphs = TextChunker.SplitPlainTextParagraphs(lines, TextChunkerOption.ParagraphsToken);
// 优化文本分块:使用重叠窗口来保持关系信息
var optimizedChunks = CreateOverlappingChunks(paragraphs);
foreach (var chunk in optimizedChunks)
{
await InsertGraphDataAsync(index, chunk);
}
}
///
/// 创建重叠文本块以保持关系信息
///
///
///
private List CreateOverlappingChunks(List paragraphs)
{
var chunks = new List();
const int maxChunkSize = 3; // 每个块最多包含3个段落
const int overlapSize = 1; // 重叠1个段落
if (paragraphs.Count <= maxChunkSize)
{
// 如果段落数量不多,直接作为一个块
chunks.Add(string.Join("\n\n", paragraphs));
}
else
{
// 创建重叠的文本块
for (int i = 0; i < paragraphs.Count; i += (maxChunkSize - overlapSize))
{
var chunkParagraphs = paragraphs
.Skip(i)
.Take(maxChunkSize)
.ToList();
if (chunkParagraphs.Count > 0)
{
var chunk = string.Join("\n\n", chunkParagraphs);
// 避免重复的块
if (!chunks.Contains(chunk))
{
chunks.Add(chunk);
}
}
// 如果剩余段落不足一个完整块,退出循环
if (i + maxChunkSize >= paragraphs.Count)
{
break;
}
}
}
Console.WriteLine($"原始段落数: {paragraphs.Count}, 优化后块数: {chunks.Count}");
return chunks;
}
///
/// 生成图谱数据
///
///
///
///
public async Task InsertGraphDataAsync(string index, string input)
{
if (string.IsNullOrWhiteSpace(index) || string.IsNullOrWhiteSpace(input))
{
throw new ArgumentException("Values required for index and input cannot be null.");
}
try
{
SemanticTextMemory textMemory = await _semanticService.GetTextMemory();
var graph = await _semanticService.CreateGraphAsync(input);
Dictionary nodeDic = new Dictionary();
List newNodes = new List(); // 收集新插入的节点
foreach (var n in graph.Nodes)
{
string Id = Guid.NewGuid().ToString();
string text2 = $"Name:{n.Name};Type:{n.Type};Desc:{n.Desc}";
bool isContinue = false;
//判断是否存在相同节点
var oldNode = _nodes_Repositories.GetFirst(p => p.Index == index && p.Name == n.Name);
if (oldNode.IsNotNull() && !string.IsNullOrWhiteSpace(n.Desc))
{
//相同节点关联edge关系
var newDesc = await _semanticService.MergeDesc(oldNode.Desc.ConvertToString(), n.Desc.ConvertToString());
if (string.IsNullOrEmpty(newDesc))
{
//可能触发了LLM规则限制,简单粗暴来拼接吧
oldNode.Desc = oldNode.Desc.ConvertToString() + "; " + n.Desc.ConvertToString();
}
else
{
oldNode.Desc = newDesc;
}
//更新描述
_nodes_Repositories.Update(oldNode);
text2 = $"Name:{oldNode.Name};Type:{oldNode.Type};Desc:{oldNode.Desc}";
nodeDic.Add(n.Id, oldNode.Id);
await textMemory.SaveInformationAsync(index, id: oldNode.Id, text: text2, cancellationToken: default);
continue;
}
//优化相关节点发现:增加搜索数量和降低阈值
List potentialRelatedNodes = new List();
await foreach (MemoryQueryResult memory in textMemory.SearchAsync(index, text2, limit: 5, minRelevanceScore: 0.7))
{
if (memory.Relevance == 1)
{
//相同节点进行合并
Console.WriteLine("节点合并");
nodeDic.Add(n.Id, memory.Metadata.Id);
isContinue = true;
break;
}
if (graph.Nodes.Select(p => p.Id).Contains(memory.Metadata.Id))
{
//如果本次包含了向量近似的数据,则跳过
continue;
}
potentialRelatedNodes.Add(memory.Metadata.Id);
}
if (isContinue)
{
//节点合并,跳出循环
continue;
}
// 创建新节点
Nodes node = new Nodes()
{
Id = Id,
Index = index,
Name = n.Name,
Type = n.Type,
Desc = n.Desc.ConvertToString()
};
if (!nodeDic.ContainsKey(n.Id))
{
nodeDic.Add(n.Id, node.Id);
}
_nodes_Repositories.Insert(node);
newNodes.Add(node);
// 检查与潜在相关节点的关系
foreach (var relatedNodeId in potentialRelatedNodes)
{
var node1 = _nodes_Repositories.GetFirst(p => p.Id == relatedNodeId);
if (node1 != null)
{
string text1 = $"Name:{node1.Name};Type:{node1.Type};Desc:{node1.Desc}";
var relationShip = await _semanticService.GetRelationship(text1, text2);
if (relationShip.IsRelationship)
{
if (relationShip.Edge.Source == "node1")
{
relationShip.Edge.Source = node1.Id;
relationShip.Edge.Target = Id;
}
else
{
relationShip.Edge.Source = Id;
relationShip.Edge.Target = node1.Id;
}
if (!_edges_Repositories.IsAny(p => p.Target == relationShip.Edge.Target && p.Source == relationShip.Edge.Source))
{
relationShip.Edge.Id = Guid.NewGuid().ToString();
relationShip.Edge.Index = index;
_edges_Repositories.Insert(relationShip.Edge);
}
}
}
}
//向量处理节点信息
await textMemory.SaveInformationAsync(index, id: node.Id, text: text2, cancellationToken: default);
}
foreach (var e in graph.Edges)
{
Edges edge = new Edges()
{
Id = Guid.NewGuid().ToString(),
Index = index,
Source = nodeDic[e.Source],
Target = nodeDic[e.Target],
Relationship = e.Relationship
};
_edges_Repositories.Insert(edge);
}
// 检测和处理孤立节点
await ProcessOrphanNodesAsync(index, newNodes, textMemory);
//查询Edges 的Source和Target 重复数据
var repeatEdges = _edges_Repositories.GetDB().Queryable()
.GroupBy(p => new { p.Source, p.Target })
.Select(p => new { p.Source, p.Target, Count = SqlFunc.AggregateCount(p.Source) })
.ToList().Where(p => p.Count > 1).ToList();
//合并查询Edges 的Source和Target 重复数据
foreach (var edge in repeatEdges)
{
var edges = _edges_Repositories.GetList(p => p.Source == edge.Source && p.Target == edge.Target);
var firstEdge = edges.First();
for (int i = 1; i < edges.Count(); i++)
{
if (firstEdge.Relationship == edges[i].Relationship)
{
//相同的边进行合并
_edges_Repositories.Delete(edges[i]);
continue;
}
var newDesc = await _semanticService.MergeDesc(firstEdge.Relationship, edges[i].Relationship);
firstEdge.Relationship = newDesc;
_edges_Repositories.Update(firstEdge);
_edges_Repositories.Delete(edges[i]);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"插入数据失败:{ex.ToString()}");
}
}
///
/// 检索相关节点
///
///
///
///
///
public async Task SearchGraphModel(string index, string input)
{
if (string.IsNullOrWhiteSpace(index) || string.IsNullOrWhiteSpace(input))
{
throw new ArgumentException("Values required for index and input cannot be null.");
}
var textMemModelList = await RetrieveTextMemModelList(index, input);
if (textMemModelList.Any())
{
var nodes = _nodes_Repositories.GetList(p => p.Index == index && textMemModelList.Select(c => c.Id).Contains(p.Id));
// 创建节点权重字典
Dictionary nodeWeights = textMemModelList.ToDictionary(
t => t.Id,
t => t.Relevance
);
var graphModel = GetGraphAllRecursion(index, nodes, nodeWeights);
// 计算预估的token数量,并在必要时限制节点数量
int estimatedTokens = EstimateTokenCount(graphModel);
if (estimatedTokens > GraphSearchOption.MaxTokens)
{
Console.WriteLine($"预估Token数量 {estimatedTokens} 超过限制 {GraphSearchOption.MaxTokens},正在调整节点数量...");
graphModel = LimitGraphByTokenCount(graphModel, nodeWeights);
}
return graphModel;
}
else
{
return new GraphModel();
}
}
///
/// 估算图模型的token数量
///
/// 图模型
/// 估算的token数量
private int EstimateTokenCount(GraphModel model)
{
int tokenCount = 0;
// 估算节点的token(每个单词约1.3个token,每个节点信息加上额外开销)
foreach (var node in model.Nodes)
{
// 节点ID和名称估算
tokenCount += (node.Id?.Length ?? 0) / 3 + 2;
tokenCount += (node.Name?.Length ?? 0) / 3 + 2;
// 节点描述估算(一个汉字约等于1个token,英文单词约等于0.75个token)
string desc = node.Desc ?? "";
int chineseCount = desc.Count(c => c >= 0x4E00 && c <= 0x9FFF);
int otherCount = desc.Length - chineseCount;
tokenCount += chineseCount + (int)(otherCount * 0.75);
// 节点额外属性估算
tokenCount += 10; // 额外结构开销
}
// 估算边的token
tokenCount += model.Edges.Count * 10; // 每个边的结构信息约10个token
// JSON结构开销
tokenCount += 200;
return tokenCount;
}
///
/// 根据token数量限制图大小
///
/// 原始图模型
/// 节点权重
/// 裁剪后的图模型
private GraphModel LimitGraphByTokenCount(GraphModel model, Dictionary nodeWeights)
{
var result = new GraphModel();
// 将节点按权重排序
var sortedNodes = model.Nodes
.OrderByDescending(n => nodeWeights.GetValueOrDefault(n.Id, 0))
.ToList();
// 从高权重节点开始添加,直到接近token限制
var selectedNodes = new List();
int currentTokens = 200; // 基础结构开销
foreach (var node in sortedNodes)
{
// 计算添加此节点后的token数
string desc = node.Desc ?? "";
int chineseCount = desc.Count(c => c >= 0x4E00 && c <= 0x9FFF);
int otherCount = desc.Length - chineseCount;
int nodeTokens = chineseCount + (int)(otherCount * 0.75) +
(node.Id?.Length ?? 0) / 3 +
(node.Name?.Length ?? 0) / 3 + 15;
// 如果添加此节点会超过限制,跳过
if (currentTokens + nodeTokens > GraphSearchOption.MaxTokens * 0.9)
{
continue;
}
selectedNodes.Add(node);
currentTokens += nodeTokens;
}
// 只保留连接选中节点的边
var selectedEdges = model.Edges.Where(e =>
selectedNodes.Any(n => n.Id == e.Source) &&
selectedNodes.Any(n => n.Id == e.Target)
).ToList();
result.Nodes = selectedNodes;
result.Edges = selectedEdges;
Console.WriteLine($"节点限制调整:从 {model.Nodes.Count} 个节点减少到 {result.Nodes.Count} 个节点");
Console.WriteLine($"预估调整后Token数:约 {currentTokens}");
return result;
}
///
/// 通过社区算法匹配相关节点信息
///
///
///
///
///
public async Task SearchGraphCommunityModel(string index, string input)
{
if (string.IsNullOrWhiteSpace(index) || string.IsNullOrWhiteSpace(input))
{
throw new ArgumentException("Values required for index and input cannot be null.");
}
var textMemModelList = await RetrieveTextMemModelList(index, input);
if (!textMemModelList.Any())
{
// 尝试降低阈值重新检索
textMemModelList = await RetrieveTextMemModelList(index, input, 0.3, 5);
}
if (textMemModelList.Any())
{
var nodes = _nodes_Repositories.GetList(p => p.Index == index && textMemModelList.Select(c => c.Id).Contains(p.Id));
var graphModel = GetGraphAllCommunitiesRecursion(index, nodes);
// 计算预估的token数量,并在必要时限制节点数量
int estimatedTokens = EstimateTokenCount(graphModel);
if (estimatedTokens > GraphSearchOption.MaxTokens)
{
Console.WriteLine($"社区检索:预估Token数量 {estimatedTokens} 超过限制 {GraphSearchOption.MaxTokens},正在调整节点数量...");
// 为社区节点创建权重字典(默认权重相同)
Dictionary nodeWeights = graphModel.Nodes.ToDictionary(
n => n.Id,
n => 1.0
);
// 为初始检索到的节点赋予更高权重
foreach (var node in nodes)
{
if (nodeWeights.ContainsKey(node.Id))
{
nodeWeights[node.Id] = 2.0; // 给初始节点更高权重
}
}
graphModel = LimitGraphByTokenCount(graphModel, nodeWeights);
}
return graphModel;
}
else
{
return new GraphModel();
}
}
///
/// 搜索递归获取节点相关的所有边和节点进行图谱对话
///
///
///
///
public async Task SearchGraphAsync(string index, string input)
{
var graphModel = await SearchGraphModel(index, input);
string answer = await _semanticService.GetGraphAnswerAsync(JsonConvert.SerializeObject(graphModel), input);
return answer;
}
///
/// 搜索递归获取节点相关的所有边和节点进行图谱对话,流式返回
///
///
///
///
public async IAsyncEnumerable SearchGraphStreamAsync(string index, string input)
{
var graphModel = await SearchGraphModel(index, input);
if (graphModel.Nodes.Count() > 0)
{
var answerStream = _semanticService.GetGraphAnswerStreamAsync(JsonConvert.SerializeObject(graphModel), input);
await foreach (var content in answerStream)
{
yield return content;
}
}
}
///
/// 通过社区算法检索社区节点进行对话
///
///
///
///
public async Task SearchGraphCommunityAsync(string index, string input)
{
string answer = "";
var graphModel = await SearchGraphCommunityModel(index, input);
var global = _globals_Repositories.GetFirst(p => p.Index == index)?.Summaries;
if (graphModel.Nodes.Count() > 0)
{
var community = string.Join(Environment.NewLine, _communities_Repositories.GetDB().Queryable().Where(p => p.Index == index).Select(p => p.Summaries).ToList());
//这里数据有点多,要通过语义进行一次过滤
answer = await _semanticService.GetGraphCommunityAnswerAsync(JsonConvert.SerializeObject(graphModel), community, global, input);
}
else
{
//如果没有匹配到节点信息,使用全局信息
answer = await _semanticService.GetGraphCommunityAnswerAsync("NoSearch", "NoSearch", global, input);
}
return answer;
}
///
/// 通过社区算法检索社区节点进行对话,流式返回
///
///
///
///
public async IAsyncEnumerable SearchGraphCommunityStreamAsync(string index, string input)
{
var textMemModelList = await RetrieveTextMemModelList(index, input);
var global = _globals_Repositories.GetFirst(p => p.Index == index)?.Summaries;
IAsyncEnumerable answer;
//匹配到节点信息
var graphModel = await SearchGraphCommunityModel(index, input);
if (graphModel.Nodes.Count() > 0)
{
var community = string.Join(Environment.NewLine, _communities_Repositories.GetDB().Queryable().Where(p => p.Index == index).Select(p => p.Summaries).ToList());
//这里数据有点多,要通过语义进行一次过滤
answer = _semanticService.GetGraphCommunityAnswerStreamAsync(JsonConvert.SerializeObject(graphModel), community, global, input);
}
else
{
//如果没有匹配到节点信息,使用全局信息
answer = _semanticService.GetGraphCommunityAnswerStreamAsync("NoSearch", "NoSearch", global, input);
}
await foreach (var content in answer)
{
yield return content;
}
}
///
/// 社区摘要
///
///
///
public async Task GraphCommunitiesAsync(string index)
{
var nodes = _nodes_Repositories.GetList(p => p.Index == index);
var edges = _edges_Repositories.GetList(p => p.Index == index);
//删除社区数据
_communitieNodes_Repositories.Delete(p => p.Index == index);
_communities_Repositories.Delete(p => p.Index == index);
var graph = new Graph();
foreach (var edge in edges)
{
graph.AddEdge(edge.Source, edge.Target);
}
//重新计算社区
var result = _communityDetectionService.FastLabelPropagationAlgorithm(graph);
Console.WriteLine("开始社区总结");
foreach (var kvp in result)
{
//插入社区节点数据
CommunitieNodes communitieNodes = new CommunitieNodes();
communitieNodes.Index = index;
communitieNodes.CommunitieId = kvp.Value;
communitieNodes.NodeId = kvp.Key;
_communitieNodes_Repositories.Insert(communitieNodes);
}
//获取所有社区ID
var communitieIds = _communitieNodes_Repositories.GetDB().Queryable().Where(p => p.Index == index).GroupBy(p => p.CommunitieId).Select(p => p.CommunitieId).ToList();
foreach (var communitieId in communitieIds)
{
var nodeList = _communitieNodes_Repositories.GetDB().Queryable()
.LeftJoin((c, n) => c.NodeId == n.Id)
.Where(c => c.CommunitieId == communitieId)
.Select((c, n) => $"Name:{n.Name}; Type:{n.Type}; Desc:{n.Desc}")
.ToList();
var nodeDescs = string.Join(Environment.NewLine, nodeList);
var summaries = await _semanticService.CommunitySummaries(nodeDescs);
Communities communities = new Communities()
{
CommunitieId = communitieId,
Index = index,
Summaries = summaries
};
//插入社区总结数据
_communities_Repositories.Insert(communities);
}
}
///
/// 全局摘要
///
///
///
public async Task GraphGlobalAsync(string index)
{
_globals_Repositories.Delete(p => p.Index == index);
var communitieSummariesList = _communities_Repositories.GetDB().Queryable().Where(p => p.Index == index).Select(p => p.Summaries).ToList();
var communitieSummaries = string.Join(Environment.NewLine, communitieSummariesList);
var globalSummaries = await _semanticService.GlobalSummaries(communitieSummaries);
Globals globals = new Globals()
{
Index = index,
Summaries = globalSummaries
};
_globals_Repositories.Insert(globals);
}
///
/// 增强图谱关系:为现有节点发现和建立新的关系
///
///
///
public async Task EnhanceGraphRelationshipsAsync(string index)
{
if (string.IsNullOrWhiteSpace(index))
{
throw new ArgumentException("Index required value cannot be null.");
}
Console.WriteLine("开始增强图谱关系...");
SemanticTextMemory textMemory = await _semanticService.GetTextMemory();
// 获取所有孤立或连接较少的节点
var allNodes = _nodes_Repositories.GetList(p => p.Index == index);
var lowConnectedNodes = new List();
foreach (var node in allNodes)
{
var connectionCount = _edges_Repositories.GetDB().Queryable()
.Where(e => (e.Source == node.Id || e.Target == node.Id) && e.Index == index)
.Count();
// 如果连接数少于2个,认为是需要增强的节点
if (connectionCount < 2)
{
lowConnectedNodes.Add(node);
}
}
Console.WriteLine($"发现 {lowConnectedNodes.Count} 个需要增强关系的节点");
// 为每个低连接节点尝试建立新关系
int enhancedCount = 0;
foreach (var node in lowConnectedNodes)
{
try
{
int newConnections = await AttemptConnectOrphanNodeAsync(index, node, textMemory);
if (newConnections > 0)
{
enhancedCount++;
}
}
catch (Exception ex)
{
Console.WriteLine($"增强节点 {node.Name} 关系时出错:{ex.Message}");
}
}
Console.WriteLine($"关系增强完成,共为 {enhancedCount} 个节点建立了新关系");
}
///
/// 批量关系验证:检查并优化现有关系的质量
///
///
///
public async Task ValidateAndOptimizeRelationshipsAsync(string index)
{
if (string.IsNullOrWhiteSpace(index))
{
throw new ArgumentException("Index required value cannot be null.");
}
Console.WriteLine("开始验证和优化现有关系...");
var allEdges = _edges_Repositories.GetList(p => p.Index == index);
var weakRelationships = new List();
// 识别可能需要优化的关系
foreach (var edge in allEdges)
{
// 检查关系描述是否过于简单或模糊
if (string.IsNullOrWhiteSpace(edge.Relationship) ||
edge.Relationship.Length < 3 ||
edge.Relationship.ToLower().Contains("related") ||
edge.Relationship.ToLower().Contains("associated"))
{
weakRelationships.Add(edge);
}
}
Console.WriteLine($"发现 {weakRelationships.Count} 个需要优化的关系");
// 优化弱关系
int optimizedCount = 0;
foreach (var edge in weakRelationships.Take(20)) // 限制处理数量
{
try
{
var sourceNode = _nodes_Repositories.GetFirst(p => p.Id == edge.Source);
var targetNode = _nodes_Repositories.GetFirst(p => p.Id == edge.Target);
if (sourceNode != null && targetNode != null)
{
string sourceText = $"Name:{sourceNode.Name};Type:{sourceNode.Type};Desc:{sourceNode.Desc}";
string targetText = $"Name:{targetNode.Name};Type:{targetNode.Type};Desc:{targetNode.Desc}";
var newRelationship = await _semanticService.GetRelationship(sourceText, targetText);
if (newRelationship.IsRelationship &&
!string.IsNullOrWhiteSpace(newRelationship.Edge.Relationship) &&
newRelationship.Edge.Relationship != edge.Relationship)
{
edge.Relationship = newRelationship.Edge.Relationship;
_edges_Repositories.Update(edge);
optimizedCount++;
Console.WriteLine($"优化关系: {sourceNode.Name} -> {targetNode.Name}: {newRelationship.Edge.Relationship}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"优化关系时出错:{ex.Message}");
}
}
Console.WriteLine($"关系优化完成,共优化了 {optimizedCount} 个关系");
}
public async Task DeleteGraph(string index)
{
SemanticTextMemory textMemory = await _semanticService.GetTextMemory();
var nodes = await _nodes_Repositories.GetListAsync(p => p.Index == index);
foreach (var node in nodes)
{
//删除向量数据
await textMemory.RemoveAsync(index, node.Id);
}
//删除索引数据
await _nodes_Repositories.DeleteAsync(p => p.Index == index);
await _edges_Repositories.DeleteAsync(p => p.Index == index);
await _communities_Repositories.DeleteAsync(p => p.Index == index);
await _communitieNodes_Repositories.DeleteAsync(p => p.Index == index);
await _globals_Repositories.DeleteAsync(p => p.Index == index);
}
#region 内部方法
///
/// 检测和处理孤立节点
///
///
///
///
///
private async Task ProcessOrphanNodesAsync(string index, List newNodes, SemanticTextMemory textMemory)
{
Console.WriteLine($"开始检测孤立节点,新增节点数:{newNodes.Count}");
foreach (var node in newNodes)
{
// 检查节点是否为孤立节点(没有任何边连接)
bool hasConnections = _edges_Repositories.IsAny(p =>
(p.Source == node.Id || p.Target == node.Id) && p.Index == index);
if (!hasConnections)
{
Console.WriteLine($"发现孤立节点:{node.Name}");
var connectionsFound = await AttemptConnectOrphanNodeAsync(index, node, textMemory);
Console.WriteLine($"为节点 {node.Name} 建立了 {connectionsFound} 个新连接");
}
}
}
///
/// 尝试为孤立节点建立连接
///
///
///
///
/// 返回建立的新连接数量
private async Task AttemptConnectOrphanNodeAsync(string index, Nodes orphanNode, SemanticTextMemory textMemory)
{
string nodeText = $"Name:{orphanNode.Name};Type:{orphanNode.Type};Desc:{orphanNode.Desc}";
// 使用更低的阈值和更多的搜索结果来寻找潜在关系
List candidateNodes = new List();
// 第一轮:基于节点描述搜索
await foreach (MemoryQueryResult memory in textMemory.SearchAsync(index, nodeText, limit: 10, minRelevanceScore: 0.5))
{
if (memory.Metadata.Id != orphanNode.Id)
{
candidateNodes.Add(memory.Metadata.Id);
}
}
// 第二轮:基于节点名称搜索(针对命名实体)
if (candidateNodes.Count < 3)
{
await foreach (MemoryQueryResult memory in textMemory.SearchAsync(index, orphanNode.Name, limit: 5, minRelevanceScore: 0.6))
{
if (memory.Metadata.Id != orphanNode.Id && !candidateNodes.Contains(memory.Metadata.Id))
{
candidateNodes.Add(memory.Metadata.Id);
}
}
}
// 第三轮:基于节点类型搜索相同类型的实体
if (candidateNodes.Count < 2)
{
var sameTypeNodes = _nodes_Repositories.GetList(p =>
p.Index == index && p.Type == orphanNode.Type && p.Id != orphanNode.Id)
.Take(5).Select(p => p.Id).ToList();
foreach (var nodeId in sameTypeNodes)
{
if (!candidateNodes.Contains(nodeId))
{
candidateNodes.Add(nodeId);
}
}
}
// 尝试建立关系
int connectionsFound = 0;
foreach (var candidateId in candidateNodes.Take(5)) // 限制检查数量以控制成本
{
var candidateNode = _nodes_Repositories.GetFirst(p => p.Id == candidateId);
if (candidateNode != null)
{
string candidateText = $"Name:{candidateNode.Name};Type:{candidateNode.Type};Desc:{candidateNode.Desc}";
try
{
var relationShip = await _semanticService.GetRelationship(candidateText, nodeText);
if (relationShip.IsRelationship)
{
// 确定关系方向
string sourceId, targetId;
if (relationShip.Edge.Source == "node1")
{
sourceId = candidateNode.Id;
targetId = orphanNode.Id;
}
else
{
sourceId = orphanNode.Id;
targetId = candidateNode.Id;
}
// 检查关系是否已存在
if (!_edges_Repositories.IsAny(p => p.Source == sourceId && p.Target == targetId && p.Index == index))
{
var edge = new Edges()
{
Id = Guid.NewGuid().ToString(),
Index = index,
Source = sourceId,
Target = targetId,
Relationship = relationShip.Edge.Relationship
};
_edges_Repositories.Insert(edge);
connectionsFound++;
Console.WriteLine($"为孤立节点 {orphanNode.Name} 建立关系:{relationShip.Edge.Relationship} -> {candidateNode.Name}");
// 如果已经找到足够的连接,停止搜索
if (connectionsFound >= 2)
{
break;
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"为孤立节点建立关系时出错:{ex.Message}");
}
}
}
if (connectionsFound == 0)
{
Console.WriteLine($"警告:未能为孤立节点 {orphanNode.Name} 建立任何关系");
}
return connectionsFound;
}
///
/// 基于搜索条件检索TextMemModel的列表。
///
/// 索引
/// 输入文本
/// 最小相关性阈值
/// 搜索结果限制
///
private async Task> RetrieveTextMemModelList(string index, string input, double? minRelevance = null, int? limit = null)
{
SemanticTextMemory textMemory = await _semanticService.GetTextMemory();
List textMemModelList = new List();
// 使用提供的阈值或默认配置
double relevanceThreshold = minRelevance ?? GraphSearchOption.SearchMinRelevance;
int resultLimit = limit ?? GraphSearchOption.SearchLimit;
int matchCount = 0;
await foreach (MemoryQueryResult memory in textMemory.SearchAsync(index, input, limit: resultLimit, minRelevanceScore: relevanceThreshold))
{
matchCount++;
var textMemModel = new TextMemModel()
{
Id = memory.Metadata.Id,
Text = memory.Metadata.Text,
Relevance = memory.Relevance
};
textMemModelList.Add(textMemModel);
}
// 如果结果不足,尝试降低阈值
if (matchCount < 2 && relevanceThreshold > 0.3)
{
double lowerThreshold = Math.Max(0.3, relevanceThreshold - 0.2);
Console.WriteLine($"结果不足,降低阈值至:{lowerThreshold}重试");
await foreach (MemoryQueryResult memory in textMemory.SearchAsync(index, input, limit: resultLimit + 2, minRelevanceScore: lowerThreshold))
{
if (!textMemModelList.Any(t => t.Id == memory.Metadata.Id))
{
matchCount++;
var textMemModel = new TextMemModel()
{
Id = memory.Metadata.Id,
Text = memory.Metadata.Text,
Relevance = memory.Relevance
};
textMemModelList.Add(textMemModel);
}
}
}
// 按相关性排序
textMemModelList = textMemModelList.OrderByDescending(t => t.Relevance).ToList();
Console.WriteLine($"向量匹配数:{matchCount}, 最高相关度:{(textMemModelList.Any() ? textMemModelList.First().Relevance.ToString("F2") : "N/A")}");
return textMemModelList;
}
///
/// 递归获取节点相关的所有边和节点
///
/// 索引
/// 初始节点列表
/// 节点权重字典
///
private GraphModel GetGraphAllRecursion(string index, List initialNodes, Dictionary nodeWeights)
{
var allNodes = new List(initialNodes);
var allEdges = new List();
var nodesToExplore = new List(initialNodes);
int depth = 0;
while (nodesToExplore.Count > 0)
{
if (depth >= GraphSearchOption.NodeDepth || allNodes.Count >= GraphSearchOption.MaxNodes)
{
break;
}
// 按权重排序待探索节点
nodesToExplore = nodesToExplore
.OrderByDescending(n => nodeWeights.ContainsKey(n.Id) ? nodeWeights[n.Id] : 0)
.ToList();
var currentNodes = nodesToExplore.Take(Math.Min(5, nodesToExplore.Count)).ToList();
nodesToExplore.RemoveRange(0, currentNodes.Count);
var newEdges = GetEdges(index, currentNodes);
if (!newEdges.Any())
{
continue;
}
// 添加新边,避免重复
foreach (var edge in newEdges)
{
if (!allEdges.Any(e => e.Source == edge.Source && e.Target == edge.Target))
{
allEdges.Add(edge);
// 为新发现的节点设置权重
double parentWeight = nodeWeights.GetValueOrDefault(edge.Source, 0);
double weightDecay = 0.8; // 权重衰减因子
if (!nodeWeights.ContainsKey(edge.Target))
{
nodeWeights[edge.Target] = parentWeight * weightDecay;
}
}
}
// 获取新节点
var newNodes = GetNodes(index, newEdges);
var nodesToAdd = newNodes.Where(n => !allNodes.Any(existingNode => existingNode.Id == n.Id)).ToList();
allNodes.AddRange(nodesToAdd);
nodesToExplore.AddRange(nodesToAdd);
depth++;
}
// 如果节点数超过限制,保留权重最高的节点
if (allNodes.Count > GraphSearchOption.MaxNodes)
{
allNodes = allNodes
.OrderByDescending(n => nodeWeights.GetValueOrDefault(n.Id, 0))
.Take(GraphSearchOption.MaxNodes)
.ToList();
// 确保边的节点都在保留的节点中
allEdges = allEdges
.Where(e => allNodes.Any(n => n.Id == e.Source) && allNodes.Any(n => n.Id == e.Target))
.ToList();
}
return new GraphModel
{
Nodes = allNodes,
Edges = allEdges
};
}
///
/// 通过社区算法检索社区节点
///
///
///
///
private GraphModel GetGraphAllCommunitiesRecursion(string index, List initialNodes)
{
var allNodes = new List();
var allEdges = new List();
var nodeIds = initialNodes.Select(x => x.Id).ToList();
var communitiesNodes = _communities_Repositories.GetDB().Queryable()
.LeftJoin((c, cn) => c.CommunitieId == cn.CommunitieId)
.LeftJoin((c, cn, n) => cn.NodeId == n.Id)
.Where((c, cn, n) => nodeIds.Contains(n.Id)).Select((c, cn, n) => new Nodes() { Index = n.Index, Id = n.Id, Name = n.Name, Type = n.Type, Desc = n.Desc }).ToList();
allNodes.AddRange(communitiesNodes);
var newEdges = GetEdges(index, allNodes);
foreach (var edge in newEdges)
{
if (!allEdges.Any(e => e.Source == edge.Source && e.Target == edge.Target))
{
allEdges.Add(edge);
}
}
// 如果节点数超过最大限制,进行截断
if (allNodes.Count > GraphSearchOption.MaxNodes)
{
allNodes = allNodes.Take(GraphSearchOption.MaxNodes).ToList();
}
// 需要相应地处理 allEdges,确保边的节点在 allNodes 中
allEdges = allEdges.Where(e => allNodes.Any(p => p.Id == e.Source) && allNodes.Any(p => p.Id == e.Target)).ToList();
return new GraphModel
{
Nodes = allNodes,
Edges = allEdges
};
}
///
/// 获取边信息
///
/// 索引
/// 节点列表
///
private List GetEdges(string index, List nodes)
{
var nodeIds = nodes.Select(x => x.Id).ToList();
var edges = new List();
edges = _edges_Repositories.GetList(x => x.Index == index && nodeIds.Contains(x.Source) && nodeIds.Contains(x.Target));
return edges;
}
///
/// 获取节点信息
///
/// 索引
/// 边列表
///
private List GetNodes(string index, List edges)
{
var targets = edges.Select(p => p.Target).ToList();
var sources = edges.Select(p => p.Source).ToList();
List nodeIds = new List();
nodeIds.AddRange(targets);
nodeIds.AddRange(sources);
var nodes = _nodes_Repositories.GetList(p => p.Index == index || nodeIds.Contains(p.Id));
return nodes;
}
///
/// 获取相关社区ID列表
///
private List GetRelevantCommunities(string index, List nodeIds)
{
var communities = _communitieNodes_Repositories.GetDB().Queryable()
.Where(cn => nodeIds.Contains(cn.NodeId))
.Select(cn => cn.CommunitieId)
.Distinct()
.ToList();
return communities;
}
#endregion
}
}
================================================
FILE: src/GraphRag.Net/Domain/Service/SemanticService.cs
================================================
using Azure.AI.OpenAI;
using GraphRag.Net.Options;
using GraphRag.Net.Domain.Interface;
using GraphRag.Net.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Connectors.Postgres;
using Microsoft.SemanticKernel.Connectors.Sqlite;
using Microsoft.SemanticKernel.Memory;
using Npgsql;
using Newtonsoft.Json;
using GraphRag.Net.Domain.Model.Graph;
using Polly;
using Polly.Retry;
using GraphRag.Net.Common.Options;
namespace GraphRag.Net.Domain.Service
{
[ServiceDescription(typeof(ISemanticService), ServiceLifetime.Scoped)]
public class SemanticService(Kernel _kernel) : ISemanticService
{
public async Task CreateGraphAsync(string input)
{
var retryPolicy = Policy.Handle().RetryAsync(GraphSysOption.RetryCounnt, (ex, count) =>
{
Console.WriteLine($"CreateGraphAsync失败,重试{count}次,异常信息{ex.Message}");
});
var result = await retryPolicy.ExecuteAsync(async () =>
{
OpenAIPromptExecutionSettings settings = new()
{
Temperature = 0,
ResponseFormat = ChatCompletionsResponseFormat.JsonObject
};
KernelFunction createFun = _kernel.Plugins.GetFunction("graph", "create");
var args = new KernelArguments(settings)
{
["input"] = input,
};
var skresult = await _kernel.InvokeAsync(createFun, args);
string json = skresult.GetValue()?.Trim() ?? "";
var graph = JsonConvert.DeserializeObject(json);
return graph;
});
return result;
}
public async Task GetGraphAnswerAsync(string graph, string input)
{
KernelFunction createFun = _kernel.Plugins.GetFunction("graph", "search");
var args = new KernelArguments()
{
["graph"] = graph,
["input"] = input,
};
var skresult = await _kernel.InvokeAsync(createFun, args);
string result = skresult.GetValue()?.Trim() ?? "";
return result;
}
public async IAsyncEnumerable GetGraphAnswerStreamAsync(string graph, string input)
{
KernelFunction createFun = _kernel.Plugins.GetFunction("graph", "search");
var args = new KernelArguments()
{
["graph"] = graph,
["input"] = input,
};
var skresult = _kernel.InvokeStreamingAsync(createFun, args);
await foreach (var content in skresult)
{
yield return content;
}
}
public async Task GetGraphCommunityAnswerAsync(string graph, string community, string global, string input)
{
KernelFunction createFun = _kernel.Plugins.GetFunction("graph", "community_search");
var args = new KernelArguments()
{
["graph"] = graph,
["community"] = community,
["global"] = global,
["input"] = input,
};
var skresult = await _kernel.InvokeAsync(createFun, args);
string result = skresult.GetValue()?.Trim() ?? "";
return result;
}
public async IAsyncEnumerable GetGraphCommunityAnswerStreamAsync(string graph, string community, string global, string input)
{
KernelFunction createFun = _kernel.Plugins.GetFunction("graph", "community_search");
var args = new KernelArguments()
{
["graph"] = graph,
["community"] = community,
["global"] = global,
["input"] = input,
};
var skresult = _kernel.InvokeStreamingAsync(createFun, args);
await foreach (var content in skresult)
{
yield return content;
}
}
public async Task GetRelationship(string node1, string node2)
{
var retryPolicy = Policy.Handle().RetryAsync(GraphSysOption.RetryCounnt, (ex, count) =>
{
Console.WriteLine($"GetRelationship失败,重试{count}次,异常信息{ex.Message}");
});
var result = await retryPolicy.ExecuteAsync(async () =>
{
OpenAIPromptExecutionSettings settings = new()
{
Temperature = 0,
ResponseFormat = ChatCompletionsResponseFormat.JsonObject
};
KernelFunction createFun = _kernel.Plugins.GetFunction("graph", "relationship");
var args = new KernelArguments(settings)
{
["node1"] = node1,
["node2"] = node2,
};
var skresult = await _kernel.InvokeAsync(createFun, args);
string json = skresult.GetValue()?.Trim() ?? "";
var relation = JsonConvert.DeserializeObject(json);
return relation;
});
return result;
}
public async Task MergeDesc(string desc1, string desc2)
{
KernelFunction createFun = _kernel.Plugins.GetFunction("graph", "mergedesc");
var args = new KernelArguments()
{
["desc1"] = desc1,
["desc2"] = desc2,
};
var skresult = await _kernel.InvokeAsync(createFun, args);
string result = skresult.GetValue()?.Trim() ?? "";
return result;
}
public async Task CommunitySummaries(string nodes)
{
KernelFunction createFun = _kernel.Plugins.GetFunction("graph", "community_summaries");
var args = new KernelArguments()
{
["nodes"] = nodes
};
var skresult = await _kernel.InvokeAsync(createFun, args);
string result = skresult.GetValue()?.Trim() ?? "";
return result;
}
public async Task GlobalSummaries(string community)
{
KernelFunction createFun = _kernel.Plugins.GetFunction("graph", "global_summaries");
var args = new KernelArguments()
{
["community"] = community
};
var skresult = await _kernel.InvokeAsync(createFun, args);
string result = skresult.GetValue()?.Trim() ?? "";
return result;
}
///
/// 获取SemanticTextMemory
///
///
///
public async Task GetTextMemory()
{
IMemoryStore memoryStore = null;
switch (GraphDBConnectionOption.DbType)
{
case "Sqlite":
memoryStore = await SqliteMemoryStore.ConnectAsync(GraphDBConnectionOption.VectorConnection);
break;
case "PostgreSQL":
NpgsqlDataSourceBuilder dataSourceBuilder = new(GraphDBConnectionOption.VectorConnection);
dataSourceBuilder.UseVector();
NpgsqlDataSource dataSource = dataSourceBuilder.Build();
memoryStore = new PostgresMemoryStore(dataSource, vectorSize: 1536, schema: "public");
break;
}
if (memoryStore == null)
{
throw new InvalidOperationException("GraphDBConnection error failed to initialize memory store.");
}
var handler = new OpenAIHttpClientHandler();
var httpClient = new HttpClient(handler);
httpClient.Timeout = TimeSpan.FromMinutes(10);
var embeddingGenerator = new OpenAITextEmbeddingGenerationService(GraphOpenAIOption.EmbeddingModel, GraphOpenAIOption.Key, httpClient: new HttpClient(handler));
SemanticTextMemory textMemory = new(memoryStore, embeddingGenerator);
return textMemory;
}
}
}
================================================
FILE: src/GraphRag.Net/Extensions/ServiceCollectionExtensions.cs
================================================
using System.Reflection;
using GraphRag.Net;
using GraphRag.Net.Options;
using GraphRag.Net.Repositories;
using GraphRag.Net.Utils;
using Microsoft.SemanticKernel;
using SqlSugar;
namespace Microsoft.Extensions.DependencyInjection
{
///
/// 容器扩展
///
public static class ServiceCollectionExtensions
{
///
/// 从程序集中加载类型并添加到容器中
///
/// 容器
///
public static IServiceCollection AddGraphRagNet(this IServiceCollection services, Kernel _kernel = null)
{
Type attributeType = typeof(ServiceDescriptionAttribute);
//var refAssembyNames = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
Assembly assembly = Assembly.Load("GraphRag.Net");
var types = assembly.GetTypes();
foreach (var classType in types)
{
if (!classType.IsAbstract && classType.IsClass && classType.IsDefined(attributeType, false))
{
ServiceDescriptionAttribute serviceAttribute = (classType.GetCustomAttribute(attributeType) as ServiceDescriptionAttribute);
switch (serviceAttribute.Lifetime)
{
case ServiceLifetime.Scoped:
services.AddScoped(serviceAttribute.ServiceType, classType);
break;
case ServiceLifetime.Singleton:
services.AddSingleton(serviceAttribute.ServiceType, classType);
break;
case ServiceLifetime.Transient:
services.AddTransient(serviceAttribute.ServiceType, classType);
break;
}
}
}
CodeFirst();
InitSK(services, _kernel);
return services;
}
///
/// 初始化SK
///
///
/// 可以提供自定义Kernel
static void InitSK(IServiceCollection services,Kernel _kernel = null)
{
var handler = new OpenAIHttpClientHandler();
services.AddTransient((serviceProvider) =>
{
if (_kernel == null)
{
_kernel = Kernel.CreateBuilder()
.AddOpenAIChatCompletion(
modelId: GraphOpenAIOption.ChatModel,
apiKey: GraphOpenAIOption.Key,
httpClient: new HttpClient(handler)
)
.Build();
}
//导入插件
if (!_kernel.Plugins.Any(p => p.Name == "graph"))
{
var pluginPatth = Path.Combine(RepoFiles.SamplePluginsPath(), "graph");
Console.WriteLine($"pluginPatth:{pluginPatth}");
_kernel.ImportPluginFromPromptDirectory(pluginPatth);
}
return _kernel;
});
}
///
/// 初始化DB
///
static void CodeFirst()
{
// 获取仓储服务
var _repository = new Nodes_Repositories();
// 创建数据库(如果不存在)
_repository.GetDB().DbMaintenance.CreateDatabase();
// 在所有程序集中查找具有[SugarTable]特性的类
var assembly = Assembly.GetExecutingAssembly();
// 获取该程序集中所有具有SugarTable特性的类型
var entityTypes = assembly.GetTypes()
.Where(type => TypeIsEntity(type));
// 为每个找到的类型初始化数据库表
foreach (var type in entityTypes)
{
_repository.GetDB().CodeFirst.InitTables(type);
}
}
static bool TypeIsEntity(Type type)
{
// 检查类型是否具有SugarTable特性
return type.GetCustomAttributes(typeof(SugarTable), inherit: false).Length > 0;
}
}
}
================================================
FILE: src/GraphRag.Net/GraphRag.Net.csproj
================================================
net6.0;net7.0;net8.0
latest
$(Version)
enable
enable
许泽宇
GraphRag.Net
xuzeyu
GraphRag.Net
GraphRag for .NET –这是一个参考GraphRag的DotNet简易实现。
基于微软在论文中提到的实现思路,执行过程GraphRAG主要实现了如下功能:
Source Documents → Text Chunks:将源文档分割成文本块。
Text Chunks → Element Instances:从每个文本块中提取图节点和边的实例。
Element Instances → Element Summaries:为每个图元素生成摘要。
Element Summaries → Graph Communities:使用社区检测算法将图划分为社区。
Graph Communities → Community Summaries:为每个社区生成摘要。
Community Summaries → Community Answers → Global Answer:使用社区摘要生成局部答案,然后汇总这些局部答案以生成全局答案。
商务需求联系微信xuzeyu91
https://github.com/xuzeyu91/GraphRag.Net
AI, Rag, GraphRag
Apache-2.0
CA1050,CA1707,CA2007,VSTHRD111,CS1591,RCS1110,CA5394,SKEXP0001,SKEXP0002,SKEXP0003,SKEXP0004,SKEXP0010,SKEXP0011,,SKEXP0012,SKEXP0020,SKEXP0021,SKEXP0022,SKEXP0023,SKEXP0024,SKEXP0025,SKEXP0026,SKEXP0027,SKEXP0028,SKEXP0029,SKEXP0030,SKEXP0031,SKEXP0032,SKEXP0040,SKEXP0041,SKEXP0042,SKEXP0050,SKEXP0051,SKEXP0052,SKEXP0053,SKEXP0054,SKEXP0055,SKEXP0060,SKEXP0061,SKEXP0101,SKEXP0102
True
GraphRag.Net.xml
================================================
FILE: src/GraphRag.Net/Repositories/Base/IRepository.cs
================================================
using GraphRag.Net.Model;
using SqlSugar;
using System.Linq.Expressions;
namespace GraphRag.Net.Base
{
public interface IRepository
{
SqlSugarScope GetDB();
List GetList();
Task> GetListAsync();
List GetList(Expression> whereExpression);
Task> GetListAsync(Expression> whereExpression);
int Count(Expression> whereExpression);
Task CountAsync(Expression> whereExpression);
PageList GetPageList(Expression> whereExpression, PageModel page);
Task> GetPageListAsync(Expression> whereExpression, PageModel page);
PageList GetPageList(Expression> whereExpression, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc);
Task> GetPageListAsync(Expression> whereExpression, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc);
PageList GetPageList(List conditionalList, PageModel page);
Task> GetPageListAsync(List conditionalList, PageModel page);
PageList GetPageList(List conditionalList, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc);
Task> GetPageListAsync(List conditionalList, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc);
T GetById(dynamic id);
Task GetByIdAsync(dynamic id);
T GetSingle(Expression> whereExpression);
Task GetSingleAsync(Expression> whereExpression);
T GetFirst(Expression> whereExpression);
Task GetFirstAsync(Expression> whereExpression);
bool Insert(T obj);
Task InsertAsync(T obj);
bool InsertRange(List objs);
Task InsertRangeAsync(List objs);
int InsertReturnIdentity(T obj);
Task InsertReturnIdentityAsync(T obj);
long InsertReturnBigIdentity(T obj);
Task InsertReturnBigIdentityAsync(T obj);
bool DeleteByIds(dynamic[] ids);
Task DeleteByIdsAsync(dynamic[] ids);
bool Delete(dynamic id);
Task DeleteAsync(dynamic id);
bool Delete(T obj);
Task DeleteAsync(T obj);
bool Delete(Expression> whereExpression);
Task DeleteAsync(Expression> whereExpression);
bool Update(T obj);
Task UpdateAsync(T obj);
bool UpdateRange(List objs);
bool InsertOrUpdate(T obj);
Task InsertOrUpdateAsync(T obj);
Task UpdateRangeAsync(List objs);
bool IsAny(Expression> whereExpression);
Task IsAnyAsync(Expression> whereExpression);
}
}
================================================
FILE: src/GraphRag.Net/Repositories/Base/Repository.cs
================================================
using GraphRag.Net.Model;
using SqlSugar;
using System.Linq.Expressions;
namespace GraphRag.Net.Base
{
public class Repository : SimpleClient where T : class, new()
{
public Repository(ISqlSugarClient context = null) : base(context)//注意这里要有默认值等于null
{
if (context == null)
{
}
}
//注意:如果使用Client不能写成静态的,Scope并发更高
public static SqlSugarScope SqlScope = SqlSugarHelper.SqlScope();
public SimpleClient CurrentDb
{ get { return new SimpleClient(SqlScope); } }//用来处理T表的常用操作
#region 通用方法
public virtual SqlSugarScope GetDB()
{
return SqlScope;
}
///
/// 获取所有list
///
///
public virtual List GetList()
{
return CurrentDb.GetList();
}
///
/// 获取所有list-异步
///
///
public virtual async Task> GetListAsync()
{
return await CurrentDb.GetListAsync();
}
///
/// 根据lambda查询
///
///
///
public virtual List GetList(Expression> whereExpression)
{
return CurrentDb.GetList(whereExpression);
}
///
/// 根据lambda查询-异步
///
///
///
public virtual async Task> GetListAsync(Expression> whereExpression)
{
return await CurrentDb.GetListAsync(whereExpression);
}
///
/// 根据lambda表达式获取数量
///
///
///
public virtual int Count(Expression> whereExpression)
{
return CurrentDb.Count(whereExpression);
}
///
/// 根据lambda表达式获取数量-异步
///
///
///
public virtual async Task CountAsync(Expression> whereExpression)
{
return await CurrentDb.CountAsync(whereExpression);
}
///
/// 获取分页
///
///
///
///
public virtual PageList GetPageList(Expression> whereExpression, PageModel page)
{
PageList list = new PageList();
list.List = CurrentDb.GetPageList(whereExpression, page);
list.PageIndex = page.PageIndex;
list.PageSize = page.PageSize;
list.TotalCount = page.TotalCount;
return list;
}
///
/// 获取分页-异步
///
///
///
///
public virtual async Task> GetPageListAsync(Expression> whereExpression, PageModel page)
{
PageList list = new PageList();
list.List = await CurrentDb.GetPageListAsync(whereExpression, page);
list.PageIndex = page.PageIndex;
list.PageSize = page.PageSize;
list.TotalCount = page.TotalCount;
return list;
}
public virtual PageList GetPageList(Expression> whereExpression, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
{
PageList list = new PageList();
list.List = CurrentDb.GetPageList(whereExpression, page, orderByExpression, orderByType);
list.PageIndex = page.PageIndex;
list.PageSize = page.PageSize;
list.TotalCount = page.TotalCount;
return list;
}
public virtual async Task> GetPageListAsync(Expression> whereExpression, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
{
PageList list = new PageList();
list.List = await CurrentDb.GetPageListAsync(whereExpression, page, orderByExpression, orderByType);
list.PageIndex = page.PageIndex;
list.PageSize = page.PageSize;
list.TotalCount = page.TotalCount;
return list;
}
public virtual PageList GetPageList(List conditionalList, PageModel page)
{
PageList list = new PageList();
list.List = CurrentDb.GetPageList(conditionalList, page);
list.PageIndex = page.PageIndex;
list.PageSize = page.PageSize;
list.TotalCount = page.TotalCount;
return list;
}
public virtual async Task> GetPageListAsync(List conditionalList, PageModel page)
{
PageList list = new PageList();
list.List = await CurrentDb.GetPageListAsync(conditionalList, page);
list.PageIndex = page.PageIndex;
list.PageSize = page.PageSize;
list.TotalCount = page.TotalCount;
return list;
}
public virtual PageList GetPageList(List conditionalList, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
{
PageList list = new PageList();
list.List = CurrentDb.GetPageList(conditionalList, page, orderByExpression, orderByType);
list.PageIndex = page.PageIndex;
list.PageSize = page.PageSize;
list.TotalCount = page.TotalCount;
return list;
}
public virtual async Task> GetPageListAsync(List conditionalList, PageModel page, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
{
PageList list = new PageList();
list.List = await CurrentDb.GetPageListAsync(conditionalList, page, orderByExpression, orderByType);
list.PageIndex = page.PageIndex;
list.PageSize = page.PageSize;
list.TotalCount = page.TotalCount;
return list;
}
///