Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,26 @@ Cargo.lock
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

.DS_Store
# AI AGENTS DELEGATE ACTIONS
# Python cache
ai-agents-delegate-actions/__pycache__/
ai-agents-delegate-actions/*.pyc

# Virtual environments
ai-agents-delegate-actions/venv/
ai-agents-delegate-actions/env/
ai-agents-delegate-actions/.venv/
ai-agents-delegate-actions/.env/

# Environment variables
ai-agents-delegate-actions/.env

# OS-specific
.DS_Store

# Editor files
.idea/
.vscode/

# IGNORE TOOLS FOLDER ENTIRELY
ai-agents-delegate-actions/tools/
1 change: 1 addition & 0 deletions AI-agents-delegate-actions/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
GOOGLE_API_KEY=INSERT_KEY_HERE
22 changes: 22 additions & 0 deletions AI-agents-delegate-actions/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Python cache
ai-agents-delegate-actions/__pycache__/
ai-agents-delegate-actions/*.pyc

# Virtual environments
ai-agents-delegate-actions/venv/
ai-agents-delegate-actions/env/
ai-agents-delegate-actions/.venv/
ai-agents-delegate-actions/.env/

# Environment variables
ai-agents-delegate-actions/.env

# OS-specific
.DS_Store

# Editor files
.idea/
.vscode/

# IGNORE TOOLS FOLDER ENTIRELY
ai-agents-delegate-actions/tools/
147 changes: 147 additions & 0 deletions AI-agents-delegate-actions/main2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
from dotenv import load_dotenv
load_dotenv()

import os
import json
import google.generativeai as genai
from registry import ToolRegistry

genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))

MODEL_NAME = "models/gemini-2.5-flash"


# -----------------------------------------------------------
# Convert FULL JSON tool schema into SAFE plain-text context
# -----------------------------------------------------------
def convert_tool_to_plain_text(tool):
"""
Convert a tool with nested JSON into a safe plain-text representation.
This avoids JSON parsing failures and finish_reason=2 from Gemini.
"""

text = f"TOOL: {tool['name']}\n"
text += f"DESCRIPTION: {tool.get('description', '')}\n"

# Arguments
args = tool.get("arguments", {})
if isinstance(args, dict):
text += "ARGUMENTS:\n"
for k, v in args.items():
text += f" - {k}: {v}\n"
else:
text += f"ARGUMENTS: {args}\n"

# Returns
returns = tool.get("returns", {})
if isinstance(returns, dict):
text += "RETURNS:\n"
for k, v in returns.items():
text += f" - {k}: {v}\n"
else:
text += f"RETURNS: {returns}\n"

# Optional usage/examples
if "usage" in tool:
text += f"USAGE EXAMPLE: {tool['usage']}\n"

# Optional advanced information
if "details" in tool:
text += f"DETAILS: {tool['details']}\n"

text += "-" * 40 + "\n"
return text


def build_full_plain_context(registry: ToolRegistry):
"""Build the FULL context (bruteforce mode) using plain text only."""
ctx = "=== SINGLE-AGENT MODE — FULL TOOLSET AS PLAIN TEXT ===\n\n"
ctx += "Modelul primește TOATE tool-urile aici în format text simplu.\n"
ctx += "Această variantă este intenționat ineficientă pentru comparație cu MCP.\n\n"

# Categories
ctx += "=== TOOL CATEGORIES ===\n"
for cat in registry.get_all_categories():
ctx += f"- {cat}\n"

ctx += "\n=== ALL TOOL DEFINITIONS (PLAIN TEXT) ===\n"

# Add each tool in plain text WITHOUT printing them in terminal
for t in registry.tools.values():
ctx += convert_tool_to_plain_text(t)

ctx += """
=== INSTRUCȚIUNI ===
Ești un singur agent (fără Sub-Agent).

1. Utilizezi informațiile de mai sus ca baza completă de tool-uri.
2. Pe baza promptului userului:
a) Identifici dacă e nevoie să folosești tool-uri.
b) Alegi toolurile relevante (din lista completă).
c) Explici ce tooluri ai alege.
d) Generezi răspunsul final.
3. Nu inventa tool-uri.
4. Ai tot contextul în system_instruction — doar un singur call.
"""

return ctx


# -----------------------------------------------------------
# MAIN — Single Agent Plain Text Bruteforce Mode
# -----------------------------------------------------------
def main():
print("=== SINGLE-AGENT (FULL TOOLSET PLAIN TEXT) ===")

# Load all tools
registry = ToolRegistry()
registry.load_folder("tools")

# Only show counts, not details
print(f"[INFO] Loaded {len(registry.tools)} total tools.")
print(f"[INFO] Loaded {len(registry.categories)} categories.")

# Build huge plain-text context
system_prompt = build_full_plain_context(registry)

# User input
user_prompt = input("\nUser: ")

# Initialize model with huge context
model = genai.GenerativeModel(
model_name=MODEL_NAME,
system_instruction=system_prompt
)

print("\n[INFO] Sending FULL single-agent prompt to Gemini...\n")

# SAFE generation
response = model.generate_content(user_prompt)
cand = response.candidates[0]

# Extract response safely
if not cand.content.parts:
print("⚠️ Model returned no text.")
print("finish_reason =", cand.finish_reason)
return

result = cand.content.parts[0].text

# 🔥 SAVE RESULT TO FILE (instead of printing)
filename = "raspuns_model_single_agent.md"
with open(filename, "w", encoding="utf-8") as f:
f.write(result)

print(f"\n=== MODEL RESPONSE SAVED TO {filename} ===")

# Token usage
usage = response.usage_metadata

print("\n=== TOKEN USAGE (SINGLE AGENT FULL CONTEXT) ===")
print("Input tokens: ", usage.prompt_token_count)
print("Output tokens:", usage.candidates_token_count)
print("Total tokens: ", usage.total_token_count)


if __name__ == "__main__":
main()
122 changes: 122 additions & 0 deletions AI-agents-delegate-actions/main_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from dotenv import load_dotenv
load_dotenv()

import json
import os
import google.generativeai as genai

from sub_agent_modular import SubAgentLLM

genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))

MODEL_NAME = "models/gemini-2.5-flash"


def gemini_answer_with_tools(prompt, selected_tools):
print("\n[MainAgent] Injecting tool context...")

FULL_LIMIT = 10

if len(selected_tools) <= FULL_LIMIT:
print("[MainAgent] Injecting FULL tool schema (<= 10 tools).")
tool_ctx = ""
for t in selected_tools:
tool_ctx += f"TOOL: {t['name']}\n"
tool_ctx += f"DESCRIPTION: {t.get('description','')}\n"
tool_ctx += f"ARGUMENTS: {json.dumps(t.get('arguments',{}), indent=2)}\n"
tool_ctx += f"RETURNS: {json.dumps(t.get('returns',{}), indent=2)}\n"
tool_ctx += f"USAGE: {t.get('usage','No example')}\n"
tool_ctx += f"DETAILS: {t.get('details','No details')}\n"
tool_ctx += "-----------------------------------------\n"
else:
print("[MainAgent] Too many tools (> 10). Injecting REDUCED schema.")
tool_ctx = ""
for t in selected_tools:
tool_ctx += f"- {t['name']}: {t['description']}\n"

system_msg = f"""
Ești agentul principal.

Ai acces la următoarele tool-uri relevante:
{tool_ctx}

Instrucțiuni:
- Dacă schema este completă, folosește argumentele corecte.
- Dacă schema este redusă, deduci intenția și explici tool-ul.
- Nu inventa tool-uri noi.
- Răspunde pe baza tool-urilor date.
"""

model = genai.GenerativeModel(
model_name=MODEL_NAME,
system_instruction=system_msg
)

response = model.generate_content(prompt)

# 🔥 returnăm și response.usage_metadata
return response.text, response.usage_metadata



def main():
print("=== AI Delegate Action System ===")

user_prompt = input("\nUser: ")
print("\n[MainAgent] Passing prompt to Sub-Agent for classification...")

sub = SubAgentLLM(model_name=MODEL_NAME)

use_tools = sub.classify_use_tools(user_prompt)
print(f"[SubAgent] USE_TOOLS = {use_tools}")

if not use_tools:
print("\n[MainAgent] No tools needed. Gemini answering normally...\n")
model = genai.GenerativeModel(MODEL_NAME)
resp = model.generate_content(user_prompt)

usage = resp.usage_metadata
print("\n=== MAIN AGENT TOKEN USAGE ===")
print(f"Input tokens (system + user): {usage.prompt_token_count}")
print(f"Output tokens: {usage.candidates_token_count}")
print(f"Total tokens: {usage.total_token_count}")

print("\n=== FINAL ANSWER ===\n")
print(resp.text)
return

print("\n[MainAgent] Asking Sub-Agent for category detection...")
categories = sub.detect_categories(user_prompt)
print("[SubAgent] Categories:", categories)

if not categories:
print("[MainAgent] No categories found.")
return

print("\n[MainAgent] Asking Sub-Agent for specific tool selection...")
minimal_defs = sub.discover_and_minimize(user_prompt, categories)

if not minimal_defs:
print("[MainAgent] No tools selected.")
return

print("\n[MainAgent] Sending final minimal tool context to Gemini...\n")
result_text, usage = gemini_answer_with_tools(user_prompt, minimal_defs)


print("\n=== FINAL ANSWER SAVED TO FILE ===")

# scrie răspunsul în fișier
with open("raspuns_model_mcp.md", "w", encoding="utf-8") as f:
f.write(result_text)

print("File created: raspuns_model_mcp.md")
print("\n=== MAIN AGENT TOKEN USAGE ===")
print(f"Input tokens (system + user): {usage.prompt_token_count}")
print(f"Output tokens: {usage.candidates_token_count}")
print(f"Total tokens: {usage.total_token_count}")



if __name__ == "__main__":
main()
56 changes: 56 additions & 0 deletions AI-agents-delegate-actions/registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import json
import os

class ToolRegistry:
def __init__(self):
self.tools = {} # name → tool schema
self.categories = set() # e.g. "cdl.calendar", "dogedit.core"

def load_folder(self, folder):
for file in os.listdir(folder):
if file.endswith(".json"):
with open(os.path.join(folder, file)) as f:
for tool in json.load(f):
name = tool["name"]
self.tools[name] = tool

# extract category prefix: "cdl.calendar"
parts = name.split(".")
if len(parts) >= 2:
cat = parts[0] + "." + parts[1]
self.categories.add(cat)

def get_all_categories(self):
"""Return sorted list of unique categories."""
return sorted(list(self.categories))

def get_all_tool_names(self):
"""Return list of all tool names (for classification)."""
return list(self.tools.keys())

def get_tools_by_category(self, category_prefix):
"""Return all tools starting with that prefix."""
return [
tool for tool in self.tools.values()
if tool["name"].startswith(category_prefix)
]

def get_tools_by_names(self, names):
"""Return tool definitions for given names (if they exist)."""
return [
self.tools[name]
for name in names
if name in self.tools
]

def get_minimal_tool_info(self, names):
"""Return minimal (name + description) for MCP-style injection."""
result = []
for n in names:
if n in self.tools:
t = self.tools[n]
result.append({
"name": t["name"],
"description": t["description"]
})
return result
Loading