[ADD][IMP] ADDED Tools : Odoo 16 to 18 version code generation, Odoo job search with indeed options, Odoo RAG with code generation with applicable of search.
Browse files- Gradio_UI.py +11 -3
- app.py +10 -2
- prompts.yaml +6 -0
- requirements.txt +3 -0
- tools/final_answer.py +47 -8
- tools/linkedin_job_search.py +73 -21
- tools/odoo_code_agent_16.py +70 -0
- tools/odoo_code_agent_17.py +70 -0
- tools/odoo_code_agent_18.py +73 -0
- tools/odoo_documentation_search.py +41 -0
Gradio_UI.py
CHANGED
@@ -142,8 +142,10 @@ def stream_to_gradio(
|
|
142 |
for step_log in agent.run(task, stream=True, reset=reset_agent_memory, additional_args=additional_args):
|
143 |
# Track tokens if model provides them
|
144 |
if hasattr(agent.model, "last_input_token_count"):
|
145 |
-
|
146 |
-
|
|
|
|
|
147 |
if isinstance(step_log, ActionStep):
|
148 |
step_log.input_token_count = agent.model.last_input_token_count
|
149 |
step_log.output_token_count = agent.model.last_output_token_count
|
@@ -171,6 +173,12 @@ def stream_to_gradio(
|
|
171 |
role="assistant",
|
172 |
content={"path": final_answer.to_string(), "mime_type": "audio/wav"},
|
173 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
else:
|
175 |
yield gr.ChatMessage(role="assistant", content=f"**Final answer:** {str(final_answer)}")
|
176 |
|
@@ -293,4 +301,4 @@ class GradioUI:
|
|
293 |
demo.launch(debug=True, share=True, **kwargs)
|
294 |
|
295 |
|
296 |
-
__all__ = ["stream_to_gradio", "GradioUI"]
|
|
|
142 |
for step_log in agent.run(task, stream=True, reset=reset_agent_memory, additional_args=additional_args):
|
143 |
# Track tokens if model provides them
|
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.last_output_token_count is not None:
|
148 |
+
total_output_tokens += agent.model.last_output_token_count
|
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
|
|
|
173 |
role="assistant",
|
174 |
content={"path": final_answer.to_string(), "mime_type": "audio/wav"},
|
175 |
)
|
176 |
+
elif isinstance(final_answer, str) and final_answer.startswith("data:application/zip;base64,"):
|
177 |
+
# Create a download link for the zip file
|
178 |
+
yield gr.ChatMessage(
|
179 |
+
role="assistant",
|
180 |
+
content=f"**Final answer:**\n[Download Odoo Module](data:application/zip;base64,{final_answer})",
|
181 |
+
)
|
182 |
else:
|
183 |
yield gr.ChatMessage(role="assistant", content=f"**Final answer:** {str(final_answer)}")
|
184 |
|
|
|
301 |
demo.launch(debug=True, share=True, **kwargs)
|
302 |
|
303 |
|
304 |
+
__all__ = ["stream_to_gradio", "GradioUI"]
|
app.py
CHANGED
@@ -7,6 +7,10 @@ from tools.final_answer import FinalAnswerTool
|
|
7 |
from tools.visit_webpage import VisitWebpageTool
|
8 |
from tools.web_search import DuckDuckGoSearchTool
|
9 |
from tools.linkedin_job_search import LinkedInJobSearchTool
|
|
|
|
|
|
|
|
|
10 |
|
11 |
from Gradio_UI import GradioUI
|
12 |
|
@@ -41,6 +45,10 @@ final_answer = FinalAnswerTool()
|
|
41 |
visit_webpage = VisitWebpageTool()
|
42 |
web_search = DuckDuckGoSearchTool()
|
43 |
job_search_tool = LinkedInJobSearchTool()
|
|
|
|
|
|
|
|
|
44 |
|
45 |
|
46 |
# 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:
|
@@ -62,7 +70,7 @@ with open("prompts.yaml", 'r') as stream:
|
|
62 |
|
63 |
agent = CodeAgent(
|
64 |
model=model,
|
65 |
-
tools=[final_answer, visit_webpage, web_search, image_generation_tool, get_current_time_in_timezone, job_search_tool],## add your tools here (don't remove final answer)
|
66 |
max_steps=6,
|
67 |
verbosity_level=1,
|
68 |
grammar=None,
|
@@ -73,4 +81,4 @@ agent = CodeAgent(
|
|
73 |
)
|
74 |
|
75 |
|
76 |
-
GradioUI(agent).launch()
|
|
|
7 |
from tools.visit_webpage import VisitWebpageTool
|
8 |
from tools.web_search import DuckDuckGoSearchTool
|
9 |
from tools.linkedin_job_search import LinkedInJobSearchTool
|
10 |
+
from tools.odoo_documentation_search import OdooDocumentationSearchTool
|
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 |
|
|
|
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:
|
|
|
70 |
|
71 |
agent = CodeAgent(
|
72 |
model=model,
|
73 |
+
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],## add your tools here (don't remove final answer)
|
74 |
max_steps=6,
|
75 |
verbosity_level=1,
|
76 |
grammar=None,
|
|
|
81 |
)
|
82 |
|
83 |
|
84 |
+
GradioUI(agent).launch()
|
prompts.yaml
CHANGED
@@ -319,3 +319,9 @@
|
|
319 |
"report": |-
|
320 |
Here is the final answer from your managed agent '{{name}}':
|
321 |
{{final_answer}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
319 |
"report": |-
|
320 |
Here is the final answer from your managed agent '{{name}}':
|
321 |
{{final_answer}}
|
322 |
+
|
323 |
+
"final_answer":
|
324 |
+
"pre_messages": |-
|
325 |
+
Provide a concise final answer to the task.
|
326 |
+
"post_messages": |-
|
327 |
+
Provide a concise final answer to the task.
|
requirements.txt
CHANGED
@@ -5,3 +5,6 @@ duckduckgo_search
|
|
5 |
pandas
|
6 |
smolagents[gradio]
|
7 |
python-dotenv
|
|
|
|
|
|
|
|
5 |
pandas
|
6 |
smolagents[gradio]
|
7 |
python-dotenv
|
8 |
+
beautifulsoup4
|
9 |
+
transformers
|
10 |
+
torch
|
tools/final_answer.py
CHANGED
@@ -13,24 +13,64 @@
|
|
13 |
# def __init__(self, *args, **kwargs):
|
14 |
# self.is_initialized = False
|
15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
from typing import Any
|
17 |
from smolagents.tools import Tool
|
18 |
|
19 |
class FinalAnswerTool(Tool):
|
20 |
name = "final_answer"
|
21 |
-
description = "Formats and presents final answers in a human-readable format."
|
22 |
-
inputs = {'answer': {'type': 'any', 'description': 'The final answer, which could be job listings
|
23 |
output_type = "string"
|
24 |
|
25 |
def forward(self, answer: Any) -> str:
|
26 |
"""
|
27 |
-
Determines the type of answer and formats it accordingly.
|
28 |
"""
|
29 |
-
# Case 1: If the answer is a simple string, return it directly
|
30 |
if isinstance(answer, str):
|
31 |
-
|
32 |
-
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
elif isinstance(answer, list) and all(isinstance(job, dict) for job in answer):
|
35 |
if not answer:
|
36 |
return "⚠️ No job listings found."
|
@@ -52,4 +92,3 @@ class FinalAnswerTool(Tool):
|
|
52 |
# Case 3: If it's an unexpected format, return it as a formatted string
|
53 |
else:
|
54 |
return f"📌 **Final Answer:**\n\n```{str(answer)}```"
|
55 |
-
|
|
|
13 |
# def __init__(self, *args, **kwargs):
|
14 |
# self.is_initialized = False
|
15 |
|
16 |
+
import os
|
17 |
+
import zipfile
|
18 |
+
import base64
|
19 |
+
import tempfile
|
20 |
+
import re
|
21 |
+
import shutil
|
22 |
from typing import Any
|
23 |
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 zipping referenced files."
|
28 |
+
inputs = {'answer': {'type': 'any', 'description': 'The final answer, which could be job listings, a general response, or file paths to be zipped'}}
|
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 |
+
|
39 |
+
# Check if the answer contains file paths
|
40 |
+
file_paths = re.findall(r"("+re.escape(cwd)+r"/[^\s'\"]+)", answer)
|
41 |
+
|
42 |
+
if file_paths:
|
43 |
+
try:
|
44 |
+
# Create a temporary directory
|
45 |
+
with tempfile.TemporaryDirectory() as tmpdir:
|
46 |
+
# Copy files to the temporary directory
|
47 |
+
for file_path in file_paths:
|
48 |
+
# Create the directory structure if it doesn't exist
|
49 |
+
dest_path = os.path.join(tmpdir, os.path.relpath(file_path, cwd))
|
50 |
+
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
51 |
+
shutil.copy2(file_path, dest_path)
|
52 |
+
|
53 |
+
# Create a zip archive
|
54 |
+
zip_filename = os.path.join(tmpdir, "download.zip")
|
55 |
+
with zipfile.ZipFile(zip_filename, "w", zipfile.ZIP_DEFLATED) as zipf:
|
56 |
+
for root, _, files in os.walk(tmpdir):
|
57 |
+
for file in files:
|
58 |
+
file_path = os.path.join(root, file)
|
59 |
+
zipf.write(file_path, os.path.relpath(file_path, tmpdir))
|
60 |
+
|
61 |
+
# Encode the zip file to base64
|
62 |
+
with open(zip_filename, "rb") as f:
|
63 |
+
zip_bytes = f.read()
|
64 |
+
base64_encoded = base64.b64encode(zip_bytes).decode("utf-8")
|
65 |
+
|
66 |
+
return "data:application/zip;base64," + base64_encoded
|
67 |
+
|
68 |
+
except Exception as e:
|
69 |
+
return f"Error creating zip file: {str(e)}"
|
70 |
+
|
71 |
+
else:
|
72 |
+
return f"📌 **Final Answer:**\n\n{answer}"
|
73 |
+
|
74 |
elif isinstance(answer, list) and all(isinstance(job, dict) for job in answer):
|
75 |
if not answer:
|
76 |
return "⚠️ No job listings found."
|
|
|
92 |
# Case 3: If it's an unexpected format, return it as a formatted string
|
93 |
else:
|
94 |
return f"📌 **Final Answer:**\n\n```{str(answer)}```"
|
|
tools/linkedin_job_search.py
CHANGED
@@ -3,12 +3,43 @@ import requests
|
|
3 |
from typing import List, Dict
|
4 |
import os
|
5 |
from dotenv import load_dotenv
|
|
|
6 |
|
7 |
load_dotenv()
|
8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
class LinkedInJobSearchTool(Tool):
|
10 |
name = "linkedin_job_search"
|
11 |
-
description = "Searches for job postings on LinkedIn based on job title, location, and work mode (remote, hybrid, in-office) for Odoo profiles."
|
12 |
|
13 |
inputs = {
|
14 |
"position": {"type": "string", "description": "Job title (e.g., Data Scientist)"},
|
@@ -20,13 +51,17 @@ class LinkedInJobSearchTool(Tool):
|
|
20 |
|
21 |
def forward(self, position: str, location: str, work_mode: str) -> List[Dict]:
|
22 |
"""
|
23 |
-
Fetches job listings from LinkedIn
|
24 |
"""
|
25 |
BRAVE_API_KEY = os.getenv("BRAVE_API_KEY")
|
26 |
if not BRAVE_API_KEY:
|
27 |
return [{"Error": "Brave API key not found in .env file."}]
|
28 |
-
|
|
|
|
|
29 |
|
|
|
|
|
30 |
params = {
|
31 |
"q": f"Odoo {position} {work_mode} jobs",
|
32 |
"location": location,
|
@@ -36,23 +71,40 @@ class LinkedInJobSearchTool(Tool):
|
|
36 |
try:
|
37 |
response = requests.get(base_url, params=params)
|
38 |
response.raise_for_status()
|
39 |
-
|
40 |
data = response.json()
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
else:
|
54 |
-
formatted_results = "No jobs found. Try different keywords."
|
55 |
-
return [{"Results": formatted_results}]
|
56 |
-
|
57 |
except requests.exceptions.RequestException as e:
|
58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
from typing import List, Dict
|
4 |
import os
|
5 |
from dotenv import load_dotenv
|
6 |
+
from bs4 import BeautifulSoup
|
7 |
|
8 |
load_dotenv()
|
9 |
|
10 |
+
def scrape_indeed(position: str, location: str) -> List[Dict]:
|
11 |
+
"""
|
12 |
+
Scrapes job postings from Indeed.
|
13 |
+
"""
|
14 |
+
url = f"https://www.indeed.com/jobs?q=Odoo+{position}&l={location}"
|
15 |
+
try:
|
16 |
+
response = requests.get(url)
|
17 |
+
response.raise_for_status()
|
18 |
+
soup = BeautifulSoup(response.content, 'html.parser')
|
19 |
+
jobs = []
|
20 |
+
for div in soup.find_all('div', class_='jobsearch-SerpJobCard'):
|
21 |
+
title_element = div.find('a', title=True)
|
22 |
+
title = title_element.text if title_element else 'N/A'
|
23 |
+
company_element = div.find('span', class_='company')
|
24 |
+
company = company_element.text.strip() if company_element else 'N/A'
|
25 |
+
location_element = div.find('div', class_='location')
|
26 |
+
location = location_element.text if location_element else 'N/A'
|
27 |
+
link = 'https://www.indeed.com' + div.find('a', href=True)['href'] if div.find('a', href=True) else 'N/A'
|
28 |
+
jobs.append({
|
29 |
+
"Title": title,
|
30 |
+
"Company": company,
|
31 |
+
"Location": location,
|
32 |
+
"Link": link,
|
33 |
+
"Source": "Indeed"
|
34 |
+
})
|
35 |
+
return jobs
|
36 |
+
except requests.exceptions.RequestException as e:
|
37 |
+
print(f"Indeed scraping failed: {e}")
|
38 |
+
return []
|
39 |
+
|
40 |
class LinkedInJobSearchTool(Tool):
|
41 |
name = "linkedin_job_search"
|
42 |
+
description = "Searches for job postings on LinkedIn and Indeed based on job title, location, and work mode (remote, hybrid, in-office) for Odoo profiles."
|
43 |
|
44 |
inputs = {
|
45 |
"position": {"type": "string", "description": "Job title (e.g., Data Scientist)"},
|
|
|
51 |
|
52 |
def forward(self, position: str, location: str, work_mode: str) -> List[Dict]:
|
53 |
"""
|
54 |
+
Fetches job listings from LinkedIn and Indeed and returns structured JSON.
|
55 |
"""
|
56 |
BRAVE_API_KEY = os.getenv("BRAVE_API_KEY")
|
57 |
if not BRAVE_API_KEY:
|
58 |
return [{"Error": "Brave API key not found in .env file."}]
|
59 |
+
|
60 |
+
linkedin_results = []
|
61 |
+
indeed_results = []
|
62 |
|
63 |
+
# LinkedIn Job Search
|
64 |
+
base_url = "https://api.brave.com/v1/jobs"
|
65 |
params = {
|
66 |
"q": f"Odoo {position} {work_mode} jobs",
|
67 |
"location": location,
|
|
|
71 |
try:
|
72 |
response = requests.get(base_url, params=params)
|
73 |
response.raise_for_status()
|
|
|
74 |
data = response.json()
|
75 |
+
linkedin_jobs = data.get("jobs", [])
|
76 |
+
|
77 |
+
if linkedin_jobs:
|
78 |
+
for job in linkedin_jobs:
|
79 |
+
linkedin_results.append({
|
80 |
+
"Title": job['title'],
|
81 |
+
"Company": job.get('company', 'N/A'),
|
82 |
+
"Location": job.get('location', 'N/A'),
|
83 |
+
"Posted": job.get('posted_date', 'N/A'),
|
84 |
+
"Link": job.get('url', 'N/A'),
|
85 |
+
"Source": "LinkedIn"
|
86 |
+
})
|
|
|
|
|
|
|
|
|
87 |
except requests.exceptions.RequestException as e:
|
88 |
+
linkedin_results = [{"Error": f"LinkedIn Error: {str(e)}"}]
|
89 |
+
|
90 |
+
# Indeed Job Search
|
91 |
+
indeed_results = scrape_indeed(position, location)
|
92 |
+
|
93 |
+
# Combine results, prioritizing LinkedIn
|
94 |
+
combined_results = linkedin_results + indeed_results
|
95 |
+
|
96 |
+
# Format the results
|
97 |
+
formatted_results = ""
|
98 |
+
if combined_results:
|
99 |
+
for job in combined_results:
|
100 |
+
formatted_results += f"Title: {job.get('Title', 'N/A')}\n"
|
101 |
+
formatted_results += f"Company: {job.get('Company', 'N/A')}\n"
|
102 |
+
formatted_results += f"Location: {job.get('Location', 'N/A')}\n"
|
103 |
+
formatted_results += f"Posted: {job.get('Posted', 'N/A')}\n"
|
104 |
+
formatted_results += f"Link: {job.get('Link', 'N/A')}\n"
|
105 |
+
formatted_results += f"Source: {job.get('Source', 'N/A')}\n"
|
106 |
+
formatted_results += "---\n"
|
107 |
+
else:
|
108 |
+
formatted_results = "No jobs found. Try different keywords."
|
109 |
+
|
110 |
+
return [{"Results": formatted_results}]
|
tools/odoo_code_agent_16.py
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from smolagents.tools import Tool
|
2 |
+
import requests
|
3 |
+
from typing import List, Dict
|
4 |
+
from bs4 import BeautifulSoup
|
5 |
+
from transformers import pipeline # Requires transformers
|
6 |
+
|
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 |
+
"""
|
24 |
+
Generates Odoo code for version 16 based on the user's query and Odoo documentation.
|
25 |
+
"""
|
26 |
+
base_url = "https://www.odoo.com/documentation/16.0/" # Specific to Odoo 16
|
27 |
+
|
28 |
+
try:
|
29 |
+
response = requests.get(base_url)
|
30 |
+
response.raise_for_status()
|
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, search_results)
|
42 |
+
|
43 |
+
return [{"Code": generated_code}]
|
44 |
+
|
45 |
+
except requests.exceptions.RequestException as e:
|
46 |
+
return [{"Error": f"Error fetching Odoo documentation: {str(e)}"}]
|
47 |
+
|
48 |
+
def perform_search(self, query: str, documentation: str) -> str:
|
49 |
+
"""
|
50 |
+
Performs semantic, keyword, and reranking search on the documentation.
|
51 |
+
(Placeholder - Replace with actual implementation)
|
52 |
+
"""
|
53 |
+
# Placeholder implementation - Replace with actual search logic
|
54 |
+
# This could involve using libraries like Sentence Transformers for semantic search,
|
55 |
+
# keyword extraction techniques, and a reranking algorithm to prioritize relevant results.
|
56 |
+
|
57 |
+
# For now, just return the first 500 characters of the documentation
|
58 |
+
return documentation[:500]
|
59 |
+
|
60 |
+
def generate_code(self, query: str, search_results: str) -> str:
|
61 |
+
"""
|
62 |
+
Generates Odoo code based on the search results and the user's query.
|
63 |
+
(Placeholder - Replace with actual implementation)
|
64 |
+
"""
|
65 |
+
# Placeholder implementation - Replace with actual code generation logic
|
66 |
+
# This could involve using the Qwen/Qwen2.5-Coder-32B-Instruct model to generate code
|
67 |
+
# based on the search results and the user's query.
|
68 |
+
|
69 |
+
# For now, just return a placeholder message
|
70 |
+
return "Placeholder Odoo 16 code generated based on the query and documentation."
|
tools/odoo_code_agent_17.py
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from smolagents.tools import Tool
|
2 |
+
import requests
|
3 |
+
from typing import List, Dict
|
4 |
+
from bs4 import BeautifulSoup
|
5 |
+
from transformers import pipeline # Requires transformers
|
6 |
+
|
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 |
+
"""
|
24 |
+
Generates Odoo code for version 17 based on the user's query and Odoo documentation.
|
25 |
+
"""
|
26 |
+
base_url = "https://www.odoo.com/documentation/17.0/" # Specific to Odoo 17
|
27 |
+
|
28 |
+
try:
|
29 |
+
response = requests.get(base_url)
|
30 |
+
response.raise_for_status()
|
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, search_results)
|
42 |
+
|
43 |
+
return [{"Code": generated_code}]
|
44 |
+
|
45 |
+
except requests.exceptions.RequestException as e:
|
46 |
+
return [{"Error": f"Error fetching Odoo documentation: {str(e)}"}]
|
47 |
+
|
48 |
+
def perform_search(self, query: str, documentation: str) -> str:
|
49 |
+
"""
|
50 |
+
Performs semantic, keyword, and reranking search on the documentation.
|
51 |
+
(Placeholder - Replace with actual implementation)
|
52 |
+
"""
|
53 |
+
# Placeholder implementation - Replace with actual search logic
|
54 |
+
# This could involve using libraries like Sentence Transformers for semantic search,
|
55 |
+
# keyword extraction techniques, and a reranking algorithm to prioritize relevant results.
|
56 |
+
|
57 |
+
# For now, just return the first 500 characters of the documentation
|
58 |
+
return documentation[:500]
|
59 |
+
|
60 |
+
def generate_code(self, query: str, search_results: str) -> str:
|
61 |
+
"""
|
62 |
+
Generates Odoo code based on the search results and the user's query.
|
63 |
+
(Placeholder - Replace with actual implementation)
|
64 |
+
"""
|
65 |
+
# Placeholder implementation - Replace with actual code generation logic
|
66 |
+
# This could involve using the Qwen/Qwen2.5-Coder-32B-Instruct model to generate code
|
67 |
+
# based on the search results and the user's query.
|
68 |
+
|
69 |
+
# For now, just return a placeholder message
|
70 |
+
return "Placeholder Odoo 17 code generated based on the query and documentation."
|
tools/odoo_code_agent_18.py
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from smolagents.tools import Tool
|
2 |
+
import requests
|
3 |
+
from typing import List, Dict
|
4 |
+
from bs4 import BeautifulSoup
|
5 |
+
from transformers import pipeline # Requires transformers
|
6 |
+
|
7 |
+
class OdooCodeAgent18(Tool):
|
8 |
+
name = "odoo_code_agent_18"
|
9 |
+
description = "Generates Odoo code for version 18 based on the user's query and Odoo documentation."
|
10 |
+
|
11 |
+
inputs = {
|
12 |
+
"query": {"type": "string", "description": "The search query (e.g., 'create a new module')"}
|
13 |
+
}
|
14 |
+
|
15 |
+
output_type = "array"
|
16 |
+
|
17 |
+
def __init__(self):
|
18 |
+
# Load the summarization pipeline
|
19 |
+
self.summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
|
20 |
+
|
21 |
+
def forward(self, query: str) -> List[Dict]:
|
22 |
+
"""
|
23 |
+
Generates Odoo code for version 18 based on the user's query and Odoo documentation.
|
24 |
+
"""
|
25 |
+
base_url = "https://www.odoo.com/documentation/18.0/" # Specific to Odoo 18
|
26 |
+
|
27 |
+
try:
|
28 |
+
response = requests.get(base_url)
|
29 |
+
response.raise_for_status()
|
30 |
+
|
31 |
+
soup = BeautifulSoup(response.content, "html.parser")
|
32 |
+
|
33 |
+
# Extract all text from the documentation
|
34 |
+
all_text = soup.get_text()
|
35 |
+
|
36 |
+
# Perform semantic, keyword, and reranking search (Placeholder - Replace with actual implementation)
|
37 |
+
search_results = self.perform_search(query, all_text)
|
38 |
+
|
39 |
+
# Generate Odoo code based on the search results (Placeholder - Replace with actual implementation)
|
40 |
+
generated_code = self.generate_code(query, search_results)
|
41 |
+
|
42 |
+
return [{"Code": generated_code}]
|
43 |
+
|
44 |
+
except requests.exceptions.RequestException as e:
|
45 |
+
return [{"Error": f"Error fetching Odoo documentation: {str(e)}"}]
|
46 |
+
|
47 |
+
def perform_search(self, query: str, documentation: str) -> str:
|
48 |
+
"""
|
49 |
+
Performs semantic, keyword, and reranking search on the documentation.
|
50 |
+
(Placeholder - Replace with actual implementation)
|
51 |
+
"""
|
52 |
+
# Placeholder implementation - Replace with actual search logic
|
53 |
+
# This could involve using libraries like Sentence Transformers for semantic search,
|
54 |
+
# keyword extraction techniques, and a reranking algorithm to prioritize relevant results.
|
55 |
+
|
56 |
+
# For now, just return the first 500 characters of the documentation
|
57 |
+
return documentation[:500]
|
58 |
+
|
59 |
+
def generate_code(self, query: str, search_results: str) -> str:
|
60 |
+
"""
|
61 |
+
Generates Odoo code based on the search results and the user's query.
|
62 |
+
(Placeholder - Replace with actual implementation)
|
63 |
+
"""
|
64 |
+
# Placeholder implementation - Replace with actual code generation logic
|
65 |
+
# This could involve using the Qwen/Qwen2.5-Coder-32B-Instruct model to generate code
|
66 |
+
# based on the search results and the user's query.
|
67 |
+
|
68 |
+
# Placeholder implementation - Replace with actual code generation logic
|
69 |
+
# This could involve using the Qwen/Qwen2.5-Coder-32B-Instruct model to generate code
|
70 |
+
# based on the search results and the user's query.
|
71 |
+
|
72 |
+
# For now, just return a placeholder message
|
73 |
+
return "Placeholder Odoo 18 code generated based on the query and documentation."
|
tools/odoo_documentation_search.py
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
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"
|
8 |
+
description = "Searches the Odoo documentation for functional or technical queries and returns related results."
|
9 |
+
|
10 |
+
inputs = {
|
11 |
+
"query": {"type": "string", "description": "The search query (e.g., 'how to create a new module')"}
|
12 |
+
}
|
13 |
+
|
14 |
+
output_type = "array"
|
15 |
+
|
16 |
+
def forward(self, query: str) -> List[Dict]:
|
17 |
+
"""
|
18 |
+
Searches the Odoo documentation and returns related results.
|
19 |
+
"""
|
20 |
+
base_url = "https://www.odoo.com/documentation/18.0/"
|
21 |
+
|
22 |
+
try:
|
23 |
+
response = requests.get(base_url)
|
24 |
+
response.raise_for_status()
|
25 |
+
|
26 |
+
soup = BeautifulSoup(response.content, "html.parser")
|
27 |
+
|
28 |
+
# Extract all text from the documentation
|
29 |
+
all_text = soup.get_text()
|
30 |
+
|
31 |
+
# Search for the query in the extracted text
|
32 |
+
results = []
|
33 |
+
if query.lower() in all_text.lower():
|
34 |
+
results.append({"Result": "Query found in documentation. Please visit " + base_url + " to find the related content."})
|
35 |
+
else:
|
36 |
+
results.append({"Result": "No results found for the query in the Odoo documentation."})
|
37 |
+
|
38 |
+
return results
|
39 |
+
|
40 |
+
except requests.exceptions.RequestException as e:
|
41 |
+
return [{"Error": f"Error fetching Odoo documentation: {str(e)}"}]
|