VPCSinfo commited on
Commit
0dd81f9
·
1 Parent(s): fbd16a2

[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 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
- total_input_tokens += agent.model.last_input_token_count
146
- total_output_tokens += agent.model.last_output_token_count
 
 
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 or a general response'}}
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
- return f"📌 **Final Answer:**\n\n{answer}"
32
-
33
- # Case 2: If the answer is a list of job listings, format them properly
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 using Brave API and returns structured JSON.
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
- base_url = "https://api.brave.com/v1/jobs"
 
 
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
- job_results = data.get("jobs", [])
42
-
43
- # ✅ Properly format job URLs
44
- formatted_results = ""
45
- if job_results:
46
- for job in job_results:
47
- formatted_results += f"Title: {job['title']}\n"
48
- formatted_results += f"Company: {job.get('company', 'N/A')}\n"
49
- formatted_results += f"Location: {job.get('location', 'N/A')}\n"
50
- formatted_results += f"Posted: {job.get('posted_date', 'N/A')}\n"
51
- formatted_results += f"Link: {job.get('url', 'N/A')}\n"
52
- formatted_results += "---\n"
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
- return [{"Error": f"Error fetching job listings: {str(e)}"}]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)}"}]