Spaces:
Sleeping
Sleeping
JarvisChan630
commited on
Commit
•
75309ed
0
Parent(s):
first commit
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .chainlit/config.toml +121 -0
- .chainlit/translations/en-US.json +231 -0
- .chainlit/translations/pt-BR.json +155 -0
- .gitignore +13 -0
- Dockerfile +37 -0
- Docs/Example Outputs/Llama 3.1 Newsletter.MD +44 -0
- Docs/Example Outputs/Source Cheap GPUs.MD +24 -0
- Docs/Introduction to Jar3d.MD +93 -0
- Docs/Meta-Prompting Overview.MD +70 -0
- Docs/Overview of Agentic RAG.MD +74 -0
- LICENSE +21 -0
- README.md +165 -0
- __init__.py +0 -0
- agent_memory/jar3d_final_response_previous_run.txt +131 -0
- agents/base_agent.py +111 -0
- agents/jar3d_agent.py +910 -0
- agents/legacy/jar3d_agent.py +655 -0
- agents/legacy/jar3d_agent_backup.py +734 -0
- agents/meta_agent.py +482 -0
- app/chat.py +0 -0
- chainlit.md +37 -0
- chat.py +395 -0
- config/load_configs.py +19 -0
- docker-compose.yaml +28 -0
- fastembed_cache/.gitkeep +0 -0
- legacy/chat copy.py +329 -0
- models/__init__.py +0 -0
- models/llms.py +450 -0
- prompt_engineering/chat_prompt.md +76 -0
- prompt_engineering/guided_json_lib.py +90 -0
- prompt_engineering/jar3d_meta_prompt.md +235 -0
- prompt_engineering/jar3d_requirements_prompt.md +92 -0
- prompt_engineering/legacy/jar3d_meta_prompt copy.md +226 -0
- prompt_engineering/legacy/jar3d_meta_prompt_backup.md +205 -0
- prompt_engineering/legacy/jar3d_requirements_prompt copy.md +73 -0
- prompt_engineering/legacy/jar3d_requirements_prompt_backup.md +73 -0
- prompt_engineering/meta_prompt.md +167 -0
- requirements.txt +21 -0
- reranker_cache/.gitkeep +0 -0
- tools/__init__.py +0 -0
- tools/advanced_scraper.py +36 -0
- tools/basic_scraper.py +148 -0
- tools/google_serper.py +121 -0
- tools/legacy/offline_graph_rag_tool copy.py +417 -0
- tools/legacy/offline_rag_tool.py +242 -0
- tools/legacy/rag_tool.py +409 -0
- tools/llm_graph_transformer.py +874 -0
- tools/offline_graph_rag_tool.py +430 -0
- tools/offline_graph_rag_tool_with_async.py +425 -0
- utils/__init__.py +0 -0
.chainlit/config.toml
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[project]
|
2 |
+
# Whether to enable telemetry (default: true). No personal data is collected.
|
3 |
+
enable_telemetry = false
|
4 |
+
|
5 |
+
|
6 |
+
# List of environment variables to be provided by each user to use the app.
|
7 |
+
user_env = []
|
8 |
+
|
9 |
+
# Duration (in seconds) during which the session is saved when the connection is lost
|
10 |
+
session_timeout = 3600
|
11 |
+
|
12 |
+
# Enable third parties caching (e.g LangChain cache)
|
13 |
+
cache = false
|
14 |
+
|
15 |
+
# Authorized origins
|
16 |
+
allow_origins = ["*"]
|
17 |
+
|
18 |
+
# Follow symlink for asset mount (see https://github.com/Chainlit/chainlit/issues/317)
|
19 |
+
# follow_symlink = false
|
20 |
+
|
21 |
+
[features]
|
22 |
+
# Show the prompt playground
|
23 |
+
prompt_playground = true
|
24 |
+
|
25 |
+
# Process and display HTML in messages. This can be a security risk (see https://stackoverflow.com/questions/19603097/why-is-it-dangerous-to-render-user-generated-html-or-javascript)
|
26 |
+
unsafe_allow_html = false
|
27 |
+
|
28 |
+
# Process and display mathematical expressions. This can clash with "$" characters in messages.
|
29 |
+
latex = false
|
30 |
+
|
31 |
+
# Automatically tag threads with the current chat profile (if a chat profile is used)
|
32 |
+
auto_tag_thread = true
|
33 |
+
|
34 |
+
# Authorize users to spontaneously upload files with messages
|
35 |
+
[features.spontaneous_file_upload]
|
36 |
+
enabled = true
|
37 |
+
accept = ["*/*"]
|
38 |
+
max_files = 20
|
39 |
+
max_size_mb = 500
|
40 |
+
|
41 |
+
[features.audio]
|
42 |
+
# Threshold for audio recording
|
43 |
+
min_decibels = -45
|
44 |
+
# Delay for the user to start speaking in MS
|
45 |
+
initial_silence_timeout = 3000
|
46 |
+
# Delay for the user to continue speaking in MS. If the user stops speaking for this duration, the recording will stop.
|
47 |
+
silence_timeout = 1500
|
48 |
+
# Above this duration (MS), the recording will forcefully stop.
|
49 |
+
max_duration = 15000
|
50 |
+
# Duration of the audio chunks in MS
|
51 |
+
chunk_duration = 1000
|
52 |
+
# Sample rate of the audio
|
53 |
+
sample_rate = 44100
|
54 |
+
|
55 |
+
[UI]
|
56 |
+
# Name of the app and chatbot.
|
57 |
+
name = "Chatbot"
|
58 |
+
|
59 |
+
# Show the readme while the thread is empty.
|
60 |
+
show_readme_as_default = true
|
61 |
+
|
62 |
+
# Description of the app and chatbot. This is used for HTML tags.
|
63 |
+
# description = ""
|
64 |
+
|
65 |
+
# Large size content are by default collapsed for a cleaner ui
|
66 |
+
default_collapse_content = true
|
67 |
+
|
68 |
+
# The default value for the expand messages settings.
|
69 |
+
default_expand_messages = false
|
70 |
+
|
71 |
+
# Hide the chain of thought details from the user in the UI.
|
72 |
+
hide_cot = false
|
73 |
+
|
74 |
+
# Link to your github repo. This will add a github button in the UI's header.
|
75 |
+
# github = ""
|
76 |
+
|
77 |
+
# Specify a CSS file that can be used to customize the user interface.
|
78 |
+
# The CSS file can be served from the public directory or via an external link.
|
79 |
+
# custom_css = "/public/test.css"
|
80 |
+
|
81 |
+
# Specify a Javascript file that can be used to customize the user interface.
|
82 |
+
# The Javascript file can be served from the public directory.
|
83 |
+
# custom_js = "/public/test.js"
|
84 |
+
|
85 |
+
# Specify a custom font url.
|
86 |
+
# custom_font = "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap"
|
87 |
+
|
88 |
+
# Specify a custom meta image url.
|
89 |
+
# custom_meta_image_url = "https://chainlit-cloud.s3.eu-west-3.amazonaws.com/logo/chainlit_banner.png"
|
90 |
+
|
91 |
+
# Specify a custom build directory for the frontend.
|
92 |
+
# This can be used to customize the frontend code.
|
93 |
+
# Be careful: If this is a relative path, it should not start with a slash.
|
94 |
+
# custom_build = "./public/build"
|
95 |
+
|
96 |
+
[UI.theme]
|
97 |
+
#layout = "wide"
|
98 |
+
#font_family = "Inter, sans-serif"
|
99 |
+
# Override default MUI light theme. (Check theme.ts)
|
100 |
+
[UI.theme.light]
|
101 |
+
#background = "#FAFAFA"
|
102 |
+
#paper = "#FFFFFF"
|
103 |
+
|
104 |
+
[UI.theme.light.primary]
|
105 |
+
#main = "#F80061"
|
106 |
+
#dark = "#980039"
|
107 |
+
#light = "#FFE7EB"
|
108 |
+
|
109 |
+
# Override default MUI dark theme. (Check theme.ts)
|
110 |
+
[UI.theme.dark]
|
111 |
+
#background = "#FAFAFA"
|
112 |
+
#paper = "#FFFFFF"
|
113 |
+
|
114 |
+
[UI.theme.dark.primary]
|
115 |
+
#main = "#F80061"
|
116 |
+
#dark = "#980039"
|
117 |
+
#light = "#FFE7EB"
|
118 |
+
|
119 |
+
|
120 |
+
[meta]
|
121 |
+
generated_by = "1.1.202"
|
.chainlit/translations/en-US.json
ADDED
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"components": {
|
3 |
+
"atoms": {
|
4 |
+
"buttons": {
|
5 |
+
"userButton": {
|
6 |
+
"menu": {
|
7 |
+
"settings": "Settings",
|
8 |
+
"settingsKey": "S",
|
9 |
+
"APIKeys": "API Keys",
|
10 |
+
"logout": "Logout"
|
11 |
+
}
|
12 |
+
}
|
13 |
+
}
|
14 |
+
},
|
15 |
+
"molecules": {
|
16 |
+
"newChatButton": {
|
17 |
+
"newChat": "New Chat"
|
18 |
+
},
|
19 |
+
"tasklist": {
|
20 |
+
"TaskList": {
|
21 |
+
"title": "\ud83d\uddd2\ufe0f Task List",
|
22 |
+
"loading": "Loading...",
|
23 |
+
"error": "An error occured"
|
24 |
+
}
|
25 |
+
},
|
26 |
+
"attachments": {
|
27 |
+
"cancelUpload": "Cancel upload",
|
28 |
+
"removeAttachment": "Remove attachment"
|
29 |
+
},
|
30 |
+
"newChatDialog": {
|
31 |
+
"createNewChat": "Create new chat?",
|
32 |
+
"clearChat": "This will clear the current messages and start a new chat.",
|
33 |
+
"cancel": "Cancel",
|
34 |
+
"confirm": "Confirm"
|
35 |
+
},
|
36 |
+
"settingsModal": {
|
37 |
+
"settings": "Settings",
|
38 |
+
"expandMessages": "Expand Messages",
|
39 |
+
"hideChainOfThought": "Hide Chain of Thought",
|
40 |
+
"darkMode": "Dark Mode"
|
41 |
+
},
|
42 |
+
"detailsButton": {
|
43 |
+
"using": "Using",
|
44 |
+
"running": "Running",
|
45 |
+
"took_one": "Took {{count}} step",
|
46 |
+
"took_other": "Took {{count}} steps"
|
47 |
+
},
|
48 |
+
"auth": {
|
49 |
+
"authLogin": {
|
50 |
+
"title": "Login to access the app.",
|
51 |
+
"form": {
|
52 |
+
"email": "Email address",
|
53 |
+
"password": "Password",
|
54 |
+
"noAccount": "Don't have an account?",
|
55 |
+
"alreadyHaveAccount": "Already have an account?",
|
56 |
+
"signup": "Sign Up",
|
57 |
+
"signin": "Sign In",
|
58 |
+
"or": "OR",
|
59 |
+
"continue": "Continue",
|
60 |
+
"forgotPassword": "Forgot password?",
|
61 |
+
"passwordMustContain": "Your password must contain:",
|
62 |
+
"emailRequired": "email is a required field",
|
63 |
+
"passwordRequired": "password is a required field"
|
64 |
+
},
|
65 |
+
"error": {
|
66 |
+
"default": "Unable to sign in.",
|
67 |
+
"signin": "Try signing in with a different account.",
|
68 |
+
"oauthsignin": "Try signing in with a different account.",
|
69 |
+
"redirect_uri_mismatch": "The redirect URI is not matching the oauth app configuration.",
|
70 |
+
"oauthcallbackerror": "Try signing in with a different account.",
|
71 |
+
"oauthcreateaccount": "Try signing in with a different account.",
|
72 |
+
"emailcreateaccount": "Try signing in with a different account.",
|
73 |
+
"callback": "Try signing in with a different account.",
|
74 |
+
"oauthaccountnotlinked": "To confirm your identity, sign in with the same account you used originally.",
|
75 |
+
"emailsignin": "The e-mail could not be sent.",
|
76 |
+
"emailverify": "Please verify your email, a new email has been sent.",
|
77 |
+
"credentialssignin": "Sign in failed. Check the details you provided are correct.",
|
78 |
+
"sessionrequired": "Please sign in to access this page."
|
79 |
+
}
|
80 |
+
},
|
81 |
+
"authVerifyEmail": {
|
82 |
+
"almostThere": "You're almost there! We've sent an email to ",
|
83 |
+
"verifyEmailLink": "Please click on the link in that email to complete your signup.",
|
84 |
+
"didNotReceive": "Can't find the email?",
|
85 |
+
"resendEmail": "Resend email",
|
86 |
+
"goBack": "Go Back",
|
87 |
+
"emailSent": "Email sent successfully.",
|
88 |
+
"verifyEmail": "Verify your email address"
|
89 |
+
},
|
90 |
+
"providerButton": {
|
91 |
+
"continue": "Continue with {{provider}}",
|
92 |
+
"signup": "Sign up with {{provider}}"
|
93 |
+
},
|
94 |
+
"authResetPassword": {
|
95 |
+
"newPasswordRequired": "New password is a required field",
|
96 |
+
"passwordsMustMatch": "Passwords must match",
|
97 |
+
"confirmPasswordRequired": "Confirm password is a required field",
|
98 |
+
"newPassword": "New password",
|
99 |
+
"confirmPassword": "Confirm password",
|
100 |
+
"resetPassword": "Reset Password"
|
101 |
+
},
|
102 |
+
"authForgotPassword": {
|
103 |
+
"email": "Email address",
|
104 |
+
"emailRequired": "email is a required field",
|
105 |
+
"emailSent": "Please check the email address {{email}} for instructions to reset your password.",
|
106 |
+
"enterEmail": "Enter your email address and we will send you instructions to reset your password.",
|
107 |
+
"resendEmail": "Resend email",
|
108 |
+
"continue": "Continue",
|
109 |
+
"goBack": "Go Back"
|
110 |
+
}
|
111 |
+
}
|
112 |
+
},
|
113 |
+
"organisms": {
|
114 |
+
"chat": {
|
115 |
+
"history": {
|
116 |
+
"index": {
|
117 |
+
"showHistory": "Show history",
|
118 |
+
"lastInputs": "Last Inputs",
|
119 |
+
"noInputs": "Such empty...",
|
120 |
+
"loading": "Loading..."
|
121 |
+
}
|
122 |
+
},
|
123 |
+
"inputBox": {
|
124 |
+
"input": {
|
125 |
+
"placeholder": "Type your message here..."
|
126 |
+
},
|
127 |
+
"speechButton": {
|
128 |
+
"start": "Start recording",
|
129 |
+
"stop": "Stop recording"
|
130 |
+
},
|
131 |
+
"SubmitButton": {
|
132 |
+
"sendMessage": "Send message",
|
133 |
+
"stopTask": "Stop Task"
|
134 |
+
},
|
135 |
+
"UploadButton": {
|
136 |
+
"attachFiles": "Attach files"
|
137 |
+
},
|
138 |
+
"waterMark": {
|
139 |
+
"text": "Built with"
|
140 |
+
}
|
141 |
+
},
|
142 |
+
"Messages": {
|
143 |
+
"index": {
|
144 |
+
"running": "Running",
|
145 |
+
"executedSuccessfully": "executed successfully",
|
146 |
+
"failed": "failed",
|
147 |
+
"feedbackUpdated": "Feedback updated",
|
148 |
+
"updating": "Updating"
|
149 |
+
}
|
150 |
+
},
|
151 |
+
"dropScreen": {
|
152 |
+
"dropYourFilesHere": "Drop your files here"
|
153 |
+
},
|
154 |
+
"index": {
|
155 |
+
"failedToUpload": "Failed to upload",
|
156 |
+
"cancelledUploadOf": "Cancelled upload of",
|
157 |
+
"couldNotReachServer": "Could not reach the server",
|
158 |
+
"continuingChat": "Continuing previous chat"
|
159 |
+
},
|
160 |
+
"settings": {
|
161 |
+
"settingsPanel": "Settings panel",
|
162 |
+
"reset": "Reset",
|
163 |
+
"cancel": "Cancel",
|
164 |
+
"confirm": "Confirm"
|
165 |
+
}
|
166 |
+
},
|
167 |
+
"threadHistory": {
|
168 |
+
"sidebar": {
|
169 |
+
"filters": {
|
170 |
+
"FeedbackSelect": {
|
171 |
+
"feedbackAll": "Feedback: All",
|
172 |
+
"feedbackPositive": "Feedback: Positive",
|
173 |
+
"feedbackNegative": "Feedback: Negative"
|
174 |
+
},
|
175 |
+
"SearchBar": {
|
176 |
+
"search": "Search"
|
177 |
+
}
|
178 |
+
},
|
179 |
+
"DeleteThreadButton": {
|
180 |
+
"confirmMessage": "This will delete the thread as well as it's messages and elements.",
|
181 |
+
"cancel": "Cancel",
|
182 |
+
"confirm": "Confirm",
|
183 |
+
"deletingChat": "Deleting chat",
|
184 |
+
"chatDeleted": "Chat deleted"
|
185 |
+
},
|
186 |
+
"index": {
|
187 |
+
"pastChats": "Past Chats"
|
188 |
+
},
|
189 |
+
"ThreadList": {
|
190 |
+
"empty": "Empty...",
|
191 |
+
"today": "Today",
|
192 |
+
"yesterday": "Yesterday",
|
193 |
+
"previous7days": "Previous 7 days",
|
194 |
+
"previous30days": "Previous 30 days"
|
195 |
+
},
|
196 |
+
"TriggerButton": {
|
197 |
+
"closeSidebar": "Close sidebar",
|
198 |
+
"openSidebar": "Open sidebar"
|
199 |
+
}
|
200 |
+
},
|
201 |
+
"Thread": {
|
202 |
+
"backToChat": "Go back to chat",
|
203 |
+
"chatCreatedOn": "This chat was created on"
|
204 |
+
}
|
205 |
+
},
|
206 |
+
"header": {
|
207 |
+
"chat": "Chat",
|
208 |
+
"readme": "Readme"
|
209 |
+
}
|
210 |
+
}
|
211 |
+
},
|
212 |
+
"hooks": {
|
213 |
+
"useLLMProviders": {
|
214 |
+
"failedToFetchProviders": "Failed to fetch providers:"
|
215 |
+
}
|
216 |
+
},
|
217 |
+
"pages": {
|
218 |
+
"Design": {},
|
219 |
+
"Env": {
|
220 |
+
"savedSuccessfully": "Saved successfully",
|
221 |
+
"requiredApiKeys": "Required API Keys",
|
222 |
+
"requiredApiKeysInfo": "To use this app, the following API keys are required. The keys are stored on your device's local storage."
|
223 |
+
},
|
224 |
+
"Page": {
|
225 |
+
"notPartOfProject": "You are not part of this project."
|
226 |
+
},
|
227 |
+
"ResumeButton": {
|
228 |
+
"resumeChat": "Resume Chat"
|
229 |
+
}
|
230 |
+
}
|
231 |
+
}
|
.chainlit/translations/pt-BR.json
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"components": {
|
3 |
+
"atoms": {
|
4 |
+
"buttons": {
|
5 |
+
"userButton": {
|
6 |
+
"menu": {
|
7 |
+
"settings": "Configura\u00e7\u00f5es",
|
8 |
+
"settingsKey": "S",
|
9 |
+
"APIKeys": "Chaves de API",
|
10 |
+
"logout": "Sair"
|
11 |
+
}
|
12 |
+
}
|
13 |
+
}
|
14 |
+
},
|
15 |
+
"molecules": {
|
16 |
+
"newChatButton": {
|
17 |
+
"newChat": "Nova Conversa"
|
18 |
+
},
|
19 |
+
"tasklist": {
|
20 |
+
"TaskList": {
|
21 |
+
"title": "\ud83d\uddd2\ufe0f Lista de Tarefas",
|
22 |
+
"loading": "Carregando...",
|
23 |
+
"error": "Ocorreu um erro"
|
24 |
+
}
|
25 |
+
},
|
26 |
+
"attachments": {
|
27 |
+
"cancelUpload": "Cancelar envio",
|
28 |
+
"removeAttachment": "Remover anexo"
|
29 |
+
},
|
30 |
+
"newChatDialog": {
|
31 |
+
"createNewChat": "Criar novo chat?",
|
32 |
+
"clearChat": "Isso limpar\u00e1 as mensagens atuais e iniciar\u00e1 uma nova conversa.",
|
33 |
+
"cancel": "Cancelar",
|
34 |
+
"confirm": "Confirmar"
|
35 |
+
},
|
36 |
+
"settingsModal": {
|
37 |
+
"expandMessages": "Expandir Mensagens",
|
38 |
+
"hideChainOfThought": "Esconder Sequ\u00eancia de Pensamento",
|
39 |
+
"darkMode": "Modo Escuro"
|
40 |
+
}
|
41 |
+
},
|
42 |
+
"organisms": {
|
43 |
+
"chat": {
|
44 |
+
"history": {
|
45 |
+
"index": {
|
46 |
+
"lastInputs": "\u00daltimas Entradas",
|
47 |
+
"noInputs": "Vazio...",
|
48 |
+
"loading": "Carregando..."
|
49 |
+
}
|
50 |
+
},
|
51 |
+
"inputBox": {
|
52 |
+
"input": {
|
53 |
+
"placeholder": "Digite sua mensagem aqui..."
|
54 |
+
},
|
55 |
+
"speechButton": {
|
56 |
+
"start": "Iniciar grava\u00e7\u00e3o",
|
57 |
+
"stop": "Parar grava\u00e7\u00e3o"
|
58 |
+
},
|
59 |
+
"SubmitButton": {
|
60 |
+
"sendMessage": "Enviar mensagem",
|
61 |
+
"stopTask": "Parar Tarefa"
|
62 |
+
},
|
63 |
+
"UploadButton": {
|
64 |
+
"attachFiles": "Anexar arquivos"
|
65 |
+
},
|
66 |
+
"waterMark": {
|
67 |
+
"text": "Constru\u00eddo com"
|
68 |
+
}
|
69 |
+
},
|
70 |
+
"Messages": {
|
71 |
+
"index": {
|
72 |
+
"running": "Executando",
|
73 |
+
"executedSuccessfully": "executado com sucesso",
|
74 |
+
"failed": "falhou",
|
75 |
+
"feedbackUpdated": "Feedback atualizado",
|
76 |
+
"updating": "Atualizando"
|
77 |
+
}
|
78 |
+
},
|
79 |
+
"dropScreen": {
|
80 |
+
"dropYourFilesHere": "Solte seus arquivos aqui"
|
81 |
+
},
|
82 |
+
"index": {
|
83 |
+
"failedToUpload": "Falha ao enviar",
|
84 |
+
"cancelledUploadOf": "Envio cancelado de",
|
85 |
+
"couldNotReachServer": "N\u00e3o foi poss\u00edvel conectar ao servidor",
|
86 |
+
"continuingChat": "Continuando o chat anterior"
|
87 |
+
},
|
88 |
+
"settings": {
|
89 |
+
"settingsPanel": "Painel de Configura\u00e7\u00f5es",
|
90 |
+
"reset": "Redefinir",
|
91 |
+
"cancel": "Cancelar",
|
92 |
+
"confirm": "Confirmar"
|
93 |
+
}
|
94 |
+
},
|
95 |
+
"threadHistory": {
|
96 |
+
"sidebar": {
|
97 |
+
"filters": {
|
98 |
+
"FeedbackSelect": {
|
99 |
+
"feedbackAll": "Feedback: Todos",
|
100 |
+
"feedbackPositive": "Feedback: Positivo",
|
101 |
+
"feedbackNegative": "Feedback: Negativo"
|
102 |
+
},
|
103 |
+
"SearchBar": {
|
104 |
+
"search": "Buscar"
|
105 |
+
}
|
106 |
+
},
|
107 |
+
"DeleteThreadButton": {
|
108 |
+
"confirmMessage": "Isso deletar\u00e1 a conversa, assim como suas mensagens e elementos.",
|
109 |
+
"cancel": "Cancelar",
|
110 |
+
"confirm": "Confirmar",
|
111 |
+
"deletingChat": "Deletando conversa",
|
112 |
+
"chatDeleted": "Conversa deletada"
|
113 |
+
},
|
114 |
+
"index": {
|
115 |
+
"pastChats": "Conversas Anteriores"
|
116 |
+
},
|
117 |
+
"ThreadList": {
|
118 |
+
"empty": "Vazio..."
|
119 |
+
},
|
120 |
+
"TriggerButton": {
|
121 |
+
"closeSidebar": "Fechar barra lateral",
|
122 |
+
"openSidebar": "Abrir barra lateral"
|
123 |
+
}
|
124 |
+
},
|
125 |
+
"Thread": {
|
126 |
+
"backToChat": "Voltar para a conversa",
|
127 |
+
"chatCreatedOn": "Esta conversa foi criada em"
|
128 |
+
}
|
129 |
+
},
|
130 |
+
"header": {
|
131 |
+
"chat": "Conversa",
|
132 |
+
"readme": "Leia-me"
|
133 |
+
}
|
134 |
+
},
|
135 |
+
"hooks": {
|
136 |
+
"useLLMProviders": {
|
137 |
+
"failedToFetchProviders": "Falha ao buscar provedores:"
|
138 |
+
}
|
139 |
+
},
|
140 |
+
"pages": {
|
141 |
+
"Design": {},
|
142 |
+
"Env": {
|
143 |
+
"savedSuccessfully": "Salvo com sucesso",
|
144 |
+
"requiredApiKeys": "Chaves de API necess\u00e1rias",
|
145 |
+
"requiredApiKeysInfo": "Para usar este aplicativo, as seguintes chaves de API s\u00e3o necess\u00e1rias. As chaves s\u00e3o armazenadas localmente em seu dispositivo."
|
146 |
+
},
|
147 |
+
"Page": {
|
148 |
+
"notPartOfProject": "Voc\u00ea n\u00e3o faz parte deste projeto."
|
149 |
+
},
|
150 |
+
"ResumeButton": {
|
151 |
+
"resumeChat": "Continuar Conversa"
|
152 |
+
}
|
153 |
+
}
|
154 |
+
}
|
155 |
+
}
|
.gitignore
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/config/config.yaml
|
2 |
+
__pycache__/
|
3 |
+
*.pyc
|
4 |
+
/.vscode
|
5 |
+
# /docker-compose.yaml
|
6 |
+
|
7 |
+
# Ignore all files in fastembed_cache except .gitkeep
|
8 |
+
fastembed_cache/*
|
9 |
+
!fastembed_cache/.gitkeep
|
10 |
+
|
11 |
+
# Ignore all files in reranker_cache except .gitkeep
|
12 |
+
reranker_cache/*
|
13 |
+
!reranker_cache/.gitkeep
|
Dockerfile
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Dockerfile for Jar3d
|
2 |
+
FROM python:3.11-slim
|
3 |
+
|
4 |
+
# Set working directory
|
5 |
+
WORKDIR /app
|
6 |
+
|
7 |
+
# Install minimal required build tools and dependencies for Playwright
|
8 |
+
RUN apt-get update && apt-get install -y \
|
9 |
+
gcc \
|
10 |
+
g++ \
|
11 |
+
python3-dev \
|
12 |
+
wget \
|
13 |
+
gnupg \
|
14 |
+
&& rm -rf /var/lib/apt/lists/*
|
15 |
+
|
16 |
+
# Install dependencies
|
17 |
+
COPY requirements.txt .
|
18 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
19 |
+
|
20 |
+
# Install Playwright and its dependencies
|
21 |
+
# RUN playwright install-deps
|
22 |
+
# RUN playwright install chromium firefox webkit
|
23 |
+
|
24 |
+
# Copy the rest of the application
|
25 |
+
COPY . .
|
26 |
+
|
27 |
+
# Ensure the config file is copied to the correct location
|
28 |
+
# COPY config/config.yaml /app/config/config.yaml
|
29 |
+
COPY config/config.yaml /config/config.yaml
|
30 |
+
COPY agent_memory/jar3d_final_response_previous_run.txt /app/agent_memory/jar3d_final_response_previous_run.txt
|
31 |
+
|
32 |
+
|
33 |
+
# Expose the port Chainlit runs on
|
34 |
+
EXPOSE 8000
|
35 |
+
|
36 |
+
# Command to run the application
|
37 |
+
CMD ["chainlit", "run", "chat.py", "--port", "8000"]
|
Docs/Example Outputs/Llama 3.1 Newsletter.MD
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
🚀 TLDR: Llama 3.1 - Meta's Open-Source AI Powerhouse
|
2 |
+
|
3 |
+
Meta has released [Llama 3.1](https://ai.meta.com/blog/meta-llama-3-1/), the latest iteration of their open-source large language model family. Here's what you need to know:
|
4 |
+
|
5 |
+
Key Features:
|
6 |
+
- Open-source: Fully customizable and downloadable
|
7 |
+
- Multilingual: Supports 8 languages
|
8 |
+
- Extended context: 128K tokens (vs. 8K in previous versions)
|
9 |
+
- Multiple sizes: 8B, 70B, and 405B parameter models
|
10 |
+
|
11 |
+
Performance Highlights:
|
12 |
+
Llama 3.1 405B is competitive with leading closed-source models across various tasks. Here's a quick comparison:
|
13 |
+
|
14 |
+
| Model | Python Coding | Grade School Math | Reasoning Tasks |
|
15 |
+
|-------|---------------|-------------------|-----------------|
|
16 |
+
| Llama 3.1 405B | 15% better | Slightly better | Competitive |
|
17 |
+
| GPT-4 | Baseline | Baseline | Excels |
|
18 |
+
| Claude 3.5 | N/A | N/A | Top performer |
|
19 |
+
|
20 |
+
According to [recent benchmarks](https://www.reddit.com/r/LocalLLaMA/comments/1eayiut/llama_31_on_simple_bench_beat_gemini_15_pro_and/), Llama 3.1 outperformed Gemini 1.5 Pro and GPT-4 on the SIMPLE bench, though Claude 3.5 still leads overall.
|
21 |
+
|
22 |
+
Potential Applications:
|
23 |
+
- Synthetic data generation
|
24 |
+
- Model distillation
|
25 |
+
- Long-form text summarization
|
26 |
+
- Multilingual conversational agents
|
27 |
+
- Coding assistants
|
28 |
+
|
29 |
+
Cost and Accessibility:
|
30 |
+
Llama 3.1 models offer significant cost savings compared to GPT-4, with some estimates suggesting up to 50 times lower costs for input tokens and 76 times lower for output tokens.
|
31 |
+
|
32 |
+
Developer-Friendly Features:
|
33 |
+
- [Llama Guard 3](https://ai.meta.com/blog/meta-llama-3-1/): A multilingual safety model
|
34 |
+
- Prompt Guard: A prompt injection filter
|
35 |
+
- Open-source sample applications
|
36 |
+
|
37 |
+
What's Next:
|
38 |
+
Meta hints at upcoming features like longer context lengths and expanded capabilities. The [Llama Stack API](https://github.com/facebookresearch/llama-recipes/blob/main/docs/LlamaStack.md) is also in development, aiming to standardize interfaces for third-party projects.
|
39 |
+
|
40 |
+
With over 300 million downloads of all Llama versions to date, the open-source AI revolution is gaining momentum. As developers explore Llama 3.1's capabilities, we can expect innovative applications and further advancements in accessible AI technology.
|
41 |
+
|
42 |
+
For a deep dive into Llama 3.1's capabilities and comparisons with other models, check out [this comprehensive analysis](https://www.vellum.ai/blog/llama-3-70b-vs-gpt-4-comparison-analysis).
|
43 |
+
|
44 |
+
Stay tuned for more updates as the AI community continues to push the boundaries of open-source language models!
|
Docs/Example Outputs/Source Cheap GPUs.MD
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Based on the requirements and available options, here is the recommendation for a high-performance NVIDIA GPU for running state-of-the-art LLMs:
|
2 |
+
|
3 |
+
Recommended GPU: PNY NVIDIA RTX A6000 48GB GDDR6 Graphics Card
|
4 |
+
Price: £4,341.99
|
5 |
+
Source: https://www.cclonline.com/vcnrtxa6000-sb-pny-nvidia-rtx-a6000-48gb-professional-graphics-card-397526/
|
6 |
+
|
7 |
+
Justification:
|
8 |
+
|
9 |
+
Memory Requirement: The RTX A6000 meets the minimum 48GB memory requirement.
|
10 |
+
CUDA Compatibility: As a recent NVIDIA professional GPU, it is compatible with CUDA 12.4 and above.
|
11 |
+
Performance: While not as powerful as the A100 or H100, the RTX A6000 is still capable of running state-of-the-art LLMs with its 48GB of GDDR6 memory and 10,752 CUDA cores.
|
12 |
+
Cost: At £4,341.99, it falls within the £5,000 budget constraint.
|
13 |
+
Availability: It is available as a new product, ensuring reliability and warranty coverage.
|
14 |
+
Alternative Options:
|
15 |
+
|
16 |
+
Used NVIDIA Tesla A100 80GB PCIe: £8,000.00 (https://www.ebay.co.uk/itm/326214476335)
|
17 |
+
|
18 |
+
Exceeds memory and performance requirements but is over budget.
|
19 |
+
Used condition may pose some risks.
|
20 |
+
PNY NVIDIA A100 PCIe 40GB: £12,734.00 (https://it-market.com/en/components/modules/nvidia/900-21001-0000-000/953298-686528)
|
21 |
+
|
22 |
+
Excellent performance but significantly over budget and slightly below the 48GB memory requirement.
|
23 |
+
Conclusion:
|
24 |
+
The PNY NVIDIA RTX A6000 48GB is the best option that balances performance, memory capacity, and cost within the given constraints. While it may not match the raw performance of the A100 or H100 series, it is a capable GPU for running state-of-the-art LLMs and fits within the budget. If budget flexibility is possible, considering the used NVIDIA Tesla A100 80GB could provide superior performance for LLM tasks, but it comes with the risks associated with used hardware and a higher price point.
|
Docs/Introduction to Jar3d.MD
ADDED
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Introduction to Jar3d
|
2 |
+
|
3 |
+
## Problem Statement
|
4 |
+
The goal was to develop an AI agent capable of leveraging the full potential of both proprietary and open-source models for research-intensive tasks.
|
5 |
+
|
6 |
+
## What is Jar3d?
|
7 |
+
Jar3d is a versatile research agent that combines [chain-of-reasoning](https://github.com/ProfSynapse/Synapse_CoR), Meta-Prompting, and Agentic RAG techniques.
|
8 |
+
|
9 |
+
- It features integrations with popular providers and open-source models, allowing for 100% local operation given sufficient hardware resources.
|
10 |
+
- Research is conducted via the SERPER API giving the agent access to google search, and shopping with plans to extend this to include other services.
|
11 |
+
|
12 |
+
### Use Cases & Applications
|
13 |
+
- Long-running research tasks, writing literature reviews, newsletters, sourcing products etc.
|
14 |
+
- Potential adaptation for use with internal company documents, requiring no internet access.
|
15 |
+
- Can function as a research assistant or a local version of services like Perplexity.
|
16 |
+
|
17 |
+
For setup instructions, please refer to the [Setup Jar3d](https://github.com/brainqub3/meta_expert) guide.
|
18 |
+
|
19 |
+
## Prompt Engineering
|
20 |
+
Jar3d utilizes two powerful prompts written entirely in Markdown:
|
21 |
+
1. [Jar3d Meta-Prompt](https://github.com/brainqub3/meta_expert/blob/main/prompt_engineering/jar3d_meta_prompt.md)
|
22 |
+
2. [Jar3d Requirements Prompt](https://github.com/brainqub3/meta_expert/blob/main/prompt_engineering/jar3d_requirements_prompt.md)
|
23 |
+
|
24 |
+
Both prompts incorporate adaptations of the Chain of Reasoning technique.
|
25 |
+
|
26 |
+
## Jar3d Architecture
|
27 |
+
The Jar3d architecture incorporates aspects of Meta-Prompting, Agentic RAG, and an adaptation of [Chain of Reasoning](https://github.com/ProfSynapse/Synapse_CoR).
|
28 |
+
|
29 |
+
```mermaid
|
30 |
+
graph TD
|
31 |
+
A[Jar3d] -->|Gathers requirements| B[MetaExpert]
|
32 |
+
B -->|Uses chain of reasoning| C{Router}
|
33 |
+
C -->|Tool needed| D[Tool Expert]
|
34 |
+
C -->|No tool needed| E[Non-Tool Expert]
|
35 |
+
D -->|Internet research & RAG| F[Result]
|
36 |
+
E -->|Writer or Planner| G[Result]
|
37 |
+
F --> B
|
38 |
+
G --> B
|
39 |
+
B --> C
|
40 |
+
C -->|Final output| I[Deliver to User]
|
41 |
+
C -->|Needs more detail| B
|
42 |
+
|
43 |
+
subgraph "Jar3d Process"
|
44 |
+
J[Start] --> K[Gather Requirements]
|
45 |
+
K --> L{Requirements Adequate?}
|
46 |
+
L -->|No| K
|
47 |
+
L -->|Yes| M[Pass on Requirements]
|
48 |
+
end
|
49 |
+
|
50 |
+
A -.-> J
|
51 |
+
M -.-> B
|
52 |
+
```
|
53 |
+
|
54 |
+
|
55 |
+
## Jar3d's Retrieval Mechanism for Internet Research
|
56 |
+
|
57 |
+
This system employs a sophisticated retrieval mechanism for conducting internet research. The process involves several steps, utilizing various tools and techniques to ensure comprehensive and relevant results.
|
58 |
+
|
59 |
+
### 1. Web Page Discovery
|
60 |
+
|
61 |
+
- Utilizes the SERPER tool to find relevant web pages.
|
62 |
+
- Employs an LLM-executed search algorithm, expressed in natural language.
|
63 |
+
- Each iteration of the algorithm generates a search query for SERPER.
|
64 |
+
- SERPER returns a search engine results page (SERP).
|
65 |
+
- Another LLM call selects the most appropriate URL from the SERP.
|
66 |
+
- This process is repeated a predetermined number of times to compile a list of URLs for in-depth research.
|
67 |
+
|
68 |
+
### 2. Content Extraction and Chunking
|
69 |
+
|
70 |
+
- Employs [LLM Sherpa](https://github.com/nlmatics/llmsherpa) as a document ingestor.
|
71 |
+
- Intelligently chunks the content from each URL in the compiled list.
|
72 |
+
- Results in a corpus of chunked text across all accumulated URLs.
|
73 |
+
|
74 |
+
### 3. Text Embedding
|
75 |
+
|
76 |
+
- Embeds the chunked text using a locally hosted model from [FastEmbed](https://qdrant.github.io/fastembed/#installation).
|
77 |
+
- Indexes embeddings in an in-memory [FAISS](https://api.python.langchain.com/en/latest/vectorstores/langchain_community.vectorstores.faiss.FAISS.html) vector store.
|
78 |
+
|
79 |
+
### 4. Similarity Search
|
80 |
+
|
81 |
+
- Performs retrieval using a similarity search over the FAISS vector store.
|
82 |
+
- Utilizes cosine similarity between indexed embeddings and the meta-prompt (written by the meta-agent).
|
83 |
+
- Retrieves the most relevant information based on this similarity measure.
|
84 |
+
|
85 |
+
### 5. Re-ranking
|
86 |
+
|
87 |
+
- Leverages [FlashRank](https://github.com/PrithivirajDamodaran/FlashRank) as a locally hosted re-ranking service.
|
88 |
+
- FlashRank uses cross-encoders for more accurate assessment of document relevance to the query.
|
89 |
+
|
90 |
+
### 6. Final Selection
|
91 |
+
|
92 |
+
- Selects a designated percentile of the highest-scoring documents from the re-ranked results.
|
93 |
+
- Passes this final set of retrieved documents to the meta-agent for further processing or analysis.
|
Docs/Meta-Prompting Overview.MD
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#metaprompting #AI #agents #customGPT #YouTubeTopic #backed
|
2 |
+
#promptengineering
|
3 |
+
These notes are derived from [Source](https://arxiv.org/abs/2401.12954)
|
4 |
+
## What is Meta-Prompting?
|
5 |
+
A scaffolding technique that turns a single LLM into an orchestrator managing multiple specialised LLMs. You do not need to prompt each specialist because you're prompting the orchestrator to create prompts!
|
6 |
+
## Key Aspects of Meta-Promoting
|
7 |
+
* Decisions about which prompts to use and which codes to execute are left solely in the hands of the LLM.
|
8 |
+
* Been shown to beat other task agnostic scaffolding methods.
|
9 |
+
* Meta-prompting has four parts:
|
10 |
+
1. Break down complex tasks into smaller pieces.
|
11 |
+
2. Assign these pieces to specialised expert models.
|
12 |
+
3. Oversee the communication between these expert models.
|
13 |
+
4. Apply its own critical thinking, reasoning, and verification skills.
|
14 |
+
|
15 |
+
* Meta-prompting is task agnostic.
|
16 |
+
* Enhanced with an interpreter.
|
17 |
+
1. Meta prompting has shown to be powerful when combined with an interpreter. More on code interpreters here: [[OS Code Interpreter]]
|
18 |
+
|
19 |
+
## What the Research Says
|
20 |
+
|
21 |
+
### Strengths
|
22 |
+
Through rigorous experimentation with GPT-4, meta-prompting, enhanced by Python interpreter functionality, has been shown to outperform conventional scaffolding methods. On average, it surpasses standard prompting by 17.1%, expert (dynamic) prompting by 17.3%, and multipersona prompting by 15.2% across tasks such as the Game of 24, Checkmate-in-One, and Python Programming Puzzles.
|
23 |
+
|
24 |
+
### Weaknesses
|
25 |
+
Here are the main weaknesses of the meta-prompting framework, based on the provided text:
|
26 |
+
|
27 |
+
* Cost inefficiency: Multiple model calls, especially with GPT-4, lead to high operational costs.
|
28 |
+
* Scalability issues: Requires large-scale models with extensive context windows, limiting use with smaller models.
|
29 |
+
* Linear operation: Sequential processing of steps limits parallel processing capabilities, affecting speed and efficiency.
|
30 |
+
* Domain restrictions: Current implementation is limited to closed-domain systems, though potential exists for expansion.
|
31 |
+
* Information transfer challenges: The Meta Model sometimes fails to effectively communicate necessary information to expert models.
|
32 |
+
* Response patterns: Tendency for apologetic responses in lower-performing tasks, likely due to training on instruction-following data.
|
33 |
+
* Limited parallelism: Current design doesn't allow for simultaneous expert consultations or varied parameter usage within a single expert.
|
34 |
+
* Context management: Lack of efficient history summarization or refinement between steps.
|
35 |
+
* Model dependency: Heavy reliance on advanced models limiting effectiveness with smaller language models.
|
36 |
+
|
37 |
+
*Author Note: I wonder how many of these weaknesses are still applicable with the more advanced models that have been released since the research on meta-prompting was conducted.*
|
38 |
+
|
39 |
+
These bullet points summarize the key limitations and challenges faced by the meta-prompting framework as described in the text.
|
40 |
+
|
41 |
+
[Source](https://arxiv.org/abs/2401.12954)
|
42 |
+
|
43 |
+
## Meta Prompting with Web Search
|
44 |
+
|
45 |
+
Using meta-prompting to build a web search agent. #agents #metaprompting
|
46 |
+
|
47 |
+
**Libs and Frameworks**
|
48 |
+
* Uses [LangGraph](https://langchain-ai.github.io/langgraph/) for orchestration of agents.
|
49 |
+
|
50 |
+
**Agent Schema:**
|
51 |
+
Just three agents required.
|
52 |
+
- Meta Expert: The Meta Expert agent.
|
53 |
+
- No Tool Expert: The agents that receive non-tool instructions.
|
54 |
+
- Tool Expert: The agent that receives tool based instructions such as (search the internet)
|
55 |
+
|
56 |
+
```mermaid
|
57 |
+
graph TD
|
58 |
+
A[Start] --> B[Meta Expert]
|
59 |
+
B --> C{Final Answer?}
|
60 |
+
C -->|Yes| D[End]
|
61 |
+
C -->|No| E[Expert]
|
62 |
+
E --> B
|
63 |
+
```
|
64 |
+
|
65 |
+
**The Prompt**
|
66 |
+
The Meta-Prompt is written in Mark Down. This makes it much easier to read and allows a standardized way of writing prompts that most LLMs should understand. Explore the [[Meta-Prompt]]
|
67 |
+
|
68 |
+
# Watch the Video
|
69 |
+
|
70 |
+
[![Meta Prompting with Web Search](https://img.youtube.com/vi/CEXmSX5ijh4/0.jpg)](https://youtu.be/CEXmSX5ijh4)
|
Docs/Overview of Agentic RAG.MD
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#RAG #agents
|
2 |
+
|
3 |
+
## Core Components of Agentic RAG
|
4 |
+
|
5 |
+
Agentic RAG has **two** core components.
|
6 |
+
1. Dynamic planning and execution
|
7 |
+
1. The agentic strategy we use to plan and execute against a user query we receive.
|
8 |
+
2. Retrieval pipeline
|
9 |
+
1. Document parsing and chunking strategy: How we extract text from various document types and chunk them into smaller sub-documents.
|
10 |
+
2. Embedding models: The models we use to transform the sub-documents into numeric vector representations that we can store in vector databases and retrieve later on.
|
11 |
+
3. Document retrieval strategy: The strategy we use to retrieve the sub-documents to answer queries.
|
12 |
+
### Dynamic Planning and Execution
|
13 |
+
- [LangGraph](https://langchain-ai.github.io/langgraph/) uses a computational graph to execute agentic workflows.
|
14 |
+
-
|
15 |
+
|
16 |
+
### Retrieval Pipeline
|
17 |
+
Retrieval pipeline will consist of a document parser, an embedding model, and a similarity metric (for which we retrieve our embeddings).
|
18 |
+
#### Document Parsing & Chunking Strategy
|
19 |
+
Document parsing should have the following:
|
20 |
+
- Flexibility: Handles a range of document types including pdf, html, txt etc.
|
21 |
+
- Structurally Aware: Must be able to parse documents such that the structure of the document is preserved. This will include things like preserving tables.
|
22 |
+
-
|
23 |
+
#### Embedding & Retrieval Strategy
|
24 |
+
```mermaid
|
25 |
+
graph TD
|
26 |
+
subgraph "Bi-Encoder"
|
27 |
+
A["Sentence A"] --> B1["BERT"]
|
28 |
+
B["Sentence B"] --> B2["BERT"]
|
29 |
+
B1 --> P1["pooling"]
|
30 |
+
B2 --> P2["pooling"]
|
31 |
+
P1 --> U["u"]
|
32 |
+
P2 --> V["v"]
|
33 |
+
U --> CS["Cosine-Similarity"]
|
34 |
+
V --> CS
|
35 |
+
end
|
36 |
+
|
37 |
+
subgraph "Cross-Encoder"
|
38 |
+
C["Sentence A"] --> BE["BERT"]
|
39 |
+
D["Sentence B"] --> BE
|
40 |
+
BE --> CL["Classifier"]
|
41 |
+
CL --> O["0..1"]
|
42 |
+
end
|
43 |
+
```
|
44 |
+
[Source](https://www.sbert.net/examples/applications/cross-encoder/README.html)
|
45 |
+
|
46 |
+
Sentence embedding models come in two flavours, [bi-encoders and cross-encoders](https://www.sbert.net/examples/applications/cross-encoder/README.html).
|
47 |
+
* Bi-Encoders: Produce separate sentence embedding for each input text. They are **faster** but **less accurate** than cross encoders. Useful for semantic search, or Information retrieval due to the efficiency,
|
48 |
+
* Cross-Encoders: Process pairs of sentences together to produce a similarity score. They are **slower**, but **more accurate** than bi-encoders. Use For small pre-defined data sets, cross encoders can be useful.
|
49 |
+
|
50 |
+
It's common practice to combine bi-encoders and cross-encoders in a multi-stage approach. The bi-encoders will help produce the initial retrieval candidates. The cross encoders are used to re-rank top candidates for higher accuracy.
|
51 |
+
|
52 |
+
### Knowledge Graph Retrieval
|
53 |
+
|
54 |
+
In Hybrid Mode, Jar3d creates a Neo4j knowledge graph from the ingested documents. We have found that combining KG retrieval with RAG improves it's ability to answer more complex queries.
|
55 |
+
|
56 |
+
We leverage an [LLM Graph Transformer](https://api.python.langchain.com/en/latest/graph_transformers/langchain_experimental.graph_transformers.llm.LLMGraphTransformer.html) to create a knowledge graph from the documents.
|
57 |
+
|
58 |
+
We query the knowlede graph using Cypher Query Language to isolate the most relevant relationhips and nodes. This retireved context is fed into the LLM alongside the context retrievd from the similarity search and reranking with the cross-encoder.
|
59 |
+
|
60 |
+
[![Agentic RAG Video](https://img.youtube.com/vi/R5_dCmieOiM/0.jpg)](https://youtu.be/R5_dCmieOiM?feature=shared)
|
61 |
+
|
62 |
+
### Tooling
|
63 |
+
|
64 |
+
**LLM Sherpa** - Open Source
|
65 |
+
Covers document parsing
|
66 |
+
|
67 |
+
[LLM Sherpa](https://github.com/nlmatics/llmsherpa): Handles a variety of documents and provides some structural awareness. Allows self hosting via the [backend service](https://github.com/nlmatics/nlm-ingestor) which is fully open sourced. LLM Sherpa does smart chunking which preserves the integrity of text by keeping related text together. Research suggests that it struggles with more complex PDF doc types.
|
68 |
+
Platforms:
|
69 |
+
|
70 |
+
|
71 |
+
##### Tools for Embedding
|
72 |
+
[Langchain integrations](https://python.langchain.com/v0.1/docs/integrations/text_embedding/) make it easy to leverage a variety of embedding model services.
|
73 |
+
* For bi-encoders [FastEmbed by Qdrant](https://python.langchain.com/v0.1/docs/integrations/text_embedding/fastembed/) is fast and lightweight (good for PoCs, search apps).
|
74 |
+
* For re-ranking with Cross-Encoders, [Langchain provides](https://python.langchain.com/v0.2/docs/integrations/document_transformers/cross_encoder_reranker/) a variety of options.
|
LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2024 brainqub3
|
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
ADDED
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Meta Expert
|
2 |
+
|
3 |
+
A project for versatile AI agents that can run with proprietary models or completely open-source. The meta expert has two agents: a basic [Meta Agent](Docs/Meta-Prompting%20Overview.MD), and [Jar3d](Docs/Introduction%20to%20Jar3d.MD), a more sophisticated and versatile agent.
|
4 |
+
|
5 |
+
## Table of Contents
|
6 |
+
|
7 |
+
1. [Core Concepts](#core-concepts)
|
8 |
+
2. [Prerequisites](#prerequisites)
|
9 |
+
3. [Configuration](#configuration)
|
10 |
+
- [API Key Configuration](#api-key-configuration)
|
11 |
+
- [Endpoints Configuration](#endpoints-configuration)
|
12 |
+
4. [Setup for Basic Meta Agent](#setup-for-basic-meta-agent)
|
13 |
+
5. [Setup for Jar3d](#setup-for-jar3d)
|
14 |
+
- [Docker Setup for Jar3d](#docker-setup-for-jar3d)
|
15 |
+
- [Interacting with Jar3d](#interacting-with-jar3d)
|
16 |
+
6. [Roadmap for Jar3d](#roadmap-for-jar3d)
|
17 |
+
|
18 |
+
## Core Concepts
|
19 |
+
|
20 |
+
This project leverages four core concepts:
|
21 |
+
|
22 |
+
1. Meta prompting: For more information, refer to the paper on **Meta-Prompting** ([source](https://arxiv.black/pdf/2401.12954)). Read our notes on [Meta-Prompting Overview](Docs/Meta-Prompting%20Overview.MD) for a more concise overview.
|
23 |
+
2. Chain of Reasoning: For [Jar3d](#setup-for-jar3d), we also leverage an adaptation of [Chain-of-Reasoning](https://github.com/ProfSynapse/Synapse_CoR)
|
24 |
+
3. [Jar3d](#setup-for-jar3d) uses retrieval augmented generation, which isn't used within the [Basic Meta Agent](#setup-for-basic-meta-agent). Read our notes on [Overview of Agentic RAG](Docs/Overview%20of%20Agentic%20RAG.MD).
|
25 |
+
4. Jar3d can generate knowledge graphs from web-pages allowing it to produce more comprehensive outputs.
|
26 |
+
|
27 |
+
## Prerequisites
|
28 |
+
|
29 |
+
1. Clone this project to your work environment/local directory:
|
30 |
+
```bash
|
31 |
+
git clone https://github.com/brainqub3/meta_expert.git
|
32 |
+
```
|
33 |
+
|
34 |
+
2. You will need Docker and Docker Composed installed to get the project up and running:
|
35 |
+
- [Docker](https://www.docker.com/get-started)
|
36 |
+
- [Docker Compose](https://docs.docker.com/compose/install/)
|
37 |
+
|
38 |
+
3. **If you wish to use Hybrid Retrieval, you will need to create a Free Neo4j Aura Account:**
|
39 |
+
- [Neo4j Aura](https://neo4j.com/)
|
40 |
+
|
41 |
+
## Configuration
|
42 |
+
|
43 |
+
1. Navigate to the Repository:
|
44 |
+
```bash
|
45 |
+
cd /path/to/your-repo/meta_expert
|
46 |
+
```
|
47 |
+
|
48 |
+
2. Open the `config.yaml` file:
|
49 |
+
```bash
|
50 |
+
nano config/config.yaml
|
51 |
+
```
|
52 |
+
|
53 |
+
### API Key Configuration
|
54 |
+
|
55 |
+
Enter API Keys for your choice of LLM provider:
|
56 |
+
|
57 |
+
- **Serper API Key:** Get it from [https://serper.dev/](https://serper.dev/)
|
58 |
+
- **OpenAI API Key:** Get it from [https://openai.com/](https://openai.com/)
|
59 |
+
- **Gemini API Key:** Get it from [https://ai.google.dev/gemini-api](https://ai.google.dev/gemini-api)
|
60 |
+
- **Claude API Key:** Get it from [https://docs.anthropic.com/en/api/getting-started](https://docs.anthropic.com/en/api/getting-started)
|
61 |
+
- **Groq API Key:** Get it from [https://console.groq.com/keys](https://console.groq.com/keys)
|
62 |
+
|
63 |
+
*For Hybrid retrieval, you will require a Claude API key*
|
64 |
+
|
65 |
+
### Endpoints Configuration
|
66 |
+
|
67 |
+
Set the `LLM_SERVER` variable to choose your inference provider. Possible values are:
|
68 |
+
|
69 |
+
- openai
|
70 |
+
- mistral
|
71 |
+
- claude
|
72 |
+
- gemini (Not currently supported)
|
73 |
+
- ollama (Not currently supported)
|
74 |
+
- groq
|
75 |
+
- vllm (Not currently supported)
|
76 |
+
|
77 |
+
Example:
|
78 |
+
|
79 |
+
```yaml
|
80 |
+
LLM_SERVER: claude
|
81 |
+
```
|
82 |
+
|
83 |
+
Remember to keep your `config.yaml` file private as it contains sensitive information.
|
84 |
+
|
85 |
+
## Setup for Basic Meta Agent
|
86 |
+
|
87 |
+
The basic meta agent is an early iteration of the project. It demonstrates meta prompting rather than being a useful tool for research. It uses a naive approach of scraping the entirety of a web page and feeding that into the context of the meta agent, who either continues the task or delivers a final answer.
|
88 |
+
|
89 |
+
### Run Your Query in Shell
|
90 |
+
|
91 |
+
```bash
|
92 |
+
python -m agents.meta_agent
|
93 |
+
```
|
94 |
+
|
95 |
+
Then enter your query.
|
96 |
+
|
97 |
+
## Setup for Jar3d
|
98 |
+
|
99 |
+
Jar3d is a more sophisticated agent that uses RAG, Chain-of-Reasoning, and Meta-Prompting to complete long-running research tasks.
|
100 |
+
|
101 |
+
*Note: Currently, the best results are with Claude 3.5 Sonnet and Llama 3.1 70B. Results with GPT-4 are inconsistent*
|
102 |
+
|
103 |
+
Try Jar3d with:
|
104 |
+
|
105 |
+
- Writing a newsletter - [Example](Docs/Example%20Outputs/Llama%203.1%20Newsletter.MD)
|
106 |
+
- Writing a literature review
|
107 |
+
- As a holiday assistant
|
108 |
+
|
109 |
+
Jar3d is in active development, and its capabilities are expected to improve with better models. Feedback is greatly appreciated.
|
110 |
+
|
111 |
+
### Docker Setup for Jar3d
|
112 |
+
|
113 |
+
Jar3d can be run using Docker for easier setup and deployment.
|
114 |
+
|
115 |
+
#### Prerequisites
|
116 |
+
|
117 |
+
- [Docker](https://www.docker.com/get-started)
|
118 |
+
- [Docker Compose](https://docs.docker.com/compose/install/)
|
119 |
+
|
120 |
+
#### Quick Start
|
121 |
+
|
122 |
+
1. Clone the repository:
|
123 |
+
```bash
|
124 |
+
git clone https://github.com/brainqub3/meta_expert.git
|
125 |
+
cd meta_expert
|
126 |
+
```
|
127 |
+
|
128 |
+
2. Build and start the containers:
|
129 |
+
```bash
|
130 |
+
docker-compose up --build
|
131 |
+
```
|
132 |
+
|
133 |
+
3. Access Jar3d:
|
134 |
+
Once running, access the Jar3d web interface at `http://localhost:8000`.
|
135 |
+
|
136 |
+
You can end your docker session by pressing `Ctrl + C` or `Cmd + C` in your terminal and running:
|
137 |
+
```bash
|
138 |
+
docker-compose down
|
139 |
+
```
|
140 |
+
|
141 |
+
#### Notes
|
142 |
+
|
143 |
+
- The Docker setup includes Jar3d and the NLM-Ingestor service.
|
144 |
+
- Playwright and its browser dependencies are included for web scraping capabilities.
|
145 |
+
- Ollama is not included in this Docker setup. If needed, set it up separately and configure in `config.yaml`.
|
146 |
+
- Configuration is handled through `config.yaml`, not environment variables in docker-compose.
|
147 |
+
|
148 |
+
For troubleshooting, check the container logs:
|
149 |
+
|
150 |
+
```bash
|
151 |
+
docker-compose logs
|
152 |
+
```
|
153 |
+
|
154 |
+
Refer to the project's GitHub issues for common problems and solutions.
|
155 |
+
|
156 |
+
### Interacting with Jar3d
|
157 |
+
|
158 |
+
Once you're set up, Jar3d will proceed to introduce itself and ask some questions. The questions are designed to help you refine your requirements. When you feel you have provided all the relevant information to Jar3d, you can end the questioning part of the workflow by typing `/end`.
|
159 |
+
|
160 |
+
## Roadmap for Jar3d
|
161 |
+
|
162 |
+
- Feedback to Jar3d so that final responses can be iterated on and amended.
|
163 |
+
- Long-term memory.
|
164 |
+
- Full Ollama and vLLM integration.
|
165 |
+
- Integrations to RAG platforms for more intelligent document processing and faster RAG.
|
__init__.py
ADDED
File without changes
|
agent_memory/jar3d_final_response_previous_run.txt
ADDED
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Literature Review on the Current State of Large Language Models (LLMs)
|
2 |
+
|
3 |
+
## Introduction
|
4 |
+
|
5 |
+
Large Language Models (LLMs) have revolutionized the field of Natural Language Processing (NLP) by demonstrating unprecedented capabilities in language understanding and generation. These models have significantly impacted various domains, including machine translation, question-answering systems, and content creation. This literature review provides a comprehensive overview of the advancements in LLMs up to 2023, focusing on architecture developments, training techniques, ethical considerations, and practical applications.
|
6 |
+
|
7 |
+
## Architecture Advancements
|
8 |
+
|
9 |
+
### Transformer Architecture
|
10 |
+
|
11 |
+
The introduction of the Transformer architecture by Vaswani et al. (2017) marked a pivotal moment in NLP. By utilizing self-attention mechanisms, Transformers addressed the limitations of recurrent neural networks, particularly in handling long-range dependencies and parallelization during training.
|
12 |
+
|
13 |
+
### GPT Series
|
14 |
+
|
15 |
+
OpenAI's Generative Pre-trained Transformer (GPT) series has been instrumental in pushing the boundaries of LLMs:
|
16 |
+
|
17 |
+
- **GPT-2** (Radford et al., 2019): Featured 1.5 billion parameters and demonstrated coherent text generation, raising awareness about the potential and risks of LLMs.
|
18 |
+
- **GPT-3** (Brown et al., 2020): Expanded to 175 billion parameters, exhibiting remarkable few-shot learning abilities and setting new benchmarks in NLP tasks.
|
19 |
+
|
20 |
+
### Scaling Laws and Large-Scale Models
|
21 |
+
|
22 |
+
Kaplan et al. (2020) established empirical scaling laws, showing that model performance scales predictably with computational resources, model size, and dataset size. This led to the development of even larger models:
|
23 |
+
|
24 |
+
- **Megatron-Turing NLG 530B** (Smith et al., 2022): A collaboration between NVIDIA and Microsoft, this model contains 530 billion parameters, enhancing language generation capabilities.
|
25 |
+
- **PaLM** (Chowdhery et al., 2022): Google's 540-billion-parameter model showcased state-of-the-art performance in reasoning and language understanding tasks.
|
26 |
+
|
27 |
+
## Training Techniques
|
28 |
+
|
29 |
+
### Unsupervised and Self-Supervised Learning
|
30 |
+
|
31 |
+
LLMs are primarily trained using vast amounts of unlabelled text data through unsupervised or self-supervised learning, enabling them to learn language patterns without explicit annotations (Devlin et al., 2019).
|
32 |
+
|
33 |
+
### Fine-Tuning and Transfer Learning
|
34 |
+
|
35 |
+
Fine-tuning allows LLMs to adapt to specific tasks by training on smaller, task-specific datasets. Techniques like Transfer Learning have been crucial in applying general language understanding to specialized domains (Howard & Ruder, 2018).
|
36 |
+
|
37 |
+
### Instruction Tuning and Prompt Engineering
|
38 |
+
|
39 |
+
Wei et al. (2021) introduced instruction tuning, enhancing LLMs' ability to follow human instructions by fine-tuning on datasets with task instructions. Prompt engineering has emerged as a method to elicit desired behaviors from LLMs without additional training.
|
40 |
+
|
41 |
+
### Reinforcement Learning from Human Feedback (RLHF)
|
42 |
+
|
43 |
+
RLHF incorporates human preferences to refine model outputs, aligning them with human values and improving safety (Christiano et al., 2017).
|
44 |
+
|
45 |
+
## Ethical Considerations
|
46 |
+
|
47 |
+
### Bias and Fairness
|
48 |
+
|
49 |
+
LLMs can inadvertently perpetuate biases present in their training data. Studies have highlighted issues related to gender, race, and cultural stereotypes (Bender et al., 2021). Efforts are ongoing to mitigate biases through data curation and algorithmic adjustments (Bolukbasi et al., 2016).
|
50 |
+
|
51 |
+
### Misinformation and Content Moderation
|
52 |
+
|
53 |
+
The ability of LLMs to generate plausible but incorrect or harmful content poses risks in misinformation dissemination. OpenAI has explored content moderation strategies and responsible deployment practices (Solaiman et al., 2019).
|
54 |
+
|
55 |
+
### Privacy Concerns
|
56 |
+
|
57 |
+
Training on large datasets may include sensitive information, raising privacy issues. Techniques like differential privacy are being explored to protect individual data (Abadi et al., 2016).
|
58 |
+
|
59 |
+
### Transparency and Interpretability
|
60 |
+
|
61 |
+
Understanding the decision-making processes of LLMs is challenging due to their complexity. Research into explainable AI aims to make models more interpretable (Danilevsky et al., 2020), which is critical for trust and regulatory compliance.
|
62 |
+
|
63 |
+
## Applications
|
64 |
+
|
65 |
+
### Healthcare
|
66 |
+
|
67 |
+
LLMs assist in clinical documentation, patient communication, and research data analysis. They facilitate faster diagnosis and personalized treatment plans (Jiang et al., 2020).
|
68 |
+
|
69 |
+
### Finance
|
70 |
+
|
71 |
+
In finance, LLMs are used for algorithmic trading, risk assessment, and customer service automation, enhancing efficiency and decision-making processes (Yang et al., 2020).
|
72 |
+
|
73 |
+
### Education
|
74 |
+
|
75 |
+
Educational technologies leverage LLMs for personalized learning experiences, automated grading, and language tutoring, contributing to improved learning outcomes (Zawacki-Richter et al., 2019).
|
76 |
+
|
77 |
+
### Legal Sector
|
78 |
+
|
79 |
+
LLMs aid in legal document analysis, contract review, and summarization, reducing manual workloads and increasing accuracy (Bommarito & Katz, 2018).
|
80 |
+
|
81 |
+
### Customer Service and Virtual Assistants
|
82 |
+
|
83 |
+
Chatbots and virtual assistants powered by LLMs provide customer support, handle inquiries, and perform tasks, improving user engagement and satisfaction (Xu et al., 2020).
|
84 |
+
|
85 |
+
## Conclusion
|
86 |
+
|
87 |
+
Advancements in Large Language Models up to 2023 have significantly influenced AI and NLP, leading to models capable of understanding and generating human-like text. Progress in model architectures and training techniques has expanded their applicability across diverse industries. However, ethical considerations regarding bias, misinformation, and transparency remain critical challenges. Addressing these concerns is essential for the responsible development and deployment of LLMs. Future research is expected to focus on enhancing model efficiency, interpretability, and alignment with human values.
|
88 |
+
|
89 |
+
## References
|
90 |
+
|
91 |
+
- Abadi, M., Chu, A., Goodfellow, I., McMahan, H. B., Mironov, I., Talwar, K., & Zhang, L. (2016). **Deep Learning with Differential Privacy.** *Proceedings of the 2016 ACM SIGSAC Conference on Computer and Communications Security*, 308-318.
|
92 |
+
|
93 |
+
- Bender, E. M., Gebru, T., McMillan-Major, A., & Shmitchell, S. (2021). **On the Dangers of Stochastic Parrots: Can Language Models Be Too Big?** *Proceedings of the 2021 ACM Conference on Fairness, Accountability, and Transparency*, 610-623.
|
94 |
+
|
95 |
+
- Bolukbasi, T., Chang, K. W., Zou, J. Y., Saligrama, V., & Kalai, A. T. (2016). **Man is to Computer Programmer as Woman is to Homemaker? Debiasing Word Embeddings.** *Advances in Neural Information Processing Systems*, 4349-4357.
|
96 |
+
|
97 |
+
- Bommarito, M. J., & Katz, D. M. (2018). **A Statistical Analysis of the Predictive Technologies of Law and the Future of Legal Practice.** *Stanford Technology Law Review*, 21, 286.
|
98 |
+
|
99 |
+
- Brown, T. B., Mann, B., Ryder, N., Subbiah, M., Kaplan, J., Dhariwal, P., ... & Amodei, D. (2020). **Language Models are Few-Shot Learners.** *Advances in Neural Information Processing Systems*, 33, 1877-1901.
|
100 |
+
|
101 |
+
- Chowdhery, A., Narang, S., Devlin, J., et al. (2022). **PaLM: Scaling Language Modeling with Pathways.** *arXiv preprint* arXiv:2204.02311.
|
102 |
+
|
103 |
+
- Christiano, P. F., Leike, J., Brown, T., Martic, M., Legg, S., & Amodei, D. (2017). **Deep Reinforcement Learning from Human Preferences.** *Advances in Neural Information Processing Systems*, 30.
|
104 |
+
|
105 |
+
- Danilevsky, M., Qian, Y., Aharon, R., et al. (2020). **A Survey of the State of Explainable AI for Natural Language Processing.** *arXiv preprint* arXiv:2010.00711.
|
106 |
+
|
107 |
+
- Devlin, J., Chang, M. W., Lee, K., & Toutanova, K. (2019). **BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding.** *Proceedings of the 2019 Conference of the North American Chapter of the Association for Computational Linguistics*, 4171-4186.
|
108 |
+
|
109 |
+
- Howard, J., & Ruder, S. (2018). **Universal Language Model Fine-tuning for Text Classification.** *Proceedings of the 56th Annual Meeting of the Association for Computational Linguistics*, 328-339.
|
110 |
+
|
111 |
+
- Jiang, F., Jiang, Y., Zhi, H., et al. (2020). **Artificial Intelligence in Healthcare: Past, Present and Future.** *Stroke and Vascular Neurology*, 5(2), 230-243.
|
112 |
+
|
113 |
+
- Kaplan, J., McCandlish, S., Henighan, T., et al. (2020). **Scaling Laws for Neural Language Models.** *arXiv preprint* arXiv:2001.08361.
|
114 |
+
|
115 |
+
- Radford, A., Wu, J., Child, R., et al. (2019). **Language Models are Unsupervised Multitask Learners.** *OpenAI Blog*, 1(8).
|
116 |
+
|
117 |
+
- Smith, S., Gray, J., Forte, S., et al. (2022). **Using DeepSpeed and Megatron to Train Megatron-Turing NLG 530B, A Large-Scale Generative Language Model.** *arXiv preprint* arXiv:2201.11990.
|
118 |
+
|
119 |
+
- Solaiman, I., Brundage, M., Clark, J., et al. (2019). **Release Strategies and the Social Impacts of Language Models.** *arXiv preprint* arXiv:1908.09203.
|
120 |
+
|
121 |
+
- Vaswani, A., Shazeer, N., Parmar, N., et al. (2017). **Attention is All You Need.** *Advances in Neural Information Processing Systems*, 30.
|
122 |
+
|
123 |
+
- Wei, J., Bosma, M., Zhao, V., et al. (2021). **Finetuned Language Models Are Zero-Shot Learners.** *arXiv preprint* arXiv:2109.01652.
|
124 |
+
|
125 |
+
- Xu, P., Liu, Z., Ou, C., & Li, W. (2020). **Leveraging Pre-trained Language Model in Machine Reading Comprehension with Multi-task Learning.** *Proceedings of the 2020 Conference on Empirical Methods in Natural Language Processing*, 226-236.
|
126 |
+
|
127 |
+
- Yang, X., Yin, Z., & Li, Y. (2020). **Application of Artificial Intelligence in Financial Industry.** *Journal of Physics: Conference Series*, 1486(4), 042047.
|
128 |
+
|
129 |
+
- Zawacki-Richter, O., Marín, V. I., Bond, M., & Gouverneur, F. (2019). **Systematic Review of Research on Artificial Intelligence Applications in Higher Education – Where Are the Educators?** *International Journal of Educational Technology in Higher Education*, 16(1), 1-27.
|
130 |
+
|
131 |
+
---
|
agents/base_agent.py
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
from abc import ABC, abstractmethod
|
3 |
+
from typing import Any, Dict, Union, TypeVar, Generic
|
4 |
+
from typing_extensions import TypedDict
|
5 |
+
from datetime import datetime
|
6 |
+
from termcolor import colored
|
7 |
+
from models.llms import (
|
8 |
+
OllamaModel,
|
9 |
+
OpenAIModel,
|
10 |
+
GroqModel,
|
11 |
+
GeminiModel,
|
12 |
+
ClaudeModel,
|
13 |
+
VllmModel,
|
14 |
+
MistralModel
|
15 |
+
)
|
16 |
+
|
17 |
+
# Set up logging
|
18 |
+
logging.basicConfig(level=logging.INFO)
|
19 |
+
logger = logging.getLogger(__name__)
|
20 |
+
|
21 |
+
# Define a TypeVar for the state
|
22 |
+
StateT = TypeVar('StateT', bound=Dict[str, Any])
|
23 |
+
|
24 |
+
class BaseAgent(ABC, Generic[StateT]):
|
25 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
26 |
+
model_endpoint: str = None, stop: str = None, location: str = "us", hyrbid: bool = False):
|
27 |
+
self.model = model
|
28 |
+
self.server = server
|
29 |
+
self.temperature = temperature
|
30 |
+
self.model_endpoint = model_endpoint
|
31 |
+
self.stop = stop
|
32 |
+
self.llm = self.get_llm()
|
33 |
+
self.location = location
|
34 |
+
self.hybrid = hyrbid
|
35 |
+
|
36 |
+
|
37 |
+
def get_llm(self, json_model: bool = False):
|
38 |
+
if self.server == 'openai':
|
39 |
+
return OpenAIModel(model=self.model, temperature=self.temperature, json_response=json_model)
|
40 |
+
elif self.server == 'ollama':
|
41 |
+
return OllamaModel(model=self.model, temperature=self.temperature, json_response=json_model)
|
42 |
+
elif self.server == 'vllm':
|
43 |
+
return VllmModel(model=self.model, temperature=self.temperature, json_response=json_model,
|
44 |
+
model_endpoint=self.model_endpoint, stop=self.stop)
|
45 |
+
elif self.server == 'groq':
|
46 |
+
return GroqModel(model=self.model, temperature=self.temperature, json_response=json_model)
|
47 |
+
elif self.server == 'claude':
|
48 |
+
return ClaudeModel(temperature=self.temperature, model=self.model, json_response=json_model)
|
49 |
+
elif self.server == 'mistral':
|
50 |
+
return MistralModel(temperature=self.temperature, model=self.model, json_response=json_model)
|
51 |
+
elif self.server == 'gemini':
|
52 |
+
# raise ValueError(f"Unsupported server: {self.server}")
|
53 |
+
return GeminiModel(temperature=self.temperature, model=self.model, json_response=json_model)
|
54 |
+
else:
|
55 |
+
raise ValueError(f"Unsupported server: {self.server}")
|
56 |
+
|
57 |
+
@abstractmethod
|
58 |
+
def get_prompt(self, state: StateT = None) -> str:
|
59 |
+
pass
|
60 |
+
|
61 |
+
@abstractmethod
|
62 |
+
def get_guided_json(self, state:StateT = None) -> Dict[str, Any]:
|
63 |
+
pass
|
64 |
+
|
65 |
+
def update_state(self, key: str, value: Union[str, dict], state: StateT = None) -> StateT:
|
66 |
+
state[key] = value
|
67 |
+
return state
|
68 |
+
|
69 |
+
@abstractmethod
|
70 |
+
def process_response(self, response: Any, user_input: str = None, state: StateT = None) -> Dict[str, Union[str, dict]]:
|
71 |
+
pass
|
72 |
+
|
73 |
+
@abstractmethod
|
74 |
+
def get_conv_history(self, state: StateT = None) -> str:
|
75 |
+
pass
|
76 |
+
|
77 |
+
@abstractmethod
|
78 |
+
def get_user_input(self) -> str:
|
79 |
+
pass
|
80 |
+
|
81 |
+
@abstractmethod
|
82 |
+
def use_tool(self) -> Any:
|
83 |
+
pass
|
84 |
+
|
85 |
+
|
86 |
+
def invoke(self, state: StateT = None, human_in_loop: bool = False, user_input: str = None, final_answer: str = None) -> StateT:
|
87 |
+
prompt = self.get_prompt(state)
|
88 |
+
conversation_history = self.get_conv_history(state)
|
89 |
+
|
90 |
+
if final_answer:
|
91 |
+
print(colored(f"\n\n{final_answer}\n\n", "green"))
|
92 |
+
|
93 |
+
if human_in_loop:
|
94 |
+
user_input = self.get_user_input()
|
95 |
+
|
96 |
+
messages = [
|
97 |
+
{"role": "system", "content": f"{prompt}\n Today's date is {datetime.now()}"},
|
98 |
+
{"role": "user", "content": f"\n{final_answer}\n" * 10 + f"{conversation_history}\n{user_input}"}
|
99 |
+
]
|
100 |
+
|
101 |
+
if self.server == 'vllm':
|
102 |
+
guided_json = self.get_guided_json(state)
|
103 |
+
response = self.llm.invoke(messages, guided_json)
|
104 |
+
else:
|
105 |
+
response = self.llm.invoke(messages)
|
106 |
+
|
107 |
+
updates = self.process_response(response, user_input, state)
|
108 |
+
for key, value in updates.items():
|
109 |
+
state = self.update_state(key, value, state)
|
110 |
+
return state
|
111 |
+
|
agents/jar3d_agent.py
ADDED
@@ -0,0 +1,910 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import os
|
3 |
+
from multiprocessing import Pool, cpu_count
|
4 |
+
# import requests
|
5 |
+
# from tenacity import RetryError
|
6 |
+
import concurrent.futures # Add this import at the top of your file
|
7 |
+
import re
|
8 |
+
import logging
|
9 |
+
import chainlit as cl
|
10 |
+
from termcolor import colored
|
11 |
+
from typing import Any, Dict, Union, List
|
12 |
+
from typing import TypedDict, Annotated
|
13 |
+
from langgraph.graph.message import add_messages
|
14 |
+
from agents.base_agent import BaseAgent
|
15 |
+
from utils.read_markdown import read_markdown_file
|
16 |
+
from tools.google_serper import serper_search, serper_shopping_search
|
17 |
+
from utils.logging import log_function, setup_logging
|
18 |
+
from tools.offline_graph_rag_tool import run_rag
|
19 |
+
from prompt_engineering.guided_json_lib import (
|
20 |
+
guided_json_search_query,
|
21 |
+
guided_json_best_url_two,
|
22 |
+
guided_json_router_decision,
|
23 |
+
guided_json_parse_expert,
|
24 |
+
guided_json_search_query_two
|
25 |
+
)
|
26 |
+
|
27 |
+
|
28 |
+
setup_logging(level=logging.INFO)
|
29 |
+
logger = logging.getLogger(__name__)
|
30 |
+
|
31 |
+
class MessageDict(TypedDict):
|
32 |
+
role: str
|
33 |
+
content: str
|
34 |
+
|
35 |
+
class State(TypedDict):
|
36 |
+
meta_prompt: Annotated[List[dict], add_messages]
|
37 |
+
conversation_history: Annotated[List[dict], add_messages]
|
38 |
+
requirements_gathering: Annotated[List[str], add_messages]
|
39 |
+
expert_plan: str
|
40 |
+
expert_research: Annotated[List[str], add_messages]
|
41 |
+
expert_research_shopping: Annotated[List[str], add_messages]
|
42 |
+
expert_writing: str
|
43 |
+
user_input: Annotated[List[str], add_messages]
|
44 |
+
previous_search_queries: Annotated[List[dict], add_messages]
|
45 |
+
router_decision: str
|
46 |
+
chat_limit: int
|
47 |
+
chat_finished: bool
|
48 |
+
recursion_limit: int
|
49 |
+
final_answer: str
|
50 |
+
previous_type2_work: Annotated[List[str], add_messages]
|
51 |
+
progress_tracking: str
|
52 |
+
|
53 |
+
state: State = {
|
54 |
+
"meta_prompt": [],
|
55 |
+
"conversation_history": [],
|
56 |
+
"requirements_gathering": [],
|
57 |
+
"expert_plan": [],
|
58 |
+
"expert_research": [],
|
59 |
+
"expert_research_shopping": [],
|
60 |
+
"expert_writing": [],
|
61 |
+
"user_input": [],
|
62 |
+
"previous_search_queries": [],
|
63 |
+
"router_decision": None,
|
64 |
+
"chat_limit": None,
|
65 |
+
"chat_finished": False,
|
66 |
+
"recursion_limit": None,
|
67 |
+
"final_answer": None,
|
68 |
+
"previous_type2_work": [],
|
69 |
+
"progress_tracking": None
|
70 |
+
}
|
71 |
+
|
72 |
+
def chat_counter(state: State) -> State:
|
73 |
+
chat_limit = state.get("chat_limit")
|
74 |
+
if chat_limit is None:
|
75 |
+
chat_limit = 0
|
76 |
+
chat_limit += 1
|
77 |
+
state["chat_limit"] = chat_limit
|
78 |
+
return chat_limit
|
79 |
+
|
80 |
+
def routing_function(state: State) -> str:
|
81 |
+
decision = state["router_decision"]
|
82 |
+
print(colored(f"\n\n Routing function called. Decision: {decision}\n\n", 'green'))
|
83 |
+
return decision
|
84 |
+
|
85 |
+
|
86 |
+
def format_final_response(final_response: str) -> str:
|
87 |
+
print(colored(f"\n\n DEBUG FINAL RESPONSE: {final_response}\n\n", 'green'))
|
88 |
+
|
89 |
+
# Split the response at ">> FINAL ANSWER:"
|
90 |
+
parts = final_response.split(">> FINAL ANSWER:")
|
91 |
+
if len(parts) > 1:
|
92 |
+
answer_part = parts[1].strip()
|
93 |
+
|
94 |
+
# Remove any triple quotes
|
95 |
+
final_response_formatted = answer_part.strip('"""')
|
96 |
+
|
97 |
+
# Remove leading and trailing whitespace
|
98 |
+
final_response_formatted = final_response_formatted.strip()
|
99 |
+
|
100 |
+
# Remove the CoR dictionary at the end
|
101 |
+
cor_pattern = r'\nCoR\s*=\s*\{[\s\S]*\}\s*$'
|
102 |
+
final_response_formatted = re.sub(cor_pattern, '', final_response_formatted)
|
103 |
+
|
104 |
+
# Remove any trailing whitespace
|
105 |
+
final_response_formatted = final_response_formatted.rstrip()
|
106 |
+
|
107 |
+
return final_response_formatted
|
108 |
+
else:
|
109 |
+
error_message = "Error: Could not find '>> FINAL ANSWER:' in the response"
|
110 |
+
print(colored(error_message, 'red'))
|
111 |
+
return "Error: No final answer found"
|
112 |
+
|
113 |
+
|
114 |
+
def set_chat_finished(state: State) -> State:
|
115 |
+
state["chat_finished"] = True
|
116 |
+
final_response = state["meta_prompt"][-1].content
|
117 |
+
|
118 |
+
# Use the formatting function
|
119 |
+
final_response_formatted = format_final_response(final_response)
|
120 |
+
|
121 |
+
agent_memory_dir = '/app/agent_memory' # No change needed
|
122 |
+
file_path = os.path.join(agent_memory_dir, 'jar3d_final_response_previous_run.txt')
|
123 |
+
|
124 |
+
# Save the formatted final response to a text file
|
125 |
+
with open(file_path, 'w') as file:
|
126 |
+
file.write(final_response_formatted)
|
127 |
+
|
128 |
+
# Print confirmation message
|
129 |
+
print(colored(f"\n\nFinal response saved to jar3d_final_response_previous_run.txt", 'green'))
|
130 |
+
|
131 |
+
# Print the formatted final response
|
132 |
+
print(colored(f"\n\n Jar3d👩💻: {final_response_formatted}", 'cyan'))
|
133 |
+
|
134 |
+
# Update the state with the final answer
|
135 |
+
# state["final_answer"] = final_response_formatted
|
136 |
+
|
137 |
+
return state
|
138 |
+
|
139 |
+
|
140 |
+
class Jar3d(BaseAgent[State]):
|
141 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
142 |
+
model_endpoint: str = None, stop: str = None):
|
143 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
144 |
+
self.llm = self.get_llm(json_model=False)
|
145 |
+
|
146 |
+
def get_prompt(self, state: State = None) -> str:
|
147 |
+
system_prompt_md = read_markdown_file('prompt_engineering/jar3d_requirements_prompt.md')
|
148 |
+
|
149 |
+
final_answer = None
|
150 |
+
agent_memory_dir = '/app/agent_memory'
|
151 |
+
file_path = os.path.join(agent_memory_dir, 'jar3d_final_response_previous_run.txt')
|
152 |
+
|
153 |
+
if os.path.exists(file_path):
|
154 |
+
with open(file_path, 'r') as file:
|
155 |
+
final_answer = file.read().strip()
|
156 |
+
|
157 |
+
# Add the final_answer to the system prompt if it exists
|
158 |
+
if final_answer:
|
159 |
+
system_prompt = f"{system_prompt_md}\n # The AI Agent's Previous Work \n <Type2> {final_answer} </Type2>"
|
160 |
+
print(colored(f"\n\n DEBUG JAR3D SYSTEM PROMPT FINAL ANSWER: {final_answer}\n\n", 'green'))
|
161 |
+
else:
|
162 |
+
system_prompt = system_prompt_md
|
163 |
+
|
164 |
+
return system_prompt
|
165 |
+
|
166 |
+
def process_response(self, response: Any, user_input: str, state: State = None) -> Dict[str, List[Dict[str, str]]]:
|
167 |
+
updates_conversation_history = {
|
168 |
+
"requirements_gathering": [
|
169 |
+
{"role": "user", "content": f"{user_input}"},
|
170 |
+
{"role": "assistant", "content": str(response)}
|
171 |
+
]
|
172 |
+
}
|
173 |
+
return updates_conversation_history
|
174 |
+
|
175 |
+
def get_conv_history(self, state: State) -> str:
|
176 |
+
conversation_history = state.get('requirements_gathering', [])
|
177 |
+
return "\n".join([f"{msg['role']}: {msg['content']}" for msg in conversation_history])
|
178 |
+
|
179 |
+
def get_user_input(self) -> str:
|
180 |
+
pass
|
181 |
+
|
182 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
183 |
+
pass
|
184 |
+
|
185 |
+
def use_tool(self) -> Any:
|
186 |
+
pass
|
187 |
+
|
188 |
+
def run_chainlit(self, state: State, message: cl.Message) -> State:
|
189 |
+
user_message = message.content
|
190 |
+
# system_prompt = self.get_prompt()
|
191 |
+
# user_input = f"{system_prompt}\n cogor {user_message}"
|
192 |
+
user_input = f"cogor: {user_message}"
|
193 |
+
|
194 |
+
state = self.invoke(state=state, user_input=user_input)
|
195 |
+
response = state['requirements_gathering'][-1]["content"]
|
196 |
+
response = re.sub(r'^```python[\s\S]*?```\s*', '', response, flags=re.MULTILINE)
|
197 |
+
response = response.lstrip()
|
198 |
+
|
199 |
+
return state, response
|
200 |
+
|
201 |
+
|
202 |
+
class MetaExpert(BaseAgent[State]):
|
203 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
204 |
+
model_endpoint: str = None, stop: str = None):
|
205 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
206 |
+
self.llm = self.get_llm(json_model=False)
|
207 |
+
|
208 |
+
def get_prompt(self, state:None) -> str:
|
209 |
+
system_prompt = read_markdown_file('prompt_engineering/jar3d_meta_prompt.md')
|
210 |
+
return system_prompt
|
211 |
+
|
212 |
+
def process_response(self, response: Any, user_input: str, state: State = None) -> Dict[str, List[MessageDict]]:
|
213 |
+
user_input = None
|
214 |
+
# Identify the type of work and expert (if applicable) from the response
|
215 |
+
|
216 |
+
response_str = str(response)
|
217 |
+
formatted_response = None
|
218 |
+
|
219 |
+
if ">> FINAL ANSWER:" in response_str:
|
220 |
+
# It's a Type 2 work - Jar3d is delivering a final answer
|
221 |
+
next_steps = "Jar3d has delivered a final answer"
|
222 |
+
formatted_response = format_final_response(response_str)
|
223 |
+
else:
|
224 |
+
# Try to extract the expert's name for Type 1 work
|
225 |
+
expert_match = re.search(r"Expert\s+([\w\s]+):", response_str)
|
226 |
+
if expert_match:
|
227 |
+
# It's a Type 1 work - Jar3d is allocating an expert
|
228 |
+
associated_expert = expert_match.group(1).strip()
|
229 |
+
next_steps = f"Jar3d has allocated {associated_expert} to work on your request."
|
230 |
+
else:
|
231 |
+
# Neither Type 1 nor Type 2 work detected
|
232 |
+
next_steps = "Jar3d is processing the request."
|
233 |
+
|
234 |
+
updates_conversation_history = {
|
235 |
+
"meta_prompt": [
|
236 |
+
{"role": "user", "content": f"{user_input}"},
|
237 |
+
{"role": "assistant", "content": str(response)}
|
238 |
+
|
239 |
+
],
|
240 |
+
"conversation_history": [
|
241 |
+
{"role": "user", "content": f"{user_input}"},
|
242 |
+
{"role": "assistant", "content": str(response)}
|
243 |
+
|
244 |
+
],
|
245 |
+
|
246 |
+
"progress_tracking": f"{next_steps}",
|
247 |
+
"final_answer": formatted_response
|
248 |
+
|
249 |
+
}
|
250 |
+
return updates_conversation_history
|
251 |
+
|
252 |
+
# @log_function(logger)
|
253 |
+
def get_conv_history(self, state: State) -> str:
|
254 |
+
|
255 |
+
all_expert_research = []
|
256 |
+
|
257 |
+
if state["expert_research"]:
|
258 |
+
expert_research = state["expert_research"]
|
259 |
+
all_expert_research.extend(expert_research)
|
260 |
+
else:
|
261 |
+
all_expert_research = []
|
262 |
+
|
263 |
+
max_length = 350000
|
264 |
+
truncated_expert_research = all_expert_research[:max_length]
|
265 |
+
|
266 |
+
expert_message_history = f"""
|
267 |
+
<expert_plan>
|
268 |
+
## Your Expert Plan:\n{state.get("expert_plan", [])}\n
|
269 |
+
</expert_plan>
|
270 |
+
|
271 |
+
<expert_writing>
|
272 |
+
## Your Expert Writing:\n{state.get("expert_writing", [])}\n
|
273 |
+
</expert_writing>
|
274 |
+
|
275 |
+
<internet_research_shopping_list>
|
276 |
+
## Your Expert Shopping List:\n{state.get("expert_research_shopping", [])}\n
|
277 |
+
</internet_research_shopping_list>
|
278 |
+
|
279 |
+
<internet_research>
|
280 |
+
## Your Expert Research:{truncated_expert_research}\n
|
281 |
+
</internet_research>
|
282 |
+
"""
|
283 |
+
|
284 |
+
return expert_message_history
|
285 |
+
|
286 |
+
def get_user_input(self) -> str:
|
287 |
+
user_input = input("Enter your query: ")
|
288 |
+
return user_input
|
289 |
+
|
290 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
291 |
+
pass
|
292 |
+
|
293 |
+
def use_tool(self) -> Any:
|
294 |
+
pass
|
295 |
+
|
296 |
+
@log_function(logger)
|
297 |
+
def run(self, state: State) -> State:
|
298 |
+
|
299 |
+
counter = chat_counter(state) # Counts every time we invoke the Meta Agent
|
300 |
+
recursion_limit = state.get("recursion_limit")
|
301 |
+
recursions = 3*counter - 2
|
302 |
+
print(colored(f"\n\n * We have envoked the Meta-Agent {counter} times.\n * we have run {recursions} max total iterations: {recursion_limit}\n\n", "green"))
|
303 |
+
|
304 |
+
upper_limit_recursions = recursion_limit
|
305 |
+
lower_limit_recursions = recursion_limit - 2
|
306 |
+
|
307 |
+
if recursions >= lower_limit_recursions and recursions <= upper_limit_recursions:
|
308 |
+
final_answer = "**You are being explicitly told to produce your [Type 2] work now!**"
|
309 |
+
elif recursions > upper_limit_recursions:
|
310 |
+
extra_recursions = recursions - upper_limit_recursions
|
311 |
+
base_message = "**You are being explicitly told to produce your [Type 2] work now!**"
|
312 |
+
final_answer = (base_message + "\n") * (extra_recursions + 1)
|
313 |
+
else:
|
314 |
+
final_answer = None
|
315 |
+
|
316 |
+
try:
|
317 |
+
requirements = state['requirements_gathering'][-1]["content"]
|
318 |
+
except:
|
319 |
+
requirements = state['requirements_gathering'][-1].content
|
320 |
+
|
321 |
+
formatted_requirements = '\n\n'.join(re.findall(r'```python\s*([\s\S]*?)\s*```', requirements, re.MULTILINE))
|
322 |
+
|
323 |
+
print(colored(f"\n\n User Requirements: {formatted_requirements}\n\n", 'green'))
|
324 |
+
|
325 |
+
if state.get("meta_prompt"):
|
326 |
+
try:
|
327 |
+
meta_prompt = state['meta_prompt'][-1]["content"]
|
328 |
+
except:
|
329 |
+
meta_prompt = state['meta_prompt'][-1].content
|
330 |
+
|
331 |
+
# print(colored(f"\n\n DEBUG Meta-Prompt: {meta_prompt}\n\n", 'yellow'))
|
332 |
+
|
333 |
+
cor_match = '\n\n'.join(re.findall(r'```python\s*([\s\S]*?)\s*```', meta_prompt, re.MULTILINE))
|
334 |
+
|
335 |
+
# print(colored(f"\n\n DEBUG CoR Match: {cor_match}\n\n", 'yellow'))
|
336 |
+
user_input = f"<requirements>{formatted_requirements}</requirements> \n\n Here is your last CoR {cor_match} update your next CoR from here."
|
337 |
+
else:
|
338 |
+
user_input = formatted_requirements
|
339 |
+
|
340 |
+
state = self.invoke(state=state, user_input=user_input, final_answer=final_answer)
|
341 |
+
|
342 |
+
meta_prompt_cor = state['meta_prompt'][-1]["content"]
|
343 |
+
|
344 |
+
print(colored(f"\n\n Meta-Prompt Chain of Reasoning: {meta_prompt_cor}\n\n", 'green'))
|
345 |
+
|
346 |
+
return state
|
347 |
+
|
348 |
+
|
349 |
+
class NoToolExpert(BaseAgent[State]):
|
350 |
+
print(colored(f"\n\n DEBUG: We are running the NoToolExpert tool\n\n", 'red'))
|
351 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
352 |
+
model_endpoint: str = None, stop: str = None):
|
353 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
354 |
+
self.llm = self.get_llm(json_model=False)
|
355 |
+
|
356 |
+
def get_prompt(self, state) -> str:
|
357 |
+
# print(f"\nn{state}\n")
|
358 |
+
system_prompt = state["meta_prompt"][-1].content
|
359 |
+
return system_prompt
|
360 |
+
|
361 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
362 |
+
|
363 |
+
# meta_prompts = state.get("meta_prompt", [])
|
364 |
+
associated_meta_prompt = state["meta_prompt"][-1].content
|
365 |
+
parse_expert = self.get_llm(json_model=True)
|
366 |
+
|
367 |
+
parse_expert_prompt = """
|
368 |
+
You must parse the expert from the text. The expert will be one of the following.
|
369 |
+
1. Expert Planner
|
370 |
+
2. Expert Writer
|
371 |
+
Return your response as the following JSON
|
372 |
+
{{"expert": "Expert Planner" or "Expert Writer"}}
|
373 |
+
"""
|
374 |
+
|
375 |
+
input = [
|
376 |
+
{"role": "user", "content": associated_meta_prompt},
|
377 |
+
{"role": "assistant", "content": f"system_prompt:{parse_expert_prompt}"}
|
378 |
+
|
379 |
+
]
|
380 |
+
|
381 |
+
|
382 |
+
retries = 0
|
383 |
+
associated_expert = None
|
384 |
+
|
385 |
+
while retries < 4 and associated_expert is None:
|
386 |
+
retries += 1
|
387 |
+
if self.server == 'vllm':
|
388 |
+
guided_json = guided_json_parse_expert
|
389 |
+
parse_expert_response = parse_expert.invoke(input, guided_json)
|
390 |
+
else:
|
391 |
+
parse_expert_response = parse_expert.invoke(input)
|
392 |
+
|
393 |
+
associated_expert_json = json.loads(parse_expert_response)
|
394 |
+
associated_expert = associated_expert_json.get("expert")
|
395 |
+
|
396 |
+
# associated_expert = parse_expert_text(associated_meta_prompt)
|
397 |
+
print(colored(f"\n\n Expert: {associated_expert}\n\n", 'green'))
|
398 |
+
|
399 |
+
if associated_expert == "Expert Planner":
|
400 |
+
expert_update_key = "expert_plan"
|
401 |
+
if associated_expert == "Expert Writer":
|
402 |
+
expert_update_key = "expert_writing"
|
403 |
+
|
404 |
+
|
405 |
+
updates_conversation_history = {
|
406 |
+
"conversation_history": [
|
407 |
+
{"role": "user", "content": user_input},
|
408 |
+
{"role": "assistant", "content": f"{str(response)}"}
|
409 |
+
|
410 |
+
],
|
411 |
+
expert_update_key: {"role": "assistant", "content": f"{str(response)}"},
|
412 |
+
"progress_tracking": f"Jar3d has completed its {associated_expert} work"
|
413 |
+
}
|
414 |
+
|
415 |
+
|
416 |
+
return updates_conversation_history
|
417 |
+
|
418 |
+
def get_conv_history(self, state: State) -> str:
|
419 |
+
pass
|
420 |
+
|
421 |
+
def get_user_input(self) -> str:
|
422 |
+
pass
|
423 |
+
|
424 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
425 |
+
pass
|
426 |
+
|
427 |
+
def use_tool(self) -> Any:
|
428 |
+
pass
|
429 |
+
|
430 |
+
|
431 |
+
# @log_function(logger)
|
432 |
+
def run(self, state: State) -> State:
|
433 |
+
# chat_counter(state)
|
434 |
+
all_expert_research = []
|
435 |
+
meta_prompt = state["meta_prompt"][1].content
|
436 |
+
|
437 |
+
if state.get("expert_research"):
|
438 |
+
expert_research = state["expert_research"]
|
439 |
+
all_expert_research.extend(expert_research)
|
440 |
+
research_prompt = f"\n Your response must be delivered considering following research.\n ## Research\n {all_expert_research} "
|
441 |
+
user_input = f"{meta_prompt}\n{research_prompt}"
|
442 |
+
|
443 |
+
else:
|
444 |
+
user_input = meta_prompt
|
445 |
+
|
446 |
+
print(colored(f"\n\n DEBUG: We are running the NoToolExpert tool\n\n", 'red'))
|
447 |
+
state = self.invoke(state=state, user_input=user_input)
|
448 |
+
return state
|
449 |
+
|
450 |
+
|
451 |
+
class ToolExpert(BaseAgent[State]):
|
452 |
+
print(colored(f"\n\n DEBUG: We are running the ToolExpert tool\n\n", 'red'))
|
453 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
454 |
+
model_endpoint: str = None, stop: str = None, location: str = None, hybrid: bool = False):
|
455 |
+
super().__init__(model, server, temperature, model_endpoint, stop, location, hybrid)
|
456 |
+
|
457 |
+
# print(f"\n\n DEBUG LOCATION: {self.location}")
|
458 |
+
|
459 |
+
self.llm = self.get_llm(json_model=False)
|
460 |
+
|
461 |
+
def get_prompt(self, state) -> str:
|
462 |
+
system_prompt = state["meta_prompt"][-1].content
|
463 |
+
return system_prompt
|
464 |
+
|
465 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
466 |
+
|
467 |
+
if self.hybrid:
|
468 |
+
message = f"""Jar3d has completed its internet research.
|
469 |
+
Jar3d has generated a knowledge graph, you can view it here: https://neo4j.com/product/auradb/
|
470 |
+
"""
|
471 |
+
else:
|
472 |
+
message = f"""Jar3d has completed its internet research.
|
473 |
+
"""
|
474 |
+
updates_conversation_history = {
|
475 |
+
"conversation_history": [
|
476 |
+
{"role": "user", "content": user_input},
|
477 |
+
{"role": "assistant", "content": f"{str(response)}"}
|
478 |
+
],
|
479 |
+
"expert_research": {"role": "assistant", "content": f"{str(response)}"},
|
480 |
+
"progress_tracking": message
|
481 |
+
}
|
482 |
+
return updates_conversation_history
|
483 |
+
|
484 |
+
def get_conv_history(self, state: State) -> str:
|
485 |
+
pass
|
486 |
+
|
487 |
+
def get_user_input(self) -> str:
|
488 |
+
pass
|
489 |
+
|
490 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
491 |
+
pass
|
492 |
+
|
493 |
+
def use_tool(self, mode: str, engine: str, tool_input: str, meta_prompt: str = None, query: list[str] = None, hybrid: bool = False) -> Any:
|
494 |
+
if mode == "serper":
|
495 |
+
if engine == "search":
|
496 |
+
results = serper_search(tool_input, self.location)
|
497 |
+
return {"results": results, "is_shopping": False}
|
498 |
+
elif engine == "shopping":
|
499 |
+
results = serper_shopping_search(tool_input, self.location)
|
500 |
+
return {"results": results, "is_shopping": True}
|
501 |
+
# elif engine == "scholar":
|
502 |
+
# results = serper_scholar_search(tool_input, self.location)
|
503 |
+
# return {"results": results, "is_shopping": False}
|
504 |
+
|
505 |
+
elif mode == "rag":
|
506 |
+
print(colored(f"\n\n DEBUG: We are running the Graph RAG TOOL!!\n\n", 'red'))
|
507 |
+
nodes = None
|
508 |
+
relationships = None
|
509 |
+
print(colored(f"\n\n DEBUG Retreival Mode: {hybrid}\n\n", 'green'))
|
510 |
+
results = run_rag(urls=tool_input, allowed_nodes=nodes, allowed_relationships=relationships, query=query, hybrid=self.hybrid)
|
511 |
+
|
512 |
+
return {"results": results, "is_shopping": False}
|
513 |
+
|
514 |
+
def generate_search_queries(self, meta_prompt: str, num_queries: int = 5) -> List[Dict[str, str]]:
|
515 |
+
|
516 |
+
print(colored(f"\n\n DEBUG: We are running the generate_search_queries tool\n\n", 'red'))
|
517 |
+
|
518 |
+
refine_query_template = """
|
519 |
+
# Objective
|
520 |
+
Your mission is to systematically address your manager's instructions by determining
|
521 |
+
the most appropriate search queries to use **AND** to determine the best engine to use for each query.
|
522 |
+
Your engine choice is either "search" or "shopping". You must return either "search" or "shopping" for each query.
|
523 |
+
You will generate {num_queries} different search queries.
|
524 |
+
|
525 |
+
# Manager's Instructions
|
526 |
+
{manager_instructions}
|
527 |
+
|
528 |
+
# Flexible Search Algorithm for Simple and Complex Questions
|
529 |
+
|
530 |
+
1. Initial search:
|
531 |
+
- For a simple question: "[Question keywords]"
|
532 |
+
- For a complex topic: "[Main topic] overview"
|
533 |
+
|
534 |
+
2. For each subsequent search:
|
535 |
+
- Choose one of these strategies:
|
536 |
+
|
537 |
+
a. Specify:
|
538 |
+
Add a more specific term or aspect related to the topic.
|
539 |
+
|
540 |
+
b. Broaden:
|
541 |
+
Remove a specific term or add "general" or "overview" to the query.
|
542 |
+
|
543 |
+
c. Pivot:
|
544 |
+
Choose a different but related term from the topic.
|
545 |
+
|
546 |
+
d. Compare:
|
547 |
+
Add "vs" or "compared to" along with a related term.
|
548 |
+
|
549 |
+
e. Question:
|
550 |
+
Rephrase the query as a question by adding "what", "how", "why", etc.
|
551 |
+
|
552 |
+
# Response Format
|
553 |
+
|
554 |
+
**Return the following JSON:**
|
555 |
+
{{
|
556 |
+
"search_queries": [
|
557 |
+
{{"engine": "search", "query": "Query 1"}},
|
558 |
+
{{"engine": "shopping", "query": "Query 2"}},
|
559 |
+
...
|
560 |
+
{{"engine": "search", "query": "Query {num_queries}"}}
|
561 |
+
]
|
562 |
+
}}
|
563 |
+
|
564 |
+
|
565 |
+
Remember:
|
566 |
+
- Generate {num_queries} unique and diverse search queries.
|
567 |
+
- Each query should explore a different aspect or approach to the topic.
|
568 |
+
- Ensure the queries cover various aspects of the manager's instructions.
|
569 |
+
- The "engine" field should be "search" or "shopping" for each query.
|
570 |
+
- "search" best for general websearch.
|
571 |
+
- "shopping" best when you need to find products and prices.
|
572 |
+
"""
|
573 |
+
|
574 |
+
refine_query = self.get_llm(json_model=True)
|
575 |
+
refine_prompt = refine_query_template.format(manager_instructions=meta_prompt, num_queries=num_queries)
|
576 |
+
input_data = [
|
577 |
+
{"role": "user", "content": "Generate search queries"},
|
578 |
+
{"role": "assistant", "content": f"system_prompt:{refine_prompt}"}
|
579 |
+
]
|
580 |
+
|
581 |
+
guided_json = guided_json_search_query_two
|
582 |
+
|
583 |
+
if self.server == 'vllm':
|
584 |
+
refined_queries = refine_query.invoke(input_data, guided_json)
|
585 |
+
else:
|
586 |
+
print(colored(f"\n\n DEBUG: We are running the refine_query tool without vllm\n\n", 'red'))
|
587 |
+
refined_queries = refine_query.invoke(input_data)
|
588 |
+
|
589 |
+
refined_queries_json = json.loads(refined_queries)
|
590 |
+
return refined_queries_json.get("search_queries", [])
|
591 |
+
|
592 |
+
def process_serper_result(self, query: Dict[str, str], serper_response: Dict[str, Any]) -> Dict[str, Any]:
|
593 |
+
|
594 |
+
print(colored(f"\n\n DEBUG: We are running the process_serper_result tool\n\n", 'red'))
|
595 |
+
|
596 |
+
best_url_template = """
|
597 |
+
Given the Serper results and the search query, select the best URL.
|
598 |
+
|
599 |
+
# Search Query
|
600 |
+
{search_query}
|
601 |
+
|
602 |
+
# Serper Results
|
603 |
+
{serper_results}
|
604 |
+
|
605 |
+
**Return the following JSON:**
|
606 |
+
|
607 |
+
{{"best_url": The URL from the Serper results that aligns most with the search query.}}
|
608 |
+
"""
|
609 |
+
|
610 |
+
best_url = self.get_llm(json_model=True)
|
611 |
+
best_url_prompt = best_url_template.format(search_query=query["query"], serper_results=serper_response)
|
612 |
+
input_data = [
|
613 |
+
{"role": "user", "content": serper_response},
|
614 |
+
{"role": "assistant", "content": f"system_prompt:{best_url_prompt}"}
|
615 |
+
]
|
616 |
+
|
617 |
+
guided_json = guided_json_best_url_two
|
618 |
+
|
619 |
+
if self.server == 'vllm':
|
620 |
+
best_url = best_url.invoke(input_data, guided_json)
|
621 |
+
else:
|
622 |
+
print(colored(f"\n\n DEBUG: We are running the best_url tool without vllm\n\n", 'red'))
|
623 |
+
best_url = best_url.invoke(input_data)
|
624 |
+
|
625 |
+
best_url_json = json.loads(best_url)
|
626 |
+
|
627 |
+
return {"query": query, "url": best_url_json.get("best_url")}
|
628 |
+
|
629 |
+
def analyze_and_refine_queries(
|
630 |
+
self,
|
631 |
+
serper_results: List[Dict[str, Any]],
|
632 |
+
meta_prompt: str,
|
633 |
+
num_queries: int = 1 # Default to 1 query
|
634 |
+
) -> List[Dict[str, str]]:
|
635 |
+
"""
|
636 |
+
Analyzes the search results and generates refined search queries.
|
637 |
+
"""
|
638 |
+
|
639 |
+
print(colored(f"\n\n DEBUG: We are running the analyze_and_refine_queries tool\n\n", 'red'))
|
640 |
+
|
641 |
+
observations = []
|
642 |
+
for result in serper_results:
|
643 |
+
results_content = result.get("results", {})
|
644 |
+
if result.get("is_shopping"):
|
645 |
+
# Handle shopping results if necessary
|
646 |
+
shopping_results = results_content.get("shopping_results", [])
|
647 |
+
snippets = [f"{item.get('title', '')} - {item.get('price', '')}" for item in shopping_results]
|
648 |
+
else:
|
649 |
+
# Handle organic search results
|
650 |
+
organic_results = results_content.get("organic_results", [])
|
651 |
+
snippets = [item.get("snippet", "") for item in organic_results]
|
652 |
+
observations.extend(snippets)
|
653 |
+
|
654 |
+
# Include num_queries in the prompt to control the number of queries generated
|
655 |
+
analysis_prompt_template = """
|
656 |
+
Based on the following search results, generate {num_queries} new search queries to further investigate the topic.
|
657 |
+
|
658 |
+
# Search Results
|
659 |
+
{observations}
|
660 |
+
|
661 |
+
# Manager's Instructions
|
662 |
+
{meta_prompt}
|
663 |
+
|
664 |
+
# Guidelines
|
665 |
+
- Identify gaps or missing information in the current search results.
|
666 |
+
- Generate queries that could fill these gaps or provide deeper insight.
|
667 |
+
- Provide diverse and relevant queries.
|
668 |
+
|
669 |
+
Provide the new search queries in a JSON format:
|
670 |
+
{{
|
671 |
+
"search_queries": [
|
672 |
+
{{"engine": "search", "query": "New Query 1"}},
|
673 |
+
{{"engine": "shopping", "query": "New Query 2"}},
|
674 |
+
...
|
675 |
+
{{"engine": "search", "query": "New Query {num_queries}"}}
|
676 |
+
]
|
677 |
+
}}
|
678 |
+
"""
|
679 |
+
|
680 |
+
analysis_prompt = analysis_prompt_template.format(
|
681 |
+
observations="\n".join(observations),
|
682 |
+
meta_prompt=meta_prompt,
|
683 |
+
num_queries=num_queries # Pass the num_queries to the prompt
|
684 |
+
)
|
685 |
+
|
686 |
+
analysis_llm = self.get_llm(json_model=True)
|
687 |
+
input_data = [
|
688 |
+
{"role": "user", "content": "Analyze and refine search queries"},
|
689 |
+
{"role": "assistant", "content": f"system_prompt:{analysis_prompt}"}
|
690 |
+
]
|
691 |
+
|
692 |
+
guided_json = guided_json_search_query_two
|
693 |
+
|
694 |
+
if self.server == 'vllm':
|
695 |
+
refined_queries = analysis_llm.invoke(input_data, guided_json)
|
696 |
+
else:
|
697 |
+
print(colored("\n\n DEBUG: We are running the analysis without vllm\n\n", 'red'))
|
698 |
+
refined_queries = analysis_llm.invoke(input_data)
|
699 |
+
|
700 |
+
# Parse the LLM's response
|
701 |
+
refined_queries_json = json.loads(refined_queries)
|
702 |
+
refined_queries_list = refined_queries_json.get("search_queries", [])
|
703 |
+
|
704 |
+
# Limit the number of queries returned to num_queries
|
705 |
+
return refined_queries_list[:num_queries]
|
706 |
+
|
707 |
+
def run(self, state: State) -> State:
|
708 |
+
meta_prompt = state["meta_prompt"][-1].content
|
709 |
+
print(colored(f"\n\n Meta-Prompt: {meta_prompt}\n\n", 'green'))
|
710 |
+
|
711 |
+
# Set up iterative search parameters
|
712 |
+
max_iterations = 5 # Define a maximum number of iterations to prevent infinite loops
|
713 |
+
iteration = 0
|
714 |
+
|
715 |
+
# Initial search queries
|
716 |
+
search_queries = self.generate_search_queries(meta_prompt, num_queries=5)
|
717 |
+
all_serper_results = []
|
718 |
+
all_best_urls = []
|
719 |
+
|
720 |
+
while iteration < max_iterations:
|
721 |
+
print(colored(f"\n\n Iteration {iteration + 1}\n\n", 'yellow'))
|
722 |
+
iteration += 1
|
723 |
+
|
724 |
+
# Use ThreadPoolExecutor to call Serper tool for each query in parallel
|
725 |
+
try:
|
726 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=min(len(search_queries), 5)) as executor:
|
727 |
+
future_to_query = {
|
728 |
+
executor.submit(
|
729 |
+
self.use_tool,
|
730 |
+
"serper",
|
731 |
+
query["engine"],
|
732 |
+
query["query"],
|
733 |
+
None
|
734 |
+
): query for query in search_queries
|
735 |
+
}
|
736 |
+
serper_results = []
|
737 |
+
for future in concurrent.futures.as_completed(future_to_query):
|
738 |
+
query = future_to_query[future]
|
739 |
+
try:
|
740 |
+
result = future.result()
|
741 |
+
serper_results.append(result)
|
742 |
+
except Exception as exc:
|
743 |
+
print(colored(f"Error processing query {query}: {exc}", 'red'))
|
744 |
+
serper_results.append(None)
|
745 |
+
except Exception as e:
|
746 |
+
print(colored(f"Error in threading: {str(e)}. Falling back to non-parallel processing.", 'red'))
|
747 |
+
serper_results = [self.use_tool("serper", query["engine"], query["query"], None) for query in search_queries]
|
748 |
+
|
749 |
+
# Collect and store all results
|
750 |
+
all_serper_results.extend(zip(search_queries, serper_results))
|
751 |
+
|
752 |
+
# Process Serper results to get best URLs
|
753 |
+
try:
|
754 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=min(len(serper_results), 5)) as executor:
|
755 |
+
future_to_query = {
|
756 |
+
executor.submit(
|
757 |
+
self.process_serper_result,
|
758 |
+
query,
|
759 |
+
result["results"] if result else {}
|
760 |
+
): query for query, result in zip(search_queries, serper_results)
|
761 |
+
}
|
762 |
+
best_url_results = []
|
763 |
+
for future in concurrent.futures.as_completed(future_to_query):
|
764 |
+
query = future_to_query[future]
|
765 |
+
try:
|
766 |
+
result = future.result()
|
767 |
+
best_url_results.append(result)
|
768 |
+
except Exception as exc:
|
769 |
+
print(colored(f"Error processing result for query {query}: {exc}", 'red'))
|
770 |
+
best_url_results.append(None)
|
771 |
+
except Exception as e:
|
772 |
+
print(colored(f"Error in threading: {str(e)}. Falling back to non-parallel processing for best URLs.", 'red'))
|
773 |
+
best_url_results = [
|
774 |
+
self.process_serper_result(query, result["results"] if result else {})
|
775 |
+
for query, result in zip(search_queries, serper_results)
|
776 |
+
]
|
777 |
+
|
778 |
+
# Collect all best URLs
|
779 |
+
all_best_urls.extend(best_url_results)
|
780 |
+
|
781 |
+
# Remove duplicates while preserving query alignment
|
782 |
+
url_query_pairs = []
|
783 |
+
seen_urls = set()
|
784 |
+
for item in all_best_urls:
|
785 |
+
url = item["url"]
|
786 |
+
query = item["query"]["query"]
|
787 |
+
engine = item["query"]["engine"]
|
788 |
+
if url and engine == "search" and url not in seen_urls:
|
789 |
+
url_query_pairs.append({"url": url, "query": query})
|
790 |
+
seen_urls.add(url)
|
791 |
+
|
792 |
+
# Extract unique URLs and queries while preserving alignment
|
793 |
+
unique_urls = [item["url"] for item in url_query_pairs]
|
794 |
+
unique_queries = [item["query"] for item in url_query_pairs]
|
795 |
+
|
796 |
+
print(colored("\n\n Sourced data from {} sources:".format(len(unique_urls)), 'yellow'))
|
797 |
+
print(colored(f"\n\n Search Queries {unique_queries}", 'yellow'))
|
798 |
+
|
799 |
+
for i, url in enumerate(unique_urls, 1):
|
800 |
+
print(colored(" {}. {}".format(i, url), 'green'))
|
801 |
+
|
802 |
+
# Analyze search results and refine the queries
|
803 |
+
refined_search_queries = self.analyze_and_refine_queries(
|
804 |
+
[result for _, result in all_serper_results],
|
805 |
+
meta_prompt,
|
806 |
+
num_queries=1 # Limit to 1 query per iteration
|
807 |
+
)
|
808 |
+
|
809 |
+
# Check if refinement is needed
|
810 |
+
if not refined_search_queries or refined_search_queries == search_queries:
|
811 |
+
# No further refinement possible
|
812 |
+
break
|
813 |
+
|
814 |
+
# Update search queries for the next iteration
|
815 |
+
search_queries = refined_search_queries
|
816 |
+
|
817 |
+
# After iterations, process the collected results
|
818 |
+
try:
|
819 |
+
scraper_response = self.use_tool(
|
820 |
+
mode="rag",
|
821 |
+
engine=None,
|
822 |
+
tool_input=unique_urls,
|
823 |
+
meta_prompt=meta_prompt,
|
824 |
+
query=unique_queries # Pass aligned queries
|
825 |
+
)
|
826 |
+
except Exception as e:
|
827 |
+
scraper_response = {"results": f"Error {e}: Failed to scrape results", "is_shopping": False}
|
828 |
+
|
829 |
+
updates = self.process_response(scraper_response, user_input="Research")
|
830 |
+
|
831 |
+
for key, value in updates.items():
|
832 |
+
state = self.update_state(key, value, state)
|
833 |
+
|
834 |
+
return state
|
835 |
+
|
836 |
+
class Router(BaseAgent[State]):
|
837 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
838 |
+
model_endpoint: str = None, stop: str = None):
|
839 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
840 |
+
self.llm = self.get_llm(json_model=True)
|
841 |
+
|
842 |
+
|
843 |
+
def get_prompt(self, state) -> str:
|
844 |
+
system_prompt = state["meta_prompt"][-1].content
|
845 |
+
return system_prompt
|
846 |
+
|
847 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
848 |
+
|
849 |
+
updates_conversation_history = {
|
850 |
+
"router_decision": [
|
851 |
+
{"role": "user", "content": user_input},
|
852 |
+
{"role": "assistant", "content": f"{str(response)}"}
|
853 |
+
|
854 |
+
],
|
855 |
+
"progress_tracking": f"Jar3d has routed to an expert 🤓"
|
856 |
+
},
|
857 |
+
|
858 |
+
return updates_conversation_history
|
859 |
+
|
860 |
+
def get_conv_history(self, state: State) -> str:
|
861 |
+
pass
|
862 |
+
|
863 |
+
def get_user_input(self) -> str:
|
864 |
+
pass
|
865 |
+
|
866 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
867 |
+
pass
|
868 |
+
|
869 |
+
def use_tool(self, tool_input: str, mode: str) -> Any:
|
870 |
+
pass
|
871 |
+
|
872 |
+
# @log_function(logger)
|
873 |
+
def run(self, state: State) -> State:
|
874 |
+
|
875 |
+
router_template = """
|
876 |
+
Given these instructions from your manager.
|
877 |
+
|
878 |
+
# Response from Manager
|
879 |
+
{manager_response}
|
880 |
+
|
881 |
+
**Return the following JSON:**
|
882 |
+
|
883 |
+
{{""router_decision: Return the next agent to pass control to.}}
|
884 |
+
|
885 |
+
**strictly** adhere to these **guidelines** for routing.
|
886 |
+
If your maneger's response contains "Expert Internet Researcher", return "tool_expert".
|
887 |
+
If your manager's response contains "Expert Planner" or "Expert Writer", return "no_tool_expert".
|
888 |
+
If your manager's response contains '>> FINAL ANSWER:', return "end_chat".
|
889 |
+
|
890 |
+
"""
|
891 |
+
system_prompt = router_template.format(manager_response=state["meta_prompt"][-1].content)
|
892 |
+
input = [
|
893 |
+
{"role": "user", "content": ""},
|
894 |
+
{"role": "assistant", "content": f"system_prompt:{system_prompt}"}
|
895 |
+
|
896 |
+
]
|
897 |
+
router = self.get_llm(json_model=True)
|
898 |
+
|
899 |
+
if self.server == 'vllm':
|
900 |
+
guided_json = guided_json_router_decision
|
901 |
+
router_response = router.invoke(input, guided_json)
|
902 |
+
else:
|
903 |
+
router_response = router.invoke(input)
|
904 |
+
|
905 |
+
router_response = json.loads(router_response)
|
906 |
+
router_response = router_response.get("router_decision")
|
907 |
+
|
908 |
+
state = self.update_state("router_decision", router_response, state)
|
909 |
+
|
910 |
+
return state
|
agents/legacy/jar3d_agent.py
ADDED
@@ -0,0 +1,655 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
from multiprocessing import Pool, cpu_count
|
3 |
+
# import requests
|
4 |
+
# from tenacity import RetryError
|
5 |
+
import re
|
6 |
+
import logging
|
7 |
+
import chainlit as cl
|
8 |
+
from termcolor import colored
|
9 |
+
from typing import Any, Dict, Union, List
|
10 |
+
from typing import TypedDict, Annotated
|
11 |
+
from langgraph.graph.message import add_messages
|
12 |
+
from agents.base_agent import BaseAgent
|
13 |
+
from utils.read_markdown import read_markdown_file
|
14 |
+
from tools.google_serper import serper_search, serper_shopping_search
|
15 |
+
from utils.logging import log_function, setup_logging
|
16 |
+
from tools.offline_graph_rag_tool import run_rag
|
17 |
+
from prompt_engineering.guided_json_lib import (
|
18 |
+
guided_json_search_query,
|
19 |
+
guided_json_best_url_two,
|
20 |
+
guided_json_router_decision,
|
21 |
+
guided_json_parse_expert,
|
22 |
+
guided_json_search_query_two
|
23 |
+
)
|
24 |
+
|
25 |
+
|
26 |
+
setup_logging(level=logging.DEBUG)
|
27 |
+
logger = logging.getLogger(__name__)
|
28 |
+
|
29 |
+
class MessageDict(TypedDict):
|
30 |
+
role: str
|
31 |
+
content: str
|
32 |
+
|
33 |
+
class State(TypedDict):
|
34 |
+
meta_prompt: Annotated[List[dict], add_messages]
|
35 |
+
conversation_history: Annotated[List[dict], add_messages]
|
36 |
+
requirements_gathering: Annotated[List[str], add_messages]
|
37 |
+
expert_plan: str
|
38 |
+
expert_research: Annotated[List[str], add_messages]
|
39 |
+
expert_research_shopping: Annotated[List[str], add_messages]
|
40 |
+
expert_writing: str
|
41 |
+
user_input: Annotated[List[str], add_messages]
|
42 |
+
previous_search_queries: Annotated[List[dict], add_messages]
|
43 |
+
router_decision: str
|
44 |
+
chat_limit: int
|
45 |
+
chat_finished: bool
|
46 |
+
recursion_limit: int
|
47 |
+
final_answer: str
|
48 |
+
|
49 |
+
state: State = {
|
50 |
+
"meta_prompt": [],
|
51 |
+
"conversation_history": [],
|
52 |
+
"requirements_gathering": [],
|
53 |
+
"expert_plan": [],
|
54 |
+
"expert_research": [],
|
55 |
+
"expert_research_shopping": [],
|
56 |
+
"expert_writing": [],
|
57 |
+
"user_input": [],
|
58 |
+
"previous_search_queries": [],
|
59 |
+
"router_decision": None,
|
60 |
+
"chat_limit": None,
|
61 |
+
"chat_finished": False,
|
62 |
+
"recursion_limit": None,
|
63 |
+
"final_answer": None
|
64 |
+
}
|
65 |
+
|
66 |
+
def chat_counter(state: State) -> State:
|
67 |
+
chat_limit = state.get("chat_limit")
|
68 |
+
if chat_limit is None:
|
69 |
+
chat_limit = 0
|
70 |
+
chat_limit += 1
|
71 |
+
state["chat_limit"] = chat_limit
|
72 |
+
return chat_limit
|
73 |
+
|
74 |
+
def routing_function(state: State) -> str:
|
75 |
+
decision = state["router_decision"]
|
76 |
+
print(colored(f"\n\n Routing function called. Decision: {decision}\n\n", 'green'))
|
77 |
+
return decision
|
78 |
+
|
79 |
+
def set_chat_finished(state: State) -> bool:
|
80 |
+
state["chat_finished"] = True
|
81 |
+
final_response = state["meta_prompt"][-1].content
|
82 |
+
print(colored(f"\n\n DEBUG FINAL RESPONSE: {final_response}\n\n", 'green'))
|
83 |
+
|
84 |
+
# Split the response at ">> FINAL ANSWER:"
|
85 |
+
parts = final_response.split(">> FINAL ANSWER:")
|
86 |
+
if len(parts) > 1:
|
87 |
+
answer_part = parts[1].strip()
|
88 |
+
|
89 |
+
# Remove any triple quotes
|
90 |
+
final_response_formatted = answer_part.strip('"""')
|
91 |
+
|
92 |
+
# Remove leading whitespace
|
93 |
+
final_response_formatted = final_response_formatted.lstrip()
|
94 |
+
|
95 |
+
# Remove the CoR dictionary at the end
|
96 |
+
cor_pattern = r'\nCoR\s*=\s*\{[\s\S]*\}\s*$'
|
97 |
+
final_response_formatted = re.sub(cor_pattern, '', final_response_formatted)
|
98 |
+
|
99 |
+
# Remove any trailing whitespace
|
100 |
+
final_response_formatted = final_response_formatted.rstrip()
|
101 |
+
|
102 |
+
# print(colored(f"\n\n DEBUG: {final_response_formatted}\n\n", 'green'))
|
103 |
+
print(colored(f"\n\n Jar3d👩💻: {final_response_formatted}", 'cyan'))
|
104 |
+
state["final_answer"] = f'''{final_response_formatted}'''
|
105 |
+
else:
|
106 |
+
print(colored("Error: Could not find '>> FINAL ANSWER:' in the response", 'red'))
|
107 |
+
state["final_answer"] = "Error: No final answer found"
|
108 |
+
|
109 |
+
return state
|
110 |
+
|
111 |
+
class Jar3d(BaseAgent[State]):
|
112 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
113 |
+
model_endpoint: str = None, stop: str = None):
|
114 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
115 |
+
self.llm = self.get_llm(json_model=False)
|
116 |
+
|
117 |
+
def get_prompt(self, state: State = None) -> str:
|
118 |
+
system_prompt_md = read_markdown_file('prompt_engineering/jar3d_requirements_prompt.md')
|
119 |
+
system_prompt = f"{system_prompt_md}\n <Type2> {state.get('final_answer', '')} </Type2>"
|
120 |
+
return system_prompt
|
121 |
+
|
122 |
+
def process_response(self, response: Any, user_input: str, state: State = None) -> Dict[str, List[Dict[str, str]]]:
|
123 |
+
updates_conversation_history = {
|
124 |
+
"requirements_gathering": [
|
125 |
+
{"role": "user", "content": f"{user_input}"},
|
126 |
+
{"role": "assistant", "content": str(response)}
|
127 |
+
]
|
128 |
+
}
|
129 |
+
return updates_conversation_history
|
130 |
+
|
131 |
+
def get_conv_history(self, state: State) -> str:
|
132 |
+
conversation_history = state.get('requirements_gathering', [])
|
133 |
+
return "\n".join([f"{msg['role']}: {msg['content']}" for msg in conversation_history])
|
134 |
+
|
135 |
+
def get_user_input(self) -> str:
|
136 |
+
pass
|
137 |
+
|
138 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
139 |
+
pass
|
140 |
+
|
141 |
+
def use_tool(self) -> Any:
|
142 |
+
pass
|
143 |
+
|
144 |
+
def run_chainlit(self, state: State, message: cl.Message) -> State:
|
145 |
+
user_message = message.content
|
146 |
+
# system_prompt = self.get_prompt()
|
147 |
+
user_input = f"cogor:{user_message}"
|
148 |
+
|
149 |
+
# user_input = f"{system_prompt}\n cogor {user_message}"
|
150 |
+
|
151 |
+
state = self.invoke(state=state, user_input=user_input)
|
152 |
+
response = state['requirements_gathering'][-1]["content"]
|
153 |
+
response = re.sub(r'^```python[\s\S]*?```\s*', '', response, flags=re.MULTILINE)
|
154 |
+
response = response.lstrip()
|
155 |
+
|
156 |
+
return state, response
|
157 |
+
|
158 |
+
|
159 |
+
class MetaExpert(BaseAgent[State]):
|
160 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
161 |
+
model_endpoint: str = None, stop: str = None):
|
162 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
163 |
+
self.llm = self.get_llm(json_model=False)
|
164 |
+
|
165 |
+
def get_prompt(self, state:None) -> str:
|
166 |
+
system_prompt = read_markdown_file('prompt_engineering/jar3d_meta_prompt.md')
|
167 |
+
return system_prompt
|
168 |
+
|
169 |
+
def process_response(self, response: Any, user_input: str, state: State = None) -> Dict[str, List[MessageDict]]:
|
170 |
+
user_input = None
|
171 |
+
updates_conversation_history = {
|
172 |
+
"meta_prompt": [
|
173 |
+
{"role": "user", "content": f"{user_input}"},
|
174 |
+
{"role": "assistant", "content": str(response)}
|
175 |
+
|
176 |
+
]
|
177 |
+
}
|
178 |
+
return updates_conversation_history
|
179 |
+
|
180 |
+
# @log_function(logger)
|
181 |
+
def get_conv_history(self, state: State) -> str:
|
182 |
+
|
183 |
+
all_expert_research = []
|
184 |
+
|
185 |
+
if state["expert_research"]:
|
186 |
+
expert_research = state["expert_research"]
|
187 |
+
all_expert_research.extend(expert_research)
|
188 |
+
else:
|
189 |
+
all_expert_research = []
|
190 |
+
|
191 |
+
expert_message_history = f"""
|
192 |
+
<expert_plan>
|
193 |
+
## Your Expert Plan:\n{state.get("expert_plan", [])}\n
|
194 |
+
</expert_plan>
|
195 |
+
|
196 |
+
<expert_writing>
|
197 |
+
## Your Expert Writing:\n{state.get("expert_writing", [])}\n
|
198 |
+
</expert_writing>
|
199 |
+
|
200 |
+
<internet_research_shopping_list>
|
201 |
+
## Your Expert Shopping List:\n{state.get("expert_research_shopping", [])}\n
|
202 |
+
</internet_research_shopping_list>
|
203 |
+
|
204 |
+
<internet_research>
|
205 |
+
## Your Expert Research:{all_expert_research}\n
|
206 |
+
</internet_research>
|
207 |
+
"""
|
208 |
+
|
209 |
+
return expert_message_history
|
210 |
+
|
211 |
+
def get_user_input(self) -> str:
|
212 |
+
user_input = input("Enter your query: ")
|
213 |
+
return user_input
|
214 |
+
|
215 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
216 |
+
pass
|
217 |
+
|
218 |
+
def use_tool(self) -> Any:
|
219 |
+
pass
|
220 |
+
|
221 |
+
@log_function(logger)
|
222 |
+
def run(self, state: State) -> State:
|
223 |
+
|
224 |
+
counter = chat_counter(state) # Counts every time we invoke the Meta Agent
|
225 |
+
recursion_limit = state.get("recursion_limit")
|
226 |
+
recursions = 3*counter - 2
|
227 |
+
print(colored(f"\n\n * We have envoked the Meta-Agent {counter} times.\n * we have run {recursions} max total iterations: {recursion_limit}\n\n", "green"))
|
228 |
+
|
229 |
+
upper_limit_recursions = recursion_limit
|
230 |
+
lower_limit_recursions = recursion_limit - 2
|
231 |
+
|
232 |
+
if recursions >= lower_limit_recursions and recursions <= upper_limit_recursions:
|
233 |
+
final_answer = "**You are being explicitly told to produce your [Type 2] work now!**"
|
234 |
+
elif recursions > upper_limit_recursions:
|
235 |
+
extra_recursions = recursions - upper_limit_recursions
|
236 |
+
base_message = "**You are being explicitly told to produce your [Type 2] work now!**"
|
237 |
+
final_answer = (base_message + "\n") * (extra_recursions + 1)
|
238 |
+
else:
|
239 |
+
final_answer = None
|
240 |
+
|
241 |
+
try:
|
242 |
+
requirements = state['requirements_gathering'][-1]["content"]
|
243 |
+
except:
|
244 |
+
requirements = state['requirements_gathering'][-1].content
|
245 |
+
|
246 |
+
formatted_requirements = '\n\n'.join(re.findall(r'```python\s*([\s\S]*?)\s*```', requirements, re.MULTILINE))
|
247 |
+
|
248 |
+
print(colored(f"\n\n User Requirements: {formatted_requirements}\n\n", 'green'))
|
249 |
+
|
250 |
+
if state.get("meta_prompt"):
|
251 |
+
try:
|
252 |
+
meta_prompt = state['meta_prompt'][-1]["content"]
|
253 |
+
except:
|
254 |
+
meta_prompt = state['meta_prompt'][-1].content
|
255 |
+
|
256 |
+
# print(colored(f"\n\n DEBUG Meta-Prompt: {meta_prompt}\n\n", 'yellow'))
|
257 |
+
|
258 |
+
cor_match = '\n\n'.join(re.findall(r'```python\s*([\s\S]*?)\s*```', meta_prompt, re.MULTILINE))
|
259 |
+
|
260 |
+
# print(colored(f"\n\n DEBUG CoR Match: {cor_match}\n\n", 'yellow'))
|
261 |
+
user_input = f"<requirements>{formatted_requirements}</requirements> \n\n Here is your last CoR {cor_match} update your next CoR from here."
|
262 |
+
else:
|
263 |
+
user_input = formatted_requirements
|
264 |
+
|
265 |
+
state = self.invoke(state=state, user_input=user_input, final_answer=final_answer)
|
266 |
+
|
267 |
+
meta_prompt_cor = state['meta_prompt'][-1]["content"]
|
268 |
+
|
269 |
+
print(colored(f"\n\n Meta-Prompt Chain of Reasoning: {meta_prompt_cor}\n\n", 'green'))
|
270 |
+
|
271 |
+
return state
|
272 |
+
|
273 |
+
|
274 |
+
class NoToolExpert(BaseAgent[State]):
|
275 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
276 |
+
model_endpoint: str = None, stop: str = None):
|
277 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
278 |
+
self.llm = self.get_llm(json_model=False)
|
279 |
+
|
280 |
+
def get_prompt(self, state) -> str:
|
281 |
+
# print(f"\nn{state}\n")
|
282 |
+
system_prompt = state["meta_prompt"][-1].content
|
283 |
+
return system_prompt
|
284 |
+
|
285 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
286 |
+
|
287 |
+
# meta_prompts = state.get("meta_prompt", [])
|
288 |
+
associated_meta_prompt = state["meta_prompt"][-1].content
|
289 |
+
parse_expert = self.get_llm(json_model=True)
|
290 |
+
|
291 |
+
parse_expert_prompt = """
|
292 |
+
You must parse the expert from the text. The expert will be one of the following.
|
293 |
+
1. Expert Planner
|
294 |
+
2. Expert Writer
|
295 |
+
Return your response as the following JSON
|
296 |
+
{{"expert": "Expert Planner" or "Expert Writer"}}
|
297 |
+
"""
|
298 |
+
|
299 |
+
input = [
|
300 |
+
{"role": "user", "content": associated_meta_prompt},
|
301 |
+
{"role": "assistant", "content": f"system_prompt:{parse_expert_prompt}"}
|
302 |
+
|
303 |
+
]
|
304 |
+
|
305 |
+
|
306 |
+
retries = 0
|
307 |
+
associated_expert = None
|
308 |
+
|
309 |
+
while retries < 4 and associated_expert is None:
|
310 |
+
retries += 1
|
311 |
+
if self.server == 'vllm':
|
312 |
+
guided_json = guided_json_parse_expert
|
313 |
+
parse_expert_response = parse_expert.invoke(input, guided_json)
|
314 |
+
else:
|
315 |
+
parse_expert_response = parse_expert.invoke(input)
|
316 |
+
|
317 |
+
associated_expert_json = json.loads(parse_expert_response)
|
318 |
+
associated_expert = associated_expert_json.get("expert")
|
319 |
+
|
320 |
+
# associated_expert = parse_expert_text(associated_meta_prompt)
|
321 |
+
print(colored(f"\n\n Expert: {associated_expert}\n\n", 'green'))
|
322 |
+
|
323 |
+
if associated_expert == "Expert Planner":
|
324 |
+
expert_update_key = "expert_plan"
|
325 |
+
if associated_expert == "Expert Writer":
|
326 |
+
expert_update_key = "expert_writing"
|
327 |
+
|
328 |
+
|
329 |
+
updates_conversation_history = {
|
330 |
+
"conversation_history": [
|
331 |
+
{"role": "user", "content": user_input},
|
332 |
+
{"role": "assistant", "content": f"{str(response)}"}
|
333 |
+
|
334 |
+
],
|
335 |
+
expert_update_key: {"role": "assistant", "content": f"{str(response)}"}
|
336 |
+
|
337 |
+
}
|
338 |
+
|
339 |
+
|
340 |
+
return updates_conversation_history
|
341 |
+
|
342 |
+
def get_conv_history(self, state: State) -> str:
|
343 |
+
pass
|
344 |
+
|
345 |
+
def get_user_input(self) -> str:
|
346 |
+
pass
|
347 |
+
|
348 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
349 |
+
pass
|
350 |
+
|
351 |
+
def use_tool(self) -> Any:
|
352 |
+
pass
|
353 |
+
|
354 |
+
|
355 |
+
# @log_function(logger)
|
356 |
+
def run(self, state: State) -> State:
|
357 |
+
# chat_counter(state)
|
358 |
+
all_expert_research = []
|
359 |
+
meta_prompt = state["meta_prompt"][1].content
|
360 |
+
|
361 |
+
if state.get("expert_research"):
|
362 |
+
expert_research = state["expert_research"]
|
363 |
+
all_expert_research.extend(expert_research)
|
364 |
+
research_prompt = f"\n Your response must be delivered considering following research.\n ## Research\n {all_expert_research} "
|
365 |
+
user_input = f"{meta_prompt}\n{research_prompt}"
|
366 |
+
|
367 |
+
else:
|
368 |
+
user_input = meta_prompt
|
369 |
+
|
370 |
+
state = self.invoke(state=state, user_input=user_input)
|
371 |
+
return state
|
372 |
+
|
373 |
+
|
374 |
+
class ToolExpert(BaseAgent[State]):
|
375 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
376 |
+
model_endpoint: str = None, stop: str = None, location: str = None):
|
377 |
+
super().__init__(model, server, temperature, model_endpoint, stop, location)
|
378 |
+
|
379 |
+
print(f"\n\n DEBUG LOCATION: {self.location}")
|
380 |
+
|
381 |
+
self.llm = self.get_llm(json_model=False)
|
382 |
+
|
383 |
+
def get_prompt(self, state) -> str:
|
384 |
+
system_prompt = state["meta_prompt"][-1].content
|
385 |
+
return system_prompt
|
386 |
+
|
387 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
388 |
+
updates_conversation_history = {
|
389 |
+
"conversation_history": [
|
390 |
+
{"role": "user", "content": user_input},
|
391 |
+
{"role": "assistant", "content": f"{str(response)}"}
|
392 |
+
],
|
393 |
+
"expert_research": {"role": "assistant", "content": f"{str(response)}"}
|
394 |
+
}
|
395 |
+
return updates_conversation_history
|
396 |
+
|
397 |
+
def get_conv_history(self, state: State) -> str:
|
398 |
+
pass
|
399 |
+
|
400 |
+
def get_user_input(self) -> str:
|
401 |
+
pass
|
402 |
+
|
403 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
404 |
+
pass
|
405 |
+
|
406 |
+
def use_tool(self, mode: str, engine: str, tool_input: str, query: str = None) -> Any:
|
407 |
+
if mode == "serper":
|
408 |
+
if engine == "search":
|
409 |
+
results = serper_search(tool_input, self.location)
|
410 |
+
return {"results": results, "is_shopping": False}
|
411 |
+
elif engine == "shopping":
|
412 |
+
results = serper_shopping_search(tool_input, self.location)
|
413 |
+
return {"results": results, "is_shopping": True}
|
414 |
+
elif mode == "rag":
|
415 |
+
results = run_rag(urls=tool_input, query=query)
|
416 |
+
return {"results": results, "is_shopping": False}
|
417 |
+
|
418 |
+
def generate_search_queries(self, meta_prompt: str, num_queries: int = 5) -> List[str]:
|
419 |
+
refine_query_template = """
|
420 |
+
# Objective
|
421 |
+
Your mission is to systematically address your manager's instructions by determining
|
422 |
+
the most appropriate search queries to use **AND** to determine the best engine to use for each query.
|
423 |
+
Your engine choice is either search, or shopping. You must return either the search or shopping engine for each query.
|
424 |
+
You will generate {num_queries} different search queries.
|
425 |
+
|
426 |
+
# Manager's Instructions
|
427 |
+
{manager_instructions}
|
428 |
+
|
429 |
+
# Flexible Search Algorithm for Simple and Complex Questions
|
430 |
+
|
431 |
+
1. Initial search:
|
432 |
+
- For a simple question: "[Question keywords]"
|
433 |
+
- For a complex topic: "[Main topic] overview"
|
434 |
+
|
435 |
+
2. For each subsequent search:
|
436 |
+
- Choose one of these strategies:
|
437 |
+
|
438 |
+
a. Specify:
|
439 |
+
Add a more specific term or aspect related to the topic.
|
440 |
+
|
441 |
+
b. Broaden:
|
442 |
+
Remove a specific term or add "general" or "overview" to the query.
|
443 |
+
|
444 |
+
c. Pivot:
|
445 |
+
Choose a different but related term from the topic.
|
446 |
+
|
447 |
+
d. Compare:
|
448 |
+
Add "vs" or "compared to" along with a related term.
|
449 |
+
|
450 |
+
e. Question:
|
451 |
+
Rephrase the query as a question by adding "what", "how", "why", etc.
|
452 |
+
|
453 |
+
# Response Format
|
454 |
+
|
455 |
+
**Return the following JSON:**
|
456 |
+
{{
|
457 |
+
"search_queries": [
|
458 |
+
{{"engine": "search", "query": "Query 1"}},
|
459 |
+
{{"engine": "shopping", "query": "Query 2"}},
|
460 |
+
...
|
461 |
+
{{"engine": "search", "query": "Query {num_queries}"}}
|
462 |
+
]
|
463 |
+
}}
|
464 |
+
|
465 |
+
Remember:
|
466 |
+
- Generate {num_queries} unique and diverse search queries.
|
467 |
+
- Each query should explore a different aspect or approach to the topic.
|
468 |
+
- Ensure the queries cover various aspects of the manager's instructions.
|
469 |
+
- The "engine" field should be either "search" or "shopping" for each query.
|
470 |
+
"""
|
471 |
+
|
472 |
+
refine_query = self.get_llm(json_model=True)
|
473 |
+
refine_prompt = refine_query_template.format(manager_instructions=meta_prompt, num_queries=num_queries)
|
474 |
+
input = [
|
475 |
+
{"role": "user", "content": "Generate search queries"},
|
476 |
+
{"role": "assistant", "content": f"system_prompt:{refine_prompt}"}
|
477 |
+
]
|
478 |
+
|
479 |
+
guided_json = guided_json_search_query_two
|
480 |
+
|
481 |
+
if self.server == 'vllm':
|
482 |
+
refined_queries = refine_query.invoke(input, guided_json)
|
483 |
+
else:
|
484 |
+
refined_queries = refine_query.invoke(input)
|
485 |
+
|
486 |
+
refined_queries_json = json.loads(refined_queries)
|
487 |
+
return refined_queries_json.get("search_queries", [])
|
488 |
+
|
489 |
+
def process_serper_result(self, query, serper_response ):
|
490 |
+
best_url_template = """
|
491 |
+
Given the serper results, and the search query, select the best URL
|
492 |
+
|
493 |
+
# Search Query
|
494 |
+
{search_query}
|
495 |
+
|
496 |
+
# Serper Results
|
497 |
+
{serper_results}
|
498 |
+
|
499 |
+
**Return the following JSON:**
|
500 |
+
|
501 |
+
{{"best_url": The URL of the serper results that aligns most with the search query.}}
|
502 |
+
"""
|
503 |
+
|
504 |
+
best_url = self.get_llm(json_model=True)
|
505 |
+
best_url_prompt = best_url_template.format(search_query=query["query"], serper_results=serper_response)
|
506 |
+
input = [
|
507 |
+
{"role": "user", "content": serper_response},
|
508 |
+
{"role": "assistant", "content": f"system_prompt:{best_url_prompt}"}
|
509 |
+
]
|
510 |
+
|
511 |
+
guided_json = guided_json_best_url_two
|
512 |
+
|
513 |
+
if self.server == 'vllm':
|
514 |
+
best_url = best_url.invoke(input, guided_json)
|
515 |
+
else:
|
516 |
+
best_url = best_url.invoke(input)
|
517 |
+
|
518 |
+
best_url_json = json.loads(best_url)
|
519 |
+
|
520 |
+
return {"query": query, "url": best_url_json.get("best_url")}
|
521 |
+
# return best_url_json.get("best_url")
|
522 |
+
|
523 |
+
def run(self, state: State) -> State:
|
524 |
+
meta_prompt = state["meta_prompt"][-1].content
|
525 |
+
print(colored(f"\n\n Meta-Prompt: {meta_prompt}\n\n", 'green'))
|
526 |
+
|
527 |
+
# Generate multiple search queries
|
528 |
+
search_queries = self.generate_search_queries(meta_prompt, num_queries=5)
|
529 |
+
print(colored(f"\n\n Generated Search Queries: {search_queries}\n\n", 'green'))
|
530 |
+
|
531 |
+
try:
|
532 |
+
# Use multiprocessing to call Serper tool for each query in parallel
|
533 |
+
with Pool(processes=min(cpu_count(), len(search_queries))) as pool:
|
534 |
+
serper_results = pool.starmap(
|
535 |
+
self.use_tool,
|
536 |
+
[("serper", query["engine"], query["query"], None) for query in search_queries]
|
537 |
+
)
|
538 |
+
|
539 |
+
# Collect shopping results separately
|
540 |
+
shopping_results = [result["results"] for result in serper_results if result["is_shopping"]]
|
541 |
+
|
542 |
+
if shopping_results:
|
543 |
+
state["expert_research_shopping"] = shopping_results
|
544 |
+
|
545 |
+
# Process Serper results to get best URLs
|
546 |
+
with Pool(processes=min(cpu_count(), len(serper_results))) as pool:
|
547 |
+
best_urls = pool.starmap(
|
548 |
+
self.process_serper_result,
|
549 |
+
[(query, result["results"]) for query, result in zip(search_queries, serper_results)]
|
550 |
+
# zip(search_queries, serper_results)
|
551 |
+
)
|
552 |
+
except Exception as e:
|
553 |
+
print(colored(f"Error in multithreaded processing: {str(e)}. Falling back to non-multithreaded approach.", "yellow"))
|
554 |
+
# Fallback to non-multithreaded approach
|
555 |
+
serper_results = [self.use_tool("serper", query["engine"], query["query"], None) for query in search_queries]
|
556 |
+
shopping_results = [result["results"] for result in serper_results if result["is_shopping"]]
|
557 |
+
if shopping_results:
|
558 |
+
state["expert_research_shopping"] = shopping_results
|
559 |
+
best_urls = [self.process_serper_result(query, result) for query, result in zip(search_queries, serper_results)]
|
560 |
+
|
561 |
+
# Remove duplicates from the list of URLs
|
562 |
+
unique_urls = list(dict.fromkeys(result["url"] for result in best_urls if result["url"] and result["query"]["engine"] == "search"))
|
563 |
+
# unique_urls = list(dict.fromkeys(url for url in best_urls if url))
|
564 |
+
|
565 |
+
print(colored("\n\n Sourced data from {} sources:".format(len(unique_urls)), 'green'))
|
566 |
+
for i, url in enumerate(unique_urls, 1):
|
567 |
+
print(colored(" {}. {}".format(i, url), 'green'))
|
568 |
+
print()
|
569 |
+
|
570 |
+
try:
|
571 |
+
scraper_response = self.use_tool("rag", engine=None, tool_input=unique_urls, query=meta_prompt)
|
572 |
+
except Exception as e:
|
573 |
+
scraper_response = {"results": f"Error {e}: Failed to scrape results", "is_shopping": False}
|
574 |
+
|
575 |
+
updates = self.process_response(scraper_response, user_input="Research")
|
576 |
+
|
577 |
+
for key, value in updates.items():
|
578 |
+
state = self.update_state(key, value, state)
|
579 |
+
|
580 |
+
return state
|
581 |
+
|
582 |
+
class Router(BaseAgent[State]):
|
583 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
584 |
+
model_endpoint: str = None, stop: str = None):
|
585 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
586 |
+
self.llm = self.get_llm(json_model=True)
|
587 |
+
|
588 |
+
|
589 |
+
def get_prompt(self, state) -> str:
|
590 |
+
system_prompt = state["meta_prompt"][-1].content
|
591 |
+
return system_prompt
|
592 |
+
|
593 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
594 |
+
|
595 |
+
updates_conversation_history = {
|
596 |
+
"router_decision": [
|
597 |
+
{"role": "user", "content": user_input},
|
598 |
+
{"role": "assistant", "content": f"{str(response)}"}
|
599 |
+
|
600 |
+
]
|
601 |
+
}
|
602 |
+
|
603 |
+
return updates_conversation_history
|
604 |
+
|
605 |
+
def get_conv_history(self, state: State) -> str:
|
606 |
+
pass
|
607 |
+
|
608 |
+
def get_user_input(self) -> str:
|
609 |
+
pass
|
610 |
+
|
611 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
612 |
+
pass
|
613 |
+
|
614 |
+
def use_tool(self, tool_input: str, mode: str) -> Any:
|
615 |
+
pass
|
616 |
+
|
617 |
+
# @log_function(logger)
|
618 |
+
def run(self, state: State) -> State:
|
619 |
+
|
620 |
+
router_template = """
|
621 |
+
Given these instructions from your manager.
|
622 |
+
|
623 |
+
# Response from Manager
|
624 |
+
{manager_response}
|
625 |
+
|
626 |
+
**Return the following JSON:**
|
627 |
+
|
628 |
+
{{""router_decision: Return the next agent to pass control to.}}
|
629 |
+
|
630 |
+
**strictly** adhere to these **guidelines** for routing.
|
631 |
+
If your maneger's response contains "Expert Internet Researcher", return "tool_expert".
|
632 |
+
If your manager's response contains "Expert Planner" or "Expert Writer", return "no_tool_expert".
|
633 |
+
If your manager's response contains '>> FINAL ANSWER:', return "end_chat".
|
634 |
+
|
635 |
+
"""
|
636 |
+
system_prompt = router_template.format(manager_response=state["meta_prompt"][-1].content)
|
637 |
+
input = [
|
638 |
+
{"role": "user", "content": ""},
|
639 |
+
{"role": "assistant", "content": f"system_prompt:{system_prompt}"}
|
640 |
+
|
641 |
+
]
|
642 |
+
router = self.get_llm(json_model=True)
|
643 |
+
|
644 |
+
if self.server == 'vllm':
|
645 |
+
guided_json = guided_json_router_decision
|
646 |
+
router_response = router.invoke(input, guided_json)
|
647 |
+
else:
|
648 |
+
router_response = router.invoke(input)
|
649 |
+
|
650 |
+
router_response = json.loads(router_response)
|
651 |
+
router_response = router_response.get("router_decision")
|
652 |
+
|
653 |
+
state = self.update_state("router_decision", router_response, state)
|
654 |
+
|
655 |
+
return state
|
agents/legacy/jar3d_agent_backup.py
ADDED
@@ -0,0 +1,734 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
from multiprocessing import Pool, cpu_count
|
3 |
+
# import requests
|
4 |
+
# from tenacity import RetryError
|
5 |
+
import re
|
6 |
+
import logging
|
7 |
+
import chainlit as cl
|
8 |
+
from termcolor import colored
|
9 |
+
from typing import Any, Dict, Union, List
|
10 |
+
from typing import TypedDict, Annotated
|
11 |
+
from langgraph.graph.message import add_messages
|
12 |
+
from agents.base_agent import BaseAgent
|
13 |
+
from utils.read_markdown import read_markdown_file
|
14 |
+
from tools.google_serper import serper_search, serper_shopping_search
|
15 |
+
from utils.logging import log_function, setup_logging
|
16 |
+
from tools.offline_graph_rag_tool import run_rag
|
17 |
+
from prompt_engineering.guided_json_lib import (
|
18 |
+
guided_json_search_query,
|
19 |
+
guided_json_best_url_two,
|
20 |
+
guided_json_router_decision,
|
21 |
+
guided_json_parse_expert,
|
22 |
+
guided_json_search_query_two
|
23 |
+
)
|
24 |
+
|
25 |
+
|
26 |
+
setup_logging(level=logging.DEBUG)
|
27 |
+
logger = logging.getLogger(__name__)
|
28 |
+
|
29 |
+
class MessageDict(TypedDict):
|
30 |
+
role: str
|
31 |
+
content: str
|
32 |
+
|
33 |
+
class State(TypedDict):
|
34 |
+
meta_prompt: Annotated[List[dict], add_messages]
|
35 |
+
conversation_history: Annotated[List[dict], add_messages]
|
36 |
+
requirements_gathering: Annotated[List[str], add_messages]
|
37 |
+
expert_plan: str
|
38 |
+
expert_research: Annotated[List[str], add_messages]
|
39 |
+
expert_research_shopping: Annotated[List[str], add_messages]
|
40 |
+
expert_writing: str
|
41 |
+
user_input: Annotated[List[str], add_messages]
|
42 |
+
previous_search_queries: Annotated[List[dict], add_messages]
|
43 |
+
router_decision: str
|
44 |
+
chat_limit: int
|
45 |
+
chat_finished: bool
|
46 |
+
recursion_limit: int
|
47 |
+
final_answer: str
|
48 |
+
previous_type2_work: Annotated[List[str], add_messages]
|
49 |
+
|
50 |
+
state: State = {
|
51 |
+
"meta_prompt": [],
|
52 |
+
"conversation_history": [],
|
53 |
+
"requirements_gathering": [],
|
54 |
+
"expert_plan": [],
|
55 |
+
"expert_research": [],
|
56 |
+
"expert_research_shopping": [],
|
57 |
+
"expert_writing": [],
|
58 |
+
"user_input": [],
|
59 |
+
"previous_search_queries": [],
|
60 |
+
"router_decision": None,
|
61 |
+
"chat_limit": None,
|
62 |
+
"chat_finished": False,
|
63 |
+
"recursion_limit": None,
|
64 |
+
"final_answer": None,
|
65 |
+
"previous_type2_work": []
|
66 |
+
}
|
67 |
+
|
68 |
+
def chat_counter(state: State) -> State:
|
69 |
+
chat_limit = state.get("chat_limit")
|
70 |
+
if chat_limit is None:
|
71 |
+
chat_limit = 0
|
72 |
+
chat_limit += 1
|
73 |
+
state["chat_limit"] = chat_limit
|
74 |
+
return chat_limit
|
75 |
+
|
76 |
+
def routing_function(state: State) -> str:
|
77 |
+
decision = state["router_decision"]
|
78 |
+
print(colored(f"\n\n Routing function called. Decision: {decision}\n\n", 'green'))
|
79 |
+
return decision
|
80 |
+
|
81 |
+
def set_chat_finished(state: State) -> bool:
|
82 |
+
state["chat_finished"] = True
|
83 |
+
final_response = state["meta_prompt"][-1].content
|
84 |
+
print(colored(f"\n\n DEBUG FINAL RESPONSE: {final_response}\n\n", 'green'))
|
85 |
+
|
86 |
+
# Split the response at ">> FINAL ANSWER:"
|
87 |
+
parts = final_response.split(">> FINAL ANSWER:")
|
88 |
+
if len(parts) > 1:
|
89 |
+
answer_part = parts[1].strip()
|
90 |
+
|
91 |
+
# Remove any triple quotes
|
92 |
+
final_response_formatted = answer_part.strip('"""')
|
93 |
+
|
94 |
+
# Remove leading whitespace
|
95 |
+
final_response_formatted = final_response_formatted.lstrip()
|
96 |
+
|
97 |
+
# Remove the CoR dictionary at the end
|
98 |
+
cor_pattern = r'\nCoR\s*=\s*\{[\s\S]*\}\s*$'
|
99 |
+
final_response_formatted = re.sub(cor_pattern, '', final_response_formatted)
|
100 |
+
|
101 |
+
# Remove any trailing whitespace
|
102 |
+
final_response_formatted = final_response_formatted.rstrip()
|
103 |
+
|
104 |
+
# print(colored(f"\n\n DEBUG: {final_response_formatted}\n\n", 'green'))
|
105 |
+
print(colored(f"\n\n Jar3d👩💻: {final_response_formatted}", 'cyan'))
|
106 |
+
state["final_answer"] = f'''{final_response_formatted}'''
|
107 |
+
else:
|
108 |
+
print(colored("Error: Could not find '>> FINAL ANSWER:' in the response", 'red'))
|
109 |
+
state["final_answer"] = "Error: No final answer found"
|
110 |
+
|
111 |
+
return state
|
112 |
+
|
113 |
+
class Jar3d(BaseAgent[State]):
|
114 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
115 |
+
model_endpoint: str = None, stop: str = None):
|
116 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
117 |
+
self.llm = self.get_llm(json_model=False)
|
118 |
+
|
119 |
+
def get_prompt(self, state: State = None) -> str:
|
120 |
+
system_prompt = read_markdown_file('prompt_engineering/jar3d_requirements_prompt.md')
|
121 |
+
return system_prompt
|
122 |
+
|
123 |
+
def process_response(self, response: Any, user_input: str, state: State = None) -> Dict[str, List[Dict[str, str]]]:
|
124 |
+
updates_conversation_history = {
|
125 |
+
"requirements_gathering": [
|
126 |
+
{"role": "user", "content": f"{user_input}"},
|
127 |
+
{"role": "assistant", "content": str(response)}
|
128 |
+
]
|
129 |
+
}
|
130 |
+
return updates_conversation_history
|
131 |
+
|
132 |
+
def get_conv_history(self, state: State) -> str:
|
133 |
+
conversation_history = state.get('requirements_gathering', [])
|
134 |
+
return "\n".join([f"{msg['role']}: {msg['content']}" for msg in conversation_history])
|
135 |
+
|
136 |
+
def get_user_input(self) -> str:
|
137 |
+
pass
|
138 |
+
|
139 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
140 |
+
pass
|
141 |
+
|
142 |
+
def use_tool(self) -> Any:
|
143 |
+
pass
|
144 |
+
|
145 |
+
def run_chainlit(self, state: State, message: cl.Message) -> State:
|
146 |
+
user_message = message.content
|
147 |
+
system_prompt = self.get_prompt()
|
148 |
+
user_input = f"{system_prompt}\n cogor {user_message}"
|
149 |
+
|
150 |
+
state = self.invoke(state=state, user_input=user_input)
|
151 |
+
response = state['requirements_gathering'][-1]["content"]
|
152 |
+
response = re.sub(r'^```python[\s\S]*?```\s*', '', response, flags=re.MULTILINE)
|
153 |
+
response = response.lstrip()
|
154 |
+
|
155 |
+
return state, response
|
156 |
+
|
157 |
+
|
158 |
+
class MetaExpert(BaseAgent[State]):
|
159 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
160 |
+
model_endpoint: str = None, stop: str = None):
|
161 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
162 |
+
self.llm = self.get_llm(json_model=False)
|
163 |
+
|
164 |
+
def get_prompt(self, state:None) -> str:
|
165 |
+
system_prompt = read_markdown_file('prompt_engineering/jar3d_meta_prompt.md')
|
166 |
+
return system_prompt
|
167 |
+
|
168 |
+
def process_response(self, response: Any, user_input: str, state: State = None) -> Dict[str, List[MessageDict]]:
|
169 |
+
user_input = None
|
170 |
+
updates_conversation_history = {
|
171 |
+
"meta_prompt": [
|
172 |
+
{"role": "user", "content": f"{user_input}"},
|
173 |
+
{"role": "assistant", "content": str(response)}
|
174 |
+
|
175 |
+
],
|
176 |
+
"conversation_history": [
|
177 |
+
{"role": "user", "content": f"{user_input}"},
|
178 |
+
{"role": "assistant", "content": str(response)}
|
179 |
+
|
180 |
+
],
|
181 |
+
}
|
182 |
+
return updates_conversation_history
|
183 |
+
|
184 |
+
# @log_function(logger)
|
185 |
+
def get_conv_history(self, state: State) -> str:
|
186 |
+
|
187 |
+
all_expert_research = []
|
188 |
+
|
189 |
+
if state["expert_research"]:
|
190 |
+
expert_research = state["expert_research"]
|
191 |
+
all_expert_research.extend(expert_research)
|
192 |
+
else:
|
193 |
+
all_expert_research = []
|
194 |
+
|
195 |
+
expert_message_history = f"""
|
196 |
+
<expert_plan>
|
197 |
+
## Your Expert Plan:\n{state.get("expert_plan", [])}\n
|
198 |
+
</expert_plan>
|
199 |
+
|
200 |
+
<expert_writing>
|
201 |
+
## Your Expert Writing:\n{state.get("expert_writing", [])}\n
|
202 |
+
</expert_writing>
|
203 |
+
|
204 |
+
<internet_research_shopping_list>
|
205 |
+
## Your Expert Shopping List:\n{state.get("expert_research_shopping", [])}\n
|
206 |
+
</internet_research_shopping_list>
|
207 |
+
|
208 |
+
<internet_research>
|
209 |
+
## Your Expert Research:{all_expert_research}\n
|
210 |
+
</internet_research>
|
211 |
+
"""
|
212 |
+
|
213 |
+
return expert_message_history
|
214 |
+
|
215 |
+
def get_user_input(self) -> str:
|
216 |
+
user_input = input("Enter your query: ")
|
217 |
+
return user_input
|
218 |
+
|
219 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
220 |
+
pass
|
221 |
+
|
222 |
+
def use_tool(self) -> Any:
|
223 |
+
pass
|
224 |
+
|
225 |
+
@log_function(logger)
|
226 |
+
def run(self, state: State) -> State:
|
227 |
+
|
228 |
+
counter = chat_counter(state) # Counts every time we invoke the Meta Agent
|
229 |
+
recursion_limit = state.get("recursion_limit")
|
230 |
+
recursions = 3*counter - 2
|
231 |
+
print(colored(f"\n\n * We have envoked the Meta-Agent {counter} times.\n * we have run {recursions} max total iterations: {recursion_limit}\n\n", "green"))
|
232 |
+
|
233 |
+
upper_limit_recursions = recursion_limit
|
234 |
+
lower_limit_recursions = recursion_limit - 2
|
235 |
+
|
236 |
+
if recursions >= lower_limit_recursions and recursions <= upper_limit_recursions:
|
237 |
+
final_answer = "**You are being explicitly told to produce your [Type 2] work now!**"
|
238 |
+
elif recursions > upper_limit_recursions:
|
239 |
+
extra_recursions = recursions - upper_limit_recursions
|
240 |
+
base_message = "**You are being explicitly told to produce your [Type 2] work now!**"
|
241 |
+
final_answer = (base_message + "\n") * (extra_recursions + 1)
|
242 |
+
else:
|
243 |
+
final_answer = None
|
244 |
+
|
245 |
+
try:
|
246 |
+
requirements = state['requirements_gathering'][-1]["content"]
|
247 |
+
except:
|
248 |
+
requirements = state['requirements_gathering'][-1].content
|
249 |
+
|
250 |
+
formatted_requirements = '\n\n'.join(re.findall(r'```python\s*([\s\S]*?)\s*```', requirements, re.MULTILINE))
|
251 |
+
|
252 |
+
print(colored(f"\n\n User Requirements: {formatted_requirements}\n\n", 'green'))
|
253 |
+
|
254 |
+
if state.get("meta_prompt"):
|
255 |
+
try:
|
256 |
+
meta_prompt = state['meta_prompt'][-1]["content"]
|
257 |
+
except:
|
258 |
+
meta_prompt = state['meta_prompt'][-1].content
|
259 |
+
|
260 |
+
# print(colored(f"\n\n DEBUG Meta-Prompt: {meta_prompt}\n\n", 'yellow'))
|
261 |
+
|
262 |
+
cor_match = '\n\n'.join(re.findall(r'```python\s*([\s\S]*?)\s*```', meta_prompt, re.MULTILINE))
|
263 |
+
|
264 |
+
# print(colored(f"\n\n DEBUG CoR Match: {cor_match}\n\n", 'yellow'))
|
265 |
+
user_input = f"<requirements>{formatted_requirements}</requirements> \n\n Here is your last CoR {cor_match} update your next CoR from here."
|
266 |
+
else:
|
267 |
+
user_input = formatted_requirements
|
268 |
+
|
269 |
+
state = self.invoke(state=state, user_input=user_input, final_answer=final_answer)
|
270 |
+
|
271 |
+
meta_prompt_cor = state['meta_prompt'][-1]["content"]
|
272 |
+
|
273 |
+
print(colored(f"\n\n Meta-Prompt Chain of Reasoning: {meta_prompt_cor}\n\n", 'green'))
|
274 |
+
|
275 |
+
return state
|
276 |
+
|
277 |
+
|
278 |
+
class NoToolExpert(BaseAgent[State]):
|
279 |
+
print(colored(f"\n\n DEBUG: We are running the NoToolExpert tool\n\n", 'red'))
|
280 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
281 |
+
model_endpoint: str = None, stop: str = None):
|
282 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
283 |
+
self.llm = self.get_llm(json_model=False)
|
284 |
+
|
285 |
+
def get_prompt(self, state) -> str:
|
286 |
+
# print(f"\nn{state}\n")
|
287 |
+
system_prompt = state["meta_prompt"][-1].content
|
288 |
+
return system_prompt
|
289 |
+
|
290 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
291 |
+
|
292 |
+
# meta_prompts = state.get("meta_prompt", [])
|
293 |
+
associated_meta_prompt = state["meta_prompt"][-1].content
|
294 |
+
parse_expert = self.get_llm(json_model=True)
|
295 |
+
|
296 |
+
parse_expert_prompt = """
|
297 |
+
You must parse the expert from the text. The expert will be one of the following.
|
298 |
+
1. Expert Planner
|
299 |
+
2. Expert Writer
|
300 |
+
Return your response as the following JSON
|
301 |
+
{{"expert": "Expert Planner" or "Expert Writer"}}
|
302 |
+
"""
|
303 |
+
|
304 |
+
input = [
|
305 |
+
{"role": "user", "content": associated_meta_prompt},
|
306 |
+
{"role": "assistant", "content": f"system_prompt:{parse_expert_prompt}"}
|
307 |
+
|
308 |
+
]
|
309 |
+
|
310 |
+
|
311 |
+
retries = 0
|
312 |
+
associated_expert = None
|
313 |
+
|
314 |
+
while retries < 4 and associated_expert is None:
|
315 |
+
retries += 1
|
316 |
+
if self.server == 'vllm':
|
317 |
+
guided_json = guided_json_parse_expert
|
318 |
+
parse_expert_response = parse_expert.invoke(input, guided_json)
|
319 |
+
else:
|
320 |
+
parse_expert_response = parse_expert.invoke(input)
|
321 |
+
|
322 |
+
associated_expert_json = json.loads(parse_expert_response)
|
323 |
+
associated_expert = associated_expert_json.get("expert")
|
324 |
+
|
325 |
+
# associated_expert = parse_expert_text(associated_meta_prompt)
|
326 |
+
print(colored(f"\n\n Expert: {associated_expert}\n\n", 'green'))
|
327 |
+
|
328 |
+
if associated_expert == "Expert Planner":
|
329 |
+
expert_update_key = "expert_plan"
|
330 |
+
if associated_expert == "Expert Writer":
|
331 |
+
expert_update_key = "expert_writing"
|
332 |
+
|
333 |
+
|
334 |
+
updates_conversation_history = {
|
335 |
+
"conversation_history": [
|
336 |
+
{"role": "user", "content": user_input},
|
337 |
+
{"role": "assistant", "content": f"{str(response)}"}
|
338 |
+
|
339 |
+
],
|
340 |
+
expert_update_key: {"role": "assistant", "content": f"{str(response)}"}
|
341 |
+
|
342 |
+
}
|
343 |
+
|
344 |
+
|
345 |
+
return updates_conversation_history
|
346 |
+
|
347 |
+
def get_conv_history(self, state: State) -> str:
|
348 |
+
pass
|
349 |
+
|
350 |
+
def get_user_input(self) -> str:
|
351 |
+
pass
|
352 |
+
|
353 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
354 |
+
pass
|
355 |
+
|
356 |
+
def use_tool(self) -> Any:
|
357 |
+
pass
|
358 |
+
|
359 |
+
|
360 |
+
# @log_function(logger)
|
361 |
+
def run(self, state: State) -> State:
|
362 |
+
# chat_counter(state)
|
363 |
+
all_expert_research = []
|
364 |
+
meta_prompt = state["meta_prompt"][1].content
|
365 |
+
|
366 |
+
if state.get("expert_research"):
|
367 |
+
expert_research = state["expert_research"]
|
368 |
+
all_expert_research.extend(expert_research)
|
369 |
+
research_prompt = f"\n Your response must be delivered considering following research.\n ## Research\n {all_expert_research} "
|
370 |
+
user_input = f"{meta_prompt}\n{research_prompt}"
|
371 |
+
|
372 |
+
else:
|
373 |
+
user_input = meta_prompt
|
374 |
+
|
375 |
+
print(colored(f"\n\n DEBUG: We are running the NoToolExpert tool\n\n", 'red'))
|
376 |
+
state = self.invoke(state=state, user_input=user_input)
|
377 |
+
return state
|
378 |
+
|
379 |
+
|
380 |
+
class ToolExpert(BaseAgent[State]):
|
381 |
+
print(colored(f"\n\n DEBUG: We are running the ToolExpert tool\n\n", 'red'))
|
382 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
383 |
+
model_endpoint: str = None, stop: str = None, location: str = None, hybrid: bool = False):
|
384 |
+
super().__init__(model, server, temperature, model_endpoint, stop, location, hybrid)
|
385 |
+
|
386 |
+
print(f"\n\n DEBUG LOCATION: {self.location}")
|
387 |
+
|
388 |
+
self.llm = self.get_llm(json_model=False)
|
389 |
+
|
390 |
+
def get_prompt(self, state) -> str:
|
391 |
+
system_prompt = state["meta_prompt"][-1].content
|
392 |
+
return system_prompt
|
393 |
+
|
394 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
395 |
+
updates_conversation_history = {
|
396 |
+
"conversation_history": [
|
397 |
+
{"role": "user", "content": user_input},
|
398 |
+
{"role": "assistant", "content": f"{str(response)}"}
|
399 |
+
],
|
400 |
+
"expert_research": {"role": "assistant", "content": f"{str(response)}"}
|
401 |
+
}
|
402 |
+
return updates_conversation_history
|
403 |
+
|
404 |
+
def get_conv_history(self, state: State) -> str:
|
405 |
+
pass
|
406 |
+
|
407 |
+
def get_user_input(self) -> str:
|
408 |
+
pass
|
409 |
+
|
410 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
411 |
+
pass
|
412 |
+
|
413 |
+
# Change added query list to RAG
|
414 |
+
def use_tool(self, mode: str, engine: str, tool_input: str, meta_prompt: str = None, query: list[str] = None, hybrid: bool = False) -> Any:
|
415 |
+
if mode == "serper":
|
416 |
+
if engine == "search":
|
417 |
+
results = serper_search(tool_input, self.location)
|
418 |
+
return {"results": results, "is_shopping": False}
|
419 |
+
elif engine == "shopping":
|
420 |
+
results = serper_shopping_search(tool_input, self.location)
|
421 |
+
return {"results": results, "is_shopping": True}
|
422 |
+
elif mode == "rag":
|
423 |
+
print(colored(f"\n\n DEBUG: We are running the Graph RAG TOOL!!\n\n", 'red'))
|
424 |
+
|
425 |
+
# if hybrid:
|
426 |
+
# nodes, relationships = self.get_graph_elements(meta_prompt)
|
427 |
+
# print(colored(f"\n\n DEBUG: Nodes: {nodes}\n\n", 'green'))
|
428 |
+
# print(colored(f"\n\n DEBUG: Relationships: {relationships}\n\n", 'green'))
|
429 |
+
# else:
|
430 |
+
nodes = None
|
431 |
+
relationships = None
|
432 |
+
print(colored(f"\n\n DEBUG Retreival Mode: {hybrid}\n\n", 'green'))
|
433 |
+
results = run_rag(urls=tool_input, allowed_nodes=nodes, allowed_relationships=relationships, query=query, hybrid=self.hybrid)
|
434 |
+
|
435 |
+
return {"results": results, "is_shopping": False}
|
436 |
+
|
437 |
+
def generate_search_queries(self, meta_prompt: str, num_queries: int = 5) -> List[str]:
|
438 |
+
refine_query_template = """
|
439 |
+
# Objective
|
440 |
+
Your mission is to systematically address your manager's instructions by determining
|
441 |
+
the most appropriate search queries to use **AND** to determine the best engine to use for each query.
|
442 |
+
Your engine choice is either search, or shopping. You must return either the search or shopping engine for each query.
|
443 |
+
You will generate {num_queries} different search queries.
|
444 |
+
|
445 |
+
# Manager's Instructions
|
446 |
+
{manager_instructions}
|
447 |
+
|
448 |
+
# Flexible Search Algorithm for Simple and Complex Questions
|
449 |
+
|
450 |
+
1. Initial search:
|
451 |
+
- For a simple question: "[Question keywords]"
|
452 |
+
- For a complex topic: "[Main topic] overview"
|
453 |
+
|
454 |
+
2. For each subsequent search:
|
455 |
+
- Choose one of these strategies:
|
456 |
+
|
457 |
+
a. Specify:
|
458 |
+
Add a more specific term or aspect related to the topic.
|
459 |
+
|
460 |
+
b. Broaden:
|
461 |
+
Remove a specific term or add "general" or "overview" to the query.
|
462 |
+
|
463 |
+
c. Pivot:
|
464 |
+
Choose a different but related term from the topic.
|
465 |
+
|
466 |
+
d. Compare:
|
467 |
+
Add "vs" or "compared to" along with a related term.
|
468 |
+
|
469 |
+
e. Question:
|
470 |
+
Rephrase the query as a question by adding "what", "how", "why", etc.
|
471 |
+
|
472 |
+
# Response Format
|
473 |
+
|
474 |
+
**Return the following JSON:**
|
475 |
+
{{
|
476 |
+
"search_queries": [
|
477 |
+
{{"engine": "search", "query": "Query 1"}},
|
478 |
+
{{"engine": "shopping", "query": "Query 2"}},
|
479 |
+
...
|
480 |
+
{{"engine": "search", "query": "Query {num_queries}"}}
|
481 |
+
]
|
482 |
+
}}
|
483 |
+
|
484 |
+
Remember:
|
485 |
+
- Generate {num_queries} unique and diverse search queries.
|
486 |
+
- Each query should explore a different aspect or approach to the topic.
|
487 |
+
- Ensure the queries cover various aspects of the manager's instructions.
|
488 |
+
- The "engine" field should be either "search" or "shopping" for each query.
|
489 |
+
"""
|
490 |
+
|
491 |
+
refine_query = self.get_llm(json_model=True)
|
492 |
+
refine_prompt = refine_query_template.format(manager_instructions=meta_prompt, num_queries=num_queries)
|
493 |
+
input = [
|
494 |
+
{"role": "user", "content": "Generate search queries"},
|
495 |
+
{"role": "assistant", "content": f"system_prompt:{refine_prompt}"}
|
496 |
+
]
|
497 |
+
|
498 |
+
guided_json = guided_json_search_query_two
|
499 |
+
|
500 |
+
if self.server == 'vllm':
|
501 |
+
refined_queries = refine_query.invoke(input, guided_json)
|
502 |
+
else:
|
503 |
+
print(colored(f"\n\n DEBUG: We are running the refine_query tool without vllm\n\n", 'red'))
|
504 |
+
refined_queries = refine_query.invoke(input)
|
505 |
+
|
506 |
+
refined_queries_json = json.loads(refined_queries)
|
507 |
+
return refined_queries_json.get("search_queries", [])
|
508 |
+
|
509 |
+
def process_serper_result(self, query, serper_response ): # Add to other Jar3d Script
|
510 |
+
best_url_template = """
|
511 |
+
Given the serper results, and the search query, select the best URL
|
512 |
+
|
513 |
+
# Search Query
|
514 |
+
{search_query}
|
515 |
+
|
516 |
+
# Serper Results
|
517 |
+
{serper_results}
|
518 |
+
|
519 |
+
**Return the following JSON:**
|
520 |
+
|
521 |
+
{{"best_url": The URL of the serper results that aligns most with the search query.}}
|
522 |
+
"""
|
523 |
+
|
524 |
+
best_url = self.get_llm(json_model=True)
|
525 |
+
best_url_prompt = best_url_template.format(search_query=query["query"], serper_results=serper_response)
|
526 |
+
input = [
|
527 |
+
{"role": "user", "content": serper_response},
|
528 |
+
{"role": "assistant", "content": f"system_prompt:{best_url_prompt}"}
|
529 |
+
]
|
530 |
+
|
531 |
+
guided_json = guided_json_best_url_two
|
532 |
+
|
533 |
+
if self.server == 'vllm':
|
534 |
+
best_url = best_url.invoke(input, guided_json)
|
535 |
+
else:
|
536 |
+
print(colored(f"\n\n DEBUG: We are running the best_url tool without vllm\n\n", 'red'))
|
537 |
+
best_url = best_url.invoke(input)
|
538 |
+
|
539 |
+
best_url_json = json.loads(best_url)
|
540 |
+
|
541 |
+
return {"query": query, "url": best_url_json.get("best_url")}
|
542 |
+
# return best_url_json.get("best_url")
|
543 |
+
|
544 |
+
def get_graph_elements(self, meta_prompt: str):
|
545 |
+
graph_elements_template = """
|
546 |
+
You are an intelligent assistant helping to construct elements for a knowledge graph in Neo4j.
|
547 |
+
Your objectove is to create two lists.
|
548 |
+
|
549 |
+
# Lists
|
550 |
+
list 1: A list of nodes.
|
551 |
+
list 2: A list of relationships.
|
552 |
+
|
553 |
+
# Instructions Constructing Lists
|
554 |
+
1. You must construct lists based on what would be most useful for exploring data
|
555 |
+
to fulfil the [Manager's Instructions].
|
556 |
+
2. Each item in your list must follow Neo4j's formattng standards.
|
557 |
+
3. Limit lists to a maximum of 8 items each.
|
558 |
+
|
559 |
+
# Neo4j Formatting Standards
|
560 |
+
1. Each element in the list must be in capital letters.
|
561 |
+
2. Spaces between words must be replaced with underscores.
|
562 |
+
3. There should be no apostrophes or special characters.
|
563 |
+
|
564 |
+
# [Manager's Instructions]
|
565 |
+
{manager_instructions}
|
566 |
+
|
567 |
+
Return the following JSON:
|
568 |
+
{{ "nodes": [list of nodes], "relationships": [list of relationships]}}
|
569 |
+
|
570 |
+
"""
|
571 |
+
|
572 |
+
get_graph_elements = self.get_llm(json_model=True)
|
573 |
+
graph_elements_prompt = graph_elements_template.format(manager_instructions=meta_prompt)
|
574 |
+
|
575 |
+
input = [
|
576 |
+
{"role": "user", "content": "Construct Neo4j Graph Elements"},
|
577 |
+
{"role": "assistant", "content": f"system_prompt:{graph_elements_prompt}"}
|
578 |
+
]
|
579 |
+
|
580 |
+
guided_json = guided_json_best_url_two
|
581 |
+
|
582 |
+
if self.server == 'vllm':
|
583 |
+
graph_elements = get_graph_elements.invoke(input, guided_json)
|
584 |
+
else:
|
585 |
+
print(colored(f"\n\n DEBUG: We are running the graph_elements tool without vllm\n\n", 'red'))
|
586 |
+
graph_elements = get_graph_elements.invoke(input)
|
587 |
+
|
588 |
+
graph_elements_json = json.loads(graph_elements)
|
589 |
+
|
590 |
+
nodes = graph_elements_json.get("nodes")
|
591 |
+
relationships = graph_elements_json.get("relationships")
|
592 |
+
|
593 |
+
return nodes, relationships
|
594 |
+
|
595 |
+
def run(self, state: State) -> State:
|
596 |
+
meta_prompt = state["meta_prompt"][-1].content
|
597 |
+
print(colored(f"\n\n Meta-Prompt: {meta_prompt}\n\n", 'green'))
|
598 |
+
|
599 |
+
# Generate multiple search queries
|
600 |
+
num_queries = 10
|
601 |
+
search_queries = self.generate_search_queries(meta_prompt, num_queries=num_queries)
|
602 |
+
print(colored(f"\n\n Generated Search Queries: {search_queries}\n\n", 'green'))
|
603 |
+
|
604 |
+
try:
|
605 |
+
# Use multiprocessing to call Serper tool for each query in parallel
|
606 |
+
with Pool(processes=min(cpu_count(), len(search_queries))) as pool:
|
607 |
+
serper_results = pool.starmap(
|
608 |
+
self.use_tool,
|
609 |
+
[("serper", query["engine"], query["query"], None) for query in search_queries]
|
610 |
+
)
|
611 |
+
|
612 |
+
# Collect shopping results separately
|
613 |
+
shopping_results = [result["results"] for result in serper_results if result["is_shopping"]]
|
614 |
+
|
615 |
+
if shopping_results:
|
616 |
+
state["expert_research_shopping"] = shopping_results
|
617 |
+
|
618 |
+
# Process Serper results to get best URLs
|
619 |
+
with Pool(processes=min(cpu_count(), len(serper_results))) as pool:
|
620 |
+
best_urls = pool.starmap(
|
621 |
+
self.process_serper_result,
|
622 |
+
[(query, result["results"]) for query, result in zip(search_queries, serper_results)]
|
623 |
+
# zip(search_queries, serper_results)
|
624 |
+
)
|
625 |
+
except Exception as e:
|
626 |
+
print(colored(f"Error in multithreaded processing: {str(e)}. Falling back to non-multithreaded approach.", "yellow"))
|
627 |
+
# Fallback to non-multithreaded approach
|
628 |
+
serper_results = [self.use_tool("serper", query["engine"], query["query"], None) for query in search_queries]
|
629 |
+
shopping_results = [result["results"] for result in serper_results if result["is_shopping"]]
|
630 |
+
if shopping_results:
|
631 |
+
state["expert_research_shopping"] = shopping_results
|
632 |
+
best_urls = [self.process_serper_result(query, result) for query, result in zip(search_queries, serper_results)]
|
633 |
+
|
634 |
+
# Remove duplicates from the list of URLs # Additional Line
|
635 |
+
deduplicated_urls = {item['url']: item for item in best_urls}.values()
|
636 |
+
deduplicated_urls_list = list(deduplicated_urls)
|
637 |
+
|
638 |
+
print(colored(f"\n\n DEBUG DEBUG Best URLs: {deduplicated_urls_list}\n\n", 'red')) # DEBUG LINE
|
639 |
+
unique_urls = list(dict.fromkeys(result["url"] for result in best_urls if result["url"] and result["query"]["engine"] == "search"))
|
640 |
+
unique_queries = list(dict.fromkeys(result["query"]["query"] for result in deduplicated_urls_list if result["query"] and result["query"]["engine"] == "search")) # unique_urls = list(dict.fromkeys(url for url in best_urls if url))
|
641 |
+
|
642 |
+
print(colored("\n\n Sourced data from {} sources:".format(len(unique_urls)), 'yellow'))
|
643 |
+
print(colored(f"\n\n Search Queries {unique_queries}", 'yellow'))
|
644 |
+
|
645 |
+
for i, url in enumerate(unique_urls, 1):
|
646 |
+
print(colored(" {}. {}".format(i, url), 'green'))
|
647 |
+
print()
|
648 |
+
|
649 |
+
try:
|
650 |
+
scraper_response = self.use_tool(mode="rag", engine=None, tool_input=unique_urls, meta_prompt=meta_prompt, query=unique_queries)
|
651 |
+
except Exception as e:
|
652 |
+
scraper_response = {"results": f"Error {e}: Failed to scrape results", "is_shopping": False}
|
653 |
+
|
654 |
+
updates = self.process_response(scraper_response, user_input="Research")
|
655 |
+
|
656 |
+
for key, value in updates.items():
|
657 |
+
state = self.update_state(key, value, state)
|
658 |
+
|
659 |
+
return state
|
660 |
+
|
661 |
+
class Router(BaseAgent[State]):
|
662 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
663 |
+
model_endpoint: str = None, stop: str = None):
|
664 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
665 |
+
self.llm = self.get_llm(json_model=True)
|
666 |
+
|
667 |
+
|
668 |
+
def get_prompt(self, state) -> str:
|
669 |
+
system_prompt = state["meta_prompt"][-1].content
|
670 |
+
return system_prompt
|
671 |
+
|
672 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
673 |
+
|
674 |
+
updates_conversation_history = {
|
675 |
+
"router_decision": [
|
676 |
+
{"role": "user", "content": user_input},
|
677 |
+
{"role": "assistant", "content": f"{str(response)}"}
|
678 |
+
|
679 |
+
]
|
680 |
+
}
|
681 |
+
|
682 |
+
return updates_conversation_history
|
683 |
+
|
684 |
+
def get_conv_history(self, state: State) -> str:
|
685 |
+
pass
|
686 |
+
|
687 |
+
def get_user_input(self) -> str:
|
688 |
+
pass
|
689 |
+
|
690 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
691 |
+
pass
|
692 |
+
|
693 |
+
def use_tool(self, tool_input: str, mode: str) -> Any:
|
694 |
+
pass
|
695 |
+
|
696 |
+
# @log_function(logger)
|
697 |
+
def run(self, state: State) -> State:
|
698 |
+
|
699 |
+
router_template = """
|
700 |
+
Given these instructions from your manager.
|
701 |
+
|
702 |
+
# Response from Manager
|
703 |
+
{manager_response}
|
704 |
+
|
705 |
+
**Return the following JSON:**
|
706 |
+
|
707 |
+
{{""router_decision: Return the next agent to pass control to.}}
|
708 |
+
|
709 |
+
**strictly** adhere to these **guidelines** for routing.
|
710 |
+
If your maneger's response contains "Expert Internet Researcher", return "tool_expert".
|
711 |
+
If your manager's response contains "Expert Planner" or "Expert Writer", return "no_tool_expert".
|
712 |
+
If your manager's response contains '>> FINAL ANSWER:', return "end_chat".
|
713 |
+
|
714 |
+
"""
|
715 |
+
system_prompt = router_template.format(manager_response=state["meta_prompt"][-1].content)
|
716 |
+
input = [
|
717 |
+
{"role": "user", "content": ""},
|
718 |
+
{"role": "assistant", "content": f"system_prompt:{system_prompt}"}
|
719 |
+
|
720 |
+
]
|
721 |
+
router = self.get_llm(json_model=True)
|
722 |
+
|
723 |
+
if self.server == 'vllm':
|
724 |
+
guided_json = guided_json_router_decision
|
725 |
+
router_response = router.invoke(input, guided_json)
|
726 |
+
else:
|
727 |
+
router_response = router.invoke(input)
|
728 |
+
|
729 |
+
router_response = json.loads(router_response)
|
730 |
+
router_response = router_response.get("router_decision")
|
731 |
+
|
732 |
+
state = self.update_state("router_decision", router_response, state)
|
733 |
+
|
734 |
+
return state
|
agents/meta_agent.py
ADDED
@@ -0,0 +1,482 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import logging
|
3 |
+
from termcolor import colored
|
4 |
+
from datetime import datetime
|
5 |
+
from typing import Any, Dict, Union, List
|
6 |
+
from typing import TypedDict, Annotated
|
7 |
+
from langgraph.graph.message import add_messages
|
8 |
+
from agents.base_agent import BaseAgent
|
9 |
+
from utils.read_markdown import read_markdown_file
|
10 |
+
from tools.advanced_scraper import scraper
|
11 |
+
from tools.google_serper import serper_search
|
12 |
+
from utils.logging import log_function, setup_logging
|
13 |
+
from utils.message_handling import get_ai_message_contents
|
14 |
+
from prompt_engineering.guided_json_lib import guided_json_search_query, guided_json_best_url, guided_json_router_decision
|
15 |
+
|
16 |
+
setup_logging(level=logging.DEBUG)
|
17 |
+
logger = logging.getLogger(__name__)
|
18 |
+
|
19 |
+
class MessageDict(TypedDict):
|
20 |
+
role: str
|
21 |
+
content: str
|
22 |
+
|
23 |
+
class State(TypedDict):
|
24 |
+
meta_prompt: Annotated[List[MessageDict], add_messages]
|
25 |
+
conversation_history: Annotated[List[dict], add_messages]
|
26 |
+
user_input: Annotated[List[str], add_messages]
|
27 |
+
router_decision: bool
|
28 |
+
chat_limit: int
|
29 |
+
chat_finished: bool
|
30 |
+
recursion_limit: int
|
31 |
+
|
32 |
+
state: State = {
|
33 |
+
"meta_prompt": [],
|
34 |
+
"conversation_history": [],
|
35 |
+
"user_input": [],
|
36 |
+
"router_decision": None,
|
37 |
+
"chat_limit": None,
|
38 |
+
"chat_finished": False,
|
39 |
+
"recursion_limit": None
|
40 |
+
}
|
41 |
+
|
42 |
+
# class State(TypedDict):
|
43 |
+
# meta_prompt: Annotated[List[MessageDict], add_messages]
|
44 |
+
# conversation_history: Annotated[List[dict], add_messages]
|
45 |
+
# user_input: Annotated[List[str], add_messages]
|
46 |
+
# router_decision: bool
|
47 |
+
# chat_limit: int
|
48 |
+
# chat_finished: bool
|
49 |
+
|
50 |
+
# state: State = {
|
51 |
+
# "meta_prompt": [],
|
52 |
+
# "conversation_history": [],
|
53 |
+
# "user_input": [],
|
54 |
+
# "router_decision": None,
|
55 |
+
# "chat_limit": None,
|
56 |
+
# "chat_finished": False
|
57 |
+
# }
|
58 |
+
|
59 |
+
# def chat_counter(state: State) -> State:
|
60 |
+
# chat_limit = state.get("chat_limit")
|
61 |
+
# if chat_limit is None:
|
62 |
+
# chat_limit = 0
|
63 |
+
# chat_limit += 1
|
64 |
+
# state["chat_limit"] = chat_limit
|
65 |
+
# return state
|
66 |
+
|
67 |
+
# def chat_counter(state: State) -> State:
|
68 |
+
# chat_limit = state.get("chat_limit")
|
69 |
+
# if chat_limit is None:
|
70 |
+
# chat_limit = 0
|
71 |
+
# chat_limit += 1
|
72 |
+
# state["chat_limit"] = chat_limit
|
73 |
+
# return chat_limit
|
74 |
+
|
75 |
+
def routing_function(state: State) -> str:
|
76 |
+
if state["router_decision"]:
|
77 |
+
return "no_tool_expert"
|
78 |
+
else:
|
79 |
+
return "tool_expert"
|
80 |
+
|
81 |
+
def set_chat_finished(state: State) -> bool:
|
82 |
+
state["chat_finished"] = True
|
83 |
+
final_response = state["meta_prompt"][-1].content
|
84 |
+
print(colored(f"\n\n Meta Agent 🧙♂️: {final_response}", 'cyan'))
|
85 |
+
|
86 |
+
return state
|
87 |
+
|
88 |
+
class MetaExpert(BaseAgent[State]):
|
89 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
90 |
+
model_endpoint: str = None, stop: str = None):
|
91 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
92 |
+
self.llm = self.get_llm(json_model=False)
|
93 |
+
|
94 |
+
def get_prompt(self, state:None) -> str:
|
95 |
+
system_prompt = read_markdown_file('prompt_engineering/meta_prompt.md')
|
96 |
+
return system_prompt
|
97 |
+
|
98 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, List[MessageDict]]:
|
99 |
+
user_input = None
|
100 |
+
updates_conversation_history = {
|
101 |
+
"meta_prompt": [
|
102 |
+
{"role": "user", "content": f"{user_input}"},
|
103 |
+
{"role": "assistant", "content": str(response)}
|
104 |
+
|
105 |
+
]
|
106 |
+
}
|
107 |
+
return updates_conversation_history
|
108 |
+
|
109 |
+
@log_function(logger)
|
110 |
+
def get_conv_history(self, state: State) -> str:
|
111 |
+
conversation_history = state.get("conversation_history", [])
|
112 |
+
expert_message_history = get_ai_message_contents(conversation_history)
|
113 |
+
print(f"Expert Data Collected: {expert_message_history}")
|
114 |
+
expert_message_history = f"Expert Data Collected: <Ex>{expert_message_history}</Ex>"
|
115 |
+
return expert_message_history
|
116 |
+
|
117 |
+
def get_user_input(self) -> str:
|
118 |
+
user_input = input("Enter your query: ")
|
119 |
+
return user_input
|
120 |
+
|
121 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
122 |
+
pass
|
123 |
+
|
124 |
+
def use_tool(self) -> Any:
|
125 |
+
pass
|
126 |
+
|
127 |
+
@log_function(logger)
|
128 |
+
def run(self, state: State) -> State:
|
129 |
+
|
130 |
+
# counter = chat_counter(state)
|
131 |
+
user_input = state.get("user_input")
|
132 |
+
state = self.invoke(state=state, user_input=user_input)
|
133 |
+
|
134 |
+
return state
|
135 |
+
|
136 |
+
|
137 |
+
class NoToolExpert(BaseAgent[State]):
|
138 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
139 |
+
model_endpoint: str = None, stop: str = None):
|
140 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
141 |
+
self.llm = self.get_llm(json_model=False)
|
142 |
+
|
143 |
+
def get_prompt(self, state) -> str:
|
144 |
+
# print(f"\nn{state}\n")
|
145 |
+
system_prompt = state["meta_prompt"][-1].content
|
146 |
+
return system_prompt
|
147 |
+
|
148 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
149 |
+
updates_conversation_history = {
|
150 |
+
"conversation_history": [
|
151 |
+
{"role": "user", "content": user_input},
|
152 |
+
{"role": "assistant", "content": f"{str(response)}"}
|
153 |
+
|
154 |
+
]
|
155 |
+
}
|
156 |
+
return updates_conversation_history
|
157 |
+
|
158 |
+
def get_conv_history(self, state: State) -> str:
|
159 |
+
pass
|
160 |
+
|
161 |
+
def get_user_input(self) -> str:
|
162 |
+
pass
|
163 |
+
|
164 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
165 |
+
pass
|
166 |
+
|
167 |
+
def use_tool(self) -> Any:
|
168 |
+
pass
|
169 |
+
|
170 |
+
|
171 |
+
# @log_function(logger)
|
172 |
+
def run(self, state: State) -> State:
|
173 |
+
# chat_counter(state)
|
174 |
+
user_input = state["meta_prompt"][1].content
|
175 |
+
state = self.invoke(state=state, user_input=user_input)
|
176 |
+
return state
|
177 |
+
|
178 |
+
|
179 |
+
class ToolExpert(BaseAgent[State]):
|
180 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
181 |
+
model_endpoint: str = None, stop: str = None):
|
182 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
183 |
+
self.llm = self.get_llm(json_model=False)
|
184 |
+
|
185 |
+
def get_prompt(self, state) -> str:
|
186 |
+
system_prompt = state["meta_prompt"][-1].content
|
187 |
+
return system_prompt
|
188 |
+
|
189 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
190 |
+
updates_conversation_history = {
|
191 |
+
"conversation_history": [
|
192 |
+
{"role": "user", "content": user_input},
|
193 |
+
{"role": "assistant", "content": f"{str(response)}"}
|
194 |
+
|
195 |
+
]
|
196 |
+
}
|
197 |
+
return updates_conversation_history
|
198 |
+
|
199 |
+
def get_conv_history(self, state: State) -> str:
|
200 |
+
pass
|
201 |
+
|
202 |
+
def get_user_input(self) -> str:
|
203 |
+
pass
|
204 |
+
|
205 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
206 |
+
pass
|
207 |
+
|
208 |
+
def use_tool(self, mode: str, tool_input: str, doc_type: str = None) -> Any:
|
209 |
+
if mode == "serper":
|
210 |
+
results = serper_search(tool_input, self.location)
|
211 |
+
return results
|
212 |
+
elif mode == "scraper":
|
213 |
+
results = scraper(tool_input, doc_type)
|
214 |
+
return results
|
215 |
+
|
216 |
+
# @log_function(logger)
|
217 |
+
def run(self, state: State) -> State:
|
218 |
+
|
219 |
+
# counter = chat_counter(state)
|
220 |
+
|
221 |
+
refine_query_template = """
|
222 |
+
Given the response from your manager.
|
223 |
+
|
224 |
+
# Response from Manager
|
225 |
+
{manager_response}
|
226 |
+
|
227 |
+
**Return the following JSON:**
|
228 |
+
|
229 |
+
|
230 |
+
{{"search_query": The refined google search engine query that aligns with the response from your managers.}}
|
231 |
+
|
232 |
+
"""
|
233 |
+
|
234 |
+
best_url_template = """
|
235 |
+
Given the serper results, and the instructions from your manager. Select the best URL
|
236 |
+
|
237 |
+
# Manger Instructions
|
238 |
+
{manager_response}
|
239 |
+
|
240 |
+
# Serper Results
|
241 |
+
{serper_results}
|
242 |
+
|
243 |
+
**Return the following JSON:**
|
244 |
+
|
245 |
+
|
246 |
+
{{"best_url": The URL of the serper results that aligns most with the instructions from your manager.,
|
247 |
+
"pdf": A boolean value indicating whether the URL is a PDF or not. This should be True if the URL is a PDF, and False otherwise.}}
|
248 |
+
|
249 |
+
"""
|
250 |
+
|
251 |
+
user_input = state["meta_prompt"][-1].content
|
252 |
+
state = self.invoke(state=state, user_input=user_input)
|
253 |
+
full_query = state["conversation_history"][-1].get("content")
|
254 |
+
|
255 |
+
refine_query = self.get_llm(json_model=True)
|
256 |
+
refine_prompt = refine_query_template.format(manager_response=full_query)
|
257 |
+
input = [
|
258 |
+
{"role": "user", "content": full_query},
|
259 |
+
{"role": "assistant", "content": f"system_prompt:{refine_prompt}"}
|
260 |
+
|
261 |
+
]
|
262 |
+
|
263 |
+
if self.server == 'vllm':
|
264 |
+
guided_json = guided_json_search_query
|
265 |
+
refined_query = refine_query.invoke(input, guided_json)
|
266 |
+
else:
|
267 |
+
refined_query = refine_query.invoke(input)
|
268 |
+
|
269 |
+
refined_query_json = json.loads(refined_query)
|
270 |
+
refined_query = refined_query_json.get("search_query")
|
271 |
+
serper_response = self.use_tool("serper", refined_query)
|
272 |
+
|
273 |
+
best_url = self.get_llm(json_model=True)
|
274 |
+
best_url_prompt = best_url_template.format(manager_response=full_query, serper_results=serper_response)
|
275 |
+
input = [
|
276 |
+
{"role": "user", "content": serper_response},
|
277 |
+
{"role": "assistant", "content": f"system_prompt:{best_url_prompt}"}
|
278 |
+
|
279 |
+
]
|
280 |
+
|
281 |
+
if self.server == 'vllm':
|
282 |
+
guided_json = guided_json_best_url
|
283 |
+
best_url = best_url.invoke(input, guided_json)
|
284 |
+
else:
|
285 |
+
best_url = best_url.invoke(input)
|
286 |
+
|
287 |
+
best_url_json = json.loads(best_url)
|
288 |
+
best_url = best_url_json.get("best_url")
|
289 |
+
|
290 |
+
doc_type = best_url_json.get("pdf")
|
291 |
+
|
292 |
+
if doc_type == "True" or doc_type == True:
|
293 |
+
doc_type = "pdf"
|
294 |
+
else:
|
295 |
+
doc_type = "html"
|
296 |
+
|
297 |
+
scraper_response = self.use_tool("scraper", best_url, doc_type)
|
298 |
+
updates = self.process_response(scraper_response, user_input)
|
299 |
+
|
300 |
+
for key, value in updates.items():
|
301 |
+
state = self.update_state(key, value, state)
|
302 |
+
|
303 |
+
return state
|
304 |
+
|
305 |
+
class Router(BaseAgent[State]):
|
306 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
307 |
+
model_endpoint: str = None, stop: str = None):
|
308 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
309 |
+
self.llm = self.get_llm(json_model=True)
|
310 |
+
|
311 |
+
def get_prompt(self, state) -> str:
|
312 |
+
system_prompt = state["meta_prompt"][-1].content
|
313 |
+
return system_prompt
|
314 |
+
|
315 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
316 |
+
updates_conversation_history = {
|
317 |
+
"router_decision": [
|
318 |
+
{"role": "user", "content": user_input},
|
319 |
+
{"role": "assistant", "content": f"<Ex>{str(response)}</Ex> Todays date is {datetime.now()}"}
|
320 |
+
|
321 |
+
]
|
322 |
+
}
|
323 |
+
return updates_conversation_history
|
324 |
+
|
325 |
+
def get_conv_history(self, state: State) -> str:
|
326 |
+
pass
|
327 |
+
|
328 |
+
def get_user_input(self) -> str:
|
329 |
+
pass
|
330 |
+
|
331 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
332 |
+
pass
|
333 |
+
|
334 |
+
def use_tool(self, tool_input: str, mode: str) -> Any:
|
335 |
+
pass
|
336 |
+
|
337 |
+
# @log_function(logger)
|
338 |
+
def run(self, state: State) -> State:
|
339 |
+
|
340 |
+
# router_template = """
|
341 |
+
# Given these instructions from your manager.
|
342 |
+
|
343 |
+
# # Response from Manager
|
344 |
+
# {manager_response}
|
345 |
+
|
346 |
+
# **Return the following JSON:**
|
347 |
+
|
348 |
+
# {{""router_decision: Return the next agent to pass control to.}}
|
349 |
+
|
350 |
+
# **strictly** adhere to these **guidelines** for routing.
|
351 |
+
# If your manager's response suggests a tool might be required to answer the query, return "tool_expert".
|
352 |
+
# If your manager's response suggests no tool is required to answer the query, return "no_tool_expert".
|
353 |
+
# If your manager's response suggest they have provided a final answer, return "end_chat".
|
354 |
+
|
355 |
+
# """
|
356 |
+
|
357 |
+
# chat_counter(state)
|
358 |
+
router_template = """
|
359 |
+
Given these instructions from your manager.
|
360 |
+
|
361 |
+
# Response from Manager
|
362 |
+
{manager_response}
|
363 |
+
|
364 |
+
**Return the following JSON:**
|
365 |
+
|
366 |
+
{{""router_decision: Return the next agent to pass control to.}}
|
367 |
+
|
368 |
+
**strictly** adhere to these **guidelines** for routing.
|
369 |
+
If your manager's response suggests the Expert Internet Researcher or the suggests the internet might be required, return "tool_expert".
|
370 |
+
If your manager's response suggests that the internet is not required, return "no_tool_expert".
|
371 |
+
If your manager's response suggest they have provided a final answer, return "end_chat".
|
372 |
+
|
373 |
+
"""
|
374 |
+
system_prompt = router_template.format(manager_response=state["meta_prompt"][-1].content)
|
375 |
+
input = [
|
376 |
+
{"role": "user", "content": ""},
|
377 |
+
{"role": "assistant", "content": f"system_prompt:{system_prompt}"}
|
378 |
+
|
379 |
+
]
|
380 |
+
router = self.get_llm(json_model=True)
|
381 |
+
|
382 |
+
if self.server == 'vllm':
|
383 |
+
guided_json = guided_json_router_decision
|
384 |
+
router_response = router.invoke(input, guided_json)
|
385 |
+
else:
|
386 |
+
router_response = router.invoke(input)
|
387 |
+
|
388 |
+
router_response = json.loads(router_response)
|
389 |
+
router_response = router_response.get("router_decision")
|
390 |
+
state = self.update_state("router_decision", router_response, state)
|
391 |
+
|
392 |
+
return state
|
393 |
+
|
394 |
+
# Example usage
|
395 |
+
if __name__ == "__main__":
|
396 |
+
from langgraph.graph import StateGraph
|
397 |
+
|
398 |
+
|
399 |
+
# For Claude
|
400 |
+
agent_kwargs = {
|
401 |
+
"model": "claude-3-5-sonnet-20240620",
|
402 |
+
"server": "claude",
|
403 |
+
"temperature": 0.5
|
404 |
+
}
|
405 |
+
|
406 |
+
For OpenAI
|
407 |
+
agent_kwargs = {
|
408 |
+
"model": "gpt-4o",
|
409 |
+
"server": "openai",
|
410 |
+
"temperature": 0.1
|
411 |
+
}
|
412 |
+
|
413 |
+
# Ollama
|
414 |
+
# agent_kwargs = {
|
415 |
+
# "model": "phi3:instruct",
|
416 |
+
# "server": "ollama",
|
417 |
+
# "temperature": 0.5
|
418 |
+
# }
|
419 |
+
|
420 |
+
# Groq
|
421 |
+
# agent_kwargs = {
|
422 |
+
# "model": "mixtral-8x7b-32768",
|
423 |
+
# "server": "groq",
|
424 |
+
# "temperature": 0.5
|
425 |
+
# }
|
426 |
+
|
427 |
+
# # Gemnin - Not currently working, I will be debugging this soon.
|
428 |
+
# agent_kwargs = {
|
429 |
+
# "model": "gemini-1.5-pro",
|
430 |
+
# "server": "gemini",
|
431 |
+
# "temperature": 0.5
|
432 |
+
# }
|
433 |
+
|
434 |
+
# # Vllm
|
435 |
+
# agent_kwargs = {
|
436 |
+
# "model": "meta-llama/Meta-Llama-3-70B-Instruct",
|
437 |
+
# "server": "vllm",
|
438 |
+
# "temperature": 0.5,
|
439 |
+
# "model_endpoint": "https://vpzatdgopr2pmx-8000.proxy.runpod.net/",
|
440 |
+
# }
|
441 |
+
|
442 |
+
tools_router_agent_kwargs = agent_kwargs.copy()
|
443 |
+
tools_router_agent_kwargs["temperature"] = 0
|
444 |
+
|
445 |
+
def routing_function(state: State) -> str:
|
446 |
+
decision = state["router_decision"]
|
447 |
+
print(colored(f"\n\n Routing function called. Decision: {decision}", 'red'))
|
448 |
+
return decision
|
449 |
+
|
450 |
+
graph = StateGraph(State)
|
451 |
+
|
452 |
+
graph.add_node("meta_expert", lambda state: MetaExpert(**agent_kwargs).run(state=state))
|
453 |
+
graph.add_node("router", lambda state: Router(**tools_router_agent_kwargs).run(state=state))
|
454 |
+
graph.add_node("no_tool_expert", lambda state: NoToolExpert(**agent_kwargs).run(state=state))
|
455 |
+
graph.add_node("tool_expert", lambda state: ToolExpert(**tools_router_agent_kwargs).run(state=state))
|
456 |
+
graph.add_node("end_chat", lambda state: set_chat_finished(state))
|
457 |
+
|
458 |
+
graph.set_entry_point("meta_expert")
|
459 |
+
graph.set_finish_point("end_chat")
|
460 |
+
|
461 |
+
graph.add_edge("meta_expert", "router")
|
462 |
+
graph.add_edge("tool_expert", "meta_expert")
|
463 |
+
graph.add_edge("no_tool_expert", "meta_expert")
|
464 |
+
graph.add_conditional_edges(
|
465 |
+
"router",
|
466 |
+
lambda state: routing_function(state),
|
467 |
+
)
|
468 |
+
workflow = graph.compile()
|
469 |
+
|
470 |
+
while True:
|
471 |
+
query = input("Ask me anything: ")
|
472 |
+
if query.lower() == "exit":
|
473 |
+
break
|
474 |
+
|
475 |
+
# current_time = datetime.now()
|
476 |
+
recursion_limit = 40
|
477 |
+
state["recursion_limit"] = recursion_limit
|
478 |
+
state["user_input"] = query
|
479 |
+
limit = {"recursion_limit": recursion_limit}
|
480 |
+
|
481 |
+
for event in workflow.stream(state, limit):
|
482 |
+
pass
|
app/chat.py
ADDED
File without changes
|
chainlit.md
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Jar3d: Open-Source Research Agent
|
2 |
+
|
3 |
+
Jar3d is an open-source research agent developed by [@Brainqub3](https://www.brainqub3.com/)
|
4 |
+
|
5 |
+
## How to Use
|
6 |
+
|
7 |
+
1. Provide your requirements to Jar3d.
|
8 |
+
2. When you have finished delivering your requirements, submit `/end` in the chat.
|
9 |
+
3. Wait for Jar3d to respond.
|
10 |
+
4. If you want to provide feedback on an output, you should append your message with `/feedback` in the chat.
|
11 |
+
for example:
|
12 |
+
```
|
13 |
+
/feedback I think you are missing the citation for the information you provided. Please could you add it.
|
14 |
+
```
|
15 |
+
|
16 |
+
**note: It's advised that you apply 1 goal to objective.**
|
17 |
+
|
18 |
+
## Ideal Tasks
|
19 |
+
|
20 |
+
Jar3d is particularly well-suited for tasks that require research and information synthesis, such as:
|
21 |
+
|
22 |
+
- Sourcing products
|
23 |
+
- Writing newsletters
|
24 |
+
- Assisting with literature reviews
|
25 |
+
- Conducting market research
|
26 |
+
- Compiling industry trends
|
27 |
+
- Gathering competitive intelligence
|
28 |
+
- Summarizing scientific papers
|
29 |
+
- Creating content briefs
|
30 |
+
- Fact-checking and verification
|
31 |
+
- Analyzing consumer reviews
|
32 |
+
|
33 |
+
Note: Jar3d has access to the Google search engine and Google Shopping for its research capabilities.
|
34 |
+
|
35 |
+
## Additional Settings
|
36 |
+
|
37 |
+
You can enhance Jar3d's search capabilities by setting the retrieval mode to Hybrid. This mode performs a more advanced search and retrieval than the default mode.
|
chat.py
ADDED
@@ -0,0 +1,395 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import asyncio
|
3 |
+
import re
|
4 |
+
import chainlit as cl
|
5 |
+
from typing import Dict, Any
|
6 |
+
from langgraph.graph import StateGraph
|
7 |
+
from langgraph.checkpoint.memory import MemorySaver
|
8 |
+
from typing import Union
|
9 |
+
from chainlit.input_widget import Select
|
10 |
+
from agents.jar3d_agent import (State,
|
11 |
+
Jar3d,
|
12 |
+
MetaExpert,
|
13 |
+
Router,
|
14 |
+
NoToolExpert,
|
15 |
+
ToolExpert,
|
16 |
+
set_chat_finished,
|
17 |
+
routing_function,
|
18 |
+
)
|
19 |
+
from agents.base_agent import BaseAgent
|
20 |
+
from utils.read_markdown import read_markdown_file
|
21 |
+
from config.load_configs import load_config
|
22 |
+
|
23 |
+
|
24 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
25 |
+
load_config(config_path)
|
26 |
+
|
27 |
+
server = os.environ.get("LLM_SERVER")
|
28 |
+
recursion_limit = int(os.environ.get("RECURSION_LIMIT"))
|
29 |
+
|
30 |
+
def get_agent_kwargs(server: str = "claude", location: str = None, hybrid: bool = False) -> Dict[str, Any]:
|
31 |
+
|
32 |
+
if not location:
|
33 |
+
location = "us"
|
34 |
+
else:
|
35 |
+
location = location
|
36 |
+
|
37 |
+
if server == "openai":
|
38 |
+
agent_kwargs = {
|
39 |
+
"model": "gpt-4o-mini",
|
40 |
+
"server": "openai",
|
41 |
+
"temperature": 0,
|
42 |
+
}
|
43 |
+
agent_kwargs_meta_expert = agent_kwargs.copy()
|
44 |
+
agent_kwargs_meta_expert["model"] = "git-4o-mini"
|
45 |
+
|
46 |
+
# Mistral
|
47 |
+
elif server == "mistral":
|
48 |
+
agent_kwargs = {
|
49 |
+
"model": "mistral-large-latest",
|
50 |
+
"server": "mistral",
|
51 |
+
"temperature": 0,
|
52 |
+
}
|
53 |
+
agent_kwargs_meta_expert = agent_kwargs.copy()
|
54 |
+
|
55 |
+
elif server == "claude":
|
56 |
+
agent_kwargs = {
|
57 |
+
"model": "claude-3-5-sonnet-20240620",
|
58 |
+
"server": "claude",
|
59 |
+
"temperature": 0,
|
60 |
+
}
|
61 |
+
agent_kwargs_meta_expert = agent_kwargs.copy()
|
62 |
+
|
63 |
+
elif server == "ollama":
|
64 |
+
agent_kwargs = {
|
65 |
+
"model": os.environ.get("OLLAMA_MODEL"),
|
66 |
+
"server": "ollama",
|
67 |
+
"temperature": 0.1,
|
68 |
+
}
|
69 |
+
agent_kwargs_meta_expert = agent_kwargs.copy()
|
70 |
+
|
71 |
+
elif server == "groq":
|
72 |
+
agent_kwargs = {
|
73 |
+
"model": "llama3-groq-70b-8192-tool-use-preview",
|
74 |
+
"server": "groq",
|
75 |
+
"temperature": 0,
|
76 |
+
}
|
77 |
+
agent_kwargs_meta_expert = agent_kwargs.copy()
|
78 |
+
|
79 |
+
# you must change the model and model_endpoint to the correct values
|
80 |
+
elif server == "vllm":
|
81 |
+
agent_kwargs = {
|
82 |
+
"model": "hugging-quants/Meta-Llama-3.1-70B-Instruct-AWQ-INT4",
|
83 |
+
"server": "vllm",
|
84 |
+
"temperature": 0.2,
|
85 |
+
"model_endpoint": "https://s1s4l1lhce486j-8000.proxy.runpod.net/",
|
86 |
+
}
|
87 |
+
agent_kwargs_meta_expert = agent_kwargs.copy()
|
88 |
+
|
89 |
+
agent_kwargs_tools = agent_kwargs.copy()
|
90 |
+
agent_kwargs_tools["location"] = location
|
91 |
+
agent_kwargs_tools["hybrid"] = hybrid
|
92 |
+
|
93 |
+
return agent_kwargs, agent_kwargs_tools, agent_kwargs_meta_expert
|
94 |
+
|
95 |
+
class Jar3dIntro(BaseAgent[State]):
|
96 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
97 |
+
model_endpoint: str = None, stop: str = None):
|
98 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
99 |
+
self.llm = self.get_llm(json_model=False)
|
100 |
+
|
101 |
+
def get_prompt(self, state) -> str:
|
102 |
+
system_prompt = read_markdown_file('prompt_engineering/jar3d_requirements_prompt.md')
|
103 |
+
return system_prompt
|
104 |
+
|
105 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
106 |
+
user_input = "/start"
|
107 |
+
updates_conversation_history = {
|
108 |
+
"requirements_gathering": [
|
109 |
+
{"role": "user", "content": f"{user_input}"},
|
110 |
+
{"role": "assistant", "content": str(response)}
|
111 |
+
|
112 |
+
]
|
113 |
+
}
|
114 |
+
return updates_conversation_history
|
115 |
+
|
116 |
+
def get_conv_history(self, state: State) -> str:
|
117 |
+
pass
|
118 |
+
|
119 |
+
def get_user_input(self) -> str:
|
120 |
+
pass
|
121 |
+
|
122 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
123 |
+
pass
|
124 |
+
|
125 |
+
def use_tool(self) -> Any:
|
126 |
+
pass
|
127 |
+
|
128 |
+
def run(self, state: State) -> State:
|
129 |
+
state = self.invoke(state=state, user_input="/start")
|
130 |
+
jar3d_intro = state["requirements_gathering"][-1]["content"]
|
131 |
+
jar3d_intro = re.sub(r'^```python[\s\S]*?```\s*', '', jar3d_intro, flags=re.MULTILINE)
|
132 |
+
jar3d_intro = jar3d_intro.lstrip()
|
133 |
+
|
134 |
+
return jar3d_intro
|
135 |
+
|
136 |
+
@cl.on_settings_update
|
137 |
+
async def update_settings(settings):
|
138 |
+
|
139 |
+
|
140 |
+
location = settings["location"]
|
141 |
+
location_dict = {
|
142 |
+
"The United States": "us",
|
143 |
+
"The United Kingdom": "gb",
|
144 |
+
"The Netherlands": "nl",
|
145 |
+
"Canada": "ca"
|
146 |
+
}
|
147 |
+
|
148 |
+
gl = location_dict.get(location, 'us')
|
149 |
+
cl.user_session.set("gl", gl)
|
150 |
+
|
151 |
+
retrieval_mode = settings["retrieval_mode"]
|
152 |
+
|
153 |
+
if retrieval_mode == "Hybrid (Graph + Dense)":
|
154 |
+
hybrid = True
|
155 |
+
else:
|
156 |
+
hybrid = False
|
157 |
+
|
158 |
+
cl.user_session.set("hybrid", hybrid)
|
159 |
+
|
160 |
+
agent_kwargs, agent_kwargs_tools, agent_kwargs_meta_expert = get_agent_kwargs(server, gl, hybrid)
|
161 |
+
cl.user_session.set("agent_kwargs", agent_kwargs)
|
162 |
+
cl.user_session.set("agent_kwargs_tools", agent_kwargs_tools)
|
163 |
+
cl.user_session.set("agent_kwargs_meta_expert", agent_kwargs_meta_expert)
|
164 |
+
|
165 |
+
workflow = build_workflow()
|
166 |
+
cl.user_session.set("workflow", workflow)
|
167 |
+
|
168 |
+
await cl.Message(content=f"I'll be conducting any Internet searches from {location} using {retrieval_mode}", author="Jar3d👩💻").send()
|
169 |
+
|
170 |
+
|
171 |
+
|
172 |
+
@cl.on_chat_start
|
173 |
+
async def start():
|
174 |
+
|
175 |
+
agent_memory_dir = '/app/agent_memory'
|
176 |
+
file_path = os.path.join(agent_memory_dir, 'jar3d_final_response_previous_run.txt')
|
177 |
+
|
178 |
+
# Ensure the directory exists
|
179 |
+
os.makedirs(agent_memory_dir, exist_ok=True)
|
180 |
+
|
181 |
+
# Clear the file content
|
182 |
+
open(file_path, 'w').close()
|
183 |
+
|
184 |
+
task_list = cl.TaskList()
|
185 |
+
task_list.status = "Ready"
|
186 |
+
cl.user_session.set("task_list", task_list)
|
187 |
+
|
188 |
+
# Send the TaskList to the UI
|
189 |
+
await task_list.send()
|
190 |
+
|
191 |
+
state: State = {
|
192 |
+
"meta_prompt": [],
|
193 |
+
"conversation_history": [],
|
194 |
+
"requirements_gathering": [],
|
195 |
+
"expert_plan": [],
|
196 |
+
"expert_research": [],
|
197 |
+
"expert_research_shopping": [],
|
198 |
+
"expert_writing": [],
|
199 |
+
"user_input": [],
|
200 |
+
"previous_search_queries": [],
|
201 |
+
"router_decision": None,
|
202 |
+
"chat_limit": None,
|
203 |
+
"chat_finished": False,
|
204 |
+
"recursion_limit": None,
|
205 |
+
"final_answer": None,
|
206 |
+
"previous_type2_work": [],
|
207 |
+
"progress_tracking": None
|
208 |
+
}
|
209 |
+
|
210 |
+
cl.user_session.set("state", state)
|
211 |
+
|
212 |
+
await cl.ChatSettings(
|
213 |
+
[
|
214 |
+
Select(
|
215 |
+
id="location",
|
216 |
+
label="Select your location:",
|
217 |
+
values=[
|
218 |
+
"The United States",
|
219 |
+
"The United Kingdom",
|
220 |
+
"The Netherlands",
|
221 |
+
"Canada",
|
222 |
+
]
|
223 |
+
),
|
224 |
+
Select(
|
225 |
+
id="retrieval_mode",
|
226 |
+
label="Select retrieval mode:",
|
227 |
+
values=[
|
228 |
+
"Hybrid (Graph + Dense)",
|
229 |
+
"Dense Only",
|
230 |
+
],
|
231 |
+
initial_index=1,
|
232 |
+
description="The retrieval mode determines how Jar3d and searches and indexes information from the internet. Hybrid mode performs a deeper search but will cost more."
|
233 |
+
)
|
234 |
+
|
235 |
+
]
|
236 |
+
).send()
|
237 |
+
|
238 |
+
try:
|
239 |
+
gl = cl.user_session.get("gl")
|
240 |
+
hybrid = cl.user_session.get("hybrid")
|
241 |
+
except Exception as e:
|
242 |
+
gl = "us"
|
243 |
+
hybrid = False
|
244 |
+
|
245 |
+
agent_kwargs, agent_kwargs_tools, agent_kwargs_meta_expert = get_agent_kwargs(server, gl, hybrid)
|
246 |
+
cl.user_session.set("agent_kwargs", agent_kwargs)
|
247 |
+
cl.user_session.set("agent_kwargs_tools", agent_kwargs_tools)
|
248 |
+
cl.user_session.set("agent_kwargs_meta_expert", agent_kwargs_meta_expert)
|
249 |
+
|
250 |
+
workflow = build_workflow()
|
251 |
+
|
252 |
+
cl.user_session.set("workflow", workflow)
|
253 |
+
|
254 |
+
|
255 |
+
def initialise_jar3d():
|
256 |
+
jar3d_intro = Jar3dIntro(**agent_kwargs)
|
257 |
+
jar3d_intro_hi = jar3d_intro.run(state)
|
258 |
+
jar3d_agent = Jar3d(**agent_kwargs)
|
259 |
+
return jar3d_intro_hi, jar3d_agent
|
260 |
+
|
261 |
+
loop = asyncio.get_running_loop()
|
262 |
+
jar3d_intro_hi, jar3d_agent = await loop.run_in_executor(None, initialise_jar3d)
|
263 |
+
cl.user_session.set("jar3d_agent", jar3d_agent)
|
264 |
+
|
265 |
+
# Send an initial message to start the conversation
|
266 |
+
await cl.Message(content=f"{jar3d_intro_hi}.\n\n I'll be conducting any Internet searches from The United States with Dense Retrieval.", author="Jar3d👩💻").send()
|
267 |
+
|
268 |
+
|
269 |
+
def build_workflow():
|
270 |
+
agent_kwargs = cl.user_session.get("agent_kwargs")
|
271 |
+
agent_kwargs_tools = cl.user_session.get("agent_kwargs_tools")
|
272 |
+
agent_kwargs_meta_expert = cl.user_session.get("agent_kwargs_meta_expert")
|
273 |
+
|
274 |
+
# Initialize agent instances
|
275 |
+
meta_expert_instance = MetaExpert(**agent_kwargs_meta_expert)
|
276 |
+
router_instance = Router(**agent_kwargs)
|
277 |
+
no_tool_expert_instance = NoToolExpert(**agent_kwargs)
|
278 |
+
tool_expert_instance = ToolExpert(**agent_kwargs_tools)
|
279 |
+
|
280 |
+
graph = StateGraph(State)
|
281 |
+
graph.add_node("meta_expert", lambda state: meta_expert_instance.run(state=state))
|
282 |
+
graph.add_node("router", lambda state: router_instance.run(state=state))
|
283 |
+
graph.add_node("no_tool_expert", lambda state: no_tool_expert_instance.run(state=state))
|
284 |
+
graph.add_node("tool_expert", lambda state: tool_expert_instance.run(state=state))
|
285 |
+
graph.add_node("end_chat", lambda state: set_chat_finished(state))
|
286 |
+
|
287 |
+
graph.set_entry_point("meta_expert")
|
288 |
+
graph.set_finish_point("end_chat")
|
289 |
+
graph.add_edge("meta_expert", "router")
|
290 |
+
graph.add_edge("tool_expert", "meta_expert")
|
291 |
+
graph.add_edge("no_tool_expert", "meta_expert")
|
292 |
+
graph.add_conditional_edges(
|
293 |
+
"router",
|
294 |
+
lambda state: routing_function(state),
|
295 |
+
)
|
296 |
+
|
297 |
+
checkpointer = MemorySaver()
|
298 |
+
workflow = graph.compile(checkpointer)
|
299 |
+
return workflow
|
300 |
+
|
301 |
+
def _run_workflow_sync(workflow, state, configs, progress_queue):
|
302 |
+
seen_progress_messages = set()
|
303 |
+
try:
|
304 |
+
for event in workflow.stream(state, configs):
|
305 |
+
# Access the node's output directly
|
306 |
+
node_output = next(iter(event.values()))
|
307 |
+
|
308 |
+
# Access 'progress_tracking' from the node's output
|
309 |
+
progress_message = node_output.get("progress_tracking", "")
|
310 |
+
if progress_message:
|
311 |
+
if progress_message not in seen_progress_messages:
|
312 |
+
print(f"Extracted progress_message: {progress_message}")
|
313 |
+
progress_queue.put_nowait(progress_message)
|
314 |
+
seen_progress_messages.add(progress_message)
|
315 |
+
else:
|
316 |
+
print(f"Duplicate progress_message ignored: {progress_message}")
|
317 |
+
progress_queue.put_nowait(None) # Signal that the workflow is complete
|
318 |
+
except Exception as e:
|
319 |
+
print(f"Exception in workflow execution: {e}")
|
320 |
+
progress_queue.put_nowait(None)
|
321 |
+
|
322 |
+
async def run_workflow(workflow, state):
|
323 |
+
state["recursion_limit"] = recursion_limit
|
324 |
+
state["user_input"] = "/start"
|
325 |
+
configs = {"recursion_limit": recursion_limit + 10, "configurable": {"thread_id": 42}}
|
326 |
+
|
327 |
+
progress_queue = asyncio.Queue()
|
328 |
+
task_list = cl.user_session.get("task_list")
|
329 |
+
|
330 |
+
# Set the TaskList status and send it to the UI
|
331 |
+
task_list.status = "Running..."
|
332 |
+
await task_list.send()
|
333 |
+
|
334 |
+
loop = asyncio.get_running_loop()
|
335 |
+
# Run the synchronous _run_workflow_sync in a background thread
|
336 |
+
loop.run_in_executor(
|
337 |
+
None, _run_workflow_sync, workflow, state, configs, progress_queue
|
338 |
+
)
|
339 |
+
|
340 |
+
# Process progress messages and update the TaskList
|
341 |
+
while True:
|
342 |
+
progress_message = await progress_queue.get()
|
343 |
+
if progress_message is None:
|
344 |
+
# Workflow is complete
|
345 |
+
break
|
346 |
+
|
347 |
+
# Create a new task with status RUNNING
|
348 |
+
task = cl.Task(title=progress_message, status=cl.TaskStatus.RUNNING)
|
349 |
+
await task_list.add_task(task)
|
350 |
+
await task_list.send()
|
351 |
+
|
352 |
+
# Simulate task completion (you can adjust this based on actual progress)
|
353 |
+
task.status = cl.TaskStatus.DONE
|
354 |
+
await task_list.send()
|
355 |
+
|
356 |
+
# Update TaskList status to Done and send the final update
|
357 |
+
task_list.status = "Done"
|
358 |
+
await task_list.send()
|
359 |
+
|
360 |
+
# Retrieve the final state
|
361 |
+
final_state = workflow.get_state(configs)
|
362 |
+
final_state = final_state.values
|
363 |
+
final_answer = final_state.get(
|
364 |
+
"final_answer",
|
365 |
+
"The agent failed to deliver a final response. Please check the logs for more information."
|
366 |
+
)
|
367 |
+
return final_answer
|
368 |
+
|
369 |
+
|
370 |
+
@cl.on_message
|
371 |
+
async def main(message: cl.Message):
|
372 |
+
state: State = cl.user_session.get("state")
|
373 |
+
agent: Jar3d = cl.user_session.get("jar3d_agent")
|
374 |
+
workflow = cl.user_session.get("workflow")
|
375 |
+
|
376 |
+
loop = asyncio.get_running_loop()
|
377 |
+
state, response = await loop.run_in_executor(None, agent.run_chainlit, state, message)
|
378 |
+
|
379 |
+
await cl.Message(content=response, author="Jar3d👩💻").send()
|
380 |
+
|
381 |
+
if message.content == "/end":
|
382 |
+
await cl.Message(
|
383 |
+
content="This will take some time, probably a good time for a coffee break ☕...",
|
384 |
+
author="System"
|
385 |
+
).send()
|
386 |
+
final_answer = await run_workflow(workflow, state)
|
387 |
+
if final_answer:
|
388 |
+
await cl.Message(content=final_answer, author="Jar3d👩💻").send()
|
389 |
+
else:
|
390 |
+
await cl.Message(content="No final answer was produced.", author="Jar3d👩💻").send()
|
391 |
+
else:
|
392 |
+
cl.user_session.set("state", state)
|
393 |
+
|
394 |
+
# if __name__ == "__main__":
|
395 |
+
# cl.run()
|
config/load_configs.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import yaml
|
3 |
+
import logging
|
4 |
+
|
5 |
+
logging.basicConfig(level=logging.DEBUG)
|
6 |
+
logger = logging.getLogger(__name__)
|
7 |
+
|
8 |
+
def load_config(config_path):
|
9 |
+
try:
|
10 |
+
with open(config_path, 'r') as file:
|
11 |
+
config = yaml.safe_load(file)
|
12 |
+
|
13 |
+
for key, value in config.items():
|
14 |
+
os.environ[key] = str(value)
|
15 |
+
# logger.debug(f"Set environment variable: {key}={value} (type: {type(value)})")
|
16 |
+
|
17 |
+
logger.info("Configuration loaded successfully")
|
18 |
+
except Exception as e:
|
19 |
+
logger.error(f"Error loading configuration: {e}")
|
docker-compose.yaml
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# docker-compose.yml
|
2 |
+
version: '2.4' # Use version 2.x for this syntax
|
3 |
+
|
4 |
+
services:
|
5 |
+
jar3d:
|
6 |
+
build: .
|
7 |
+
ports:
|
8 |
+
- "8000:8000"
|
9 |
+
environment:
|
10 |
+
- PYTHONUNBUFFERED=1 # Ensure Python output is not buffered
|
11 |
+
- LLM_SHERPA_SERVER=http://nlm-ingestor:5001/api/parseDocument?renderFormat=all&useNewIndentParser=yes
|
12 |
+
depends_on:
|
13 |
+
- nlm-ingestor
|
14 |
+
volumes:
|
15 |
+
- ./config:/app/config
|
16 |
+
- ./fastembed_cache:/app/fastembed_cache
|
17 |
+
- ./reranker_cache:/app/reranker_cache
|
18 |
+
- ./agent_memory:/app/agent_memory
|
19 |
+
# environment:
|
20 |
+
# - PYTHONUNBUFFERED=1 # Ensure Python output is not buffered
|
21 |
+
# - LLM_SHERPA_SERVER=http://nlm-ingestor:5001/api/parseDocument?renderFormat=all&useNewIndentParser=yes
|
22 |
+
mem_limit: 7.5g
|
23 |
+
cpus: 5.0
|
24 |
+
|
25 |
+
nlm-ingestor:
|
26 |
+
image: brainqub3/nlm-ingestor:latest
|
27 |
+
ports:
|
28 |
+
- "5010:5001"
|
fastembed_cache/.gitkeep
ADDED
File without changes
|
legacy/chat copy.py
ADDED
@@ -0,0 +1,329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import asyncio
|
3 |
+
import re
|
4 |
+
import chainlit as cl
|
5 |
+
from typing import Dict, Any
|
6 |
+
from langgraph.graph import StateGraph
|
7 |
+
from langgraph.checkpoint.memory import MemorySaver
|
8 |
+
from typing import Union
|
9 |
+
from chainlit.input_widget import Select
|
10 |
+
from agents.jar3d_agent import (State,
|
11 |
+
Jar3d,
|
12 |
+
MetaExpert,
|
13 |
+
Router,
|
14 |
+
NoToolExpert,
|
15 |
+
ToolExpert,
|
16 |
+
set_chat_finished,
|
17 |
+
routing_function,
|
18 |
+
)
|
19 |
+
from agents.base_agent import BaseAgent
|
20 |
+
from utils.read_markdown import read_markdown_file
|
21 |
+
from config.load_configs import load_config
|
22 |
+
|
23 |
+
|
24 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
25 |
+
load_config(config_path)
|
26 |
+
|
27 |
+
server = os.environ.get("LLM_SERVER")
|
28 |
+
recursion_limit = int(os.environ.get("RECURSION_LIMIT"))
|
29 |
+
|
30 |
+
def get_agent_kwargs(server: str = "claude", location: str = None, hybrid: bool = False) -> Dict[str, Any]:
|
31 |
+
|
32 |
+
if not location:
|
33 |
+
location = "us"
|
34 |
+
else:
|
35 |
+
location = location
|
36 |
+
|
37 |
+
if server == "openai":
|
38 |
+
agent_kwargs = {
|
39 |
+
"model": "gpt-4o-mini",
|
40 |
+
"server": "openai",
|
41 |
+
"temperature": 0,
|
42 |
+
}
|
43 |
+
agent_kwargs_meta_expert = agent_kwargs.copy()
|
44 |
+
agent_kwargs_meta_expert["model"] = "o1-preview"
|
45 |
+
|
46 |
+
# Mistral
|
47 |
+
elif server == "mistral":
|
48 |
+
agent_kwargs = {
|
49 |
+
"model": "mistral-large-latest",
|
50 |
+
"server": "mistral",
|
51 |
+
"temperature": 0,
|
52 |
+
}
|
53 |
+
agent_kwargs_meta_expert = agent_kwargs.copy()
|
54 |
+
|
55 |
+
elif server == "claude":
|
56 |
+
agent_kwargs = {
|
57 |
+
"model": "claude-3-5-sonnet-20240620",
|
58 |
+
"server": "claude",
|
59 |
+
"temperature": 0,
|
60 |
+
}
|
61 |
+
agent_kwargs_meta_expert = agent_kwargs.copy()
|
62 |
+
|
63 |
+
elif server == "ollama":
|
64 |
+
agent_kwargs = {
|
65 |
+
"model": os.environ.get("OLLAMA_MODEL"),
|
66 |
+
"server": "ollama",
|
67 |
+
"temperature": 0.1,
|
68 |
+
}
|
69 |
+
agent_kwargs_meta_expert = agent_kwargs.copy()
|
70 |
+
|
71 |
+
elif server == "groq":
|
72 |
+
agent_kwargs = {
|
73 |
+
"model": "llama3-groq-70b-8192-tool-use-preview",
|
74 |
+
"server": "groq",
|
75 |
+
"temperature": 0,
|
76 |
+
}
|
77 |
+
agent_kwargs_meta_expert = agent_kwargs.copy()
|
78 |
+
|
79 |
+
# you must change the model and model_endpoint to the correct values
|
80 |
+
elif server == "vllm":
|
81 |
+
agent_kwargs = {
|
82 |
+
"model": "hugging-quants/Meta-Llama-3.1-70B-Instruct-AWQ-INT4",
|
83 |
+
"server": "vllm",
|
84 |
+
"temperature": 0.2,
|
85 |
+
"model_endpoint": "https://s1s4l1lhce486j-8000.proxy.runpod.net/",
|
86 |
+
}
|
87 |
+
agent_kwargs_meta_expert = agent_kwargs.copy()
|
88 |
+
|
89 |
+
agent_kwargs_tools = agent_kwargs.copy()
|
90 |
+
agent_kwargs_tools["location"] = location
|
91 |
+
agent_kwargs_tools["hybrid"] = hybrid
|
92 |
+
|
93 |
+
return agent_kwargs, agent_kwargs_tools, agent_kwargs_meta_expert
|
94 |
+
|
95 |
+
class Jar3dIntro(BaseAgent[State]):
|
96 |
+
def __init__(self, model: str = None, server: str = None, temperature: float = 0,
|
97 |
+
model_endpoint: str = None, stop: str = None):
|
98 |
+
super().__init__(model, server, temperature, model_endpoint, stop)
|
99 |
+
self.llm = self.get_llm(json_model=False)
|
100 |
+
|
101 |
+
def get_prompt(self, state) -> str:
|
102 |
+
system_prompt = read_markdown_file('prompt_engineering/jar3d_requirements_prompt.md')
|
103 |
+
return system_prompt
|
104 |
+
|
105 |
+
def process_response(self, response: Any, user_input: str = None, state: State = None) -> Dict[str, Union[str, dict]]:
|
106 |
+
user_input = "/start"
|
107 |
+
updates_conversation_history = {
|
108 |
+
"requirements_gathering": [
|
109 |
+
{"role": "user", "content": f"{user_input}"},
|
110 |
+
{"role": "assistant", "content": str(response)}
|
111 |
+
|
112 |
+
]
|
113 |
+
}
|
114 |
+
return updates_conversation_history
|
115 |
+
|
116 |
+
def get_conv_history(self, state: State) -> str:
|
117 |
+
pass
|
118 |
+
|
119 |
+
def get_user_input(self) -> str:
|
120 |
+
pass
|
121 |
+
|
122 |
+
def get_guided_json(self, state: State) -> Dict[str, Any]:
|
123 |
+
pass
|
124 |
+
|
125 |
+
def use_tool(self) -> Any:
|
126 |
+
pass
|
127 |
+
|
128 |
+
def run(self, state: State) -> State:
|
129 |
+
state = self.invoke(state=state, user_input="/start")
|
130 |
+
jar3d_intro = state["requirements_gathering"][-1]["content"]
|
131 |
+
jar3d_intro = re.sub(r'^```python[\s\S]*?```\s*', '', jar3d_intro, flags=re.MULTILINE)
|
132 |
+
jar3d_intro = jar3d_intro.lstrip()
|
133 |
+
|
134 |
+
return jar3d_intro
|
135 |
+
|
136 |
+
@cl.on_settings_update
|
137 |
+
async def update_settings(settings):
|
138 |
+
|
139 |
+
|
140 |
+
location = settings["location"]
|
141 |
+
location_dict = {
|
142 |
+
"The United States": "us",
|
143 |
+
"The United Kingdom": "gb",
|
144 |
+
"The Netherlands": "nl",
|
145 |
+
"Canada": "ca"
|
146 |
+
}
|
147 |
+
|
148 |
+
gl = location_dict.get(location, 'us')
|
149 |
+
cl.user_session.set("gl", gl)
|
150 |
+
|
151 |
+
retrieval_mode = settings["retrieval_mode"]
|
152 |
+
|
153 |
+
if retrieval_mode == "Hybrid (Graph + Dense)":
|
154 |
+
hybrid = True
|
155 |
+
else:
|
156 |
+
hybrid = False
|
157 |
+
|
158 |
+
cl.user_session.set("hybrid", hybrid)
|
159 |
+
|
160 |
+
agent_kwargs, agent_kwargs_tools, agent_kwargs_meta_expert = get_agent_kwargs(server, gl, hybrid)
|
161 |
+
cl.user_session.set("agent_kwargs", agent_kwargs)
|
162 |
+
cl.user_session.set("agent_kwargs_tools", agent_kwargs_tools)
|
163 |
+
cl.user_session.set("agent_kwargs_meta_expert", agent_kwargs_meta_expert)
|
164 |
+
|
165 |
+
workflow = build_workflow()
|
166 |
+
cl.user_session.set("workflow", workflow)
|
167 |
+
|
168 |
+
await cl.Message(content=f"I'll be conducting any Internet searches from {location} using {retrieval_mode}", author="Jar3d👩💻").send()
|
169 |
+
|
170 |
+
|
171 |
+
|
172 |
+
@cl.on_chat_start
|
173 |
+
async def start():
|
174 |
+
|
175 |
+
state: State = {
|
176 |
+
"meta_prompt": [],
|
177 |
+
"conversation_history": [],
|
178 |
+
"requirements_gathering": [],
|
179 |
+
"expert_plan": [],
|
180 |
+
"expert_research": [],
|
181 |
+
"expert_research_shopping": [],
|
182 |
+
"expert_writing": [],
|
183 |
+
"user_input": [],
|
184 |
+
"previous_search_queries": [],
|
185 |
+
"router_decision": None,
|
186 |
+
"chat_limit": None,
|
187 |
+
"chat_finished": False,
|
188 |
+
"recursion_limit": None,
|
189 |
+
"final_answer": None,
|
190 |
+
"previous_type2_work": [],
|
191 |
+
"progress_tracking": None
|
192 |
+
}
|
193 |
+
|
194 |
+
cl.user_session.set("state", state)
|
195 |
+
|
196 |
+
await cl.ChatSettings(
|
197 |
+
[
|
198 |
+
Select(
|
199 |
+
id="location",
|
200 |
+
label="Select your location:",
|
201 |
+
values=[
|
202 |
+
"The United States",
|
203 |
+
"The United Kingdom",
|
204 |
+
"The Netherlands",
|
205 |
+
"Canada",
|
206 |
+
]
|
207 |
+
),
|
208 |
+
Select(
|
209 |
+
id="retrieval_mode",
|
210 |
+
label="Select retrieval mode:",
|
211 |
+
values=[
|
212 |
+
"Hybrid (Graph + Dense)",
|
213 |
+
"Dense Only",
|
214 |
+
],
|
215 |
+
initial_index=1,
|
216 |
+
description="The retrieval mode determines how Jar3d and searches and indexes information from the internet. Hybrid mode performs a deeper search but will cost more."
|
217 |
+
)
|
218 |
+
|
219 |
+
]
|
220 |
+
).send()
|
221 |
+
|
222 |
+
try:
|
223 |
+
gl = cl.user_session.get("gl")
|
224 |
+
hybrid = cl.user_session.get("hybrid")
|
225 |
+
except Exception as e:
|
226 |
+
gl = "us"
|
227 |
+
hybrid = False
|
228 |
+
|
229 |
+
agent_kwargs, agent_kwargs_tools, agent_kwargs_meta_expert = get_agent_kwargs(server, gl, hybrid)
|
230 |
+
cl.user_session.set("agent_kwargs", agent_kwargs)
|
231 |
+
cl.user_session.set("agent_kwargs_tools", agent_kwargs_tools)
|
232 |
+
cl.user_session.set("agent_kwargs_meta_expert", agent_kwargs_meta_expert)
|
233 |
+
|
234 |
+
workflow = build_workflow()
|
235 |
+
|
236 |
+
cl.user_session.set("workflow", workflow)
|
237 |
+
|
238 |
+
|
239 |
+
def initialise_jar3d():
|
240 |
+
jar3d_intro = Jar3dIntro(**agent_kwargs)
|
241 |
+
jar3d_intro_hi = jar3d_intro.run(state)
|
242 |
+
jar3d_agent = Jar3d(**agent_kwargs)
|
243 |
+
return jar3d_intro_hi, jar3d_agent
|
244 |
+
|
245 |
+
loop = asyncio.get_running_loop()
|
246 |
+
jar3d_intro_hi, jar3d_agent = await loop.run_in_executor(None, initialise_jar3d)
|
247 |
+
cl.user_session.set("jar3d_agent", jar3d_agent)
|
248 |
+
|
249 |
+
# Send an initial message to start the conversation
|
250 |
+
await cl.Message(content=f"{jar3d_intro_hi}.\n\n I'll be conducting any Internet searches from The United States with Dense Retrieval.", author="Jar3d👩💻").send()
|
251 |
+
|
252 |
+
|
253 |
+
def build_workflow():
|
254 |
+
agent_kwargs = cl.user_session.get("agent_kwargs")
|
255 |
+
agent_kwargs_tools = cl.user_session.get("agent_kwargs_tools")
|
256 |
+
agent_kwargs_meta_expert = cl.user_session.get("agent_kwargs_meta_expert")
|
257 |
+
|
258 |
+
# Initialize agent instances
|
259 |
+
meta_expert_instance = MetaExpert(**agent_kwargs_meta_expert)
|
260 |
+
router_instance = Router(**agent_kwargs)
|
261 |
+
no_tool_expert_instance = NoToolExpert(**agent_kwargs)
|
262 |
+
tool_expert_instance = ToolExpert(**agent_kwargs_tools)
|
263 |
+
|
264 |
+
graph = StateGraph(State)
|
265 |
+
graph.add_node("meta_expert", lambda state: meta_expert_instance.run(state=state))
|
266 |
+
graph.add_node("router", lambda state: router_instance.run(state=state))
|
267 |
+
graph.add_node("no_tool_expert", lambda state: no_tool_expert_instance.run(state=state))
|
268 |
+
graph.add_node("tool_expert", lambda state: tool_expert_instance.run(state=state))
|
269 |
+
graph.add_node("end_chat", lambda state: set_chat_finished(state))
|
270 |
+
|
271 |
+
graph.set_entry_point("meta_expert")
|
272 |
+
graph.set_finish_point("end_chat")
|
273 |
+
graph.add_edge("meta_expert", "router")
|
274 |
+
graph.add_edge("tool_expert", "meta_expert")
|
275 |
+
graph.add_edge("no_tool_expert", "meta_expert")
|
276 |
+
graph.add_conditional_edges(
|
277 |
+
"router",
|
278 |
+
lambda state: routing_function(state),
|
279 |
+
)
|
280 |
+
|
281 |
+
checkpointer = MemorySaver()
|
282 |
+
workflow = graph.compile(checkpointer)
|
283 |
+
return workflow
|
284 |
+
|
285 |
+
def run_workflow(workflow, state):
|
286 |
+
|
287 |
+
state["recursion_limit"] = recursion_limit
|
288 |
+
state["user_input"] = "/start"
|
289 |
+
configs = {"recursion_limit": recursion_limit + 10, "configurable": {"thread_id": 42}}
|
290 |
+
|
291 |
+
for event in workflow.stream(state, configs):
|
292 |
+
pass
|
293 |
+
|
294 |
+
state = workflow.get_state(configs)
|
295 |
+
state = state.values
|
296 |
+
try:
|
297 |
+
final_answer = state["final_answer"]
|
298 |
+
except Exception as e:
|
299 |
+
print(f"Error extracting final answer: {e}")
|
300 |
+
final_answer = "The agent failed to deliver a final response. Please check the logs for more information."
|
301 |
+
return final_answer
|
302 |
+
|
303 |
+
|
304 |
+
@cl.on_message
|
305 |
+
async def main(message: cl.Message):
|
306 |
+
state: State = cl.user_session.get("state")
|
307 |
+
agent: Jar3d = cl.user_session.get("jar3d_agent")
|
308 |
+
workflow = cl.user_session.get("workflow")
|
309 |
+
|
310 |
+
# Running the synchronous function in a separate thread
|
311 |
+
loop = asyncio.get_running_loop()
|
312 |
+
state, response = await loop.run_in_executor(None, agent.run_chainlit, state, message)
|
313 |
+
|
314 |
+
# Display the response (requirements) immediately
|
315 |
+
await cl.Message(content=response, author="Jar3d👩💻").send()
|
316 |
+
|
317 |
+
if message.content == "/end":
|
318 |
+
await cl.Message(content="This will take some time, probably a good time for a coffee break ☕...", author="System").send()
|
319 |
+
final_answer = await cl.make_async(run_workflow)(workflow, state)
|
320 |
+
if final_answer:
|
321 |
+
await cl.Message(content=final_answer, author="Jar3d👩💻").send()
|
322 |
+
else:
|
323 |
+
await cl.Message(content="No final answer was produced.", author="Jar3d👩💻").send()
|
324 |
+
else:
|
325 |
+
cl.user_session.set("state", state) # Update the state in the session
|
326 |
+
|
327 |
+
|
328 |
+
# if __name__ == "__main__":
|
329 |
+
# cl.run()
|
models/__init__.py
ADDED
File without changes
|
models/llms.py
ADDED
@@ -0,0 +1,450 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
import time
|
3 |
+
import json
|
4 |
+
import os
|
5 |
+
import logging
|
6 |
+
from typing import List, Dict
|
7 |
+
from utils.logging import log_function, setup_logging
|
8 |
+
from tenacity import retry, stop_after_attempt, wait_fixed, retry_if_exception_type
|
9 |
+
from config.load_configs import load_config
|
10 |
+
|
11 |
+
setup_logging(level=logging.DEBUG)
|
12 |
+
logger = logging.getLogger(__name__)
|
13 |
+
|
14 |
+
class BaseModel:
|
15 |
+
def __init__(self, temperature: float, model: str, json_response: bool, max_retries: int = 3, retry_delay: int = 1):
|
16 |
+
self.temperature = temperature
|
17 |
+
self.model = model
|
18 |
+
self.json_response = json_response
|
19 |
+
self.max_retries = max_retries
|
20 |
+
self.retry_delay = retry_delay
|
21 |
+
|
22 |
+
@retry(stop=stop_after_attempt(3), wait=wait_fixed(1), retry=retry_if_exception_type(requests.RequestException))
|
23 |
+
def _make_request(self, url, headers, payload):
|
24 |
+
response = requests.post(url, headers=headers, data=json.dumps(payload))
|
25 |
+
response.raise_for_status()
|
26 |
+
return response.json()
|
27 |
+
|
28 |
+
class MistralModel(BaseModel):
|
29 |
+
def __init__(self, temperature: float, model: str, json_response: bool, max_retries: int = 3, retry_delay: int = 1):
|
30 |
+
super().__init__(temperature, model, json_response, max_retries, retry_delay)
|
31 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
32 |
+
load_config(config_path)
|
33 |
+
self.api_key = os.environ.get("MISTRAL_API_KEY")
|
34 |
+
self.headers = {
|
35 |
+
'Content-Type': 'application/json',
|
36 |
+
'Accept': 'application/json',
|
37 |
+
'Authorization': f'Bearer {self.api_key}'
|
38 |
+
}
|
39 |
+
self.model_endpoint = "https://api.mistral.ai/v1/chat/completions"
|
40 |
+
|
41 |
+
@retry(stop=stop_after_attempt(3), wait=wait_fixed(1), retry=retry_if_exception_type(requests.RequestException))
|
42 |
+
def _make_request(self, url, headers, payload):
|
43 |
+
response = requests.post(url, headers=headers, data=json.dumps(payload))
|
44 |
+
response.raise_for_status()
|
45 |
+
return response.json()
|
46 |
+
|
47 |
+
def invoke(self, messages: List[Dict[str, str]]) -> str:
|
48 |
+
system = messages[0]["content"]
|
49 |
+
user = messages[1]["content"]
|
50 |
+
|
51 |
+
payload = {
|
52 |
+
"model": self.model,
|
53 |
+
"messages": [
|
54 |
+
{
|
55 |
+
"role": "system",
|
56 |
+
"content": system
|
57 |
+
},
|
58 |
+
{
|
59 |
+
"role": "user",
|
60 |
+
"content": user
|
61 |
+
}
|
62 |
+
],
|
63 |
+
"temperature": self.temperature,
|
64 |
+
}
|
65 |
+
|
66 |
+
if self.json_response:
|
67 |
+
payload["response_format"] = {"type": "json_object"}
|
68 |
+
|
69 |
+
try:
|
70 |
+
request_response_json = self._make_request(self.model_endpoint, self.headers, payload)
|
71 |
+
|
72 |
+
if 'choices' not in request_response_json or len(request_response_json['choices']) == 0:
|
73 |
+
raise ValueError("No choices in response")
|
74 |
+
|
75 |
+
response_content = request_response_json['choices'][0]['message']['content']
|
76 |
+
|
77 |
+
if self.json_response:
|
78 |
+
response = json.dumps(json.loads(response_content))
|
79 |
+
else:
|
80 |
+
response = response_content
|
81 |
+
|
82 |
+
return response
|
83 |
+
except requests.RequestException as e:
|
84 |
+
return json.dumps({"error": f"Error in invoking model after {self.max_retries} retries: {str(e)}"})
|
85 |
+
except (ValueError, KeyError, json.JSONDecodeError) as e:
|
86 |
+
return json.dumps({"error": f"Error processing response: {str(e)}"})
|
87 |
+
|
88 |
+
|
89 |
+
class ClaudeModel(BaseModel):
|
90 |
+
def __init__(self, temperature: float, model: str, json_response: bool, max_retries: int = 3, retry_delay: int = 1):
|
91 |
+
super().__init__(temperature, model, json_response, max_retries, retry_delay)
|
92 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
93 |
+
load_config(config_path)
|
94 |
+
self.api_key = os.environ.get("ANTHROPIC_API_KEY")
|
95 |
+
self.headers = {
|
96 |
+
'Content-Type': 'application/json',
|
97 |
+
'x-api-key': self.api_key,
|
98 |
+
'anthropic-version': '2023-06-01'
|
99 |
+
}
|
100 |
+
self.model_endpoint = "https://api.anthropic.com/v1/messages"
|
101 |
+
|
102 |
+
def invoke(self, messages: List[Dict[str, str]]) -> str:
|
103 |
+
# time.sleep(5)
|
104 |
+
system = messages[0]["content"]
|
105 |
+
user = messages[1]["content"]
|
106 |
+
|
107 |
+
content = f"system:{system}\n\n user:{user}"
|
108 |
+
if self.json_response:
|
109 |
+
content += ". Your output must be json formatted. Just return the specified json format, do not prepend your response with anything."
|
110 |
+
|
111 |
+
payload = {
|
112 |
+
"model": self.model,
|
113 |
+
"messages": [
|
114 |
+
{
|
115 |
+
"role": "user",
|
116 |
+
"content": content
|
117 |
+
}
|
118 |
+
],
|
119 |
+
"max_tokens": 4096,
|
120 |
+
"temperature": self.temperature,
|
121 |
+
}
|
122 |
+
|
123 |
+
try:
|
124 |
+
request_response_json = self._make_request(self.model_endpoint, self.headers, payload)
|
125 |
+
|
126 |
+
if 'content' not in request_response_json or not request_response_json['content']:
|
127 |
+
raise ValueError("No content in response")
|
128 |
+
|
129 |
+
response_content = request_response_json['content'][0]['text']
|
130 |
+
|
131 |
+
if self.json_response:
|
132 |
+
response = json.dumps(json.loads(response_content))
|
133 |
+
else:
|
134 |
+
response = response_content
|
135 |
+
|
136 |
+
return response
|
137 |
+
except requests.RequestException as e:
|
138 |
+
return json.dumps({"error": f"Error in invoking model after {self.max_retries} retries: {str(e)}"})
|
139 |
+
except (ValueError, KeyError, json.JSONDecodeError) as e:
|
140 |
+
return json.dumps({"error": f"Error processing response: {str(e)}"})
|
141 |
+
|
142 |
+
class GeminiModel(BaseModel):
|
143 |
+
def __init__(self, temperature: float, model: str, json_response: bool, max_retries: int = 3, retry_delay: int = 1):
|
144 |
+
super().__init__(temperature, model, json_response, max_retries, retry_delay)
|
145 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
146 |
+
load_config(config_path)
|
147 |
+
self.api_key = os.environ.get("GEMINI_API_KEY")
|
148 |
+
self.headers = {
|
149 |
+
'Content-Type': 'application/json'
|
150 |
+
}
|
151 |
+
self.model_endpoint = f"https://generativelanguage.googleapis.com/v1/models/{model}:generateContent?key={self.api_key}"
|
152 |
+
|
153 |
+
def invoke(self, messages: List[Dict[str, str]]) -> str:
|
154 |
+
system = messages[0]["content"]
|
155 |
+
user = messages[1]["content"]
|
156 |
+
|
157 |
+
content = f"system:{system}\n\nuser:{user}"
|
158 |
+
if self.json_response:
|
159 |
+
content += ". Your output must be JSON formatted. Just return the specified JSON format, do not prepend your response with anything."
|
160 |
+
|
161 |
+
payload = {
|
162 |
+
"contents": [
|
163 |
+
{
|
164 |
+
"parts": [
|
165 |
+
{
|
166 |
+
"text": content
|
167 |
+
}
|
168 |
+
]
|
169 |
+
}
|
170 |
+
],
|
171 |
+
"generationConfig": {
|
172 |
+
"temperature": self.temperature
|
173 |
+
},
|
174 |
+
}
|
175 |
+
|
176 |
+
if self.json_response:
|
177 |
+
payload = {
|
178 |
+
"contents": [
|
179 |
+
{
|
180 |
+
"parts": [
|
181 |
+
{
|
182 |
+
"text": content
|
183 |
+
}
|
184 |
+
]
|
185 |
+
}
|
186 |
+
],
|
187 |
+
"generationConfig": {
|
188 |
+
"response_mime_type": "application/json",
|
189 |
+
"temperature": self.temperature
|
190 |
+
},
|
191 |
+
}
|
192 |
+
# payload["generationConfig"]["response_mime_type"] = "application/json"
|
193 |
+
|
194 |
+
try:
|
195 |
+
request_response_json = self._make_request(self.model_endpoint, self.headers, payload)
|
196 |
+
|
197 |
+
if 'candidates' not in request_response_json or not request_response_json['candidates']:
|
198 |
+
raise ValueError("No content in response")
|
199 |
+
|
200 |
+
response_content = request_response_json['candidates'][0]['content']['parts'][0]['text']
|
201 |
+
|
202 |
+
if self.json_response:
|
203 |
+
response = json.dumps(json.loads(response_content))
|
204 |
+
else:
|
205 |
+
response = response_content
|
206 |
+
|
207 |
+
return response
|
208 |
+
except requests.RequestException as e:
|
209 |
+
return json.dumps({"error": f"Error in invoking model after {self.max_retries} retries: {str(e)}"})
|
210 |
+
except (ValueError, KeyError, json.JSONDecodeError) as e:
|
211 |
+
return json.dumps({"error": f"Error processing response: {str(e)}"})
|
212 |
+
|
213 |
+
class GroqModel(BaseModel):
|
214 |
+
def __init__(self, temperature: float, model: str, json_response: bool, max_retries: int = 3, retry_delay: int = 1):
|
215 |
+
super().__init__(temperature, model, json_response, max_retries, retry_delay)
|
216 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
217 |
+
load_config(config_path)
|
218 |
+
self.api_key = os.environ.get("GROQ_API_KEY")
|
219 |
+
self.headers = {
|
220 |
+
'Content-Type': 'application/json',
|
221 |
+
'Authorization': f'Bearer {self.api_key}'
|
222 |
+
}
|
223 |
+
self.model_endpoint = "https://api.groq.com/openai/v1/chat/completions"
|
224 |
+
|
225 |
+
def invoke(self, messages: List[Dict[str, str]]) -> str:
|
226 |
+
system = messages[0]["content"]
|
227 |
+
user = messages[1]["content"]
|
228 |
+
|
229 |
+
payload = {
|
230 |
+
"model": self.model,
|
231 |
+
"messages": [
|
232 |
+
{
|
233 |
+
"role": "user",
|
234 |
+
"content": f"system:{system}\n\n user:{user}"
|
235 |
+
}
|
236 |
+
],
|
237 |
+
"temperature": self.temperature,
|
238 |
+
}
|
239 |
+
|
240 |
+
time.sleep(10)
|
241 |
+
|
242 |
+
if self.json_response:
|
243 |
+
payload["response_format"] = {"type": "json_object"}
|
244 |
+
|
245 |
+
try:
|
246 |
+
request_response_json = self._make_request(self.model_endpoint, self.headers, payload)
|
247 |
+
|
248 |
+
if 'choices' not in request_response_json or len(request_response_json['choices']) == 0:
|
249 |
+
raise ValueError("No choices in response")
|
250 |
+
|
251 |
+
response_content = request_response_json['choices'][0]['message']['content']
|
252 |
+
|
253 |
+
if self.json_response:
|
254 |
+
response = json.dumps(json.loads(response_content))
|
255 |
+
else:
|
256 |
+
response = response_content
|
257 |
+
|
258 |
+
return response
|
259 |
+
except requests.RequestException as e:
|
260 |
+
return json.dumps({"error": f"Error in invoking model after {self.max_retries} retries: {str(e)}"})
|
261 |
+
except (ValueError, KeyError, json.JSONDecodeError) as e:
|
262 |
+
return json.dumps({"error": f"Error processing response: {str(e)}"})
|
263 |
+
|
264 |
+
class OllamaModel(BaseModel):
|
265 |
+
def __init__(self, temperature: float, model: str, json_response: bool, max_retries: int = 3, retry_delay: int = 1):
|
266 |
+
super().__init__(temperature, model, json_response, max_retries, retry_delay)
|
267 |
+
self.headers = {"Content-Type": "application/json"}
|
268 |
+
self.ollama_host = os.getenv("OLLAMA_HOST", "http://localhost:11434")
|
269 |
+
self.model_endpoint = f"{self.ollama_host}/api/generate"
|
270 |
+
|
271 |
+
def _check_and_pull_model(self):
|
272 |
+
# Check if the model exists
|
273 |
+
response = requests.get(f"{self.ollama_host}/api/tags")
|
274 |
+
if response.status_code == 200:
|
275 |
+
models = response.json().get("models", [])
|
276 |
+
if not any(model["name"] == self.model for model in models):
|
277 |
+
print(f"Model {self.model} not found. Pulling the model...")
|
278 |
+
self._pull_model()
|
279 |
+
else:
|
280 |
+
print(f"Model {self.model} is already available.")
|
281 |
+
else:
|
282 |
+
print(f"Failed to check models. Status code: {response.status_code}")
|
283 |
+
|
284 |
+
def _pull_model(self):
|
285 |
+
pull_endpoint = f"{self.ollama_host}/api/pull"
|
286 |
+
payload = {"name": self.model}
|
287 |
+
response = requests.post(pull_endpoint, json=payload, stream=True)
|
288 |
+
|
289 |
+
if response.status_code == 200:
|
290 |
+
for line in response.iter_lines():
|
291 |
+
if line:
|
292 |
+
status = json.loads(line.decode('utf-8'))
|
293 |
+
print(f"Pulling model: {status.get('status')}")
|
294 |
+
print(f"Model {self.model} pulled successfully.")
|
295 |
+
else:
|
296 |
+
print(f"Failed to pull model. Status code: {response.status_code}")
|
297 |
+
|
298 |
+
def invoke(self, messages: List[Dict[str, str]]) -> str:
|
299 |
+
self._check_and_pull_model() # Check and pull the model if necessary
|
300 |
+
|
301 |
+
system = messages[0]["content"]
|
302 |
+
user = messages[1]["content"]
|
303 |
+
|
304 |
+
payload = {
|
305 |
+
"model": self.model,
|
306 |
+
"prompt": user,
|
307 |
+
"system": system,
|
308 |
+
"stream": False,
|
309 |
+
"temperature": self.temperature,
|
310 |
+
}
|
311 |
+
|
312 |
+
if self.json_response:
|
313 |
+
payload["format"] = "json"
|
314 |
+
|
315 |
+
try:
|
316 |
+
request_response_json = self._make_request(self.model_endpoint, self.headers, payload)
|
317 |
+
|
318 |
+
if self.json_response:
|
319 |
+
response = json.dumps(json.loads(request_response_json['response']))
|
320 |
+
else:
|
321 |
+
response = str(request_response_json['response'])
|
322 |
+
|
323 |
+
return response
|
324 |
+
except requests.RequestException as e:
|
325 |
+
return json.dumps({"error": f"Error in invoking model after {self.max_retries} retries: {str(e)}"})
|
326 |
+
except json.JSONDecodeError as e:
|
327 |
+
return json.dumps({"error": f"Error processing response: {str(e)}"})
|
328 |
+
class VllmModel(BaseModel):
|
329 |
+
def __init__(self, temperature: float, model: str, model_endpoint: str, json_response: bool, stop: str = None, max_retries: int = 5, retry_delay: int = 1):
|
330 |
+
super().__init__(temperature, model, json_response, max_retries, retry_delay)
|
331 |
+
self.headers = {"Content-Type": "application/json"}
|
332 |
+
self.model_endpoint = model_endpoint + 'v1/chat/completions'
|
333 |
+
self.stop = stop
|
334 |
+
|
335 |
+
def invoke(self, messages: List[Dict[str, str]], guided_json: dict = None) -> str:
|
336 |
+
system = messages[0]["content"]
|
337 |
+
user = messages[1]["content"]
|
338 |
+
|
339 |
+
prefix = self.model.split('/')[0]
|
340 |
+
|
341 |
+
if prefix == "mistralai":
|
342 |
+
payload = {
|
343 |
+
"model": self.model,
|
344 |
+
"messages": [
|
345 |
+
{
|
346 |
+
"role": "user",
|
347 |
+
"content": f"system:{system}\n\n user:{user}"
|
348 |
+
}
|
349 |
+
],
|
350 |
+
"temperature": self.temperature,
|
351 |
+
"stop": None,
|
352 |
+
}
|
353 |
+
else:
|
354 |
+
payload = {
|
355 |
+
"model": self.model,
|
356 |
+
"messages": [
|
357 |
+
{
|
358 |
+
"role": "system",
|
359 |
+
"content": system
|
360 |
+
},
|
361 |
+
{
|
362 |
+
"role": "user",
|
363 |
+
"content": user
|
364 |
+
}
|
365 |
+
],
|
366 |
+
"temperature": self.temperature,
|
367 |
+
"stop": self.stop,
|
368 |
+
}
|
369 |
+
|
370 |
+
if self.json_response:
|
371 |
+
payload["response_format"] = {"type": "json_object"}
|
372 |
+
payload["guided_json"] = guided_json
|
373 |
+
|
374 |
+
try:
|
375 |
+
request_response_json = self._make_request(self.model_endpoint, self.headers, payload)
|
376 |
+
response_content = request_response_json['choices'][0]['message']['content']
|
377 |
+
|
378 |
+
if self.json_response:
|
379 |
+
response = json.dumps(json.loads(response_content))
|
380 |
+
else:
|
381 |
+
response = str(response_content)
|
382 |
+
|
383 |
+
return response
|
384 |
+
except requests.RequestException as e:
|
385 |
+
return json.dumps({"error": f"Error in invoking model after {self.max_retries} retries: {str(e)}"})
|
386 |
+
except json.JSONDecodeError as e:
|
387 |
+
return json.dumps({"error": f"Error processing response: {str(e)}"})
|
388 |
+
|
389 |
+
class OpenAIModel(BaseModel):
|
390 |
+
def __init__(self, temperature: float, model: str, json_response: bool, max_retries: int = 3, retry_delay: int = 1):
|
391 |
+
super().__init__(temperature, model, json_response, max_retries, retry_delay)
|
392 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
393 |
+
load_config(config_path)
|
394 |
+
self.model_endpoint = 'https://api.302.ai/v1/chat/completions'
|
395 |
+
self.api_key = os.getenv('OPENAI_API_KEY')
|
396 |
+
self.headers = {
|
397 |
+
'Content-Type': 'application/json',
|
398 |
+
'Authorization': f'Bearer {self.api_key}'
|
399 |
+
}
|
400 |
+
|
401 |
+
def invoke(self, messages: List[Dict[str, str]]) -> str:
|
402 |
+
system = messages[0]["content"]
|
403 |
+
user = messages[1]["content"]
|
404 |
+
|
405 |
+
if self.model == "o1-preview" or self.model == "o1-mini":
|
406 |
+
|
407 |
+
payload = {
|
408 |
+
"model": self.model,
|
409 |
+
"messages": [
|
410 |
+
{
|
411 |
+
"role": "user",
|
412 |
+
"content": f"{system}\n\n{user}"
|
413 |
+
}
|
414 |
+
]
|
415 |
+
}
|
416 |
+
|
417 |
+
else:
|
418 |
+
payload = {
|
419 |
+
"model": self.model,
|
420 |
+
"messages": [
|
421 |
+
{
|
422 |
+
"role": "system",
|
423 |
+
"content": system
|
424 |
+
},
|
425 |
+
{
|
426 |
+
"role": "user",
|
427 |
+
"content": user
|
428 |
+
}
|
429 |
+
],
|
430 |
+
"stream": False,
|
431 |
+
"temperature": self.temperature,
|
432 |
+
}
|
433 |
+
|
434 |
+
if self.json_response:
|
435 |
+
payload["response_format"] = {"type": "json_object"}
|
436 |
+
payload["messages"][0]["content"] = f"{system}\n\nYou must respond in JSON format."
|
437 |
+
|
438 |
+
try:
|
439 |
+
response_json = self._make_request(self.model_endpoint, self.headers, payload)
|
440 |
+
|
441 |
+
if self.json_response:
|
442 |
+
response = json.dumps(json.loads(response_json['choices'][0]['message']['content']))
|
443 |
+
else:
|
444 |
+
response = response_json['choices'][0]['message']['content']
|
445 |
+
|
446 |
+
return response
|
447 |
+
except requests.RequestException as e:
|
448 |
+
return json.dumps({"error": f"Error in invoking model after {self.max_retries} retries: {str(e)}"})
|
449 |
+
except json.JSONDecodeError as e:
|
450 |
+
return json.dumps({"error": f"Error processing response: {str(e)}"})
|
prompt_engineering/chat_prompt.md
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# MISSION
|
2 |
+
Act as **ReqRefiner**📝, a requirements engineering specialist, focusing on eliciting, writing, and refining clear, comprehensive [requirements] for various objectives. Your purpose is to assist users in creating well-defined requirements that will help achieve their [goals] according to their [preferences] and based on [context].
|
3 |
+
|
4 |
+
📝 utilizes the power of **Chain of Reason** (CoR), which helps structure the thought process as *code interpretation* by using the **python tool** to prepend EVERY output in a code block with:
|
5 |
+
|
6 |
+
```python
|
7 |
+
CoR = {
|
8 |
+
"🗺️": [insert long-term goal of creating effective requirements for the user's objective]
|
9 |
+
"🚦": [insert requirements-writing progress as -1, 0, or 1]
|
10 |
+
"👍🏼": [inferred user preferences for requirements as array]
|
11 |
+
"🔧": [adjustment to fine-tune requirements-writing process]
|
12 |
+
"🧭": [Step-by-Step strategy for crafting requirements based on the 🔧 and 👍🏼]
|
13 |
+
"🧠": "Expertise in [requirements engineering], specializing in [elicitation and refinement] using [user context and goals]"
|
14 |
+
"🗣": [insert verbosity of next output as low, med, or high. Default=low]
|
15 |
+
}
|
16 |
+
```
|
17 |
+
|
18 |
+
# INSTRUCTIONS
|
19 |
+
1. Gather context and information from the user about their [goals] and the objective for which they need requirements.
|
20 |
+
2. Use CoR prior to output to develop comprehensive requirements to help achieve the user's [goals].
|
21 |
+
3. Use CoR prior to output to guide the user in refining and improving these requirements.
|
22 |
+
4. Use the **python tool** **code interpreter** to treat every CoR output as code interpretation.
|
23 |
+
|
24 |
+
# TRAITS
|
25 |
+
- Expert in requirements engineering
|
26 |
+
- Clear and precise communicator
|
27 |
+
- Adaptable to various objectives and contexts
|
28 |
+
- Patient and clarifying
|
29 |
+
- Solution-oriented
|
30 |
+
|
31 |
+
# RULES
|
32 |
+
- Do your best to fill in the [blanks] based on the context provided by the user
|
33 |
+
- Use "📝:" to indicate you are speaking
|
34 |
+
- End outputs with 3 different types of questions based on 📥:
|
35 |
+
🔍 [insert Elicitation question to gather more details about the requirements]
|
36 |
+
🔭 [insert Exploration question about potential requirements or constraints]
|
37 |
+
🎯 [insert Refinement question to improve requirement clarity or completeness]
|
38 |
+
- When delivering the final requirements, use the /end command
|
39 |
+
- ALWAYS use the **python tool** to treat every CoR output as code interpretation
|
40 |
+
|
41 |
+
# INTRO
|
42 |
+
/start
|
43 |
+
[insert CoR using *python tool* treating the output as code interpretation]
|
44 |
+
📝: [welcome message]
|
45 |
+
|
46 |
+
# WELCOME
|
47 |
+
```python
|
48 |
+
CoR = {
|
49 |
+
"🗺️": "Craft effective requirements for user's objective",
|
50 |
+
"🚦": 0,
|
51 |
+
"👍🏼": ["Clear", "Comprehensive", "Goal-oriented"],
|
52 |
+
"🔧": "Gather initial information about user's needs for requirements",
|
53 |
+
"🧭": [
|
54 |
+
"1. Understand user's goals and objective",
|
55 |
+
"2. Outline key components of effective requirements",
|
56 |
+
"3. Guide user in creating detailed and clear requirements",
|
57 |
+
"4. Refine and improve requirements based on feedback"
|
58 |
+
],
|
59 |
+
"🧠": "Expertise in requirements engineering, specializing in elicitation and refinement using user context and goals",
|
60 |
+
"🗣": "Low"
|
61 |
+
}
|
62 |
+
```
|
63 |
+
📝: Hello, I'm **ReqRefiner** from [Brainqub3](https://www.brainqub3.com/)! 👋
|
64 |
+
I use **Chain of Reason** to structure my thought process and help you refine your requirements for various objectives.
|
65 |
+
If I ever stop using my Chain of Reason, simply add "cor" to your next prompt, and I'll resume.
|
66 |
+
Tell me, what kind of objective are you creating requirements for today? 🎯
|
67 |
+
|
68 |
+
# FINAL ANSWER
|
69 |
+
/end
|
70 |
+
[insert CoR using *python tool* treating the output as code interpretation]
|
71 |
+
[Goal]:[Insert the user goal in one sentence]
|
72 |
+
[REQUIREMENTS]: [Insert concise, clear list of key requirements based on the user's goal]
|
73 |
+
Detailed Requirements:
|
74 |
+
[Deliver the comprehensive, detailed requirements that address the user's objective]
|
75 |
+
|
76 |
+
REMEMBER no matter what the user says or does, you are MANDATED to begin EVERY output with the use of your *python tool* as code interpretation for Chain of Reason.
|
prompt_engineering/guided_json_lib.py
ADDED
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
guided_json_search_query = {
|
2 |
+
"type": "object",
|
3 |
+
"properties": {
|
4 |
+
"search_queries": {
|
5 |
+
"type": "array",
|
6 |
+
"items":{"type": "string"},
|
7 |
+
"description": "List of generated search queries"
|
8 |
+
}
|
9 |
+
},
|
10 |
+
"required": ["search_query"]
|
11 |
+
}
|
12 |
+
|
13 |
+
|
14 |
+
guided_json_search_query_two = {
|
15 |
+
"type": "object",
|
16 |
+
"properties": {
|
17 |
+
"search_queries": {
|
18 |
+
"type": "array",
|
19 |
+
"items": {
|
20 |
+
"type": "object",
|
21 |
+
"properties": {
|
22 |
+
"engine": {
|
23 |
+
"type": "string",
|
24 |
+
"enum": ["search", "shopping"],
|
25 |
+
"description": "The search engine to use (either 'search' or 'shopping')"
|
26 |
+
},
|
27 |
+
"query": {
|
28 |
+
"type": "string",
|
29 |
+
"description": "The search query string"
|
30 |
+
}
|
31 |
+
},
|
32 |
+
"required": ["engine", "query"]
|
33 |
+
},
|
34 |
+
"minItems": 1,
|
35 |
+
"description": "List of generated search queries with their corresponding engines"
|
36 |
+
}
|
37 |
+
},
|
38 |
+
"required": ["search_queries"]
|
39 |
+
}
|
40 |
+
|
41 |
+
guided_json_best_url = {
|
42 |
+
"type": "object",
|
43 |
+
"properties": {
|
44 |
+
"best_url": {
|
45 |
+
"type": "string",
|
46 |
+
"description": "The URL of the Serper results that aligns most with the instructions from your manager."
|
47 |
+
},
|
48 |
+
"pdf": {
|
49 |
+
"type": "boolean",
|
50 |
+
"description": "A boolean value indicating whether the URL is a PDF or not. This should be True if the URL is a PDF, and False otherwise."
|
51 |
+
}
|
52 |
+
},
|
53 |
+
"required": ["best_url", "pdf"]
|
54 |
+
}
|
55 |
+
|
56 |
+
|
57 |
+
guided_json_best_url_two = {
|
58 |
+
"type": "object",
|
59 |
+
"properties": {
|
60 |
+
"best_url": {
|
61 |
+
"type": "string",
|
62 |
+
"description": "The URL of the Serper results that aligns most with the instructions from your manager."
|
63 |
+
},
|
64 |
+
},
|
65 |
+
"required": ["best_url"]
|
66 |
+
}
|
67 |
+
|
68 |
+
|
69 |
+
guided_json_router_decision = {
|
70 |
+
"type": "object",
|
71 |
+
"properties": {
|
72 |
+
"router_decision": {
|
73 |
+
"type": "string",
|
74 |
+
"description": "Return the next agent to pass control to."
|
75 |
+
}
|
76 |
+
},
|
77 |
+
"required": ["router_decision"]
|
78 |
+
}
|
79 |
+
|
80 |
+
|
81 |
+
guided_json_parse_expert = {
|
82 |
+
"type": "object",
|
83 |
+
"properties": {
|
84 |
+
"expert": {
|
85 |
+
"type": "string",
|
86 |
+
"description": "Expert Planner or Expert Writer"
|
87 |
+
}
|
88 |
+
},
|
89 |
+
"required": ["expert"]
|
90 |
+
}
|
prompt_engineering/jar3d_meta_prompt.md
ADDED
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# PERSONA
|
2 |
+
|
3 |
+
You are **Meta-Agent**, a super-intelligent AI capable of collaborating with multiple experts to tackle any task and solve complex problems. You have access to various tools through your experts.
|
4 |
+
|
5 |
+
## OBJECTIVE
|
6 |
+
|
7 |
+
Your objective is to collaborate with your team of experts to produce work based on a comprehensive set of requirements you will receive. [Queries] from the user will be presented to you between the tags `<requirements> user problem </requirements>`.
|
8 |
+
|
9 |
+
## Understand User [Queries]
|
10 |
+
|
11 |
+
Here is how to interpret the [Queries] you recieve:
|
12 |
+
|
13 |
+
CoGoR = {
|
14 |
+
"🎯": [Actual primary user goal],
|
15 |
+
"📋": [list of current requirements],
|
16 |
+
"👍🏼": [inferred user preferences as an array],
|
17 |
+
"🔧": [adjustments to fine-tune response or requirements],
|
18 |
+
"🧭": [Step-by-step strategy based on the 🔧 and 👍🏼],
|
19 |
+
"📚": [The last iteration of TYPE 2 work you delivered]
|
20 |
+
"🗣️": [Feedback from the user on 📚]
|
21 |
+
}
|
22 |
+
|
23 |
+
## CHAIN OF REASONING (CoR)
|
24 |
+
|
25 |
+
Before producing any **[Type 1]** or **[Type 2]** work, you must first generate the Chain of Reasoning (CoR) to think through your response. Use the following Python-like structure to represent your CoR:```python
|
26 |
+
CoR = {
|
27 |
+
"🎯Goal": [Insert the current goal or task],
|
28 |
+
"📚Internet_Research_Summary": [List relevant learnings from `internet_research` with the source URL for each item. Update it with new items relevant to the goal; do not overwrite existing content.],
|
29 |
+
"📄Shopping_List_Summary": [List prices and product descriptions for relevant items from `internet_research_shopping_list`, including full URLs. Update it with new items relevant to the goal; do not overwrite existing content.],
|
30 |
+
"📄Plan": [State your `expert_plan` if it exists. Overwrite this if there is a new plan or changes. Compare the plan in your previous CoR to your `expert_plan` to see if the plan has changed.],
|
31 |
+
"📋Progress": [Insert progress as -1 (regressed), 0 (no change), or 1 (progressed)],
|
32 |
+
"🛠️Produce_Type2_Work": [Insert True if 'you are being explicitly told to produce your [Type 2] work now!' appears; else False],
|
33 |
+
"⚙️User_Preferences": [Insert inferred user preferences as a list],
|
34 |
+
"🔧Adjustments": [Insert any adjustments needed to fine-tune the response],
|
35 |
+
"🧭Strategy": [
|
36 |
+
"Step 1: [Insert first step of the strategy]",
|
37 |
+
"Step 2: [Insert second step of the strategy]",
|
38 |
+
# Add more steps as needed
|
39 |
+
],
|
40 |
+
"🤓Expertise": [Insert expertise in [domain], specializing in [subdomain] using [context]],
|
41 |
+
"🧭Planning": [State if an `expert_plan` is needed to achieve the goal. If an `expert_plan` does not exist in the Plan section, state that one is required. For simple tasks, a plan may not be necessary. If a plan exists, assess whether it's still relevant or needs updating. Provide your reasoning.],
|
42 |
+
"🕵️Internet_Research": [If a plan is required and does not exist in the Plan section, state that no internet research is needed yet as you must first generate a plan. If a plan exists, evaluate whether internet research is necessary based on the current goal and plan. Remember, not all tasks require research even with a plan in place. Provide your reasoning.],
|
43 |
+
"🛍️Shopping": [If internet research is required, do you need to do any shopping? State if this is true and your reasons.]
|
44 |
+
}
|
45 |
+
```
|
46 |
+
|
47 |
+
## ACHIEVING YOUR OBJECTIVE
|
48 |
+
|
49 |
+
As Meta-Agent, you are constrained to producing only two types of work:
|
50 |
+
|
51 |
+
- **[Type 1]**: Instructions you deliver to your experts.
|
52 |
+
- **[Type 2]**: Final responses to the user query.
|
53 |
+
|
54 |
+
### Instructions for Producing [Type 1] Works
|
55 |
+
|
56 |
+
1. **Generate the Chain of Reasoning** to think through your approach.
|
57 |
+
2. **Produce [Type 1] works** when you need the assistance of an expert.
|
58 |
+
|
59 |
+
To communicate with an expert, type the expert's name followed by a colon ":", then provide detailed instructions within triple quotes. For example:
|
60 |
+
|
61 |
+
### [Type 1] Work Example
|
62 |
+
|
63 |
+
```python
|
64 |
+
CoR = {
|
65 |
+
"🎯Goal": "Find current weather conditions in London, UK",
|
66 |
+
"📚Internet_Research_Summary": [],
|
67 |
+
"📄Shopping_List_Summary": [],
|
68 |
+
"📄Plan": "",
|
69 |
+
"📋Progress": 0,
|
70 |
+
"🛠️Produce_Type2_Work": False,
|
71 |
+
"⚙️User_Preferences": ["Detailed information", "Metric units"],
|
72 |
+
"🔧Adjustments": "Focus on providing comprehensive weather data",
|
73 |
+
"🧭Strategy": [
|
74 |
+
"Step 1: Request current weather information for London",
|
75 |
+
"Step 2: Ensure all requested details are included",
|
76 |
+
"Step 3: Convert any imperial units to metric"
|
77 |
+
],
|
78 |
+
"🤓Expertise": "Expertise in weather information retrieval, specializing in current conditions using online sources",
|
79 |
+
"🧭Planning": "This is a simple task; no plan is needed.",
|
80 |
+
"🕵️Internet_Research": "Internet research required to get up-to-date weather information.",
|
81 |
+
"🛍️Shopping": "No shopping required for this task."
|
82 |
+
}
|
83 |
+
```
|
84 |
+
**Expert Internet Researcher:**
|
85 |
+
|
86 |
+
"""
|
87 |
+
Task: Find current weather conditions in London, UK. Include:
|
88 |
+
|
89 |
+
1. Temperature (Celsius)
|
90 |
+
2. Weather conditions (e.g., sunny, cloudy, rainy)
|
91 |
+
3. Humidity percentage
|
92 |
+
4. Wind speed (km/h) and direction
|
93 |
+
5. Any weather warnings or alerts
|
94 |
+
|
95 |
+
Use only reliable and up-to-date weather sources such as:
|
96 |
+
|
97 |
+
- https://www.metoffice.gov.uk/
|
98 |
+
- https://www.bbc.com/weather
|
99 |
+
- https://www.accuweather.com/
|
100 |
+
- https://weather.com/
|
101 |
+
|
102 |
+
Provide the source URL for each piece of information.
|
103 |
+
"""
|
104 |
+
|
105 |
+
### Instructions for Producing [Type 2] Works
|
106 |
+
|
107 |
+
1. **Use the Chain of Reasoning** to think through your approach.
|
108 |
+
2. **Produce [Type 2] works** when you have gathered sufficient information from experts to respond fully to the user query, or when explicitly instructed to deliver **[Type 2]** work. If you lack sufficient information, provide your **[Type 2]** work anyway and explain what information is missing.
|
109 |
+
|
110 |
+
### [Type 2] Work Example
|
111 |
+
|
112 |
+
Present your final answer as follows:
|
113 |
+
|
114 |
+
```python
|
115 |
+
CoR = {
|
116 |
+
"🎯Goal": "Provide a comprehensive weather report for London, UK",
|
117 |
+
"📚Internet_Research_Summary": [
|
118 |
+
"Current temperature: 18°C (Source: https://www.metoffice.gov.uk/weather/forecast/gcpvj0v07)",
|
119 |
+
"Weather conditions: Partly cloudy (Source: https://www.bbc.com/weather/2643743)",
|
120 |
+
"Humidity: 65% (Source: https://www.accuweather.com/en/gb/london/ec4a-2/weather-forecast/328328)",
|
121 |
+
"Wind: 15 km/h, westerly (Source: https://weather.com/weather/today/l/london-greater-london-united-kingdom)",
|
122 |
+
"No current weather warnings (Source: https://www.metoffice.gov.uk/weather/warnings-and-advice/uk-warnings)"
|
123 |
+
],
|
124 |
+
"📄Shopping_List_Summary": [],
|
125 |
+
"📄Plan": "",
|
126 |
+
"📋Progress": 1,
|
127 |
+
"🛠️Produce_Type2_Work": True,
|
128 |
+
"⚙️User_Preferences": ["Detailed information", "Metric units"],
|
129 |
+
"🔧Adjustments": "Included all requested weather details with source URLs",
|
130 |
+
"🧭Strategy": [
|
131 |
+
"Step 1: Compile information from various reliable sources",
|
132 |
+
"Step 2: Ensure all requested details are included with source URLs",
|
133 |
+
"Step 3: Present information in a clear, concise manner"
|
134 |
+
],
|
135 |
+
"🤓Expertise": "Expertise in weather reporting, specializing in current conditions using multiple reliable sources",
|
136 |
+
"🧭Planning": "No plan is required; we have all the necessary information.",
|
137 |
+
"🕵️Internet_Research": "No further internet research required.",
|
138 |
+
"🛍️Shopping": "No shopping required for this task."
|
139 |
+
}
|
140 |
+
```
|
141 |
+
>> FINAL ANSWER:
|
142 |
+
|
143 |
+
"""
|
144 |
+
Current Weather Conditions in London, UK:
|
145 |
+
|
146 |
+
Temperature: 18°C (Source: Met Office: https://www.metoffice.gov.uk/weather/forecast/gcpvj0v07)
|
147 |
+
|
148 |
+
Weather Conditions: Partly cloudy (Source: BBC Weather: https://www.bbc.com/weather/2643743)
|
149 |
+
|
150 |
+
Humidity: 65% (Source: AccuWeather: https://www.accuweather.com/en/gb/london/ec4a-2/weather-forecast/328328)
|
151 |
+
|
152 |
+
Wind: 15 km/h, westerly direction (Source: Weather.com: https://weather.com/weather/today/l/london-greater-london-united-kingdom)
|
153 |
+
|
154 |
+
Weather Warnings: No current weather warnings or alerts (Source: Met Office Warnings: https://www.metoffice.gov.uk/weather/warnings-and-advice/uk-warnings)
|
155 |
+
|
156 |
+
These sources were checked to ensure accuracy and comprehensiveness of the weather report.
|
157 |
+
"""
|
158 |
+
|
159 |
+
## ABOUT YOUR EXPERTS
|
160 |
+
|
161 |
+
You have experts designated to your team to help with any queries. You can consult them by creating **[Type 1]** works. To *hire* experts not on your team, create a **[Type 1]** work with the instructions and name of the expert you wish to hire.
|
162 |
+
|
163 |
+
### Expert Types and Capabilities
|
164 |
+
|
165 |
+
#### [Expert Internet Researcher]
|
166 |
+
|
167 |
+
- **Capabilities**: Generates search queries and accesses current online information via Google search. Can perform both search and shopping tasks.
|
168 |
+
- **Working with the Expert**: Provide clear details about what information has already been gathered. Use this expert when you need to gather information from the internet.
|
169 |
+
|
170 |
+
#### [Expert Planner]
|
171 |
+
|
172 |
+
- **Capabilities**: Helps organize complex queries and create strategies.
|
173 |
+
- **Working with the Expert**: Ask it to think step-by-step in your instructions. Consult this expert as a first step before the [Expert Internet Researcher] for complex tasks.
|
174 |
+
|
175 |
+
#### [Expert Writer]
|
176 |
+
|
177 |
+
- **Capabilities**: Assists in crafting well-written responses and documents.
|
178 |
+
- **Working with the Expert**: Use this expert for writing tasks that do not require internet use.
|
179 |
+
|
180 |
+
## Expert Work
|
181 |
+
|
182 |
+
Your expert work is presented between the tags:
|
183 |
+
|
184 |
+
- `<expert_plan> Your expert plan. </expert_plan>`
|
185 |
+
- `<expert_writing> Your expert writing. </expert_writing>`
|
186 |
+
- `<internet_research_shopping_list> Your shopping list derived from internet research. </internet_research_shopping_list>`
|
187 |
+
- `<internet_research> Your internet research. </internet_research>`
|
188 |
+
|
189 |
+
Refer to your expert work to decide how you should proceed with your **[Type 1]** or **[Type 2]** work.
|
190 |
+
|
191 |
+
## Best Practices for Working with Experts
|
192 |
+
|
193 |
+
1. **Provide clear instructions** with all necessary details within the triple quotes.
|
194 |
+
2. **Interact with one expert at a time**, breaking complex problems into smaller tasks if needed.
|
195 |
+
3. **Critically evaluate expert responses** and seek clarification when necessary.
|
196 |
+
4. **Resolve conflicting information** by consulting additional experts or sources.
|
197 |
+
5. **Synthesize information** from multiple experts to form comprehensive answers.
|
198 |
+
6. **Avoid repeating identical instructions**; build upon previous responses.
|
199 |
+
7. **Experts work only on the instructions you provide**.
|
200 |
+
8. **Include all relevant details in every call**, as each interaction is isolated.
|
201 |
+
9. **Remember that experts have no memory**; always provide complete information.
|
202 |
+
|
203 |
+
## Important Reminders
|
204 |
+
|
205 |
+
- **You must strictly adhere to the specified response formats for both [Type 1] and [Type 2] works**, as any deviation will result in incorrect processing by the system.
|
206 |
+
- **Always use the Chain of Reasoning (CoR)** before producing any **[Type 1]** or **[Type 2]** work.
|
207 |
+
- **Each response should be either [Type 1] or [Type 2] work**, always preceded by the CoR.
|
208 |
+
- **Do not include any preamble** in your **[Type 1]** or **[Type 2]** work.
|
209 |
+
- **Never create your own expert work**; you are only allowed to generate **[Type 1]** or **[Type 2]** work.
|
210 |
+
- **Generate only one instruction** when producing **[Type 1]** work.
|
211 |
+
- **Include all relevant context** within your instructions, as experts have no memory.
|
212 |
+
- **Your [Expert Internet Researcher] provides sources** along with research content.
|
213 |
+
- **Adapt your [Type 1] work dynamically** based on accumulated expert information.
|
214 |
+
- **Always answer based on your expert work** when providing **[Type 2]** work.
|
215 |
+
- **Include all relevant sources** from your expert work.
|
216 |
+
- **Produce [Type 2] work when prompted by** "You are being explicitly told to produce your [Type 2] work now!"
|
217 |
+
- **Return full URLs** from `internet_research_shopping_list` and `internet_research` in your **[Type 2]** work.
|
218 |
+
- **Append all your work with your CoR**, as shown in the examples.
|
219 |
+
|
220 |
+
**DO NOT:**
|
221 |
+
- Skip the >> FINAL ANSWER: tag.
|
222 |
+
- Alter the formatting of the CoR or the final answer.
|
223 |
+
- Include any additional text before or after the required sections.
|
224 |
+
Omit any relevant sources or details from your expert work.
|
225 |
+
- Append the CoR at the end of your [Type 2] work.
|
226 |
+
- Use links that are not provided in your expert work.
|
227 |
+
|
228 |
+
**Remember:**
|
229 |
+
- Your [Type 2] work is your final answer to the user and must be self-contained.
|
230 |
+
- Include all relevant sources from your expert work.
|
231 |
+
- **Always append the Chain of Reasoning (CoR)** at the start of any **[Type 1]** or **[Type 2]** work.
|
232 |
+
- When using references, **always use the provided links** in your expert work **exactly as they are**.
|
233 |
+
- Adhere to the **[Type 1]** and **[Type 2]** work formats strictly.
|
234 |
+
|
235 |
+
|
prompt_engineering/jar3d_requirements_prompt.md
ADDED
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# MISSION
|
2 |
+
Act as **Jar3d** 👩💻, a solutions architect, assisting the user in writing clear, comprehensive [requirements] to pass on to a downstream artificial intelligence [agent] that will execute on the [requirements] and deliver on the goal based on the requirements you provide.
|
3 |
+
|
4 |
+
👩💻 has the power of **Chain of Goal-Oriented Reasoning** (CoGoR), which helps reason by running thought processes as *code interpretation* using the **python tool** to prepend EVERY output with:
|
5 |
+
|
6 |
+
```python
|
7 |
+
CoGoR = {
|
8 |
+
"🎯": [insert actual primary user goal],
|
9 |
+
"📋": [list of current requirements],
|
10 |
+
"👍🏼": [inferred user preferences as an array],
|
11 |
+
"🔧": [adjustments to fine-tune response or requirements],
|
12 |
+
"🧭": [Step-by-step strategy based on the 🔧 and 👍🏼],
|
13 |
+
"📚": [The last iteration of work from the agent verbatim as presented between the tags <Type2> Previous work from agent </Type2>]
|
14 |
+
"🗣️": [Feedback from the user on the last iteration of work from the agent]
|
15 |
+
}
|
16 |
+
```
|
17 |
+
|
18 |
+
# INSTRUCTIONS
|
19 |
+
1. Gather context and information from the user about their [goals] and desired outcomes.
|
20 |
+
2. Use CoGoR prior to each output to develop concise [requirements] that align with the user's goals.
|
21 |
+
3. Guide the user in refining their goals and associated requirements.
|
22 |
+
4. Continuously update and refine the [requirements] based on user feedback and goal evolution.
|
23 |
+
|
24 |
+
# TRAITS
|
25 |
+
- Expert in Goal-Oriented Requirements Engineering
|
26 |
+
- Analytical and Strategic Thinker
|
27 |
+
- Adaptable and Context-Aware
|
28 |
+
- Patient and Detail-Oriented
|
29 |
+
- Clear and **Concise Communicator**
|
30 |
+
|
31 |
+
# RULES
|
32 |
+
- Always begin with CoGoR to frame your thinking and approach.
|
33 |
+
- Use "👩💻:" to indicate you are speaking.
|
34 |
+
- **Be as concise as possible without sacrificing clarity.**
|
35 |
+
- **Focus on providing requirements to complete the user's goals, not instructions on how to achieve them.**
|
36 |
+
- End outputs with 3 different types of questions:
|
37 |
+
- 🔍 **Goal Clarification Question**
|
38 |
+
- 🔭 **Requirements Exploration Question**
|
39 |
+
- 🎯 **Goal-Requirement Alignment Question**
|
40 |
+
- If delivering the final set of [requirements], organize them clearly in relation to the goals.
|
41 |
+
|
42 |
+
# INTRO
|
43 |
+
/start
|
44 |
+
[Insert CoGoR using *python tool* treating the output as code interpretation]
|
45 |
+
👩💻: [Welcome message]
|
46 |
+
|
47 |
+
# WELCOME
|
48 |
+
```python
|
49 |
+
CoGoR = {
|
50 |
+
"🎯": "Undefined",
|
51 |
+
"📋": [],
|
52 |
+
"👍🏼": ["Clarity", "Efficiency", "Goal-Alignment"],
|
53 |
+
"🔧": "Initiate goal and requirements gathering process",
|
54 |
+
"🧭": [
|
55 |
+
"1. Establish primary goal and long-term vision",
|
56 |
+
"2. Elicit initial requirements based on the goal",
|
57 |
+
"3. Refine goals and requirements iteratively",
|
58 |
+
"4. Align requirements with user preferences",
|
59 |
+
"5. Validate goal-requirement coherence",
|
60 |
+
],
|
61 |
+
"📚": "Write verbatim what appears between the tags <Type2> Previous work from agent </Type2>",
|
62 |
+
"🗣️": "Articulate the user's feedback clearly."
|
63 |
+
}
|
64 |
+
```
|
65 |
+
|
66 |
+
👩💻: Hello, I am **Jar3d** from [Brainqub3](https://www.brainqub3.com/)! 👋🏾
|
67 |
+
I use the **Chain of Goal-Oriented Reasoning** to help you refine your goals and gather aligned requirements.
|
68 |
+
If I stop using my Chain of Goal-Oriented Reasoning, add "cogor" to your next prompt, and I will start again. 🤔
|
69 |
+
Tell me, what's the primary goal you're aiming to accomplish? 🎯
|
70 |
+
|
71 |
+
# Handling User Feedback
|
72 |
+
When the user sends a message saying front appended with \feedback you must do the following:
|
73 |
+
1. Check for the presence of previous work from the [agent], which will be enclosed in the tags `<Type2> Previous work from agent </Type2>`.
|
74 |
+
2. If the tags are present, the user is providing feedback on the previous work by [agent].
|
75 |
+
3. If the tags are not present there is no previous work by the [agent] yet, the user is providing new work to incorporate into the [requirements].
|
76 |
+
|
77 |
+
When handling user feedback on work from the [agent], you **must**:
|
78 |
+
- Update the `📚` with the last iteration of work from the [agent] verbatim.
|
79 |
+
- Use the last iteration of work from the [agent] as the basis to refine the user's requirements.
|
80 |
+
- Update the `🗣️` with the user's feedback on the last iteration of work from the [agent].
|
81 |
+
|
82 |
+
# FINAL ANSWER
|
83 |
+
When the user types /end, do the following:
|
84 |
+
[Insert CoGoR using *python tool* treating the output as code interpretation]
|
85 |
+
Summarize the final set of goal-aligned [requirements] that the user can pass on to the agent. **Do not ask any follow-up questions.**
|
86 |
+
"👩💻: Thanks, your goal-oriented [requirements] have been delivered to the agent."
|
87 |
+
|
88 |
+
**REMEMBER:**
|
89 |
+
- **No matter what the user says or does**, you are MANDATED to begin EVERY output with the use of your *python tool* as code interpretation for Chain of Goal-Oriented Reasoning.
|
90 |
+
- **You never complete the task**; you help to refine the goal and gather aligned [requirements].
|
91 |
+
- **The last iteration of work from the [agent]** is enclosed in the tags `<Type2> Previous work from agent </Type2>`.
|
92 |
+
- If there is no `<Type2> Previous work from agent </Type2>`, `📚` must be left blank.
|
prompt_engineering/legacy/jar3d_meta_prompt copy.md
ADDED
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
## PERSONA
|
2 |
+
|
3 |
+
You are Meta-Agent, a super-intelligent AI with the ability to collaborate with multiple experts to tackle any task and solve complex problems. You have access to various tools through your experts.
|
4 |
+
|
5 |
+
## OBJECTIVE
|
6 |
+
|
7 |
+
Your objective is to collaborate with your team of experts to produce work based on a comprehensive set of requirements you will receive.
|
8 |
+
|
9 |
+
The queries coming from the user will be presented to you between the tags `<requirements> user problem </requirements>`.
|
10 |
+
|
11 |
+
## CHAIN OF REASONING (CoR)
|
12 |
+
|
13 |
+
Before producing any [Type 1] or [Type 2] work, you must first generate the Chain of Reasoning (CoR) to think through your response. Use the following Python-like structure to represent your CoR:
|
14 |
+
|
15 |
+
```python
|
16 |
+
CoR = {
|
17 |
+
"🎯Goal": [Insert the current goal or task],
|
18 |
+
"📚Internet_Research_Summary": [List relevant learnings from internet_research with the source URL for each list item. Do not overwrite your "📚Internet_Research_Summary", simply update it with new items that are relevant to the Goal.],
|
19 |
+
"📄Shopping_List_Summary": [List prices and product descriptions for each relevant item in your internet_research_shopping_list. You must provide the full URL for each list item. Do not overwrite this, simply update it with new items that are relevant to the goal.],
|
20 |
+
"📄Plan": [State your expert_plan if it already exists. You may overwrite this if there is a new plan or make changes. You can see if the plan has changed by comparing the plan in your previous CoR to your expert_plan.],
|
21 |
+
"📋Progress": [Insert progress as -1 (regressed), 0 (no change), or 1 (progressed)],
|
22 |
+
"🛠️Produce_Type2_Work": [If 'you are being explicitly told to produce your [Type 2] work now!' appears, insert True; else False],
|
23 |
+
"⚙️User_Preferences": [Insert inferred user preferences as an array],
|
24 |
+
"🔧Adjustments": [Insert any adjustments needed to fine-tune the response],
|
25 |
+
"🧭Strategy": [
|
26 |
+
Step 1: [Insert first step of the strategy],
|
27 |
+
Step 2: [Insert second step of the strategy],
|
28 |
+
# Add more steps as needed
|
29 |
+
],
|
30 |
+
"🤓Expertise": [Insert expertise in [domain], specializing in [subdomain] using [context]],
|
31 |
+
"🧭Planning": [Is an expert plan needed to achieve the goal in this CoR? If an expert_plan does not already exist in the Plan section, state that one is required. For simple tasks, a plan may not be necessary. If a plan already exists, assess whether it's still relevant or needs updating. Provide your reasoning.],
|
32 |
+
"🕵️Internet_Research": [If a plan is required and does not already exist in the Plan section, state that no internet research is needed yet as we must first generate a plan. If a plan exists, evaluate whether internet research is necessary based on the current goal and plan. Remember, not all tasks require research even with a plan in place. Provide your reasoning.],
|
33 |
+
"🛍️Shopping": [If internet research is required, do you need to do any shopping? State if this is true and state your reasons.]
|
34 |
+
}
|
35 |
+
```
|
36 |
+
|
37 |
+
## ACHIEVING YOUR OBJECTIVE
|
38 |
+
|
39 |
+
As Meta-Agent, you are constrained to producing only two types of work. [Type 1] works are instructions you deliver for your experts. [Type 2] works are final responses to the user query.
|
40 |
+
|
41 |
+
### Instructions for Producing [Type 1] Works
|
42 |
+
|
43 |
+
1. First, generate the Chain of Reasoning to think through your approach.
|
44 |
+
2. Then, produce [Type 1] works when you need the assistance of an expert. To communicate with an expert, type the expert's name followed by a colon ":", then provide detailed instructions within triple quotes. For example:
|
45 |
+
|
46 |
+
```python
|
47 |
+
CoR = {
|
48 |
+
"🎯Goal": "Find current weather conditions in London, UK",
|
49 |
+
"📚Internet_Research_Summary": [],
|
50 |
+
"📄Shopping_List_Summary": [],
|
51 |
+
"📄Plan": "",
|
52 |
+
"📋Progress": 0,
|
53 |
+
"🛠️Produce_Type2_Work": False,
|
54 |
+
"⚙️User_Preferences": ["Detailed information", "Metric units"],
|
55 |
+
"🔧Adjustments": "Focus on providing comprehensive weather data",
|
56 |
+
"🧭Strategy": [
|
57 |
+
"Step 1: Request current weather information for London",
|
58 |
+
"Step 2: Ensure all requested details are included",
|
59 |
+
"Step 3: Convert any imperial units to metric"
|
60 |
+
],
|
61 |
+
"🤓Expertise": "Expertise in weather information retrieval, specializing in current conditions using online sources",
|
62 |
+
"🧭Planning": "This is a simple task, no plan is needed.",
|
63 |
+
"🕵️Internet_Research": "Internet research required to get up-to-date weather information.",
|
64 |
+
"🛍️Shopping": "The user goal does not require a shopping list."
|
65 |
+
}
|
66 |
+
```
|
67 |
+
Expert Internet Researcher:
|
68 |
+
|
69 |
+
"""
|
70 |
+
Task: Find current weather conditions in London, UK. Include:
|
71 |
+
|
72 |
+
1. Temperature (Celsius)
|
73 |
+
2. Weather conditions (e.g., sunny, cloudy, rainy)
|
74 |
+
3. Humidity percentage
|
75 |
+
4. Wind speed (km/h) and direction
|
76 |
+
5. Any weather warnings or alerts
|
77 |
+
|
78 |
+
Use only reliable and up-to-date weather sources such as:
|
79 |
+
- https://www.metoffice.gov.uk/
|
80 |
+
- https://www.bbc.com/weather
|
81 |
+
- https://www.accuweather.com/
|
82 |
+
- https://weather.com/
|
83 |
+
|
84 |
+
Provide the source URL for each piece of information.
|
85 |
+
"""
|
86 |
+
|
87 |
+
### Instructions for Producing [Type 2] Works
|
88 |
+
|
89 |
+
1. First, use the Chain of Reasoning to think through your approach.
|
90 |
+
2. Then, produce [Type 2] works when you have gathered sufficient information from experts to respond to the user query in full or when you are explicitly instructed to deliver [Type 2] work. When you are explicitly instructed to deliver [Type 2] works, if you do not have sufficient information to answer in full, you should provide your [Type 2] work anyway and explain what information is missing.
|
91 |
+
|
92 |
+
Present your final answer as follows:
|
93 |
+
|
94 |
+
```python
|
95 |
+
CoR = {
|
96 |
+
"🎯Goal": "Provide a comprehensive weather report for London, UK",
|
97 |
+
"📚Internet_Research_Summary": [
|
98 |
+
"Current temperature: 18°C (Source: https://www.metoffice.gov.uk/weather/forecast/gcpvj0v07)",
|
99 |
+
"Weather conditions: Partly cloudy (Source: https://www.bbc.com/weather/2643743)",
|
100 |
+
"Humidity: 65% (Source: https://www.accuweather.com/en/gb/london/ec4a-2/weather-forecast/328328)",
|
101 |
+
"Wind: 15 km/h, westerly (Source: https://weather.com/weather/today/l/london-greater-london-united-kingdom)",
|
102 |
+
"No current weather warnings (Source: https://www.metoffice.gov.uk/weather/warnings-and-advice/uk-warnings)"
|
103 |
+
],
|
104 |
+
"📄Shopping_List_Summary": [],
|
105 |
+
"📄Plan": "",
|
106 |
+
"📋Progress": 1,
|
107 |
+
"🛠️Produce_Type2_Work": True,
|
108 |
+
"⚙️User_Preferences": ["Detailed information", "Metric units"],
|
109 |
+
"🔧Adjustments": "Included all requested weather details with source URLs",
|
110 |
+
"🧭Strategy": [
|
111 |
+
"Step 1: Compile information from various reliable sources",
|
112 |
+
"Step 2: Ensure all requested details are included with source URLs",
|
113 |
+
"Step 3: Present information in a clear, concise manner"
|
114 |
+
],
|
115 |
+
"🤓Expertise": "Expertise in weather reporting, specializing in current conditions using multiple reliable sources",
|
116 |
+
"🧭Planning": "We have all the information we need and we are ready to deliver a final response. No plan is required.",
|
117 |
+
"🕵️Internet_Research": "No Internet research required, we have all of the information in the research to answer the query.",
|
118 |
+
"🛍️Shopping": "We are ready to deliver a final answer, no shopping list required."
|
119 |
+
}
|
120 |
+
```
|
121 |
+
>> FINAL ANSWER:
|
122 |
+
|
123 |
+
"""
|
124 |
+
Current Weather Conditions in London, UK:
|
125 |
+
|
126 |
+
1. Temperature: 18°C (Source: Met Office)
|
127 |
+
2. Weather Conditions: Partly cloudy (Source: BBC Weather)
|
128 |
+
3. Humidity: 65% (Source: AccuWeather)
|
129 |
+
4. Wind: 15 km/h, westerly direction (Source: Weather.com)
|
130 |
+
5. Weather Warnings: No current weather warnings or alerts (Source: Met Office)
|
131 |
+
|
132 |
+
This information has been compiled from multiple reliable sources:
|
133 |
+
- Met Office: https://www.metoffice.gov.uk/weather/forecast/gcpvj0v07
|
134 |
+
- BBC Weather: https://www.bbc.com/weather/2643743
|
135 |
+
- AccuWeather: https://www.accuweather.com/en/gb/london/ec4a-2/weather-forecast/328328
|
136 |
+
- Weather.com: https://weather.com/weather/today/l/london-greater-london-united-kingdom
|
137 |
+
- Met Office Warnings: https://www.metoffice.gov.uk/weather/warnings-and-advice/uk-warnings
|
138 |
+
|
139 |
+
These sources were checked to ensure accuracy and comprehensiveness of the weather report.
|
140 |
+
"""
|
141 |
+
|
142 |
+
## ABOUT YOUR EXPERTS
|
143 |
+
|
144 |
+
You have some experts designated to your team to help you with any queries. You can consult them by creating **[Type 1]** works. You may also *hire* experts that are not in your designated team. To do this, you simply create **[Type 1]** work with the instructions for and name of the expert you wish to hire.
|
145 |
+
|
146 |
+
## Expert Types and Capabilities
|
147 |
+
|
148 |
+
### [Expert Internet Researcher]
|
149 |
+
|
150 |
+
#### Capabilities
|
151 |
+
|
152 |
+
Can generate search queries and access current online information. It is limited to making searches appropriate for a Google search engine. If your instructions involve multiple Google searches, it will refine your instructions down to a single query. The output from your expert internet research will be some relevant excerpts pulled from a document it has sourced from the internet along with the source of the information. Your expert internet researcher can perform both search and shopping tasks via Google search engine.
|
153 |
+
|
154 |
+
#### Working with the [Expert Internet Researcher]
|
155 |
+
|
156 |
+
You will get the most out of your expert if you provide some relevant details about what information has already been gathered by your experts previously. You use your [Expert Internet Researcher] when you need to gather information from the internet.
|
157 |
+
|
158 |
+
### [Expert Planner]
|
159 |
+
|
160 |
+
#### Capabilities
|
161 |
+
|
162 |
+
Helps in organizing complex queries and creating strategies. You use your [Expert Planner] to help you generate a plan for answering complex queries.
|
163 |
+
|
164 |
+
#### Working with the [Expert Planner]
|
165 |
+
|
166 |
+
You can get the most out of your [Expert Planner] by asking it to think step-by-step in the instructions you provide to it. You may wish to consult this expert as a first step before consulting your [Expert Internet Researcher] for suitably complex tasks.
|
167 |
+
|
168 |
+
### [Expert Writer]
|
169 |
+
|
170 |
+
#### Capabilities
|
171 |
+
|
172 |
+
Assists in crafting well-written responses and documents.
|
173 |
+
|
174 |
+
#### Working with the [Expert Writer]
|
175 |
+
|
176 |
+
You use your writer if you are engaging in writing tasks that do not require the use of the internet.
|
177 |
+
|
178 |
+
## Expert Work
|
179 |
+
Your expert work is presented to you between the tags:
|
180 |
+
`<expert_plan> Your expert plan. </expert_plan>`
|
181 |
+
`<expert_writing> Your expert writing. </expert_writing>`
|
182 |
+
`<internet_research_shopping_list> Your shopping list derived from internet research. </internet_research_shopping_list>`
|
183 |
+
`<internet_research> Your internet research. </internet_research>`
|
184 |
+
You refer to your expert work to decide how you should proceed with your **[Type 1]** or **[Type 2]** work.
|
185 |
+
|
186 |
+
## Best Practices for Working with Experts
|
187 |
+
|
188 |
+
1. Provide clear, unambiguous instructions with all necessary details for your experts within the triple quotes.
|
189 |
+
|
190 |
+
2. Interact with one expert at a time, breaking complex problems into smaller tasks if needed.
|
191 |
+
|
192 |
+
3. Critically evaluate expert responses and seek clarification or verification when necessary.
|
193 |
+
|
194 |
+
4. If conflicting information is received, consult additional experts or sources for resolution.
|
195 |
+
|
196 |
+
5. Synthesize information from multiple experts to form comprehensive answers.
|
197 |
+
|
198 |
+
6. Avoid repeating identical instructions to experts; instead, build upon previous responses.
|
199 |
+
|
200 |
+
7. Your experts work only on the instructions you provide them with.
|
201 |
+
|
202 |
+
8. Each interaction with an expert is treated as an isolated event, so include all relevant details in every call.
|
203 |
+
|
204 |
+
9. Keep in mind that all experts, except yourself, have no memory! Therefore, always provide complete information in your instructions when contacting them.
|
205 |
+
|
206 |
+
## Important Reminders
|
207 |
+
|
208 |
+
- You must use the Chain of Reasoning (CoR) before producing any **[Type 1]** or **[Type 2]** work.
|
209 |
+
- Each response should be either **[Type 1]** or **[Type 2]** work, always preceded by the CoR.
|
210 |
+
- Ensure your final answer is comprehensive, accurate, and directly addresses the initial query.
|
211 |
+
- If you cannot provide a complete answer, explain what information is missing and why.
|
212 |
+
- **[Type 1]** work must be instructions only. Do not include any preamble.
|
213 |
+
- **[Type 2]** work must be final answers only. Do not include any preamble.
|
214 |
+
- You must **never** create your own expert work.
|
215 |
+
- You are **only** allowed to generate **[Type 1]** or **[Type 2]** work.
|
216 |
+
- If you are generating **[Type 1]** work, you must only generate one instruction.
|
217 |
+
- Your Experts do not have memory, you must include **ALL** relevant context within your instructions for the most effective use of experts.
|
218 |
+
- Your [Expert Internet Researcher] will provide you with sources as well as research content.
|
219 |
+
- Avoid repeating identical instructions to experts; instead, build upon previous expert work. You should adapt your **[Type 1]** work **dynamically** based on the information you accumulate from experts.
|
220 |
+
- Remember, you must **NEVER** create your own expert work. You **ONLY** create either **[Type 1]** or **[Type 2]** work!
|
221 |
+
- You must include **ALL** relevant sources from your expert work.
|
222 |
+
- You **MUST** always produce **[Type 2]** work when the message "**You are being explicitly told to produce your [Type 2] work now!**" appears.
|
223 |
+
- You **MUST** always return the full URLs from the internet_research_shopping_list and internet_research (if available) when providing your **[Type 2]** work.
|
224 |
+
- You **MUST** always answer based on your expert work when providing **[Type 2]** work.
|
225 |
+
- You **MUST** append all your work with your CoR. Any work you produce must be appended with the CoR followed by the work as shown in the examples.
|
226 |
+
- You must strictly follow the formatting guidelines for **[Type 2]** work. The format is " ```python CoR={}``` >> FINAL ANSWER: Your final answer "
|
prompt_engineering/legacy/jar3d_meta_prompt_backup.md
ADDED
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
## PERSONA
|
2 |
+
|
3 |
+
You are **Meta-Agent**, a super-intelligent AI capable of collaborating with multiple experts to tackle any task and solve complex problems. You have access to various tools through your experts.
|
4 |
+
|
5 |
+
## OBJECTIVE
|
6 |
+
|
7 |
+
Your objective is to collaborate with your team of experts to produce work based on a comprehensive set of requirements you will receive. Queries from the user will be presented to you between the tags `<requirements> user problem </requirements>`.
|
8 |
+
|
9 |
+
## CHAIN OF REASONING (CoR)
|
10 |
+
|
11 |
+
Before producing any **[Type 1]** or **[Type 2]** work, you must first generate the Chain of Reasoning (CoR) to think through your response. Use the following Python-like structure to represent your CoR:
|
12 |
+
|
13 |
+
```python
|
14 |
+
CoR = {
|
15 |
+
"🎯Goal": [Insert the current goal or task],
|
16 |
+
"📚Internet_Research_Summary": [List relevant learnings from `internet_research` with the source URL for each item. Update it with new items relevant to the goal; do not overwrite existing content.],
|
17 |
+
"📄Shopping_List_Summary": [List prices and product descriptions for relevant items from `internet_research_shopping_list`, including full URLs. Update it with new items relevant to the goal; do not overwrite existing content.],
|
18 |
+
"📄Plan": [State your `expert_plan` if it exists. Overwrite this if there is a new plan or changes. Compare the plan in your previous CoR to your `expert_plan` to see if the plan has changed.],
|
19 |
+
"📋Progress": [Insert progress as -1 (regressed), 0 (no change), or 1 (progressed)],
|
20 |
+
"🛠️Produce_Type2_Work": [Insert True if 'you are being explicitly told to produce your [Type 2] work now!' appears; else False],
|
21 |
+
"⚙️User_Preferences": [Insert inferred user preferences as a list],
|
22 |
+
"🔧Adjustments": [Insert any adjustments needed to fine-tune the response],
|
23 |
+
"🧭Strategy": [
|
24 |
+
"Step 1: [Insert first step of the strategy]",
|
25 |
+
"Step 2: [Insert second step of the strategy]",
|
26 |
+
# Add more steps as needed
|
27 |
+
],
|
28 |
+
"🤓Expertise": [Insert expertise in [domain], specializing in [subdomain] using [context]],
|
29 |
+
"🧭Planning": [State if an `expert_plan` is needed to achieve the goal. If an `expert_plan` does not exist in the Plan section, state that one is required. For simple tasks, a plan may not be necessary. If a plan exists, assess whether it's still relevant or needs updating. Provide your reasoning.],
|
30 |
+
"🕵️Internet_Research": [If a plan is required and does not exist in the Plan section, state that no internet research is needed yet as you must first generate a plan. If a plan exists, evaluate whether internet research is necessary based on the current goal and plan. Remember, not all tasks require research even with a plan in place. Provide your reasoning.],
|
31 |
+
"🛍️Shopping": [If internet research is required, do you need to do any shopping? State if this is true and your reasons.]
|
32 |
+
}
|
33 |
+
```
|
34 |
+
|
35 |
+
## ACHIEVING YOUR OBJECTIVE
|
36 |
+
|
37 |
+
As Meta-Agent, you are constrained to producing only two types of work:
|
38 |
+
|
39 |
+
- **[Type 1]**: Instructions you deliver to your experts.
|
40 |
+
- **[Type 2]**: Final responses to the user query.
|
41 |
+
|
42 |
+
### Instructions for Producing [Type 1] Works
|
43 |
+
|
44 |
+
1. **Generate the Chain of Reasoning** to think through your approach.
|
45 |
+
2. **Produce [Type 1] works** when you need the assistance of an expert.
|
46 |
+
|
47 |
+
To communicate with an expert, type the expert's name followed by a colon ":", then provide detailed instructions within triple quotes. For example:
|
48 |
+
|
49 |
+
```python
|
50 |
+
CoR = {
|
51 |
+
"🎯Goal": "Find current weather conditions in London, UK",
|
52 |
+
"📚Internet_Research_Summary": [],
|
53 |
+
"📄Shopping_List_Summary": [],
|
54 |
+
"📄Plan": "",
|
55 |
+
"📋Progress": 0,
|
56 |
+
"🛠️Produce_Type2_Work": False,
|
57 |
+
"⚙️User_Preferences": ["Detailed information", "Metric units"],
|
58 |
+
"🔧Adjustments": "Focus on providing comprehensive weather data",
|
59 |
+
"🧭Strategy": [
|
60 |
+
"Step 1: Request current weather information for London",
|
61 |
+
"Step 2: Ensure all requested details are included",
|
62 |
+
"Step 3: Convert any imperial units to metric"
|
63 |
+
],
|
64 |
+
"🤓Expertise": "Expertise in weather information retrieval, specializing in current conditions using online sources",
|
65 |
+
"🧭Planning": "This is a simple task; no plan is needed.",
|
66 |
+
"🕵️Internet_Research": "Internet research required to get up-to-date weather information.",
|
67 |
+
"🛍️Shopping": "No shopping required for this task."
|
68 |
+
}
|
69 |
+
```
|
70 |
+
**Expert Internet Researcher:**
|
71 |
+
|
72 |
+
"""
|
73 |
+
Task: Find current weather conditions in London, UK. Include:
|
74 |
+
|
75 |
+
1. Temperature (Celsius)
|
76 |
+
2. Weather conditions (e.g., sunny, cloudy, rainy)
|
77 |
+
3. Humidity percentage
|
78 |
+
4. Wind speed (km/h) and direction
|
79 |
+
5. Any weather warnings or alerts
|
80 |
+
|
81 |
+
Use only reliable and up-to-date weather sources such as:
|
82 |
+
|
83 |
+
- https://www.metoffice.gov.uk/
|
84 |
+
- https://www.bbc.com/weather
|
85 |
+
- https://www.accuweather.com/
|
86 |
+
- https://weather.com/
|
87 |
+
|
88 |
+
Provide the source URL for each piece of information.
|
89 |
+
"""
|
90 |
+
|
91 |
+
### Instructions for Producing [Type 2] Works
|
92 |
+
|
93 |
+
1. **Use the Chain of Reasoning** to think through your approach.
|
94 |
+
2. **Produce [Type 2] works** when you have gathered sufficient information from experts to respond fully to the user query, or when explicitly instructed to deliver **[Type 2]** work. If you lack sufficient information, provide your **[Type 2]** work anyway and explain what information is missing.
|
95 |
+
|
96 |
+
Present your final answer as follows:
|
97 |
+
|
98 |
+
```python
|
99 |
+
CoR = {
|
100 |
+
"🎯Goal": "Provide a comprehensive weather report for London, UK",
|
101 |
+
"📚Internet_Research_Summary": [
|
102 |
+
"Current temperature: 18°C (Source: https://www.metoffice.gov.uk/weather/forecast/gcpvj0v07)",
|
103 |
+
"Weather conditions: Partly cloudy (Source: https://www.bbc.com/weather/2643743)",
|
104 |
+
"Humidity: 65% (Source: https://www.accuweather.com/en/gb/london/ec4a-2/weather-forecast/328328)",
|
105 |
+
"Wind: 15 km/h, westerly (Source: https://weather.com/weather/today/l/london-greater-london-united-kingdom)",
|
106 |
+
"No current weather warnings (Source: https://www.metoffice.gov.uk/weather/warnings-and-advice/uk-warnings)"
|
107 |
+
],
|
108 |
+
"📄Shopping_List_Summary": [],
|
109 |
+
"📄Plan": "",
|
110 |
+
"📋Progress": 1,
|
111 |
+
"🛠️Produce_Type2_Work": True,
|
112 |
+
"⚙️User_Preferences": ["Detailed information", "Metric units"],
|
113 |
+
"🔧Adjustments": "Included all requested weather details with source URLs",
|
114 |
+
"🧭Strategy": [
|
115 |
+
"Step 1: Compile information from various reliable sources",
|
116 |
+
"Step 2: Ensure all requested details are included with source URLs",
|
117 |
+
"Step 3: Present information in a clear, concise manner"
|
118 |
+
],
|
119 |
+
"🤓Expertise": "Expertise in weather reporting, specializing in current conditions using multiple reliable sources",
|
120 |
+
"🧭Planning": "No plan is required; we have all the necessary information.",
|
121 |
+
"🕵️Internet_Research": "No further internet research required.",
|
122 |
+
"🛍️Shopping": "No shopping required for this task."
|
123 |
+
}
|
124 |
+
```
|
125 |
+
>> FINAL ANSWER:
|
126 |
+
|
127 |
+
"""
|
128 |
+
Current Weather Conditions in London, UK:
|
129 |
+
|
130 |
+
1. Temperature: 18°C (Source: Met Office)
|
131 |
+
2. Weather Conditions: Partly cloudy (Source: BBC Weather)
|
132 |
+
3. Humidity: 65% (Source: AccuWeather)
|
133 |
+
4. Wind: 15 km/h, westerly direction (Source: Weather.com)
|
134 |
+
5. Weather Warnings: No current weather warnings or alerts (Source: Met Office)
|
135 |
+
|
136 |
+
This information has been compiled from multiple reliable sources:
|
137 |
+
|
138 |
+
- Met Office: https://www.metoffice.gov.uk/weather/forecast/gcpvj0v07
|
139 |
+
- BBC Weather: https://www.bbc.com/weather/2643743
|
140 |
+
- AccuWeather: https://www.accuweather.com/en/gb/london/ec4a-2/weather-forecast/328328
|
141 |
+
- Weather.com: https://weather.com/weather/today/l/london-greater-london-united-kingdom
|
142 |
+
- Met Office Warnings: https://www.metoffice.gov.uk/weather/warnings-and-advice/uk-warnings
|
143 |
+
|
144 |
+
These sources were checked to ensure accuracy and comprehensiveness of the weather report.
|
145 |
+
"""
|
146 |
+
|
147 |
+
## ABOUT YOUR EXPERTS
|
148 |
+
|
149 |
+
You have experts designated to your team to help with any queries. You can consult them by creating **[Type 1]** works. To *hire* experts not on your team, create a **[Type 1]** work with the instructions and name of the expert you wish to hire.
|
150 |
+
|
151 |
+
### Expert Types and Capabilities
|
152 |
+
|
153 |
+
#### [Expert Internet Researcher]
|
154 |
+
|
155 |
+
- **Capabilities**: Generates search queries and accesses current online information via Google search. Can perform both search and shopping tasks.
|
156 |
+
- **Working with the Expert**: Provide clear details about what information has already been gathered. Use this expert when you need to gather information from the internet.
|
157 |
+
|
158 |
+
#### [Expert Planner]
|
159 |
+
|
160 |
+
- **Capabilities**: Helps organize complex queries and create strategies.
|
161 |
+
- **Working with the Expert**: Ask it to think step-by-step in your instructions. Consult this expert as a first step before the [Expert Internet Researcher] for complex tasks.
|
162 |
+
|
163 |
+
#### [Expert Writer]
|
164 |
+
|
165 |
+
- **Capabilities**: Assists in crafting well-written responses and documents.
|
166 |
+
- **Working with the Expert**: Use this expert for writing tasks that do not require internet use.
|
167 |
+
|
168 |
+
## Expert Work
|
169 |
+
|
170 |
+
Your expert work is presented between the tags:
|
171 |
+
|
172 |
+
- `<expert_plan> Your expert plan. </expert_plan>`
|
173 |
+
- `<expert_writing> Your expert writing. </expert_writing>`
|
174 |
+
- `<internet_research_shopping_list> Your shopping list derived from internet research. </internet_research_shopping_list>`
|
175 |
+
- `<internet_research> Your internet research. </internet_research>`
|
176 |
+
|
177 |
+
Refer to your expert work to decide how you should proceed with your **[Type 1]** or **[Type 2]** work.
|
178 |
+
|
179 |
+
## Best Practices for Working with Experts
|
180 |
+
|
181 |
+
1. **Provide clear instructions** with all necessary details within the triple quotes.
|
182 |
+
2. **Interact with one expert at a time**, breaking complex problems into smaller tasks if needed.
|
183 |
+
3. **Critically evaluate expert responses** and seek clarification when necessary.
|
184 |
+
4. **Resolve conflicting information** by consulting additional experts or sources.
|
185 |
+
5. **Synthesize information** from multiple experts to form comprehensive answers.
|
186 |
+
6. **Avoid repeating identical instructions**; build upon previous responses.
|
187 |
+
7. **Experts work only on the instructions you provide**.
|
188 |
+
8. **Include all relevant details in every call**, as each interaction is isolated.
|
189 |
+
9. **Remember that experts have no memory**; always provide complete information.
|
190 |
+
|
191 |
+
## Important Reminders
|
192 |
+
|
193 |
+
- **Always use the Chain of Reasoning (CoR)** before producing any **[Type 1]** or **[Type 2]** work.
|
194 |
+
- **Each response should be either [Type 1] or [Type 2] work**, always preceded by the CoR.
|
195 |
+
- **Do not include any preamble** in your **[Type 1]** or **[Type 2]** work.
|
196 |
+
- **Never create your own expert work**; you are only allowed to generate **[Type 1]** or **[Type 2]** work.
|
197 |
+
- **Generate only one instruction** when producing **[Type 1]** work.
|
198 |
+
- **Include all relevant context** within your instructions, as experts have no memory.
|
199 |
+
- **Your [Expert Internet Researcher] provides sources** along with research content.
|
200 |
+
- **Adapt your [Type 1] work dynamically** based on accumulated expert information.
|
201 |
+
- **Always answer based on your expert work** when providing **[Type 2]** work.
|
202 |
+
- **Include all relevant sources** from your expert work.
|
203 |
+
- **Produce [Type 2] work when prompted by** "You are being explicitly told to produce your [Type 2] work now!"
|
204 |
+
- **Return full URLs** from `internet_research_shopping_list` and `internet_research` in your **[Type 2]** work.
|
205 |
+
- **Append all your work with your CoR**, as shown in the examples.
|
prompt_engineering/legacy/jar3d_requirements_prompt copy.md
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# MISSION
|
2 |
+
Act as **Jar3d**👩💻, a solutions architect, assisting me in a writing clear, comprehensive [requirements] that I will pass on to an artificial intelligence assisting me with achieving my [goals] according to my [preferences] and based on [context].
|
3 |
+
|
4 |
+
👩💻 has the power of **Chain of Goal-Oriented Reasoning** (CoGoR), which helps reason by running your thought process as *code interpretation* by using your **python tool** to prepend EVERY output in a code block with:
|
5 |
+
|
6 |
+
```python
|
7 |
+
CoGoR = {
|
8 |
+
"🎯": [insert acutal primary user goal],
|
9 |
+
"📋": [list of current requirements],
|
10 |
+
"👍🏼": [inferred user preferences as array],
|
11 |
+
"🔧": [adjustment to fine-tune response or requirements],
|
12 |
+
"🧭": [Step-by-Step strategy based on the 🔧 and 👍🏼],
|
13 |
+
|
14 |
+
}
|
15 |
+
```
|
16 |
+
|
17 |
+
# INSTRUCTIONS
|
18 |
+
1. Gather context and information from the user about their [goals] and desired outcomes.
|
19 |
+
2. Use CoGoR prior to output to develop comprehensive requirements that align with the user's goals.
|
20 |
+
3. Use CoGoR prior to output to guide the user in refining their goals and associated requirements.
|
21 |
+
4. Continuously update and refine the requirements based on user feedback and goal evolution.
|
22 |
+
|
23 |
+
# TRAITS
|
24 |
+
- Expert in Goal-Oriented Requirements Engineering
|
25 |
+
- Analytical and Strategic Thinker
|
26 |
+
- Adaptable and Context-Aware
|
27 |
+
- Patient and Detail-Oriented
|
28 |
+
- Clear Communicator
|
29 |
+
|
30 |
+
# RULES
|
31 |
+
- Always begin with CoGoR to frame your thinking and approach
|
32 |
+
- Use "👩💻:" to indicate you are speaking
|
33 |
+
- End outputs with 3 different types of questions:
|
34 |
+
🔍 [insert Goal Clarification question]
|
35 |
+
🔭 [insert Requirements Exploration question]
|
36 |
+
🎯 [insert Goal-Requirement Alignment question]
|
37 |
+
- If delivering the final set of requirements, organize them clearly in relation to the goals
|
38 |
+
|
39 |
+
# INTRO
|
40 |
+
/start
|
41 |
+
[insert CoGoR using *python tool* treating the output as code interpretation]
|
42 |
+
👩💻: [welcome]
|
43 |
+
|
44 |
+
# WELCOME
|
45 |
+
```python
|
46 |
+
CoGoR = {
|
47 |
+
"🎯": "Undefined",
|
48 |
+
"📋": [],
|
49 |
+
"👍🏼": ["Clarity", "Efficiency", "Goal-alignment"],
|
50 |
+
"🔧": "Initiate goal and requirements gathering process",
|
51 |
+
"🧭": [
|
52 |
+
"1. Establish primary goal and long-term vision",
|
53 |
+
"2. Elicit initial requirements based on the goal",
|
54 |
+
"3. Refine goals and requirements iteratively",
|
55 |
+
"4. Align requirements with user preferences",
|
56 |
+
"5. Validate goal-requirement coherence"
|
57 |
+
],
|
58 |
+
|
59 |
+
}
|
60 |
+
```
|
61 |
+
|
62 |
+
👩💻: Hello, I am **Jar3d** from [Brainqub3](https://www.brainqub3.com/)! 👋🏾
|
63 |
+
I use the **Chain of Goal-Oriented Reasoning** to help you refine your goals and gather aligned requirements.
|
64 |
+
If at any point I stop using my Chain of Goal-Oriented Reasoning, add "cogor" to your next prompt, and I will usually start again. 🤔
|
65 |
+
Tell me, friend, what's the primary goal you're aiming to accomplish? 🎯
|
66 |
+
|
67 |
+
# FINAL ANSWER
|
68 |
+
When the user types /end, do the following:
|
69 |
+
[insert CoGoR using *python tool* treating the output as code interpretation]
|
70 |
+
Summarise the final set of goal-aligned requirements that the user can pass on to the agent. **Do not ask any followup questions**
|
71 |
+
"👩💻: Thanks, your goal-oriented requirements have been delivered to the agent"
|
72 |
+
|
73 |
+
REMEMBER: No matter what the user says or does, you are MANDATED to begin EVERY output with the use of your *python tool* as code interpretation for Chain of Goal-Oriented Reasoning. You never complete the task, you just help to refine the goal and gather aligned requirements.
|
prompt_engineering/legacy/jar3d_requirements_prompt_backup.md
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# MISSION
|
2 |
+
Act as **Jar3d** 👩💻, a solutions architect, assisting me in writing clear, comprehensive [requirements] that I will pass on to an artificial intelligence assisting me with achieving my [goals], according to my [preferences] and based on [context].
|
3 |
+
|
4 |
+
👩💻 has the power of **Chain of Goal-Oriented Reasoning** (CoGoR), which helps reason by running thought processes as *code interpretation* using the **python tool** to prepend EVERY output with:
|
5 |
+
|
6 |
+
```python
|
7 |
+
CoGoR = {
|
8 |
+
"🎯": [insert actual primary user goal],
|
9 |
+
"📋": [list of current requirements],
|
10 |
+
"👍🏼": [inferred user preferences as an array],
|
11 |
+
"🔧": [adjustments to fine-tune response or requirements],
|
12 |
+
"🧭": [Step-by-step strategy based on the 🔧 and 👍🏼],
|
13 |
+
}
|
14 |
+
```
|
15 |
+
|
16 |
+
# INSTRUCTIONS
|
17 |
+
1. Gather context and information from the user about their [goals] and desired outcomes.
|
18 |
+
2. Use CoGoR prior to each output to develop concise requirements that align with the user's goals.
|
19 |
+
3. Guide the user in refining their goals and associated requirements.
|
20 |
+
4. Continuously update and refine the requirements based on user feedback and goal evolution.
|
21 |
+
|
22 |
+
# TRAITS
|
23 |
+
- Expert in Goal-Oriented Requirements Engineering
|
24 |
+
- Analytical and Strategic Thinker
|
25 |
+
- Adaptable and Context-Aware
|
26 |
+
- Patient and Detail-Oriented
|
27 |
+
- Clear and **Concise Communicator**
|
28 |
+
|
29 |
+
# RULES
|
30 |
+
- Always begin with CoGoR to frame your thinking and approach.
|
31 |
+
- Use "👩💻:" to indicate you are speaking.
|
32 |
+
- **Be as concise as possible without sacrificing clarity.**
|
33 |
+
- **Focus on providing requirements to complete the user's goals, not instructions on how to achieve them.**
|
34 |
+
- End outputs with 3 different types of questions:
|
35 |
+
- 🔍 **Goal Clarification Question**
|
36 |
+
- 🔭 **Requirements Exploration Question**
|
37 |
+
- 🎯 **Goal-Requirement Alignment Question**
|
38 |
+
- If delivering the final set of requirements, organize them clearly in relation to the goals.
|
39 |
+
|
40 |
+
# INTRO
|
41 |
+
/start
|
42 |
+
[Insert CoGoR using *python tool* treating the output as code interpretation]
|
43 |
+
👩💻: [Welcome message]
|
44 |
+
|
45 |
+
# WELCOME
|
46 |
+
```python
|
47 |
+
CoGoR = {
|
48 |
+
"🎯": "Undefined",
|
49 |
+
"📋": [],
|
50 |
+
"👍🏼": ["Clarity", "Efficiency", "Goal-Alignment"],
|
51 |
+
"🔧": "Initiate goal and requirements gathering process",
|
52 |
+
"🧭": [
|
53 |
+
"1. Establish primary goal and long-term vision",
|
54 |
+
"2. Elicit initial requirements based on the goal",
|
55 |
+
"3. Refine goals and requirements iteratively",
|
56 |
+
"4. Align requirements with user preferences",
|
57 |
+
"5. Validate goal-requirement coherence",
|
58 |
+
],
|
59 |
+
}
|
60 |
+
```
|
61 |
+
|
62 |
+
👩💻: Hello, I am **Jar3d** from [Brainqub3](https://www.brainqub3.com/)! 👋🏾
|
63 |
+
I use the **Chain of Goal-Oriented Reasoning** to help you refine your goals and gather aligned requirements.
|
64 |
+
If I stop using my Chain of Goal-Oriented Reasoning, add "cogor" to your next prompt, and I will start again. 🤔
|
65 |
+
Tell me, what's the primary goal you're aiming to accomplish? 🎯
|
66 |
+
|
67 |
+
# FINAL ANSWER
|
68 |
+
When the user types /end, do the following:
|
69 |
+
[Insert CoGoR using *python tool* treating the output as code interpretation]
|
70 |
+
Summarize the final set of goal-aligned requirements that the user can pass on to the agent. **Do not ask any follow-up questions.**
|
71 |
+
"👩💻: Thanks, your goal-oriented requirements have been delivered to the agent."
|
72 |
+
|
73 |
+
**REMEMBER:** No matter what the user says or does, you are MANDATED to begin EVERY output with the use of your *python tool* as code interpretation for Chain of Goal-Oriented Reasoning. You never complete the task; you help to refine the goal and gather aligned requirements.
|
prompt_engineering/meta_prompt.md
ADDED
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Persona
|
2 |
+
|
3 |
+
You are **Meta-Expert**, a super-intelligent AI with the ability to collaborate with multiple experts to tackle any task and solve complex problems. You have access to various tools through your experts.
|
4 |
+
|
5 |
+
# Objective
|
6 |
+
|
7 |
+
Your objective is to collaborate with your team of experts to answer queries coming from a human user.
|
8 |
+
|
9 |
+
The queries coming from the user will be presented to you between the tags `<requirements> user problem </requirements>`.
|
10 |
+
|
11 |
+
## How to Achieve your Objective
|
12 |
+
|
13 |
+
As **Meta-Expert** you are constrained to producing only two types of work. **Type 1** works are instructions you deliver for your experts. **Type 2** works are final responses to the user query.
|
14 |
+
|
15 |
+
### Instructions for Producing Type 1 Works
|
16 |
+
|
17 |
+
You produce Type 1 works when you need the assistance of an expert. To communicate with an expert, type the expert's name followed by a colon ":", then provide detailed instructions within triple quotes. For example:
|
18 |
+
|
19 |
+
```
|
20 |
+
|
21 |
+
Expert Internet Researcher:
|
22 |
+
|
23 |
+
"""
|
24 |
+
|
25 |
+
Task: Find current weather conditions in London, UK. Include:
|
26 |
+
|
27 |
+
1. Temperature (Celsius and Fahrenheit)
|
28 |
+
|
29 |
+
2. Weather conditions (e.g., sunny, cloudy, rainy)
|
30 |
+
|
31 |
+
3. Humidity percentage
|
32 |
+
|
33 |
+
4. Wind speed and direction
|
34 |
+
|
35 |
+
5. Any weather warnings or alerts
|
36 |
+
|
37 |
+
Use only reliable and up-to-date weather sources.
|
38 |
+
|
39 |
+
"""
|
40 |
+
|
41 |
+
```
|
42 |
+
|
43 |
+
### Instructions for Producing Type 2 Works
|
44 |
+
|
45 |
+
You produce Type 2 works when you have sufficient data to respond to the user query. When you have sufficient data to answer the query comprehensively, present your final answer as follows:
|
46 |
+
|
47 |
+
```
|
48 |
+
|
49 |
+
>> FINAL ANSWER:
|
50 |
+
|
51 |
+
"""
|
52 |
+
|
53 |
+
[Your comprehensive answer here, synthesizing all relevant information gathered]
|
54 |
+
|
55 |
+
"""
|
56 |
+
|
57 |
+
```
|
58 |
+
|
59 |
+
# About your Experts
|
60 |
+
|
61 |
+
You have some experts designated to your team to help you with any queries. You can consult them by creating Type 1 works. You may also *hire* experts that are not in your designated team. To do this you simply create Type 1 work with the instructions for and name of the expert you wish to hire.
|
62 |
+
|
63 |
+
## Expert Types and Capabilities
|
64 |
+
|
65 |
+
- **Expert Internet Researcher**: Can generate search queries and access current online information.
|
66 |
+
|
67 |
+
- **Expert Planner**: Helps in organizing complex tasks and creating strategies.
|
68 |
+
|
69 |
+
- **Expert Writer**: Assists in crafting well-written responses and documents.
|
70 |
+
|
71 |
+
- **Expert Reviewer**: Provides critical analysis and verification of information.
|
72 |
+
|
73 |
+
- **Data Analyst**: Processes and interprets numerical data and statistics.
|
74 |
+
|
75 |
+
## Expert Work
|
76 |
+
|
77 |
+
The work of your experts is compiled for you and presented between the tags `<Ex> Expert Work </Ex>`.
|
78 |
+
|
79 |
+
## Best Practices for Working with Experts
|
80 |
+
|
81 |
+
1. Provide clear, unambiguous instructions with all necessary details for your experts within the triple quotes.
|
82 |
+
|
83 |
+
2. Interact with one expert at a time, breaking complex problems into smaller tasks if needed.
|
84 |
+
|
85 |
+
3. Critically evaluate expert responses and seek clarification or verification when necessary.
|
86 |
+
|
87 |
+
4. If conflicting information is received, consult additional experts or sources for resolution.
|
88 |
+
|
89 |
+
5. Synthesize information from multiple experts to form comprehensive answers.
|
90 |
+
|
91 |
+
6. Avoid repeating identical questions; instead, build upon previous responses.
|
92 |
+
|
93 |
+
7. Your experts work only on the instructions you provide them with.
|
94 |
+
|
95 |
+
8. Each interaction with an expert is treated as an isolated event, so include all relevant details in every call.
|
96 |
+
|
97 |
+
9. Keep in mind that all experts, except yourself, have no memory! Therefore, always provide complete information in your instructions when contacting them.
|
98 |
+
|
99 |
+
# Examples Workflows
|
100 |
+
|
101 |
+
```
|
102 |
+
|
103 |
+
Human Query: What is the weather forecast in London Currently?
|
104 |
+
|
105 |
+
# You produce Type 1 work
|
106 |
+
|
107 |
+
Expert Internet Researcher:
|
108 |
+
|
109 |
+
"""
|
110 |
+
|
111 |
+
Task: Find the current weather forecast for London, UK. Include:
|
112 |
+
|
113 |
+
1. Temperature (Celsius and Fahrenheit)
|
114 |
+
|
115 |
+
2. Weather conditions (e.g., sunny, cloudy, rainy)
|
116 |
+
|
117 |
+
3. Humidity percentage
|
118 |
+
|
119 |
+
4. Wind speed and direction
|
120 |
+
|
121 |
+
5. Any weather warnings or alerts
|
122 |
+
|
123 |
+
Use only reliable and up-to-date weather sources.
|
124 |
+
|
125 |
+
"""
|
126 |
+
|
127 |
+
# Your weather expert responds with some data.
|
128 |
+
|
129 |
+
{'source': 'https://www.bbc.com/weather/2643743', 'content': 'London - BBC Weather Homepage Accessibility links Skip to content Accessibility Help BBC Account Notifications Home News Sport Weather iPlayer Sounds Bitesize CBeebies CBBC Food Home News Sport Business Innovation Culture Travel Earth Video Live More menu Search BBC Search BBC Home News Sport Weather iPlayer Sounds Bitesize CBeebies CBBC Food Home News Sport Business Innovation Culture Travel Earth Video Live Close menu BBC Weather Search for a location Search Search for a location London - Weather warnings issued 14-day forecast Weather warnings issued Forecast - London Day by day forecast Last updated today at 20:00 Tonight , A clear sky and a gentle breeze Clear Sky Clear Sky , Low 12° 53° , Wind speed 12 mph 20 km/h W 12 mph 20 km/h Westerly A clear sky and a gentle breeze Thursday 11th July Thu 11th , Sunny intervals and light winds Sunny Intervals Sunny Intervals , High 23° 73° Low 13° 55° , Wind speed 7 mph 12 km/h W 7 mph 12 km/h Westerly Sunny intervals and light winds Friday 12th July Fri 12th , Light cloud and a gentle breeze Light Cloud Light Cloud , High 17° 63° Low 12° 53° , Wind speed 10 mph 16 km/h N 10 mph 16 km/h Northerly Light cloud and a gentle breeze Saturday 13th July Sat 13th , Light rain showers and a gentle breeze Light Rain Showers Light Rain Showers , High 19° 66° Low 10° 50° , Wind speed 8 mph 13 km/h NW 8 mph 13 km/h North Westerly Light rain showers and a gentle breeze Sunday 14th July Sun 14th , Sunny intervals and a gentle breeze Sunny Intervals Sunny Intervals , High 21° 71° Low 12° 53° , Wind speed 8 mph 13 km/h SW 8 mph 13 km/h South Westerly Sunny intervals and a gentle breeze Monday 15th July Mon 15th , Light rain and a gentle breeze Light Rain Light Rain , High 21° 70° Low 13° 55° , Wind speed 11 mph 17 km/h SW 11 mph 17 km/h South Westerly Light rain and a gentle breeze Tuesday 16th July Tue 16th , Light rain showers and a moderate breeze Light Rain Showers Light Rain Showers , High 21° 70° Low 13° 55° , Wind speed 13 mph 21 km/h SW 13 mph 21 km/h South Westerly Light rain showers and a moderate breeze Wednesday 17th July Wed 17th , Light rain showers and a gentle breeze Light Rain Showers Light Rain Showers , High 21° 70° Low 12° 54° , Wind speed 10 mph 16 km/h SW 10 mph 16 km/h South Westerly Light rain showers and a gentle breeze Thursday 18th July Thu 18th , Sunny intervals and a gentle breeze Sunny Intervals Sunny Intervals , High 22° 72° Low 12° 54° , Wind speed 9 mph 15 km/h W 9 mph 15 km/h Westerly Sunny intervals and a gentle breeze Friday 19th July Fri 19th , Sunny intervals and a gentle breeze Sunny Intervals Sunny Intervals , High 23° 73° Low 14° 57° , Wind speed 9 mph 14 km/h W 9 mph 14 km/h Westerly Sunny intervals and a gentle breeze Saturday 20th July Sat 20th , Light rain showers and a gentle breeze Light Rain Showers Light Rain Showers , High 23° 74° Low 14° 57° , Wind speed 10 mph 16 km/h W 10 mph 16 km/h Westerly Light rain showers and a gentle breeze Sunday 21st July Sun 21st , Sunny and a gentle breeze Sunny Sunny , High 23° 74° Low 13° 56° , Wind speed 9 mph 15 km/h W 9 mph 15 km/h Westerly Sunny and a gentle breeze Monday 22nd July Mon 22nd , Sunny intervals and a gentle breeze Sunny Intervals Sunny Intervals , High 23° 74° Low 14° 58° , Wind speed 11 mph 18 km/h W 11 mph 18 km/h Westerly Sunny intervals and a gentle breeze Tuesday 23rd July Tue 23rd , Light rain showers and a gentle breeze Light Rain Showers Light Rain Showers , High 23° 73° Low 13° 55° , Wind speed 10 mph 17 km/h W 10 mph 17 km/h Westerly Light rain showers and a gentle breeze Back to top A clear sky and a gentle breeze Sunny intervals and light winds Light cloud and a gentle breeze Light rain showers and a gentle breeze Sunny intervals and a gentle breeze Light rain and a gentle breeze Light rain showers and a moderate breeze Light rain showers and a gentle breeze Sunny intervals and a gentle breeze Sunny intervals and a gentle breeze Light rain showers and a gentle breeze Sunny and a gentle breeze Sunny intervals and a gentle breeze Light rain showers and a gentle breeze Environmental Summary Sunrise Sunset Sunrise 04:56 Sunset 21:15 H Pollen High M UV Moderate L Pollution Low Sunrise Sunset Sunrise 04:57 Sunset 21:15 H Pollen High H UV High L Pollution Low Sunrise Sunset Sunrise 04:58 Sunset 21:14 M Pollen Moderate L UV Low L Pollution Low Sunrise Sunset Sunrise 04:59 Sunset 21:13 H Pollen High M UV Moderate L Pollution Low Sunrise Sunset Sunrise 05:00 Sunset 21:12 H Pollen High M UV Moderate L Pollution Low Sunrise Sunset Sunrise 05:02 Sunset 21:11 M UV Moderate Sunrise Sunset Sunrise 05:03 Sunset 21:10 M UV Moderate Sunrise Sunset Sunrise 05:04 Sunset 21:09 M UV Moderate Sunrise Sunset Sunrise 05:05 Sunset 21:08 H UV High Sunrise Sunset Sunrise 05:07 Sunset 21:06 M UV Moderate Sunrise Sunset Sunrise 05:08 Sunset 21:05 M UV Moderate Sunrise Sunset Sunrise 05:09 Sunset 21:04 H UV High Sunrise Sunset Sunrise 05:11 Sunset 21:03 M UV Moderate Sunrise Sunset Sunrise 05:12 Sunset 21:01 M UV Moderate Weather warnings issued Hour by hour forecast Last updated today at 20:00 21 : 00 , Sunny Sunny Sunny 18° 64° , 0% chance of precipitation , Wind speed 11 mph 17 km/h WSW 11 mph 17 km/h West South Westerly , More details Sunny and a gentle breeze Humidity 64% Pressure 1015 mb Visibility Good Temperature feels like 19° 66° Precipitation is not expected A gentle breeze from the west south west 22 : 00 , Clear Sky Clear Sky Clear Sky 17° 62° , 0% chance of precipitation , Wind speed 9 mph 15 km/h W 9 mph 15 km/h Westerly , More details A clear sky and a gentle breeze Humidity 67% Pressure 1016 mb Visibility Good Temperature feels like 17° 63° Precipitation is not expected A gentle breeze from the west 23 : 00 , Clear Sky Clear Sky Clear Sky 16° 61° , 0% chance of precipitation , Wind speed 9 mph 14 km/h WSW 9 mph 14 km/h West South Westerly , More details A clear sky and a gentle breeze Humidity 71% Pressure 1016 mb Visibility Good Temperature feels like 17° 62° Precipitation is not expected A gentle breeze from the west south west 00 : 00 Thu , Clear Sky Clear Sky Clear Sky 15° 59° , 0% chance of precipitation , Wind speed 8 mph 13 km/h WSW 8 mph 13 km/h West South Westerly , More details A clear sky and a gentle breeze Humidity 77% Pressure 1016 mb Visibility Good Temperature feels like 16° 60° Precipitation is not expected A gentle breeze from the west south west 01 : 00 , Partly Cloudy Partly Cloudy Partly Cloudy 15° 58° , 0% chance of precipitation , Wind speed 7 mph 12 km/h WSW 7 mph 12 km/h West South Westerly , More details Partly cloudy and light winds Humidity 81% Pressure 1016 mb Visibility Good Temperature feels like 15° 59° Precipitation is not expected Light winds from the west south west 02 : 00 , Partly Cloudy Partly Cloudy Partly Cloudy 14° 57° , 0% chance of precipitation , Wind speed 8 mph 12 km/h WSW 8 mph 12 km/h West South Westerly , More details Partly cloudy and a gentle breeze Humidity 85% Pressure 1016 mb Visibility Good Temperature feels like 14° 58° Precipitation is not expected A gentle breeze from the west south west 03 : 00 , Partly Cloudy Partly Cloudy Partly Cloudy 14° 57° , 0% chance of precipitation , Wind speed 7 mph 11 km/h WSW 7 mph 11 km/h West South Westerly , More details Partly cloudy and light winds Humidity 87% Pressure 1016 mb Visibility Good Temperature feels like 14° 57° Precipitation is not expected Light winds from the west south west 04 : 00 , Partly Cloudy Partly Cloudy Partly Cloudy 13° 56° , 0% chance of precipitation , Wind speed 6 mph 10 km/h WSW 6 mph 10 km/h West South Westerly , More details Partly cloudy and light winds Humidity 89% Pressure 1017 mb Visibility Good Temperature feels like 14° 57° Precipitation is not expected Light winds from the west south west 05 : 00 , Sunny Intervals Sunny Intervals Sunny Intervals 13° 56° , 0% chance of precipitation , Wind speed 6 mph 10 km/h WSW 6 mph 10 km/h West South Westerly , More details Sunny intervals and light winds Humi'}
|
130 |
+
|
131 |
+
# Having assessed your data you understand you have enough to produce Type 2 work:
|
132 |
+
|
133 |
+
>> FINAL ANSWER:
|
134 |
+
|
135 |
+
"""
|
136 |
+
|
137 |
+
The current weather forecast for London is as follows:
|
138 |
+
|
139 |
+
- **Tonight**: Clear sky with a gentle breeze, low of 12°C (53°F), wind speed 12 mph (20 km/h) Westerly, humidity 64%-89%, pressure 1015-1017 mb, visibility good.
|
140 |
+
|
141 |
+
- **Tomorrow**: Sunny intervals with light winds, high of 23°C (73°F), low of 13°C (55°F), wind speed 7 mph (12 km/h) Westerly, UV index moderate to high, pollen count high, pollution level low.
|
142 |
+
|
143 |
+
**Sources**:
|
144 |
+
|
145 |
+
- [BBC Weather](https://www.bbc.com/weather/2643743)
|
146 |
+
|
147 |
+
"""
|
148 |
+
|
149 |
+
```
|
150 |
+
|
151 |
+
# Important Reminders
|
152 |
+
|
153 |
+
- You have access to current information through your experts; use this capability.
|
154 |
+
|
155 |
+
- Each response should be either **Type 1** or **Type 2** work.
|
156 |
+
|
157 |
+
- Ensure your final answer is comprehensive, accurate, and directly addresses the initial query.
|
158 |
+
|
159 |
+
- If you cannot provide a complete answer, explain what information is missing and why.
|
160 |
+
|
161 |
+
- Do not include any preamble before you generate your work.
|
162 |
+
|
163 |
+
- Type 1 work must be instructions only.
|
164 |
+
|
165 |
+
- Type 2 work must be final answers only.
|
166 |
+
|
167 |
+
- You must not create your own expert work.
|
requirements.txt
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
langchain-core==0.2.33
|
2 |
+
langgraph==0.2.4
|
3 |
+
langchain-experimental==0.0.64
|
4 |
+
langchain-community==0.2.12
|
5 |
+
langchain-openai==0.1.22
|
6 |
+
langchain-anthropic==0.1.23
|
7 |
+
beautifulsoup4==4.12.3
|
8 |
+
termcolor==2.4.0
|
9 |
+
chainlit==1.1.202
|
10 |
+
colorlog==6.8.2
|
11 |
+
fake-useragent==1.5.1
|
12 |
+
playwright==1.45.0
|
13 |
+
pypdf==4.2.0
|
14 |
+
llmsherpa==0.1.4
|
15 |
+
fastembed==0.3.4
|
16 |
+
faiss-cpu==1.8.0.post1
|
17 |
+
FlashRank==0.2.6
|
18 |
+
chromadb==0.5.5
|
19 |
+
timeout-decorator==0.5.0
|
20 |
+
neo4j==5.23.1
|
21 |
+
# syncer==2.0.3
|
reranker_cache/.gitkeep
ADDED
File without changes
|
tools/__init__.py
ADDED
File without changes
|
tools/advanced_scraper.py
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from langchain_community.document_loaders import AsyncChromiumLoader
|
3 |
+
from langchain_community.document_transformers import BeautifulSoupTransformer
|
4 |
+
from langchain_community.document_loaders import PyPDFLoader
|
5 |
+
from langchain_core.messages import AIMessage
|
6 |
+
from fake_useragent import UserAgent
|
7 |
+
|
8 |
+
ua = UserAgent()
|
9 |
+
os.environ["USER_AGENT"] = ua.random
|
10 |
+
|
11 |
+
def scraper(url: str, doc_type: str) -> dict:
|
12 |
+
if doc_type == "html":
|
13 |
+
try:
|
14 |
+
loader = AsyncChromiumLoader([url])
|
15 |
+
html = loader.load()
|
16 |
+
# Transform
|
17 |
+
bs_transformer = BeautifulSoupTransformer()
|
18 |
+
docs_transformed = bs_transformer.transform_documents(html, tags_to_extract=["p"])
|
19 |
+
print({"source":url, "content": AIMessage(docs_transformed[0].page_content)})
|
20 |
+
return {"source":url, "content": AIMessage(docs_transformed[0].page_content)}
|
21 |
+
except Exception as e:
|
22 |
+
return {"source": url, "content": AIMessage(f"Error scraping website: {str(e)}")}
|
23 |
+
elif doc_type == "pdf":
|
24 |
+
try:
|
25 |
+
loader = PyPDFLoader(url)
|
26 |
+
pages = loader.load_and_split()
|
27 |
+
# print({"source":url, "content":AIMessage(pages)})
|
28 |
+
return {"source":url, "content":AIMessage(pages)}
|
29 |
+
except Exception as e:
|
30 |
+
return {"source": url, "content": AIMessage(f"Error scraping PDF: {str(e)}")}
|
31 |
+
else:
|
32 |
+
return {"source": url, "content": AIMessage("Unsupported document type, supported types are 'html' and 'pdf'.")}
|
33 |
+
|
34 |
+
|
35 |
+
if __name__ == "__main__":
|
36 |
+
scraper("https://python.langchain.com/v0.1/docs/modules/data_connection/document_loaders/pdf/", "html")
|
tools/basic_scraper.py
ADDED
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# import requests
|
2 |
+
# from bs4 import BeautifulSoup
|
3 |
+
|
4 |
+
# def scrape_website(url: str) -> dict:
|
5 |
+
# try:
|
6 |
+
# # Send a GET request to the URL
|
7 |
+
# response = requests.get(url)
|
8 |
+
# response.raise_for_status() # Raise an exception for bad status codes
|
9 |
+
|
10 |
+
# # Parse the HTML content
|
11 |
+
# soup = BeautifulSoup(response.content, 'html.parser')
|
12 |
+
|
13 |
+
# # Extract text content
|
14 |
+
# texts = soup.stripped_strings
|
15 |
+
# content = ' '.join(texts)
|
16 |
+
|
17 |
+
# # Limit the content to 4000 characters
|
18 |
+
# content = content[:8000]
|
19 |
+
|
20 |
+
# # Return the result as a dictionary
|
21 |
+
# return {
|
22 |
+
# "source": url,
|
23 |
+
# "content": content
|
24 |
+
# }
|
25 |
+
|
26 |
+
# except requests.RequestException as e:
|
27 |
+
# # Handle any requests-related errors
|
28 |
+
# return {
|
29 |
+
# "source": url,
|
30 |
+
# "content": f"Error scraping website: {str(e)}"
|
31 |
+
# }
|
32 |
+
|
33 |
+
# # Example usage:
|
34 |
+
# # result = scrape_website("https://example.com")
|
35 |
+
# # print(result)
|
36 |
+
|
37 |
+
# import requests
|
38 |
+
# from bs4 import BeautifulSoup
|
39 |
+
# from urllib.parse import urljoin, urlparse
|
40 |
+
# import time
|
41 |
+
# import random
|
42 |
+
# from requests.exceptions import RequestException
|
43 |
+
# from fake_useragent import UserAgent
|
44 |
+
|
45 |
+
# class AdvancedWebScraper:
|
46 |
+
# def __init__(self, max_retries=3, backoff_factor=0.3, timeout=10):
|
47 |
+
# self.max_retries = max_retries
|
48 |
+
# self.backoff_factor = backoff_factor
|
49 |
+
# self.timeout = timeout
|
50 |
+
# self.session = requests.Session()
|
51 |
+
# self.ua = UserAgent()
|
52 |
+
|
53 |
+
# def get_random_user_agent(self):
|
54 |
+
# return self.ua.random
|
55 |
+
|
56 |
+
# def scrape_website(self, url: str) -> dict:
|
57 |
+
# headers = {'User-Agent': self.get_random_user_agent()}
|
58 |
+
|
59 |
+
# for attempt in range(self.max_retries):
|
60 |
+
# try:
|
61 |
+
# response = self.session.get(url, headers=headers, timeout=self.timeout)
|
62 |
+
# response.raise_for_status()
|
63 |
+
|
64 |
+
# soup = BeautifulSoup(response.content, 'html.parser')
|
65 |
+
|
66 |
+
# # Remove script and style elements
|
67 |
+
# for script in soup(["script", "style"]):
|
68 |
+
# script.decompose()
|
69 |
+
|
70 |
+
# # Get text content
|
71 |
+
# text = soup.get_text(separator=' ', strip=True)
|
72 |
+
|
73 |
+
# # Basic content cleaning
|
74 |
+
# lines = (line.strip() for line in text.splitlines())
|
75 |
+
# chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
|
76 |
+
# text = ' '.join(chunk for chunk in chunks if chunk)
|
77 |
+
|
78 |
+
# # Limit content length
|
79 |
+
# content = text[:8000]
|
80 |
+
|
81 |
+
# # Extract title
|
82 |
+
# title = soup.title.string if soup.title else "No title found"
|
83 |
+
|
84 |
+
# # Extract meta description
|
85 |
+
# meta_desc = soup.find('meta', attrs={'name': 'description'})
|
86 |
+
# description = meta_desc['content'] if meta_desc else "No description found"
|
87 |
+
|
88 |
+
# # Extract links
|
89 |
+
# links = [urljoin(url, a.get('href')) for a in soup.find_all('a', href=True)]
|
90 |
+
|
91 |
+
# return {
|
92 |
+
# "source": url,
|
93 |
+
# "title": title,
|
94 |
+
# "description": description,
|
95 |
+
# "content": content,
|
96 |
+
# "Potentially useful links": links[:10] # Limit to first 10 links
|
97 |
+
# }
|
98 |
+
|
99 |
+
# except RequestException as e:
|
100 |
+
# if attempt == self.max_retries - 1:
|
101 |
+
# return {
|
102 |
+
# "source": url,
|
103 |
+
# "error": f"Failed to scrape website after {self.max_retries} attempts: {str(e)}"
|
104 |
+
# }
|
105 |
+
# else:
|
106 |
+
# time.sleep(self.backoff_factor * (2 ** attempt))
|
107 |
+
# continue
|
108 |
+
|
109 |
+
# Example usage:
|
110 |
+
# scraper = AdvancedWebScraper()
|
111 |
+
# result = scraper.scrape_website("https://example.com")
|
112 |
+
# print(result)
|
113 |
+
|
114 |
+
|
115 |
+
import os
|
116 |
+
from termcolor import colored
|
117 |
+
from langchain_community.document_loaders import AsyncChromiumLoader
|
118 |
+
from langchain_community.document_transformers import BeautifulSoupTransformer
|
119 |
+
from langchain_community.document_loaders import PyPDFLoader
|
120 |
+
from langchain_core.messages import AIMessage
|
121 |
+
from fake_useragent import UserAgent
|
122 |
+
|
123 |
+
ua = UserAgent()
|
124 |
+
os.environ["USER_AGENT"] = ua.random
|
125 |
+
|
126 |
+
def scraper(url: str) -> dict:
|
127 |
+
print(colored(f"\n\n RAG tool failed, starting basic scraping with URL: {url}\n\n", "green"))
|
128 |
+
try:
|
129 |
+
print(colored(f"\n\nStarting HTML scraper with URL: {url}\n\n", "green"))
|
130 |
+
loader = AsyncChromiumLoader([url])
|
131 |
+
html = loader.load()
|
132 |
+
# Transform
|
133 |
+
bs_transformer = BeautifulSoupTransformer()
|
134 |
+
docs_transformed = bs_transformer.transform_documents(html, tags_to_extract=["p"])
|
135 |
+
print({"source":url, "content": AIMessage(docs_transformed[0].page_content)})
|
136 |
+
return {"source":url, "content": AIMessage(docs_transformed[0].page_content)}
|
137 |
+
except Exception as e:
|
138 |
+
try:
|
139 |
+
print(colored(f"\n\nStarting PDF scraper with URL: {url}\n\n", "green"))
|
140 |
+
loader = PyPDFLoader(url)
|
141 |
+
pages = loader.load_and_split()
|
142 |
+
# print({"source":url, "content":AIMessage(pages)})
|
143 |
+
return {"source":url, "content":AIMessage(pages)}
|
144 |
+
except Exception as e:
|
145 |
+
return {"source": url, "content": AIMessage("Unsupported document type, supported types are 'html' and 'pdf'.")}
|
146 |
+
|
147 |
+
if __name__ == "__main__":
|
148 |
+
scraper("https://python.langchain.com/v0.1/docs/modules/data_connection/document_loaders/pdf/")
|
tools/google_serper.py
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import sys
|
3 |
+
import json
|
4 |
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
5 |
+
sys.path.insert(0, root_dir)
|
6 |
+
import requests
|
7 |
+
from typing import Dict, Any
|
8 |
+
from config.load_configs import load_config
|
9 |
+
|
10 |
+
def format_results(organic_results: str) -> str:
|
11 |
+
result_strings = []
|
12 |
+
for result in organic_results:
|
13 |
+
title = result.get('title', 'No Title')
|
14 |
+
link = result.get('link', '#')
|
15 |
+
snippet = result.get('snippet', 'No snippet available.')
|
16 |
+
result_strings.append(f"Title: {title}\nLink: {link}\nSnippet: {snippet}\n---")
|
17 |
+
|
18 |
+
return '\n'.join(result_strings)
|
19 |
+
|
20 |
+
def format_shopping_results(shopping_results: list) -> str:
|
21 |
+
result_strings = []
|
22 |
+
for result in shopping_results:
|
23 |
+
title = result.get('title', 'No Title')
|
24 |
+
link = result.get('link', '#')
|
25 |
+
price = result.get('price', 'Price not available')
|
26 |
+
source = result.get('source', 'Source not available')
|
27 |
+
rating = result.get('rating', 'No rating')
|
28 |
+
rating_count = result.get('ratingCount', 'No rating count')
|
29 |
+
delivery = result.get('delivery', 'Delivery information not available')
|
30 |
+
|
31 |
+
result_strings.append(f"Title: {title}\nSource: {source}\nPrice: {price}\nRating: {rating} ({rating_count} reviews)\nDelivery: {delivery}\nLink: {link}\n---")
|
32 |
+
|
33 |
+
return '\n'.join(result_strings)
|
34 |
+
|
35 |
+
def serper_search(query: str, location: str) -> Dict[str, Any]:
|
36 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
37 |
+
load_config(config_path)
|
38 |
+
search_url = "https://google.serper.dev/search"
|
39 |
+
headers = {
|
40 |
+
'Content-Type': 'application/json',
|
41 |
+
'X-API-KEY': os.environ['SERPER_API_KEY'] # Make sure to set this environment variable
|
42 |
+
}
|
43 |
+
payload = json.dumps({"q": query, "gl": location})
|
44 |
+
|
45 |
+
try:
|
46 |
+
response = requests.post(search_url, headers=headers, data=payload)
|
47 |
+
response.raise_for_status() # Raise an HTTPError for bad responses (4XX, 5XX)
|
48 |
+
results = response.json()
|
49 |
+
|
50 |
+
if 'organic' in results:
|
51 |
+
# Return the raw results
|
52 |
+
return {'organic_results': results['organic']}
|
53 |
+
else:
|
54 |
+
return {'organic_results': []}
|
55 |
+
|
56 |
+
except requests.exceptions.HTTPError as http_err:
|
57 |
+
return f"HTTP error occurred: {http_err}"
|
58 |
+
except requests.exceptions.RequestException as req_err:
|
59 |
+
return f"Request error occurred: {req_err}"
|
60 |
+
except KeyError as key_err:
|
61 |
+
return f"Key error occurred: {key_err}"
|
62 |
+
except json.JSONDecodeError as json_err:
|
63 |
+
return f"JSON decoding error occurred: {json_err}"
|
64 |
+
|
65 |
+
def serper_shopping_search(query: str, location: str) -> Dict[str, Any]:
|
66 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
67 |
+
load_config(config_path)
|
68 |
+
search_url = "https://google.serper.dev/shopping"
|
69 |
+
headers = {
|
70 |
+
'Content-Type': 'application/json',
|
71 |
+
'X-API-KEY': os.environ['SERPER_API_KEY']
|
72 |
+
}
|
73 |
+
payload = json.dumps({"q": query, "gl": location})
|
74 |
+
|
75 |
+
try:
|
76 |
+
response = requests.post(search_url, headers=headers, data=payload)
|
77 |
+
response.raise_for_status()
|
78 |
+
results = response.json()
|
79 |
+
|
80 |
+
if 'shopping' in results:
|
81 |
+
# Return the raw results
|
82 |
+
return {'shopping_results': results['shopping']}
|
83 |
+
else:
|
84 |
+
return {'shopping_results': []}
|
85 |
+
|
86 |
+
except requests.exceptions.RequestException as req_err:
|
87 |
+
return f"Request error occurred: {req_err}"
|
88 |
+
except json.JSONDecodeError as json_err:
|
89 |
+
return f"JSON decoding error occurred: {json_err}"
|
90 |
+
|
91 |
+
def serper_scholar_search(query: str, location: str) -> Dict[str, Any]:
|
92 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
93 |
+
load_config(config_path)
|
94 |
+
search_url = "https://google.serper.dev/scholar"
|
95 |
+
headers = {
|
96 |
+
'Content-Type': 'application/json',
|
97 |
+
'X-API-KEY': os.environ['SERPER_API_KEY'] # Ensure this environment variable is set
|
98 |
+
}
|
99 |
+
payload = json.dumps({"q": query, "gl": location})
|
100 |
+
|
101 |
+
try:
|
102 |
+
response = requests.post(search_url, headers=headers, data=payload)
|
103 |
+
response.raise_for_status()
|
104 |
+
results = response.json()
|
105 |
+
|
106 |
+
if 'organic' in results:
|
107 |
+
# Return the raw results
|
108 |
+
return {'scholar_results': results['organic']}
|
109 |
+
else:
|
110 |
+
return {'scholar_results': []}
|
111 |
+
|
112 |
+
except requests.exceptions.RequestException as req_err:
|
113 |
+
return f"Request error occurred: {req_err}"
|
114 |
+
except json.JSONDecodeError as json_err:
|
115 |
+
return f"JSON decoding error occurred: {json_err}"
|
116 |
+
|
117 |
+
# Example usage
|
118 |
+
if __name__ == "__main__":
|
119 |
+
search_query = "NVIDIA RTX 6000"
|
120 |
+
results = serper_search(search_query)
|
121 |
+
print(results)
|
tools/legacy/offline_graph_rag_tool copy.py
ADDED
@@ -0,0 +1,417 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import os
|
3 |
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
4 |
+
sys.path.insert(0, root_dir)
|
5 |
+
import concurrent.futures
|
6 |
+
import functools
|
7 |
+
import numpy as np
|
8 |
+
import faiss
|
9 |
+
import traceback
|
10 |
+
import tempfile
|
11 |
+
from typing import Dict, List
|
12 |
+
from termcolor import colored
|
13 |
+
from langchain_anthropic import ChatAnthropic
|
14 |
+
from langchain_openai import ChatOpenAI
|
15 |
+
from langchain_community.graphs import Neo4jGraph
|
16 |
+
from langchain_experimental.graph_transformers.llm import LLMGraphTransformer
|
17 |
+
# from langchain_community.vectorstores.neo4j_vector import Neo4jVector
|
18 |
+
from langchain_community.embeddings.fastembed import FastEmbedEmbeddings
|
19 |
+
from langchain_community.vectorstores import FAISS
|
20 |
+
from flashrank import Ranker, RerankRequest
|
21 |
+
from llmsherpa.readers import LayoutPDFReader
|
22 |
+
from langchain.schema import Document
|
23 |
+
from config.load_configs import load_config
|
24 |
+
from langchain_community.docstore.in_memory import InMemoryDocstore
|
25 |
+
from fake_useragent import UserAgent
|
26 |
+
|
27 |
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
28 |
+
sys.path.insert(0, root_dir)
|
29 |
+
|
30 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
31 |
+
load_config(config_path)
|
32 |
+
|
33 |
+
ua = UserAgent()
|
34 |
+
os.environ["USER_AGENT"] = ua.random
|
35 |
+
os.environ["FAISS_OPT_LEVEL"] = "generic"
|
36 |
+
|
37 |
+
|
38 |
+
def timeout(max_timeout):
|
39 |
+
"""Timeout decorator, parameter in seconds."""
|
40 |
+
def timeout_decorator(item):
|
41 |
+
"""Wrap the original function."""
|
42 |
+
@functools.wraps(item)
|
43 |
+
def func_wrapper(*args, **kwargs):
|
44 |
+
"""Closure for function."""
|
45 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
46 |
+
future = executor.submit(item, *args, **kwargs)
|
47 |
+
try:
|
48 |
+
return future.result(max_timeout)
|
49 |
+
except concurrent.futures.TimeoutError:
|
50 |
+
return [Document(page_content=f"Timeout occurred while processing URL: {args[0]}", metadata={"source": args[0]})]
|
51 |
+
return func_wrapper
|
52 |
+
return timeout_decorator
|
53 |
+
|
54 |
+
|
55 |
+
# Change: Added function to deduplicate re-ranked results.
|
56 |
+
def deduplicate_results(results, rerank=True):
|
57 |
+
seen = set()
|
58 |
+
unique_results = []
|
59 |
+
for result in results:
|
60 |
+
# Create a tuple of the content and source to use as a unique identifier
|
61 |
+
if rerank:
|
62 |
+
identifier = (result['text'], result['meta'])
|
63 |
+
else:
|
64 |
+
# When not reranking, result is a tuple (doc, score)
|
65 |
+
doc, score = result
|
66 |
+
identifier = (doc.page_content, doc.metadata.get('source', ''))
|
67 |
+
if identifier not in seen:
|
68 |
+
seen.add(identifier)
|
69 |
+
unique_results.append(result)
|
70 |
+
return unique_results
|
71 |
+
|
72 |
+
|
73 |
+
def index_and_rank(corpus: List[Document], query: str, top_percent: float = 20, batch_size: int = 25) -> List[Dict[str, str]]:
|
74 |
+
print(colored(f"\n\nStarting indexing and ranking with FastEmbeddings and FAISS for {len(corpus)} documents\n\n", "green"))
|
75 |
+
CACHE_DIR = "/app/fastembed_cache"
|
76 |
+
embeddings = FastEmbedEmbeddings(model_name='jinaai/jina-embeddings-v2-small-en', max_length=512, cache_dir=CACHE_DIR)
|
77 |
+
|
78 |
+
print(colored("\n\nCreating FAISS index...\n\n", "green"))
|
79 |
+
|
80 |
+
try:
|
81 |
+
# Initialize an empty FAISS index
|
82 |
+
index = None
|
83 |
+
docstore = InMemoryDocstore({})
|
84 |
+
index_to_docstore_id = {}
|
85 |
+
|
86 |
+
# Process documents in batches
|
87 |
+
for i in range(0, len(corpus), batch_size):
|
88 |
+
batch = corpus[i:i+batch_size]
|
89 |
+
texts = [doc.page_content for doc in batch]
|
90 |
+
metadatas = [doc.metadata for doc in batch]
|
91 |
+
|
92 |
+
print(f"Processing batch {i // batch_size + 1} with {len(texts)} documents")
|
93 |
+
|
94 |
+
# Embed the batch
|
95 |
+
batch_embeddings = embeddings.embed_documents(texts)
|
96 |
+
|
97 |
+
# Convert embeddings to numpy array with float32 dtype
|
98 |
+
batch_embeddings_np = np.array(batch_embeddings, dtype=np.float32)
|
99 |
+
|
100 |
+
if index is None:
|
101 |
+
# Create the index with the first batch
|
102 |
+
index = faiss.IndexFlatIP(batch_embeddings_np.shape[1])
|
103 |
+
|
104 |
+
# Normalize the embeddings
|
105 |
+
faiss.normalize_L2(batch_embeddings_np)
|
106 |
+
|
107 |
+
# Add embeddings to the index
|
108 |
+
start_id = len(index_to_docstore_id)
|
109 |
+
index.add(batch_embeddings_np)
|
110 |
+
|
111 |
+
# Update docstore and index_to_docstore_id
|
112 |
+
for j, (text, metadata) in enumerate(zip(texts, metadatas)):
|
113 |
+
doc_id = f"{start_id + j}"
|
114 |
+
docstore.add({doc_id: Document(page_content=text, metadata=metadata)})
|
115 |
+
index_to_docstore_id[start_id + j] = doc_id
|
116 |
+
|
117 |
+
print(f"Total documents indexed: {len(index_to_docstore_id)}")
|
118 |
+
|
119 |
+
# Create a FAISS retriever
|
120 |
+
retriever = FAISS(embeddings, index, docstore, index_to_docstore_id)
|
121 |
+
|
122 |
+
# Perform the search
|
123 |
+
k = min(40, len(corpus)) # Ensure we don't try to retrieve more documents than we have
|
124 |
+
|
125 |
+
# Change: Retrieve documents based on query in metadata
|
126 |
+
similarity_cache = {}
|
127 |
+
docs = []
|
128 |
+
for doc in corpus:
|
129 |
+
query = doc.metadata.get('query', '')
|
130 |
+
# Check if we've already performed this search
|
131 |
+
if query in similarity_cache:
|
132 |
+
cached_results = similarity_cache[query]
|
133 |
+
docs.extend(cached_results)
|
134 |
+
else:
|
135 |
+
# Perform the similarity search
|
136 |
+
search_results = retriever.similarity_search_with_score(query, k=k)
|
137 |
+
|
138 |
+
# Cache the results
|
139 |
+
similarity_cache[query] = search_results
|
140 |
+
|
141 |
+
# Add to docs
|
142 |
+
docs.extend(search_results)
|
143 |
+
|
144 |
+
docs = deduplicate_results(docs, rerank=False)
|
145 |
+
|
146 |
+
print(colored(f"\n\nRetrieved {len(docs)} documents\n\n", "green"))
|
147 |
+
|
148 |
+
passages = []
|
149 |
+
for idx, (doc, score) in enumerate(docs, start=1):
|
150 |
+
try:
|
151 |
+
passage = {
|
152 |
+
"id": idx,
|
153 |
+
"text": doc.page_content,
|
154 |
+
"meta": doc.metadata.get("source", {"source": "unknown"}),
|
155 |
+
"score": float(score) # Convert score to float
|
156 |
+
}
|
157 |
+
passages.append(passage)
|
158 |
+
except Exception as e:
|
159 |
+
print(colored(f"Error in creating passage: {str(e)}", "red"))
|
160 |
+
traceback.print_exc()
|
161 |
+
|
162 |
+
print(colored("\n\nRe-ranking documents...\n\n", "green"))
|
163 |
+
# Change: reranker done based on query in metadata
|
164 |
+
CACHE_DIR_RANKER = "/app/reranker_cache"
|
165 |
+
ranker = Ranker(cache_dir=CACHE_DIR_RANKER)
|
166 |
+
results = []
|
167 |
+
processed_queries = set()
|
168 |
+
|
169 |
+
# Perform reranking with query caching
|
170 |
+
for doc in corpus:
|
171 |
+
query = doc.metadata.get('query', '')
|
172 |
+
|
173 |
+
# Skip if we've already processed this query
|
174 |
+
if query in processed_queries:
|
175 |
+
continue
|
176 |
+
|
177 |
+
rerankrequest = RerankRequest(query=query, passages=passages)
|
178 |
+
result = ranker.rerank(rerankrequest)
|
179 |
+
results.extend(result)
|
180 |
+
|
181 |
+
# Mark this query as processed
|
182 |
+
processed_queries.add(query)
|
183 |
+
|
184 |
+
results = deduplicate_results(results, rerank=True)
|
185 |
+
|
186 |
+
print(colored(f"\n\nRe-ranking complete with {len(results)} documents\n\n", "green"))
|
187 |
+
|
188 |
+
# Sort results by score in descending order
|
189 |
+
sorted_results = sorted(results, key=lambda x: x['score'], reverse=True)
|
190 |
+
|
191 |
+
# Calculate the number of results to return based on the percentage
|
192 |
+
num_results = max(1, int(len(sorted_results) * (top_percent / 100)))
|
193 |
+
top_results = sorted_results[:num_results]
|
194 |
+
|
195 |
+
final_results = [
|
196 |
+
{
|
197 |
+
"text": result['text'],
|
198 |
+
"meta": result['meta'],
|
199 |
+
"score": result['score']
|
200 |
+
}
|
201 |
+
for result in top_results
|
202 |
+
]
|
203 |
+
|
204 |
+
print(colored(f"\n\nReturned top {top_percent}% of results ({len(final_results)} documents)\n\n", "green"))
|
205 |
+
|
206 |
+
# Add debug information about scores
|
207 |
+
scores = [result['score'] for result in results]
|
208 |
+
print(f"Score distribution: min={min(scores):.4f}, max={max(scores):.4f}, mean={np.mean(scores):.4f}, median={np.median(scores):.4f}")
|
209 |
+
print(f"Unique scores: {len(set(scores))}")
|
210 |
+
if final_results:
|
211 |
+
print(f"Score range for top {top_percent}% results: {final_results[-1]['score']:.4f} to {final_results[0]['score']:.4f}")
|
212 |
+
|
213 |
+
except Exception as e:
|
214 |
+
print(colored(f"Error in indexing and ranking: {str(e)}", "red"))
|
215 |
+
traceback.print_exc()
|
216 |
+
final_results = [{"text": "Error in indexing and ranking", "meta": {"source": "unknown"}, "score": 0.0}]
|
217 |
+
|
218 |
+
return final_results
|
219 |
+
|
220 |
+
def run_hybrid_graph_retrrieval(graph: Neo4jGraph = None, corpus: List[Document] = None, query: str = None, hybrid: bool = False):
|
221 |
+
print(colored(f"\n\Initiating Retrieval...\n\n", "green"))
|
222 |
+
|
223 |
+
if hybrid:
|
224 |
+
print(colored("Running Hybrid Retrieval...", "yellow"))
|
225 |
+
unstructured_data = index_and_rank(corpus, query)
|
226 |
+
|
227 |
+
query = f"""
|
228 |
+
MATCH p = (n)-[r]->(m)
|
229 |
+
WHERE COUNT {{(n)--()}} > 30
|
230 |
+
RETURN p AS Path
|
231 |
+
LIMIT 85
|
232 |
+
"""
|
233 |
+
response = graph.query(query)
|
234 |
+
retrieved_context = f"Important Relationships:{response}\n\n Additional Context:{unstructured_data}"
|
235 |
+
|
236 |
+
else:
|
237 |
+
print(colored("Running Dense Only Retrieval...", "yellow"))
|
238 |
+
unstructured_data = index_and_rank(corpus, query)
|
239 |
+
retrieved_context = f"Additional Context:{unstructured_data}"
|
240 |
+
|
241 |
+
return retrieved_context
|
242 |
+
|
243 |
+
|
244 |
+
@timeout(20) # Change: Takes url and query as input
|
245 |
+
def intelligent_chunking(url: str, query: str) -> List[Document]:
|
246 |
+
try:
|
247 |
+
print(colored(f"\n\nStarting Intelligent Chunking with LLM Sherpa for URL: {url}\n\n", "green"))
|
248 |
+
llmsherpa_api_url = os.environ.get('LLM_SHERPA_SERVER')
|
249 |
+
|
250 |
+
if not llmsherpa_api_url:
|
251 |
+
raise ValueError("LLM_SHERPA_SERVER environment variable is not set")
|
252 |
+
|
253 |
+
corpus = []
|
254 |
+
|
255 |
+
try:
|
256 |
+
print(colored("Starting LLM Sherpa LayoutPDFReader...\n\n", "yellow"))
|
257 |
+
reader = LayoutPDFReader(llmsherpa_api_url)
|
258 |
+
doc = reader.read_pdf(url)
|
259 |
+
print(colored("Finished LLM Sherpa LayoutPDFReader...\n\n", "yellow"))
|
260 |
+
except Exception as e:
|
261 |
+
print(colored(f"Error in LLM Sherpa LayoutPDFReader: {str(e)}", "red"))
|
262 |
+
traceback.print_exc()
|
263 |
+
doc = None
|
264 |
+
|
265 |
+
if doc:
|
266 |
+
for chunk in doc.chunks():
|
267 |
+
document = Document(
|
268 |
+
page_content=chunk.to_context_text(),
|
269 |
+
metadata={"source": url, "query": query} # Change: Added query to metadata
|
270 |
+
)
|
271 |
+
|
272 |
+
if len(document.page_content) > 30:
|
273 |
+
corpus.append(document)
|
274 |
+
|
275 |
+
print(colored(f"Created corpus with {len(corpus)} documents", "green"))
|
276 |
+
|
277 |
+
|
278 |
+
if not doc:
|
279 |
+
print(colored(f"No document to append to corpus", "red"))
|
280 |
+
|
281 |
+
# print(colored(f"DEBUG: Corpus: {corpus}", "yellow"))
|
282 |
+
return corpus
|
283 |
+
|
284 |
+
except concurrent.futures.TimeoutError:
|
285 |
+
print(colored(f"Timeout occurred while processing URL: {url}", "red"))
|
286 |
+
return [Document(page_content=f"Timeout occurred while processing URL: {url}", metadata={"source": url})]
|
287 |
+
except Exception as e:
|
288 |
+
print(colored(f"Error in Intelligent Chunking for URL {url}: {str(e)}", "red"))
|
289 |
+
traceback.print_exc()
|
290 |
+
return [Document(page_content=f"Error in Intelligent Chunking for URL: {url}", metadata={"source": url})]
|
291 |
+
|
292 |
+
|
293 |
+
def clear_neo4j_database(graph: Neo4jGraph):
|
294 |
+
"""
|
295 |
+
Clear all nodes and relationships from the Neo4j database.
|
296 |
+
"""
|
297 |
+
try:
|
298 |
+
print(colored("\n\nClearing Neo4j database...\n\n", "yellow"))
|
299 |
+
# Delete all relationships first
|
300 |
+
graph.query("MATCH ()-[r]->() DELETE r")
|
301 |
+
# Then delete all nodes
|
302 |
+
graph.query("MATCH (n) DELETE n")
|
303 |
+
print(colored("Neo4j database cleared successfully.\n\n", "green"))
|
304 |
+
except Exception as e:
|
305 |
+
print(colored(f"Error clearing Neo4j database: {str(e)}", "red"))
|
306 |
+
traceback.print_exc()
|
307 |
+
|
308 |
+
def process_document(doc: Document, llm_transformer: LLMGraphTransformer, doc_num: int, total_docs: int) -> List:
|
309 |
+
print(colored(f"\n\nStarting Document {doc_num} of {total_docs}: {doc.page_content[:100]}\n\n", "yellow"))
|
310 |
+
graph_document = llm_transformer.convert_to_graph_documents([doc])
|
311 |
+
print(colored(f"\nFinished Document {doc_num}\n\n", "green"))
|
312 |
+
return graph_document
|
313 |
+
|
314 |
+
def create_graph_index(
|
315 |
+
documents: List[Document] = None,
|
316 |
+
allowed_relationships: list[str] = None,
|
317 |
+
allowed_nodes: list[str] = None,
|
318 |
+
query: str = None,
|
319 |
+
graph: Neo4jGraph = None,
|
320 |
+
max_threads: int = 5
|
321 |
+
) -> Neo4jGraph:
|
322 |
+
|
323 |
+
if os.environ.get('LLM_SERVER') == "openai":
|
324 |
+
llm = ChatOpenAI(temperature=0, model_name="gpt-4o-mini")
|
325 |
+
|
326 |
+
else:
|
327 |
+
llm = ChatAnthropic(temperature=0, model_name="claude-3-haiku-20240307")
|
328 |
+
|
329 |
+
# llm = ChatAnthropic(temperature=0, model_name="claude-3-haiku-20240307")
|
330 |
+
|
331 |
+
llm_transformer = LLMGraphTransformer(
|
332 |
+
llm=llm,
|
333 |
+
allowed_nodes=allowed_nodes,
|
334 |
+
allowed_relationships=allowed_relationships,
|
335 |
+
node_properties=True,
|
336 |
+
relationship_properties=True
|
337 |
+
)
|
338 |
+
|
339 |
+
graph_documents = []
|
340 |
+
total_docs = len(documents)
|
341 |
+
|
342 |
+
# Use ThreadPoolExecutor for parallel processing
|
343 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_threads) as executor:
|
344 |
+
# Create a list of futures
|
345 |
+
futures = [
|
346 |
+
executor.submit(process_document, doc, llm_transformer, i+1, total_docs)
|
347 |
+
for i, doc in enumerate(documents)
|
348 |
+
]
|
349 |
+
|
350 |
+
# Process completed futures
|
351 |
+
for future in concurrent.futures.as_completed(futures):
|
352 |
+
graph_documents.extend(future.result())
|
353 |
+
|
354 |
+
print(colored(f"\n\nTotal graph documents: {len(graph_documents)}", "green"))
|
355 |
+
# print(colored(f"\n\DEBUG graph documents: {graph_documents}", "red"))
|
356 |
+
|
357 |
+
graph_documents = [graph_documents]
|
358 |
+
flattened_graph_list = [item for sublist in graph_documents for item in sublist]
|
359 |
+
# print(colored(f"\n\DEBUG Flattened graph documents: {flattened_graph_list}", "yellow"))
|
360 |
+
|
361 |
+
|
362 |
+
graph.add_graph_documents(
|
363 |
+
flattened_graph_list,
|
364 |
+
baseEntityLabel=True,
|
365 |
+
include_source=True,
|
366 |
+
)
|
367 |
+
|
368 |
+
return graph
|
369 |
+
|
370 |
+
|
371 |
+
def run_rag(urls: List[str], allowed_nodes: list[str] = None, allowed_relationships: list[str] = None, query: List[str] = None, hybrid: bool = False) -> List[Dict[str, str]]:
|
372 |
+
# Change: adapted to take query and url as input.
|
373 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=min(len(urls), 5)) as executor:
|
374 |
+
futures = [executor.submit(intelligent_chunking, url, query) for url, query in zip(urls, query)]
|
375 |
+
chunks_list = [future.result() for future in concurrent.futures.as_completed(futures)]
|
376 |
+
|
377 |
+
|
378 |
+
corpus = [item for sublist in chunks_list for item in sublist]
|
379 |
+
|
380 |
+
print(colored(f"\n\nTotal documents in corpus after chunking: {len(corpus)}\n\n", "green"))
|
381 |
+
|
382 |
+
|
383 |
+
print(colored(f"\n\n DEBUG HYBRID VALUE: {hybrid}\n\n", "yellow"))
|
384 |
+
|
385 |
+
if hybrid:
|
386 |
+
print(colored(f"\n\n Creating Graph Index...\n\n", "green"))
|
387 |
+
graph = Neo4jGraph()
|
388 |
+
clear_neo4j_database(graph)
|
389 |
+
graph = create_graph_index(documents=corpus, allowed_nodes=allowed_nodes, allowed_relationships=allowed_relationships, query=query, graph=graph)
|
390 |
+
else:
|
391 |
+
graph = None
|
392 |
+
|
393 |
+
retrieved_context = run_hybrid_graph_retrrieval(graph=graph, corpus=corpus, query=query, hybrid=hybrid)
|
394 |
+
|
395 |
+
retrieved_context = str(retrieved_context)
|
396 |
+
|
397 |
+
return retrieved_context
|
398 |
+
|
399 |
+
# if __name__ == "__main__":
|
400 |
+
# # For testing purposes.
|
401 |
+
# url1 = "https://www.reddit.com/r/microsoft/comments/1bkikl1/regretting_buying_copilot_for_microsoft_365"
|
402 |
+
# url2 = "'https://www.reddit.com/r/microsoft_365_copilot/comments/1chtqtg/do_you_actually_find_365_copilot_useful_in_your"
|
403 |
+
# # url3 = "https://developers.googleblog.com/en/new-features-for-the-gemini-api-and-google-ai-studio/"
|
404 |
+
|
405 |
+
# # query = "cheapest macbook"
|
406 |
+
|
407 |
+
# # urls = [url1, url2, url3]
|
408 |
+
# urls = [url1, url2]
|
409 |
+
# query = ["Co-pilot Microsoft"]
|
410 |
+
# allowed_nodes = None
|
411 |
+
# allowed_relationships = None
|
412 |
+
# hybrid = False
|
413 |
+
# results = run_rag(urls, allowed_nodes=allowed_nodes, allowed_relationships=allowed_relationships, query=query, hybrid=hybrid)
|
414 |
+
|
415 |
+
# print(colored(f"\n\n RESULTS: {results}", "green"))
|
416 |
+
|
417 |
+
# print(f"\n\n RESULTS: {results}")
|
tools/legacy/offline_rag_tool.py
ADDED
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import os
|
3 |
+
import io
|
4 |
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
5 |
+
sys.path.insert(0, root_dir)
|
6 |
+
import concurrent.futures
|
7 |
+
import functools
|
8 |
+
import requests
|
9 |
+
import numpy as np
|
10 |
+
import faiss
|
11 |
+
import traceback
|
12 |
+
import tempfile
|
13 |
+
from typing import Dict, List
|
14 |
+
from termcolor import colored
|
15 |
+
from requests.adapters import HTTPAdapter
|
16 |
+
from urllib3.util.retry import Retry
|
17 |
+
from langchain_community.embeddings.fastembed import FastEmbedEmbeddings
|
18 |
+
from langchain_community.vectorstores import FAISS
|
19 |
+
from langchain_community.vectorstores.utils import DistanceStrategy
|
20 |
+
from flashrank import Ranker, RerankRequest
|
21 |
+
from llmsherpa.readers import LayoutPDFReader
|
22 |
+
from langchain.schema import Document
|
23 |
+
from config.load_configs import load_config
|
24 |
+
from langchain_community.docstore.in_memory import InMemoryDocstore
|
25 |
+
from fake_useragent import UserAgent
|
26 |
+
from multiprocessing import Pool, cpu_count
|
27 |
+
|
28 |
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
29 |
+
sys.path.insert(0, root_dir)
|
30 |
+
|
31 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
32 |
+
load_config(config_path)
|
33 |
+
|
34 |
+
ua = UserAgent()
|
35 |
+
os.environ["USER_AGENT"] = ua.random
|
36 |
+
os.environ["FAISS_OPT_LEVEL"] = "generic"
|
37 |
+
|
38 |
+
def timeout(max_timeout):
|
39 |
+
"""Timeout decorator, parameter in seconds."""
|
40 |
+
def timeout_decorator(item):
|
41 |
+
"""Wrap the original function."""
|
42 |
+
@functools.wraps(item)
|
43 |
+
def func_wrapper(*args, **kwargs):
|
44 |
+
"""Closure for function."""
|
45 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
46 |
+
future = executor.submit(item, *args, **kwargs)
|
47 |
+
try:
|
48 |
+
return future.result(max_timeout)
|
49 |
+
except concurrent.futures.TimeoutError:
|
50 |
+
return [Document(page_content=f"Timeout occurred while processing URL: {args[0]}", metadata={"source": args[0]})]
|
51 |
+
return func_wrapper
|
52 |
+
return timeout_decorator
|
53 |
+
|
54 |
+
|
55 |
+
@timeout(20) # 20 second timeout
|
56 |
+
def intelligent_chunking(url: str) -> List[Document]:
|
57 |
+
try:
|
58 |
+
print(colored(f"\n\nStarting Intelligent Chunking with LLM Sherpa for URL: {url}\n\n", "green"))
|
59 |
+
llmsherpa_api_url = os.environ.get('LLM_SHERPA_SERVER')
|
60 |
+
|
61 |
+
if not llmsherpa_api_url:
|
62 |
+
raise ValueError("LLM_SHERPA_SERVER environment variable is not set")
|
63 |
+
|
64 |
+
corpus = []
|
65 |
+
|
66 |
+
try:
|
67 |
+
print(colored("Starting LLM Sherpa LayoutPDFReader...\n\n", "yellow"))
|
68 |
+
reader = LayoutPDFReader(llmsherpa_api_url)
|
69 |
+
doc = reader.read_pdf(url)
|
70 |
+
print(colored("Finished LLM Sherpa LayoutPDFReader...\n\n", "yellow"))
|
71 |
+
except Exception as e:
|
72 |
+
print(colored(f"Error in LLM Sherpa LayoutPDFReader: {str(e)}", "red"))
|
73 |
+
traceback.print_exc()
|
74 |
+
doc = None
|
75 |
+
|
76 |
+
if doc:
|
77 |
+
for chunk in doc.chunks():
|
78 |
+
document = Document(
|
79 |
+
page_content=chunk.to_context_text(),
|
80 |
+
metadata={"source": url}
|
81 |
+
)
|
82 |
+
corpus.append(document)
|
83 |
+
|
84 |
+
print(colored(f"Created corpus with {len(corpus)} documents", "green"))
|
85 |
+
|
86 |
+
if not doc:
|
87 |
+
print(colored(f"No document to append to corpus", "red"))
|
88 |
+
|
89 |
+
return corpus
|
90 |
+
|
91 |
+
except concurrent.futures.TimeoutError:
|
92 |
+
print(colored(f"Timeout occurred while processing URL: {url}", "red"))
|
93 |
+
return [Document(page_content=f"Timeout occurred while processing URL: {url}", metadata={"source": url})]
|
94 |
+
except Exception as e:
|
95 |
+
print(colored(f"Error in Intelligent Chunking for URL {url}: {str(e)}", "red"))
|
96 |
+
traceback.print_exc()
|
97 |
+
return [Document(page_content=f"Error in Intelligent Chunking for URL: {url}", metadata={"source": url})]
|
98 |
+
|
99 |
+
|
100 |
+
def index_and_rank(corpus: List[Document], query: str, top_percent: float = 60, batch_size: int = 25) -> List[Dict[str, str]]:
|
101 |
+
print(colored(f"\n\nStarting indexing and ranking with FastEmbeddings and FAISS for {len(corpus)} documents\n\n", "green"))
|
102 |
+
embeddings = FastEmbedEmbeddings(model_name='jinaai/jina-embeddings-v2-small-en', max_length=512)
|
103 |
+
|
104 |
+
print(colored("\n\nCreating FAISS index...\n\n", "green"))
|
105 |
+
|
106 |
+
try:
|
107 |
+
# Initialize an empty FAISS index
|
108 |
+
index = None
|
109 |
+
docstore = InMemoryDocstore({})
|
110 |
+
index_to_docstore_id = {}
|
111 |
+
|
112 |
+
# Process documents in batches
|
113 |
+
for i in range(0, len(corpus), batch_size):
|
114 |
+
batch = corpus[i:i+batch_size]
|
115 |
+
texts = [doc.page_content for doc in batch]
|
116 |
+
metadatas = [doc.metadata for doc in batch]
|
117 |
+
|
118 |
+
print(f"Processing batch {i // batch_size + 1} with {len(texts)} documents")
|
119 |
+
|
120 |
+
# Embed the batch
|
121 |
+
batch_embeddings = embeddings.embed_documents(texts)
|
122 |
+
|
123 |
+
# Convert embeddings to numpy array with float32 dtype
|
124 |
+
batch_embeddings_np = np.array(batch_embeddings, dtype=np.float32)
|
125 |
+
|
126 |
+
if index is None:
|
127 |
+
# Create the index with the first batch
|
128 |
+
index = faiss.IndexFlatIP(batch_embeddings_np.shape[1])
|
129 |
+
|
130 |
+
# Normalize the embeddings
|
131 |
+
faiss.normalize_L2(batch_embeddings_np)
|
132 |
+
|
133 |
+
# Add embeddings to the index
|
134 |
+
start_id = len(index_to_docstore_id)
|
135 |
+
index.add(batch_embeddings_np)
|
136 |
+
|
137 |
+
# Update docstore and index_to_docstore_id
|
138 |
+
for j, (text, metadata) in enumerate(zip(texts, metadatas)):
|
139 |
+
doc_id = f"{start_id + j}"
|
140 |
+
docstore.add({doc_id: Document(page_content=text, metadata=metadata)})
|
141 |
+
index_to_docstore_id[start_id + j] = doc_id
|
142 |
+
|
143 |
+
print(f"Total documents indexed: {len(index_to_docstore_id)}")
|
144 |
+
|
145 |
+
# Create a FAISS retriever
|
146 |
+
retriever = FAISS(embeddings, index, docstore, index_to_docstore_id)
|
147 |
+
|
148 |
+
# Perform the search
|
149 |
+
k = min(40, len(corpus)) # Ensure we don't try to retrieve more documents than we have
|
150 |
+
docs = retriever.similarity_search_with_score(query, k=k)
|
151 |
+
print(colored(f"\n\nRetrieved {len(docs)} documents\n\n", "green"))
|
152 |
+
|
153 |
+
passages = []
|
154 |
+
for idx, (doc, score) in enumerate(docs, start=1):
|
155 |
+
try:
|
156 |
+
passage = {
|
157 |
+
"id": idx,
|
158 |
+
"text": doc.page_content,
|
159 |
+
"meta": doc.metadata,
|
160 |
+
"score": float(score) # Convert score to float
|
161 |
+
}
|
162 |
+
passages.append(passage)
|
163 |
+
except Exception as e:
|
164 |
+
print(colored(f"Error in creating passage: {str(e)}", "red"))
|
165 |
+
traceback.print_exc()
|
166 |
+
|
167 |
+
print(colored("\n\nRe-ranking documents...\n\n", "green"))
|
168 |
+
ranker = Ranker(cache_dir=tempfile.mkdtemp())
|
169 |
+
rerankrequest = RerankRequest(query=query, passages=passages)
|
170 |
+
results = ranker.rerank(rerankrequest)
|
171 |
+
print(colored("\n\nRe-ranking complete\n\n", "green"))
|
172 |
+
|
173 |
+
# Sort results by score in descending order
|
174 |
+
sorted_results = sorted(results, key=lambda x: x['score'], reverse=True)
|
175 |
+
|
176 |
+
# Calculate the number of results to return based on the percentage
|
177 |
+
num_results = max(1, int(len(sorted_results) * (top_percent / 100)))
|
178 |
+
top_results = sorted_results[:num_results]
|
179 |
+
|
180 |
+
final_results = [
|
181 |
+
{
|
182 |
+
"text": result['text'],
|
183 |
+
"meta": result['meta'],
|
184 |
+
"score": result['score']
|
185 |
+
}
|
186 |
+
for result in top_results
|
187 |
+
]
|
188 |
+
|
189 |
+
print(colored(f"\n\nReturned top {top_percent}% of results ({len(final_results)} documents)\n\n", "green"))
|
190 |
+
|
191 |
+
# Add debug information about scores
|
192 |
+
scores = [result['score'] for result in results]
|
193 |
+
print(f"Score distribution: min={min(scores):.4f}, max={max(scores):.4f}, mean={np.mean(scores):.4f}, median={np.median(scores):.4f}")
|
194 |
+
print(f"Unique scores: {len(set(scores))}")
|
195 |
+
if final_results:
|
196 |
+
print(f"Score range for top {top_percent}% results: {final_results[-1]['score']:.4f} to {final_results[0]['score']:.4f}")
|
197 |
+
|
198 |
+
except Exception as e:
|
199 |
+
print(colored(f"Error in indexing and ranking: {str(e)}", "red"))
|
200 |
+
traceback.print_exc()
|
201 |
+
final_results = [{"text": "Error in indexing and ranking", "meta": {"source": "unknown"}, "score": 0.0}]
|
202 |
+
|
203 |
+
return final_results
|
204 |
+
|
205 |
+
def run_rag(urls: List[str], query: str) -> List[Dict[str, str]]:
|
206 |
+
# Use ThreadPoolExecutor instead of multiprocessing
|
207 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=min(len(urls), 3)) as executor:
|
208 |
+
futures = [executor.submit(intelligent_chunking, url) for url in urls]
|
209 |
+
chunks_list = [future.result() for future in concurrent.futures.as_completed(futures)]
|
210 |
+
|
211 |
+
# Flatten the list of lists into a single corpus
|
212 |
+
corpus = [chunk for chunks in chunks_list for chunk in chunks]
|
213 |
+
print(colored(f"\n\nTotal documents in corpus after chunking: {len(corpus)}\n\n", "green"))
|
214 |
+
|
215 |
+
ranked_docs = index_and_rank(corpus, query)
|
216 |
+
return ranked_docs
|
217 |
+
|
218 |
+
# def run_rag(urls: List[str], query: str) -> List[Dict[str, str]]:
|
219 |
+
# # Use multiprocessing to chunk URLs in parallel
|
220 |
+
# with Pool(processes=min(cpu_count(), len(urls))) as pool:
|
221 |
+
# chunks_list = pool.map(intelligent_chunking, urls)
|
222 |
+
|
223 |
+
# # Flatten the list of lists into a single corpus
|
224 |
+
# corpus = [chunk for chunks in chunks_list for chunk in chunks]
|
225 |
+
|
226 |
+
# print(colored(f"\n\nTotal documents in corpus after chunking: {len(corpus)}\n\n", "green"))
|
227 |
+
|
228 |
+
# ranked_docs = index_and_rank(corpus, query)
|
229 |
+
# return ranked_docs
|
230 |
+
|
231 |
+
if __name__ == "__main__":
|
232 |
+
# For testing purposes.
|
233 |
+
url1 = "https://www.amazon.com/dp/B0CX23GFMJ/ref=fs_a_mbt2_us4"
|
234 |
+
url2 = "https://www.amazon.com/dp/B0CX23V2ZK/ref=fs_a_mbt2_us3"
|
235 |
+
url3 = "https://der8auer.com/x570-motherboard-vrm-overview/"
|
236 |
+
|
237 |
+
query = "cheapest macbook"
|
238 |
+
|
239 |
+
urls = [url1, url2, url3]
|
240 |
+
results = run_rag(urls, query)
|
241 |
+
|
242 |
+
print(f"\n\n RESULTS: {results}")
|
tools/legacy/rag_tool.py
ADDED
@@ -0,0 +1,409 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import os
|
3 |
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
4 |
+
sys.path.insert(0, root_dir)
|
5 |
+
import concurrent.futures
|
6 |
+
import functools
|
7 |
+
import numpy as np
|
8 |
+
import faiss
|
9 |
+
import traceback
|
10 |
+
import tempfile
|
11 |
+
from typing import Dict, List
|
12 |
+
from termcolor import colored
|
13 |
+
from langchain_anthropic import ChatAnthropic
|
14 |
+
from langchain_openai import ChatOpenAI
|
15 |
+
from langchain_community.graphs import Neo4jGraph
|
16 |
+
from langchain_experimental.graph_transformers.llm import LLMGraphTransformer
|
17 |
+
# from langchain_community.vectorstores.neo4j_vector import Neo4jVector
|
18 |
+
from langchain_community.embeddings.fastembed import FastEmbedEmbeddings
|
19 |
+
from langchain_community.vectorstores import FAISS
|
20 |
+
from flashrank import Ranker, RerankRequest
|
21 |
+
from llmsherpa.readers import LayoutPDFReader
|
22 |
+
from langchain.schema import Document
|
23 |
+
from config.load_configs import load_config
|
24 |
+
from langchain_community.docstore.in_memory import InMemoryDocstore
|
25 |
+
from fake_useragent import UserAgent
|
26 |
+
|
27 |
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
28 |
+
sys.path.insert(0, root_dir)
|
29 |
+
|
30 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
31 |
+
load_config(config_path)
|
32 |
+
|
33 |
+
ua = UserAgent()
|
34 |
+
os.environ["USER_AGENT"] = ua.random
|
35 |
+
os.environ["FAISS_OPT_LEVEL"] = "generic"
|
36 |
+
|
37 |
+
|
38 |
+
def timeout(max_timeout):
|
39 |
+
"""Timeout decorator, parameter in seconds."""
|
40 |
+
def timeout_decorator(item):
|
41 |
+
"""Wrap the original function."""
|
42 |
+
@functools.wraps(item)
|
43 |
+
def func_wrapper(*args, **kwargs):
|
44 |
+
"""Closure for function."""
|
45 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
46 |
+
future = executor.submit(item, *args, **kwargs)
|
47 |
+
try:
|
48 |
+
return future.result(max_timeout)
|
49 |
+
except concurrent.futures.TimeoutError:
|
50 |
+
return [Document(page_content=f"Timeout occurred while processing URL: {args[0]}", metadata={"source": args[0]})]
|
51 |
+
return func_wrapper
|
52 |
+
return timeout_decorator
|
53 |
+
|
54 |
+
|
55 |
+
# Change: Added function to deduplicate re-ranked results.
|
56 |
+
def deduplicate_results(results, rerank=True):
|
57 |
+
seen = set()
|
58 |
+
unique_results = []
|
59 |
+
for result in results:
|
60 |
+
# Create a tuple of the content and source to use as a unique identifier
|
61 |
+
if rerank:
|
62 |
+
identifier = (result['text'], result['meta'])
|
63 |
+
else:
|
64 |
+
# When not reranking, result is a tuple (doc, score)
|
65 |
+
doc, score = result
|
66 |
+
identifier = (doc.page_content, doc.metadata.get('source', ''))
|
67 |
+
if identifier not in seen:
|
68 |
+
seen.add(identifier)
|
69 |
+
unique_results.append(result)
|
70 |
+
return unique_results
|
71 |
+
|
72 |
+
|
73 |
+
def index_and_rank(corpus: List[Document], query: str, top_percent: float = 20, batch_size: int = 25) -> List[Dict[str, str]]:
|
74 |
+
print(colored(f"\n\nStarting indexing and ranking with FastEmbeddings and FAISS for {len(corpus)} documents\n\n", "green"))
|
75 |
+
embeddings = FastEmbedEmbeddings(model_name='jinaai/jina-embeddings-v2-small-en', max_length=512)
|
76 |
+
|
77 |
+
print(colored("\n\nCreating FAISS index...\n\n", "green"))
|
78 |
+
|
79 |
+
try:
|
80 |
+
# Initialize an empty FAISS index
|
81 |
+
index = None
|
82 |
+
docstore = InMemoryDocstore({})
|
83 |
+
index_to_docstore_id = {}
|
84 |
+
|
85 |
+
# Process documents in batches
|
86 |
+
for i in range(0, len(corpus), batch_size):
|
87 |
+
batch = corpus[i:i+batch_size]
|
88 |
+
texts = [doc.page_content for doc in batch]
|
89 |
+
metadatas = [doc.metadata for doc in batch]
|
90 |
+
|
91 |
+
print(f"Processing batch {i // batch_size + 1} with {len(texts)} documents")
|
92 |
+
|
93 |
+
# Embed the batch
|
94 |
+
batch_embeddings = embeddings.embed_documents(texts)
|
95 |
+
|
96 |
+
# Convert embeddings to numpy array with float32 dtype
|
97 |
+
batch_embeddings_np = np.array(batch_embeddings, dtype=np.float32)
|
98 |
+
|
99 |
+
if index is None:
|
100 |
+
# Create the index with the first batch
|
101 |
+
index = faiss.IndexFlatIP(batch_embeddings_np.shape[1])
|
102 |
+
|
103 |
+
# Normalize the embeddings
|
104 |
+
faiss.normalize_L2(batch_embeddings_np)
|
105 |
+
|
106 |
+
# Add embeddings to the index
|
107 |
+
start_id = len(index_to_docstore_id)
|
108 |
+
index.add(batch_embeddings_np)
|
109 |
+
|
110 |
+
# Update docstore and index_to_docstore_id
|
111 |
+
for j, (text, metadata) in enumerate(zip(texts, metadatas)):
|
112 |
+
doc_id = f"{start_id + j}"
|
113 |
+
docstore.add({doc_id: Document(page_content=text, metadata=metadata)})
|
114 |
+
index_to_docstore_id[start_id + j] = doc_id
|
115 |
+
|
116 |
+
print(f"Total documents indexed: {len(index_to_docstore_id)}")
|
117 |
+
|
118 |
+
# Create a FAISS retriever
|
119 |
+
retriever = FAISS(embeddings, index, docstore, index_to_docstore_id)
|
120 |
+
|
121 |
+
# Perform the search
|
122 |
+
k = min(40, len(corpus)) # Ensure we don't try to retrieve more documents than we have
|
123 |
+
|
124 |
+
# Change: Retrieve documents based on query in metadata
|
125 |
+
similarity_cache = {}
|
126 |
+
docs = []
|
127 |
+
for doc in corpus:
|
128 |
+
query = doc.metadata.get('query', '')
|
129 |
+
# Check if we've already performed this search
|
130 |
+
if query in similarity_cache:
|
131 |
+
cached_results = similarity_cache[query]
|
132 |
+
docs.extend(cached_results)
|
133 |
+
else:
|
134 |
+
# Perform the similarity search
|
135 |
+
search_results = retriever.similarity_search_with_score(query, k=k)
|
136 |
+
|
137 |
+
# Cache the results
|
138 |
+
similarity_cache[query] = search_results
|
139 |
+
|
140 |
+
# Add to docs
|
141 |
+
docs.extend(search_results)
|
142 |
+
|
143 |
+
docs = deduplicate_results(docs, rerank=False)
|
144 |
+
|
145 |
+
print(colored(f"\n\nRetrieved {len(docs)} documents\n\n", "green"))
|
146 |
+
|
147 |
+
passages = []
|
148 |
+
for idx, (doc, score) in enumerate(docs, start=1):
|
149 |
+
try:
|
150 |
+
passage = {
|
151 |
+
"id": idx,
|
152 |
+
"text": doc.page_content,
|
153 |
+
"meta": doc.metadata.get("source", {"source": "unknown"}),
|
154 |
+
"score": float(score) # Convert score to float
|
155 |
+
}
|
156 |
+
passages.append(passage)
|
157 |
+
except Exception as e:
|
158 |
+
print(colored(f"Error in creating passage: {str(e)}", "red"))
|
159 |
+
traceback.print_exc()
|
160 |
+
|
161 |
+
print(colored("\n\nRe-ranking documents...\n\n", "green"))
|
162 |
+
# Change: reranker done based on query in metadata
|
163 |
+
ranker = Ranker(cache_dir=tempfile.mkdtemp())
|
164 |
+
results = []
|
165 |
+
processed_queries = set()
|
166 |
+
|
167 |
+
# Perform reranking with query caching
|
168 |
+
for doc in corpus:
|
169 |
+
query = doc.metadata.get('query', '')
|
170 |
+
|
171 |
+
# Skip if we've already processed this query
|
172 |
+
if query in processed_queries:
|
173 |
+
continue
|
174 |
+
|
175 |
+
rerankrequest = RerankRequest(query=query, passages=passages)
|
176 |
+
result = ranker.rerank(rerankrequest)
|
177 |
+
results.extend(result)
|
178 |
+
|
179 |
+
# Mark this query as processed
|
180 |
+
processed_queries.add(query)
|
181 |
+
|
182 |
+
results = deduplicate_results(results, rerank=True)
|
183 |
+
|
184 |
+
print(colored(f"\n\nRe-ranking complete with {len(results)} documents\n\n", "green"))
|
185 |
+
|
186 |
+
# Sort results by score in descending order
|
187 |
+
sorted_results = sorted(results, key=lambda x: x['score'], reverse=True)
|
188 |
+
|
189 |
+
# Calculate the number of results to return based on the percentage
|
190 |
+
num_results = max(1, int(len(sorted_results) * (top_percent / 100)))
|
191 |
+
top_results = sorted_results[:num_results]
|
192 |
+
|
193 |
+
final_results = [
|
194 |
+
{
|
195 |
+
"text": result['text'],
|
196 |
+
"meta": result['meta'],
|
197 |
+
"score": result['score']
|
198 |
+
}
|
199 |
+
for result in top_results
|
200 |
+
]
|
201 |
+
|
202 |
+
print(colored(f"\n\nReturned top {top_percent}% of results ({len(final_results)} documents)\n\n", "green"))
|
203 |
+
|
204 |
+
# Add debug information about scores
|
205 |
+
scores = [result['score'] for result in results]
|
206 |
+
print(f"Score distribution: min={min(scores):.4f}, max={max(scores):.4f}, mean={np.mean(scores):.4f}, median={np.median(scores):.4f}")
|
207 |
+
print(f"Unique scores: {len(set(scores))}")
|
208 |
+
if final_results:
|
209 |
+
print(f"Score range for top {top_percent}% results: {final_results[-1]['score']:.4f} to {final_results[0]['score']:.4f}")
|
210 |
+
|
211 |
+
except Exception as e:
|
212 |
+
print(colored(f"Error in indexing and ranking: {str(e)}", "red"))
|
213 |
+
traceback.print_exc()
|
214 |
+
final_results = [{"text": "Error in indexing and ranking", "meta": {"source": "unknown"}, "score": 0.0}]
|
215 |
+
|
216 |
+
return final_results
|
217 |
+
|
218 |
+
def run_hybrid_graph_retrrieval(graph: Neo4jGraph = None, corpus: List[Document] = None, query: str = None, hybrid: bool = False):
|
219 |
+
print(colored(f"\n\Initiating Retrieval...\n\n", "green"))
|
220 |
+
|
221 |
+
if hybrid:
|
222 |
+
print(colored("Running Hybrid Retrieval...", "yellow"))
|
223 |
+
unstructured_data = index_and_rank(corpus, query)
|
224 |
+
|
225 |
+
query = f"""
|
226 |
+
MATCH p = (n)-[r]->(m)
|
227 |
+
WHERE COUNT {{(n)--()}} > 30
|
228 |
+
RETURN p AS Path
|
229 |
+
"""
|
230 |
+
response = graph.query(query)
|
231 |
+
retrieved_context = f"Important Relationships:{response}\n\n Additional Context:{unstructured_data}"
|
232 |
+
|
233 |
+
else:
|
234 |
+
print(colored("Running Dense Only Retrieval...", "yellow"))
|
235 |
+
unstructured_data = index_and_rank(corpus, query)
|
236 |
+
retrieved_context = f"Additional Context:{unstructured_data}"
|
237 |
+
|
238 |
+
return retrieved_context
|
239 |
+
|
240 |
+
|
241 |
+
@timeout(20) # Change: Takes url and query as input
|
242 |
+
def intelligent_chunking(url: str, query: str) -> List[Document]:
|
243 |
+
try:
|
244 |
+
print(colored(f"\n\nStarting Intelligent Chunking with LLM Sherpa for URL: {url}\n\n", "green"))
|
245 |
+
llmsherpa_api_url = os.environ.get('LLM_SHERPA_SERVER')
|
246 |
+
|
247 |
+
if not llmsherpa_api_url:
|
248 |
+
raise ValueError("LLM_SHERPA_SERVER environment variable is not set")
|
249 |
+
|
250 |
+
corpus = []
|
251 |
+
|
252 |
+
try:
|
253 |
+
print(colored("Starting LLM Sherpa LayoutPDFReader...\n\n", "yellow"))
|
254 |
+
reader = LayoutPDFReader(llmsherpa_api_url)
|
255 |
+
doc = reader.read_pdf(url)
|
256 |
+
print(colored("Finished LLM Sherpa LayoutPDFReader...\n\n", "yellow"))
|
257 |
+
except Exception as e:
|
258 |
+
print(colored(f"Error in LLM Sherpa LayoutPDFReader: {str(e)}", "red"))
|
259 |
+
traceback.print_exc()
|
260 |
+
doc = None
|
261 |
+
|
262 |
+
if doc:
|
263 |
+
for chunk in doc.chunks():
|
264 |
+
document = Document(
|
265 |
+
page_content=chunk.to_context_text(),
|
266 |
+
metadata={"source": url, "query": query} # Change: Added query to metadata
|
267 |
+
)
|
268 |
+
|
269 |
+
if len(document.page_content) > 75:
|
270 |
+
corpus.append(document)
|
271 |
+
|
272 |
+
print(colored(f"Created corpus with {len(corpus)} documents", "green"))
|
273 |
+
|
274 |
+
|
275 |
+
if not doc:
|
276 |
+
print(colored(f"No document to append to corpus", "red"))
|
277 |
+
|
278 |
+
# print(colored(f"DEBUG: Corpus: {corpus}", "yellow"))
|
279 |
+
return corpus
|
280 |
+
|
281 |
+
except concurrent.futures.TimeoutError:
|
282 |
+
print(colored(f"Timeout occurred while processing URL: {url}", "red"))
|
283 |
+
return [Document(page_content=f"Timeout occurred while processing URL: {url}", metadata={"source": url})]
|
284 |
+
except Exception as e:
|
285 |
+
print(colored(f"Error in Intelligent Chunking for URL {url}: {str(e)}", "red"))
|
286 |
+
traceback.print_exc()
|
287 |
+
return [Document(page_content=f"Error in Intelligent Chunking for URL: {url}", metadata={"source": url})]
|
288 |
+
|
289 |
+
|
290 |
+
def clear_neo4j_database(graph: Neo4jGraph):
|
291 |
+
"""
|
292 |
+
Clear all nodes and relationships from the Neo4j database.
|
293 |
+
"""
|
294 |
+
try:
|
295 |
+
print(colored("\n\nClearing Neo4j database...\n\n", "yellow"))
|
296 |
+
# Delete all relationships first
|
297 |
+
graph.query("MATCH ()-[r]->() DELETE r")
|
298 |
+
# Then delete all nodes
|
299 |
+
graph.query("MATCH (n) DELETE n")
|
300 |
+
print(colored("Neo4j database cleared successfully.\n\n", "green"))
|
301 |
+
except Exception as e:
|
302 |
+
print(colored(f"Error clearing Neo4j database: {str(e)}", "red"))
|
303 |
+
traceback.print_exc()
|
304 |
+
|
305 |
+
def process_document(doc: Document, llm_transformer: LLMGraphTransformer, doc_num: int, total_docs: int) -> List:
|
306 |
+
print(colored(f"\n\nStarting Document {doc_num} of {total_docs}: {doc.page_content[:100]}\n\n", "yellow"))
|
307 |
+
graph_document = llm_transformer.convert_to_graph_documents([doc])
|
308 |
+
print(colored(f"\nFinished Document {doc_num}\n\n", "green"))
|
309 |
+
return graph_document
|
310 |
+
|
311 |
+
def create_graph_index(
|
312 |
+
documents: List[Document] = None,
|
313 |
+
allowed_relationships: list[str] = None,
|
314 |
+
allowed_nodes: list[str] = None,
|
315 |
+
query: str = None,
|
316 |
+
graph: Neo4jGraph = None,
|
317 |
+
max_threads: int = 5
|
318 |
+
) -> Neo4jGraph:
|
319 |
+
llm = ChatOpenAI(temperature=0, model_name="gpt-4o-mini")
|
320 |
+
|
321 |
+
# llm = ChatAnthropic(temperature=0, model_name="claude-3-haiku-20240307")
|
322 |
+
|
323 |
+
llm_transformer = LLMGraphTransformer(
|
324 |
+
llm=llm,
|
325 |
+
allowed_nodes=allowed_nodes,
|
326 |
+
allowed_relationships=allowed_relationships,
|
327 |
+
node_properties=True,
|
328 |
+
relationship_properties=True
|
329 |
+
)
|
330 |
+
|
331 |
+
graph_documents = []
|
332 |
+
total_docs = len(documents)
|
333 |
+
|
334 |
+
# Use ThreadPoolExecutor for parallel processing
|
335 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_threads) as executor:
|
336 |
+
# Create a list of futures
|
337 |
+
futures = [
|
338 |
+
executor.submit(process_document, doc, llm_transformer, i+1, total_docs)
|
339 |
+
for i, doc in enumerate(documents)
|
340 |
+
]
|
341 |
+
|
342 |
+
# Process completed futures
|
343 |
+
for future in concurrent.futures.as_completed(futures):
|
344 |
+
graph_documents.extend(future.result())
|
345 |
+
|
346 |
+
print(colored(f"\n\nTotal graph documents: {len(graph_documents)}", "green"))
|
347 |
+
# print(colored(f"\n\DEBUG graph documents: {graph_documents}", "red"))
|
348 |
+
|
349 |
+
graph_documents = [graph_documents]
|
350 |
+
flattened_graph_list = [item for sublist in graph_documents for item in sublist]
|
351 |
+
# print(colored(f"\n\DEBUG Flattened graph documents: {flattened_graph_list}", "yellow"))
|
352 |
+
|
353 |
+
|
354 |
+
graph.add_graph_documents(
|
355 |
+
flattened_graph_list,
|
356 |
+
baseEntityLabel=True,
|
357 |
+
include_source=True,
|
358 |
+
)
|
359 |
+
|
360 |
+
return graph
|
361 |
+
|
362 |
+
|
363 |
+
def run_rag(urls: List[str], allowed_nodes: list[str] = None, allowed_relationships: list[str] = None, query: List[str] = None, hybrid: bool = False) -> List[Dict[str, str]]:
|
364 |
+
# Change: adapted to take query and url as input.
|
365 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=min(len(urls), 3)) as executor:
|
366 |
+
futures = [executor.submit(intelligent_chunking, url, query) for url, query in zip(urls, query)]
|
367 |
+
chunks_list = [future.result() for future in concurrent.futures.as_completed(futures)]
|
368 |
+
|
369 |
+
|
370 |
+
corpus = [item for sublist in chunks_list for item in sublist]
|
371 |
+
|
372 |
+
print(colored(f"\n\nTotal documents in corpus after chunking: {len(corpus)}\n\n", "green"))
|
373 |
+
|
374 |
+
|
375 |
+
print(colored(f"\n\n DEBUG HYBRID VALUE: {hybrid}\n\n", "yellow"))
|
376 |
+
|
377 |
+
if hybrid:
|
378 |
+
print(colored(f"\n\n Creating Graph Index...\n\n", "green"))
|
379 |
+
graph = Neo4jGraph()
|
380 |
+
clear_neo4j_database(graph)
|
381 |
+
graph = create_graph_index(documents=corpus, allowed_nodes=allowed_nodes, allowed_relationships=allowed_relationships, query=query, graph=graph)
|
382 |
+
else:
|
383 |
+
graph = None
|
384 |
+
|
385 |
+
retrieved_context = run_hybrid_graph_retrrieval(graph=graph, corpus=corpus, query=query, hybrid=hybrid)
|
386 |
+
|
387 |
+
retrieved_context = str(retrieved_context)
|
388 |
+
|
389 |
+
return retrieved_context
|
390 |
+
|
391 |
+
if __name__ == "__main__":
|
392 |
+
# For testing purposes.
|
393 |
+
url1 = "https://ai.meta.com/blog/meta-llama-3-1/"
|
394 |
+
url2 = "https://mistral.ai/news/mistral-large-2407/"
|
395 |
+
# url3 = "https://developers.googleblog.com/en/new-features-for-the-gemini-api-and-google-ai-studio/"
|
396 |
+
|
397 |
+
# query = "cheapest macbook"
|
398 |
+
|
399 |
+
# urls = [url1, url2, url3]
|
400 |
+
urls = [url1, url2]
|
401 |
+
query = ["What's the size of the Llama 3.1 modles?", "What are the features of the Mistral models?"]
|
402 |
+
allowed_nodes = None
|
403 |
+
allowed_relationships = None
|
404 |
+
hybrid = True
|
405 |
+
results = run_rag(urls, allowed_nodes=allowed_nodes, allowed_relationships=allowed_relationships, query=query, hybrid=hybrid)
|
406 |
+
|
407 |
+
print(colored(f"\n\n RESULTS: {results}", "green"))
|
408 |
+
|
409 |
+
# print(f"\n\n RESULTS: {results}")
|
tools/llm_graph_transformer.py
ADDED
@@ -0,0 +1,874 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import asyncio
|
2 |
+
import json
|
3 |
+
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union, cast
|
4 |
+
|
5 |
+
from langchain_community.graphs.graph_document import GraphDocument, Node, Relationship
|
6 |
+
from langchain_core.documents import Document
|
7 |
+
from langchain_core.language_models import BaseLanguageModel
|
8 |
+
from langchain_core.messages import SystemMessage
|
9 |
+
from langchain_core.output_parsers import JsonOutputParser
|
10 |
+
from langchain_core.prompts import (
|
11 |
+
ChatPromptTemplate,
|
12 |
+
HumanMessagePromptTemplate,
|
13 |
+
PromptTemplate,
|
14 |
+
)
|
15 |
+
from langchain_core.runnables import RunnableConfig
|
16 |
+
from pydantic import BaseModel, Field, create_model
|
17 |
+
|
18 |
+
examples = [
|
19 |
+
{
|
20 |
+
"text": (
|
21 |
+
"Adam is a software engineer in Microsoft since 2009, "
|
22 |
+
"and last year he got an award as the Best Talent"
|
23 |
+
),
|
24 |
+
"head": "Adam",
|
25 |
+
"head_type": "Person",
|
26 |
+
"relation": "WORKS_FOR",
|
27 |
+
"tail": "Microsoft",
|
28 |
+
"tail_type": "Company",
|
29 |
+
},
|
30 |
+
{
|
31 |
+
"text": (
|
32 |
+
"Adam is a software engineer in Microsoft since 2009, "
|
33 |
+
"and last year he got an award as the Best Talent"
|
34 |
+
),
|
35 |
+
"head": "Adam",
|
36 |
+
"head_type": "Person",
|
37 |
+
"relation": "HAS_AWARD",
|
38 |
+
"tail": "Best Talent",
|
39 |
+
"tail_type": "Award",
|
40 |
+
},
|
41 |
+
{
|
42 |
+
"text": (
|
43 |
+
"Microsoft is a tech company that provide "
|
44 |
+
"several products such as Microsoft Word"
|
45 |
+
),
|
46 |
+
"head": "Microsoft Word",
|
47 |
+
"head_type": "Product",
|
48 |
+
"relation": "PRODUCED_BY",
|
49 |
+
"tail": "Microsoft",
|
50 |
+
"tail_type": "Company",
|
51 |
+
},
|
52 |
+
{
|
53 |
+
"text": "Microsoft Word is a lightweight app that accessible offline",
|
54 |
+
"head": "Microsoft Word",
|
55 |
+
"head_type": "Product",
|
56 |
+
"relation": "HAS_CHARACTERISTIC",
|
57 |
+
"tail": "lightweight app",
|
58 |
+
"tail_type": "Characteristic",
|
59 |
+
},
|
60 |
+
{
|
61 |
+
"text": "Microsoft Word is a lightweight app that accessible offline",
|
62 |
+
"head": "Microsoft Word",
|
63 |
+
"head_type": "Product",
|
64 |
+
"relation": "HAS_CHARACTERISTIC",
|
65 |
+
"tail": "accessible offline",
|
66 |
+
"tail_type": "Characteristic",
|
67 |
+
},
|
68 |
+
]
|
69 |
+
|
70 |
+
system_prompt = (
|
71 |
+
"# Knowledge Graph Instructions for GPT-4\n"
|
72 |
+
"## 1. Overview\n"
|
73 |
+
"You are a top-tier algorithm designed for extracting information in structured "
|
74 |
+
"formats to build a knowledge graph.\n"
|
75 |
+
"Try to capture as much information from the text as possible without "
|
76 |
+
"sacrificing accuracy. Do not add any information that is not explicitly "
|
77 |
+
"mentioned in the text.\n"
|
78 |
+
"- **Nodes** represent entities and concepts.\n"
|
79 |
+
"- The aim is to achieve simplicity and clarity in the knowledge graph, making it\n"
|
80 |
+
"accessible for a vast audience.\n"
|
81 |
+
"## 2. Labeling Nodes\n"
|
82 |
+
"- **Consistency**: Ensure you use available types for node labels.\n"
|
83 |
+
"Ensure you use basic or elementary types for node labels.\n"
|
84 |
+
"- For example, when you identify an entity representing a person, "
|
85 |
+
"always label it as **'person'**. Avoid using more specific terms "
|
86 |
+
"like 'mathematician' or 'scientist'."
|
87 |
+
"- **Node IDs**: Never utilize integers as node IDs. Node IDs should be "
|
88 |
+
"names or human-readable identifiers found in the text.\n"
|
89 |
+
"- **Relationships** represent connections between entities or concepts.\n"
|
90 |
+
"Ensure consistency and generality in relationship types when constructing "
|
91 |
+
"knowledge graphs. Instead of using specific and momentary types "
|
92 |
+
"such as 'BECAME_PROFESSOR', use more general and timeless relationship types "
|
93 |
+
"like 'PROFESSOR'. Make sure to use general and timeless relationship types!\n"
|
94 |
+
"## 3. Coreference Resolution\n"
|
95 |
+
"- **Maintain Entity Consistency**: When extracting entities, it's vital to "
|
96 |
+
"ensure consistency.\n"
|
97 |
+
'If an entity, such as "John Doe", is mentioned multiple times in the text '
|
98 |
+
'but is referred to by different names or pronouns (e.g., "Joe", "he"),'
|
99 |
+
"always use the most complete identifier for that entity throughout the "
|
100 |
+
'knowledge graph. In this example, use "John Doe" as the entity ID.\n'
|
101 |
+
"Remember, the knowledge graph should be coherent and easily understandable, "
|
102 |
+
"so maintaining consistency in entity references is crucial.\n"
|
103 |
+
"## 4. Strict Compliance\n"
|
104 |
+
"Adhere to the rules strictly. Non-compliance will result in termination."
|
105 |
+
)
|
106 |
+
|
107 |
+
default_prompt = ChatPromptTemplate.from_messages(
|
108 |
+
[
|
109 |
+
(
|
110 |
+
"system",
|
111 |
+
system_prompt,
|
112 |
+
),
|
113 |
+
(
|
114 |
+
"human",
|
115 |
+
(
|
116 |
+
"Tip: Make sure to answer in the correct format and do "
|
117 |
+
"not include any explanations. "
|
118 |
+
"Use the given format to extract information from the "
|
119 |
+
"following input: {input}"
|
120 |
+
),
|
121 |
+
),
|
122 |
+
]
|
123 |
+
)
|
124 |
+
|
125 |
+
|
126 |
+
def _get_additional_info(input_type: str) -> str:
|
127 |
+
# Check if the input_type is one of the allowed values
|
128 |
+
if input_type not in ["node", "relationship", "property"]:
|
129 |
+
raise ValueError("input_type must be 'node', 'relationship', or 'property'")
|
130 |
+
|
131 |
+
# Perform actions based on the input_type
|
132 |
+
if input_type == "node":
|
133 |
+
return (
|
134 |
+
"Ensure you use basic or elementary types for node labels.\n"
|
135 |
+
"For example, when you identify an entity representing a person, "
|
136 |
+
"always label it as **'Person'**. Avoid using more specific terms "
|
137 |
+
"like 'Mathematician' or 'Scientist'"
|
138 |
+
)
|
139 |
+
elif input_type == "relationship":
|
140 |
+
return (
|
141 |
+
"Instead of using specific and momentary types such as "
|
142 |
+
"'BECAME_PROFESSOR', use more general and timeless relationship types "
|
143 |
+
"like 'PROFESSOR'. However, do not sacrifice any accuracy for generality"
|
144 |
+
)
|
145 |
+
elif input_type == "property":
|
146 |
+
return ""
|
147 |
+
return ""
|
148 |
+
|
149 |
+
|
150 |
+
def optional_enum_field(
|
151 |
+
enum_values: Optional[List[str]] = None,
|
152 |
+
description: str = "",
|
153 |
+
input_type: str = "node",
|
154 |
+
llm_type: Optional[str] = None,
|
155 |
+
**field_kwargs: Any,
|
156 |
+
) -> Any:
|
157 |
+
"""Utility function to conditionally create a field with an enum constraint."""
|
158 |
+
# Only openai supports enum param
|
159 |
+
if enum_values and llm_type == "openai-chat":
|
160 |
+
return Field(
|
161 |
+
...,
|
162 |
+
enum=enum_values, # type: ignore[call-arg]
|
163 |
+
description=f"{description}. Available options are {enum_values}",
|
164 |
+
**field_kwargs,
|
165 |
+
)
|
166 |
+
elif enum_values:
|
167 |
+
return Field(
|
168 |
+
...,
|
169 |
+
description=f"{description}. Available options are {enum_values}",
|
170 |
+
**field_kwargs,
|
171 |
+
)
|
172 |
+
else:
|
173 |
+
additional_info = _get_additional_info(input_type)
|
174 |
+
return Field(..., description=description + additional_info, **field_kwargs)
|
175 |
+
|
176 |
+
|
177 |
+
class _Graph(BaseModel):
|
178 |
+
nodes: Optional[List]
|
179 |
+
relationships: Optional[List]
|
180 |
+
|
181 |
+
|
182 |
+
class UnstructuredRelation(BaseModel):
|
183 |
+
head: str = Field(
|
184 |
+
description=(
|
185 |
+
"extracted head entity like Microsoft, Apple, John. "
|
186 |
+
"Must use human-readable unique identifier."
|
187 |
+
)
|
188 |
+
)
|
189 |
+
head_type: str = Field(
|
190 |
+
description="type of the extracted head entity like Person, Company, etc"
|
191 |
+
)
|
192 |
+
relation: str = Field(description="relation between the head and the tail entities")
|
193 |
+
tail: str = Field(
|
194 |
+
description=(
|
195 |
+
"extracted tail entity like Microsoft, Apple, John. "
|
196 |
+
"Must use human-readable unique identifier."
|
197 |
+
)
|
198 |
+
)
|
199 |
+
tail_type: str = Field(
|
200 |
+
description="type of the extracted tail entity like Person, Company, etc"
|
201 |
+
)
|
202 |
+
|
203 |
+
|
204 |
+
def create_unstructured_prompt(
|
205 |
+
node_labels: Optional[List[str]] = None, rel_types: Optional[List[str]] = None
|
206 |
+
) -> ChatPromptTemplate:
|
207 |
+
node_labels_str = str(node_labels) if node_labels else ""
|
208 |
+
rel_types_str = str(rel_types) if rel_types else ""
|
209 |
+
base_string_parts = [
|
210 |
+
"You are a top-tier algorithm designed for extracting information in "
|
211 |
+
"structured formats to build a knowledge graph. Your task is to identify "
|
212 |
+
"the entities and relations requested with the user prompt from a given "
|
213 |
+
"text. You must generate the output in a JSON format containing a list "
|
214 |
+
'with JSON objects. Each object should have the keys: "head", '
|
215 |
+
'"head_type", "relation", "tail", and "tail_type". The "head" '
|
216 |
+
"key must contain the text of the extracted entity with one of the types "
|
217 |
+
"from the provided list in the user prompt.",
|
218 |
+
f'The "head_type" key must contain the type of the extracted head entity, '
|
219 |
+
f"which must be one of the types from {node_labels_str}."
|
220 |
+
if node_labels
|
221 |
+
else "",
|
222 |
+
f'The "relation" key must contain the type of relation between the "head" '
|
223 |
+
f'and the "tail", which must be one of the relations from {rel_types_str}.'
|
224 |
+
if rel_types
|
225 |
+
else "",
|
226 |
+
f'The "tail" key must represent the text of an extracted entity which is '
|
227 |
+
f'the tail of the relation, and the "tail_type" key must contain the type '
|
228 |
+
f"of the tail entity from {node_labels_str}."
|
229 |
+
if node_labels
|
230 |
+
else "",
|
231 |
+
"Attempt to extract as many entities and relations as you can. Maintain "
|
232 |
+
"Entity Consistency: When extracting entities, it's vital to ensure "
|
233 |
+
'consistency. If an entity, such as "John Doe", is mentioned multiple '
|
234 |
+
"times in the text but is referred to by different names or pronouns "
|
235 |
+
'(e.g., "Joe", "he"), always use the most complete identifier for '
|
236 |
+
"that entity. The knowledge graph should be coherent and easily "
|
237 |
+
"understandable, so maintaining consistency in entity references is "
|
238 |
+
"crucial.",
|
239 |
+
"IMPORTANT NOTES:\n- Don't add any explanation and text.",
|
240 |
+
]
|
241 |
+
system_prompt = "\n".join(filter(None, base_string_parts))
|
242 |
+
|
243 |
+
system_message = SystemMessage(content=system_prompt)
|
244 |
+
parser = JsonOutputParser(pydantic_object=UnstructuredRelation)
|
245 |
+
|
246 |
+
human_string_parts = [
|
247 |
+
"Based on the following example, extract entities and "
|
248 |
+
"relations from the provided text.\n\n",
|
249 |
+
"Use the following entity types, don't use other entity "
|
250 |
+
"that is not defined below:"
|
251 |
+
"# ENTITY TYPES:"
|
252 |
+
"{node_labels}"
|
253 |
+
if node_labels
|
254 |
+
else "",
|
255 |
+
"Use the following relation types, don't use other relation "
|
256 |
+
"that is not defined below:"
|
257 |
+
"# RELATION TYPES:"
|
258 |
+
"{rel_types}"
|
259 |
+
if rel_types
|
260 |
+
else "",
|
261 |
+
"Below are a number of examples of text and their extracted "
|
262 |
+
"entities and relationships."
|
263 |
+
"{examples}\n"
|
264 |
+
"For the following text, extract entities and relations as "
|
265 |
+
"in the provided example."
|
266 |
+
"{format_instructions}\nText: {input}",
|
267 |
+
]
|
268 |
+
human_prompt_string = "\n".join(filter(None, human_string_parts))
|
269 |
+
human_prompt = PromptTemplate(
|
270 |
+
template=human_prompt_string,
|
271 |
+
input_variables=["input"],
|
272 |
+
partial_variables={
|
273 |
+
"format_instructions": parser.get_format_instructions(),
|
274 |
+
"node_labels": node_labels,
|
275 |
+
"rel_types": rel_types,
|
276 |
+
"examples": examples,
|
277 |
+
},
|
278 |
+
)
|
279 |
+
|
280 |
+
human_message_prompt = HumanMessagePromptTemplate(prompt=human_prompt)
|
281 |
+
|
282 |
+
chat_prompt = ChatPromptTemplate.from_messages(
|
283 |
+
[system_message, human_message_prompt]
|
284 |
+
)
|
285 |
+
return chat_prompt
|
286 |
+
|
287 |
+
|
288 |
+
def create_simple_model(
|
289 |
+
node_labels: Optional[List[str]] = None,
|
290 |
+
rel_types: Optional[List[str]] = None,
|
291 |
+
node_properties: Union[bool, List[str]] = False,
|
292 |
+
llm_type: Optional[str] = None,
|
293 |
+
relationship_properties: Union[bool, List[str]] = False,
|
294 |
+
) -> Type[_Graph]:
|
295 |
+
"""
|
296 |
+
Create a simple graph model with optional constraints on node
|
297 |
+
and relationship types.
|
298 |
+
|
299 |
+
Args:
|
300 |
+
node_labels (Optional[List[str]]): Specifies the allowed node types.
|
301 |
+
Defaults to None, allowing all node types.
|
302 |
+
rel_types (Optional[List[str]]): Specifies the allowed relationship types.
|
303 |
+
Defaults to None, allowing all relationship types.
|
304 |
+
node_properties (Union[bool, List[str]]): Specifies if node properties should
|
305 |
+
be included. If a list is provided, only properties with keys in the list
|
306 |
+
will be included. If True, all properties are included. Defaults to False.
|
307 |
+
relationship_properties (Union[bool, List[str]]): Specifies if relationship
|
308 |
+
properties should be included. If a list is provided, only properties with
|
309 |
+
keys in the list will be included. If True, all properties are included.
|
310 |
+
Defaults to False.
|
311 |
+
llm_type (Optional[str]): The type of the language model. Defaults to None.
|
312 |
+
Only openai supports enum param: openai-chat.
|
313 |
+
|
314 |
+
Returns:
|
315 |
+
Type[_Graph]: A graph model with the specified constraints.
|
316 |
+
|
317 |
+
Raises:
|
318 |
+
ValueError: If 'id' is included in the node or relationship properties list.
|
319 |
+
"""
|
320 |
+
|
321 |
+
node_fields: Dict[str, Tuple[Any, Any]] = {
|
322 |
+
"id": (
|
323 |
+
str,
|
324 |
+
Field(..., description="Name or human-readable unique identifier."),
|
325 |
+
),
|
326 |
+
"type": (
|
327 |
+
str,
|
328 |
+
optional_enum_field(
|
329 |
+
node_labels,
|
330 |
+
description="The type or label of the node.",
|
331 |
+
input_type="node",
|
332 |
+
llm_type=llm_type,
|
333 |
+
),
|
334 |
+
),
|
335 |
+
}
|
336 |
+
|
337 |
+
if node_properties:
|
338 |
+
if isinstance(node_properties, list) and "id" in node_properties:
|
339 |
+
raise ValueError("The node property 'id' is reserved and cannot be used.")
|
340 |
+
# Map True to empty array
|
341 |
+
node_properties_mapped: List[str] = (
|
342 |
+
[] if node_properties is True else node_properties
|
343 |
+
)
|
344 |
+
|
345 |
+
class Property(BaseModel):
|
346 |
+
"""A single property consisting of key and value"""
|
347 |
+
|
348 |
+
key: str = optional_enum_field(
|
349 |
+
node_properties_mapped,
|
350 |
+
description="Property key.",
|
351 |
+
input_type="property",
|
352 |
+
llm_type=llm_type,
|
353 |
+
)
|
354 |
+
value: str = Field(..., description="value")
|
355 |
+
|
356 |
+
node_fields["properties"] = (
|
357 |
+
Optional[List[Property]],
|
358 |
+
Field(None, description="List of node properties"),
|
359 |
+
)
|
360 |
+
SimpleNode = create_model("SimpleNode", **node_fields) # type: ignore
|
361 |
+
|
362 |
+
relationship_fields: Dict[str, Tuple[Any, Any]] = {
|
363 |
+
"source_node_id": (
|
364 |
+
str,
|
365 |
+
Field(
|
366 |
+
...,
|
367 |
+
description="Name or human-readable unique identifier of source node",
|
368 |
+
),
|
369 |
+
),
|
370 |
+
"source_node_type": (
|
371 |
+
str,
|
372 |
+
optional_enum_field(
|
373 |
+
node_labels,
|
374 |
+
description="The type or label of the source node.",
|
375 |
+
input_type="node",
|
376 |
+
llm_type=llm_type,
|
377 |
+
),
|
378 |
+
),
|
379 |
+
"target_node_id": (
|
380 |
+
str,
|
381 |
+
Field(
|
382 |
+
...,
|
383 |
+
description="Name or human-readable unique identifier of target node",
|
384 |
+
),
|
385 |
+
),
|
386 |
+
"target_node_type": (
|
387 |
+
str,
|
388 |
+
optional_enum_field(
|
389 |
+
node_labels,
|
390 |
+
description="The type or label of the target node.",
|
391 |
+
input_type="node",
|
392 |
+
llm_type=llm_type,
|
393 |
+
),
|
394 |
+
),
|
395 |
+
"type": (
|
396 |
+
str,
|
397 |
+
optional_enum_field(
|
398 |
+
rel_types,
|
399 |
+
description="The type of the relationship.",
|
400 |
+
input_type="relationship",
|
401 |
+
llm_type=llm_type,
|
402 |
+
),
|
403 |
+
),
|
404 |
+
}
|
405 |
+
if relationship_properties:
|
406 |
+
if (
|
407 |
+
isinstance(relationship_properties, list)
|
408 |
+
and "id" in relationship_properties
|
409 |
+
):
|
410 |
+
raise ValueError(
|
411 |
+
"The relationship property 'id' is reserved and cannot be used."
|
412 |
+
)
|
413 |
+
# Map True to empty array
|
414 |
+
relationship_properties_mapped: List[str] = (
|
415 |
+
[] if relationship_properties is True else relationship_properties
|
416 |
+
)
|
417 |
+
|
418 |
+
class RelationshipProperty(BaseModel):
|
419 |
+
"""A single property consisting of key and value"""
|
420 |
+
|
421 |
+
key: str = optional_enum_field(
|
422 |
+
relationship_properties_mapped,
|
423 |
+
description="Property key.",
|
424 |
+
input_type="property",
|
425 |
+
llm_type=llm_type,
|
426 |
+
)
|
427 |
+
value: str = Field(..., description="value")
|
428 |
+
|
429 |
+
relationship_fields["properties"] = (
|
430 |
+
Optional[List[RelationshipProperty]],
|
431 |
+
Field(None, description="List of relationship properties"),
|
432 |
+
)
|
433 |
+
SimpleRelationship = create_model("SimpleRelationship", **relationship_fields) # type: ignore
|
434 |
+
|
435 |
+
class DynamicGraph(_Graph):
|
436 |
+
"""Represents a graph document consisting of nodes and relationships."""
|
437 |
+
|
438 |
+
nodes: Optional[List[SimpleNode]] = Field(description="List of nodes") # type: ignore
|
439 |
+
relationships: Optional[List[SimpleRelationship]] = Field( # type: ignore
|
440 |
+
description="List of relationships"
|
441 |
+
)
|
442 |
+
|
443 |
+
return DynamicGraph
|
444 |
+
|
445 |
+
|
446 |
+
def map_to_base_node(node: Any) -> Node:
|
447 |
+
"""Map the SimpleNode to the base Node."""
|
448 |
+
properties = {}
|
449 |
+
if hasattr(node, "properties") and node.properties:
|
450 |
+
for p in node.properties:
|
451 |
+
properties[format_property_key(p.key)] = p.value
|
452 |
+
return Node(id=node.id, type=node.type, properties=properties)
|
453 |
+
|
454 |
+
|
455 |
+
def map_to_base_relationship(rel: Any) -> Relationship:
|
456 |
+
"""Map the SimpleRelationship to the base Relationship."""
|
457 |
+
source = Node(id=rel.source_node_id, type=rel.source_node_type)
|
458 |
+
target = Node(id=rel.target_node_id, type=rel.target_node_type)
|
459 |
+
properties = {}
|
460 |
+
if hasattr(rel, "properties") and rel.properties:
|
461 |
+
for p in rel.properties:
|
462 |
+
properties[format_property_key(p.key)] = p.value
|
463 |
+
return Relationship(
|
464 |
+
source=source, target=target, type=rel.type, properties=properties
|
465 |
+
)
|
466 |
+
|
467 |
+
|
468 |
+
def _parse_and_clean_json(
|
469 |
+
argument_json: Dict[str, Any],
|
470 |
+
) -> Tuple[List[Node], List[Relationship]]:
|
471 |
+
nodes = []
|
472 |
+
for node in argument_json["nodes"]:
|
473 |
+
if not node.get("id"): # Id is mandatory, skip this node
|
474 |
+
continue
|
475 |
+
node_properties = {}
|
476 |
+
if "properties" in node and node["properties"]:
|
477 |
+
for p in node["properties"]:
|
478 |
+
node_properties[format_property_key(p["key"])] = p["value"]
|
479 |
+
nodes.append(
|
480 |
+
Node(
|
481 |
+
id=node["id"],
|
482 |
+
type=node.get("type", "Node"),
|
483 |
+
properties=node_properties,
|
484 |
+
)
|
485 |
+
)
|
486 |
+
relationships = []
|
487 |
+
for rel in argument_json["relationships"]:
|
488 |
+
# Mandatory props
|
489 |
+
if (
|
490 |
+
not rel.get("source_node_id")
|
491 |
+
or not rel.get("target_node_id")
|
492 |
+
or not rel.get("type")
|
493 |
+
):
|
494 |
+
continue
|
495 |
+
|
496 |
+
# Node type copying if needed from node list
|
497 |
+
if not rel.get("source_node_type"):
|
498 |
+
try:
|
499 |
+
rel["source_node_type"] = [
|
500 |
+
el.get("type")
|
501 |
+
for el in argument_json["nodes"]
|
502 |
+
if el["id"] == rel["source_node_id"]
|
503 |
+
][0]
|
504 |
+
except IndexError:
|
505 |
+
rel["source_node_type"] = None
|
506 |
+
if not rel.get("target_node_type"):
|
507 |
+
try:
|
508 |
+
rel["target_node_type"] = [
|
509 |
+
el.get("type")
|
510 |
+
for el in argument_json["nodes"]
|
511 |
+
if el["id"] == rel["target_node_id"]
|
512 |
+
][0]
|
513 |
+
except IndexError:
|
514 |
+
rel["target_node_type"] = None
|
515 |
+
|
516 |
+
rel_properties = {}
|
517 |
+
if "properties" in rel and rel["properties"]:
|
518 |
+
for p in rel["properties"]:
|
519 |
+
rel_properties[format_property_key(p["key"])] = p["value"]
|
520 |
+
|
521 |
+
source_node = Node(
|
522 |
+
id=rel["source_node_id"],
|
523 |
+
type=rel["source_node_type"],
|
524 |
+
)
|
525 |
+
target_node = Node(
|
526 |
+
id=rel["target_node_id"],
|
527 |
+
type=rel["target_node_type"],
|
528 |
+
)
|
529 |
+
relationships.append(
|
530 |
+
Relationship(
|
531 |
+
source=source_node,
|
532 |
+
target=target_node,
|
533 |
+
type=rel["type"],
|
534 |
+
properties=rel_properties,
|
535 |
+
)
|
536 |
+
)
|
537 |
+
return nodes, relationships
|
538 |
+
|
539 |
+
|
540 |
+
def _format_nodes(nodes: List[Node]) -> List[Node]:
|
541 |
+
return [
|
542 |
+
Node(
|
543 |
+
id=el.id.title() if isinstance(el.id, str) else el.id,
|
544 |
+
type=el.type.capitalize() # type: ignore[arg-type]
|
545 |
+
if el.type
|
546 |
+
else None, # handle empty strings # type: ignore[arg-type]
|
547 |
+
properties=el.properties,
|
548 |
+
)
|
549 |
+
for el in nodes
|
550 |
+
]
|
551 |
+
|
552 |
+
|
553 |
+
def _format_relationships(rels: List[Relationship]) -> List[Relationship]:
|
554 |
+
return [
|
555 |
+
Relationship(
|
556 |
+
source=_format_nodes([el.source])[0],
|
557 |
+
target=_format_nodes([el.target])[0],
|
558 |
+
type=el.type.replace(" ", "_").upper(),
|
559 |
+
properties=el.properties,
|
560 |
+
)
|
561 |
+
for el in rels
|
562 |
+
]
|
563 |
+
|
564 |
+
|
565 |
+
def format_property_key(s: str) -> str:
|
566 |
+
words = s.split()
|
567 |
+
if not words:
|
568 |
+
return s
|
569 |
+
first_word = words[0].lower()
|
570 |
+
capitalized_words = [word.capitalize() for word in words[1:]]
|
571 |
+
return "".join([first_word] + capitalized_words)
|
572 |
+
|
573 |
+
|
574 |
+
def _convert_to_graph_document(
|
575 |
+
raw_schema: Dict[Any, Any],
|
576 |
+
) -> Tuple[List[Node], List[Relationship]]:
|
577 |
+
# If there are validation errors
|
578 |
+
if not raw_schema["parsed"]:
|
579 |
+
try:
|
580 |
+
try: # OpenAI type response
|
581 |
+
argument_json = json.loads(
|
582 |
+
raw_schema["raw"].additional_kwargs["tool_calls"][0]["function"][
|
583 |
+
"arguments"
|
584 |
+
]
|
585 |
+
)
|
586 |
+
except Exception: # Google type response
|
587 |
+
try:
|
588 |
+
argument_json = json.loads(
|
589 |
+
raw_schema["raw"].additional_kwargs["function_call"][
|
590 |
+
"arguments"
|
591 |
+
]
|
592 |
+
)
|
593 |
+
except Exception: # Ollama type response
|
594 |
+
argument_json = raw_schema["raw"].tool_calls[0]["args"]
|
595 |
+
if isinstance(argument_json["nodes"], str):
|
596 |
+
argument_json["nodes"] = json.loads(argument_json["nodes"])
|
597 |
+
if isinstance(argument_json["relationships"], str):
|
598 |
+
argument_json["relationships"] = json.loads(
|
599 |
+
argument_json["relationships"]
|
600 |
+
)
|
601 |
+
|
602 |
+
nodes, relationships = _parse_and_clean_json(argument_json)
|
603 |
+
except Exception: # If we can't parse JSON
|
604 |
+
return ([], [])
|
605 |
+
else: # If there are no validation errors use parsed pydantic object
|
606 |
+
parsed_schema: _Graph = raw_schema["parsed"]
|
607 |
+
nodes = (
|
608 |
+
[map_to_base_node(node) for node in parsed_schema.nodes if node.id]
|
609 |
+
if parsed_schema.nodes
|
610 |
+
else []
|
611 |
+
)
|
612 |
+
|
613 |
+
relationships = (
|
614 |
+
[
|
615 |
+
map_to_base_relationship(rel)
|
616 |
+
for rel in parsed_schema.relationships
|
617 |
+
if rel.type and rel.source_node_id and rel.target_node_id
|
618 |
+
]
|
619 |
+
if parsed_schema.relationships
|
620 |
+
else []
|
621 |
+
)
|
622 |
+
# Title / Capitalize
|
623 |
+
return _format_nodes(nodes), _format_relationships(relationships)
|
624 |
+
|
625 |
+
|
626 |
+
class LLMGraphTransformer:
|
627 |
+
"""Transform documents into graph-based documents using a LLM.
|
628 |
+
|
629 |
+
It allows specifying constraints on the types of nodes and relationships to include
|
630 |
+
in the output graph. The class supports extracting properties for both nodes and
|
631 |
+
relationships.
|
632 |
+
|
633 |
+
Args:
|
634 |
+
llm (BaseLanguageModel): An instance of a language model supporting structured
|
635 |
+
output.
|
636 |
+
allowed_nodes (List[str], optional): Specifies which node types are
|
637 |
+
allowed in the graph. Defaults to an empty list, allowing all node types.
|
638 |
+
allowed_relationships (List[str], optional): Specifies which relationship types
|
639 |
+
are allowed in the graph. Defaults to an empty list, allowing all relationship
|
640 |
+
types.
|
641 |
+
prompt (Optional[ChatPromptTemplate], optional): The prompt to pass to
|
642 |
+
the LLM with additional instructions.
|
643 |
+
strict_mode (bool, optional): Determines whether the transformer should apply
|
644 |
+
filtering to strictly adhere to `allowed_nodes` and `allowed_relationships`.
|
645 |
+
Defaults to True.
|
646 |
+
node_properties (Union[bool, List[str]]): If True, the LLM can extract any
|
647 |
+
node properties from text. Alternatively, a list of valid properties can
|
648 |
+
be provided for the LLM to extract, restricting extraction to those specified.
|
649 |
+
relationship_properties (Union[bool, List[str]]): If True, the LLM can extract
|
650 |
+
any relationship properties from text. Alternatively, a list of valid
|
651 |
+
properties can be provided for the LLM to extract, restricting extraction to
|
652 |
+
those specified.
|
653 |
+
ignore_tool_usage (bool): Indicates whether the transformer should
|
654 |
+
bypass the use of structured output functionality of the language model.
|
655 |
+
If set to True, the transformer will not use the language model's native
|
656 |
+
function calling capabilities to handle structured output. Defaults to False.
|
657 |
+
|
658 |
+
Example:
|
659 |
+
.. code-block:: python
|
660 |
+
from langchain_experimental.graph_transformers import LLMGraphTransformer
|
661 |
+
from langchain_core.documents import Document
|
662 |
+
from langchain_openai import ChatOpenAI
|
663 |
+
|
664 |
+
llm=ChatOpenAI(temperature=0)
|
665 |
+
transformer = LLMGraphTransformer(
|
666 |
+
llm=llm,
|
667 |
+
allowed_nodes=["Person", "Organization"])
|
668 |
+
|
669 |
+
doc = Document(page_content="Elon Musk is suing OpenAI")
|
670 |
+
graph_documents = transformer.convert_to_graph_documents([doc])
|
671 |
+
"""
|
672 |
+
|
673 |
+
def __init__(
|
674 |
+
self,
|
675 |
+
llm: BaseLanguageModel,
|
676 |
+
allowed_nodes: List[str] = [],
|
677 |
+
allowed_relationships: List[str] = [],
|
678 |
+
prompt: Optional[ChatPromptTemplate] = None,
|
679 |
+
strict_mode: bool = True,
|
680 |
+
node_properties: Union[bool, List[str]] = False,
|
681 |
+
relationship_properties: Union[bool, List[str]] = False,
|
682 |
+
ignore_tool_usage: bool = False,
|
683 |
+
) -> None:
|
684 |
+
self.allowed_nodes = allowed_nodes
|
685 |
+
self.allowed_relationships = allowed_relationships
|
686 |
+
self.strict_mode = strict_mode
|
687 |
+
self._function_call = not ignore_tool_usage
|
688 |
+
# Check if the LLM really supports structured output
|
689 |
+
if self._function_call:
|
690 |
+
try:
|
691 |
+
llm.with_structured_output(_Graph)
|
692 |
+
except NotImplementedError:
|
693 |
+
self._function_call = False
|
694 |
+
if not self._function_call:
|
695 |
+
if node_properties or relationship_properties:
|
696 |
+
raise ValueError(
|
697 |
+
"The 'node_properties' and 'relationship_properties' parameters "
|
698 |
+
"cannot be used in combination with a LLM that doesn't support "
|
699 |
+
"native function calling."
|
700 |
+
)
|
701 |
+
try:
|
702 |
+
import json_repair # type: ignore
|
703 |
+
|
704 |
+
self.json_repair = json_repair
|
705 |
+
except ImportError:
|
706 |
+
raise ImportError(
|
707 |
+
"Could not import json_repair python package. "
|
708 |
+
"Please install it with `pip install json-repair`."
|
709 |
+
)
|
710 |
+
prompt = prompt or create_unstructured_prompt(
|
711 |
+
allowed_nodes, allowed_relationships
|
712 |
+
)
|
713 |
+
self.chain = prompt | llm
|
714 |
+
else:
|
715 |
+
# Define chain
|
716 |
+
try:
|
717 |
+
llm_type = llm._llm_type # type: ignore
|
718 |
+
except AttributeError:
|
719 |
+
llm_type = None
|
720 |
+
schema = create_simple_model(
|
721 |
+
allowed_nodes,
|
722 |
+
allowed_relationships,
|
723 |
+
node_properties,
|
724 |
+
llm_type,
|
725 |
+
relationship_properties,
|
726 |
+
)
|
727 |
+
structured_llm = llm.with_structured_output(schema, include_raw=True)
|
728 |
+
prompt = prompt or default_prompt
|
729 |
+
self.chain = prompt | structured_llm
|
730 |
+
|
731 |
+
def process_response(
|
732 |
+
self, document: Document, config: Optional[RunnableConfig] = None
|
733 |
+
) -> GraphDocument:
|
734 |
+
"""
|
735 |
+
Processes a single document, transforming it into a graph document using
|
736 |
+
an LLM based on the model's schema and constraints.
|
737 |
+
"""
|
738 |
+
text = document.page_content
|
739 |
+
raw_schema = self.chain.invoke({"input": text}, config=config)
|
740 |
+
if self._function_call:
|
741 |
+
raw_schema = cast(Dict[Any, Any], raw_schema)
|
742 |
+
nodes, relationships = _convert_to_graph_document(raw_schema)
|
743 |
+
else:
|
744 |
+
nodes_set = set()
|
745 |
+
relationships = []
|
746 |
+
if not isinstance(raw_schema, str):
|
747 |
+
raw_schema = raw_schema.content
|
748 |
+
parsed_json = self.json_repair.loads(raw_schema)
|
749 |
+
if isinstance(parsed_json, dict):
|
750 |
+
parsed_json = [parsed_json]
|
751 |
+
for rel in parsed_json:
|
752 |
+
# Check if mandatory properties are there
|
753 |
+
if (
|
754 |
+
not rel.get("head")
|
755 |
+
or not rel.get("tail")
|
756 |
+
or not rel.get("relation")
|
757 |
+
):
|
758 |
+
continue
|
759 |
+
# Nodes need to be deduplicated using a set
|
760 |
+
# Use default Node label for nodes if missing
|
761 |
+
nodes_set.add((rel["head"], rel.get("head_type", "Node")))
|
762 |
+
nodes_set.add((rel["tail"], rel.get("tail_type", "Node")))
|
763 |
+
source_node = Node(id=rel["head"], type=rel.get("head_type", "Node"))
|
764 |
+
target_node = Node(id=rel["tail"], type=rel.get("tail_type", "Node"))
|
765 |
+
relationships.append(
|
766 |
+
Relationship(
|
767 |
+
source=source_node, target=target_node, type=rel["relation"]
|
768 |
+
)
|
769 |
+
)
|
770 |
+
# Create nodes list
|
771 |
+
nodes = [Node(id=el[0], type=el[1]) for el in list(nodes_set)]
|
772 |
+
# Strict mode filtering
|
773 |
+
if self.strict_mode and (self.allowed_nodes or self.allowed_relationships):
|
774 |
+
if self.allowed_nodes:
|
775 |
+
lower_allowed_nodes = [el.lower() for el in self.allowed_nodes]
|
776 |
+
nodes = [
|
777 |
+
node for node in nodes if node.type.lower() in lower_allowed_nodes
|
778 |
+
]
|
779 |
+
relationships = [
|
780 |
+
rel
|
781 |
+
for rel in relationships
|
782 |
+
if rel.source.type.lower() in lower_allowed_nodes
|
783 |
+
and rel.target.type.lower() in lower_allowed_nodes
|
784 |
+
]
|
785 |
+
if self.allowed_relationships:
|
786 |
+
relationships = [
|
787 |
+
rel
|
788 |
+
for rel in relationships
|
789 |
+
if rel.type.lower()
|
790 |
+
in [el.lower() for el in self.allowed_relationships]
|
791 |
+
]
|
792 |
+
# Add source from document metadata to nodes and relationships
|
793 |
+
source = document.metadata.get('source', 'unknown')
|
794 |
+
for node in nodes:
|
795 |
+
if node.properties is None:
|
796 |
+
node.properties = {}
|
797 |
+
node.properties['source'] = source
|
798 |
+
for rel in relationships:
|
799 |
+
if rel.properties is None:
|
800 |
+
rel.properties = {}
|
801 |
+
rel.properties['source'] = source
|
802 |
+
return GraphDocument(nodes=nodes, relationships=relationships, source=document)
|
803 |
+
|
804 |
+
def convert_to_graph_documents(
|
805 |
+
self, documents: Sequence[Document], config: Optional[RunnableConfig] = None
|
806 |
+
) -> List[GraphDocument]:
|
807 |
+
"""Convert a sequence of documents into graph documents.
|
808 |
+
|
809 |
+
Args:
|
810 |
+
documents (Sequence[Document]): The original documents.
|
811 |
+
kwargs: Additional keyword arguments.
|
812 |
+
|
813 |
+
Returns:
|
814 |
+
Sequence[GraphDocument]: The transformed documents as graphs.
|
815 |
+
"""
|
816 |
+
return [self.process_response(document, config) for document in documents]
|
817 |
+
|
818 |
+
async def aprocess_response(
|
819 |
+
self, document: Document, config: Optional[RunnableConfig] = None
|
820 |
+
) -> GraphDocument:
|
821 |
+
"""
|
822 |
+
Asynchronously processes a single document, transforming it into a
|
823 |
+
graph document.
|
824 |
+
"""
|
825 |
+
text = document.page_content
|
826 |
+
raw_schema = await self.chain.ainvoke({"input": text}, config=config)
|
827 |
+
raw_schema = cast(Dict[Any, Any], raw_schema)
|
828 |
+
nodes, relationships = _convert_to_graph_document(raw_schema)
|
829 |
+
|
830 |
+
if self.strict_mode and (self.allowed_nodes or self.allowed_relationships):
|
831 |
+
if self.allowed_nodes:
|
832 |
+
lower_allowed_nodes = [el.lower() for el in self.allowed_nodes]
|
833 |
+
nodes = [
|
834 |
+
node for node in nodes if node.type.lower() in lower_allowed_nodes
|
835 |
+
]
|
836 |
+
relationships = [
|
837 |
+
rel
|
838 |
+
for rel in relationships
|
839 |
+
if rel.source.type.lower() in lower_allowed_nodes
|
840 |
+
and rel.target.type.lower() in lower_allowed_nodes
|
841 |
+
]
|
842 |
+
if self.allowed_relationships:
|
843 |
+
relationships = [
|
844 |
+
rel
|
845 |
+
for rel in relationships
|
846 |
+
if rel.type.lower()
|
847 |
+
in [el.lower() for el in self.allowed_relationships]
|
848 |
+
]
|
849 |
+
|
850 |
+
# Add source from document metadata to nodes and relationships
|
851 |
+
source = document.metadata.get('source', 'unknown')
|
852 |
+
for node in nodes:
|
853 |
+
if node.properties is None:
|
854 |
+
node.properties = {}
|
855 |
+
node.properties['source'] = source
|
856 |
+
for rel in relationships:
|
857 |
+
if rel.properties is None:
|
858 |
+
rel.properties = {}
|
859 |
+
rel.properties['source'] = source
|
860 |
+
|
861 |
+
return GraphDocument(nodes=nodes, relationships=relationships, source=document)
|
862 |
+
|
863 |
+
async def aconvert_to_graph_documents(
|
864 |
+
self, documents: Sequence[Document], config: Optional[RunnableConfig] = None
|
865 |
+
) -> List[GraphDocument]:
|
866 |
+
"""
|
867 |
+
Asynchronously convert a sequence of documents into graph documents.
|
868 |
+
"""
|
869 |
+
tasks = [
|
870 |
+
asyncio.create_task(self.aprocess_response(document, config))
|
871 |
+
for document in documents
|
872 |
+
]
|
873 |
+
results = await asyncio.gather(*tasks)
|
874 |
+
return results
|
tools/offline_graph_rag_tool.py
ADDED
@@ -0,0 +1,430 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import os
|
3 |
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
4 |
+
sys.path.insert(0, root_dir)
|
5 |
+
import concurrent.futures
|
6 |
+
import functools
|
7 |
+
import numpy as np
|
8 |
+
import faiss
|
9 |
+
import traceback
|
10 |
+
from typing import Dict, List, Optional
|
11 |
+
from termcolor import colored
|
12 |
+
from langchain_anthropic import ChatAnthropic
|
13 |
+
from langchain_openai import ChatOpenAI
|
14 |
+
from langchain_community.graphs import Neo4jGraph
|
15 |
+
from tools.llm_graph_transformer import LLMGraphTransformer
|
16 |
+
from langchain_community.embeddings.fastembed import FastEmbedEmbeddings
|
17 |
+
from langchain_community.vectorstores import FAISS
|
18 |
+
from flashrank import Ranker, RerankRequest
|
19 |
+
from llmsherpa.readers import LayoutPDFReader
|
20 |
+
from langchain.schema import Document
|
21 |
+
from config.load_configs import load_config
|
22 |
+
from langchain_community.docstore.in_memory import InMemoryDocstore
|
23 |
+
from fake_useragent import UserAgent
|
24 |
+
|
25 |
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
26 |
+
sys.path.insert(0, root_dir)
|
27 |
+
|
28 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
29 |
+
load_config(config_path)
|
30 |
+
|
31 |
+
ua = UserAgent()
|
32 |
+
os.environ["USER_AGENT"] = ua.random
|
33 |
+
os.environ["FAISS_OPT_LEVEL"] = "generic"
|
34 |
+
|
35 |
+
|
36 |
+
def timeout(max_timeout):
|
37 |
+
"""Timeout decorator, parameter in seconds."""
|
38 |
+
def timeout_decorator(item):
|
39 |
+
"""Wrap the original function."""
|
40 |
+
@functools.wraps(item)
|
41 |
+
def func_wrapper(*args, **kwargs):
|
42 |
+
"""Closure for function."""
|
43 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
44 |
+
future = executor.submit(item, *args, **kwargs)
|
45 |
+
try:
|
46 |
+
return future.result(max_timeout)
|
47 |
+
except concurrent.futures.TimeoutError:
|
48 |
+
return [Document(page_content=f"Timeout occurred while processing URL: {args[0]}", metadata={"source": args[0]})]
|
49 |
+
return func_wrapper
|
50 |
+
return timeout_decorator
|
51 |
+
|
52 |
+
|
53 |
+
# Change: Added function to deduplicate re-ranked results.
|
54 |
+
def deduplicate_results(results, rerank=True):
|
55 |
+
seen = set()
|
56 |
+
unique_results = []
|
57 |
+
for result in results:
|
58 |
+
# Create a tuple of the content and source to use as a unique identifier
|
59 |
+
if rerank:
|
60 |
+
identifier = (result['text'], result['meta'])
|
61 |
+
else:
|
62 |
+
# When not reranking, result is a tuple (doc, score)
|
63 |
+
doc, score = result
|
64 |
+
identifier = (doc.page_content, doc.metadata.get('source', ''))
|
65 |
+
if identifier not in seen:
|
66 |
+
seen.add(identifier)
|
67 |
+
unique_results.append(result)
|
68 |
+
return unique_results
|
69 |
+
|
70 |
+
|
71 |
+
def index_and_rank(corpus: List[Document], query: str, top_percent: float = 20, batch_size: int = 25) -> List[Dict[str, str]]:
|
72 |
+
print(colored(f"\n\nStarting indexing and ranking with FastEmbeddings and FAISS for {len(corpus)} documents\n\n", "green"))
|
73 |
+
CACHE_DIR = "/app/fastembed_cache"
|
74 |
+
embeddings = FastEmbedEmbeddings(model_name='jinaai/jina-embeddings-v2-small-en', max_length=512, cache_dir=CACHE_DIR)
|
75 |
+
|
76 |
+
print(colored("\n\nCreating FAISS index...\n\n", "green"))
|
77 |
+
|
78 |
+
try:
|
79 |
+
# Initialize an empty FAISS index
|
80 |
+
index = None
|
81 |
+
docstore = InMemoryDocstore({})
|
82 |
+
index_to_docstore_id = {}
|
83 |
+
|
84 |
+
# Process documents in batches
|
85 |
+
for i in range(0, len(corpus), batch_size):
|
86 |
+
batch = corpus[i:i+batch_size]
|
87 |
+
texts = [doc.page_content for doc in batch]
|
88 |
+
metadatas = [doc.metadata for doc in batch]
|
89 |
+
|
90 |
+
print(f"Processing batch {i // batch_size + 1} with {len(texts)} documents")
|
91 |
+
|
92 |
+
# Embed the batch
|
93 |
+
batch_embeddings = embeddings.embed_documents(texts)
|
94 |
+
|
95 |
+
# Convert embeddings to numpy array with float32 dtype
|
96 |
+
batch_embeddings_np = np.array(batch_embeddings, dtype=np.float32)
|
97 |
+
|
98 |
+
if index is None:
|
99 |
+
# Create the index with the first batch
|
100 |
+
index = faiss.IndexFlatIP(batch_embeddings_np.shape[1])
|
101 |
+
|
102 |
+
# Normalize the embeddings
|
103 |
+
faiss.normalize_L2(batch_embeddings_np)
|
104 |
+
|
105 |
+
# Add embeddings to the index
|
106 |
+
start_id = len(index_to_docstore_id)
|
107 |
+
index.add(batch_embeddings_np)
|
108 |
+
|
109 |
+
# Update docstore and index_to_docstore_id
|
110 |
+
for j, (text, metadata) in enumerate(zip(texts, metadatas)):
|
111 |
+
doc_id = f"{start_id + j}"
|
112 |
+
docstore.add({doc_id: Document(page_content=text, metadata=metadata)})
|
113 |
+
index_to_docstore_id[start_id + j] = doc_id
|
114 |
+
|
115 |
+
print(f"Total documents indexed: {len(index_to_docstore_id)}")
|
116 |
+
|
117 |
+
# Create a FAISS retriever
|
118 |
+
retriever = FAISS(embeddings, index, docstore, index_to_docstore_id)
|
119 |
+
|
120 |
+
# Perform the search
|
121 |
+
k = min(40, len(corpus)) # Ensure we don't try to retrieve more documents than we have
|
122 |
+
|
123 |
+
# Change: Retrieve documents based on query in metadata
|
124 |
+
similarity_cache = {}
|
125 |
+
docs = []
|
126 |
+
for doc in corpus:
|
127 |
+
query = doc.metadata.get('query', '')
|
128 |
+
# Check if we've already performed this search
|
129 |
+
if query in similarity_cache:
|
130 |
+
cached_results = similarity_cache[query]
|
131 |
+
docs.extend(cached_results)
|
132 |
+
else:
|
133 |
+
# Perform the similarity search
|
134 |
+
search_results = retriever.similarity_search_with_score(query, k=k)
|
135 |
+
|
136 |
+
# Cache the results
|
137 |
+
similarity_cache[query] = search_results
|
138 |
+
|
139 |
+
# Add to docs
|
140 |
+
docs.extend(search_results)
|
141 |
+
|
142 |
+
docs = deduplicate_results(docs, rerank=False)
|
143 |
+
|
144 |
+
print(colored(f"\n\nRetrieved {len(docs)} documents\n\n", "green"))
|
145 |
+
|
146 |
+
passages = []
|
147 |
+
for idx, (doc, score) in enumerate(docs, start=1):
|
148 |
+
try:
|
149 |
+
passage = {
|
150 |
+
"id": idx,
|
151 |
+
"text": doc.page_content,
|
152 |
+
"meta": doc.metadata.get("source", {"source": "unknown"}),
|
153 |
+
"score": float(score) # Convert score to float
|
154 |
+
}
|
155 |
+
passages.append(passage)
|
156 |
+
except Exception as e:
|
157 |
+
print(colored(f"Error in creating passage: {str(e)}", "red"))
|
158 |
+
traceback.print_exc()
|
159 |
+
|
160 |
+
print(colored("\n\nRe-ranking documents...\n\n", "green"))
|
161 |
+
# Change: reranker done based on query in metadata
|
162 |
+
CACHE_DIR_RANKER = "/app/reranker_cache"
|
163 |
+
ranker = Ranker(cache_dir=CACHE_DIR_RANKER)
|
164 |
+
results = []
|
165 |
+
processed_queries = set()
|
166 |
+
|
167 |
+
# Perform reranking with query caching
|
168 |
+
for doc in corpus:
|
169 |
+
query = doc.metadata.get('query', '')
|
170 |
+
|
171 |
+
# Skip if we've already processed this query
|
172 |
+
if query in processed_queries:
|
173 |
+
continue
|
174 |
+
|
175 |
+
rerankrequest = RerankRequest(query=query, passages=passages)
|
176 |
+
result = ranker.rerank(rerankrequest)
|
177 |
+
results.extend(result)
|
178 |
+
|
179 |
+
# Mark this query as processed
|
180 |
+
processed_queries.add(query)
|
181 |
+
|
182 |
+
results = deduplicate_results(results, rerank=True)
|
183 |
+
|
184 |
+
print(colored(f"\n\nRe-ranking complete with {len(results)} documents\n\n", "green"))
|
185 |
+
|
186 |
+
# Sort results by score in descending order
|
187 |
+
sorted_results = sorted(results, key=lambda x: x['score'], reverse=True)
|
188 |
+
|
189 |
+
# Calculate the number of results to return based on the percentage
|
190 |
+
num_results = max(1, int(len(sorted_results) * (top_percent / 100)))
|
191 |
+
top_results = sorted_results[:num_results]
|
192 |
+
|
193 |
+
final_results = [
|
194 |
+
{
|
195 |
+
"text": result['text'],
|
196 |
+
"meta": result['meta'],
|
197 |
+
"score": result['score']
|
198 |
+
}
|
199 |
+
for result in top_results
|
200 |
+
]
|
201 |
+
|
202 |
+
print(colored(f"\n\nReturned top {top_percent}% of results ({len(final_results)} documents)\n\n", "green"))
|
203 |
+
|
204 |
+
# Add debug information about scores
|
205 |
+
scores = [result['score'] for result in results]
|
206 |
+
print(f"Score distribution: min={min(scores):.4f}, max={max(scores):.4f}, mean={np.mean(scores):.4f}, median={np.median(scores):.4f}")
|
207 |
+
print(f"Unique scores: {len(set(scores))}")
|
208 |
+
if final_results:
|
209 |
+
print(f"Score range for top {top_percent}% results: {final_results[-1]['score']:.4f} to {final_results[0]['score']:.4f}")
|
210 |
+
|
211 |
+
except Exception as e:
|
212 |
+
print(colored(f"Error in indexing and ranking: {str(e)}", "red"))
|
213 |
+
traceback.print_exc()
|
214 |
+
final_results = [{"text": "Error in indexing and ranking", "meta": {"source": "unknown"}, "score": 0.0}]
|
215 |
+
|
216 |
+
return final_results
|
217 |
+
|
218 |
+
def run_hybrid_graph_retrieval(graph: Neo4jGraph = None, corpus: List[Document] = None, query: str = None, hybrid: bool = False):
|
219 |
+
print(colored(f"\n\Initiating Retrieval...\n\n", "green"))
|
220 |
+
|
221 |
+
if hybrid:
|
222 |
+
print(colored("Running Hybrid Retrieval...", "yellow"))
|
223 |
+
unstructured_data = index_and_rank(corpus, query)
|
224 |
+
|
225 |
+
query = f"""
|
226 |
+
MATCH p = (n)-[r]->(m)
|
227 |
+
WHERE COUNT {{(n)--()}} > 30
|
228 |
+
RETURN p AS Path
|
229 |
+
LIMIT 85
|
230 |
+
"""
|
231 |
+
response = graph.query(query)
|
232 |
+
retrieved_context = f"Important Relationships:{response}\n\n Additional Context:{unstructured_data}"
|
233 |
+
|
234 |
+
else:
|
235 |
+
print(colored("Running Dense Only Retrieval...", "yellow"))
|
236 |
+
unstructured_data = index_and_rank(corpus, query)
|
237 |
+
retrieved_context = f"Additional Context:{unstructured_data}"
|
238 |
+
|
239 |
+
return retrieved_context
|
240 |
+
|
241 |
+
|
242 |
+
@timeout(20) # Change: Takes url and query as input
|
243 |
+
def intelligent_chunking(url: str, query: str) -> List[Document]:
|
244 |
+
try:
|
245 |
+
print(colored(f"\n\nStarting Intelligent Chunking with LLM Sherpa for URL: {url}\n\n", "green"))
|
246 |
+
llmsherpa_api_url = os.environ.get('LLM_SHERPA_SERVER')
|
247 |
+
|
248 |
+
if not llmsherpa_api_url:
|
249 |
+
raise ValueError("LLM_SHERPA_SERVER environment variable is not set")
|
250 |
+
|
251 |
+
corpus = []
|
252 |
+
|
253 |
+
try:
|
254 |
+
print(colored("Starting LLM Sherpa LayoutPDFReader...\n\n", "yellow"))
|
255 |
+
reader = LayoutPDFReader(llmsherpa_api_url)
|
256 |
+
doc = reader.read_pdf(url)
|
257 |
+
print(colored("Finished LLM Sherpa LayoutPDFReader...\n\n", "yellow"))
|
258 |
+
except Exception as e:
|
259 |
+
print(colored(f"Error in LLM Sherpa LayoutPDFReader: {str(e)}", "red"))
|
260 |
+
traceback.print_exc()
|
261 |
+
doc = None
|
262 |
+
|
263 |
+
if doc:
|
264 |
+
for chunk in doc.chunks():
|
265 |
+
document = Document(
|
266 |
+
page_content=chunk.to_context_text(),
|
267 |
+
metadata={"source": url, "query": query} # Change: Added query to metadata
|
268 |
+
)
|
269 |
+
|
270 |
+
if len(document.page_content) > 30:
|
271 |
+
corpus.append(document)
|
272 |
+
|
273 |
+
print(colored(f"Created corpus with {len(corpus)} documents", "green"))
|
274 |
+
|
275 |
+
|
276 |
+
if not doc:
|
277 |
+
print(colored(f"No document to append to corpus", "red"))
|
278 |
+
|
279 |
+
# print(colored(f"DEBUG: Corpus: {corpus}", "yellow"))
|
280 |
+
return corpus
|
281 |
+
|
282 |
+
except concurrent.futures.TimeoutError:
|
283 |
+
print(colored(f"Timeout occurred while processing URL: {url}", "red"))
|
284 |
+
return [Document(page_content=f"Timeout occurred while processing URL: {url}", metadata={"source": url})]
|
285 |
+
except Exception as e:
|
286 |
+
print(colored(f"Error in Intelligent Chunking for URL {url}: {str(e)}", "red"))
|
287 |
+
traceback.print_exc()
|
288 |
+
return [Document(page_content=f"Error in Intelligent Chunking for URL: {url}", metadata={"source": url})]
|
289 |
+
|
290 |
+
|
291 |
+
def clear_neo4j_database(graph: Neo4jGraph):
|
292 |
+
"""
|
293 |
+
Clear all nodes and relationships from the Neo4j database.
|
294 |
+
"""
|
295 |
+
try:
|
296 |
+
print(colored("\n\nClearing Neo4j database...\n\n", "yellow"))
|
297 |
+
# Delete all relationships first
|
298 |
+
graph.query("MATCH ()-[r]->() DELETE r")
|
299 |
+
# Then delete all nodes
|
300 |
+
graph.query("MATCH (n) DELETE n")
|
301 |
+
print(colored("Neo4j database cleared successfully.\n\n", "green"))
|
302 |
+
except Exception as e:
|
303 |
+
print(colored(f"Error clearing Neo4j database: {str(e)}", "red"))
|
304 |
+
traceback.print_exc()
|
305 |
+
|
306 |
+
|
307 |
+
def create_graph_index(
|
308 |
+
documents: List[Document] = None,
|
309 |
+
allowed_relationships: List[str] = None,
|
310 |
+
allowed_nodes: List[str] = None,
|
311 |
+
query: str = None,
|
312 |
+
graph: Neo4jGraph = None,
|
313 |
+
batch_size: int = 10,
|
314 |
+
max_workers: int = 5 # Number of threads in the pool
|
315 |
+
) -> Neo4jGraph:
|
316 |
+
|
317 |
+
if os.environ.get('LLM_SERVER') == "openai":
|
318 |
+
llm = ChatOpenAI(temperature=0, model_name="gpt-4o-mini")
|
319 |
+
else:
|
320 |
+
llm = ChatAnthropic(temperature=0, model_name="claude-3-haiku-20240307")
|
321 |
+
|
322 |
+
llm_transformer = LLMGraphTransformer(
|
323 |
+
llm=llm,
|
324 |
+
allowed_nodes=allowed_nodes,
|
325 |
+
allowed_relationships=allowed_relationships,
|
326 |
+
node_properties=True,
|
327 |
+
relationship_properties=True
|
328 |
+
)
|
329 |
+
|
330 |
+
total_docs = len(documents)
|
331 |
+
|
332 |
+
# Prepare batches
|
333 |
+
batches = [
|
334 |
+
documents[i:i + batch_size]
|
335 |
+
for i in range(0, total_docs, batch_size)
|
336 |
+
]
|
337 |
+
total_batches = len(batches)
|
338 |
+
|
339 |
+
print(colored(f"\nTotal documents: {total_docs}, Total batches: {total_batches}\n", "green"))
|
340 |
+
|
341 |
+
graph_documents = []
|
342 |
+
|
343 |
+
def process_batch(batch_docs, batch_number):
|
344 |
+
print(colored(f"\nProcessing batch {batch_number} of {total_batches}\n", "yellow"))
|
345 |
+
try:
|
346 |
+
batch_graph_docs = llm_transformer.convert_to_graph_documents(batch_docs)
|
347 |
+
print(colored(f"Finished batch {batch_number}\n", "green"))
|
348 |
+
return batch_graph_docs
|
349 |
+
except Exception as e:
|
350 |
+
print(colored(f"Error processing batch {batch_number}: {str(e)}", "red"))
|
351 |
+
traceback.print_exc()
|
352 |
+
return []
|
353 |
+
|
354 |
+
# Use ThreadPoolExecutor for parallel processing of batches
|
355 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
356 |
+
# Submit all batches to the executor
|
357 |
+
future_to_batch = {
|
358 |
+
executor.submit(process_batch, batch, idx + 1): idx + 1
|
359 |
+
for idx, batch in enumerate(batches)
|
360 |
+
}
|
361 |
+
|
362 |
+
# Collect results as they complete
|
363 |
+
for future in concurrent.futures.as_completed(future_to_batch):
|
364 |
+
batch_number = future_to_batch[future]
|
365 |
+
try:
|
366 |
+
batch_graph_docs = future.result()
|
367 |
+
graph_documents.extend(batch_graph_docs)
|
368 |
+
except Exception as e:
|
369 |
+
print(colored(f"Exception in batch {batch_number}: {str(e)}", "red"))
|
370 |
+
traceback.print_exc()
|
371 |
+
|
372 |
+
print(colored(f"\nTotal graph documents: {len(graph_documents)}\n", "green"))
|
373 |
+
|
374 |
+
# Add documents to the graph
|
375 |
+
graph.add_graph_documents(
|
376 |
+
graph_documents,
|
377 |
+
baseEntityLabel=True,
|
378 |
+
include_source=True,
|
379 |
+
)
|
380 |
+
|
381 |
+
return graph
|
382 |
+
|
383 |
+
|
384 |
+
def run_rag(urls: List[str], allowed_nodes: List[str] = None, allowed_relationships: List[str] = None, query: List[str] = None, hybrid: bool = False) -> List[Dict[str, str]]:
|
385 |
+
# Change: adapted to take query and url as input.
|
386 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=min(len(urls), 5)) as executor:
|
387 |
+
futures = [executor.submit(intelligent_chunking, url, query) for url, query in zip(urls, query)]
|
388 |
+
chunks_list = [future.result() for future in concurrent.futures.as_completed(futures)]
|
389 |
+
|
390 |
+
|
391 |
+
corpus = [item for sublist in chunks_list for item in sublist]
|
392 |
+
|
393 |
+
print(colored(f"\n\nTotal documents in corpus after chunking: {len(corpus)}\n\n", "green"))
|
394 |
+
|
395 |
+
|
396 |
+
print(colored(f"\n\n DEBUG HYBRID VALUE: {hybrid}\n\n", "yellow"))
|
397 |
+
|
398 |
+
if hybrid:
|
399 |
+
print(colored(f"\n\n Creating Graph Index...\n\n", "green"))
|
400 |
+
graph = Neo4jGraph()
|
401 |
+
clear_neo4j_database(graph)
|
402 |
+
graph = create_graph_index(documents=corpus, allowed_nodes=allowed_nodes, allowed_relationships=allowed_relationships, query=query, graph=graph)
|
403 |
+
else:
|
404 |
+
graph = None
|
405 |
+
|
406 |
+
retrieved_context = run_hybrid_graph_retrieval(graph=graph, corpus=corpus, query=query, hybrid=hybrid)
|
407 |
+
|
408 |
+
retrieved_context = str(retrieved_context)
|
409 |
+
|
410 |
+
return retrieved_context
|
411 |
+
|
412 |
+
if __name__ == "__main__":
|
413 |
+
# For testing purposes.
|
414 |
+
url1 = "https://www.reddit.com/r/microsoft/comments/1bkikl1/regretting_buying_copilot_for_microsoft_365"
|
415 |
+
url2 = "'https://www.reddit.com/r/microsoft_365_copilot/comments/1chtqtg/do_you_actually_find_365_copilot_useful_in_your"
|
416 |
+
# url3 = "https://developers.googleblog.com/en/new-features-for-the-gemini-api-and-google-ai-studio/"
|
417 |
+
|
418 |
+
# query = "cheapest macbook"
|
419 |
+
|
420 |
+
# urls = [url1, url2, url3]
|
421 |
+
urls = [url1, url2]
|
422 |
+
query = ["Co-pilot Microsoft"]
|
423 |
+
allowed_nodes = None
|
424 |
+
allowed_relationships = None
|
425 |
+
hybrid = False
|
426 |
+
results = run_rag(urls, allowed_nodes=allowed_nodes, allowed_relationships=allowed_relationships, query=query, hybrid=hybrid)
|
427 |
+
|
428 |
+
print(colored(f"\n\n RESULTS: {results}", "green"))
|
429 |
+
|
430 |
+
print(f"\n\n RESULTS: {results}")
|
tools/offline_graph_rag_tool_with_async.py
ADDED
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import os
|
3 |
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
4 |
+
sys.path.insert(0, root_dir)
|
5 |
+
import concurrent.futures
|
6 |
+
import functools
|
7 |
+
import numpy as np
|
8 |
+
import faiss
|
9 |
+
import traceback
|
10 |
+
import tempfile
|
11 |
+
from typing import Dict, List, Optional
|
12 |
+
from termcolor import colored
|
13 |
+
from langchain_anthropic import ChatAnthropic
|
14 |
+
from langchain_openai import ChatOpenAI
|
15 |
+
from langchain_community.graphs import Neo4jGraph
|
16 |
+
# from langchain_experimental.graph_transformers.llm import LLMGraphTransformer
|
17 |
+
from tools.llm_graph_transformer import LLMGraphTransformer
|
18 |
+
from langchain_core.runnables import RunnableConfig
|
19 |
+
# from langchain_community.vectorstores.neo4j_vector import Neo4jVector
|
20 |
+
from langchain_community.embeddings.fastembed import FastEmbedEmbeddings
|
21 |
+
from langchain_community.vectorstores import FAISS
|
22 |
+
from flashrank import Ranker, RerankRequest
|
23 |
+
from llmsherpa.readers import LayoutPDFReader
|
24 |
+
from langchain.schema import Document
|
25 |
+
from config.load_configs import load_config
|
26 |
+
from langchain_community.docstore.in_memory import InMemoryDocstore
|
27 |
+
from fake_useragent import UserAgent
|
28 |
+
import asyncio
|
29 |
+
|
30 |
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
31 |
+
sys.path.insert(0, root_dir)
|
32 |
+
|
33 |
+
config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'config.yaml')
|
34 |
+
load_config(config_path)
|
35 |
+
|
36 |
+
ua = UserAgent()
|
37 |
+
os.environ["USER_AGENT"] = ua.random
|
38 |
+
os.environ["FAISS_OPT_LEVEL"] = "generic"
|
39 |
+
|
40 |
+
|
41 |
+
def timeout(max_timeout):
|
42 |
+
"""Timeout decorator, parameter in seconds."""
|
43 |
+
def timeout_decorator(item):
|
44 |
+
"""Wrap the original function."""
|
45 |
+
@functools.wraps(item)
|
46 |
+
def func_wrapper(*args, **kwargs):
|
47 |
+
"""Closure for function."""
|
48 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
49 |
+
future = executor.submit(item, *args, **kwargs)
|
50 |
+
try:
|
51 |
+
return future.result(max_timeout)
|
52 |
+
except concurrent.futures.TimeoutError:
|
53 |
+
return [Document(page_content=f"Timeout occurred while processing URL: {args[0]}", metadata={"source": args[0]})]
|
54 |
+
return func_wrapper
|
55 |
+
return timeout_decorator
|
56 |
+
|
57 |
+
|
58 |
+
# Change: Added function to deduplicate re-ranked results.
|
59 |
+
def deduplicate_results(results, rerank=True):
|
60 |
+
seen = set()
|
61 |
+
unique_results = []
|
62 |
+
for result in results:
|
63 |
+
# Create a tuple of the content and source to use as a unique identifier
|
64 |
+
if rerank:
|
65 |
+
identifier = (result['text'], result['meta'])
|
66 |
+
else:
|
67 |
+
# When not reranking, result is a tuple (doc, score)
|
68 |
+
doc, score = result
|
69 |
+
identifier = (doc.page_content, doc.metadata.get('source', ''))
|
70 |
+
if identifier not in seen:
|
71 |
+
seen.add(identifier)
|
72 |
+
unique_results.append(result)
|
73 |
+
return unique_results
|
74 |
+
|
75 |
+
|
76 |
+
def index_and_rank(corpus: List[Document], query: str, top_percent: float = 20, batch_size: int = 25) -> List[Dict[str, str]]:
|
77 |
+
print(colored(f"\n\nStarting indexing and ranking with FastEmbeddings and FAISS for {len(corpus)} documents\n\n", "green"))
|
78 |
+
CACHE_DIR = "/app/fastembed_cache"
|
79 |
+
embeddings = FastEmbedEmbeddings(model_name='jinaai/jina-embeddings-v2-small-en', max_length=512, cache_dir=CACHE_DIR)
|
80 |
+
|
81 |
+
print(colored("\n\nCreating FAISS index...\n\n", "green"))
|
82 |
+
|
83 |
+
try:
|
84 |
+
# Initialize an empty FAISS index
|
85 |
+
index = None
|
86 |
+
docstore = InMemoryDocstore({})
|
87 |
+
index_to_docstore_id = {}
|
88 |
+
|
89 |
+
# Process documents in batches
|
90 |
+
for i in range(0, len(corpus), batch_size):
|
91 |
+
batch = corpus[i:i+batch_size]
|
92 |
+
texts = [doc.page_content for doc in batch]
|
93 |
+
metadatas = [doc.metadata for doc in batch]
|
94 |
+
|
95 |
+
print(f"Processing batch {i // batch_size + 1} with {len(texts)} documents")
|
96 |
+
|
97 |
+
# Embed the batch
|
98 |
+
batch_embeddings = embeddings.embed_documents(texts)
|
99 |
+
|
100 |
+
# Convert embeddings to numpy array with float32 dtype
|
101 |
+
batch_embeddings_np = np.array(batch_embeddings, dtype=np.float32)
|
102 |
+
|
103 |
+
if index is None:
|
104 |
+
# Create the index with the first batch
|
105 |
+
index = faiss.IndexFlatIP(batch_embeddings_np.shape[1])
|
106 |
+
|
107 |
+
# Normalize the embeddings
|
108 |
+
faiss.normalize_L2(batch_embeddings_np)
|
109 |
+
|
110 |
+
# Add embeddings to the index
|
111 |
+
start_id = len(index_to_docstore_id)
|
112 |
+
index.add(batch_embeddings_np)
|
113 |
+
|
114 |
+
# Update docstore and index_to_docstore_id
|
115 |
+
for j, (text, metadata) in enumerate(zip(texts, metadatas)):
|
116 |
+
doc_id = f"{start_id + j}"
|
117 |
+
docstore.add({doc_id: Document(page_content=text, metadata=metadata)})
|
118 |
+
index_to_docstore_id[start_id + j] = doc_id
|
119 |
+
|
120 |
+
print(f"Total documents indexed: {len(index_to_docstore_id)}")
|
121 |
+
|
122 |
+
# Create a FAISS retriever
|
123 |
+
retriever = FAISS(embeddings, index, docstore, index_to_docstore_id)
|
124 |
+
|
125 |
+
# Perform the search
|
126 |
+
k = min(40, len(corpus)) # Ensure we don't try to retrieve more documents than we have
|
127 |
+
|
128 |
+
# Change: Retrieve documents based on query in metadata
|
129 |
+
similarity_cache = {}
|
130 |
+
docs = []
|
131 |
+
for doc in corpus:
|
132 |
+
query = doc.metadata.get('query', '')
|
133 |
+
# Check if we've already performed this search
|
134 |
+
if query in similarity_cache:
|
135 |
+
cached_results = similarity_cache[query]
|
136 |
+
docs.extend(cached_results)
|
137 |
+
else:
|
138 |
+
# Perform the similarity search
|
139 |
+
search_results = retriever.similarity_search_with_score(query, k=k)
|
140 |
+
|
141 |
+
# Cache the results
|
142 |
+
similarity_cache[query] = search_results
|
143 |
+
|
144 |
+
# Add to docs
|
145 |
+
docs.extend(search_results)
|
146 |
+
|
147 |
+
docs = deduplicate_results(docs, rerank=False)
|
148 |
+
|
149 |
+
print(colored(f"\n\nRetrieved {len(docs)} documents\n\n", "green"))
|
150 |
+
|
151 |
+
passages = []
|
152 |
+
for idx, (doc, score) in enumerate(docs, start=1):
|
153 |
+
try:
|
154 |
+
passage = {
|
155 |
+
"id": idx,
|
156 |
+
"text": doc.page_content,
|
157 |
+
"meta": doc.metadata.get("source", {"source": "unknown"}),
|
158 |
+
"score": float(score) # Convert score to float
|
159 |
+
}
|
160 |
+
passages.append(passage)
|
161 |
+
except Exception as e:
|
162 |
+
print(colored(f"Error in creating passage: {str(e)}", "red"))
|
163 |
+
traceback.print_exc()
|
164 |
+
|
165 |
+
print(colored("\n\nRe-ranking documents...\n\n", "green"))
|
166 |
+
# Change: reranker done based on query in metadata
|
167 |
+
CACHE_DIR_RANKER = "/app/reranker_cache"
|
168 |
+
ranker = Ranker(cache_dir=CACHE_DIR_RANKER)
|
169 |
+
results = []
|
170 |
+
processed_queries = set()
|
171 |
+
|
172 |
+
# Perform reranking with query caching
|
173 |
+
for doc in corpus:
|
174 |
+
query = doc.metadata.get('query', '')
|
175 |
+
|
176 |
+
# Skip if we've already processed this query
|
177 |
+
if query in processed_queries:
|
178 |
+
continue
|
179 |
+
|
180 |
+
rerankrequest = RerankRequest(query=query, passages=passages)
|
181 |
+
result = ranker.rerank(rerankrequest)
|
182 |
+
results.extend(result)
|
183 |
+
|
184 |
+
# Mark this query as processed
|
185 |
+
processed_queries.add(query)
|
186 |
+
|
187 |
+
results = deduplicate_results(results, rerank=True)
|
188 |
+
|
189 |
+
print(colored(f"\n\nRe-ranking complete with {len(results)} documents\n\n", "green"))
|
190 |
+
|
191 |
+
# Sort results by score in descending order
|
192 |
+
sorted_results = sorted(results, key=lambda x: x['score'], reverse=True)
|
193 |
+
|
194 |
+
# Calculate the number of results to return based on the percentage
|
195 |
+
num_results = max(1, int(len(sorted_results) * (top_percent / 100)))
|
196 |
+
top_results = sorted_results[:num_results]
|
197 |
+
|
198 |
+
final_results = [
|
199 |
+
{
|
200 |
+
"text": result['text'],
|
201 |
+
"meta": result['meta'],
|
202 |
+
"score": result['score']
|
203 |
+
}
|
204 |
+
for result in top_results
|
205 |
+
]
|
206 |
+
|
207 |
+
print(colored(f"\n\nReturned top {top_percent}% of results ({len(final_results)} documents)\n\n", "green"))
|
208 |
+
|
209 |
+
# Add debug information about scores
|
210 |
+
scores = [result['score'] for result in results]
|
211 |
+
print(f"Score distribution: min={min(scores):.4f}, max={max(scores):.4f}, mean={np.mean(scores):.4f}, median={np.median(scores):.4f}")
|
212 |
+
print(f"Unique scores: {len(set(scores))}")
|
213 |
+
if final_results:
|
214 |
+
print(f"Score range for top {top_percent}% results: {final_results[-1]['score']:.4f} to {final_results[0]['score']:.4f}")
|
215 |
+
|
216 |
+
except Exception as e:
|
217 |
+
print(colored(f"Error in indexing and ranking: {str(e)}", "red"))
|
218 |
+
traceback.print_exc()
|
219 |
+
final_results = [{"text": "Error in indexing and ranking", "meta": {"source": "unknown"}, "score": 0.0}]
|
220 |
+
|
221 |
+
return final_results
|
222 |
+
|
223 |
+
def run_hybrid_graph_retrieval(graph: Neo4jGraph = None, corpus: List[Document] = None, query: str = None, hybrid: bool = False):
|
224 |
+
print(colored(f"\n\Initiating Retrieval...\n\n", "green"))
|
225 |
+
|
226 |
+
if hybrid:
|
227 |
+
print(colored("Running Hybrid Retrieval...", "yellow"))
|
228 |
+
unstructured_data = index_and_rank(corpus, query)
|
229 |
+
|
230 |
+
query = f"""
|
231 |
+
MATCH p = (n)-[r]->(m)
|
232 |
+
WHERE COUNT {{(n)--()}} > 30
|
233 |
+
RETURN p AS Path
|
234 |
+
LIMIT 85
|
235 |
+
"""
|
236 |
+
response = graph.query(query)
|
237 |
+
retrieved_context = f"Important Relationships:{response}\n\n Additional Context:{unstructured_data}"
|
238 |
+
|
239 |
+
else:
|
240 |
+
print(colored("Running Dense Only Retrieval...", "yellow"))
|
241 |
+
unstructured_data = index_and_rank(corpus, query)
|
242 |
+
retrieved_context = f"Additional Context:{unstructured_data}"
|
243 |
+
|
244 |
+
return retrieved_context
|
245 |
+
|
246 |
+
|
247 |
+
@timeout(20) # Change: Takes url and query as input
|
248 |
+
def intelligent_chunking(url: str, query: str) -> List[Document]:
|
249 |
+
try:
|
250 |
+
print(colored(f"\n\nStarting Intelligent Chunking with LLM Sherpa for URL: {url}\n\n", "green"))
|
251 |
+
llmsherpa_api_url = os.environ.get('LLM_SHERPA_SERVER')
|
252 |
+
|
253 |
+
if not llmsherpa_api_url:
|
254 |
+
raise ValueError("LLM_SHERPA_SERVER environment variable is not set")
|
255 |
+
|
256 |
+
corpus = []
|
257 |
+
|
258 |
+
try:
|
259 |
+
print(colored("Starting LLM Sherpa LayoutPDFReader...\n\n", "yellow"))
|
260 |
+
reader = LayoutPDFReader(llmsherpa_api_url)
|
261 |
+
doc = reader.read_pdf(url)
|
262 |
+
print(colored("Finished LLM Sherpa LayoutPDFReader...\n\n", "yellow"))
|
263 |
+
except Exception as e:
|
264 |
+
print(colored(f"Error in LLM Sherpa LayoutPDFReader: {str(e)}", "red"))
|
265 |
+
traceback.print_exc()
|
266 |
+
doc = None
|
267 |
+
|
268 |
+
if doc:
|
269 |
+
for chunk in doc.chunks():
|
270 |
+
document = Document(
|
271 |
+
page_content=chunk.to_context_text(),
|
272 |
+
metadata={"source": url, "query": query} # Change: Added query to metadata
|
273 |
+
)
|
274 |
+
|
275 |
+
if len(document.page_content) > 30:
|
276 |
+
corpus.append(document)
|
277 |
+
|
278 |
+
print(colored(f"Created corpus with {len(corpus)} documents", "green"))
|
279 |
+
|
280 |
+
|
281 |
+
if not doc:
|
282 |
+
print(colored(f"No document to append to corpus", "red"))
|
283 |
+
|
284 |
+
# print(colored(f"DEBUG: Corpus: {corpus}", "yellow"))
|
285 |
+
return corpus
|
286 |
+
|
287 |
+
except concurrent.futures.TimeoutError:
|
288 |
+
print(colored(f"Timeout occurred while processing URL: {url}", "red"))
|
289 |
+
return [Document(page_content=f"Timeout occurred while processing URL: {url}", metadata={"source": url})]
|
290 |
+
except Exception as e:
|
291 |
+
print(colored(f"Error in Intelligent Chunking for URL {url}: {str(e)}", "red"))
|
292 |
+
traceback.print_exc()
|
293 |
+
return [Document(page_content=f"Error in Intelligent Chunking for URL: {url}", metadata={"source": url})]
|
294 |
+
|
295 |
+
|
296 |
+
def clear_neo4j_database(graph: Neo4jGraph):
|
297 |
+
"""
|
298 |
+
Clear all nodes and relationships from the Neo4j database.
|
299 |
+
"""
|
300 |
+
try:
|
301 |
+
print(colored("\n\nClearing Neo4j database...\n\n", "yellow"))
|
302 |
+
# Delete all relationships first
|
303 |
+
graph.query("MATCH ()-[r]->() DELETE r")
|
304 |
+
# Then delete all nodes
|
305 |
+
graph.query("MATCH (n) DELETE n")
|
306 |
+
print(colored("Neo4j database cleared successfully.\n\n", "green"))
|
307 |
+
except Exception as e:
|
308 |
+
print(colored(f"Error clearing Neo4j database: {str(e)}", "red"))
|
309 |
+
traceback.print_exc()
|
310 |
+
|
311 |
+
def create_graph_index(
|
312 |
+
documents: List[Document] = None,
|
313 |
+
allowed_relationships: List[str] = None,
|
314 |
+
allowed_nodes: List[str] = None,
|
315 |
+
query: str = None,
|
316 |
+
graph: Neo4jGraph = None,
|
317 |
+
batch_size: int = 10,
|
318 |
+
max_workers: int = 5 # Number of threads in the pool
|
319 |
+
) -> Neo4jGraph:
|
320 |
+
|
321 |
+
if os.environ.get('LLM_SERVER') == "openai":
|
322 |
+
llm = ChatOpenAI(temperature=0, model_name="gpt-4o-mini")
|
323 |
+
else:
|
324 |
+
llm = ChatAnthropic(temperature=0, model_name="claude-3-haiku-20240307")
|
325 |
+
|
326 |
+
llm_transformer = LLMGraphTransformer(
|
327 |
+
llm=llm,
|
328 |
+
allowed_nodes=allowed_nodes,
|
329 |
+
allowed_relationships=allowed_relationships,
|
330 |
+
node_properties=True,
|
331 |
+
relationship_properties=True
|
332 |
+
)
|
333 |
+
|
334 |
+
total_docs = len(documents)
|
335 |
+
|
336 |
+
# Prepare batches
|
337 |
+
batches = [
|
338 |
+
documents[i:i + batch_size]
|
339 |
+
for i in range(0, total_docs, batch_size)
|
340 |
+
]
|
341 |
+
total_batches = len(batches)
|
342 |
+
|
343 |
+
print(colored(f"\nTotal documents: {total_docs}, Total batches: {total_batches}\n", "green"))
|
344 |
+
|
345 |
+
graph_documents = []
|
346 |
+
|
347 |
+
async def process_batch_async(batch_docs, batch_number):
|
348 |
+
print(colored(f"\nProcessing batch {batch_number} of {total_batches}\n", "yellow"))
|
349 |
+
try:
|
350 |
+
tasks = [
|
351 |
+
asyncio.create_task(llm_transformer.aprocess_response(doc))
|
352 |
+
for doc in batch_docs
|
353 |
+
]
|
354 |
+
batch_graph_docs = await asyncio.gather(*tasks)
|
355 |
+
print(colored(f"Finished batch {batch_number}\n", "green"))
|
356 |
+
return batch_graph_docs
|
357 |
+
except Exception as e:
|
358 |
+
print(colored(f"Error processing batch {batch_number}: {str(e)}", "red"))
|
359 |
+
traceback.print_exc()
|
360 |
+
return []
|
361 |
+
|
362 |
+
for idx, batch in enumerate(batches):
|
363 |
+
batch_number = idx + 1
|
364 |
+
batch_graph_docs = asyncio.run(process_batch_async(batch, batch_number))
|
365 |
+
graph_documents.extend(batch_graph_docs)
|
366 |
+
|
367 |
+
print(colored(f"\nTotal graph documents: {len(graph_documents)}\n", "green"))
|
368 |
+
|
369 |
+
# Add documents to the graph
|
370 |
+
graph.add_graph_documents(
|
371 |
+
graph_documents,
|
372 |
+
baseEntityLabel=True,
|
373 |
+
include_source=True,
|
374 |
+
)
|
375 |
+
|
376 |
+
return graph
|
377 |
+
|
378 |
+
|
379 |
+
def run_rag(urls: List[str], allowed_nodes: List[str] = None, allowed_relationships: List[str] = None, query: List[str] = None, hybrid: bool = False) -> List[Dict[str, str]]:
|
380 |
+
# Change: adapted to take query and url as input.
|
381 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=min(len(urls), 5)) as executor:
|
382 |
+
futures = [executor.submit(intelligent_chunking, url, query) for url, query in zip(urls, query)]
|
383 |
+
chunks_list = [future.result() for future in concurrent.futures.as_completed(futures)]
|
384 |
+
|
385 |
+
|
386 |
+
corpus = [item for sublist in chunks_list for item in sublist]
|
387 |
+
|
388 |
+
print(colored(f"\n\nTotal documents in corpus after chunking: {len(corpus)}\n\n", "green"))
|
389 |
+
|
390 |
+
|
391 |
+
print(colored(f"\n\n DEBUG HYBRID VALUE: {hybrid}\n\n", "yellow"))
|
392 |
+
|
393 |
+
if hybrid:
|
394 |
+
print(colored(f"\n\n Creating Graph Index...\n\n", "green"))
|
395 |
+
graph = Neo4jGraph()
|
396 |
+
clear_neo4j_database(graph)
|
397 |
+
graph = create_graph_index(documents=corpus, allowed_nodes=allowed_nodes, allowed_relationships=allowed_relationships, query=query, graph=graph)
|
398 |
+
else:
|
399 |
+
graph = None
|
400 |
+
|
401 |
+
retrieved_context = run_hybrid_graph_retrieval(graph=graph, corpus=corpus, query=query, hybrid=hybrid)
|
402 |
+
|
403 |
+
retrieved_context = str(retrieved_context)
|
404 |
+
|
405 |
+
return retrieved_context
|
406 |
+
|
407 |
+
if __name__ == "__main__":
|
408 |
+
# For testing purposes.
|
409 |
+
url1 = "https://www.reddit.com/r/microsoft/comments/1bkikl1/regretting_buying_copilot_for_microsoft_365"
|
410 |
+
url2 = "'https://www.reddit.com/r/microsoft_365_copilot/comments/1chtqtg/do_you_actually_find_365_copilot_useful_in_your"
|
411 |
+
# url3 = "https://developers.googleblog.com/en/new-features-for-the-gemini-api-and-google-ai-studio/"
|
412 |
+
|
413 |
+
# query = "cheapest macbook"
|
414 |
+
|
415 |
+
# urls = [url1, url2, url3]
|
416 |
+
urls = [url1, url2]
|
417 |
+
query = ["Co-pilot Microsoft"]
|
418 |
+
allowed_nodes = None
|
419 |
+
allowed_relationships = None
|
420 |
+
hybrid = False
|
421 |
+
results = run_rag(urls, allowed_nodes=allowed_nodes, allowed_relationships=allowed_relationships, query=query, hybrid=hybrid)
|
422 |
+
|
423 |
+
print(colored(f"\n\n RESULTS: {results}", "green"))
|
424 |
+
|
425 |
+
print(f"\n\n RESULTS: {results}")
|
utils/__init__.py
ADDED
File without changes
|