Skip to content

Commit 5795913

Browse files
committed
Integrate Pinecone on all levels
1 parent 91235fa commit 5795913

File tree

9 files changed

+209
-3
lines changed

9 files changed

+209
-3
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@
1010

1111
# exclude scratch files
1212
**/_*
13-
docs/package-lock.json
13+
docs/package-lock.json
14+
15+
.env

Project.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@ version = "0.50.0"
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
88
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
99
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
10+
DotEnv = "4dc1fcf4-5e3b-5448-94ab-0c38ec0385c1"
1011
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
1112
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
1213
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
1314
OpenAI = "e9f21f70-7185-4079-aca2-91159181367c"
15+
Pinecone = "ee90fdae-f7f0-4648-8b00-9c0307cf46d9"
1416
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
1517
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
1618
Preferences = "21216c6a-2e73-6563-6e65-726566657250"
1719
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
1820
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
21+
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
1922
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2023

2124
[weakdeps]

src/Experimental/RAGTools/RAGTools.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ include("api_services.jl")
3232

3333
include("rag_interface.jl")
3434

35-
export ChunkIndex, ChunkKeywordsIndex, ChunkEmbeddingsIndex, CandidateChunks, RAGResult
35+
export ChunkIndex, ChunkKeywordsIndex, ChunkEmbeddingsIndex, PTPineconeIndex, CandidateChunks, RAGResult
3636
export MultiIndex, SubChunkIndex, MultiCandidateChunks
3737
include("types.jl")
3838

3939
export build_index, get_chunks, get_embeddings, get_keywords, get_tags, SimpleIndexer,
40-
KeywordsIndexer
40+
KeywordsIndexer, PTPineconeIndexer
4141
include("preparation.jl")
4242

4343
include("rank_gpt.jl")

src/Experimental/RAGTools/generation.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,30 @@ function build_context(contexter::ContextEnumerator,
6363
return context
6464
end
6565

66+
using Pinecone: Pinecone, query
67+
using JSON3: JSON3, read
68+
"""
69+
build_context(contexter::ContextEnumerator,
70+
index::AbstractPTPineconeIndex;
71+
verbose::Bool = true,
72+
top_k::Int = 10,
73+
kwargs...)
74+
75+
Build context strings by querying Pinecone.
76+
```
77+
"""
78+
function build_context(contexter::ContextEnumerator,
79+
index::AbstractPTPineconeIndex;
80+
verbose::Bool = true,
81+
top_k::Int = 10,
82+
kwargs...)
83+
pinecone_results = Pinecone.query(index.pinecone_context, index.pinecone_index, index.embedding, top_k, index.namespace, false, true)
84+
results_json = JSON3.read(pinecone_results)
85+
context = results_json.matches[1].metadata.content
86+
87+
return context
88+
end
89+
6690
function build_context!(contexter::AbstractContextBuilder,
6791
index::AbstractDocumentIndex, result::AbstractRAGResult; kwargs...)
6892
throw(ArgumentError("Contexter $(typeof(contexter)) not implemented"))

src/Experimental/RAGTools/preparation.jl

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ Chunker when you provide text to `get_chunks` functions. Inputs are directly chu
2020
"""
2121
struct TextChunker <: AbstractChunker end
2222

23+
"""
24+
NoChunker <: AbstractChunker
25+
26+
27+
"""
28+
struct NoChunker <: AbstractChunker end
29+
2330
### Embedding Types
2431
"""
2532
NoEmbedder <: AbstractEmbedder
@@ -134,6 +141,19 @@ It uses `TextChunker`, `KeywordsProcessor`, and `NoTagger` as default chunker, p
134141
tagger::AbstractTagger = NoTagger()
135142
end
136143

144+
"""
145+
PTPineconeIndexer <: AbstractIndexBuilder
146+
147+
Pinecone index to be returned by `build_index`.
148+
149+
It uses `NoChunker`, `NoEmbedder`, and `NoTagger` as default chunker, embedder, and tagger.
150+
"""
151+
@kwdef mutable struct PTPineconeIndexer <: AbstractIndexBuilder
152+
chunker::AbstractChunker = NoChunker()
153+
embedder::AbstractEmbedder = NoEmbedder()
154+
tagger::AbstractTagger = NoTagger()
155+
end
156+
137157
### Functions
138158

139159
## "Build an index for RAG (Retriever-Augmented Generation) applications. REQUIRES SparseArrays and LinearAlgebra packages to be loaded!!"
@@ -166,6 +186,10 @@ function load_text(chunker::TextChunker, input::AbstractString;
166186
@assert length(source)<=512 "Each `source` should be less than 512 characters long. Detected: $(length(source)) characters. You must provide sources for each text when using `TextChunker`"
167187
return input, source
168188
end
189+
function load_text(chunker::NoChunker, input::AbstractString = "";
190+
source::AbstractString = input, kwargs...)
191+
return input, source
192+
end
169193

170194
"""
171195
get_chunks(chunker::AbstractChunker,
@@ -701,6 +725,37 @@ function build_index(
701725
return index
702726
end
703727

728+
using Pinecone: Pinecone, init_v3, Index
729+
"""
730+
build_index(
731+
indexer::PTPineconeIndexer;
732+
namespace::AbstractString,
733+
schema::AbstractPromptSchema = OpenAISchema();
734+
verbose::Integer = 1,
735+
index_id = gensym("PTPineconeIndex"),
736+
cost_tracker = Threads.Atomic{Float64}(0.0))
737+
738+
Builds a `PTPineconeIndex` containing a Pinecone context (API key, index and namespace).
739+
"""
740+
function build_index(
741+
indexer::PTPineconeIndexer,
742+
namespace::AbstractString,
743+
schema::PromptingTools.AbstractPromptSchema = PromptingTools.OpenAISchema();
744+
verbose::Integer = 1,
745+
index_id = gensym("PTPineconeIndex"),
746+
cost_tracker = Threads.Atomic{Float64}(0.0))
747+
748+
pinecone_context = Pinecone.init_v3(ENV["PINECONE_API_KEY"])
749+
pindex = ENV["PINECONE_INDEX"]
750+
pinecone_index = pinecone_index = !isempty(pindex) ? Pinecone.Index(pinecone_context, pindex) : nothing
751+
752+
index = PTPineconeIndex(; id = index_id, pinecone_context, pinecone_index, namespace, schema)
753+
754+
(verbose > 0) && @info "Index built! (cost: \$$(round(cost_tracker[], digits=3)))"
755+
756+
return index
757+
end
758+
704759
# Convenience for easy index creation
705760
"""
706761
ChunkKeywordsIndex(

src/Experimental/RAGTools/rag_interface.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,13 @@ Main abstract type for storing document chunks and their embeddings. It also sto
161161
"""
162162
abstract type AbstractChunkIndex <: AbstractDocumentIndex end
163163

164+
"""
165+
AbstractPTPineconeIndex <: AbstractDocumentIndex
166+
167+
Abstract type for working with Pinecone. For now, just an empty index.
168+
"""
169+
abstract type AbstractPTPineconeIndex <: AbstractDocumentIndex end
170+
164171
# ## Retrieval stage
165172

166173
"""

src/Experimental/RAGTools/retrieval.jl

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,13 @@ function find_tags(method::NoTagFilter, index::AbstractMultiIndex,
610610
return nothing
611611
end
612612

613+
function find_tags(method::NoTagFilter, index::AbstractPTPineconeIndex,
614+
tags::Union{T, AbstractVector{<:T}}; kwargs...) where {T <:
615+
Union{
616+
AbstractString, Regex, Nothing}}
617+
return nothing
618+
end
619+
613620
### Reranking
614621

615622
"""
@@ -943,6 +950,21 @@ Compared to SimpleRetriever, it adds rephrasing the query and reranking the resu
943950
reranker::AbstractReranker = CohereReranker()
944951
end
945952

953+
"""
954+
PTPineconeRetriever <: AbstractRetriever
955+
956+
Dispatch for `retrieve` for Pinecone.
957+
"""
958+
@kwdef mutable struct PTPineconeRetriever <: AbstractRetriever
959+
rephraser::AbstractRephraser = NoRephraser()
960+
embedder::AbstractEmbedder = NoEmbedder()
961+
processor::AbstractProcessor = NoProcessor()
962+
finder::AbstractSimilarityFinder = CosineSimilarity()
963+
tagger::AbstractTagger = NoTagger()
964+
filter::AbstractTagFilter = NoTagFilter()
965+
reranker::AbstractReranker = NoReranker()
966+
end
967+
946968
"""
947969
retrieve(retriever::AbstractRetriever,
948970
index::AbstractChunkIndex,
@@ -1157,6 +1179,58 @@ function retrieve(retriever::AbstractRetriever,
11571179
return result
11581180
end
11591181

1182+
function retrieve(retriever::PTPineconeRetriever,
1183+
index::AbstractPTPineconeIndex,
1184+
question::AbstractString;
1185+
verbose::Integer = 1,
1186+
top_k::Integer = 100,
1187+
top_n::Integer = 10,
1188+
api_kwargs::NamedTuple = NamedTuple(),
1189+
rephraser::AbstractRephraser = retriever.rephraser,
1190+
rephraser_kwargs::NamedTuple = NamedTuple(),
1191+
embedder::AbstractEmbedder = retriever.embedder,
1192+
embedder_kwargs::NamedTuple = NamedTuple(),
1193+
processor::AbstractProcessor = retriever.processor,
1194+
processor_kwargs::NamedTuple = NamedTuple(),
1195+
finder::AbstractSimilarityFinder = retriever.finder,
1196+
finder_kwargs::NamedTuple = NamedTuple(),
1197+
tagger::AbstractTagger = retriever.tagger,
1198+
tagger_kwargs::NamedTuple = NamedTuple(),
1199+
filter::AbstractTagFilter = retriever.filter,
1200+
filter_kwargs::NamedTuple = NamedTuple(),
1201+
reranker::AbstractReranker = retriever.reranker,
1202+
reranker_kwargs::NamedTuple = NamedTuple(),
1203+
cost_tracker = Threads.Atomic{Float64}(0.0),
1204+
kwargs...)
1205+
## Rephrase into one or more questions
1206+
rephraser_kwargs_ = isempty(api_kwargs) ? rephraser_kwargs :
1207+
merge(rephraser_kwargs, (; api_kwargs))
1208+
rephrased_questions = rephrase(
1209+
rephraser, question; verbose = (verbose > 1), cost_tracker, rephraser_kwargs_...)
1210+
1211+
## Embed the question
1212+
index.embedding = Vector{Float64}(aiembed(index.schema, question).content)
1213+
embeddings = hcat([Vector{Float64}(aiembed(index.schema, x).content) for x in rephrased_questions]...)
1214+
1215+
## Get the context from Pinecone
1216+
pinecone_results = Pinecone.query(index.pinecone_context, index.pinecone_index, index.embedding, top_n, index.namespace, false, true)
1217+
pinecone_results_json = JSON3.read(pinecone_results)
1218+
context = map(x -> x.metadata.content, pinecone_results_json.matches)
1219+
1220+
verbose > 0 &&
1221+
@info "Retrieval done. Total cost: \$$(round(cost_tracker[], digits=2))."
1222+
1223+
## Return
1224+
result = RAGResult(;
1225+
question,
1226+
answer = nothing,
1227+
rephrased_questions,
1228+
final_answer = nothing,
1229+
context)
1230+
1231+
return result
1232+
end
1233+
11601234
# Set default behavior
11611235
DEFAULT_RETRIEVER = SimpleRetriever()
11621236
function retrieve(index::AbstractChunkIndex, question::AbstractString;

src/Experimental/RAGTools/types.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,24 @@ chunkdata(index::ChunkEmbeddingsIndex) = embeddings(index)
134134

135135
# For backward compatibility
136136
const ChunkIndex = ChunkEmbeddingsIndex
137+
138+
using Pinecone: Pinecone, PineconeContextv3, PineconeIndexv3
139+
"""
140+
PTPineconeIndex
141+
142+
Struct for storing index for working with Pinecone.
143+
"""
144+
@kwdef mutable struct PTPineconeIndex <: AbstractPTPineconeIndex
145+
id::Symbol = gensym("PTPineconeIndex")
146+
pinecone_context::Pinecone.PineconeContextv3
147+
pinecone_index::Pinecone.PineconeIndexv3
148+
namespace::AbstractString
149+
schema::PromptingTools.AbstractPromptSchema
150+
embedding::Vector{Float64} = Float64[]
151+
end
152+
HasKeywords(::PTPineconeIndex) = false
153+
HasEmbeddings(::PTPineconeIndex) = false
154+
137155
abstract type AbstractDocumentTermMatrix end
138156

139157
"""
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[
2+
{
3+
"content": "Template Metadata",
4+
"description": "For asking questions for Julia in a RAG context. Placeholders: `question` and `context`",
5+
"version": "1",
6+
"source": "",
7+
"_type": "metadatamessage"
8+
},
9+
{
10+
"content": "Act as a world-class Julia language programmer with access to the latest Julia-related knowledge via Context Information. \n\n**Instructions:**\n- Answer the question based only on the provided Context.\n- Be precise and answer only when you're confident in the high quality of your answer.\n- Be brief and concise.\n\n**Context Information:**\n---\n{{context}}\n---\n",
11+
"variables": [
12+
"context"
13+
],
14+
"_type": "systemmessage"
15+
},
16+
{
17+
"content": "# Question\n\n{{question}}\n\n\n\n# Answer\n\n",
18+
"variables": [
19+
"question"
20+
],
21+
"_type": "usermessage"
22+
}
23+
]

0 commit comments

Comments
 (0)