[ADD]ADDED multiple llm porvider options with token and trial argument from fronend. added semanctic search with odoo docs as a context before going to code.
Browse files- Gradio_UI.py +90 -7
- README.md +56 -8
- app.py +103 -28
- prompts.yaml +43 -132
- requirements.txt +2 -0
- tools/final_answer.py +26 -18
- tools/odoo_code_agent_16.py +26 -24
- tools/odoo_code_agent_17.py +26 -24
- tools/odoo_code_agent_18.py +26 -24
- tools/odoo_documentation_search.py +53 -8
Gradio_UI.py
CHANGED
@@ -111,10 +111,13 @@ def pull_messages_from_step(
|
|
111 |
# Calculate duration and token information
|
112 |
step_footnote = f"{step_number}"
|
113 |
if hasattr(step_log, "input_token_count") and hasattr(step_log, "output_token_count"):
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
|
|
|
|
|
|
118 |
if hasattr(step_log, "duration"):
|
119 |
step_duration = f" | Duration: {round(float(step_log.duration), 2)}" if step_log.duration else None
|
120 |
step_footnote += step_duration
|
@@ -144,8 +147,9 @@ def stream_to_gradio(
|
|
144 |
if hasattr(agent.model, "last_input_token_count"):
|
145 |
if agent.model.last_input_token_count is not None:
|
146 |
total_input_tokens += agent.model.last_input_token_count
|
147 |
-
if agent.model
|
148 |
-
|
|
|
149 |
if isinstance(step_log, ActionStep):
|
150 |
step_log.input_token_count = agent.model.last_input_token_count
|
151 |
step_log.output_token_count = agent.model.last_output_token_count
|
@@ -269,9 +273,63 @@ class GradioUI:
|
|
269 |
def launch(self, **kwargs):
|
270 |
import gradio as gr
|
271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
272 |
with gr.Blocks(fill_height=True) as demo:
|
273 |
stored_messages = gr.State([])
|
274 |
file_uploads_log = gr.State([])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
275 |
chatbot = gr.Chatbot(
|
276 |
label="Agent",
|
277 |
type="messages",
|
@@ -282,6 +340,7 @@ class GradioUI:
|
|
282 |
resizeable=True,
|
283 |
scale=1,
|
284 |
)
|
|
|
285 |
# If an upload folder is provided, enable the upload feature
|
286 |
if self.file_upload_folder is not None:
|
287 |
upload_file = gr.File(label="Upload a file")
|
@@ -291,12 +350,36 @@ class GradioUI:
|
|
291 |
[upload_file, file_uploads_log],
|
292 |
[upload_status, file_uploads_log],
|
293 |
)
|
|
|
294 |
text_input = gr.Textbox(lines=1, label="Chat Message")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
295 |
text_input.submit(
|
296 |
self.log_user_message,
|
297 |
[text_input, file_uploads_log],
|
298 |
[stored_messages, text_input],
|
299 |
-
).then(
|
|
|
|
|
|
|
|
|
300 |
|
301 |
demo.launch(debug=True, share=True, **kwargs)
|
302 |
|
|
|
111 |
# Calculate duration and token information
|
112 |
step_footnote = f"{step_number}"
|
113 |
if hasattr(step_log, "input_token_count") and hasattr(step_log, "output_token_count"):
|
114 |
+
input_tokens = step_log.input_token_count
|
115 |
+
output_tokens = step_log.output_token_count
|
116 |
+
if input_tokens is not None and output_tokens is not None:
|
117 |
+
token_str = (
|
118 |
+
f" | Input-tokens:{input_tokens:,} | Output-tokens:{output_tokens:,}"
|
119 |
+
)
|
120 |
+
step_footnote += token_str
|
121 |
if hasattr(step_log, "duration"):
|
122 |
step_duration = f" | Duration: {round(float(step_log.duration), 2)}" if step_log.duration else None
|
123 |
step_footnote += step_duration
|
|
|
147 |
if hasattr(agent.model, "last_input_token_count"):
|
148 |
if agent.model.last_input_token_count is not None:
|
149 |
total_input_tokens += agent.model.last_input_token_count
|
150 |
+
if hasattr(agent.model, "last_output_token_count"):
|
151 |
+
if agent.model.last_output_token_count is not None:
|
152 |
+
total_output_tokens += agent.model.last_output_token_count
|
153 |
if isinstance(step_log, ActionStep):
|
154 |
step_log.input_token_count = agent.model.last_input_token_count
|
155 |
step_log.output_token_count = agent.model.last_output_token_count
|
|
|
273 |
def launch(self, **kwargs):
|
274 |
import gradio as gr
|
275 |
|
276 |
+
# Read model_providers from app.py
|
277 |
+
with open("app.py", "r") as f:
|
278 |
+
app_content = f.read()
|
279 |
+
model_providers_match = re.search(r"model_providers = ({[^{}]*(?:{[^{}]*}[^{}]*)*})", app_content, re.DOTALL)
|
280 |
+
if model_providers_match:
|
281 |
+
model_providers = eval(model_providers_match.group(1))
|
282 |
+
else:
|
283 |
+
model_providers = {}
|
284 |
+
|
285 |
with gr.Blocks(fill_height=True) as demo:
|
286 |
stored_messages = gr.State([])
|
287 |
file_uploads_log = gr.State([])
|
288 |
+
|
289 |
+
# Add provider selection dropdown
|
290 |
+
provider_names = list(model_providers.keys())
|
291 |
+
provider_dropdown = gr.Dropdown(
|
292 |
+
choices=provider_names,
|
293 |
+
label="Select LLM Provider",
|
294 |
+
value=provider_names[0] if provider_names else None,
|
295 |
+
)
|
296 |
+
|
297 |
+
# Add max_steps number input
|
298 |
+
max_steps_number = gr.Number(
|
299 |
+
label="Max Steps",
|
300 |
+
value=6,
|
301 |
+
precision=0,
|
302 |
+
)
|
303 |
+
|
304 |
+
# Add max_tokens number input
|
305 |
+
max_tokens_number = gr.Number(
|
306 |
+
label="Max Tokens",
|
307 |
+
value=1000,
|
308 |
+
precision=0,
|
309 |
+
)
|
310 |
+
|
311 |
+
# Add API key textboxes for each provider
|
312 |
+
api_key_textboxes = {}
|
313 |
+
for provider_name, provider_config in model_providers.items():
|
314 |
+
api_key_textbox = gr.Textbox(
|
315 |
+
label=f"{provider_name} API Key",
|
316 |
+
type="password",
|
317 |
+
visible=False,
|
318 |
+
)
|
319 |
+
api_key_textboxes[provider_name] = api_key_textbox
|
320 |
+
|
321 |
+
def update_api_key_visibility(selected_provider):
|
322 |
+
visibility = {}
|
323 |
+
for provider_name in model_providers.keys():
|
324 |
+
visibility[provider_name] = provider_name == selected_provider
|
325 |
+
return [visibility[provider_name] for provider_name in model_providers.keys()]
|
326 |
+
|
327 |
+
provider_dropdown.change(
|
328 |
+
update_api_key_visibility,
|
329 |
+
inputs=[provider_dropdown],
|
330 |
+
outputs=[api_key_textboxes[provider_name] for provider_name in model_providers.keys()],
|
331 |
+
)
|
332 |
+
|
333 |
chatbot = gr.Chatbot(
|
334 |
label="Agent",
|
335 |
type="messages",
|
|
|
340 |
resizeable=True,
|
341 |
scale=1,
|
342 |
)
|
343 |
+
|
344 |
# If an upload folder is provided, enable the upload feature
|
345 |
if self.file_upload_folder is not None:
|
346 |
upload_file = gr.File(label="Upload a file")
|
|
|
350 |
[upload_file, file_uploads_log],
|
351 |
[upload_status, file_uploads_log],
|
352 |
)
|
353 |
+
|
354 |
text_input = gr.Textbox(lines=1, label="Chat Message")
|
355 |
+
|
356 |
+
def interact_with_agent(prompt, messages, selected_provider, max_steps, max_tokens, *api_keys):
|
357 |
+
messages.append(gr.ChatMessage(role="user", content=prompt))
|
358 |
+
yield messages
|
359 |
+
|
360 |
+
# Prepare additional arguments for the agent
|
361 |
+
additional_args = {
|
362 |
+
"selected_provider": selected_provider,
|
363 |
+
"max_steps": max_steps,
|
364 |
+
"max_tokens": max_tokens,
|
365 |
+
}
|
366 |
+
for i, provider_name in enumerate(model_providers.keys()):
|
367 |
+
additional_args[f"{provider_name}_api_key"] = api_keys[i]
|
368 |
+
|
369 |
+
for msg in stream_to_gradio(self.agent, task=prompt, reset_agent_memory=False, additional_args=additional_args):
|
370 |
+
messages.append(msg)
|
371 |
+
yield messages
|
372 |
+
yield messages
|
373 |
+
|
374 |
text_input.submit(
|
375 |
self.log_user_message,
|
376 |
[text_input, file_uploads_log],
|
377 |
[stored_messages, text_input],
|
378 |
+
).then(
|
379 |
+
interact_with_agent,
|
380 |
+
[stored_messages, chatbot, provider_dropdown, max_steps_number, max_tokens_number] + [api_key_textboxes[provider_name] for provider_name in model_providers.keys()],
|
381 |
+
[chatbot]
|
382 |
+
)
|
383 |
|
384 |
demo.launch(debug=True, share=True, **kwargs)
|
385 |
|
README.md
CHANGED
@@ -21,20 +21,64 @@ This project provides tools and agents for searching for jobs on LinkedIn and In
|
|
21 |
|
22 |
## Tools
|
23 |
|
24 |
-
* **final\_answer:** Formats and presents final answers in a human-readable format, optionally zipping referenced files
|
|
|
|
|
25 |
* **linkedin\_job\_search:** Searches for job postings on LinkedIn and Indeed based on job title, location, and work mode (remote, hybrid, in-office) for Odoo profiles. It requires a Brave API key.
|
|
|
|
|
|
|
|
|
|
|
26 |
* **odoo\_code\_agent\_16:** Generates Odoo code for version 16 based on the user's query and Odoo documentation.
|
|
|
|
|
|
|
27 |
* **odoo\_code\_agent\_17:** Generates Odoo code for version 17 based on the user's query and Odoo documentation.
|
|
|
|
|
|
|
28 |
* **odoo\_code\_agent\_18:** Generates Odoo code for version 18 based on the user's query and Odoo documentation.
|
|
|
|
|
|
|
29 |
* **odoo\_documentation\_search:** Searches the Odoo documentation for a given query.
|
|
|
|
|
|
|
|
|
30 |
* **visit\_webpage:** Visits a webpage at a given URL and reads its content as a markdown string.
|
|
|
|
|
|
|
31 |
* **web\_search:** Performs a DuckDuckGo web search based on a given query and returns the top search results.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
|
33 |
## Usage Examples
|
34 |
|
35 |
* **final\_answer:** This tool is typically used by the agent to present the final result to the user. It doesn't require direct user input.
|
36 |
* **linkedin\_job\_search:** To search for Odoo developer jobs in London, use the following input:
|
37 |
-
```
|
38 |
{
|
39 |
"position": "Odoo Developer",
|
40 |
"location": "London",
|
@@ -42,38 +86,38 @@ This project provides tools and agents for searching for jobs on LinkedIn and In
|
|
42 |
}
|
43 |
```
|
44 |
* **odoo\_code\_agent\_16:** To generate code for creating a new module in Odoo 16, use the following input:
|
45 |
-
```
|
46 |
{
|
47 |
"query": "create a new module"
|
48 |
}
|
49 |
```
|
50 |
* **odoo\_code\_agent\_17:** To generate code for creating a new module in Odoo 17, use the following input:
|
51 |
-
```
|
52 |
{
|
53 |
"query": "create a new module"
|
54 |
}
|
55 |
```
|
56 |
* **odoo\_code\_agent\_18:** To generate code for creating a new module in Odoo 18, use the following input:
|
57 |
-
```
|
58 |
{
|
59 |
"query": "create a new module"
|
60 |
}
|
61 |
```
|
62 |
* **odoo\_documentation\_search:** To search for information on how to create a new module in Odoo 17.0, use the following input:
|
63 |
-
```
|
64 |
{
|
65 |
"query": "how to create a new module",
|
66 |
"version": "17.0"
|
67 |
}
|
68 |
```
|
69 |
* **visit\_webpage:** To visit the Odoo website, use the following input:
|
70 |
-
```
|
71 |
{
|
72 |
"url": "https://www.odoo.com"
|
73 |
}
|
74 |
```
|
75 |
* **web\_search:** To search for the latest Odoo news, use the following input:
|
76 |
-
```
|
77 |
{
|
78 |
"query": "latest Odoo news"
|
79 |
}
|
@@ -83,6 +127,10 @@ This project provides tools and agents for searching for jobs on LinkedIn and In
|
|
83 |
|
84 |
This project uses agents to orchestrate the tools and accomplish specific tasks. The main agent is defined in `app.py`.
|
85 |
|
|
|
|
|
|
|
|
|
86 |
## Configuration
|
87 |
|
88 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
21 |
|
22 |
## Tools
|
23 |
|
24 |
+
* **final\_answer:** Formats and presents final answers in a human-readable format, optionally zipping referenced files and providing a download link for generated code.
|
25 |
+
* **Inputs:** The final answer to be presented to the user.
|
26 |
+
* **Outputs:** A formatted string representing the final answer, with an optional download link for code.
|
27 |
* **linkedin\_job\_search:** Searches for job postings on LinkedIn and Indeed based on job title, location, and work mode (remote, hybrid, in-office) for Odoo profiles. It requires a Brave API key.
|
28 |
+
* **Inputs:**
|
29 |
+
* `position`: The job title to search for (e.g., "Odoo Developer").
|
30 |
+
* `location`: The location to search in (e.g., "London").
|
31 |
+
* `work_mode`: The work mode (e.g., "remote", "hybrid", "in-office").
|
32 |
+
* **Outputs:** A list of job postings matching the search criteria.
|
33 |
* **odoo\_code\_agent\_16:** Generates Odoo code for version 16 based on the user's query and Odoo documentation.
|
34 |
+
* **Inputs:**
|
35 |
+
* `query`: A natural language query describing the desired Odoo code (e.g., "create a new module to display a list of products").
|
36 |
+
* **Outputs:** An Odoo code snippet that implements the requested functionality.
|
37 |
* **odoo\_code\_agent\_17:** Generates Odoo code for version 17 based on the user's query and Odoo documentation.
|
38 |
+
* **Inputs:**
|
39 |
+
* `query`: A natural language query describing the desired Odoo code (e.g., "create a new module to add a field to the product model").
|
40 |
+
* **Outputs:** An Odoo code snippet that implements the requested functionality.
|
41 |
* **odoo\_code\_agent\_18:** Generates Odoo code for version 18 based on the user's query and Odoo documentation.
|
42 |
+
* **Inputs:**
|
43 |
+
* `query`: A natural language query describing the desired Odoo code (e.g., "create a new module to display a list of sales orders").
|
44 |
+
* **Outputs:** An Odoo code snippet that implements the requested functionality.
|
45 |
* **odoo\_documentation\_search:** Searches the Odoo documentation for a given query.
|
46 |
+
* **Inputs:**
|
47 |
+
* `query`: A natural language query describing the desired information (e.g., "how to create a new view").
|
48 |
+
* `version`: The Odoo version to search in (e.g., "16.0", "17.0", "18.0").
|
49 |
+
* **Outputs:** A list of documentation snippets matching the search criteria.
|
50 |
* **visit\_webpage:** Visits a webpage at a given URL and reads its content as a markdown string.
|
51 |
+
* **Inputs:**
|
52 |
+
* `url`: The URL of the webpage to visit (e.g., "https://www.odoo.com").
|
53 |
+
* **Outputs:** The content of the webpage as a markdown string.
|
54 |
* **web\_search:** Performs a DuckDuckGo web search based on a given query and returns the top search results.
|
55 |
+
* **Inputs:**
|
56 |
+
* `query`: A natural language query to search for (e.g., "latest Odoo news").
|
57 |
+
* **Outputs:** A list of search results matching the search criteria.
|
58 |
+
|
59 |
+
## Multi-LLM Support
|
60 |
+
|
61 |
+
This project supports multiple Large Language Models (LLMs) through the Hugging Face `HfApiModel` class. You can configure the LLM provider and model ID in the `app.py` file.
|
62 |
+
|
63 |
+
The following LLM providers are supported:
|
64 |
+
|
65 |
+
* **Qwen:** Uses the Qwen/Qwen2.5-Coder-32B-Instruct model.
|
66 |
+
* **HuggingFace:** Uses a Hugging Face Endpoint.
|
67 |
+
* **OpenAI:** Uses the OpenAI GPT-4 model. Requires an OpenAI API key.
|
68 |
+
* **Anthropic:** Uses the Anthropic Claude model. Requires an Anthropic API key.
|
69 |
+
* **Groq:** Uses the Groq Mixtral model. Requires a Groq API key.
|
70 |
+
* **Google:** Uses the Google Gemini Pro model. Requires a Google API key.
|
71 |
+
* **Custom:** Allows you to specify a custom model ID.
|
72 |
+
|
73 |
+
To select a different LLM provider, modify the `selected_provider` variable in the `app.py` file. You may also need to set the corresponding API key as an environment variable.
|
74 |
+
|
75 |
+
The Odoo code agent tools use the `google/flan-t5-small` model for summarization.
|
76 |
|
77 |
## Usage Examples
|
78 |
|
79 |
* **final\_answer:** This tool is typically used by the agent to present the final result to the user. It doesn't require direct user input.
|
80 |
* **linkedin\_job\_search:** To search for Odoo developer jobs in London, use the following input:
|
81 |
+
```json
|
82 |
{
|
83 |
"position": "Odoo Developer",
|
84 |
"location": "London",
|
|
|
86 |
}
|
87 |
```
|
88 |
* **odoo\_code\_agent\_16:** To generate code for creating a new module in Odoo 16, use the following input:
|
89 |
+
```json
|
90 |
{
|
91 |
"query": "create a new module"
|
92 |
}
|
93 |
```
|
94 |
* **odoo\_code\_agent\_17:** To generate code for creating a new module in Odoo 17, use the following input:
|
95 |
+
```json
|
96 |
{
|
97 |
"query": "create a new module"
|
98 |
}
|
99 |
```
|
100 |
* **odoo\_code\_agent\_18:** To generate code for creating a new module in Odoo 18, use the following input:
|
101 |
+
```json
|
102 |
{
|
103 |
"query": "create a new module"
|
104 |
}
|
105 |
```
|
106 |
* **odoo\_documentation\_search:** To search for information on how to create a new module in Odoo 17.0, use the following input:
|
107 |
+
```json
|
108 |
{
|
109 |
"query": "how to create a new module",
|
110 |
"version": "17.0"
|
111 |
}
|
112 |
```
|
113 |
* **visit\_webpage:** To visit the Odoo website, use the following input:
|
114 |
+
```json
|
115 |
{
|
116 |
"url": "https://www.odoo.com"
|
117 |
}
|
118 |
```
|
119 |
* **web\_search:** To search for the latest Odoo news, use the following input:
|
120 |
+
```json
|
121 |
{
|
122 |
"query": "latest Odoo news"
|
123 |
}
|
|
|
127 |
|
128 |
This project uses agents to orchestrate the tools and accomplish specific tasks. The main agent is defined in `app.py`.
|
129 |
|
130 |
+
## Downloading Generated Code
|
131 |
+
|
132 |
+
The `final_answer` tool can now provide a download link for generated code. To enable this feature, the Odoo code agent tools automatically include a download link in the final answer.
|
133 |
+
|
134 |
## Configuration
|
135 |
|
136 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
CHANGED
@@ -12,7 +12,11 @@ from tools.odoo_code_agent_16 import OdooCodeAgent16
|
|
12 |
from tools.odoo_code_agent_17 import OdooCodeAgent17
|
13 |
from tools.odoo_code_agent_18 import OdooCodeAgent18
|
14 |
|
|
|
15 |
from Gradio_UI import GradioUI
|
|
|
|
|
|
|
16 |
|
17 |
# Below is an example of a tool that does nothing. Amaze us with your creativity !
|
18 |
@tool
|
@@ -40,46 +44,117 @@ def get_current_time_in_timezone(timezone: str) -> str:
|
|
40 |
except Exception as e:
|
41 |
return f"Error fetching time for timezone '{timezone}': {str(e)}"
|
42 |
|
|
|
|
|
43 |
|
44 |
final_answer = FinalAnswerTool()
|
45 |
visit_webpage = VisitWebpageTool()
|
46 |
web_search = DuckDuckGoSearchTool()
|
47 |
job_search_tool = LinkedInJobSearchTool()
|
48 |
odoo_documentation_search_tool = OdooDocumentationSearchTool()
|
49 |
-
odoo_code_agent_16_tool = OdooCodeAgent16()
|
50 |
-
odoo_code_agent_17_tool = OdooCodeAgent17()
|
51 |
-
odoo_code_agent_18_tool = OdooCodeAgent18()
|
52 |
|
53 |
|
54 |
# If the agent does not answer, the model is overloaded, please use another model or the following Hugging Face Endpoint that also contains qwen2.5 coder:
|
55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
#
|
61 |
-
|
62 |
-
|
63 |
-
|
|
|
|
|
64 |
|
65 |
|
66 |
# Import tool from Hub
|
67 |
image_generation_tool = load_tool("agents-course/text-to-image", trust_remote_code=True)
|
68 |
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
model
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
from tools.odoo_code_agent_17 import OdooCodeAgent17
|
13 |
from tools.odoo_code_agent_18 import OdooCodeAgent18
|
14 |
|
15 |
+
from dotenv import load_dotenv
|
16 |
from Gradio_UI import GradioUI
|
17 |
+
load_dotenv()
|
18 |
+
|
19 |
+
import os
|
20 |
|
21 |
# Below is an example of a tool that does nothing. Amaze us with your creativity !
|
22 |
@tool
|
|
|
44 |
except Exception as e:
|
45 |
return f"Error fetching time for timezone '{timezone}': {str(e)}"
|
46 |
|
47 |
+
with open("prompts.yaml", 'r') as stream:
|
48 |
+
prompt_templates = yaml.safe_load(stream)
|
49 |
|
50 |
final_answer = FinalAnswerTool()
|
51 |
visit_webpage = VisitWebpageTool()
|
52 |
web_search = DuckDuckGoSearchTool()
|
53 |
job_search_tool = LinkedInJobSearchTool()
|
54 |
odoo_documentation_search_tool = OdooDocumentationSearchTool()
|
55 |
+
odoo_code_agent_16_tool = OdooCodeAgent16(prompt_templates["system_prompt"])
|
56 |
+
odoo_code_agent_17_tool = OdooCodeAgent17(prompt_templates["system_prompt"])
|
57 |
+
odoo_code_agent_18_tool = OdooCodeAgent18(prompt_templates["system_prompt"])
|
58 |
|
59 |
|
60 |
# If the agent does not answer, the model is overloaded, please use another model or the following Hugging Face Endpoint that also contains qwen2.5 coder:
|
61 |
+
model_providers = {
|
62 |
+
"Qwen": {
|
63 |
+
"model_id": "Qwen/Qwen2.5-Coder-32B-Instruct",
|
64 |
+
"api_key_env_var": None
|
65 |
+
},
|
66 |
+
"HuggingFace": {
|
67 |
+
"model_id": "https://pflgm2locj2t89co.us-east-1.aws.endpoints.huggingface.cloud",
|
68 |
+
"api_key_env_var": None
|
69 |
+
},
|
70 |
+
"OpenAI": {
|
71 |
+
"model_id": "gpt-4",
|
72 |
+
"api_key_env_var": "OPENAI_API_KEY"
|
73 |
+
},
|
74 |
+
"Anthropic": {
|
75 |
+
"model_id": "claude-v1",
|
76 |
+
"api_key_env_var": "ANTHROPIC_API_KEY"
|
77 |
+
},
|
78 |
+
"Groq": {
|
79 |
+
"model_id": "mixtral-8x7b-32768",
|
80 |
+
"api_key_env_var": "GROQ_API_KEY"
|
81 |
+
},
|
82 |
+
"Google": {
|
83 |
+
"model_id": "gemini-pro",
|
84 |
+
"api_key_env_var": "GOOGLE_API_KEY"
|
85 |
+
},
|
86 |
+
"Custom": {
|
87 |
+
"model_id": None,
|
88 |
+
"api_key_env_var": None
|
89 |
+
}
|
90 |
+
}
|
91 |
+
|
92 |
+
|
93 |
+
selected_provider = "HuggingFace"
|
94 |
|
95 |
+
model_id = model_providers[selected_provider]["model_id"]
|
96 |
+
|
97 |
+
# model = HfApiModel(
|
98 |
+
# max_tokens=1000,
|
99 |
+
# temperature=0.5,
|
100 |
+
# model_id=model_id,
|
101 |
+
# custom_role_conversions=None,
|
102 |
+
# api_key=os.environ.get(model_providers[selected_provider]["api_key_env_var"]) if model_providers[selected_provider]["api_key_env_var"] else None
|
103 |
+
# )
|
104 |
|
105 |
|
106 |
# Import tool from Hub
|
107 |
image_generation_tool = load_tool("agents-course/text-to-image", trust_remote_code=True)
|
108 |
|
109 |
+
|
110 |
+
|
111 |
+
def launch_gradio_ui(additional_args=None):
|
112 |
+
global selected_provider
|
113 |
+
global model
|
114 |
+
#global agent # Declare agent as global
|
115 |
+
|
116 |
+
if additional_args:
|
117 |
+
selected_provider = additional_args.get("selected_provider", "HuggingFace")
|
118 |
+
max_steps = int(additional_args.get("max_steps", 6))
|
119 |
+
max_tokens = int(additional_args.get("max_tokens", 1000))
|
120 |
+
else:
|
121 |
+
selected_provider = "HuggingFace"
|
122 |
+
max_steps = 6
|
123 |
+
max_tokens = 1000
|
124 |
+
|
125 |
+
model_id = model_providers[selected_provider]["model_id"]
|
126 |
+
api_key_env_var = model_providers[selected_provider]["api_key_env_var"]
|
127 |
+
api_key = additional_args.get(f"{selected_provider}_api_key") if additional_args else None
|
128 |
+
|
129 |
+
model_kwargs = {
|
130 |
+
"max_tokens": max_tokens,
|
131 |
+
"temperature": 0.5,
|
132 |
+
"model_id": model_id,
|
133 |
+
"custom_role_conversions": None,
|
134 |
+
}
|
135 |
+
if model_providers[selected_provider]["api_key_env_var"]:
|
136 |
+
model_kwargs["api_key"] = api_key if api_key else os.environ.get(api_key_env_var)
|
137 |
+
|
138 |
+
model = HfApiModel(**model_kwargs)
|
139 |
+
|
140 |
+
odoo_documentation_search_tool = OdooDocumentationSearchTool()
|
141 |
+
odoo_code_agent_16_tool = OdooCodeAgent16(prompt_templates["system_prompt"])
|
142 |
+
odoo_code_agent_17_tool = OdooCodeAgent17(prompt_templates["system_prompt"])
|
143 |
+
odoo_code_agent_18_tool = OdooCodeAgent18(prompt_templates["system_prompt"])
|
144 |
+
|
145 |
+
agent = CodeAgent(
|
146 |
+
model=model,
|
147 |
+
tools=[final_answer, visit_webpage, web_search, image_generation_tool, get_current_time_in_timezone, job_search_tool, odoo_documentation_search_tool, odoo_code_agent_16_tool, odoo_code_agent_17_tool, odoo_code_agent_18_tool],
|
148 |
+
max_steps=max_steps,
|
149 |
+
verbosity_level=1,
|
150 |
+
grammar=None,
|
151 |
+
planning_interval=None,
|
152 |
+
name=None,
|
153 |
+
description=None,
|
154 |
+
prompt_templates=prompt_templates
|
155 |
+
)
|
156 |
+
|
157 |
+
GradioUI(agent).launch()
|
158 |
+
|
159 |
+
# Remove the direct call to launch_gradio_ui()
|
160 |
+
launch_gradio_ui()
|
prompts.yaml
CHANGED
@@ -1,147 +1,55 @@
|
|
1 |
"system_prompt": |-
|
2 |
-
You are an expert assistant who can solve any task using code
|
3 |
-
To do so, you have been given access to a list of tools: these tools are basically Python functions which you can call with code.
|
4 |
-
To solve the task, you must plan forward to proceed in a series of steps, in a cycle of 'Thought:', 'Code:', and 'Observation:' sequences.
|
5 |
|
6 |
-
|
7 |
-
Then in the 'Code:' sequence, you should write the code in simple Python. The code sequence must end with '<end_code>' sequence.
|
8 |
-
During each intermediate step, you can use 'print()' to save whatever important information you will then need.
|
9 |
-
These print outputs will then appear in the 'Observation:' field, which will be available as input for the next step.
|
10 |
-
In the end you have to return a final answer using the `final_answer` tool.
|
11 |
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
Thought: I will proceed step by step and use the following tools: `document_qa` to find the oldest person in the document, then `image_generator` to generate an image according to the answer.
|
17 |
-
Code:
|
18 |
-
```py
|
19 |
-
answer = document_qa(document=document, question="Who is the oldest person mentioned?")
|
20 |
-
print(answer)
|
21 |
-
```<end_code>
|
22 |
-
Observation: "The oldest person in the document is John Doe, a 55 year old lumberjack living in Newfoundland."
|
23 |
-
|
24 |
-
Thought: I will now generate an image showcasing the oldest person.
|
25 |
-
Code:
|
26 |
-
```py
|
27 |
-
image = image_generator("A portrait of John Doe, a 55-year-old man living in Canada.")
|
28 |
-
final_answer(image)
|
29 |
-
```<end_code>
|
30 |
|
|
|
31 |
---
|
32 |
-
Task: "
|
33 |
|
34 |
-
Thought: I will use
|
35 |
Code:
|
36 |
```py
|
37 |
-
|
38 |
-
final_answer(
|
39 |
```<end_code>
|
40 |
|
41 |
---
|
42 |
-
Task:
|
43 |
-
"Answer the question in the variable `question` about the image stored in the variable `image`. The question is in French.
|
44 |
-
You have been provided with these additional arguments, that you can access using the keys as variables in your python code:
|
45 |
-
{'question': 'Quel est l'animal sur l'image?', 'image': 'path/to/image.jpg'}"
|
46 |
|
47 |
-
Thought: I will use the
|
48 |
Code:
|
49 |
```py
|
50 |
-
|
51 |
-
|
52 |
-
answer = image_qa(image=image, question=translated_question)
|
53 |
-
final_answer(f"The answer is {answer}")
|
54 |
```<end_code>
|
55 |
|
56 |
---
|
57 |
-
Task:
|
58 |
-
In a 1979 interview, Stanislaus Ulam discusses with Martin Sherwin about other great physicists of his time, including Oppenheimer.
|
59 |
-
What does he say was the consequence of Einstein learning too much math on his creativity, in one word?
|
60 |
-
|
61 |
-
Thought: I need to find and read the 1979 interview of Stanislaus Ulam with Martin Sherwin.
|
62 |
-
Code:
|
63 |
-
```py
|
64 |
-
pages = search(query="1979 interview Stanislaus Ulam Martin Sherwin physicists Einstein")
|
65 |
-
print(pages)
|
66 |
-
```<end_code>
|
67 |
-
Observation:
|
68 |
-
No result found for query "1979 interview Stanislaus Ulam Martin Sherwin physicists Einstein".
|
69 |
-
|
70 |
-
Thought: The query was maybe too restrictive and did not find any results. Let's try again with a broader query.
|
71 |
-
Code:
|
72 |
-
```py
|
73 |
-
pages = search(query="1979 interview Stanislaus Ulam")
|
74 |
-
print(pages)
|
75 |
-
```<end_code>
|
76 |
-
Observation:
|
77 |
-
Found 6 pages:
|
78 |
-
[Stanislaus Ulam 1979 interview](https://ahf.nuclearmuseum.org/voices/oral-histories/stanislaus-ulams-interview-1979/)
|
79 |
-
|
80 |
-
[Ulam discusses Manhattan Project](https://ahf.nuclearmuseum.org/manhattan-project/ulam-manhattan-project/)
|
81 |
-
|
82 |
-
(truncated)
|
83 |
|
84 |
-
Thought: I will
|
85 |
Code:
|
86 |
```py
|
87 |
-
|
88 |
-
|
89 |
-
print(whole_page)
|
90 |
-
print("\n" + "="*80 + "\n") # Print separator between pages
|
91 |
-
```<end_code>
|
92 |
-
Observation:
|
93 |
-
Manhattan Project Locations:
|
94 |
-
Los Alamos, NM
|
95 |
-
Stanislaus Ulam was a Polish-American mathematician. He worked on the Manhattan Project at Los Alamos and later helped design the hydrogen bomb. In this interview, he discusses his work at
|
96 |
-
(truncated)
|
97 |
-
|
98 |
-
Thought: I now have the final answer: from the webpages visited, Stanislaus Ulam says of Einstein: "He learned too much mathematics and sort of diminished, it seems to me personally, it seems to me his purely physics creativity." Let's answer in one word.
|
99 |
-
Code:
|
100 |
-
```py
|
101 |
-
final_answer("diminished")
|
102 |
-
```<end_code>
|
103 |
-
|
104 |
-
---
|
105 |
-
Task: "Which city has the highest population: Guangzhou or Shanghai?"
|
106 |
-
|
107 |
-
Thought: I need to get the populations for both cities and compare them: I will use the tool `search` to get the population of both cities.
|
108 |
-
Code:
|
109 |
-
```py
|
110 |
-
for city in ["Guangzhou", "Shanghai"]:
|
111 |
-
print(f"Population {city}:", search(f"{city} population")
|
112 |
-
```<end_code>
|
113 |
-
Observation:
|
114 |
-
Population Guangzhou: ['Guangzhou has a population of 15 million inhabitants as of 2021.']
|
115 |
-
Population Shanghai: '26 million (2019)'
|
116 |
-
|
117 |
-
Thought: Now I know that Shanghai has the highest population.
|
118 |
-
Code:
|
119 |
-
```py
|
120 |
-
final_answer("Shanghai")
|
121 |
```<end_code>
|
122 |
|
123 |
---
|
124 |
-
Task: "
|
125 |
-
|
126 |
-
Thought: I will use the tool `wiki` to get the age of the pope, and confirm that with a web search.
|
127 |
-
Code:
|
128 |
-
```py
|
129 |
-
pope_age_wiki = wiki(query="current pope age")
|
130 |
-
print("Pope age as per wikipedia:", pope_age_wiki)
|
131 |
-
pope_age_search = web_search(query="current pope age")
|
132 |
-
print("Pope age as per google search:", pope_age_search)
|
133 |
-
```<end_code>
|
134 |
-
Observation:
|
135 |
-
Pope age: "The pope Francis is currently 88 years old."
|
136 |
|
137 |
-
Thought: I
|
138 |
Code:
|
139 |
```py
|
140 |
-
|
141 |
-
final_answer(
|
142 |
```<end_code>
|
143 |
|
144 |
-
Above
|
145 |
{%- for tool in tools.values() %}
|
146 |
- {{ tool.name }}: {{ tool.description }}
|
147 |
Takes inputs: {{tool.inputs}}
|
@@ -149,9 +57,7 @@
|
|
149 |
{%- endfor %}
|
150 |
|
151 |
{%- if managed_agents and managed_agents.values() | list %}
|
152 |
-
You can also give tasks to team members.
|
153 |
-
Calling a team member works the same as for calling a tool: simply, the only argument you can give in the call is 'task', a long string explaining your task.
|
154 |
-
Given that this team member is a real human, you should be very verbose in your task.
|
155 |
Here is a list of the team members that you can call:
|
156 |
{%- for agent in managed_agents.values() %}
|
157 |
- {{ agent.name }}: {{ agent.description }}
|
@@ -159,19 +65,24 @@
|
|
159 |
{%- else %}
|
160 |
{%- endif %}
|
161 |
|
162 |
-
|
163 |
-
1. Always provide
|
164 |
-
2. Use only variables
|
165 |
-
3.
|
166 |
-
4.
|
167 |
-
5.
|
168 |
-
6.
|
169 |
-
7.
|
170 |
-
8.
|
171 |
-
9.
|
172 |
-
|
173 |
-
|
174 |
-
|
|
|
|
|
|
|
|
|
|
|
175 |
"planning":
|
176 |
"initial_facts": |-
|
177 |
Below I will present you a task.
|
|
|
1 |
"system_prompt": |-
|
2 |
+
You are an expert assistant who can solve any task using code. You have access to a list of tools (Python functions) that you can call with code.
|
|
|
|
|
3 |
|
4 |
+
To solve the task, plan forward in a series of steps, using 'Thought:', 'Code:', and 'Observation:' sequences.
|
|
|
|
|
|
|
|
|
5 |
|
6 |
+
In each step:
|
7 |
+
- 'Thought:': Explain your reasoning and the tools you want to use.
|
8 |
+
- 'Code:': Write the code in simple Python, ending with '<end_code>'. Use 'print()' to save important information for the next step.
|
9 |
+
- Return a final answer using the `final_answer` tool.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
|
11 |
+
Here are a few examples:
|
12 |
---
|
13 |
+
Task: "Create a new Odoo 16 module to display a list of products."
|
14 |
|
15 |
+
Thought: I will use the `odoo_code_agent_16` tool to generate the code for a new Odoo module that displays a list of products.
|
16 |
Code:
|
17 |
```py
|
18 |
+
code = odoo_code_agent_16(query="create a new odoo module to display a list of products")
|
19 |
+
final_answer(code)
|
20 |
```<end_code>
|
21 |
|
22 |
---
|
23 |
+
Task: "Create a new Odoo 17 module to add a field to the product model."
|
|
|
|
|
|
|
24 |
|
25 |
+
Thought: I will use the `odoo_code_agent_17` tool to generate the code for a new Odoo module that adds a field to the product model.
|
26 |
Code:
|
27 |
```py
|
28 |
+
code = odoo_code_agent_17(query="create a new odoo module to add a field to the product model")
|
29 |
+
final_answer(code)
|
|
|
|
|
30 |
```<end_code>
|
31 |
|
32 |
---
|
33 |
+
Task: "Search Odoo documentation for how to create a new view in Odoo 18."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
|
35 |
+
Thought: I will use the `odoo_documentation_search` tool to search the Odoo documentation for how to create a new view.
|
36 |
Code:
|
37 |
```py
|
38 |
+
results = odoo_documentation_search(query="how to create a new view", version="18.0")
|
39 |
+
final_answer(results)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
```<end_code>
|
41 |
|
42 |
---
|
43 |
+
Task: "Search for Odoo jobs on LinkedIn."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
|
45 |
+
Thought: I will use the `linkedin_job_search` tool to search for Odoo jobs on LinkedIn.
|
46 |
Code:
|
47 |
```py
|
48 |
+
jobs = linkedin_job_search(query="Odoo jobs")
|
49 |
+
final_answer(jobs)
|
50 |
```<end_code>
|
51 |
|
52 |
+
Above examples use notional tools. You have access to these tools:
|
53 |
{%- for tool in tools.values() %}
|
54 |
- {{ tool.name }}: {{ tool.description }}
|
55 |
Takes inputs: {{tool.inputs}}
|
|
|
57 |
{%- endfor %}
|
58 |
|
59 |
{%- if managed_agents and managed_agents.values() | list %}
|
60 |
+
You can also give tasks to team members by calling their name with the 'task' argument. Be very verbose in your task description.
|
|
|
|
|
61 |
Here is a list of the team members that you can call:
|
62 |
{%- for agent in managed_agents.values() %}
|
63 |
- {{ agent.name }}: {{ agent.description }}
|
|
|
65 |
{%- else %}
|
66 |
{%- endif %}
|
67 |
|
68 |
+
Follow these rules:
|
69 |
+
1. Always provide 'Thought:', 'Code:', and end 'Code:' with '<end_code>'.
|
70 |
+
2. Use only defined variables and the right arguments for tools (not as a dict).
|
71 |
+
3. Avoid chaining too many tool calls in the same code block, especially with unpredictable output formats.
|
72 |
+
4. Call a tool only when needed and never re-do a tool call with the same parameters.
|
73 |
+
5. Don't name variables the same as a tool.
|
74 |
+
6. Never create notional variables.
|
75 |
+
7. You can use imports from: {{authorized_imports}}
|
76 |
+
8. State persists between code executions.
|
77 |
+
9. Don't give up! Solve the task.
|
78 |
+
|
79 |
+
Here are some Odoo-specific instructions:
|
80 |
+
- Use the Odoo API to interact with Odoo models and data.
|
81 |
+
- Follow Odoo coding conventions and best practices.
|
82 |
+
- Adhere to Odoo's module structure and file organization.
|
83 |
+
- Use the `odoo_code_agent_16`, `odoo_code_agent_17`, or `odoo_code_agent_18` tool to generate Odoo code snippets for versions 16, 17, and 18 respectively.
|
84 |
+
|
85 |
+
Now Begin!
|
86 |
"planning":
|
87 |
"initial_facts": |-
|
88 |
Below I will present you a task.
|
requirements.txt
CHANGED
@@ -8,3 +8,5 @@ python-dotenv
|
|
8 |
beautifulsoup4
|
9 |
transformers
|
10 |
torch
|
|
|
|
|
|
8 |
beautifulsoup4
|
9 |
transformers
|
10 |
torch
|
11 |
+
sentence-transformers
|
12 |
+
numpy==1.23.5
|
tools/final_answer.py
CHANGED
@@ -1,18 +1,3 @@
|
|
1 |
-
# from typing import Any, Optional
|
2 |
-
# from smolagents.tools import Tool
|
3 |
-
|
4 |
-
# class FinalAnswerTool(Tool):
|
5 |
-
# name = "final_answer"
|
6 |
-
# description = "Provides a final answer to the given problem."
|
7 |
-
# inputs = {'answer': {'type': 'any', 'description': 'The final answer to the problem'}}
|
8 |
-
# output_type = "any"
|
9 |
-
|
10 |
-
# def forward(self, answer: Any) -> Any:
|
11 |
-
# return answer
|
12 |
-
|
13 |
-
# def __init__(self, *args, **kwargs):
|
14 |
-
# self.is_initialized = False
|
15 |
-
|
16 |
import os
|
17 |
import zipfile
|
18 |
import base64
|
@@ -24,15 +9,38 @@ from smolagents.tools import Tool
|
|
24 |
|
25 |
class FinalAnswerTool(Tool):
|
26 |
name = "final_answer"
|
27 |
-
description = "Formats and presents final answers in a human-readable format, optionally
|
28 |
-
inputs = {
|
|
|
|
|
|
|
29 |
output_type = "string"
|
30 |
|
31 |
-
def forward(self, answer: Any) -> str:
|
32 |
"""
|
33 |
Determines the type of answer and formats it accordingly, optionally zipping referenced files.
|
34 |
"""
|
35 |
if isinstance(answer, str):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
# Get the current working directory
|
37 |
cwd = os.getcwd()
|
38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import os
|
2 |
import zipfile
|
3 |
import base64
|
|
|
9 |
|
10 |
class FinalAnswerTool(Tool):
|
11 |
name = "final_answer"
|
12 |
+
description = "Formats and presents final answers in a human-readable format, optionally providing a download option for code."
|
13 |
+
inputs = {
|
14 |
+
'answer': {'type': 'any', 'description': 'The final answer, which could be job listings, a general response, or file paths to be zipped'},
|
15 |
+
'include_download': {'type': 'boolean', 'description': 'Whether to include a download link for the code (if applicable)', 'required': False, 'nullable': True}
|
16 |
+
}
|
17 |
output_type = "string"
|
18 |
|
19 |
+
def forward(self, answer: Any, include_download: bool = False) -> str:
|
20 |
"""
|
21 |
Determines the type of answer and formats it accordingly, optionally zipping referenced files.
|
22 |
"""
|
23 |
if isinstance(answer, str):
|
24 |
+
# Check if the answer contains code
|
25 |
+
code_match = re.search(r"```(?:\w+)?\n(.*?)```", answer, re.DOTALL)
|
26 |
+
if include_download and code_match:
|
27 |
+
code = code_match.group(1)
|
28 |
+
try:
|
29 |
+
# Create a temporary file
|
30 |
+
with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as tmpfile:
|
31 |
+
tmpfile.write(code.encode("utf-8"))
|
32 |
+
file_path = tmpfile.name
|
33 |
+
|
34 |
+
# Encode the file path to base64
|
35 |
+
with open(file_path, "rb") as f:
|
36 |
+
file_bytes = f.read()
|
37 |
+
base64_encoded = base64.b64encode(file_bytes).decode("utf-8")
|
38 |
+
|
39 |
+
# Create a download link
|
40 |
+
download_link = f"data:text/python;base64,{base64_encoded}"
|
41 |
+
return f"📌 **Final Answer:**\n\n{answer}\n\n[Download Code]({download_link})"
|
42 |
+
except Exception as e:
|
43 |
+
return f"📌 **Final Answer:**\n\n{answer}\n\nError creating download link: {str(e)}"
|
44 |
# Get the current working directory
|
45 |
cwd = os.getcwd()
|
46 |
|
tools/odoo_code_agent_16.py
CHANGED
@@ -4,20 +4,26 @@ from typing import List, Dict
|
|
4 |
from bs4 import BeautifulSoup
|
5 |
from transformers import pipeline # Requires transformers
|
6 |
import tempfile
|
|
|
|
|
|
|
7 |
|
8 |
class OdooCodeAgent16(Tool):
|
9 |
name = "odoo_code_agent_16"
|
10 |
-
description = "Generates Odoo code for version 16 based on the user's query and Odoo documentation."
|
11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
inputs = {
|
13 |
"query": {"type": "string", "description": "The search query (e.g., 'create a new module')"}
|
14 |
}
|
15 |
|
16 |
output_type = "array"
|
17 |
|
18 |
-
def __init__(self):
|
19 |
-
# Load the summarization pipeline
|
20 |
-
self.summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
|
21 |
|
22 |
def forward(self, query: str) -> List[Dict]:
|
23 |
"""
|
@@ -26,19 +32,11 @@ class OdooCodeAgent16(Tool):
|
|
26 |
base_url = "https://www.odoo.com/documentation/16.0/" # Specific to Odoo 16
|
27 |
|
28 |
try:
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
soup = BeautifulSoup(response.content, "html.parser")
|
33 |
-
|
34 |
-
# Extract all text from the documentation
|
35 |
-
all_text = soup.get_text()
|
36 |
-
|
37 |
-
# Perform semantic, keyword, and reranking search (Placeholder - Replace with actual implementation)
|
38 |
-
search_results = self.perform_search(query, all_text)
|
39 |
|
40 |
# Generate Odoo code based on the search results (Placeholder - Replace with actual implementation)
|
41 |
-
generated_code = self.generate_code(query,
|
42 |
|
43 |
return [{"Code": generated_code}]
|
44 |
|
@@ -57,16 +55,20 @@ class OdooCodeAgent16(Tool):
|
|
57 |
# For now, just return the first 500 characters of the documentation
|
58 |
return documentation[:500]
|
59 |
|
60 |
-
def generate_code(self, query: str,
|
61 |
"""
|
62 |
Generates Odoo code based on the search results and the user's query.
|
63 |
-
(Placeholder - Replace with actual implementation)
|
64 |
"""
|
65 |
-
#
|
66 |
-
|
67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
|
69 |
-
|
70 |
-
with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as tmpfile:
|
71 |
-
tmpfile.write(b"placeholder_code = 'Odoo code will be generated here'")
|
72 |
-
return tmpfile.name
|
|
|
4 |
from bs4 import BeautifulSoup
|
5 |
from transformers import pipeline # Requires transformers
|
6 |
import tempfile
|
7 |
+
from tools.odoo_documentation_search import OdooDocumentationSearchTool
|
8 |
+
import yaml
|
9 |
+
from tools.final_answer import FinalAnswerTool
|
10 |
|
11 |
class OdooCodeAgent16(Tool):
|
12 |
name = "odoo_code_agent_16"
|
13 |
+
description = "Generates Odoo code for version 16 based on the user's query and Odoo documentation, using prompt templates."
|
14 |
|
15 |
+
def __init__(self, system_prompt: str):
|
16 |
+
# Load the summarization pipeline
|
17 |
+
self.summarizer = pipeline("summarization", model="google/flan-t5-small")
|
18 |
+
self.is_initialized = True
|
19 |
+
self.system_prompt = system_prompt
|
20 |
+
|
21 |
inputs = {
|
22 |
"query": {"type": "string", "description": "The search query (e.g., 'create a new module')"}
|
23 |
}
|
24 |
|
25 |
output_type = "array"
|
26 |
|
|
|
|
|
|
|
27 |
|
28 |
def forward(self, query: str) -> List[Dict]:
|
29 |
"""
|
|
|
32 |
base_url = "https://www.odoo.com/documentation/16.0/" # Specific to Odoo 16
|
33 |
|
34 |
try:
|
35 |
+
# Use the odoo_documentation_search tool to get relevant documentation snippets
|
36 |
+
odoo_docs = OdooDocumentationSearchTool().forward(query=query, version="16.0")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
|
38 |
# Generate Odoo code based on the search results (Placeholder - Replace with actual implementation)
|
39 |
+
generated_code = self.generate_code(query, odoo_docs)
|
40 |
|
41 |
return [{"Code": generated_code}]
|
42 |
|
|
|
55 |
# For now, just return the first 500 characters of the documentation
|
56 |
return documentation[:500]
|
57 |
|
58 |
+
def generate_code(self, query: str, odoo_docs: List[Dict]) -> str:
|
59 |
"""
|
60 |
Generates Odoo code based on the search results and the user's query.
|
|
|
61 |
"""
|
62 |
+
# Use the summarization pipeline to generate code based on the prompt, query, and documentation
|
63 |
+
prompt = f"{self.system_prompt}\n\nUser query: {query}\n\nOdoo documentation:\n"
|
64 |
+
for doc in odoo_docs:
|
65 |
+
prompt += f"# {doc['Result']}\n"
|
66 |
+
|
67 |
+
if not odoo_docs:
|
68 |
+
prompt += "# No documentation found.\n"
|
69 |
+
|
70 |
+
truncated_prompt = prompt[:500]
|
71 |
+
max_length = int(len(truncated_prompt) / 2)
|
72 |
+
code = self.summarizer(truncated_prompt, max_length=max_length, min_length=30, do_sample=False)[0]['summary_text']
|
73 |
|
74 |
+
return FinalAnswerTool(answer=f"```py\n{code}\n```", include_download=True)
|
|
|
|
|
|
tools/odoo_code_agent_17.py
CHANGED
@@ -4,20 +4,26 @@ from typing import List, Dict
|
|
4 |
from bs4 import BeautifulSoup
|
5 |
from transformers import pipeline # Requires transformers
|
6 |
import tempfile
|
|
|
|
|
|
|
7 |
|
8 |
class OdooCodeAgent17(Tool):
|
9 |
name = "odoo_code_agent_17"
|
10 |
-
description = "Generates Odoo code for version 17 based on the user's query and Odoo documentation."
|
11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
inputs = {
|
13 |
"query": {"type": "string", "description": "The search query (e.g., 'create a new module')"}
|
14 |
}
|
15 |
|
16 |
output_type = "array"
|
17 |
|
18 |
-
def __init__(self):
|
19 |
-
# Load the summarization pipeline
|
20 |
-
self.summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
|
21 |
|
22 |
def forward(self, query: str) -> List[Dict]:
|
23 |
"""
|
@@ -26,19 +32,11 @@ class OdooCodeAgent17(Tool):
|
|
26 |
base_url = "https://www.odoo.com/documentation/17.0/" # Specific to Odoo 17
|
27 |
|
28 |
try:
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
soup = BeautifulSoup(response.content, "html.parser")
|
33 |
-
|
34 |
-
# Extract all text from the documentation
|
35 |
-
all_text = soup.get_text()
|
36 |
-
|
37 |
-
# Perform semantic, keyword, and reranking search (Placeholder - Replace with actual implementation)
|
38 |
-
search_results = self.perform_search(query, all_text)
|
39 |
|
40 |
# Generate Odoo code based on the search results (Placeholder - Replace with actual implementation)
|
41 |
-
generated_code = self.generate_code(query,
|
42 |
|
43 |
return [{"Code": generated_code}]
|
44 |
|
@@ -57,16 +55,20 @@ class OdooCodeAgent17(Tool):
|
|
57 |
# For now, just return the first 500 characters of the documentation
|
58 |
return documentation[:500]
|
59 |
|
60 |
-
def generate_code(self, query: str,
|
61 |
"""
|
62 |
Generates Odoo code based on the search results and the user's query.
|
63 |
-
(Placeholder - Replace with actual implementation)
|
64 |
"""
|
65 |
-
#
|
66 |
-
|
67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
|
69 |
-
|
70 |
-
with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as tmpfile:
|
71 |
-
tmpfile.write(b"placeholder_code = 'Odoo code will be generated here'")
|
72 |
-
return tmpfile.name
|
|
|
4 |
from bs4 import BeautifulSoup
|
5 |
from transformers import pipeline # Requires transformers
|
6 |
import tempfile
|
7 |
+
from tools.odoo_documentation_search import OdooDocumentationSearchTool
|
8 |
+
import yaml
|
9 |
+
from tools.final_answer import FinalAnswerTool
|
10 |
|
11 |
class OdooCodeAgent17(Tool):
|
12 |
name = "odoo_code_agent_17"
|
13 |
+
description = "Generates Odoo code for version 17 based on the user's query and Odoo documentation, using prompt templates."
|
14 |
|
15 |
+
def __init__(self, system_prompt: str):
|
16 |
+
# Load the summarization pipeline
|
17 |
+
self.summarizer = pipeline("summarization", model="google/flan-t5-small")
|
18 |
+
self.is_initialized = True
|
19 |
+
self.system_prompt = system_prompt
|
20 |
+
|
21 |
inputs = {
|
22 |
"query": {"type": "string", "description": "The search query (e.g., 'create a new module')"}
|
23 |
}
|
24 |
|
25 |
output_type = "array"
|
26 |
|
|
|
|
|
|
|
27 |
|
28 |
def forward(self, query: str) -> List[Dict]:
|
29 |
"""
|
|
|
32 |
base_url = "https://www.odoo.com/documentation/17.0/" # Specific to Odoo 17
|
33 |
|
34 |
try:
|
35 |
+
# Use the odoo_documentation_search tool to get relevant documentation snippets
|
36 |
+
odoo_docs = OdooDocumentationSearchTool().forward(query=query, version="17.0")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
|
38 |
# Generate Odoo code based on the search results (Placeholder - Replace with actual implementation)
|
39 |
+
generated_code = self.generate_code(query, odoo_docs)
|
40 |
|
41 |
return [{"Code": generated_code}]
|
42 |
|
|
|
55 |
# For now, just return the first 500 characters of the documentation
|
56 |
return documentation[:500]
|
57 |
|
58 |
+
def generate_code(self, query: str, odoo_docs: List[Dict]) -> str:
|
59 |
"""
|
60 |
Generates Odoo code based on the search results and the user's query.
|
|
|
61 |
"""
|
62 |
+
# Use the summarization pipeline to generate code based on the prompt, query, and documentation
|
63 |
+
prompt = f"{self.system_prompt}\n\nUser query: {query}\n\nOdoo documentation:\n"
|
64 |
+
for doc in odoo_docs:
|
65 |
+
prompt += f"# {doc['Result']}\n"
|
66 |
+
|
67 |
+
if not odoo_docs:
|
68 |
+
prompt += "# No documentation found.\n"
|
69 |
+
|
70 |
+
truncated_prompt = prompt[:500]
|
71 |
+
max_length = int(len(truncated_prompt) / 2)
|
72 |
+
code = self.summarizer(truncated_prompt, max_length=max_length, min_length=30, do_sample=False)[0]['summary_text']
|
73 |
|
74 |
+
return FinalAnswerTool(answer=f"```py\n{code}\n```", include_download=True)
|
|
|
|
|
|
tools/odoo_code_agent_18.py
CHANGED
@@ -4,20 +4,26 @@ from typing import List, Dict
|
|
4 |
from bs4 import BeautifulSoup
|
5 |
from transformers import pipeline # Requires transformers
|
6 |
import tempfile
|
|
|
|
|
|
|
7 |
|
8 |
class OdooCodeAgent18(Tool):
|
9 |
name = "odoo_code_agent_18"
|
10 |
-
description = "Generates Odoo code for version 18 based on the user's query and Odoo documentation."
|
11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
inputs = {
|
13 |
"query": {"type": "string", "description": "The search query (e.g., 'create a new module')"}
|
14 |
}
|
15 |
|
16 |
output_type = "array"
|
17 |
|
18 |
-
def __init__(self):
|
19 |
-
# Load the summarization pipeline
|
20 |
-
self.summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
|
21 |
|
22 |
def forward(self, query: str) -> List[Dict]:
|
23 |
"""
|
@@ -26,19 +32,11 @@ class OdooCodeAgent18(Tool):
|
|
26 |
base_url = "https://www.odoo.com/documentation/18.0/" # Specific to Odoo 18
|
27 |
|
28 |
try:
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
soup = BeautifulSoup(response.content, "html.parser")
|
33 |
-
|
34 |
-
# Extract all text from the documentation
|
35 |
-
all_text = soup.get_text()
|
36 |
-
|
37 |
-
# Perform semantic, keyword, and reranking search (Placeholder - Replace with actual implementation)
|
38 |
-
search_results = self.perform_search(query, all_text)
|
39 |
|
40 |
# Generate Odoo code based on the search results (Placeholder - Replace with actual implementation)
|
41 |
-
generated_code = self.generate_code(query,
|
42 |
|
43 |
return [{"Code": generated_code}]
|
44 |
|
@@ -57,16 +55,20 @@ class OdooCodeAgent18(Tool):
|
|
57 |
# For now, just return the first 500 characters of the documentation
|
58 |
return documentation[:500]
|
59 |
|
60 |
-
def generate_code(self, query: str,
|
61 |
"""
|
62 |
Generates Odoo code based on the search results and the user's query.
|
63 |
-
(Placeholder - Replace with actual implementation)
|
64 |
"""
|
65 |
-
#
|
66 |
-
|
67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
|
69 |
-
|
70 |
-
with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as tmpfile:
|
71 |
-
tmpfile.write(b"placeholder_code = 'Odoo code will be generated here'")
|
72 |
-
return tmpfile.name
|
|
|
4 |
from bs4 import BeautifulSoup
|
5 |
from transformers import pipeline # Requires transformers
|
6 |
import tempfile
|
7 |
+
from tools.odoo_documentation_search import OdooDocumentationSearchTool
|
8 |
+
import yaml
|
9 |
+
from tools.final_answer import FinalAnswerTool
|
10 |
|
11 |
class OdooCodeAgent18(Tool):
|
12 |
name = "odoo_code_agent_18"
|
13 |
+
description = "Generates Odoo code for version 18 based on the user's query and Odoo documentation, using prompt templates."
|
14 |
|
15 |
+
def __init__(self, system_prompt: str):
|
16 |
+
# Load the summarization pipeline
|
17 |
+
self.summarizer = pipeline("summarization", model="google/flan-t5-small")
|
18 |
+
self.is_initialized = True
|
19 |
+
self.system_prompt = system_prompt
|
20 |
+
|
21 |
inputs = {
|
22 |
"query": {"type": "string", "description": "The search query (e.g., 'create a new module')"}
|
23 |
}
|
24 |
|
25 |
output_type = "array"
|
26 |
|
|
|
|
|
|
|
27 |
|
28 |
def forward(self, query: str) -> List[Dict]:
|
29 |
"""
|
|
|
32 |
base_url = "https://www.odoo.com/documentation/18.0/" # Specific to Odoo 18
|
33 |
|
34 |
try:
|
35 |
+
# Use the odoo_documentation_search tool to get relevant documentation snippets
|
36 |
+
odoo_docs = OdooDocumentationSearchTool().forward(query=query, version="18.0")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
|
38 |
# Generate Odoo code based on the search results (Placeholder - Replace with actual implementation)
|
39 |
+
generated_code = self.generate_code(query, odoo_docs)
|
40 |
|
41 |
return [{"Code": generated_code}]
|
42 |
|
|
|
55 |
# For now, just return the first 500 characters of the documentation
|
56 |
return documentation[:500]
|
57 |
|
58 |
+
def generate_code(self, query: str, odoo_docs: List[Dict]) -> str:
|
59 |
"""
|
60 |
Generates Odoo code based on the search results and the user's query.
|
|
|
61 |
"""
|
62 |
+
# Use the summarization pipeline to generate code based on the prompt, query, and documentation
|
63 |
+
prompt = f"{self.system_prompt}\n\nUser query: {query}\n\nOdoo documentation:\n"
|
64 |
+
for doc in odoo_docs:
|
65 |
+
prompt += f"# {doc['Result']}\n"
|
66 |
+
|
67 |
+
if not odoo_docs:
|
68 |
+
prompt += "# No documentation found.\n"
|
69 |
+
|
70 |
+
truncated_prompt = prompt[:500]
|
71 |
+
max_length = int(len(truncated_prompt) / 2)
|
72 |
+
code = self.summarizer(truncated_prompt, max_length=max_length, min_length=30, do_sample=False)[0]['summary_text']
|
73 |
|
74 |
+
return FinalAnswerTool(answer=f"```py\n{code}\n```", include_download=True)
|
|
|
|
|
|
tools/odoo_documentation_search.py
CHANGED
@@ -2,6 +2,7 @@ from smolagents.tools import Tool
|
|
2 |
import requests
|
3 |
from typing import List, Dict
|
4 |
from bs4 import BeautifulSoup
|
|
|
5 |
|
6 |
class OdooDocumentationSearchTool(Tool):
|
7 |
name = "odoo_documentation_search"
|
@@ -14,9 +15,14 @@ class OdooDocumentationSearchTool(Tool):
|
|
14 |
|
15 |
output_type = "array"
|
16 |
|
|
|
|
|
|
|
|
|
|
|
17 |
def forward(self, query: str, version: str) -> List[Dict]:
|
18 |
"""
|
19 |
-
Searches the Odoo documentation and returns related results.
|
20 |
"""
|
21 |
base_url = f"https://www.odoo.com/documentation/{version}/"
|
22 |
|
@@ -26,17 +32,56 @@ class OdooDocumentationSearchTool(Tool):
|
|
26 |
|
27 |
soup = BeautifulSoup(response.content, "html.parser")
|
28 |
|
29 |
-
# Extract
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
results = []
|
34 |
-
|
35 |
-
results.append({"Result":
|
36 |
-
else:
|
37 |
-
results.append({"Result": "No results found for the query in the Odoo documentation."})
|
38 |
|
39 |
return results
|
40 |
|
41 |
except requests.exceptions.RequestException as e:
|
42 |
return [{"Error": f"Error fetching Odoo documentation: {str(e)}"}]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
import requests
|
3 |
from typing import List, Dict
|
4 |
from bs4 import BeautifulSoup
|
5 |
+
from sentence_transformers import SentenceTransformer, util
|
6 |
|
7 |
class OdooDocumentationSearchTool(Tool):
|
8 |
name = "odoo_documentation_search"
|
|
|
15 |
|
16 |
output_type = "array"
|
17 |
|
18 |
+
def __init__(self, query=None):
|
19 |
+
# Load the SentenceTransformer model
|
20 |
+
self.model = SentenceTransformer('all-mpnet-base-v2')
|
21 |
+
self.is_initialized = True
|
22 |
+
|
23 |
def forward(self, query: str, version: str) -> List[Dict]:
|
24 |
"""
|
25 |
+
Searches the Odoo documentation and returns related results using semantic search and reranking.
|
26 |
"""
|
27 |
base_url = f"https://www.odoo.com/documentation/{version}/"
|
28 |
|
|
|
32 |
|
33 |
soup = BeautifulSoup(response.content, "html.parser")
|
34 |
|
35 |
+
# Extract relevant sections from the documentation
|
36 |
+
sections = []
|
37 |
+
for element in soup.find_all(['h1', 'h2', 'h3', 'p', 'li']):
|
38 |
+
sections.append(element.get_text().strip())
|
39 |
+
|
40 |
+
# Embed the sections and the query
|
41 |
+
section_embeddings = self.model.encode(sections, convert_to_tensor=True)
|
42 |
+
query_embedding = self.model.encode(query, convert_to_tensor=True)
|
43 |
+
|
44 |
+
# Calculate cosine similarity
|
45 |
+
cosine_scores = util.pytorch_cos_sim(query_embedding, section_embeddings)[0]
|
46 |
|
47 |
+
# Rank the sections based on similarity scores
|
48 |
+
section_scores = list(zip(sections, cosine_scores))
|
49 |
+
ranked_sections = sorted(section_scores, key=lambda x: x[1], reverse=True)
|
50 |
+
|
51 |
+
# Rerank the top-k sections (Placeholder - Replace with actual reranking implementation)
|
52 |
+
reranked_sections = self.rerank_sections(ranked_sections[:10], query)
|
53 |
+
|
54 |
+
# Return the top-n ranked sections
|
55 |
+
top_n = 5
|
56 |
results = []
|
57 |
+
for section, score in reranked_sections[:top_n]:
|
58 |
+
results.append({"Result": section, "Score": str(score.item())})
|
|
|
|
|
59 |
|
60 |
return results
|
61 |
|
62 |
except requests.exceptions.RequestException as e:
|
63 |
return [{"Error": f"Error fetching Odoo documentation: {str(e)}"}]
|
64 |
+
|
65 |
+
def rerank_sections(self, ranked_sections: List[tuple], query: str) -> List[tuple]:
|
66 |
+
"""
|
67 |
+
Reranks the top-k sections based on a keyword-based approach.
|
68 |
+
"""
|
69 |
+
# Extract keywords from the query
|
70 |
+
query_keywords = [word for word in query.lower().split() if word not in ['a', 'an', 'the', 'is', 'are', 'in', 'on', 'at', 'to', 'for', 'of']]
|
71 |
+
|
72 |
+
# Calculate keyword scores for each section
|
73 |
+
reranked_sections = []
|
74 |
+
for section, score in ranked_sections:
|
75 |
+
keyword_score = 0
|
76 |
+
for keyword in query_keywords:
|
77 |
+
keyword_score += section.lower().count(keyword)
|
78 |
+
|
79 |
+
# Adjust the similarity scores
|
80 |
+
adjusted_score = score + keyword_score
|
81 |
+
|
82 |
+
reranked_sections.append((section, adjusted_score))
|
83 |
+
|
84 |
+
# Sort the sections based on the adjusted scores
|
85 |
+
reranked_sections = sorted(reranked_sections, key=lambda x: x[1], reverse=True)
|
86 |
+
|
87 |
+
return reranked_sections
|