apify-haystack 0.0.1a2__tar.gz → 0.0.1a4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {apify_haystack-0.0.1a2 → apify_haystack-0.0.1a4}/PKG-INFO +4 -1
- {apify_haystack-0.0.1a2 → apify_haystack-0.0.1a4}/pyproject.toml +15 -2
- apify_haystack-0.0.1a4/src/apify_haystack/__init__.py +7 -0
- apify_haystack-0.0.1a4/src/apify_haystack/apify_dataset.py +223 -0
- apify_haystack-0.0.1a4/src/apify_haystack/constants.py +2 -0
- apify_haystack-0.0.1a4/src/apify_haystack/examples/apify_actor_call.py +44 -0
- apify_haystack-0.0.1a4/src/apify_haystack/examples/apify_dataset_load.py +31 -0
- apify_haystack-0.0.1a4/src/apify_haystack/examples/crawl_and_process_data.py +84 -0
- apify_haystack-0.0.1a4/src/apify_haystack/examples/rag_with_crawled_website.py +111 -0
- apify_haystack-0.0.1a2/src/apify_haystack/__init__.py +0 -3
- {apify_haystack-0.0.1a2 → apify_haystack-0.0.1a4}/LICENSE +0 -0
- {apify_haystack-0.0.1a2 → apify_haystack-0.0.1a4}/README.md +0 -0
- {apify_haystack-0.0.1a2 → apify_haystack-0.0.1a4}/src/apify_haystack/main.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: apify_haystack
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.1a4
|
|
4
4
|
Summary: Apify-haystack integration
|
|
5
5
|
Home-page: https://apify.com
|
|
6
6
|
License: Apache-2.0
|
|
@@ -18,6 +18,9 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Requires-Dist: apify-client (>=1.7.1,<2.0.0)
|
|
22
|
+
Requires-Dist: haystack-ai (>=2.3.0,<3.0.0)
|
|
23
|
+
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
|
21
24
|
Project-URL: Changelog, https://github.com/apify/apify-haystack/blob/master/CHANGELOG.md
|
|
22
25
|
Project-URL: Documentation, https://github.com/apify/apify-haystack
|
|
23
26
|
Project-URL: Issue Tracker, https://github.com/apify/apify-haystack/issues
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "apify_haystack"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.1a4"
|
|
8
8
|
description = "Apify-haystack integration"
|
|
9
9
|
authors = ["Apify Technologies s.r.o. <support@apify.com>"]
|
|
10
10
|
homepage = "https://apify.com"
|
|
@@ -43,6 +43,9 @@ keywords = [
|
|
|
43
43
|
# https://github.com/apify/apify-sdk-python/pull/154.
|
|
44
44
|
[tool.poetry.dependencies]
|
|
45
45
|
python = "^3.9"
|
|
46
|
+
apify-client = "^1.7.1"
|
|
47
|
+
haystack-ai = "^2.3.0"
|
|
48
|
+
python-dotenv = "^1.0.1"
|
|
46
49
|
|
|
47
50
|
[tool.poetry.group.dev.dependencies]
|
|
48
51
|
build = "~1.2.0"
|
|
@@ -54,7 +57,7 @@ ruff = "~0.5.0"
|
|
|
54
57
|
setuptools = "~70.3.0" # setuptools are used by pytest, but not explicitly required
|
|
55
58
|
|
|
56
59
|
[tool.ruff]
|
|
57
|
-
line-length =
|
|
60
|
+
line-length = 120
|
|
58
61
|
|
|
59
62
|
[tool.ruff.lint]
|
|
60
63
|
select = ["ALL"]
|
|
@@ -110,6 +113,12 @@ indent-style = "space"
|
|
|
110
113
|
"T20", # flake8-print
|
|
111
114
|
"TRY301", # Abstract `raise` to an inner function
|
|
112
115
|
]
|
|
116
|
+
"**/{examples}/*" = [
|
|
117
|
+
"D", # Everything from the pydocstyle
|
|
118
|
+
"INP001", # File {filename} is part of an implicit namespace package, add an __init__.py
|
|
119
|
+
"T20", # flake8-print
|
|
120
|
+
]
|
|
121
|
+
|
|
113
122
|
|
|
114
123
|
[tool.ruff.lint.flake8-quotes]
|
|
115
124
|
docstring-quotes = "double"
|
|
@@ -139,6 +148,10 @@ warn_return_any = true
|
|
|
139
148
|
warn_unreachable = true
|
|
140
149
|
warn_unused_ignores = true
|
|
141
150
|
|
|
151
|
+
[[tool.mypy.overrides]]
|
|
152
|
+
module = "haystack.*"
|
|
153
|
+
ignore_missing_imports = true
|
|
154
|
+
|
|
142
155
|
[tool.mypy-sortedcollections]
|
|
143
156
|
ignore_missing_imports = true
|
|
144
157
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from importlib import metadata
|
|
2
|
+
|
|
3
|
+
__version__ = metadata.version("apify-haystack")
|
|
4
|
+
|
|
5
|
+
from apify_haystack.apify_dataset import ApifyDatasetFromActorCall, ApifyDatasetLoader
|
|
6
|
+
|
|
7
|
+
__all__ = ["ApifyDatasetLoader", "ApifyDatasetFromActorCall", "ApifyDatasetFromActorCall"]
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
from apify_client import ApifyClient
|
|
7
|
+
from haystack import component
|
|
8
|
+
from haystack.dataclasses import Document
|
|
9
|
+
|
|
10
|
+
from apify_haystack.constants import HAYSTACK_ATTRIBUTE_USER_AGENT
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@component
|
|
14
|
+
class ApifyDatasetLoader:
|
|
15
|
+
"""Load datasets produced by the `Apify Actors`.
|
|
16
|
+
|
|
17
|
+
Actors are serverless functions that can be used to scrape, crawl, and extract data from the web.
|
|
18
|
+
For details, see https://docs.apify.com/platform/integrations/haystack
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
.. code-block:: python
|
|
22
|
+
|
|
23
|
+
from haystack import Document
|
|
24
|
+
|
|
25
|
+
from apify_haystack import ApifyDatasetLoader
|
|
26
|
+
|
|
27
|
+
def dataset_mapping_function(dataset_item):
|
|
28
|
+
return Document(content=dataset_item.get("text"), meta={"url": dataset_item.get("url")})
|
|
29
|
+
|
|
30
|
+
loader = ApifyDatasetLoader(dataset_id=dataset_id, dataset_mapping_function=dataset_mapping_function)
|
|
31
|
+
dataset = loader.run()
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
dataset_id: str,
|
|
37
|
+
dataset_mapping_function: Callable[[dict], Document],
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Initialize the Apify dataset loader.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
dataset_id (str): The ID of the dataset on the Apify platform.
|
|
43
|
+
dataset_mapping_function (Callable): A function that takes a single (an Apify dataset item) and converts
|
|
44
|
+
it to an instance of the Document class.
|
|
45
|
+
"""
|
|
46
|
+
self.dataset_id = dataset_id
|
|
47
|
+
self.dataset_mapping_function = dataset_mapping_function
|
|
48
|
+
|
|
49
|
+
# Add a custom user-agent to the httpx client for attribution purposes
|
|
50
|
+
self.client = ApifyClient()
|
|
51
|
+
if httpx_client := getattr(self.client.http_client, "httpx_client", None):
|
|
52
|
+
httpx_client.headers["user-agent"] += f"; {HAYSTACK_ATTRIBUTE_USER_AGENT}"
|
|
53
|
+
|
|
54
|
+
@component.output_types(documents=list[Document])
|
|
55
|
+
def run(self): # type: ignore[no-untyped-def] # noqa: ANN201
|
|
56
|
+
"""Load datasets produced by the `Apify Actors`.
|
|
57
|
+
|
|
58
|
+
Type-hint note: the output is not type-hinted, otherwise linting complains
|
|
59
|
+
that `run` method is not haystack a component
|
|
60
|
+
"""
|
|
61
|
+
dataset_items = self.client.dataset(self.dataset_id).list_items(clean=True).items
|
|
62
|
+
return {"documents": list(map(self.dataset_mapping_function, dataset_items))}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@component
|
|
66
|
+
class ApifyDatasetFromActorCall:
|
|
67
|
+
"""Get Apify dataset by calling Apify Actor.
|
|
68
|
+
|
|
69
|
+
Perform call of Apify actor and load produced dataset.
|
|
70
|
+
For details, see https://docs.apify.com/platform/integrations/haystack
|
|
71
|
+
|
|
72
|
+
To use, you should have the ``apify-client`` python package installed,
|
|
73
|
+
and the environment variable ``APIFY_API_TOKEN`` set with your API key, or pass
|
|
74
|
+
`apify_api_token` as a named parameter to the constructor.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
actor_id: str,
|
|
80
|
+
dataset_mapping_function: Callable[[dict], Document],
|
|
81
|
+
run_input: dict,
|
|
82
|
+
*,
|
|
83
|
+
build: str | None = None,
|
|
84
|
+
memory_mbytes: int | None = None,
|
|
85
|
+
timeout_secs: int | None = None,
|
|
86
|
+
apify_api_token: str | None = None,
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Initialize the Apify Actor loader.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
actor_id (str): The ID or name of the Actor on the Apify platform.
|
|
92
|
+
run_input (Dict): The input object of the Actor that you're trying to run.
|
|
93
|
+
dataset_mapping_function (Callable): A function that takes a single
|
|
94
|
+
dictionary (an Apify dataset item) and converts it to an
|
|
95
|
+
instance of the Document class.
|
|
96
|
+
build (str, optional): Optionally specifies the actor build to run.
|
|
97
|
+
It can be either a build tag or build number.
|
|
98
|
+
memory_mbytes (int, optional): Optional memory limit for the run, in megabytes.
|
|
99
|
+
timeout_secs (int, optional): Optional timeout for the run, in seconds.
|
|
100
|
+
apify_api_token (str, optional): The API token for the Apify platform.
|
|
101
|
+
If not provided, it will be read from the environment
|
|
102
|
+
"""
|
|
103
|
+
self.actor_id = actor_id
|
|
104
|
+
self.dataset_mapping_function = dataset_mapping_function
|
|
105
|
+
self.run_input = run_input
|
|
106
|
+
self.build = build
|
|
107
|
+
self.memory_mbytes = memory_mbytes
|
|
108
|
+
self.timeout_secs = timeout_secs
|
|
109
|
+
|
|
110
|
+
if not (apify_api_token := apify_api_token or os.getenv("APIFY_API_TOKEN")):
|
|
111
|
+
raise ValueError(
|
|
112
|
+
"Apify API token not found. Please provide Apify API token when initializing "
|
|
113
|
+
"ApifyDatasetCall, or set the environment variable APIFY_API_TOKEN."
|
|
114
|
+
)
|
|
115
|
+
# Add a custom user-agent to the httpx client for attribution purposes
|
|
116
|
+
self.client = ApifyClient(apify_api_token)
|
|
117
|
+
|
|
118
|
+
if httpx_client := getattr(self.client.http_client, "httpx_client", None):
|
|
119
|
+
httpx_client.headers["user-agent"] += f"; {HAYSTACK_ATTRIBUTE_USER_AGENT}"
|
|
120
|
+
|
|
121
|
+
@component.output_types(documents=list[Document])
|
|
122
|
+
def run(self): # type: ignore[no-untyped-def] # noqa: ANN201
|
|
123
|
+
"""Run an Actor on the Apify platform and wait for results to be ready.
|
|
124
|
+
|
|
125
|
+
Type-hint note: the output is not type-hinted, otherwise linting complains
|
|
126
|
+
that `run` method is not haystack a component
|
|
127
|
+
"""
|
|
128
|
+
if not (
|
|
129
|
+
actor_call := self.client.actor(self.actor_id).call(
|
|
130
|
+
run_input=self.run_input,
|
|
131
|
+
build=self.build,
|
|
132
|
+
memory_mbytes=self.memory_mbytes,
|
|
133
|
+
timeout_secs=self.timeout_secs,
|
|
134
|
+
)
|
|
135
|
+
):
|
|
136
|
+
raise ValueError(f"No response found in the actor {self.actor_id} call")
|
|
137
|
+
|
|
138
|
+
if not (dataset_id := actor_call.get("defaultDatasetId")):
|
|
139
|
+
raise ValueError(f"No dataset found in the actor {self.actor_id} call response.")
|
|
140
|
+
|
|
141
|
+
loader = ApifyDatasetLoader(
|
|
142
|
+
dataset_id=dataset_id,
|
|
143
|
+
dataset_mapping_function=self.dataset_mapping_function,
|
|
144
|
+
)
|
|
145
|
+
return loader.run()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@component
|
|
149
|
+
class ApifyDatasetFromTaskCall:
|
|
150
|
+
"""Get Apify dataset by calling Apify Task.
|
|
151
|
+
|
|
152
|
+
Perform call of Apify task and load produced dataset.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
task_id: str,
|
|
158
|
+
dataset_mapping_function: Callable[[dict], Document],
|
|
159
|
+
task_input: dict,
|
|
160
|
+
*,
|
|
161
|
+
build: str | None = None,
|
|
162
|
+
memory_mbytes: int | None = None,
|
|
163
|
+
timeout_secs: int | None = None,
|
|
164
|
+
apify_api_token: str | None = None,
|
|
165
|
+
) -> None:
|
|
166
|
+
"""Initialize the Apify Actor loader.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
task_id (str): The ID or name of the Actor on the Apify platform.
|
|
170
|
+
task_input (Dict): The input object of the Actor that you're trying to run.
|
|
171
|
+
dataset_mapping_function (Callable): A function that takes a single
|
|
172
|
+
dictionary (an Apify dataset item) and converts it to an
|
|
173
|
+
instance of the Document class.
|
|
174
|
+
build (str, optional): Optionally specifies the actor build to run.
|
|
175
|
+
It can be either a build tag or build number.
|
|
176
|
+
memory_mbytes (int, optional): Optional memory limit for the run, in megabytes.
|
|
177
|
+
timeout_secs (int, optional): Optional timeout for the run, in seconds.
|
|
178
|
+
apify_api_token (str, optional): The API token for the Apify platform.
|
|
179
|
+
If not provided, it will be read from the environment
|
|
180
|
+
"""
|
|
181
|
+
self.task_id = task_id
|
|
182
|
+
self.dataset_mapping_function = dataset_mapping_function
|
|
183
|
+
self.task_input = task_input
|
|
184
|
+
self.build = build
|
|
185
|
+
self.memory_mbytes = memory_mbytes
|
|
186
|
+
self.timeout_secs = timeout_secs
|
|
187
|
+
|
|
188
|
+
if not (apify_api_token := apify_api_token or os.getenv("APIFY_API_TOKEN")):
|
|
189
|
+
raise ValueError(
|
|
190
|
+
"Apify API token not found. Please provide Apify API token when initializing "
|
|
191
|
+
"ApifyDatasetCall, or set the environment variable APIFY_API_TOKEN."
|
|
192
|
+
)
|
|
193
|
+
# Add a custom user-agent to the httpx client for attribution purposes
|
|
194
|
+
self.client = ApifyClient(apify_api_token)
|
|
195
|
+
|
|
196
|
+
if httpx_client := getattr(self.client.http_client, "httpx_client", None):
|
|
197
|
+
httpx_client.headers["user-agent"] += f"; {HAYSTACK_ATTRIBUTE_USER_AGENT}"
|
|
198
|
+
|
|
199
|
+
@component.output_types(documents=list[Document])
|
|
200
|
+
def run(self): # type: ignore[no-untyped-def] # noqa: ANN201
|
|
201
|
+
"""Run an Actor on the Apify platform and wait for results to be ready.
|
|
202
|
+
|
|
203
|
+
Type-hint note: the output is not type-hinted, otherwise linting complains
|
|
204
|
+
that `run` method is not haystack a component
|
|
205
|
+
"""
|
|
206
|
+
if not (
|
|
207
|
+
actor_call := self.client.task(self.task_id).call(
|
|
208
|
+
task_input=self.task_input,
|
|
209
|
+
build=self.build,
|
|
210
|
+
memory_mbytes=self.memory_mbytes,
|
|
211
|
+
timeout_secs=self.timeout_secs,
|
|
212
|
+
)
|
|
213
|
+
):
|
|
214
|
+
raise ValueError(f"No response found in the actor {self.task_id} call")
|
|
215
|
+
|
|
216
|
+
if not (dataset_id := actor_call.get("defaultDatasetId")):
|
|
217
|
+
raise ValueError(f"No dataset found in the actor {self.task_id} call response.")
|
|
218
|
+
|
|
219
|
+
loader = ApifyDatasetLoader(
|
|
220
|
+
dataset_id=dataset_id,
|
|
221
|
+
dataset_mapping_function=self.dataset_mapping_function,
|
|
222
|
+
)
|
|
223
|
+
return loader.run()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Call Apify Actor and load a dataset to convert it to Haystack Documents.
|
|
3
|
+
|
|
4
|
+
This script demonstrates how to use Apify's Website Content Crawler to scrape text content from a specified website
|
|
5
|
+
and convert the scraped data into Haystack Documents.
|
|
6
|
+
|
|
7
|
+
For example, if you want to crawl and get text content from https://haystack.deepset.ai/, you can use this script
|
|
8
|
+
to call the Apify actor and retrieve the data in a structured format.
|
|
9
|
+
|
|
10
|
+
The script should produce the following output (an example of a single Document):
|
|
11
|
+
......
|
|
12
|
+
Document(id=a617d376*****, content: 'Introduction to Haystack 2.x)
|
|
13
|
+
Haystack is an open-source framework fo...', meta: {'url': 'https://docs.haystack.deepset.ai/docs/intro'}
|
|
14
|
+
.....
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from dotenv import load_dotenv
|
|
18
|
+
from haystack import Document
|
|
19
|
+
|
|
20
|
+
from apify_haystack import ApifyDatasetFromActorCall
|
|
21
|
+
|
|
22
|
+
# Sey APIFY-API-TOKEN here or load it from .env file
|
|
23
|
+
apify_token = "" or load_dotenv()
|
|
24
|
+
|
|
25
|
+
actor_id = "apify/website-content-crawler"
|
|
26
|
+
run_input = {
|
|
27
|
+
"maxCrawlPages": 3, # limit the number of pages to crawl
|
|
28
|
+
"startUrls": [{"url": "https://haystack.deepset.ai/"}],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def dataset_mapping_function(dataset_item: dict) -> Document:
|
|
33
|
+
return Document(content=dataset_item.get("text"), meta={"url": dataset_item.get("url")})
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
actor = ApifyDatasetFromActorCall(
|
|
37
|
+
actor_id=actor_id, run_input=run_input, dataset_mapping_function=dataset_mapping_function
|
|
38
|
+
)
|
|
39
|
+
print(f"Calling Apify actor {actor_id} ... crawling will take some time ...")
|
|
40
|
+
dataset = actor.run().get("documents")
|
|
41
|
+
|
|
42
|
+
print(f"Loaded {len(dataset)} documents from the Apify Actor {actor_id}:")
|
|
43
|
+
for d in dataset:
|
|
44
|
+
print(d)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Load a dataset from Apify and convert it to Haystack Documents.
|
|
3
|
+
|
|
4
|
+
This script demonstrates how to load a dataset from Apify and convert it into Haystack Documents.
|
|
5
|
+
Assuming you have run an Apify actor that scraped web pages from https://docs.haystack.deepset.ai/,
|
|
6
|
+
you can use the dataset ID to load the dataset and transform it into a structured format suitable for Haystack.
|
|
7
|
+
|
|
8
|
+
The script should produce the following output (an example of a single Document):
|
|
9
|
+
......
|
|
10
|
+
Document(id=a617d376*****, content: 'Introduction to Haystack 2.x)
|
|
11
|
+
Haystack is an open-source framework fo...', meta: {'url': 'https://docs.haystack.deepset.ai/docs/intro'}
|
|
12
|
+
.....
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from haystack import Document
|
|
16
|
+
|
|
17
|
+
from apify_haystack import ApifyDatasetLoader
|
|
18
|
+
|
|
19
|
+
dataset_id = "YOUR-DATASET-ID"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def dataset_mapping_function(dataset_item: dict) -> Document:
|
|
23
|
+
return Document(content=dataset_item.get("text"), meta={"url": dataset_item.get("url")})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
loader = ApifyDatasetLoader(dataset_id=dataset_id, dataset_mapping_function=dataset_mapping_function)
|
|
27
|
+
dataset = loader.run().get("documents")
|
|
28
|
+
|
|
29
|
+
print(f"Loaded {len(dataset)} documents from Apify dataset {dataset_id}:")
|
|
30
|
+
for d in dataset:
|
|
31
|
+
print(d)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This script demonstrates how to extract content from a website using the Apify actor: Website Content Crawler.
|
|
3
|
+
The content is then cleaned, split into smaller chunks, embedded, and stored in the InMemoryDocumentStore.
|
|
4
|
+
|
|
5
|
+
After the pipeline is executed, the documents are retrieved from the document store using BM25 retrieval.
|
|
6
|
+
|
|
7
|
+
The script should produce the following output (an example of a single Document):
|
|
8
|
+
......
|
|
9
|
+
Document(id=15a9f391a9f14871675826af8767bd3d2c81d7de1892fd9288b6be0dac26ea89, content: 'Get Started | Haystack
|
|
10
|
+
Haystack is an open-source Python framework that helps developers build LLM-p...',
|
|
11
|
+
meta: {'url': 'https://haystack.deepset.ai/overview/quick-start',
|
|
12
|
+
'source_id': '2a7263cb668b8353749cc990f487a3feb7a7d7f3abe3e3af3a69772cdecc8200', 'page_number': 1,
|
|
13
|
+
'split_id': 0, 'split_idx_start': 0, '_split_overlap': ......
|
|
14
|
+
.....
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
from dotenv import load_dotenv
|
|
20
|
+
from haystack import Document, Pipeline
|
|
21
|
+
from haystack.components.embedders import OpenAIDocumentEmbedder
|
|
22
|
+
from haystack.components.preprocessors import DocumentCleaner, DocumentSplitter
|
|
23
|
+
from haystack.components.writers import DocumentWriter
|
|
24
|
+
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
|
25
|
+
from haystack.utils.auth import Secret
|
|
26
|
+
|
|
27
|
+
from apify_haystack import ApifyDatasetFromActorCall
|
|
28
|
+
|
|
29
|
+
load_dotenv()
|
|
30
|
+
|
|
31
|
+
# Set APIFY-API-TOKEN here or use it from .env file
|
|
32
|
+
apify_token = "" or os.getenv("APIFY_API_TOKEN")
|
|
33
|
+
openai_api_key = "" or os.getenv("OPENAI_API_KEY")
|
|
34
|
+
|
|
35
|
+
actor_id = "apify/website-content-crawler"
|
|
36
|
+
run_input = {
|
|
37
|
+
"maxCrawlPages": 3, # limit the number of pages to crawl
|
|
38
|
+
"startUrls": [{"url": "https://haystack.deepset.ai/"}],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def dataset_mapping_function(dataset_item: dict) -> Document:
|
|
43
|
+
return Document(content=dataset_item.get("text"), meta={"url": dataset_item.get("url")})
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
document_loader = ApifyDatasetFromActorCall(
|
|
47
|
+
actor_id=actor_id, run_input=run_input, dataset_mapping_function=dataset_mapping_function
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
document_store = InMemoryDocumentStore()
|
|
51
|
+
print(f"Initialized InMemoryDocumentStore with {document_store.count_documents()} documents")
|
|
52
|
+
|
|
53
|
+
document_cleaner = DocumentCleaner()
|
|
54
|
+
document_splitter = DocumentSplitter(split_by="word", split_length=150, split_overlap=50)
|
|
55
|
+
document_embedder = OpenAIDocumentEmbedder(api_key=Secret.from_token(openai_api_key))
|
|
56
|
+
document_writer = DocumentWriter(document_store)
|
|
57
|
+
|
|
58
|
+
pipe = Pipeline()
|
|
59
|
+
pipe.add_component("document_loader", document_loader)
|
|
60
|
+
pipe.add_component("document_cleaner", document_cleaner)
|
|
61
|
+
pipe.add_component("document_splitter", document_splitter)
|
|
62
|
+
pipe.add_component("document_embedder", document_embedder)
|
|
63
|
+
pipe.add_component("document_writer", document_writer)
|
|
64
|
+
|
|
65
|
+
pipe.connect("document_loader", "document_cleaner")
|
|
66
|
+
pipe.connect("document_cleaner", "document_splitter")
|
|
67
|
+
pipe.connect("document_splitter", "document_embedder")
|
|
68
|
+
pipe.connect("document_embedder", "document_writer")
|
|
69
|
+
|
|
70
|
+
print(
|
|
71
|
+
"Running pipeline the Apify document_loader -> document_cleaner -> document_splitter "
|
|
72
|
+
"-> document_embedder -> document_writer"
|
|
73
|
+
)
|
|
74
|
+
print("Crawling will take some time ...")
|
|
75
|
+
print("You can visit https://console.apify.com/actors/runs to monitor the progress")
|
|
76
|
+
|
|
77
|
+
pipe.run({"document_loader": {}})
|
|
78
|
+
|
|
79
|
+
print(f"Added {document_store.count_documents()} to vector from Website Content Crawler")
|
|
80
|
+
|
|
81
|
+
print("\nRetrieving documents from the document store: query='Haystack'")
|
|
82
|
+
print("query='Haystack'\n")
|
|
83
|
+
for doc in document_store.bm25_retrieval("Haystack", top_k=1):
|
|
84
|
+
print(doc)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Retrieval-Augmented Generation (RAG)
|
|
3
|
+
|
|
4
|
+
This script demonstrates how to extract text content from a website, store it in a vector database, and use
|
|
5
|
+
it for question answering.
|
|
6
|
+
|
|
7
|
+
Steps involved:
|
|
8
|
+
- Load environment variables, including API keys.
|
|
9
|
+
- Define the Apify actor and its input parameters for web scraping.
|
|
10
|
+
- Initialize the Apify dataset loader to fetch and process the dataset.
|
|
11
|
+
- Set up the components for the RAG pipeline:
|
|
12
|
+
- Load documents from Apify and store their embeddings in the document store.
|
|
13
|
+
- Define a prompt template for the question-answering task.
|
|
14
|
+
- Build and connect the components in the pipeline.
|
|
15
|
+
- Run the pipeline to answer a sample question and print the response.
|
|
16
|
+
|
|
17
|
+
Expected output:
|
|
18
|
+
question: "What is haystack?"
|
|
19
|
+
......
|
|
20
|
+
Haystack is an open-source framework for building production-ready LLM applications
|
|
21
|
+
......
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
|
|
26
|
+
from dotenv import load_dotenv
|
|
27
|
+
from haystack import Document, Pipeline
|
|
28
|
+
from haystack.components.builders import PromptBuilder
|
|
29
|
+
from haystack.components.embedders import OpenAIDocumentEmbedder, OpenAITextEmbedder
|
|
30
|
+
from haystack.components.generators import OpenAIGenerator
|
|
31
|
+
from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever
|
|
32
|
+
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
|
33
|
+
from haystack.utils.auth import Secret
|
|
34
|
+
|
|
35
|
+
from apify_haystack import ApifyDatasetFromActorCall
|
|
36
|
+
|
|
37
|
+
# Set APIFY-API-TOKEN here or use it from .env file
|
|
38
|
+
apify_token = "" or os.getenv("APIFY_API_TOKEN")
|
|
39
|
+
openai_api_key = "" or os.getenv("OPENAI_API_KEY")
|
|
40
|
+
load_dotenv()
|
|
41
|
+
|
|
42
|
+
actor_id = "apify/website-content-crawler"
|
|
43
|
+
run_input = {
|
|
44
|
+
"maxCrawlPages": 1, # limit the number of pages to crawl
|
|
45
|
+
"startUrls": [{"url": "https://haystack.deepset.ai/"}],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def dataset_mapping_function(dataset_item: dict) -> Document:
|
|
50
|
+
return Document(content=dataset_item.get("text"), meta={"url": dataset_item.get("url")})
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
apify_dataset_loader = ApifyDatasetFromActorCall(
|
|
54
|
+
actor_id=actor_id, run_input=run_input, dataset_mapping_function=dataset_mapping_function
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Components
|
|
58
|
+
document_store = InMemoryDocumentStore()
|
|
59
|
+
|
|
60
|
+
docs_embedder = OpenAIDocumentEmbedder(api_key=Secret.from_token(openai_api_key))
|
|
61
|
+
text_embedder = OpenAITextEmbedder(api_key=Secret.from_token(openai_api_key))
|
|
62
|
+
retriever = InMemoryEmbeddingRetriever(document_store)
|
|
63
|
+
generator = OpenAIGenerator(model="gpt-3.5-turbo")
|
|
64
|
+
|
|
65
|
+
# Load documents from Apify
|
|
66
|
+
docs = apify_dataset_loader.run()
|
|
67
|
+
embeddings = docs_embedder.run(docs.get("documents"))
|
|
68
|
+
document_store.write_documents(embeddings["documents"])
|
|
69
|
+
|
|
70
|
+
template = """
|
|
71
|
+
Given the following information, answer the question.
|
|
72
|
+
|
|
73
|
+
Context:
|
|
74
|
+
{% for document in documents %}
|
|
75
|
+
{{ document.content }}
|
|
76
|
+
{% endfor %}
|
|
77
|
+
|
|
78
|
+
Question: {{question}}
|
|
79
|
+
Answer:
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
prompt_builder = PromptBuilder(template=template)
|
|
83
|
+
|
|
84
|
+
# Add components to your pipeline
|
|
85
|
+
pipe = Pipeline()
|
|
86
|
+
pipe.add_component("embedder", text_embedder)
|
|
87
|
+
pipe.add_component("retriever", retriever)
|
|
88
|
+
pipe.add_component("prompt_builder", prompt_builder)
|
|
89
|
+
pipe.add_component("llm", generator)
|
|
90
|
+
|
|
91
|
+
# Now, connect the components to each other
|
|
92
|
+
pipe.connect("embedder.embedding", "retriever.query_embedding")
|
|
93
|
+
pipe.connect("retriever", "prompt_builder.documents")
|
|
94
|
+
pipe.connect("prompt_builder", "llm")
|
|
95
|
+
|
|
96
|
+
question = "What is haystack?"
|
|
97
|
+
|
|
98
|
+
response = pipe.run({"embedder": {"text": question}, "prompt_builder": {"question": question}})
|
|
99
|
+
|
|
100
|
+
print(f"question: {question}")
|
|
101
|
+
print(f"answer: {response['llm']['replies'][0]}")
|
|
102
|
+
|
|
103
|
+
# Other questions
|
|
104
|
+
examples = [
|
|
105
|
+
"Who created Haystack AI?",
|
|
106
|
+
"Are there any upcoming events or community talks?",
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
for example in examples:
|
|
110
|
+
response = pipe.run({"embedder": {"text": example}, "prompt_builder": {"question": example}})
|
|
111
|
+
print(response["llm"]["replies"][0])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|