Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse files- .env.tpl +10 -0
- .gitignore +164 -0
- LICENSE +21 -0
- README.md +160 -8
- ask.py +1020 -0
- demos/search_and_answer.md +57 -0
- demos/search_and_extract.md +200 -0
- demos/search_on_site_and_date.md +44 -0
- instructions/extract_example.txt +3 -0
- instructions/links.txt +4 -0
- requirements.txt +9 -0
.env.tpl
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# right now we use Google search API
|
2 |
+
SEARCH_API_KEY=your-google-search-api-key
|
3 |
+
SEARCH_PROJECT_KEY=your-google-cx-key
|
4 |
+
|
5 |
+
# right now we use OpenAI API
|
6 |
+
LLM_API_KEY=your-openai-api-key
|
7 |
+
|
8 |
+
# Run and share Gradio UI
|
9 |
+
RUN_GRADIO_UI=False
|
10 |
+
SHARE_GRADIO_UI=False
|
.gitignore
ADDED
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
|
6 |
+
# C extensions
|
7 |
+
*.so
|
8 |
+
|
9 |
+
# Distribution / packaging
|
10 |
+
.Python
|
11 |
+
build/
|
12 |
+
develop-eggs/
|
13 |
+
dist/
|
14 |
+
downloads/
|
15 |
+
eggs/
|
16 |
+
.eggs/
|
17 |
+
lib/
|
18 |
+
lib64/
|
19 |
+
parts/
|
20 |
+
sdist/
|
21 |
+
var/
|
22 |
+
wheels/
|
23 |
+
share/python-wheels/
|
24 |
+
*.egg-info/
|
25 |
+
.installed.cfg
|
26 |
+
*.egg
|
27 |
+
MANIFEST
|
28 |
+
|
29 |
+
# PyInstaller
|
30 |
+
# Usually these files are written by a python script from a template
|
31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
32 |
+
*.manifest
|
33 |
+
*.spec
|
34 |
+
|
35 |
+
# Installer logs
|
36 |
+
pip-log.txt
|
37 |
+
pip-delete-this-directory.txt
|
38 |
+
|
39 |
+
# Unit test / coverage reports
|
40 |
+
htmlcov/
|
41 |
+
.tox/
|
42 |
+
.nox/
|
43 |
+
.coverage
|
44 |
+
.coverage.*
|
45 |
+
.cache
|
46 |
+
nosetests.xml
|
47 |
+
coverage.xml
|
48 |
+
*.cover
|
49 |
+
*.py,cover
|
50 |
+
.hypothesis/
|
51 |
+
.pytest_cache/
|
52 |
+
cover/
|
53 |
+
|
54 |
+
# Translations
|
55 |
+
*.mo
|
56 |
+
*.pot
|
57 |
+
|
58 |
+
# Django stuff:
|
59 |
+
*.log
|
60 |
+
local_settings.py
|
61 |
+
db.sqlite3
|
62 |
+
db.sqlite3-journal
|
63 |
+
|
64 |
+
# Flask stuff:
|
65 |
+
instance/
|
66 |
+
.webassets-cache
|
67 |
+
|
68 |
+
# Scrapy stuff:
|
69 |
+
.scrapy
|
70 |
+
|
71 |
+
# Sphinx documentation
|
72 |
+
docs/_build/
|
73 |
+
|
74 |
+
# PyBuilder
|
75 |
+
.pybuilder/
|
76 |
+
target/
|
77 |
+
|
78 |
+
# Jupyter Notebook
|
79 |
+
.ipynb_checkpoints
|
80 |
+
|
81 |
+
# IPython
|
82 |
+
profile_default/
|
83 |
+
ipython_config.py
|
84 |
+
|
85 |
+
# pyenv
|
86 |
+
# For a library or package, you might want to ignore these files since the code is
|
87 |
+
# intended to run in multiple environments; otherwise, check them in:
|
88 |
+
# .python-version
|
89 |
+
|
90 |
+
# pipenv
|
91 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
92 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
93 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
94 |
+
# install all needed dependencies.
|
95 |
+
#Pipfile.lock
|
96 |
+
|
97 |
+
# poetry
|
98 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
99 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
100 |
+
# commonly ignored for libraries.
|
101 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
102 |
+
#poetry.lock
|
103 |
+
|
104 |
+
# pdm
|
105 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
106 |
+
#pdm.lock
|
107 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
108 |
+
# in version control.
|
109 |
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
110 |
+
.pdm.toml
|
111 |
+
.pdm-python
|
112 |
+
.pdm-build/
|
113 |
+
|
114 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
115 |
+
__pypackages__/
|
116 |
+
|
117 |
+
# Celery stuff
|
118 |
+
celerybeat-schedule
|
119 |
+
celerybeat.pid
|
120 |
+
|
121 |
+
# SageMath parsed files
|
122 |
+
*.sage.py
|
123 |
+
|
124 |
+
# Environments
|
125 |
+
.env
|
126 |
+
.venv
|
127 |
+
env/
|
128 |
+
venv/
|
129 |
+
ENV/
|
130 |
+
env.bak/
|
131 |
+
venv.bak/
|
132 |
+
|
133 |
+
# Spyder project settings
|
134 |
+
.spyderproject
|
135 |
+
.spyproject
|
136 |
+
|
137 |
+
# Rope project settings
|
138 |
+
.ropeproject
|
139 |
+
|
140 |
+
# mkdocs documentation
|
141 |
+
/site
|
142 |
+
|
143 |
+
# mypy
|
144 |
+
.mypy_cache/
|
145 |
+
.dmypy.json
|
146 |
+
dmypy.json
|
147 |
+
|
148 |
+
# Pyre type checker
|
149 |
+
.pyre/
|
150 |
+
|
151 |
+
# pytype static type analyzer
|
152 |
+
.pytype/
|
153 |
+
|
154 |
+
# Cython debug symbols
|
155 |
+
cython_debug/
|
156 |
+
|
157 |
+
# PyCharm
|
158 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
159 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
160 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
161 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
162 |
+
#.idea/
|
163 |
+
|
164 |
+
.gradio
|
LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2024 pengfeng
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
README.md
CHANGED
@@ -1,12 +1,164 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
|
4 |
-
colorFrom: yellow
|
5 |
-
colorTo: gray
|
6 |
sdk: gradio
|
7 |
-
sdk_version: 5.
|
8 |
-
app_file: app.py
|
9 |
-
pinned: false
|
10 |
---
|
|
|
11 |
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
---
|
2 |
+
title: ask2.py
|
3 |
+
app_file: ask.py
|
|
|
|
|
4 |
sdk: gradio
|
5 |
+
sdk_version: 5.3.0
|
|
|
|
|
6 |
---
|
7 |
+
# ask.py
|
8 |
|
9 |
+
[![License](https://img.shields.io/github/license/pengfeng/ask.py)](LICENSE)
|
10 |
+
|
11 |
+
A single Python program to implement the search-extract-summarize flow, similar to AI search
|
12 |
+
engines such as Perplexity.
|
13 |
+
|
14 |
+
> [!NOTE]
|
15 |
+
> Our main goal is to illustrate the basic concepts of AI search engines with the raw constructs.
|
16 |
+
> Performance or scalability is not in the scope of this program.
|
17 |
+
|
18 |
+
> [UPDATE]
|
19 |
+
>
|
20 |
+
> - 2024-10-28: add extract function as a new output mode
|
21 |
+
> - 2024-10-25: add hybrid search demo using DuckDB full-text search
|
22 |
+
> - 2024-10-22: add GradIO integation
|
23 |
+
> - 2024-10-21: use DuckDB for the vector search and use API for embedding
|
24 |
+
> - 2024-10-20: allow to specify a list of input urls
|
25 |
+
> - 2024-10-18: output-language and output-length parameters for LLM
|
26 |
+
> - 2024-10-18: date-restrict and target-site parameters for seach
|
27 |
+
|
28 |
+
## The search-extract-summarize flow
|
29 |
+
|
30 |
+
Given a query, the program will
|
31 |
+
|
32 |
+
- search Google for the top 10 web pages
|
33 |
+
- crawl and scape the pages for their text content
|
34 |
+
- chunk the text content into chunks and save them into a vectordb
|
35 |
+
- perform a vector search with the query and find the top 10 matched chunks
|
36 |
+
- [Optional] search using full-text search and combine the results with the vector search
|
37 |
+
- [Optional] use a reranker to re-rank the top chunks
|
38 |
+
- use the top chunks as the context to ask an LLM to generate the answer
|
39 |
+
- output the answer with the references
|
40 |
+
|
41 |
+
Of course this flow is a very simplified version of the real AI search engines, but it is a good
|
42 |
+
starting point to understand the basic concepts.
|
43 |
+
|
44 |
+
One benefit is that we can manipulate the search function and output format.
|
45 |
+
|
46 |
+
For example, we can:
|
47 |
+
|
48 |
+
- search with date-restrict to only retrieve the latest information.
|
49 |
+
- search within a target-site to only create the answer from the contents from it.
|
50 |
+
- ask LLM to use a specific language to answer the question.
|
51 |
+
- ask LLM to answer with a specific length.
|
52 |
+
- crawl a specific list of urls and answer based on those contents only.
|
53 |
+
|
54 |
+
## Quick start
|
55 |
+
|
56 |
+
```bash
|
57 |
+
# recommend to use Python 3.10 or later and use venv or conda to create a virtual environment
|
58 |
+
pip install -r requirements.txt
|
59 |
+
|
60 |
+
# modify .env file to set the API keys or export them as environment variables as below
|
61 |
+
|
62 |
+
# right now we use Google search API
|
63 |
+
export SEARCH_API_KEY="your-google-search-api-key"
|
64 |
+
export SEARCH_PROJECT_KEY="your-google-cx-key"
|
65 |
+
|
66 |
+
# right now we use OpenAI API
|
67 |
+
export LLM_API_KEY="your-openai-api-key"
|
68 |
+
|
69 |
+
# run the program
|
70 |
+
python ask.py -q "What is an LLM agent?"
|
71 |
+
|
72 |
+
# we can specify more parameters to control the behavior such as date_restrict and target_site
|
73 |
+
python ask.py --help
|
74 |
+
Usage: ask.py [OPTIONS]
|
75 |
+
|
76 |
+
Search web for the query and summarize the results.
|
77 |
+
|
78 |
+
Options:
|
79 |
+
-q, --query TEXT Query to search
|
80 |
+
-o, --output-mode [answer|extract]
|
81 |
+
Output mode for the answer, default is a
|
82 |
+
simple answer
|
83 |
+
-d, --date-restrict INTEGER Restrict search results to a specific date
|
84 |
+
range, default is no restriction
|
85 |
+
-s, --target-site TEXT Restrict search results to a specific site,
|
86 |
+
default is no restriction
|
87 |
+
--output-language TEXT Output language for the answer
|
88 |
+
--output-length INTEGER Output length for the answer
|
89 |
+
--url-list-file TEXT Instead of doing web search, scrape the
|
90 |
+
target URL list and answer the query based
|
91 |
+
on the content
|
92 |
+
--extract-schema-file TEXT Pydantic schema for the extract mode
|
93 |
+
-m, --inference-model-name TEXT
|
94 |
+
Model name to use for inference
|
95 |
+
--hybrid-search Use hybrid search mode with both vector
|
96 |
+
search and full-text search
|
97 |
+
--web-ui Launch the web interface
|
98 |
+
-l, --log-level [DEBUG|INFO|WARNING|ERROR]
|
99 |
+
Set the logging level [default: INFO]
|
100 |
+
--help Show this message and exit.
|
101 |
+
```
|
102 |
+
|
103 |
+
## Libraries and APIs used
|
104 |
+
|
105 |
+
- [Google Search API](https://developers.google.com/custom-search/v1/overview)
|
106 |
+
- [OpenAI API](https://beta.openai.com/docs/api-reference/completions/create)
|
107 |
+
- [Jinja2](https://jinja.palletsprojects.com/en/3.0.x/)
|
108 |
+
- [bs4](https://www.crummy.com/software/BeautifulSoup/bs4/doc/)
|
109 |
+
- [DuckDB](https://github.com/duckdb/duckdb)
|
110 |
+
- [GradIO](https://github.com/gradio-app/gradio)
|
111 |
+
|
112 |
+
## GradIO Deployment
|
113 |
+
|
114 |
+
> [!NOTE]
|
115 |
+
> Original GradIO app-sharing document [here](https://www.gradio.app/guides/sharing-your-app).
|
116 |
+
> We have a running example [here](https://huggingface.co/spaces/leettools/AskPy).
|
117 |
+
|
118 |
+
### Quick test and sharing
|
119 |
+
|
120 |
+
You can run the program with `--web-ui` option to launch the web interface and check it locally.
|
121 |
+
|
122 |
+
```bash
|
123 |
+
python ask.py --web-ui
|
124 |
+
* Running on local URL: http://127.0.0.1:7860
|
125 |
+
|
126 |
+
# you can also specify SHARE_GRADIO_UI to run a sharable UI through GradIO
|
127 |
+
export SHARE_GRADIO_UI=True
|
128 |
+
python ask.py --web-ui
|
129 |
+
* Running on local URL: http://127.0.0.1:7860
|
130 |
+
* Running on public URL: https://77c277af0330326587.gradio.live
|
131 |
+
```
|
132 |
+
|
133 |
+
### To share a more permanent link using HuggingFace Space
|
134 |
+
|
135 |
+
- First, you need to [create a free HuggingFace account](https://huggingface.co/welcome).
|
136 |
+
- Then in your [settings/token page](https://huggingface.co/settings/tokens), create a new token with Write permissions.
|
137 |
+
- In your terminal, run the following commands in you app directory to deploy your program to
|
138 |
+
HuggingFace Space:
|
139 |
+
|
140 |
+
```bash
|
141 |
+
pip install gradio
|
142 |
+
gradio deploy
|
143 |
+
# You will be prompted to enter your HuggingFace token
|
144 |
+
```
|
145 |
+
|
146 |
+
After the deployment, the app should be on https://huggingface.co/spaces/your_username/AskPy
|
147 |
+
|
148 |
+
Now you need to go to the settings page to add some variables and secrets https://huggingface.co/spaces/your_username/AskPy/settings
|
149 |
+
|
150 |
+
- variable: RUN_GRADIO_UI=True
|
151 |
+
- variable: SHARE_GRADIO_UI=True
|
152 |
+
- secret: SEARCH_API_KEY=<YOUR_SEARCH_API_KEY>
|
153 |
+
- secret: SEARCH_PROJECT_KEY=<YOUR_SEARCH_PROJECT_KEY>
|
154 |
+
- sercet: LLM_API_KEY=<YOUR_LLM_API_KEY>
|
155 |
+
|
156 |
+
Now you can use the HuggingFace space app to run your queries.
|
157 |
+
|
158 |
+
![image](https://github.com/user-attachments/assets/0483e6a2-75d7-4fbd-813f-bfa13839c836)
|
159 |
+
|
160 |
+
## Use Cases
|
161 |
+
|
162 |
+
- [Search like Perplexity](demos/search_and_answer.md)
|
163 |
+
- [Only use the latest information from a specific site](demos/search_on_site_and_date.md)
|
164 |
+
- [Extract information from web search results](demos/search_and_extract.md)
|
ask.py
ADDED
@@ -0,0 +1,1020 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import csv
|
2 |
+
import io
|
3 |
+
import json
|
4 |
+
import logging
|
5 |
+
import os
|
6 |
+
import queue
|
7 |
+
import urllib.parse
|
8 |
+
from concurrent.futures import ThreadPoolExecutor
|
9 |
+
from datetime import datetime
|
10 |
+
from enum import Enum
|
11 |
+
from functools import partial
|
12 |
+
from queue import Queue
|
13 |
+
from typing import Any, Dict, Generator, List, Optional, Tuple, TypeVar
|
14 |
+
|
15 |
+
import click
|
16 |
+
import duckdb
|
17 |
+
import gradio as gr
|
18 |
+
import requests
|
19 |
+
from bs4 import BeautifulSoup
|
20 |
+
from dotenv import load_dotenv
|
21 |
+
from jinja2 import BaseLoader, Environment
|
22 |
+
from openai import OpenAI
|
23 |
+
from pydantic import BaseModel, create_model
|
24 |
+
|
25 |
+
TypeVar_BaseModel = TypeVar("TypeVar_BaseModel", bound=BaseModel)
|
26 |
+
|
27 |
+
|
28 |
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
29 |
+
default_env_file = os.path.abspath(os.path.join(script_dir, ".env"))
|
30 |
+
|
31 |
+
|
32 |
+
class OutputMode(str, Enum):
|
33 |
+
answer = "answer"
|
34 |
+
extract = "extract"
|
35 |
+
|
36 |
+
|
37 |
+
class AskSettings(BaseModel):
|
38 |
+
date_restrict: int
|
39 |
+
target_site: str
|
40 |
+
output_language: str
|
41 |
+
output_length: int
|
42 |
+
url_list: List[str]
|
43 |
+
inference_model_name: str
|
44 |
+
hybrid_search: bool
|
45 |
+
output_mode: OutputMode
|
46 |
+
extract_schema_str: str
|
47 |
+
|
48 |
+
|
49 |
+
def _get_logger(log_level: str) -> logging.Logger:
|
50 |
+
logger = logging.getLogger(__name__)
|
51 |
+
logger.setLevel(log_level)
|
52 |
+
if len(logger.handlers) > 0:
|
53 |
+
return logger
|
54 |
+
|
55 |
+
handler = logging.StreamHandler()
|
56 |
+
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
57 |
+
handler.setFormatter(formatter)
|
58 |
+
logger.addHandler(handler)
|
59 |
+
return logger
|
60 |
+
|
61 |
+
|
62 |
+
def _read_url_list(url_list_file: str) -> List[str]:
|
63 |
+
if not url_list_file:
|
64 |
+
return []
|
65 |
+
|
66 |
+
with open(url_list_file, "r") as f:
|
67 |
+
links = f.readlines()
|
68 |
+
url_list = [
|
69 |
+
link.strip()
|
70 |
+
for link in links
|
71 |
+
if link.strip() != "" and not link.startswith("#")
|
72 |
+
]
|
73 |
+
return url_list
|
74 |
+
|
75 |
+
|
76 |
+
def _read_extract_schema_str(extract_schema_file: str) -> str:
|
77 |
+
if not extract_schema_file:
|
78 |
+
return ""
|
79 |
+
|
80 |
+
with open(extract_schema_file, "r") as f:
|
81 |
+
schema_str = f.read()
|
82 |
+
return schema_str
|
83 |
+
|
84 |
+
|
85 |
+
def _output_csv(result_dict: Dict[str, List[BaseModel]], key_name: str) -> str:
|
86 |
+
# generate the CSV content from a Dict of URL and list of extracted items
|
87 |
+
output = io.StringIO()
|
88 |
+
csv_writer = None
|
89 |
+
for src_url, items in result_dict.items():
|
90 |
+
for item in items:
|
91 |
+
value_dict = item.model_dump()
|
92 |
+
item_with_url = {**value_dict, key_name: src_url}
|
93 |
+
|
94 |
+
if csv_writer is None:
|
95 |
+
headers = list(value_dict.keys()) + [key_name]
|
96 |
+
csv_writer = csv.DictWriter(output, fieldnames=headers)
|
97 |
+
csv_writer.writeheader()
|
98 |
+
|
99 |
+
csv_writer.writerow(item_with_url)
|
100 |
+
|
101 |
+
csv_content = output.getvalue()
|
102 |
+
output.close()
|
103 |
+
return csv_content
|
104 |
+
|
105 |
+
|
106 |
+
class Ask:
|
107 |
+
|
108 |
+
def __init__(self, logger: Optional[logging.Logger] = None):
|
109 |
+
self.read_env_variables()
|
110 |
+
|
111 |
+
if logger is not None:
|
112 |
+
self.logger = logger
|
113 |
+
else:
|
114 |
+
self.logger = _get_logger("INFO")
|
115 |
+
|
116 |
+
self.db_con = duckdb.connect(":memory:")
|
117 |
+
|
118 |
+
self.db_con.install_extension("vss")
|
119 |
+
self.db_con.load_extension("vss")
|
120 |
+
self.db_con.install_extension("fts")
|
121 |
+
self.db_con.load_extension("fts")
|
122 |
+
self.db_con.sql("CREATE SEQUENCE seq_docid START 1000")
|
123 |
+
|
124 |
+
self.session = requests.Session()
|
125 |
+
user_agent: str = (
|
126 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
127 |
+
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
128 |
+
"Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0"
|
129 |
+
)
|
130 |
+
self.session.headers.update({"User-Agent": user_agent})
|
131 |
+
|
132 |
+
def read_env_variables(self) -> None:
|
133 |
+
err_msg = ""
|
134 |
+
|
135 |
+
self.search_api_key = os.environ.get("SEARCH_API_KEY")
|
136 |
+
if self.search_api_key is None:
|
137 |
+
err_msg += "SEARCH_API_KEY env variable not set.\n"
|
138 |
+
self.search_project_id = os.environ.get("SEARCH_PROJECT_KEY")
|
139 |
+
if self.search_project_id is None:
|
140 |
+
err_msg += "SEARCH_PROJECT_KEY env variable not set.\n"
|
141 |
+
self.llm_api_key = os.environ.get("LLM_API_KEY")
|
142 |
+
if self.llm_api_key is None:
|
143 |
+
err_msg += "LLM_API_KEY env variable not set.\n"
|
144 |
+
|
145 |
+
if err_msg != "":
|
146 |
+
raise Exception(f"\n{err_msg}\n")
|
147 |
+
|
148 |
+
self.llm_base_url = os.environ.get("LLM_BASE_URL")
|
149 |
+
if self.llm_base_url is None:
|
150 |
+
self.llm_base_url = "https://api.openai.com/v1"
|
151 |
+
|
152 |
+
self.embedding_model = os.environ.get("EMBEDDING_MODEL")
|
153 |
+
self.embedding_dimensions = os.environ.get("EMBEDDING_DIMENSIONS")
|
154 |
+
|
155 |
+
if self.embedding_model is None or self.embedding_dimensions is None:
|
156 |
+
self.embedding_model = "text-embedding-3-small"
|
157 |
+
self.embedding_dimensions = 1536
|
158 |
+
|
159 |
+
def search_web(self, query: str, settings: AskSettings) -> List[str]:
|
160 |
+
escaped_query = urllib.parse.quote(query)
|
161 |
+
url_base = (
|
162 |
+
f"https://www.googleapis.com/customsearch/v1?key={self.search_api_key}"
|
163 |
+
f"&cx={self.search_project_id}&q={escaped_query}"
|
164 |
+
)
|
165 |
+
url_paras = f"&safe=active"
|
166 |
+
if settings.date_restrict > 0:
|
167 |
+
url_paras += f"&dateRestrict={settings.date_restrict}"
|
168 |
+
if settings.target_site:
|
169 |
+
url_paras += f"&siteSearch={settings.target_site}&siteSearchFilter=i"
|
170 |
+
url = f"{url_base}{url_paras}"
|
171 |
+
|
172 |
+
self.logger.debug(f"Searching for query: {query}")
|
173 |
+
|
174 |
+
resp = requests.get(url)
|
175 |
+
|
176 |
+
if resp is None:
|
177 |
+
raise Exception("No response from search API")
|
178 |
+
|
179 |
+
search_results_dict = json.loads(resp.text)
|
180 |
+
if "error" in search_results_dict:
|
181 |
+
raise Exception(
|
182 |
+
f"Error in search API response: {search_results_dict['error']}"
|
183 |
+
)
|
184 |
+
|
185 |
+
if "searchInformation" not in search_results_dict:
|
186 |
+
raise Exception(
|
187 |
+
f"No search information in search API response: {resp.text}"
|
188 |
+
)
|
189 |
+
|
190 |
+
total_results = search_results_dict["searchInformation"].get("totalResults", 0)
|
191 |
+
if total_results == 0:
|
192 |
+
self.logger.warning(f"No results found for query: {query}")
|
193 |
+
return []
|
194 |
+
|
195 |
+
results = search_results_dict.get("items", [])
|
196 |
+
if results is None or len(results) == 0:
|
197 |
+
self.logger.warning(f"No result items in the response for query: {query}")
|
198 |
+
return []
|
199 |
+
|
200 |
+
found_links = []
|
201 |
+
for result in results:
|
202 |
+
link = result.get("link", None)
|
203 |
+
if link is None or link == "":
|
204 |
+
self.logger.warning(f"Search result link missing: {result}")
|
205 |
+
continue
|
206 |
+
found_links.append(link)
|
207 |
+
return found_links
|
208 |
+
|
209 |
+
def _scape_url(self, url: str) -> Tuple[str, str]:
|
210 |
+
self.logger.info(f"Scraping {url} ...")
|
211 |
+
try:
|
212 |
+
response = self.session.get(url, timeout=10)
|
213 |
+
soup = BeautifulSoup(response.content, "lxml", from_encoding="utf-8")
|
214 |
+
|
215 |
+
body_tag = soup.body
|
216 |
+
if body_tag:
|
217 |
+
body_text = body_tag.get_text()
|
218 |
+
body_text = " ".join(body_text.split()).strip()
|
219 |
+
self.logger.debug(f"Scraped {url}: {body_text}...")
|
220 |
+
if len(body_text) > 100:
|
221 |
+
self.logger.info(
|
222 |
+
f"✅ Successfully scraped {url} with length: {len(body_text)}"
|
223 |
+
)
|
224 |
+
return url, body_text
|
225 |
+
else:
|
226 |
+
self.logger.warning(
|
227 |
+
f"Body text too short for url: {url}, length: {len(body_text)}"
|
228 |
+
)
|
229 |
+
return url, ""
|
230 |
+
else:
|
231 |
+
self.logger.warning(f"No body tag found in the response for url: {url}")
|
232 |
+
return url, ""
|
233 |
+
except Exception as e:
|
234 |
+
self.logger.error(f"Scraping error {url}: {e}")
|
235 |
+
return url, ""
|
236 |
+
|
237 |
+
def scrape_urls(self, urls: List[str]) -> Dict[str, str]:
|
238 |
+
# the key is the url and the value is the body text
|
239 |
+
scrape_results: Dict[str, str] = {}
|
240 |
+
|
241 |
+
partial_scrape = partial(self._scape_url)
|
242 |
+
with ThreadPoolExecutor(max_workers=10) as executor:
|
243 |
+
results = executor.map(partial_scrape, urls)
|
244 |
+
|
245 |
+
for url, body_text in results:
|
246 |
+
if body_text != "":
|
247 |
+
scrape_results[url] = body_text
|
248 |
+
|
249 |
+
return scrape_results
|
250 |
+
|
251 |
+
def chunk_results(
|
252 |
+
self, scrape_results: Dict[str, str], size: int, overlap: int
|
253 |
+
) -> Dict[str, List[str]]:
|
254 |
+
chunking_results: Dict[str, List[str]] = {}
|
255 |
+
for url, text in scrape_results.items():
|
256 |
+
chunks = []
|
257 |
+
for pos in range(0, len(text), size - overlap):
|
258 |
+
chunks.append(text[pos : pos + size])
|
259 |
+
chunking_results[url] = chunks
|
260 |
+
return chunking_results
|
261 |
+
|
262 |
+
def get_embedding(self, client: OpenAI, texts: List[str]) -> List[List[float]]:
|
263 |
+
if len(texts) == 0:
|
264 |
+
return []
|
265 |
+
|
266 |
+
response = client.embeddings.create(input=texts, model=self.embedding_model)
|
267 |
+
embeddings = []
|
268 |
+
for i in range(len(response.data)):
|
269 |
+
embeddings.append(response.data[i].embedding)
|
270 |
+
return embeddings
|
271 |
+
|
272 |
+
def batch_get_embedding(
|
273 |
+
self, client: OpenAI, chunk_batch: Tuple[str, List[str]]
|
274 |
+
) -> Tuple[Tuple[str, List[str]], List[List[float]]]:
|
275 |
+
"""
|
276 |
+
Return the chunk_batch as well as the embeddings for each chunk so that
|
277 |
+
we can aggregate them and save them to the database together.
|
278 |
+
|
279 |
+
Args:
|
280 |
+
- client: OpenAI client
|
281 |
+
- chunk_batch: Tuple of URL and list of chunks scraped from the URL
|
282 |
+
|
283 |
+
Returns:
|
284 |
+
- Tuple of chunk_bach and list of result embeddings
|
285 |
+
"""
|
286 |
+
texts = chunk_batch[1]
|
287 |
+
embeddings = self.get_embedding(client, texts)
|
288 |
+
return chunk_batch, embeddings
|
289 |
+
|
290 |
+
def _create_table(self) -> str:
|
291 |
+
# Simple ways to get a unique table name
|
292 |
+
timestamp = datetime.now().strftime("%Y_%m_%d_%H_%M_%S_%f")
|
293 |
+
table_name = f"document_chunks_{timestamp}"
|
294 |
+
|
295 |
+
self.db_con.execute(
|
296 |
+
f"""
|
297 |
+
CREATE TABLE {table_name} (
|
298 |
+
doc_id INTEGER PRIMARY KEY DEFAULT nextval('seq_docid'),
|
299 |
+
url TEXT,
|
300 |
+
chunk TEXT,
|
301 |
+
vec FLOAT[{self.embedding_dimensions}]
|
302 |
+
);
|
303 |
+
"""
|
304 |
+
)
|
305 |
+
return table_name
|
306 |
+
|
307 |
+
def save_chunks_to_db(self, chunking_results: Dict[str, List[str]]) -> str:
|
308 |
+
"""
|
309 |
+
The key of chunking_results is the URL and the value is the list of chunks.
|
310 |
+
"""
|
311 |
+
client = self._get_api_client()
|
312 |
+
embed_batch_size = 50
|
313 |
+
query_batch_size = 100
|
314 |
+
insert_data = []
|
315 |
+
|
316 |
+
table_name = self._create_table()
|
317 |
+
|
318 |
+
batches: List[Tuple[str, List[str]]] = []
|
319 |
+
for url, list_chunks in chunking_results.items():
|
320 |
+
for i in range(0, len(list_chunks), embed_batch_size):
|
321 |
+
list_chunks = list_chunks[i : i + embed_batch_size]
|
322 |
+
batches.append((url, list_chunks))
|
323 |
+
|
324 |
+
self.logger.info(f"Embedding {len(batches)} batches of chunks ...")
|
325 |
+
partial_get_embedding = partial(self.batch_get_embedding, client)
|
326 |
+
with ThreadPoolExecutor(max_workers=10) as executor:
|
327 |
+
all_embeddings = executor.map(partial_get_embedding, batches)
|
328 |
+
self.logger.info(f"✅ Finished embedding.")
|
329 |
+
|
330 |
+
# we batch the insert data to speed up the insertion operation
|
331 |
+
# although the DuckDB doc says executeMany is optimized for batch insert
|
332 |
+
# but we found that it is faster to batch the insert data and run a single insert
|
333 |
+
for chunk_batch, embeddings in all_embeddings:
|
334 |
+
url = chunk_batch[0]
|
335 |
+
list_chunks = chunk_batch[1]
|
336 |
+
insert_data.extend(
|
337 |
+
[
|
338 |
+
(url.replace("'", " "), chunk.replace("'", " "), embedding)
|
339 |
+
for chunk, embedding in zip(list_chunks, embeddings)
|
340 |
+
]
|
341 |
+
)
|
342 |
+
|
343 |
+
for i in range(0, len(insert_data), query_batch_size):
|
344 |
+
value_str = ", ".join(
|
345 |
+
[
|
346 |
+
f"('{url}', '{chunk}', {embedding})"
|
347 |
+
for url, chunk, embedding in insert_data[i : i + embed_batch_size]
|
348 |
+
]
|
349 |
+
)
|
350 |
+
query = f"""
|
351 |
+
INSERT INTO {table_name} (url, chunk, vec) VALUES {value_str};
|
352 |
+
"""
|
353 |
+
self.db_con.execute(query)
|
354 |
+
|
355 |
+
self.db_con.execute(
|
356 |
+
f"""
|
357 |
+
CREATE INDEX {table_name}_cos_idx ON {table_name} USING HNSW (vec)
|
358 |
+
WITH (metric = 'cosine');
|
359 |
+
"""
|
360 |
+
)
|
361 |
+
self.logger.info(f"✅ Created the vector index ...")
|
362 |
+
self.db_con.execute(
|
363 |
+
f"""
|
364 |
+
PRAGMA create_fts_index(
|
365 |
+
{table_name}, 'doc_id', 'chunk'
|
366 |
+
);
|
367 |
+
"""
|
368 |
+
)
|
369 |
+
self.logger.info(f"✅ Created the full text search index ...")
|
370 |
+
return table_name
|
371 |
+
|
372 |
+
def vector_search(
|
373 |
+
self, table_name: str, query: str, settings: AskSettings
|
374 |
+
) -> List[Dict[str, Any]]:
|
375 |
+
"""
|
376 |
+
The return value is a list of {url: str, chunk: str} records.
|
377 |
+
In a real world, we will define a class of Chunk to have more metadata such as offsets.
|
378 |
+
"""
|
379 |
+
client = self._get_api_client()
|
380 |
+
embeddings = self.get_embedding(client, [query])[0]
|
381 |
+
|
382 |
+
query_result: duckdb.DuckDBPyRelation = self.db_con.sql(
|
383 |
+
f"""
|
384 |
+
SELECT * FROM {table_name}
|
385 |
+
ORDER BY array_distance(vec, {embeddings}::FLOAT[{self.embedding_dimensions}])
|
386 |
+
LIMIT 10;
|
387 |
+
"""
|
388 |
+
)
|
389 |
+
|
390 |
+
self.logger.debug(query_result)
|
391 |
+
|
392 |
+
# use a dict to remove duplicates from vector search and full-text search
|
393 |
+
matched_chunks_dict = {}
|
394 |
+
for vec_result in query_result.fetchall():
|
395 |
+
doc_id = vec_result[0]
|
396 |
+
result_record = {
|
397 |
+
"url": vec_result[1],
|
398 |
+
"chunk": vec_result[2],
|
399 |
+
}
|
400 |
+
matched_chunks_dict[doc_id] = result_record
|
401 |
+
|
402 |
+
if settings.hybrid_search:
|
403 |
+
self.logger.info("Running full-text search ...")
|
404 |
+
|
405 |
+
self.db_con.execute(
|
406 |
+
f"""
|
407 |
+
PREPARE fts_query AS (
|
408 |
+
WITH scored_docs AS (
|
409 |
+
SELECT *, fts_main_{table_name}.match_bm25(
|
410 |
+
doc_id, ?, fields := 'chunk'
|
411 |
+
) AS score FROM {table_name})
|
412 |
+
SELECT doc_id, url, chunk, score
|
413 |
+
FROM scored_docs
|
414 |
+
WHERE score IS NOT NULL
|
415 |
+
ORDER BY score DESC
|
416 |
+
LIMIT 10)
|
417 |
+
"""
|
418 |
+
)
|
419 |
+
self.db_con.execute("PRAGMA threads=4")
|
420 |
+
|
421 |
+
# You can run more complex query rewrite methods here
|
422 |
+
# usually: stemming, stop words, etc.
|
423 |
+
escaped_query = query.replace("'", " ")
|
424 |
+
fts_result: duckdb.DuckDBPyRelation = self.db_con.execute(
|
425 |
+
f"EXECUTE fts_query('{escaped_query}')"
|
426 |
+
)
|
427 |
+
|
428 |
+
index = 0
|
429 |
+
for fts_record in fts_result.fetchall():
|
430 |
+
index += 1
|
431 |
+
self.logger.debug(f"The full text search record #{index}: {fts_record}")
|
432 |
+
doc_id = fts_record[0]
|
433 |
+
result_record = {
|
434 |
+
"url": fts_record[1],
|
435 |
+
"chunk": fts_record[2],
|
436 |
+
}
|
437 |
+
|
438 |
+
# You can configure the score threashold and top-k
|
439 |
+
if fts_record[3] > 1:
|
440 |
+
matched_chunks_dict[doc_id] = result_record
|
441 |
+
else:
|
442 |
+
break
|
443 |
+
|
444 |
+
if index >= 10:
|
445 |
+
break
|
446 |
+
|
447 |
+
return matched_chunks_dict.values()
|
448 |
+
|
449 |
+
def _get_api_client(self) -> OpenAI:
|
450 |
+
return OpenAI(api_key=self.llm_api_key, base_url=self.llm_base_url)
|
451 |
+
|
452 |
+
def _render_template(self, template_str: str, variables: Dict[str, Any]) -> str:
|
453 |
+
env = Environment(loader=BaseLoader(), autoescape=False)
|
454 |
+
template = env.from_string(template_str)
|
455 |
+
return template.render(variables)
|
456 |
+
|
457 |
+
def _get_target_class(self, extract_schema_str: str) -> TypeVar_BaseModel:
|
458 |
+
local_namespace = {"BaseModel": BaseModel}
|
459 |
+
exec(extract_schema_str, local_namespace, local_namespace)
|
460 |
+
for key, value in local_namespace.items():
|
461 |
+
if key == "__builtins__":
|
462 |
+
continue
|
463 |
+
if key == "BaseModel":
|
464 |
+
continue
|
465 |
+
if isinstance(value, type):
|
466 |
+
if issubclass(value, BaseModel):
|
467 |
+
return value
|
468 |
+
raise Exception("No Pydantic schema found in the extract schema str.")
|
469 |
+
|
470 |
+
def run_inference(
|
471 |
+
self,
|
472 |
+
query: str,
|
473 |
+
matched_chunks: List[Dict[str, Any]],
|
474 |
+
settings: AskSettings,
|
475 |
+
) -> str:
|
476 |
+
system_prompt = (
|
477 |
+
"You are an expert summarizing the answers based on the provided contents."
|
478 |
+
)
|
479 |
+
user_promt_template = """
|
480 |
+
Given the context as a sequence of references with a reference id in the
|
481 |
+
format of a leading [x], please answer the following question using {{ language }}:
|
482 |
+
|
483 |
+
{{ query }}
|
484 |
+
|
485 |
+
In the answer, use format [1], [2], ..., [n] in line where the reference is used.
|
486 |
+
For example, "According to the research from Google[3], ...".
|
487 |
+
|
488 |
+
Please create the answer strictly related to the context. If the context has no
|
489 |
+
information about the query, please write "No related information found in the context."
|
490 |
+
using {{ language }}.
|
491 |
+
|
492 |
+
{{ length_instructions }}
|
493 |
+
|
494 |
+
Here is the context:
|
495 |
+
{{ context }}
|
496 |
+
"""
|
497 |
+
context = ""
|
498 |
+
for i, chunk in enumerate(matched_chunks):
|
499 |
+
context += f"[{i+1}] {chunk['chunk']}\n"
|
500 |
+
|
501 |
+
if not settings.output_length:
|
502 |
+
length_instructions = ""
|
503 |
+
else:
|
504 |
+
length_instructions = (
|
505 |
+
f"Please provide the answer in { settings.output_length } words."
|
506 |
+
)
|
507 |
+
|
508 |
+
user_prompt = self._render_template(
|
509 |
+
user_promt_template,
|
510 |
+
{
|
511 |
+
"query": query,
|
512 |
+
"context": context,
|
513 |
+
"language": settings.output_language,
|
514 |
+
"length_instructions": length_instructions,
|
515 |
+
},
|
516 |
+
)
|
517 |
+
|
518 |
+
self.logger.debug(
|
519 |
+
f"Running inference with model: {settings.inference_model_name}"
|
520 |
+
)
|
521 |
+
self.logger.debug(f"Final user prompt: {user_prompt}")
|
522 |
+
|
523 |
+
api_client = self._get_api_client()
|
524 |
+
completion = api_client.chat.completions.create(
|
525 |
+
model=settings.inference_model_name,
|
526 |
+
messages=[
|
527 |
+
{
|
528 |
+
"role": "system",
|
529 |
+
"content": system_prompt,
|
530 |
+
},
|
531 |
+
{
|
532 |
+
"role": "user",
|
533 |
+
"content": user_prompt,
|
534 |
+
},
|
535 |
+
],
|
536 |
+
)
|
537 |
+
if completion is None:
|
538 |
+
raise Exception("No completion from the API")
|
539 |
+
|
540 |
+
response_str = completion.choices[0].message.content
|
541 |
+
return response_str
|
542 |
+
|
543 |
+
def run_extract(
|
544 |
+
self,
|
545 |
+
query: str,
|
546 |
+
extract_schema_str: str,
|
547 |
+
target_content: str,
|
548 |
+
settings: AskSettings,
|
549 |
+
) -> List[TypeVar_BaseModel]:
|
550 |
+
target_class = self._get_target_class(extract_schema_str)
|
551 |
+
system_prompt = (
|
552 |
+
"You are an expert of extract structual information from the document."
|
553 |
+
)
|
554 |
+
user_promt_template = """
|
555 |
+
Given the provided content, if it contains information about {{ query }}, please extract the
|
556 |
+
list of structured data items as defined in the following Pydantic schema:
|
557 |
+
|
558 |
+
{{ extract_schema_str }}
|
559 |
+
|
560 |
+
Below is the provided content:
|
561 |
+
{{ content }}
|
562 |
+
"""
|
563 |
+
user_prompt = self._render_template(
|
564 |
+
user_promt_template,
|
565 |
+
{
|
566 |
+
"query": query,
|
567 |
+
"content": target_content,
|
568 |
+
"extract_schema_str": extract_schema_str,
|
569 |
+
},
|
570 |
+
)
|
571 |
+
|
572 |
+
self.logger.debug(
|
573 |
+
f"Running extraction with model: {settings.inference_model_name}"
|
574 |
+
)
|
575 |
+
self.logger.debug(f"Final user prompt: {user_prompt}")
|
576 |
+
|
577 |
+
class_name = target_class.__name__
|
578 |
+
list_class_name = f"{class_name}_list"
|
579 |
+
response_pydantic_model = create_model(
|
580 |
+
list_class_name,
|
581 |
+
items=(List[target_class], ...),
|
582 |
+
)
|
583 |
+
|
584 |
+
api_client = self._get_api_client()
|
585 |
+
completion = api_client.beta.chat.completions.parse(
|
586 |
+
model=settings.inference_model_name,
|
587 |
+
messages=[
|
588 |
+
{
|
589 |
+
"role": "system",
|
590 |
+
"content": system_prompt,
|
591 |
+
},
|
592 |
+
{
|
593 |
+
"role": "user",
|
594 |
+
"content": user_prompt,
|
595 |
+
},
|
596 |
+
],
|
597 |
+
response_format=response_pydantic_model,
|
598 |
+
)
|
599 |
+
if completion is None:
|
600 |
+
raise Exception("No completion from the API")
|
601 |
+
|
602 |
+
message = completion.choices[0].message
|
603 |
+
if message.refusal:
|
604 |
+
raise Exception(
|
605 |
+
f"Refused to extract information from the document: {message.refusal}."
|
606 |
+
)
|
607 |
+
|
608 |
+
extract_result = message.parsed
|
609 |
+
return extract_result.items
|
610 |
+
|
611 |
+
def run_query_gradio(
|
612 |
+
self,
|
613 |
+
query: str,
|
614 |
+
date_restrict: int,
|
615 |
+
target_site: str,
|
616 |
+
output_language: str,
|
617 |
+
output_length: int,
|
618 |
+
url_list_str: str,
|
619 |
+
inference_model_name: str,
|
620 |
+
hybrid_search: bool,
|
621 |
+
output_mode_str: str,
|
622 |
+
extract_schema_str: str,
|
623 |
+
) -> Generator[Tuple[str, str], None, Tuple[str, str]]:
|
624 |
+
logger = self.logger
|
625 |
+
log_queue = Queue()
|
626 |
+
|
627 |
+
if url_list_str:
|
628 |
+
url_list = url_list_str.split("\n")
|
629 |
+
else:
|
630 |
+
url_list = []
|
631 |
+
|
632 |
+
settings = AskSettings(
|
633 |
+
date_restrict=date_restrict,
|
634 |
+
target_site=target_site,
|
635 |
+
output_language=output_language,
|
636 |
+
output_length=output_length,
|
637 |
+
url_list=url_list,
|
638 |
+
inference_model_name=inference_model_name,
|
639 |
+
hybrid_search=hybrid_search,
|
640 |
+
output_mode=OutputMode(output_mode_str),
|
641 |
+
extract_schema_str=extract_schema_str,
|
642 |
+
)
|
643 |
+
|
644 |
+
queue_handler = logging.Handler()
|
645 |
+
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
646 |
+
queue_handler.emit = lambda record: log_queue.put(formatter.format(record))
|
647 |
+
logger.addHandler(queue_handler)
|
648 |
+
|
649 |
+
def update_logs():
|
650 |
+
logs = []
|
651 |
+
while True:
|
652 |
+
try:
|
653 |
+
log = log_queue.get_nowait()
|
654 |
+
logs.append(log)
|
655 |
+
except queue.Empty:
|
656 |
+
break
|
657 |
+
return "\n".join(logs)
|
658 |
+
|
659 |
+
# wrap the process in a generator to yield the logs to integrate with GradIO
|
660 |
+
def process_with_logs():
|
661 |
+
if len(settings.url_list) > 0:
|
662 |
+
links = settings.url_list
|
663 |
+
else:
|
664 |
+
logger.info("Searching the web ...")
|
665 |
+
yield "", update_logs()
|
666 |
+
links = self.search_web(query, settings)
|
667 |
+
logger.info(f"✅ Found {len(links)} links for query: {query}")
|
668 |
+
for i, link in enumerate(links):
|
669 |
+
logger.debug(f"{i+1}. {link}")
|
670 |
+
yield "", update_logs()
|
671 |
+
|
672 |
+
logger.info("Scraping the URLs ...")
|
673 |
+
yield "", update_logs()
|
674 |
+
scrape_results = self.scrape_urls(links)
|
675 |
+
logger.info(f"✅ Scraped {len(scrape_results)} URLs.")
|
676 |
+
yield "", update_logs()
|
677 |
+
|
678 |
+
if settings.output_mode == OutputMode.answer:
|
679 |
+
logger.info("Chunking the text ...")
|
680 |
+
yield "", update_logs()
|
681 |
+
chunking_results = self.chunk_results(scrape_results, 1000, 100)
|
682 |
+
total_chunks = 0
|
683 |
+
for url, chunks in chunking_results.items():
|
684 |
+
logger.debug(f"URL: {url}")
|
685 |
+
total_chunks += len(chunks)
|
686 |
+
for i, chunk in enumerate(chunks):
|
687 |
+
logger.debug(f"Chunk {i+1}: {chunk}")
|
688 |
+
logger.info(f"✅ Generated {total_chunks} chunks ...")
|
689 |
+
yield "", update_logs()
|
690 |
+
|
691 |
+
logger.info(f"Saving {total_chunks} chunks to DB ...")
|
692 |
+
yield "", update_logs()
|
693 |
+
table_name = self.save_chunks_to_db(chunking_results)
|
694 |
+
logger.info(f"✅ Successfully embedded and saved chunks to DB.")
|
695 |
+
yield "", update_logs()
|
696 |
+
|
697 |
+
logger.info("Querying the vector DB to get context ...")
|
698 |
+
matched_chunks = self.vector_search(table_name, query, settings)
|
699 |
+
for i, result in enumerate(matched_chunks):
|
700 |
+
logger.debug(f"{i+1}. {result}")
|
701 |
+
logger.info(f"✅ Got {len(matched_chunks)} matched chunks.")
|
702 |
+
yield "", update_logs()
|
703 |
+
|
704 |
+
logger.info("Running inference with context ...")
|
705 |
+
yield "", update_logs()
|
706 |
+
answer = self.run_inference(
|
707 |
+
query=query,
|
708 |
+
matched_chunks=matched_chunks,
|
709 |
+
settings=settings,
|
710 |
+
)
|
711 |
+
logger.info("✅ Finished inference API call.")
|
712 |
+
logger.info("Generating output ...")
|
713 |
+
yield "", update_logs()
|
714 |
+
|
715 |
+
answer = f"# Answer\n\n{answer}\n"
|
716 |
+
references = "\n".join(
|
717 |
+
[
|
718 |
+
f"[{i+1}] {result['url']}"
|
719 |
+
for i, result in enumerate(matched_chunks)
|
720 |
+
]
|
721 |
+
)
|
722 |
+
yield f"{answer}\n\n# References\n\n{references}", update_logs()
|
723 |
+
elif settings.output_mode == OutputMode.extract:
|
724 |
+
logger.info("Extracting structured data ...")
|
725 |
+
yield "", update_logs()
|
726 |
+
|
727 |
+
aggregated_output = {}
|
728 |
+
for url, text in scrape_results.items():
|
729 |
+
items = self.run_extract(
|
730 |
+
query=query,
|
731 |
+
extract_schema_str=extract_schema_str,
|
732 |
+
target_content=text,
|
733 |
+
settings=settings,
|
734 |
+
)
|
735 |
+
self.logger.info(
|
736 |
+
f"✅ Finished inference API call. Extracted {len(items)} items from {url}."
|
737 |
+
)
|
738 |
+
yield "", update_logs()
|
739 |
+
|
740 |
+
self.logger.debug(items)
|
741 |
+
aggregated_output[url] = items
|
742 |
+
|
743 |
+
logger.info("✅ Finished extraction from all urls.")
|
744 |
+
logger.info("Generating output ...")
|
745 |
+
yield "", update_logs()
|
746 |
+
answer = _output_csv(aggregated_output, "SourceURL")
|
747 |
+
yield f"{answer}", update_logs()
|
748 |
+
else:
|
749 |
+
raise Exception(f"Invalid output mode: {settings.output_mode}")
|
750 |
+
|
751 |
+
logs = ""
|
752 |
+
final_result = ""
|
753 |
+
|
754 |
+
try:
|
755 |
+
for result, log_update in process_with_logs():
|
756 |
+
logs += log_update + "\n"
|
757 |
+
final_result = result
|
758 |
+
yield final_result, logs
|
759 |
+
finally:
|
760 |
+
logger.removeHandler(queue_handler)
|
761 |
+
|
762 |
+
return final_result, logs
|
763 |
+
|
764 |
+
def run_query(
|
765 |
+
self,
|
766 |
+
query: str,
|
767 |
+
settings: AskSettings,
|
768 |
+
) -> str:
|
769 |
+
url_list_str = "\n".join(settings.url_list)
|
770 |
+
|
771 |
+
for result, logs in self.run_query_gradio(
|
772 |
+
query=query,
|
773 |
+
date_restrict=settings.date_restrict,
|
774 |
+
target_site=settings.target_site,
|
775 |
+
output_language=settings.output_language,
|
776 |
+
output_length=settings.output_length,
|
777 |
+
url_list_str=url_list_str,
|
778 |
+
inference_model_name=settings.inference_model_name,
|
779 |
+
hybrid_search=settings.hybrid_search,
|
780 |
+
output_mode_str=settings.output_mode,
|
781 |
+
extract_schema_str=settings.extract_schema_str,
|
782 |
+
):
|
783 |
+
final_result = result
|
784 |
+
return final_result
|
785 |
+
|
786 |
+
|
787 |
+
def launch_gradio(
|
788 |
+
query: str,
|
789 |
+
init_settings: AskSettings,
|
790 |
+
share_ui: bool,
|
791 |
+
logger: logging.Logger,
|
792 |
+
) -> None:
|
793 |
+
ask = Ask(logger=logger)
|
794 |
+
|
795 |
+
def toggle_schema_textbox(option):
|
796 |
+
if option == "extract":
|
797 |
+
return gr.update(visible=True)
|
798 |
+
else:
|
799 |
+
return gr.update(visible=False)
|
800 |
+
|
801 |
+
with gr.Blocks() as demo:
|
802 |
+
gr.Markdown("# Ask.py - Web Search-Extract-Summarize")
|
803 |
+
gr.Markdown(
|
804 |
+
"Search the web with the query and summarize the results. Source code: https://github.com/pengfeng/ask.py"
|
805 |
+
)
|
806 |
+
|
807 |
+
with gr.Row():
|
808 |
+
with gr.Column():
|
809 |
+
|
810 |
+
query_input = gr.Textbox(label="Query", value=query)
|
811 |
+
output_mode_input = gr.Radio(
|
812 |
+
label="Output Mode [answer: simple answer, extract: get structured data]",
|
813 |
+
choices=["answer", "extract"],
|
814 |
+
value=init_settings.output_mode,
|
815 |
+
)
|
816 |
+
extract_schema_input = gr.Textbox(
|
817 |
+
label="Extract Pydantic Schema",
|
818 |
+
visible=(init_settings.output_mode == "extract"),
|
819 |
+
value=init_settings.extract_schema_str,
|
820 |
+
lines=5,
|
821 |
+
max_lines=20,
|
822 |
+
)
|
823 |
+
output_mode_input.change(
|
824 |
+
fn=toggle_schema_textbox,
|
825 |
+
inputs=output_mode_input,
|
826 |
+
outputs=extract_schema_input,
|
827 |
+
)
|
828 |
+
date_restrict_input = gr.Number(
|
829 |
+
label="Date Restrict (Optional) [0 or empty means no date limit.]",
|
830 |
+
value=init_settings.date_restrict,
|
831 |
+
)
|
832 |
+
target_site_input = gr.Textbox(
|
833 |
+
label="Target Sites (Optional) [Empty means searching the whole web.]",
|
834 |
+
value=init_settings.target_site,
|
835 |
+
)
|
836 |
+
output_language_input = gr.Textbox(
|
837 |
+
label="Output Language (Optional) [Default is English.]",
|
838 |
+
value=init_settings.output_language,
|
839 |
+
)
|
840 |
+
output_length_input = gr.Number(
|
841 |
+
label="Output Length in words (Optional) [Default is automatically decided by LLM.]",
|
842 |
+
value=init_settings.output_length,
|
843 |
+
)
|
844 |
+
url_list_input = gr.Textbox(
|
845 |
+
label="URL List (Optional) [When specified, scrape the urls instead of searching the web.]",
|
846 |
+
lines=5,
|
847 |
+
max_lines=20,
|
848 |
+
value="\n".join(init_settings.url_list),
|
849 |
+
)
|
850 |
+
|
851 |
+
with gr.Accordion("More Options", open=False):
|
852 |
+
hybrid_search_input = gr.Checkbox(
|
853 |
+
label="Hybrid Search [Use both vector search and full-text search.]",
|
854 |
+
value=init_settings.hybrid_search,
|
855 |
+
)
|
856 |
+
inference_model_name_input = gr.Textbox(
|
857 |
+
label="Inference Model Name",
|
858 |
+
value=init_settings.inference_model_name,
|
859 |
+
)
|
860 |
+
|
861 |
+
submit_button = gr.Button("Submit")
|
862 |
+
|
863 |
+
with gr.Column():
|
864 |
+
answer_output = gr.Textbox(label="Answer")
|
865 |
+
logs_output = gr.Textbox(label="Logs", lines=10)
|
866 |
+
|
867 |
+
submit_button.click(
|
868 |
+
fn=ask.run_query_gradio,
|
869 |
+
inputs=[
|
870 |
+
query_input,
|
871 |
+
date_restrict_input,
|
872 |
+
target_site_input,
|
873 |
+
output_language_input,
|
874 |
+
output_length_input,
|
875 |
+
url_list_input,
|
876 |
+
inference_model_name_input,
|
877 |
+
hybrid_search_input,
|
878 |
+
output_mode_input,
|
879 |
+
extract_schema_input,
|
880 |
+
],
|
881 |
+
outputs=[answer_output, logs_output],
|
882 |
+
)
|
883 |
+
|
884 |
+
demo.queue().launch(share=share_ui)
|
885 |
+
|
886 |
+
|
887 |
+
@click.command(help="Search web for the query and summarize the results.")
|
888 |
+
@click.option("--query", "-q", required=False, help="Query to search")
|
889 |
+
@click.option(
|
890 |
+
"--output-mode",
|
891 |
+
"-o",
|
892 |
+
type=click.Choice(["answer", "extract"], case_sensitive=False),
|
893 |
+
default="answer",
|
894 |
+
required=False,
|
895 |
+
help="Output mode for the answer, default is a simple answer",
|
896 |
+
)
|
897 |
+
@click.option(
|
898 |
+
"--date-restrict",
|
899 |
+
"-d",
|
900 |
+
type=int,
|
901 |
+
required=False,
|
902 |
+
default=0,
|
903 |
+
help="Restrict search results to a specific date range, default is no restriction",
|
904 |
+
)
|
905 |
+
@click.option(
|
906 |
+
"--target-site",
|
907 |
+
"-s",
|
908 |
+
required=False,
|
909 |
+
default="",
|
910 |
+
help="Restrict search results to a specific site, default is no restriction",
|
911 |
+
)
|
912 |
+
@click.option(
|
913 |
+
"--output-language",
|
914 |
+
required=False,
|
915 |
+
default="English",
|
916 |
+
help="Output language for the answer",
|
917 |
+
)
|
918 |
+
@click.option(
|
919 |
+
"--output-length",
|
920 |
+
type=int,
|
921 |
+
required=False,
|
922 |
+
default=0,
|
923 |
+
help="Output length for the answer",
|
924 |
+
)
|
925 |
+
@click.option(
|
926 |
+
"--url-list-file",
|
927 |
+
type=str,
|
928 |
+
required=False,
|
929 |
+
default="",
|
930 |
+
show_default=True,
|
931 |
+
help="Instead of doing web search, scrape the target URL list and answer the query based on the content",
|
932 |
+
)
|
933 |
+
@click.option(
|
934 |
+
"--extract-schema-file",
|
935 |
+
type=str,
|
936 |
+
required=False,
|
937 |
+
default="",
|
938 |
+
show_default=True,
|
939 |
+
help="Pydantic schema for the extract mode",
|
940 |
+
)
|
941 |
+
@click.option(
|
942 |
+
"--inference-model-name",
|
943 |
+
"-m",
|
944 |
+
required=False,
|
945 |
+
default="gpt-4o-mini",
|
946 |
+
help="Model name to use for inference",
|
947 |
+
)
|
948 |
+
@click.option(
|
949 |
+
"--hybrid-search",
|
950 |
+
is_flag=True,
|
951 |
+
help="Use hybrid search mode with both vector search and full-text search",
|
952 |
+
)
|
953 |
+
@click.option(
|
954 |
+
"--web-ui",
|
955 |
+
is_flag=True,
|
956 |
+
help="Launch the web interface",
|
957 |
+
)
|
958 |
+
@click.option(
|
959 |
+
"-l",
|
960 |
+
"--log-level",
|
961 |
+
"log_level",
|
962 |
+
default="INFO",
|
963 |
+
type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False),
|
964 |
+
help="Set the logging level",
|
965 |
+
show_default=True,
|
966 |
+
)
|
967 |
+
def search_extract_summarize(
|
968 |
+
query: str,
|
969 |
+
output_mode: str,
|
970 |
+
date_restrict: int,
|
971 |
+
target_site: str,
|
972 |
+
output_language: str,
|
973 |
+
output_length: int,
|
974 |
+
url_list_file: str,
|
975 |
+
extract_schema_file: str,
|
976 |
+
inference_model_name: str,
|
977 |
+
hybrid_search: bool,
|
978 |
+
web_ui: bool,
|
979 |
+
log_level: str,
|
980 |
+
):
|
981 |
+
load_dotenv(dotenv_path=default_env_file, override=False)
|
982 |
+
logger = _get_logger(log_level)
|
983 |
+
|
984 |
+
if output_mode == "extract" and not extract_schema_file:
|
985 |
+
raise Exception("Extract mode requires the --extract-schema-file argument.")
|
986 |
+
|
987 |
+
settings = AskSettings(
|
988 |
+
date_restrict=date_restrict,
|
989 |
+
target_site=target_site,
|
990 |
+
output_language=output_language,
|
991 |
+
output_length=output_length,
|
992 |
+
url_list=_read_url_list(url_list_file),
|
993 |
+
inference_model_name=inference_model_name,
|
994 |
+
hybrid_search=hybrid_search,
|
995 |
+
output_mode=OutputMode(output_mode),
|
996 |
+
extract_schema_str=_read_extract_schema_str(extract_schema_file),
|
997 |
+
)
|
998 |
+
|
999 |
+
if web_ui or os.environ.get("RUN_GRADIO_UI", "false").lower() != "false":
|
1000 |
+
if os.environ.get("SHARE_GRADIO_UI", "false").lower() == "true":
|
1001 |
+
share_ui = True
|
1002 |
+
else:
|
1003 |
+
share_ui = False
|
1004 |
+
launch_gradio(
|
1005 |
+
query=query,
|
1006 |
+
init_settings=settings,
|
1007 |
+
share_ui=share_ui,
|
1008 |
+
logger=logger,
|
1009 |
+
)
|
1010 |
+
else:
|
1011 |
+
if query is None:
|
1012 |
+
raise Exception("Query is required for the command line mode")
|
1013 |
+
ask = Ask(logger=logger)
|
1014 |
+
|
1015 |
+
final_result = ask.run_query(query=query, settings=settings)
|
1016 |
+
click.echo(final_result)
|
1017 |
+
|
1018 |
+
|
1019 |
+
if __name__ == "__main__":
|
1020 |
+
search_extract_summarize()
|
demos/search_and_answer.md
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
```
|
2 |
+
% python ask.py -q "Why do we need agentic RAG even if we have ChatGPT?"
|
3 |
+
|
4 |
+
✅ Found 10 links for query: Why do we need agentic RAG even if we have ChatGPT?
|
5 |
+
✅ Scraping the URLs ...
|
6 |
+
✅ Scraped 10 URLs ...
|
7 |
+
✅ Chunking the text ...
|
8 |
+
✅ Saving to vector DB ...
|
9 |
+
✅ Querying the vector DB ...
|
10 |
+
✅ Running inference with context ...
|
11 |
+
|
12 |
+
# Answer
|
13 |
+
|
14 |
+
Agentic RAG (Retrieval-Augmented Generation) is needed alongside ChatGPT for several reasons:
|
15 |
+
|
16 |
+
1. **Precision and Contextual Relevance**: While ChatGPT offers generative responses, it may not
|
17 |
+
reliably provide precise answers, especially when specific, accurate information is critical[5].
|
18 |
+
Agentic RAG enhances this by integrating retrieval mechanisms that improve response context and
|
19 |
+
accuracy, allowing users to access the most relevant and recent data without the need for costly
|
20 |
+
model fine-tuning[2].
|
21 |
+
|
22 |
+
2. **Customizability**: RAG allows businesses to create tailored chatbots that can securely
|
23 |
+
reference company-specific data[2]. In contrast, ChatGPT’s broader capabilities may not be
|
24 |
+
directly suited for specialized, domain-specific questions without comprehensive customization[3].
|
25 |
+
|
26 |
+
3. **Complex Query Handling**: RAG can be optimized for complex queries and can be adjusted to
|
27 |
+
work better with specific types of inputs, such as comparing and contrasting information, a task
|
28 |
+
where ChatGPT may struggle under certain circumstances[9]. This level of customization can lead to
|
29 |
+
better performance in niche applications where precise retrieval of information is crucial.
|
30 |
+
|
31 |
+
4. **Asynchronous Processing Capabilities**: Future agentic systems aim to integrate asynchronous
|
32 |
+
handling of actions, allowing for parallel processing and reducing wait times for retrieval and
|
33 |
+
computation, which is a limitation in the current form of ChatGPT[7]. This advancement would enhance
|
34 |
+
overall efficiency and responsiveness in conversations.
|
35 |
+
|
36 |
+
5. **Incorporating Retrieved Information Effectively**: Using RAG can significantly improve how
|
37 |
+
retrieved information is utilized within a conversation. By effectively managing the context and
|
38 |
+
relevance of retrieved documents, RAG helps in framing prompts that can guide ChatGPT towards
|
39 |
+
delivering more accurate responses[10].
|
40 |
+
|
41 |
+
In summary, while ChatGPT excels in generating conversational responses, agentic RAG brings
|
42 |
+
precision, customization, and efficiency that can significantly enhance the overall conversational
|
43 |
+
AI experience.
|
44 |
+
|
45 |
+
# References
|
46 |
+
|
47 |
+
[1] https://community.openai.com/t/how-to-use-rag-properly-and-what-types-of-query-it-is-good-at/658204
|
48 |
+
[2] https://www.linkedin.com/posts/brianjuliusdc_dax-powerbi-chatgpt-activity-7235953280177041408-wQqq
|
49 |
+
[3] https://community.openai.com/t/how-to-use-rag-properly-and-what-types-of-query-it-is-good-at/658204
|
50 |
+
[4] https://community.openai.com/t/prompt-engineering-for-rag/621495
|
51 |
+
[5] https://www.ben-evans.com/benedictevans/2024/6/8/building-ai-products
|
52 |
+
[6] https://community.openai.com/t/prompt-engineering-for-rag/621495
|
53 |
+
[7] https://www.linkedin.com/posts/kurtcagle_agentic-rag-personalizing-and-optimizing-activity-7198097129993613312-z7Sm
|
54 |
+
[8] https://community.openai.com/t/how-to-use-rag-properly-and-what-types-of-query-it-is-good-at/658204
|
55 |
+
[9] https://community.openai.com/t/how-to-use-rag-properly-and-what-types-of-query-it-is-good-at/658204
|
56 |
+
[10] https://community.openai.com/t/prompt-engineering-for-rag/621495
|
57 |
+
```
|
demos/search_and_extract.md
ADDED
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
```
|
2 |
+
% python ask.py -q "LLM Gen-AI Startups" -o extract --extract-schema-file instructions/extract_example.txt
|
3 |
+
2024-10-29 08:15:24,320 - INFO - Searching the web ...
|
4 |
+
2024-10-29 08:15:24,684 - INFO - ✅ Found 10 links for query: LLM Gen-AI Startups
|
5 |
+
2024-10-29 08:15:24,684 - INFO - Scraping the URLs ...
|
6 |
+
2024-10-29 08:15:24,685 - INFO - Scraping https://www.ycombinator.com/companies/industry/generative-ai ...
|
7 |
+
2024-10-29 08:15:24,686 - INFO - Scraping https://app.dealroom.co/lists/33530 ...
|
8 |
+
2024-10-29 08:15:24,687 - INFO - Scraping https://explodingtopics.com/blog/generative-ai-startups ...
|
9 |
+
2024-10-29 08:15:24,688 - INFO - Scraping https://www.reddit.com/r/learnprogramming/comments/1e0gzbo/are_most_ai_startups_these_days_just_openai/ ...
|
10 |
+
2024-10-29 08:15:24,689 - INFO - Scraping https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc ...
|
11 |
+
2024-10-29 08:15:24,690 - INFO - Scraping https://www.reddit.com/r/ycombinator/comments/16xhwz7/is_it_really_impossible_for_genai_startups_to/ ...
|
12 |
+
2024-10-29 08:15:24,691 - INFO - Scraping https://medium.com/point-nine-news/where-are-the-opportunities-for-new-startups-in-generative-ai-f48068b5f8f9 ...
|
13 |
+
2024-10-29 08:15:24,692 - INFO - Scraping https://a16z.com/ai/ ...
|
14 |
+
2024-10-29 08:15:24,693 - INFO - Scraping https://www.eweek.com/artificial-intelligence/generative-ai-startups/ ...
|
15 |
+
2024-10-29 08:15:24,695 - INFO - Scraping https://cohere.com/ ...
|
16 |
+
2024-10-29 08:15:24,875 - WARNING - Body text too short for url: https://app.dealroom.co/lists/33530, length: 41
|
17 |
+
2024-10-29 08:15:24,975 - INFO - ✅ Successfully scraped https://explodingtopics.com/blog/generative-ai-startups with length: 17631
|
18 |
+
2024-10-29 08:15:25,429 - INFO - ✅ Successfully scraped https://medium.com/point-nine-news/where-are-the-opportunities-for-new-startups-in-generative-ai-f48068b5f8f9 with length: 3649
|
19 |
+
2024-10-29 08:15:26,119 - INFO - ✅ Successfully scraped https://a16z.com/ai/ with length: 14828
|
20 |
+
2024-10-29 08:15:26,144 - INFO - ✅ Successfully scraped https://cohere.com/ with length: 2696
|
21 |
+
2024-10-29 08:15:26,220 - INFO - ✅ Successfully scraped https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc with length: 13858
|
22 |
+
2024-10-29 08:15:26,520 - INFO - ✅ Successfully scraped https://www.reddit.com/r/learnprogramming/comments/1e0gzbo/are_most_ai_startups_these_days_just_openai/ with length: 2029
|
23 |
+
2024-10-29 08:15:26,541 - INFO - ✅ Successfully scraped https://www.reddit.com/r/ycombinator/comments/16xhwz7/is_it_really_impossible_for_genai_startups_to/ with length: 3445
|
24 |
+
2024-10-29 08:15:26,947 - WARNING - Body text too short for url: https://www.ycombinator.com/companies/industry/generative-ai, length: 0
|
25 |
+
2024-10-29 08:15:26,989 - INFO - ✅ Successfully scraped https://www.eweek.com/artificial-intelligence/generative-ai-startups/ with length: 69104
|
26 |
+
2024-10-29 08:15:26,990 - INFO - ✅ Scraped 8 URLs.
|
27 |
+
2024-10-29 08:15:26,990 - INFO - Extracting structured data ...
|
28 |
+
2024-10-29 08:15:34,019 - INFO - ✅ Finished inference API call. Extracted 33 items from https://explodingtopics.com/blog/generative-ai-startups.
|
29 |
+
2024-10-29 08:15:34,490 - INFO - ✅ Finished inference API call. Extracted 0 items from https://www.reddit.com/r/learnprogramming/comments/1e0gzbo/are_most_ai_startups_these_days_just_openai/.
|
30 |
+
2024-10-29 08:15:38,135 - INFO - ✅ Finished inference API call. Extracted 20 items from https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc.
|
31 |
+
2024-10-29 08:15:39,165 - INFO - ✅ Finished inference API call. Extracted 2 items from https://www.reddit.com/r/ycombinator/comments/16xhwz7/is_it_really_impossible_for_genai_startups_to/.
|
32 |
+
2024-10-29 08:15:40,463 - INFO - ✅ Finished inference API call. Extracted 7 items from https://medium.com/point-nine-news/where-are-the-opportunities-for-new-startups-in-generative-ai-f48068b5f8f9.
|
33 |
+
2024-10-29 08:15:44,150 - INFO - ✅ Finished inference API call. Extracted 23 items from https://a16z.com/ai/.
|
34 |
+
2024-10-29 08:16:02,785 - INFO - ✅ Finished inference API call. Extracted 73 items from https://www.eweek.com/artificial-intelligence/generative-ai-startups/.
|
35 |
+
2024-10-29 08:16:04,423 - INFO - ✅ Finished inference API call. Extracted 3 items from https://cohere.com/.
|
36 |
+
2024-10-29 08:16:04,423 - INFO - ✅ Finished extraction from all urls.
|
37 |
+
2024-10-29 08:16:04,423 - INFO - Generating output ...
|
38 |
+
name,description,SourceURL
|
39 |
+
Cohere,Cohere is an AI startup that builds multilingual LLMs for enterprise businesses to streamline tasks.,https://explodingtopics.com/blog/generative-ai-startups
|
40 |
+
Hugging Face,"Hugging Face is a collaborative AI community that creates tools for developers, offering over 61,000 pre-trained models and 7,000 datasets.",https://explodingtopics.com/blog/generative-ai-startups
|
41 |
+
Tabnine,Tabnine is an AI assistant for software developers that uses generative AI to predict or suggest the next lines of code.,https://explodingtopics.com/blog/generative-ai-startups
|
42 |
+
Soundraw,Soundraw is a royalty-free AI music generator allowing creators to make original songs.,https://explodingtopics.com/blog/generative-ai-startups
|
43 |
+
Tome.app,Tome is an AI-powered storytelling platform that helps users create presentations using generative AI.,https://explodingtopics.com/blog/generative-ai-startups
|
44 |
+
AssemblyAI,AssemblyAI is an AI-as-a-service startup providing APIs in 80 different languages for automated speech transcription.,https://explodingtopics.com/blog/generative-ai-startups
|
45 |
+
Promptbase,Promptbase is a marketplace for buying and selling prompts to generate content with AI tools.,https://explodingtopics.com/blog/generative-ai-startups
|
46 |
+
Photoroom,PhotoRoom is an AI-powered photo editing tool that combines generative AI with traditional editing tools.,https://explodingtopics.com/blog/generative-ai-startups
|
47 |
+
Taskade,Taskade is a generative AI productivity tool for task management and team collaboration.,https://explodingtopics.com/blog/generative-ai-startups
|
48 |
+
Synthesia,Synthesia AI is a generative AI video maker that creates videos from text in multiple languages.,https://explodingtopics.com/blog/generative-ai-startups
|
49 |
+
Humata AI,Humata AI is a tool integrating with your desktop to answer questions about documents using generative AI.,https://explodingtopics.com/blog/generative-ai-startups
|
50 |
+
Chatbase,"Chatbase is a chatbot integrated into websites, providing instant answers to users.",https://explodingtopics.com/blog/generative-ai-startups
|
51 |
+
Stability AI,"Stability AI is known for creating Stable Diffusion, a deep learning text-to-image AI model.",https://explodingtopics.com/blog/generative-ai-startups
|
52 |
+
Anyword,Anyword is a generative AI content generation platform that uses natural language processing for copywriting.,https://explodingtopics.com/blog/generative-ai-startups
|
53 |
+
Rephrase AI,Rephrase AI is a text-to-video generation platform that allows customers to create personalized videos.,https://explodingtopics.com/blog/generative-ai-startups
|
54 |
+
Inworld AI,Inworld AI specializes in AI-powered character generation for video games.,https://explodingtopics.com/blog/generative-ai-startups
|
55 |
+
Runway,Runway is a generative AI video editing platform that produces videos based on text prompts.,https://explodingtopics.com/blog/generative-ai-startups
|
56 |
+
Sudowrite,Sudowrite is a generative AI writing assistant designed for novel writing and storytelling.,https://explodingtopics.com/blog/generative-ai-startups
|
57 |
+
Steve.ai,Steve.ai is an online video creation platform that turns text prompts into animated videos.,https://explodingtopics.com/blog/generative-ai-startups
|
58 |
+
PlayHT,PlayHT is a text-to-speech software that uses generative AI to create audio from text.,https://explodingtopics.com/blog/generative-ai-startups
|
59 |
+
Elicit,Elicit is a generative AI research tool for finding and analyzing academic papers.,https://explodingtopics.com/blog/generative-ai-startups
|
60 |
+
TalkPal,TalkPal is an AI-powered language learning platform that personalizes sessions based on the user's level.,https://explodingtopics.com/blog/generative-ai-startups
|
61 |
+
Dubverse,Dubverse is an AI video dubbing platform capable of translating videos into multiple languages.,https://explodingtopics.com/blog/generative-ai-startups
|
62 |
+
Codeium,Codeium is an AI-powered toolkit for developers that auto-generates and explains code.,https://explodingtopics.com/blog/generative-ai-startups
|
63 |
+
Fliki,Fliki is an AI video and audio generation platform that incorporates text to speech capabilities.,https://explodingtopics.com/blog/generative-ai-startups
|
64 |
+
LOVO AI,LOVO is an AI voice generator startup focused on text-to-speech and voice cloning.,https://explodingtopics.com/blog/generative-ai-startups
|
65 |
+
Decktopus,"Decktopus creates presentations from prompts, streamlining slide generation.",https://explodingtopics.com/blog/generative-ai-startups
|
66 |
+
Character.ai,Character AI is a generative AI platform that generates customizable animated chatbots.,https://explodingtopics.com/blog/generative-ai-startups
|
67 |
+
Descript,Descript is a generative AI video and audio editing application catering to podcasters and videographers.,https://explodingtopics.com/blog/generative-ai-startups
|
68 |
+
Papercup,"Papercup uses AI to translate speech and dubbing, creating realistic voiceovers.",https://explodingtopics.com/blog/generative-ai-startups
|
69 |
+
Vizcom,Vizcom is a generative AI tool that aids designers in creating 3D concept drawings.,https://explodingtopics.com/blog/generative-ai-startups
|
70 |
+
Vidnoz,Vidnoz is a free AI video platform aimed at reducing costs and increasing productivity.,https://explodingtopics.com/blog/generative-ai-startups
|
71 |
+
Scalenut,Scalenut is an AI-powered SEO and content marketing platform that automates content creation.,https://explodingtopics.com/blog/generative-ai-startups
|
72 |
+
Huma.AI,"A generative AI for life sciences SaaS platform, recognized by Gartner in multiple reports and collaborating with OpenAI for a validated GenAI solution for medical affairs.",https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
73 |
+
Viz.ai,"A medical imaging startup specializing in stroke care and expanded into cardiology and oncology, using LLMs for early disease detection.",https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
74 |
+
Arionkoder,"A product development studio and AI lab specializing in AI, computer vision, and ML solutions for healthcare.",https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
75 |
+
HeHealth,Delivers AI and LLM technologies for efficient recommendations for male healthcare.,https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
76 |
+
HOPPR,A multimodal imaging platform improving communication and medical processes through deep image analysis.,https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
77 |
+
Medical IP,A medical metaverse solution utilizing generative AI for streamlined medical imaging segmentation.,https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
78 |
+
NexusMD,An LLM-powered medical imaging platform automating medical imaging data capture across service sites.,https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
79 |
+
Abridge,"A leading generative AI for clinical documentation, converting patient-clinician conversations into structured notes.",https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
80 |
+
Autonomize AI,A healthcare-optimized AI platform that cuts clinical administrative time and accelerates care gap closures.,https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
81 |
+
DeepScribe,"A med-tech firm leveraging LLMs to automate clinical documentation, reducing clinician burnout.",https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
82 |
+
HiLabs,Utilizes AI and LLMs to refine data and identify care gaps for large health plans.,https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
83 |
+
Nabla,"Offers Copilot, an ambient AI for physicians that streamlines clinical note generation.",https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
84 |
+
AgentifAI,A voice-first AI assistant for healthcare enhancing patient experience.,https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
85 |
+
Artisight,An end-to-end sensor fusion platform deployed in hospitals to enhance operational efficiency.,https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
86 |
+
dacadoo,"A digital health platform connecting to various devices, with plans to incorporate an LLM-based streaming model.",https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
87 |
+
Hippocratic AI,Developing a safety-focused LLM for patient-facing applications.,https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
88 |
+
Idoven,An AI-powered cardiology platform with deep-learning models for patient diagnosis.,https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
89 |
+
Inference Analytics,A generative AI healthcare platform trained on a vast dataset for various impactful healthcare use cases.,https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
90 |
+
Pingoo,"An AI health chatbot providing personalized health education, serving various health systems.",https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
91 |
+
Talkie.ai,Automates patient phone interactions using AI voice and LLM technology for healthcare organizations.,https://www.linkedin.com/pulse/20-gen-ai-healthcare-startups-shaping-future-recap-from-renee-yao-q7lkc
|
92 |
+
Jasper,Jasper (YC GenAI company) who became one of the fastest growing startups of all time is losing customers fast. It is often cited as an example of how GenAI companies struggle to develop a moat.,https://www.reddit.com/r/ycombinator/comments/16xhwz7/is_it_really_impossible_for_genai_startups_to/
|
93 |
+
Unnamed GenAI Startup,"We are a GenAI startup that automates tasks in a specific niche area and utilizes a network of LLMs. Our MVP is looking great, and we have a substantial interest from investors.",https://www.reddit.com/r/ycombinator/comments/16xhwz7/is_it_really_impossible_for_genai_startups_to/
|
94 |
+
beautiful.ai,A startup providing tools for creating presentations with an innovative approach.,https://medium.com/point-nine-news/where-are-the-opportunities-for-new-startups-in-generative-ai-f48068b5f8f9
|
95 |
+
Tome,A startup focused on revolutionizing presentation tools.,https://medium.com/point-nine-news/where-are-the-opportunities-for-new-startups-in-generative-ai-f48068b5f8f9
|
96 |
+
Rows,A startup rethinking spreadsheets with an LLM-first perspective.,https://medium.com/point-nine-news/where-are-the-opportunities-for-new-startups-in-generative-ai-f48068b5f8f9
|
97 |
+
mem,A note-taking startup utilizing LLM technology.,https://medium.com/point-nine-news/where-are-the-opportunities-for-new-startups-in-generative-ai-f48068b5f8f9
|
98 |
+
Clio,"A practice management solution for law firms, sitting on a wealth of data to build AI solutions.",https://medium.com/point-nine-news/where-are-the-opportunities-for-new-startups-in-generative-ai-f48068b5f8f9
|
99 |
+
Bench,An accounting startup that has implemented auto-pilot strategies.,https://medium.com/point-nine-news/where-are-the-opportunities-for-new-startups-in-generative-ai-f48068b5f8f9
|
100 |
+
Pilot,A recent entrant in the accounting space following a similar auto-pilot approach.,https://medium.com/point-nine-news/where-are-the-opportunities-for-new-startups-in-generative-ai-f48068b5f8f9
|
101 |
+
a16z,"A venture capital firm that invests in technology companies, with a focus on artificial intelligence and related sectors.",https://a16z.com/ai/
|
102 |
+
AI Stack Products,"A collection of open-source AI stacks and tools for developers, including projects for building AI companions and chatbots.",https://a16z.com/ai/
|
103 |
+
Llama2 Chatbot,A 13 billion parameter language model fine-tuned for chat completions systems.,https://a16z.com/ai/
|
104 |
+
AI Grants,"Grant funding for AI developers focused on LLM training, hosting, and evaluation.",https://a16z.com/ai/
|
105 |
+
AI Canon,"A curated collection of impactful papers, posts, courses, and guides on modern artificial intelligence.",https://a16z.com/ai/
|
106 |
+
Brain Trust,A community-driven platform aimed at training AI models with human data.,https://a16z.com/ai/
|
107 |
+
Character.AI,A platform for creating and interacting with AI characters.,https://a16z.com/ai/
|
108 |
+
Civiti,A startup focused on deploying community-driven AI models.,https://a16z.com/ai/
|
109 |
+
Creta,A developer's assistant using AI for coding support.,https://a16z.com/ai/
|
110 |
+
Databricks,A company that provides a Unified Analytics Platform powered by Apache Spark.,https://a16z.com/ai/
|
111 |
+
ElevenLabs,An AI company that specializes in voice generation and text-to-speech technologies.,https://a16z.com/ai/
|
112 |
+
Freeno,A healthcare-focused AI startup working on early detection systems.,https://a16z.com/ai/
|
113 |
+
OpenAI,An AI research lab dedicated to ensuring that artificial general intelligence benefits all of humanity.,https://a16z.com/ai/
|
114 |
+
Zuma,An AI startup aimed at enhancing user experience through intelligent design.,https://a16z.com/ai/
|
115 |
+
Nautilus Biotechnology,A company transforming the experience and outcome of biotech with AI applications.,https://a16z.com/ai/
|
116 |
+
Saronic,An AI model focused on enhancing predictive analytics in business settings.,https://a16z.com/ai/
|
117 |
+
Mistral AI,A company developing scaled AI technologies for multiple applications.,https://a16z.com/ai/
|
118 |
+
Turquoise Health,An AI-driven health technology startup offering transparent healthcare pricing.,https://a16z.com/ai/
|
119 |
+
Rasa,An open-source framework for building AI assistants.,https://a16z.com/ai/
|
120 |
+
Viggle,A startup revolutionizing how audiences engage with multimedia through AI.,https://a16z.com/ai/
|
121 |
+
Waymark,A company dedicated to automating video production through generative AI techniques.,https://a16z.com/ai/
|
122 |
+
Yolo AI,An emerging company specializing in real-time object detection technology.,https://a16z.com/ai/
|
123 |
+
Luma AI,An innovative AI platform designed to enhance the visual experience in various sectors.,https://a16z.com/ai/
|
124 |
+
OpenAI,"OpenAI is the highest profile company in the generative AI space. Along with its prebuilt AI solutions, OpenAI also offers API and application development support for developers who want to use its models as baselines. Its close partnership with Microsoft and growing commitment to ethical AI continue to boost its reputation and reach.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
125 |
+
Anthropic,"Anthropic’s Claude platform is similar to OpenAI’s ChatGPT, with its large language model and content generation focus. Claude has evolved into an enterprise-level AI assistant with high-level conversational capabilities.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
126 |
+
Cohere,Cohere offers natural language processing (NLP) solutions that are specifically designed to support business operations. Its conversational AI agent allows enterprise users to quickly search for and retrieve company information.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
127 |
+
Glean,"Glean is a generative AI enterprise search company that connects to a variety of enterprise apps and platforms, enabling easy access to business information sources with a focus on AI privacy and governance.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
128 |
+
Jasper,"Jasper’s core product is designed for marketing content generation, effective for establishing a consistent brand voice and managing digital marketing campaigns. Jasper also acquired the AI image platform Clickdrop.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
129 |
+
Hugging Face,"Hugging Face is a community forum focused on AI and ML model development, offering access to BLOOM, an open-source LLM that can generate content in multiple languages.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
130 |
+
Inflection AI,"Inflection AI, founded by former leaders from LinkedIn and DeepMind, focuses on creating conversational AI tools, recently releasing Pi, a personal AI tool.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
131 |
+
Stability AI,"Stability AI specializes in generative AI for image and video content generation, with its app Stable Diffusion being a popular solution in the industry.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
132 |
+
MOSTLY AI,"MOSTLY AI specializes in synthetic data generation, balancing data democratization with anonymity and security requirements particularly useful in banking and telecommunications.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
133 |
+
Lightricks,"Lightricks is known for its social media-friendly image editing app, Facetune, and uses AI for content generation and avatar creation.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
134 |
+
AI21 Labs,"AI21 Labs creates enterprise tools focusing on contextual natural language processing, allowing third-party developers to build on their language models.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
135 |
+
Tabnine,"Tabnine offers generative AI code assistance for software development, helping with code completion and other programming tasks.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
136 |
+
Mistral AI,"Mistral AI provides developer-facing open AI models and deployment resources, focusing on scalable AI solutions.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
137 |
+
Codeium,"Codeium is a generative AI company that provides resources for generating logical code, with features for autocomplete and contextual knowledge.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
138 |
+
Clarifai,"Clarifai's multipurpose platform allows users to build, deploy, and manage AI data projects across various sectors.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
139 |
+
Gong,"Gong provides revenue intelligence solutions that use generative AI to enhance sales coaching, customer service engagement, and revenue forecasting.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
140 |
+
Twain,"Twain is an AI writing assistant that helps sales professionals generate captivating content, particularly for outreach emails.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
141 |
+
Bertha.ai,"Bertha.ai specializes in content generation solutions for WordPress and similar platforms, assisting with written content and imagery.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
142 |
+
Tome,Tome is a creative generative AI platform known for its versatile interface to create presentations and reports.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
143 |
+
CopyAI,CopyAI's platform supports marketing and sales professionals in generating effective go-to-market strategies.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
144 |
+
Synthesia,"Synthesia focuses on AI-driven video creation, allowing users to generate professional-quality videos based on simple text inputs.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
145 |
+
Midjourney,"Midjourney offers a generative AI solution for image and artwork creation, notable for its advanced editing features.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
146 |
+
MURF.AI,"MURF.AI provides text-to-speech solutions with bidirectional voice capabilities, designed for creative content production.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
147 |
+
PlayHT,PlayHT specializes in AI voice generation and personalized podcast content creation.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
148 |
+
ElevenLabs,ElevenLabs is renowned for its high-quality voice generation technology and enterprise scalability.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
149 |
+
Colossyan,Colossyan offers tools for creating corporate training videos without the need for actors or original scripting.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
150 |
+
AssemblyAI,AssemblyAI provides speech-to-text modeling and transcription solutions with strong analysis capacities.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
151 |
+
Plask,Plask automates animation processes and simplifies creation of hyper-realistic 3D motion videos.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
152 |
+
LOVO,LOVO delivers video and voice AI generation solutions through its comprehensive platform called Genny.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
153 |
+
DeepBrain AI,"DeepBrain AI produces AI-generated videos including interactive virtual assistants, enhancing accessibility in public service.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
154 |
+
Elai.io,"Elai.io provides collaborative tools for AI video generation, tailored for business audiences.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
155 |
+
Sudowrite,"Sudowrite supports writers by expanding on story outlines, generating ideas, and creating AI art.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
156 |
+
Tavus,Tavus enables automated video generation personalized for each viewer's characteristics.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
157 |
+
Hippocratic AI,Hippocratic AI offers a generative AI platform for healthcare focused on patient care and HIPAA compliance.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
158 |
+
Paige AI,Paige AI integrates generative AI for optimizing cancer diagnostics and pathology.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
159 |
+
Iambic Therapeutics,Iambic Therapeutics optimizes drug discovery using machine learning in the oncology sector.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
160 |
+
Insilico Medicine,Insilico Medicine uses AI for efficient drug development across various medical fields.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
161 |
+
Etcembly,Etcembly enhances T-cell receptor immunotherapies through machine learning advancements.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
162 |
+
Biomatter,Biomatter employs AI for intelligent architecture in protein design across multiple sectors.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
163 |
+
Activ Surgical,Activ Surgical utilizes AI for enhanced surgical guidance and visualization.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
164 |
+
Kaliber Labs,Kaliber Labs designs AI-powered surgical software for improved communication and patient experiences.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
165 |
+
Osmo,Osmo utilizes machine learning in olfactory science to predict smells based on molecular structure.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
166 |
+
Aqemia,Aqemia focuses on AI-assisted drug discovery incorporating both quantum and statistical mechanics.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
167 |
+
Synthetaic,Synthetaic's platform generates AI models for analyzing unstructured and unlabeled datasets.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
168 |
+
Synthesis AI,Synthesis AI develops synthetic data-driven imagery and human simulations for ethical AI applications.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
169 |
+
Syntho,Syntho generates synthetic data twins for analytics and product demos while ensuring ease of use.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
170 |
+
GenRocket,GenRocket emphasizes automation in synthetic data generation and management for various industries.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
171 |
+
Gridspace,Gridspace creates hybrid voice AI and human agent solutions for improved contact center management.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
172 |
+
Revery AI,Revery AI utilizes generative AI to create virtual dressing rooms and smart shopping assistants.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
173 |
+
Veesual,Veesual offers deep learning-based virtual try-on solutions for e-commerce.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
174 |
+
Frame AI,Frame AI analyzes customer interactions to provide insights for improving customer service.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
175 |
+
Zowie,Zowie specializes in AI customer service technology tailored for e-commerce environments.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
176 |
+
Forethought,Forethought provides generative AI solutions for optimizing customer service workflows.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
177 |
+
Lily AI,Lily AI employs AI to enhance product management and customer service experiences for retailers.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
178 |
+
Runway,Runway enables filmmakers to use AI for cinema-quality video production.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
179 |
+
Latitude.io,"Latitude.io offers AI-driven gaming experiences, allowing users to create dynamic narratives.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
180 |
+
Character.AI,Character.AI allows users to develop and interact with user-created virtual characters.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
181 |
+
Charisma Entertainment,Charisma Entertainment creates engaging narratives and character-driven storylines for various media.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
182 |
+
Replika,Replika generates AI companions for personalized conversations and social interactions.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
183 |
+
Aimi.fm,Aimi.fm provides users with a generative AI music player for composing customized music loops.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
184 |
+
Inworld AI,Inworld AI utilizes generative AI to enhance the realism of non-player characters in gaming and media.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
185 |
+
SOUNDRAW,SOUNDRAW enables tailored music composition for various media including videos and games.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
186 |
+
Notion,Notion offers AI-assisted task management tools for improved workflow efficiency.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
187 |
+
Harvey,Harvey targets the legal sector with generative AI-driven professional services support.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
188 |
+
Ironclad,Ironclad provides AI contract management tools for simplifying contract workflows across industries.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
189 |
+
Taskade,Taskade focuses on AI-powered task management and collaborative tools for creative projects.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
190 |
+
Humata,"Humata helps users extract useful insights from documents and files, enhancing knowledge management.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
191 |
+
Simplifai,"Simplifai automates banking and finance processes, offering solutions tailored to regulatory requirements.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
192 |
+
PatentPal,"PatentPal generates drafts for patent specifications, optimizing the intellectual property protection process.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
193 |
+
Adept AI,Adept AI utilizes natural language processing to enhance workplace interactions and automate workflows.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
194 |
+
Perplexity AI,"Perplexity AI offers a personalized AI search engine, resembling chatbots with more detailed outputs.",https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
195 |
+
Andi,Andi is a friendly generative AI search bot that summarizes web information efficiently.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
196 |
+
You.com,You.com is a private search engine that uses AI to summarize and personalize search results.,https://www.eweek.com/artificial-intelligence/generative-ai-startups/
|
197 |
+
Cohere Command,"Command models are used by companies to build production-ready, scalable and efficient AI-powered applications.",https://cohere.com/
|
198 |
+
Cohere Embed,"Unlocking the full potential of your enterprise data with the highest performing embedding model, supporting over 100 languages.",https://cohere.com/
|
199 |
+
Cohere Rerank,"Surfaces the industry’s most accurate responses, combining Rerank and Embed for reliable and up-to-date responses.",https://cohere.com/
|
200 |
+
```
|
demos/search_on_site_and_date.md
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
This following query will only use the information from openai.com that are updated in the previous
|
2 |
+
day. The behavior is similar to the "site:openai.com" and "date-restrict" search parameters in Google
|
3 |
+
search.
|
4 |
+
|
5 |
+
```
|
6 |
+
% python ask.py -q "OpenAI Swarm Framework" -d 1 -s openai.com
|
7 |
+
✅ Found 10 links for query: OpenAI Swarm Framework
|
8 |
+
✅ Scraping the URLs ...
|
9 |
+
✅ Scraped 10 URLs ...
|
10 |
+
✅ Chunking the text ...
|
11 |
+
✅ Saving to vector DB ...
|
12 |
+
✅ Querying the vector DB to get context ...
|
13 |
+
✅ Running inference with context ...
|
14 |
+
|
15 |
+
# Answer
|
16 |
+
|
17 |
+
OpenAI Swarm Framework is an experimental platform designed for building, orchestrating, and
|
18 |
+
deploying multi-agent systems, enabling multiple AI agents to collaborate on complex tasks. It contrasts
|
19 |
+
with traditional single-agent models by facilitating agent interaction and coordination, thus enhancing
|
20 |
+
efficiency[5][9]. The framework provides developers with a way to orchestrate these agent systems in
|
21 |
+
a lightweight manner, leveraging Node.js for scalable applications[1][4].
|
22 |
+
|
23 |
+
One implementation of this framework is Swarm.js, which serves as a Node.js SDK, allowing users to
|
24 |
+
create and manage agents that perform tasks and hand off conversations. Swarm.js is positioned as
|
25 |
+
an educational tool, making it accessible for both beginners and experts, although it may still contain
|
26 |
+
bugs and is currently lightweight[1][3][7]. This new approach emphasizes multi-agent collaboration and is
|
27 |
+
well-suited for back-end development, requiring some programming expertise for effective implementation[9].
|
28 |
+
|
29 |
+
Overall, OpenAI Swarm facilitates a shift in how AI systems can collaborate, differing from existing
|
30 |
+
OpenAI tools by focusing on backend orchestration rather than user-interactive front-end applications[9].
|
31 |
+
|
32 |
+
# References
|
33 |
+
|
34 |
+
[1] https://community.openai.com/t/introducing-swarm-js-node-js-implementation-of-openai-swarm/977510
|
35 |
+
[2] https://community.openai.com/t/introducing-swarm-js-a-node-js-implementation-of-openai-swarm/977510
|
36 |
+
[3] https://community.openai.com/t/introducing-swarm-js-node-js-implementation-of-openai-swarm/977510
|
37 |
+
[4] https://community.openai.com/t/introducing-swarm-js-a-node-js-implementation-of-openai-swarm/977510
|
38 |
+
[5] https://community.openai.com/t/swarm-some-initial-insights/976602
|
39 |
+
[6] https://community.openai.com/t/swarm-some-initial-insights/976602
|
40 |
+
[7] https://community.openai.com/t/introducing-swarm-js-node-js-implementation-of-openai-swarm/977510
|
41 |
+
[8] https://community.openai.com/t/introducing-swarm-js-a-node-js-implementation-of-openai-swarm/977510
|
42 |
+
[9] https://community.openai.com/t/swarm-some-initial-insights/976602
|
43 |
+
[10] https://community.openai.com/t/swarm-some-initial-insights/976602
|
44 |
+
```
|
instructions/extract_example.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
class CompanyInfo(BaseModel):
|
2 |
+
name: str
|
3 |
+
description: str
|
instructions/links.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# you can specify a --url-list-file argument with links similar to the ones below
|
2 |
+
# ask.py will crawl these pages and answer the question based on their contents
|
3 |
+
https://en.wikipedia.org/wiki/Large_language_model
|
4 |
+
https://en.wikipedia.org/wiki/Retrieval-augmented_generation
|
requirements.txt
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
click==8.1.7
|
2 |
+
requests==2.31.0
|
3 |
+
openai==1.40.2
|
4 |
+
jinja2==3.1.3
|
5 |
+
bs4==0.0.2
|
6 |
+
lxml==4.8.0
|
7 |
+
python-dotenv==1.0.1
|
8 |
+
duckdb==1.1.2
|
9 |
+
gradio==5.3.0
|