Cleaned and improved
This commit is contained in:
+63
-115
@@ -4,39 +4,17 @@ import json
|
||||
|
||||
from .llm import DeepSeekClient
|
||||
from .memory import Memory
|
||||
from .tools import make_tools, Tool
|
||||
from .registry import make_tools, Tool
|
||||
from .prompts import PromptBuilder
|
||||
|
||||
class Agent:
|
||||
def __init__(self, llm: DeepSeekClient, memory: Memory):
|
||||
def __init__(self, llm: DeepSeekClient, memory: Memory, max_tool_iterations: int = 5):
|
||||
self.llm = llm
|
||||
self.memory = memory
|
||||
self.tools: Dict[str, Tool] = make_tools(memory)
|
||||
self.prompt_builder = PromptBuilder(self.tools)
|
||||
self.max_tool_iterations = max_tool_iterations
|
||||
|
||||
def _build_system_prompt(self) -> str:
|
||||
ctx = {"project_root": self.memory.get_project_root()}
|
||||
tools_desc = "\n".join(
|
||||
f"- {t.name}: {t.description}" for t in self.tools.values()
|
||||
)
|
||||
return (
|
||||
"Tu es un agent IA qui aide un développeur senior à gérer son projet local.\n"
|
||||
"Tu peux demander des informations de base (comme le chemin du projet)\n"
|
||||
"et tu peux utiliser des outils pour interagir avec le système de fichiers.\n\n"
|
||||
"Contexte utilisateur (JSON):\n"
|
||||
f"{json.dumps(ctx, ensure_ascii=False)}\n\n"
|
||||
"Règles IMPORTANTES pour les outils:\n"
|
||||
"1. Si tu ne connais pas la valeur d'un argument (par exemple project_root), "
|
||||
"TU NE DOIS PAS mettre null ou une valeur inventée.\n"
|
||||
" À la place, tu dois poser une question à l'utilisateur pour obtenir l'information.\n"
|
||||
"2. Ne propose set_user_profile QUE lorsque l'utilisateur a donné un chemin de projet explicite.\n"
|
||||
"3. Quand tu veux utiliser un outil, réponds STRICTEMENT avec un JSON de la forme :\n"
|
||||
'{ "thought": "...", "action": { "name": "tool_name", "args": { ... } } }\n'
|
||||
" - Pas de texte avant/après.\n"
|
||||
" - Les args doivent être COMPLETS et NON nuls.\n"
|
||||
"4. Quand JE (le système) te fournis un object JSON contenant 'tool_result' et 'intent', "
|
||||
"tu dois ALORS répondre à l'utilisateur en TEXTE NATUREL, et NE PAS renvoyer de JSON d'action.\n\n"
|
||||
"Tools disponibles:\n"
|
||||
f"{tools_desc}\n"
|
||||
)
|
||||
|
||||
def _parse_intent(self, text: str) -> Dict[str, Any] | None:
|
||||
try:
|
||||
@@ -62,18 +40,6 @@ class Agent:
|
||||
name: str = action["name"]
|
||||
args: Dict[str, Any] = action.get("args", {}) or {}
|
||||
|
||||
if name == "set_user_profile":
|
||||
project_root = args.get("project_root")
|
||||
if not project_root:
|
||||
return {
|
||||
"error": "missing_project_root",
|
||||
"message": (
|
||||
"Le modèle a demandé set_user_profile sans project_root. "
|
||||
"Tu dois d'abord demander à l'utilisateur de fournir un chemin "
|
||||
"de projet valide (ex: /home/francois/mon_projet)."
|
||||
),
|
||||
}
|
||||
|
||||
tool = self.tools.get(name)
|
||||
if not tool:
|
||||
return {"error": "unknown_tool", "tool": name}
|
||||
@@ -87,95 +53,77 @@ class Agent:
|
||||
return result
|
||||
|
||||
def step(self, user_input: str) -> str:
|
||||
"""
|
||||
Execute one agent step with iterative tool execution:
|
||||
- Build system prompt
|
||||
- Query LLM
|
||||
- Loop: If JSON intent -> execute tool, add result to conversation, query LLM again
|
||||
- Continue until LLM responds with text (no tool call) or max iterations reached
|
||||
- Return final text response
|
||||
"""
|
||||
print("Starting a new step...")
|
||||
"""
|
||||
Un 'tour' d'agent :
|
||||
- construit le prompt system
|
||||
- interroge DeepSeek
|
||||
- si JSON d'intent -> exécute tool, refait un appel, renvoie réponse finale
|
||||
- sinon -> renvoie texte brut
|
||||
"""
|
||||
print("User input:", user_input)
|
||||
root = self.memory.data.get("project_root")
|
||||
print("Current project_root in memory:", root)
|
||||
|
||||
# Unified system prompt that always allows tools
|
||||
tools_desc = "\n".join(
|
||||
f"- {t.name}: {t.description}\n Paramètres: {json.dumps(t.parameters, ensure_ascii=False)}"
|
||||
for t in self.tools.values()
|
||||
)
|
||||
print("Current memory state:", self.memory.data)
|
||||
|
||||
if root is None:
|
||||
print("No project_root set - asking user and allowing tool use")
|
||||
system_prompt = (
|
||||
"Tu es un agent IA qui aide un développeur à gérer son projet local.\n\n"
|
||||
"CONTEXTE ACTUEL:\n"
|
||||
f"- project_root: {root} (NON DÉFINI)\n\n"
|
||||
"RÈGLES IMPORTANTES:\n"
|
||||
"1. Le project_root n'est pas encore défini. Tu DOIS d'abord demander à l'utilisateur "
|
||||
"le chemin absolu de son projet (ex: /home/user/mon_projet).\n"
|
||||
"2. Quand l'utilisateur te donne un chemin, tu DOIS immédiatement utiliser l'outil "
|
||||
"'set_project_root' pour le sauvegarder.\n"
|
||||
"3. Pour utiliser un outil, réponds STRICTEMENT avec ce format JSON:\n"
|
||||
' { "thought": "explication", "action": { "name": "nom_outil", "args": { "arg": "valeur" } } }\n'
|
||||
"4. Si tu réponds en texte (pas d'outil), réponds normalement en français.\n"
|
||||
"5. Quand le système te renvoie un tool_result, réponds à l'utilisateur en TEXTE NATUREL.\n\n"
|
||||
"OUTILS DISPONIBLES:\n"
|
||||
f"{tools_desc}\n"
|
||||
)
|
||||
else:
|
||||
print("Project_root is set - normal operation mode")
|
||||
system_prompt = (
|
||||
"Tu es un agent IA qui aide un développeur à gérer son projet local.\n\n"
|
||||
"CONTEXTE ACTUEL:\n"
|
||||
f"- project_root: {root}\n\n"
|
||||
"RÈGLES IMPORTANTES:\n"
|
||||
"1. Le project_root est défini. Tu peux utiliser les outils disponibles.\n"
|
||||
"2. Pour utiliser un outil, réponds STRICTEMENT avec ce format JSON:\n"
|
||||
' { "thought": "explication", "action": { "name": "nom_outil", "args": { "param": "valeur" } } }\n'
|
||||
" EXEMPLE pour lister le dossier 'src':\n"
|
||||
' { "thought": "L\'utilisateur veut voir le contenu de src", "action": { "name": "list_directory", "args": { "path": "src" } } }\n'
|
||||
" EXEMPLE pour lister la racine du projet:\n"
|
||||
' { "thought": "L\'utilisateur veut voir la racine", "action": { "name": "list_directory", "args": { "path": "." } } }\n'
|
||||
"3. Si tu réponds en texte (pas d'outil), réponds normalement en français.\n"
|
||||
"4. Quand le système te renvoie un tool_result, réponds à l'utilisateur en TEXTE NATUREL.\n"
|
||||
"5. IMPORTANT: Extrais le chemin demandé par l'utilisateur et passe-le comme argument 'path'.\n\n"
|
||||
"OUTILS DISPONIBLES:\n"
|
||||
f"{tools_desc}\n"
|
||||
)
|
||||
# Build system prompt using PromptBuilder
|
||||
system_prompt = self.prompt_builder.build_system_prompt(self.memory.data)
|
||||
|
||||
# Initialize conversation with user input
|
||||
messages: List[Dict[str, Any]] = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_input},
|
||||
]
|
||||
|
||||
raw_first = self.llm.complete(messages)
|
||||
intent = self._parse_intent(raw_first)
|
||||
print("raw_first:", raw_first)
|
||||
print("Intent:", intent)
|
||||
if not intent:
|
||||
# Réponse texte simple
|
||||
#self.memory.append_history("user", user_input)
|
||||
#self.memory.append_history("assistant", raw_first)
|
||||
return raw_first
|
||||
# Tool execution loop
|
||||
iteration = 0
|
||||
while iteration < self.max_tool_iterations:
|
||||
print(f"\n--- Iteration {iteration + 1} ---")
|
||||
|
||||
# Exécuter l'action
|
||||
tool_result = self._execute_action(intent)
|
||||
# Get LLM response
|
||||
llm_response = self.llm.complete(messages)
|
||||
print("LLM response:", llm_response)
|
||||
|
||||
followup_messages: List[Dict[str, Any]] = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_input},
|
||||
{
|
||||
# Try to parse as tool intent
|
||||
intent = self._parse_intent(llm_response)
|
||||
|
||||
if not intent:
|
||||
# No tool call - this is the final text response
|
||||
print("No tool intent detected, returning final response")
|
||||
# Save to history
|
||||
self.memory.append_history("user", user_input)
|
||||
self.memory.append_history("assistant", llm_response)
|
||||
return llm_response
|
||||
|
||||
# Tool call detected - execute it
|
||||
print("Intent detected:", intent)
|
||||
tool_result = self._execute_action(intent)
|
||||
print("Tool result:", tool_result)
|
||||
|
||||
# Add assistant's tool call and result to conversation
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": json.dumps(intent, ensure_ascii=False)
|
||||
})
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": json.dumps(
|
||||
{"tool_result": tool_result, "intent": intent},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
},
|
||||
]
|
||||
{"tool_result": tool_result},
|
||||
ensure_ascii=False
|
||||
)
|
||||
})
|
||||
|
||||
raw_second = self.llm.complete(followup_messages)
|
||||
iteration += 1
|
||||
|
||||
#self.memory.append_history("user", user_input)
|
||||
#self.memory.append_history("assistant", raw_second)
|
||||
return raw_second
|
||||
# Max iterations reached - ask LLM for final response
|
||||
print(f"\n--- Max iterations ({self.max_tool_iterations}) reached, requesting final response ---")
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": "Merci pour ces résultats. Peux-tu maintenant me donner une réponse finale en texte naturel ?"
|
||||
})
|
||||
|
||||
final_response = self.llm.complete(messages)
|
||||
# Save to history
|
||||
self.memory.append_history("user", user_input)
|
||||
self.memory.append_history("assistant", final_response)
|
||||
return final_response
|
||||
|
||||
Reference in New Issue
Block a user