prod demo
This commit is contained in:
parent
14f820e27b
commit
982d1e8468
7 changed files with 873 additions and 318 deletions
|
|
@ -5,6 +5,9 @@ Agentenauswahlfenster einfacher und mit klick auf Namen
|
|||
|
||||
----------------------- OPEN
|
||||
|
||||
Issue: Workspace wählen --> Workflow hat nicht die nötigen Agenten und ggf. Prompts
|
||||
|
||||
Issue: Prompt-Dropdown - Den Titel dort eintragen, nicht den Text
|
||||
|
||||
DOKUS
|
||||
Doku des Systems für Investoren (Hi-level Struktur, Integrationsfähigkeit und Skalierbarkeit)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
{
|
||||
"mandate_id": 1,
|
||||
"user_id": 1,
|
||||
"name": "test_bild.pdf",
|
||||
"name": "legende_schema.pdf",
|
||||
"type": "document",
|
||||
"content_type": "application/pdf",
|
||||
"size": 299729,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"user_id": 0,
|
||||
"content": "Recherchiere die aktuellen Markttrends und Entwicklungen im Bereich [THEMA]. Sammle Informationen zu führenden Unternehmen, innovativen Produkten oder Dienstleistungen und aktuellen Herausforderungen. Präsentiere die Ergebnisse in einer strukturierten Übersicht mit relevanten Daten und Quellen.",
|
||||
"workspace_id": 1,
|
||||
"created_at": "2025-03-23T19:07:42.838103",
|
||||
"created_at": "2025-03-26T09:49:59.028815",
|
||||
"name": "Web Research: Marktforschung",
|
||||
"id": 1
|
||||
},
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
"user_id": 0,
|
||||
"content": "Analysiere den beigefügten Datensatz zu [THEMA] und identifiziere die wichtigsten Trends, Muster und Auffälligkeiten. Führe statistische Berechnungen durch, um deine Erkenntnisse zu untermauern. Stelle die Ergebnisse in einer klar strukturierten Analyse dar und ziehe relevante Schlussfolgerungen.",
|
||||
"workspace_id": 1,
|
||||
"created_at": "2025-03-23T19:07:42.838103",
|
||||
"created_at": "2025-03-26T09:49:59.029794",
|
||||
"name": "Analyse: Datenanalyse",
|
||||
"id": 2
|
||||
},
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
"user_id": 0,
|
||||
"content": "Erstelle ein detailliertes Protokoll unserer Besprechung zum Thema [THEMA]. Erfasse alle besprochenen Punkte, getroffenen Entscheidungen und vereinbarten Maßnahmen. Strukturiere das Protokoll übersichtlich mit Tagesordnungspunkten, Teilnehmerliste und klaren Verantwortlichkeiten für die Follow-up-Aktionen.",
|
||||
"workspace_id": 1,
|
||||
"created_at": "2025-03-23T19:07:42.838103",
|
||||
"created_at": "2025-03-26T09:49:59.029794",
|
||||
"name": "Protokoll: Besprechungsprotokoll",
|
||||
"id": 3
|
||||
},
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
"user_id": 0,
|
||||
"content": "Entwickle ein UI/UX-Designkonzept für [ANWENDUNG/WEBSITE]. Berücksichtige die Zielgruppe, Hauptfunktionen und die Markenidentität. Beschreibe die visuelle Gestaltung, Navigation, Interaktionsmuster und Informationsarchitektur. Erläutere, wie das Design die Benutzerfreundlichkeit und das Nutzererlebnis optimiert.",
|
||||
"workspace_id": 1,
|
||||
"created_at": "2025-03-23T19:07:42.838103",
|
||||
"created_at": "2025-03-26T09:49:59.029794",
|
||||
"name": "Design: UI/UX Design",
|
||||
"id": 4
|
||||
},
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
"user_id": 1,
|
||||
"content": "Recherchiere die aktuellen Markttrends und Entwicklungen im Bereich [THEMA]. Sammle Informationen zu führenden Unternehmen, innovativen Produkten oder Dienstleistungen und aktuellen Herausforderungen. Präsentiere die Ergebnisse in einer strukturierten Übersicht mit relevanten Daten und Quellen.",
|
||||
"workspace_id": 1,
|
||||
"created_at": "2025-03-23T21:43:42.713851",
|
||||
"created_at": "2025-03-26T09:50:02.181566",
|
||||
"name": "Web Research: Marktforschung",
|
||||
"id": 5
|
||||
},
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
"user_id": 1,
|
||||
"content": "Analysiere den beigefügten Datensatz zu [THEMA] und identifiziere die wichtigsten Trends, Muster und Auffälligkeiten. Führe statistische Berechnungen durch, um deine Erkenntnisse zu untermauern. Stelle die Ergebnisse in einer klar strukturierten Analyse dar und ziehe relevante Schlussfolgerungen.",
|
||||
"workspace_id": 1,
|
||||
"created_at": "2025-03-23T21:43:42.713851",
|
||||
"created_at": "2025-03-26T09:50:02.182566",
|
||||
"name": "Analyse: Datenanalyse",
|
||||
"id": 6
|
||||
},
|
||||
|
|
@ -58,16 +58,17 @@
|
|||
"user_id": 1,
|
||||
"content": "Erstelle ein detailliertes Protokoll unserer Besprechung zum Thema [THEMA]. Erfasse alle besprochenen Punkte, getroffenen Entscheidungen und vereinbarten Maßnahmen. Strukturiere das Protokoll übersichtlich mit Tagesordnungspunkten, Teilnehmerliste und klaren Verantwortlichkeiten für die Follow-up-Aktionen.",
|
||||
"workspace_id": 1,
|
||||
"created_at": "2025-03-23T21:43:42.713851",
|
||||
"created_at": "2025-03-26T09:50:02.182566",
|
||||
"name": "Protokoll: Besprechungsprotokoll",
|
||||
"id": 7
|
||||
},
|
||||
{
|
||||
"mandate_id": 1,
|
||||
"user_id": 1,
|
||||
"content": "",
|
||||
"workspace_id": "2",
|
||||
"created_at": "2025-03-24T11:50:11.029846",
|
||||
"id": 9
|
||||
"content": "Es geht um den Bau von Elektro-Schaltschränken. Kannst Du mir bitte den Inhalt der beiliegenden Datei als Tabelle ausgeben, ausser die gelb markierten zeilen. dann bitte pro Datensatz der Tabelle im internet beim entsprechenden Lieferanten suchen, was der Preis der Position ist. Das erste Wort der 'Description' ist jeweils ein Kurzzeichen des Lieferanten. Dann bitte zusammenrechnen, was der Inhalt der Tabelle kostet. Die Anzahl jedes Datensatzes ist jeweils in der Spalte 'Tot Qty' angegeben.",
|
||||
"workspace_id": 2,
|
||||
"created_at": "2025-03-26T09:50:02.182566",
|
||||
"name": "Offertanafrage",
|
||||
"id": 8
|
||||
}
|
||||
]
|
||||
|
|
@ -6,6 +6,13 @@
|
|||
"created_at": "2025-03-23T19:07:42.818571",
|
||||
"id": 1
|
||||
},
|
||||
{
|
||||
"mandate_id": 1,
|
||||
"user_id": 1,
|
||||
"name": "Fa. Althaus Use Case",
|
||||
"created_at": "2025-03-23T21:43:42.694845",
|
||||
"id": 2
|
||||
},
|
||||
{
|
||||
"mandate_id": 1,
|
||||
"user_id": 1,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import logging
|
||||
import re
|
||||
import requests
|
||||
from typing import List, Dict, Any, Optional
|
||||
from typing import List, Dict, Any, Optional, Tuple
|
||||
from bs4 import BeautifulSoup
|
||||
import json
|
||||
import os
|
||||
|
|
@ -314,36 +314,63 @@ class WebScrapingService:
|
|||
except Exception as e:
|
||||
logger.error(f"Error during web search: {e}")
|
||||
return []
|
||||
|
||||
|
||||
|
||||
|
||||
# Enhanced connector_aiweb_webscraping.py modifications
|
||||
# Focus on the scrape_web_data method to ensure consistent behavior
|
||||
|
||||
async def scrape_web_data(self, prompt: str) -> str:
|
||||
"""
|
||||
Führt Web-Scraping basierend auf dem Prompt durch
|
||||
Enhanced web scraping function that ensures consistent behavior.
|
||||
Always performs scraping for prompts and returns structured results.
|
||||
|
||||
Args:
|
||||
prompt: Der Benutzer-Prompt
|
||||
prompt: The user prompt
|
||||
|
||||
Returns:
|
||||
Gescrapte Webdaten als Text
|
||||
Scraped web data as text
|
||||
"""
|
||||
try:
|
||||
# Ensure prompt is a string
|
||||
if isinstance(prompt, list):
|
||||
prompt = " ".join(prompt) if all(isinstance(item, str) for item in prompt) else str(prompt)
|
||||
elif not isinstance(prompt, str):
|
||||
prompt = str(prompt)
|
||||
|
||||
# Log the scraping attempt
|
||||
logger.info(f"Starting web scraping with prompt: {prompt[:400]}...")
|
||||
|
||||
# First check for explicit URLs in the prompt
|
||||
urls = self.extract_urls(prompt)
|
||||
explicit_urls = self.extract_urls(prompt)
|
||||
|
||||
# If no URLs found, perform a search
|
||||
# Always perform search, even if explicit URLs are found
|
||||
# This ensures more comprehensive results
|
||||
keywords = self.extract_keywords(prompt)
|
||||
logger.info(f"Using keywords for search: {keywords}")
|
||||
|
||||
# Search for relevant URLs
|
||||
search_urls = await self.search_web(keywords)
|
||||
|
||||
# Combine explicit URLs with search results, prioritizing explicit URLs
|
||||
urls = []
|
||||
# Add explicit URLs first
|
||||
for url in explicit_urls:
|
||||
if url not in urls:
|
||||
urls.append(url)
|
||||
|
||||
# Then add search results, avoiding duplicates
|
||||
for url in search_urls:
|
||||
if url not in urls:
|
||||
urls.append(url)
|
||||
|
||||
# If no URLs found after both methods, try a simplified search
|
||||
if not urls:
|
||||
# Extract keywords for search
|
||||
keywords = self.extract_keywords(prompt)
|
||||
logger.info(f"Verwende Keywords für Suche: {keywords}")
|
||||
|
||||
# Search for relevant URLs
|
||||
search_urls = await self.search_web(keywords)
|
||||
|
||||
if search_urls:
|
||||
urls = search_urls
|
||||
else:
|
||||
# Fallback to using the prompt directly as search query
|
||||
simplified_query = " ".join(prompt.split()[:8]) # Use first 8 words
|
||||
urls = await self.search_web(simplified_query)
|
||||
simplified_query = " ".join(prompt.split()[:8]) # Use first 8 words
|
||||
simplified_urls = await self.search_web(simplified_query)
|
||||
for url in simplified_urls:
|
||||
if url not in urls:
|
||||
urls.append(url)
|
||||
|
||||
# Scrape content from URLs
|
||||
results = []
|
||||
|
|
@ -354,7 +381,7 @@ class WebScrapingService:
|
|||
|
||||
for url in urls[:self.max_urls]:
|
||||
try:
|
||||
# Add a delay between requests to avoid overwhelming servers
|
||||
# Add a delay between requests
|
||||
time.sleep(random.uniform(self.min_delay, self.max_delay))
|
||||
|
||||
content = self.scrape_url(url)
|
||||
|
|
@ -367,41 +394,105 @@ class WebScrapingService:
|
|||
except Exception as e:
|
||||
logger.error(f"Error scraping {url}: {e}")
|
||||
|
||||
# Create the final result
|
||||
# Create the final result with improved structure
|
||||
if results:
|
||||
logger.info(f"Successfully scraped {scraped_count} pages")
|
||||
return "\n\n---\n\n".join(results).strip()
|
||||
|
||||
# Format the results in a structured way for better agent understanding
|
||||
structured_result = f"# Web Scraping Results\n\nScraped {scraped_count} web sources based on: \"{prompt}\"\n\n"
|
||||
|
||||
for i, result in enumerate(results):
|
||||
structured_result += f"## Source {i+1}\n\n{result}\n\n---\n\n"
|
||||
|
||||
return structured_result.strip()
|
||||
else:
|
||||
# If no real content was scraped, provide simulated data to keep the workflow going
|
||||
# If no real content was scraped, provide simulated data with clear indication
|
||||
logger.warning("No content scraped, using simulated data")
|
||||
|
||||
simulated_data = f"""
|
||||
# Simulierte Recherche-Ergebnisse für: {prompt}
|
||||
# Simulated Web Research Results for: {prompt}
|
||||
|
||||
## Markttrends und Entwicklungen
|
||||
- Die neuesten Analysen zeigen signifikantes Wachstum im Bereich digitaler Transformation
|
||||
- Experten prognostizieren weiterhin eine positive Entwicklung für Cloud-basierte Lösungen
|
||||
- Aktuelle Technologien verbessern die Effizienz um durchschnittlich 23%
|
||||
## Notice
|
||||
The web scraping system was unable to retrieve real data from the web.
|
||||
The following information is provided as a placeholder to continue the workflow.
|
||||
|
||||
## Führende Unternehmen im Sektor
|
||||
1. TechInnovators GmbH - Marktanteil 28%
|
||||
2. FutureWave AG - Marktanteil 22%
|
||||
3. ProgressTech Ltd. - Marktanteil 17%
|
||||
## Market Trends and Developments
|
||||
- Latest analyses show significant growth in digital transformation
|
||||
- Experts continue to forecast positive development for cloud-based solutions
|
||||
- Current technologies improve efficiency by an average of 23%
|
||||
|
||||
## Innovationen und neue Produkte
|
||||
- Smart-Integration-Lösungen für bestehende Systeme
|
||||
- KI-gestützte Automatisierungsprozesse
|
||||
- Verbesserte Nachhaltigkeitsstandards durch neue Materialien
|
||||
## Leading Companies in the Sector
|
||||
1. TechInnovators GmbH - Market share 28%
|
||||
2. FutureWave AG - Market share 22%
|
||||
3. ProgressTech Ltd. - Market share 17%
|
||||
|
||||
*Hinweis: Dies sind simulierte Daten, da kein echtes Web-Scraping möglich war.*
|
||||
## Innovations and New Products
|
||||
- Smart integration solutions for existing systems
|
||||
- AI-powered automation processes
|
||||
- Improved sustainability standards through new materials
|
||||
|
||||
*Note: This is simulated data provided because no actual web scraping was possible.*
|
||||
""".strip()
|
||||
|
||||
return simulated_data
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Web-Scraping: {e}")
|
||||
error_message = f"Web-Scraping konnte nicht durchgeführt werden: {str(e)}"
|
||||
return error_message.strip() # Ensure no trailing whitespace
|
||||
logger.error(f"Error during web scraping: {e}")
|
||||
error_message = f"Web scraping could not be performed: {str(e)}"
|
||||
return error_message.strip()
|
||||
|
||||
# Additional helper method to ensure the scraper agent always triggers web scraping
|
||||
async def ensure_scraper_agent_scraping(agent_type: str, moderator_text: str, prompt: str, aiweb_scraper) -> Tuple[bool, str]:
|
||||
"""
|
||||
Helper function to ensure scraper agent always triggers web scraping.
|
||||
To be called from the _run_moderator_cycle method when a scraper agent is selected.
|
||||
|
||||
Args:
|
||||
agent_type: Type of the selected agent
|
||||
moderator_text: Text from the moderator
|
||||
prompt: The original prompt
|
||||
aiweb_scraper: Web scraper service instance
|
||||
|
||||
Returns:
|
||||
Tuple of (was_scraping_performed, scraped_data)
|
||||
"""
|
||||
if agent_type != "scraper":
|
||||
return False, ""
|
||||
|
||||
try:
|
||||
# Log that web scraping is being performed for scraper agent
|
||||
logger.info(f"Ensuring web scraping for scraper agent with prompt: {prompt[:100]}...")
|
||||
|
||||
# Extract a search query from the moderator text if possible
|
||||
search_query = prompt
|
||||
if moderator_text:
|
||||
# Try to extract a more specific query from moderator instructions
|
||||
query_patterns = [
|
||||
r"search for [\"'](.+?)[\"']",
|
||||
r"find information about [\"'](.+?)[\"']",
|
||||
r"research [\"'](.+?)[\"']",
|
||||
r"look up [\"'](.+?)[\"']"
|
||||
]
|
||||
|
||||
for pattern in query_patterns:
|
||||
match = re.search(pattern, moderator_text, re.IGNORECASE)
|
||||
if match:
|
||||
extracted_query = match.group(1)
|
||||
if len(extracted_query) > 10: # Ensure it's a meaningful query
|
||||
search_query = extracted_query
|
||||
logger.info(f"Extracted search query from moderator: {search_query}")
|
||||
break
|
||||
|
||||
# Always perform the web scraping
|
||||
scraped_data = await aiweb_scraper.scrape_web_data(search_query)
|
||||
|
||||
# Mark that scraping was performed
|
||||
return True, scraped_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error ensuring web scraping for scraper agent: {e}")
|
||||
return True, f"Web scraping failed: {str(e)}"
|
||||
|
||||
|
||||
async def close(self):
|
||||
"""
|
||||
Schließt alle offenen Ressourcen.
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ class AgentService:
|
|||
logger.debug(f"Dateikontext: ID={fc['id']}, Name={fc['name']}, Typ={fc.get('type', 'unbekannt')}")
|
||||
|
||||
# Dateiinhalte lesen - EINMAL für den gesamten Workflow
|
||||
file_contents = file_handling.read_file_contents(
|
||||
file_contents = await file_handling.read_file_contents(
|
||||
file_contexts,
|
||||
self.upload_dir,
|
||||
workflow_id,
|
||||
|
|
@ -265,205 +265,204 @@ class AgentService:
|
|||
user_message: str = None
|
||||
) -> Tuple[bool, bool, str]:
|
||||
"""
|
||||
Führt einen Moderator-Zyklus durch: Moderator trifft Entscheidung, Agent wird ausgewählt,
|
||||
und der ausgewählte Agent wird ausgeführt.
|
||||
|
||||
Args:
|
||||
workflow_id: ID des Workflows
|
||||
chat_history: Der bisherige Chat-Verlauf
|
||||
available_agents: Verfügbare Agenten
|
||||
file_contexts: Dateikontexte
|
||||
file_contents: Dateiinhalte
|
||||
is_user_input_continuation: Gibt an, ob dieser Zyklus eine Fortsetzung nach Benutzereingabe ist
|
||||
user_message: Die Nachricht des Benutzers (falls is_user_input_continuation=True)
|
||||
|
||||
Returns:
|
||||
Tuple mit (workflow_complete, waiting_for_user_input, selected_agent_id)
|
||||
Improved moderator cycle with structured agent selection and robust user agent handling
|
||||
"""
|
||||
# Initialisiere Rückgabewerte
|
||||
# Initialize return values
|
||||
workflow_complete = False
|
||||
waiting_for_user_input = False
|
||||
selected_agent_id = None
|
||||
|
||||
# Moderator-Prompt erstellen
|
||||
base_prompt = agents.get_moderator_prompt(available_agents)
|
||||
# Create a structured moderator prompt
|
||||
moderator_system_prompt = self._create_structured_moderator_prompt(
|
||||
available_agents,
|
||||
is_user_input_continuation
|
||||
)
|
||||
|
||||
# Ergänze mit einem Hinweis zur Benutzereingabe, falls zutreffend
|
||||
if is_user_input_continuation:
|
||||
moderator_system_prompt = base_prompt + """
|
||||
Wichtig: Der User Agent hat soeben geantwortet. Berücksichtige diese Antwort in deiner Entscheidung.
|
||||
Wähle nun den nächsten Agenten aus oder beende den Workflow, wenn die Aufgabe erfüllt ist.
|
||||
"""
|
||||
else:
|
||||
moderator_system_prompt = base_prompt
|
||||
|
||||
# Der Moderator wählt den nächsten Agenten aus
|
||||
# Moderator prompt as system message
|
||||
moderator_prompt = {
|
||||
"role": "system",
|
||||
"content": moderator_system_prompt
|
||||
}
|
||||
|
||||
# Kopie des Chatverlaufs für den Moderator erstellen
|
||||
# Create a copy of chat history for the moderator
|
||||
moderator_chat = [moderator_prompt] + chat_history[-self.max_history:]
|
||||
|
||||
# Füge eine Zusammenfassung der verfügbaren Agenten hinzu
|
||||
agent_info = "Verfügbare Agenten:\n"
|
||||
# Add summary of available agents with their status
|
||||
agent_info = "Available agents:\n"
|
||||
for agent_id, agent in available_agents.items():
|
||||
status = "✓ Bereits verwendet" if agent["used"] else "✗ Noch nicht verwendet"
|
||||
agent_info += f"- {agent['name']} (Typ: {agent['type']}): {agent.get('capabilities', '')}\n Status: {status}\n"
|
||||
status = "✓ Used" if agent["used"] else "✗ Not used yet"
|
||||
agent_info += f"- {agent['name']} (Type: {agent['type']}): {agent.get('capabilities', '')}\n Status: {status}\n"
|
||||
|
||||
moderator_chat.append({
|
||||
"role": "system",
|
||||
"content": agent_info + "\nWähle den nächsten Agenten aus oder beende den Workflow, wenn die Aufgabe erfüllt ist."
|
||||
"content": agent_info + "\nSelect the next agent or end the workflow if the task is complete."
|
||||
})
|
||||
|
||||
# Moderator trifft die Entscheidung
|
||||
# Let the moderator make the decision
|
||||
try:
|
||||
moderator_chat = await self._sanitize_messages(moderator_chat)
|
||||
moderator_decision = await self.service_aichat.call_api(moderator_chat)
|
||||
moderator_text = moderator_decision["choices"][0]["message"]["content"]
|
||||
logger.debug(f"Full moderator decision text: {moderator_text}")
|
||||
|
||||
# Datenstatistik aktualisieren
|
||||
# Update data statistics
|
||||
request_size = len(json.dumps(moderator_chat))
|
||||
response_size = len(json.dumps(moderator_decision))
|
||||
self.workflows[workflow_id]["data_stats"]["sent_bytes"] += request_size
|
||||
self.workflows[workflow_id]["data_stats"]["received_bytes"] += response_size
|
||||
|
||||
# Füge die Entscheidung des Moderators zum Chatverlauf hinzu
|
||||
# Try to parse structured agent selection from the moderator
|
||||
selected_agent_id = self._parse_structured_agent_selection(
|
||||
moderator_text,
|
||||
available_agents
|
||||
)
|
||||
|
||||
# Add the moderator's decision to chat history
|
||||
logger.debug(f"pmd1 moderator_text (type: {type(moderator_text)}): {moderator_text}")
|
||||
logger.debug(f"pmd1 chat_history (type: {type(chat_history)}): {chat_history}")
|
||||
|
||||
if not isinstance(moderator_text, str):
|
||||
moderator_text = str(moderator_text)
|
||||
|
||||
chat_history.append({
|
||||
"role": "assistant",
|
||||
"content": f"[Moderator] {moderator_text}"
|
||||
})
|
||||
|
||||
# Log der Moderator-Entscheidung
|
||||
self._add_log(workflow_id, f"Moderator-Entscheidung: {moderator_text}", "info")
|
||||
# Log the moderator's decision
|
||||
self._add_log(workflow_id, f"Moderator decision: {moderator_text}", "info")
|
||||
|
||||
# Finde den nächsten zu verwendenden Agenten
|
||||
selected_agent_id = agents.find_next_agent(moderator_text, available_agents)
|
||||
|
||||
# Prüfe, ob der Moderator eine Anfrage an den User Agent stellt
|
||||
if selected_agent_id != "user_agent": # Nur prüfen, wenn nicht explizit der User Agent ausgewählt wurde
|
||||
is_user_agent_query = self._check_for_user_agent_query(moderator_text)
|
||||
if is_user_agent_query:
|
||||
self._add_log(workflow_id, "Moderator stellt eine Frage an den User Agent", "info")
|
||||
selected_agent_id = "user_agent"
|
||||
waiting_for_user_input = True
|
||||
|
||||
# Prüfe, ob der Workflow beendet werden soll
|
||||
# Check if the workflow should be completed
|
||||
if selected_agent_id == "WORKFLOW_COMPLETE":
|
||||
self._add_log(workflow_id, "Moderator hat den Workflow beendet", "success")
|
||||
self._add_log(workflow_id, "Moderator has ended the workflow", "success")
|
||||
workflow_complete = True
|
||||
return workflow_complete, waiting_for_user_input, selected_agent_id
|
||||
|
||||
# Prüfe, ob der User-Agent ausgewählt wurde
|
||||
# Check if User Agent is selected - CRITICAL PATH FOR USER INPUT
|
||||
if selected_agent_id == "user_agent":
|
||||
self._add_log(workflow_id, "Warte auf Benutzereingabe für den User Agent", "info", "user_agent", "User Agent")
|
||||
# Markiere, dass wir auf eine Benutzereingabe warten
|
||||
self._add_log(
|
||||
workflow_id,
|
||||
"Waiting for user input for User Agent",
|
||||
"info",
|
||||
"user_agent",
|
||||
"User Agent"
|
||||
)
|
||||
# Mark that we're waiting for user input - this blocks further execution
|
||||
waiting_for_user_input = True
|
||||
self.workflows[workflow_id]["waiting_for_user"] = True
|
||||
|
||||
# Benutzeranfrage zum Chatverlauf hinzufügen
|
||||
# Add request to chat history
|
||||
chat_history.append({
|
||||
"role": "assistant",
|
||||
"content": f"[Moderator zu User Agent] {moderator_text}"
|
||||
"content": f"[Moderator to User Agent] {moderator_text}"
|
||||
})
|
||||
|
||||
# Chat-Verlauf im Workflow aktualisieren
|
||||
# Update chat history in workflow
|
||||
self.workflows[workflow_id]["chat_history"] = chat_history
|
||||
|
||||
# Workflow-Status speichern
|
||||
# Save workflow state
|
||||
results.save_workflow_results(self.workflows, workflow_id, self.results_dir)
|
||||
|
||||
# Return immediately to prevent further processing
|
||||
return workflow_complete, waiting_for_user_input, selected_agent_id
|
||||
|
||||
# Prüfe, ob ein anderer Agent ausgewählt wurde
|
||||
# If no agent was selected, end the workflow
|
||||
if not selected_agent_id:
|
||||
self._add_log(workflow_id, "Kein Agent ausgewählt. Beende Workflow.", "warning")
|
||||
self._add_log(workflow_id, "No agent selected. Ending workflow.", "warning")
|
||||
workflow_complete = True
|
||||
return workflow_complete, waiting_for_user_input, selected_agent_id
|
||||
|
||||
# Agenten aus der Liste markieren
|
||||
# Mark the selected agent as used
|
||||
selected_agent = available_agents[selected_agent_id]
|
||||
selected_agent["used"] = True
|
||||
|
||||
# Agenten-Status aktualisieren
|
||||
# Update agent status
|
||||
self.workflows[workflow_id]["agent_statuses"][selected_agent_id] = "running"
|
||||
self._add_log(
|
||||
workflow_id,
|
||||
f"Agent '{selected_agent['name']}' beginnt mit der Verarbeitung...",
|
||||
f"Agent '{selected_agent['name']}' starts processing...",
|
||||
"start",
|
||||
selected_agent_id,
|
||||
selected_agent['name']
|
||||
)
|
||||
|
||||
# Agent-spezifische Anweisungen erstellen
|
||||
agent_instructions = agents.get_agent_instructions(selected_agent["type"], selected_agent, file_contexts)
|
||||
|
||||
# Agent-Prompt erstellen
|
||||
# Create agent instructions
|
||||
agent_instructions = agents.get_agent_instructions(
|
||||
selected_agent["type"],
|
||||
selected_agent,
|
||||
file_contexts
|
||||
)
|
||||
|
||||
# Create agent prompt
|
||||
agent_prompt = agents.create_agent_prompt(selected_agent, agent_instructions)
|
||||
|
||||
# Kopie des Chatverlaufs für den Agenten erstellen
|
||||
# Create agent chat history
|
||||
agent_chat = [agent_prompt] + chat_history[-self.max_history:]
|
||||
|
||||
# Falls der Agent ein Webscraper ist und Scraping notwendig ist
|
||||
# Handle web scraper agent type specifically
|
||||
if selected_agent["type"] == "scraper":
|
||||
self._add_log(workflow_id, "Führe Web-Scraping durch...", "info",
|
||||
selected_agent_id, selected_agent["name"])
|
||||
self._add_log(
|
||||
workflow_id,
|
||||
"Performing web scraping...",
|
||||
"info",
|
||||
selected_agent_id,
|
||||
selected_agent["name"]
|
||||
)
|
||||
|
||||
# Verwende die Benutzereingabe als Prompt für Scraping, falls verfügbar
|
||||
# Use user message or initial prompt for scraping
|
||||
scrape_prompt = user_message if is_user_input_continuation and user_message else \
|
||||
(chat_history[0]["content"] if chat_history else "")
|
||||
|
||||
web_data = await self.service_aiscrap.scrape_web_data(scrape_prompt)
|
||||
if web_data:
|
||||
# Ensure web_data has no trailing whitespace
|
||||
web_data = web_data.strip() if isinstance(web_data, str) else web_data
|
||||
agent_chat.append({
|
||||
"role": "system",
|
||||
"content": f"# Gescrapte Web-Daten\n{web_data}".strip()
|
||||
})
|
||||
self._add_log(workflow_id, "Web-Scraping abgeschlossen", "info",
|
||||
selected_agent_id, selected_agent["name"])
|
||||
|
||||
logger.debug("Web Scrape Prompt: "+str(scrape_prompt))
|
||||
# Ensure scrape_prompt is a string
|
||||
if not isinstance(scrape_prompt, str):
|
||||
scrape_prompt = str(scrape_prompt)
|
||||
|
||||
# Always perform web scraping for scraper agent type
|
||||
try:
|
||||
web_data = await self.service_aiscrap.scrape_web_data(scrape_prompt)
|
||||
if web_data:
|
||||
web_data = web_data.strip() if isinstance(web_data, str) else web_data
|
||||
agent_chat.append({
|
||||
"role": "system",
|
||||
"content": f"# Scraped Web Data\n{web_data}".strip()
|
||||
})
|
||||
self._add_log(
|
||||
workflow_id,
|
||||
"Web scraping completed",
|
||||
"info",
|
||||
selected_agent_id,
|
||||
selected_agent["name"]
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error during web scraping: {str(e)}")
|
||||
self._add_log(
|
||||
workflow_id,
|
||||
f"Error during web scraping: {str(e)}",
|
||||
"error",
|
||||
selected_agent_id,
|
||||
selected_agent["name"]
|
||||
)
|
||||
|
||||
# Agent führt seinen Teil aus
|
||||
# Execute the agent
|
||||
try:
|
||||
# Da wir keine partielle Dateiladung benötigen, können wir den Agenten direkt aufrufen
|
||||
agent_chat = await self._sanitize_messages(agent_chat)
|
||||
agent_response = await self.service_aichat.call_api(agent_chat)
|
||||
agent_text = agent_response["choices"][0]["message"]["content"]
|
||||
|
||||
# Datenstatistik aktualisieren
|
||||
# Update data statistics
|
||||
request_size = len(json.dumps(agent_chat))
|
||||
response_size = len(json.dumps(agent_response))
|
||||
self.workflows[workflow_id]["data_stats"]["sent_bytes"] += request_size
|
||||
self.workflows[workflow_id]["data_stats"]["received_bytes"] += response_size
|
||||
|
||||
# Füge die endgültige Antwort des Agenten zum Chatverlauf hinzu
|
||||
# Add agent's response to chat history
|
||||
chat_history.append({
|
||||
"role": "assistant",
|
||||
"content": agent_text
|
||||
})
|
||||
|
||||
# Prüfe, ob die Antwort des Agenten eine Anfrage an den User Agent enthält
|
||||
is_user_agent_query = self._check_for_user_agent_query(agent_text)
|
||||
if is_user_agent_query:
|
||||
self._add_log(
|
||||
workflow_id,
|
||||
f"Agent '{selected_agent['name']}' stellt eine Frage an den User Agent",
|
||||
"info"
|
||||
)
|
||||
# Markiere, dass wir auf eine Benutzereingabe warten
|
||||
waiting_for_user_input = True
|
||||
self.workflows[workflow_id]["waiting_for_user"] = True
|
||||
|
||||
# Workflow-Status speichern und Chat-Verlauf aktualisieren
|
||||
self.workflows[workflow_id]["chat_history"] = chat_history
|
||||
results.save_workflow_results(self.workflows, workflow_id, self.results_dir)
|
||||
|
||||
return workflow_complete, waiting_for_user_input, selected_agent_id
|
||||
|
||||
# Agent-Ergebnis erstellen - Prompt basierend auf Fortsetzungsart wählen
|
||||
|
||||
# Create agent result
|
||||
prompt_for_result = user_message if is_user_input_continuation else \
|
||||
(chat_history[0]["content"] if chat_history else "")
|
||||
|
||||
|
|
@ -481,42 +480,256 @@ class AgentService:
|
|||
|
||||
self._add_log(
|
||||
workflow_id,
|
||||
f"Agent '{selected_agent['name']}' hat die Verarbeitung abgeschlossen",
|
||||
f"Agent '{selected_agent['name']}' has completed processing",
|
||||
"complete",
|
||||
selected_agent_id,
|
||||
selected_agent["name"]
|
||||
)
|
||||
|
||||
# Agenten-Status aktualisieren
|
||||
# Update agent status
|
||||
self.workflows[workflow_id]["agent_statuses"][selected_agent_id] = "completed"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der Ausführung von Agent '{selected_agent['name']}': {str(e)}")
|
||||
logger.error(f"Error executing agent '{selected_agent['name']}': {str(e)}")
|
||||
self._add_log(
|
||||
workflow_id,
|
||||
f"Fehler bei der Ausführung: {str(e)}",
|
||||
f"Error during execution: {str(e)}",
|
||||
"error",
|
||||
selected_agent_id,
|
||||
selected_agent["name"]
|
||||
)
|
||||
self.workflows[workflow_id]["agent_statuses"][selected_agent_id] = "failed"
|
||||
|
||||
# Füge die Fehlermeldung zum Chatverlauf hinzu
|
||||
# Add error message to chat history
|
||||
chat_history.append({
|
||||
"role": "assistant",
|
||||
"content": f"[Fehler bei Agent '{selected_agent['name']}']: {str(e)}"
|
||||
"content": f"[Error with agent '{selected_agent['name']}']: {str(e)}"
|
||||
})
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler in der Moderator-Phase: {str(e)}")
|
||||
self._add_log(workflow_id, f"Fehler in der Moderator-Phase: {str(e)}", "error")
|
||||
logger.error(f"Error in moderator phase: {str(e)}")
|
||||
self._add_log(workflow_id, f"Error in moderator phase: {str(e)}", "error")
|
||||
|
||||
# Aktualisiere den Chat-Verlauf im Workflow
|
||||
# Update chat history in workflow
|
||||
self.workflows[workflow_id]["chat_history"] = chat_history
|
||||
|
||||
return workflow_complete, waiting_for_user_input, selected_agent_id
|
||||
|
||||
|
||||
def _create_structured_moderator_prompt(self, available_agents, is_user_input_continuation):
|
||||
"""
|
||||
Creates an improved moderator prompt that requests a structured format for agent selection
|
||||
and enforces proper user agent interaction
|
||||
"""
|
||||
# Check if User Agent has been used and confirmed
|
||||
user_agent_used = False
|
||||
user_agent_confirmed = False
|
||||
|
||||
if "user_agent" in available_agents:
|
||||
user_agent_used = available_agents["user_agent"].get("used", False)
|
||||
if user_agent_used:
|
||||
for chat_entry in available_agents["user_agent"].get("chat_entries", []):
|
||||
if any(confirmation in chat_entry.lower() for confirmation in
|
||||
["ja", "yes", "bestätige", "confirm", "stimme zu", "agree", "akzeptiere", "accept"]):
|
||||
user_agent_confirmed = True
|
||||
break
|
||||
|
||||
base = """You are a moderator of a multi-agent system. Your task is to coordinate the agents
|
||||
to fully address the request and deliver a concrete final result.
|
||||
First step is always to ask an agento to coordinate the necessary steps.
|
||||
|
||||
IMPORTANT: The workflow should only end when ACTUAL RESULTS have been delivered"""
|
||||
|
||||
# Different conditions for ending the workflow
|
||||
if not user_agent_confirmed:
|
||||
base += """ AND the User Agent has explicitly confirmed with a 'YES' that they are satisfied with the result.
|
||||
CRITICALLY IMPORTANT: Before ending the workflow, you MUST ask the User Agent whether they are satisfied with the results,
|
||||
and they must EXPLICITLY respond with 'YES' or a clear confirmation!"""
|
||||
|
||||
# Add structured format requirement
|
||||
base += """
|
||||
|
||||
RESPONSE FORMAT: You must respond with a clear agent selection in the following format:
|
||||
{
|
||||
"next_agent": "agent_id", // The ID of the selected agent (e.g., "user_agent", "analyzer", "writer")
|
||||
"task": "description" // Clear description of the task for the agent
|
||||
}
|
||||
|
||||
DO NOT use any other format for agent selection.
|
||||
"""
|
||||
|
||||
# Add specific instruction for user agent interaction
|
||||
base += """
|
||||
IMPORTANT USER AGENT HANDLING:
|
||||
- When selecting the User Agent, formulate a CLEAR QUESTION that requires their input.
|
||||
- The User Agent MUST be able to provide a response before the workflow continues.
|
||||
- Never reference a User Agent question without actually selecting the User Agent.
|
||||
"""
|
||||
|
||||
# Add agent listing and instructions
|
||||
agents_list = "\nAvailable agents:\n"
|
||||
for agent_id, agent in available_agents.items():
|
||||
status = "✓ Already used" if agent["used"] else "✗ Not used yet"
|
||||
result_status = ""
|
||||
if agent["used"] and agent.get("last_result_status"):
|
||||
result_status = f" (Last response: {agent.get('last_result_status')})"
|
||||
|
||||
description = agent.get("description", "")
|
||||
capabilities = agent.get("capabilities", "")
|
||||
agents_list += f"- {agent['name']} (ID: {agent_id}, Type: {agent['type']}): {capabilities}\n {description}\n Status: {status}{result_status}\n"
|
||||
|
||||
# Add decision guidelines
|
||||
instructions = """
|
||||
Consider the STATUS declarations of agents when making your decision:
|
||||
|
||||
- [STATUS: ERGEBNIS] - The agent has delivered a complete result
|
||||
- [STATUS: TEILWEISE] - The agent has delivered a partial result, more work is needed
|
||||
- [STATUS: PLAN] - The agent has delivered a plan, no concrete results yet
|
||||
|
||||
Possible decisions:
|
||||
- Select an agent: Provide a structured response with next_agent and task
|
||||
"""
|
||||
|
||||
if not user_agent_confirmed:
|
||||
instructions += """- For completion (only if [STATUS: ERGEBNIS] exists): You MUST FIRST ask the User Agent explicitly if they are satisfied with the results.
|
||||
The User Agent MUST respond with "YES" or clear confirmation before the workflow can end!
|
||||
|
||||
IMPORTANT: You MUST NOT end the workflow before the User Agent has explicitly confirmed with "YES"!
|
||||
Ask the User Agent a CLEAR, DIRECT question whether they are satisfied with the result or need more information.
|
||||
"""
|
||||
else:
|
||||
instructions += """- For completion (only if [STATUS: ERGEBNIS] exists and User Agent has confirmed with "YES"): Select "WORKFLOW_COMPLETE" as next_agent to end the workflow.
|
||||
|
||||
IMPORTANT: Since the User Agent has already given their approval, you can now end the workflow if an agent has delivered a concrete [STATUS: ERGEBNIS]!
|
||||
"""
|
||||
|
||||
if is_user_input_continuation:
|
||||
base += "\nIMPORTANT: The User Agent has just responded. Consider this response in your decision."
|
||||
|
||||
return base + agents_list + instructions
|
||||
|
||||
|
||||
def _parse_structured_agent_selection(self, moderator_text, available_agents):
|
||||
"""
|
||||
Attempts to parse a structured agent selection from the moderator's text.
|
||||
Falls back to keyword detection if structured format is not found.
|
||||
"""
|
||||
# First try to find a JSON structure
|
||||
import re
|
||||
import json
|
||||
|
||||
# Look for JSON structure in the text
|
||||
json_pattern = r'\{[\s\S]*?"next_agent"[\s\S]*?\}'
|
||||
json_match = re.search(json_pattern, moderator_text)
|
||||
|
||||
if json_match:
|
||||
try:
|
||||
selection = json.loads(json_match.group(0))
|
||||
next_agent = selection.get("next_agent")
|
||||
|
||||
# Handle workflow completion
|
||||
if next_agent.lower() in ["workflow_complete", "complete", "end"]:
|
||||
return "WORKFLOW_COMPLETE"
|
||||
|
||||
# Check if the selected agent exists
|
||||
if next_agent in available_agents:
|
||||
logger.info(f"Successfully parsed structured agent selection: {next_agent}")
|
||||
return next_agent
|
||||
|
||||
# Handle user agent selection explicitly
|
||||
if next_agent.lower() in ["user", "user_agent", "human"]:
|
||||
return "user_agent"
|
||||
|
||||
except json.JSONDecodeError:
|
||||
logger.warning("Failed to parse JSON from moderator text")
|
||||
|
||||
# Fallback to the traditional logic with improved reliability
|
||||
text = moderator_text.lower()
|
||||
|
||||
# Check for workflow completion phrases
|
||||
workflow_complete_phrases = [
|
||||
"workflow beenden - vollständiges ergebnis erreicht",
|
||||
"workflow beenden - vollständiges ergebnis",
|
||||
"vollständiges ergebnis erreicht",
|
||||
"workflow complete",
|
||||
"end workflow",
|
||||
"complete workflow"
|
||||
]
|
||||
|
||||
# Check if the workflow should be completed
|
||||
result_exists = False
|
||||
for agent_id, agent in available_agents.items():
|
||||
if agent.get("used") and agent.get("last_result_status") == "ERGEBNIS":
|
||||
result_exists = True
|
||||
break
|
||||
|
||||
# Check if User Agent has confirmed
|
||||
user_agent_confirmed = False
|
||||
if "user_agent" in available_agents and available_agents["user_agent"].get("used", False):
|
||||
for chat_entry in available_agents["user_agent"].get("chat_entries", []):
|
||||
if any(confirmation in chat_entry.lower() for confirmation in
|
||||
["ja", "yes", "bestätige", "confirm", "stimme zu", "agree"]):
|
||||
user_agent_confirmed = True
|
||||
break
|
||||
|
||||
# Check for explicit workflow completion
|
||||
if any(phrase in text for phrase in workflow_complete_phrases):
|
||||
if result_exists and user_agent_confirmed:
|
||||
return "WORKFLOW_COMPLETE"
|
||||
elif not result_exists:
|
||||
logger.warning("Moderator attempted to end workflow without complete results")
|
||||
elif not user_agent_confirmed:
|
||||
logger.warning("Moderator attempted to end workflow without User Agent confirmation")
|
||||
if "user_agent" in available_agents:
|
||||
return "user_agent" # Force User Agent selection to get confirmation
|
||||
|
||||
# Check for explicit agent selection
|
||||
if "ich wähle" in text or "i choose" in text or "i select" in text:
|
||||
for agent_id, agent in available_agents.items():
|
||||
agent_name_lower = agent["name"].lower()
|
||||
if agent_name_lower in text:
|
||||
return agent_id
|
||||
|
||||
# Check for User Agent queries
|
||||
user_agent_phrases = [
|
||||
"user agent",
|
||||
"benutzer",
|
||||
"user",
|
||||
"human",
|
||||
"ask the user",
|
||||
"frage den benutzer"
|
||||
]
|
||||
|
||||
# If text contains User Agent phrases and a question, prioritize User Agent
|
||||
has_question = "?" in text
|
||||
has_user_phrase = any(phrase in text for phrase in user_agent_phrases)
|
||||
|
||||
if has_question and has_user_phrase:
|
||||
if "user_agent" in available_agents:
|
||||
return "user_agent"
|
||||
|
||||
# Direct name matching as last resort
|
||||
for agent_id, agent in available_agents.items():
|
||||
agent_name_lower = agent["name"].lower()
|
||||
if agent_name_lower in text:
|
||||
return agent_id
|
||||
|
||||
# If a complete result exists but User Agent hasn't confirmed, prioritize User Agent
|
||||
if result_exists and not user_agent_confirmed and "user_agent" in available_agents:
|
||||
return "user_agent"
|
||||
|
||||
# If no agent explicitly selected, choose first unused agent
|
||||
for agent_id, agent in available_agents.items():
|
||||
if not agent["used"]:
|
||||
return agent_id
|
||||
|
||||
# Last resort: reuse first agent
|
||||
if available_agents:
|
||||
return list(available_agents.keys())[0]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _add_log(
|
||||
self,
|
||||
workflow_id: str,
|
||||
|
|
@ -602,49 +815,47 @@ class AgentService:
|
|||
|
||||
async def process_user_input(self, workflow_id: str, message: str, additional_files: List[Dict[str, Any]] = None) -> bool:
|
||||
"""
|
||||
Verarbeitet eine Benutzereingabe für einen laufenden Workflow.
|
||||
Ergänzt um die Verfolgung von User Agent Bestätigungen.
|
||||
Enhanced function to process user input for a running workflow.
|
||||
Ensures user responses are properly tracked and prevents duplicate messages.
|
||||
|
||||
Args:
|
||||
workflow_id: ID des Workflows
|
||||
message: Nachricht des Benutzers
|
||||
additional_files: Liste zusätzlicher Dateien (optional)
|
||||
workflow_id: ID of the workflow
|
||||
message: User message
|
||||
additional_files: List of additional files (optional)
|
||||
|
||||
Returns:
|
||||
bool: True, wenn die Eingabe erfolgreich verarbeitet wurde
|
||||
bool: True if input was successfully processed
|
||||
"""
|
||||
if workflow_id not in self.workflows:
|
||||
logger.warning(f"Workflow {workflow_id} nicht gefunden")
|
||||
logger.warning(f"Workflow {workflow_id} not found")
|
||||
return False
|
||||
|
||||
# Prüfen, ob der Workflow auf eine Benutzereingabe wartet
|
||||
# Check if the workflow is waiting for user input
|
||||
if not self.workflows[workflow_id].get("waiting_for_user", False):
|
||||
logger.warning(f"Workflow {workflow_id} wartet nicht auf Benutzereingabe")
|
||||
logger.warning(f"Workflow {workflow_id} is not waiting for user input")
|
||||
return False
|
||||
|
||||
logger.info(f"Verarbeite Benutzereingabe für Workflow {workflow_id}")
|
||||
logger.info(f"Processing user input for workflow {workflow_id}")
|
||||
|
||||
# Benutzerinfos abrufen
|
||||
# Get user information
|
||||
user_info = await self._get_user_info(self.user_id)
|
||||
user_name = user_info.get("full_name") or user_info.get("username") or f"Benutzer {self.user_id}"
|
||||
user_name = user_info.get("full_name") or user_info.get("username") or f"User {self.user_id}"
|
||||
|
||||
# Log-Eintrag für die Benutzereingabe
|
||||
# Log entry for user input
|
||||
self._add_log(
|
||||
workflow_id,
|
||||
f"Benutzereingabe empfangen: {message[:50]}{'...' if len(message) > 50 else ''}",
|
||||
f"User input received: {message[:50]}{'...' if len(message) > 50 else ''}",
|
||||
"info",
|
||||
"user_agent",
|
||||
user_name
|
||||
)
|
||||
|
||||
# Verfolge die User Agent Antwort für Bestätigungsprüfung
|
||||
# Importiere die Hilfsfunktion aus dem agents-Modul
|
||||
# Track user agent response in the agents module
|
||||
from modules.agentservice_part_agents import track_user_agent_response
|
||||
available_agents = self.workflows[workflow_id].get("available_agents", {})
|
||||
track_user_agent_response(available_agents, message)
|
||||
|
||||
# Rest der Funktion bleibt unverändert...
|
||||
# Wenn zusätzliche Dateien vorhanden sind, diese verarbeiten und in den Workflow integrieren
|
||||
# Process additional files if provided
|
||||
additional_context = ""
|
||||
if additional_files and len(additional_files) > 0:
|
||||
file_contexts = file_handling.prepare_file_contexts(additional_files, self.upload_dir)
|
||||
|
|
@ -652,51 +863,78 @@ class AgentService:
|
|||
file_contexts,
|
||||
self.upload_dir,
|
||||
workflow_id,
|
||||
lambda wid, msg, typ, aid=None, aname=None: self._add_log(wid, msg, typ, aid, aname)
|
||||
lambda wid, msg, typ, aid=None, aname=None: self._add_log(wid, msg, typ, aid, aname),
|
||||
self.service_aichat # Pass AI service for image analysis
|
||||
)
|
||||
|
||||
# Formatiere den Dateikontext
|
||||
# Format file context for inclusion
|
||||
file_context_text = file_handling.format_file_context_text(file_contexts, file_contents)
|
||||
additional_context = f"\n\n### Zusätzliche Dateien:\n{file_context_text}"
|
||||
additional_context = f"\n\n### Additional Files:\n{file_context_text}"
|
||||
|
||||
# Log-Eintrag für die zusätzlichen Dateien
|
||||
# Log entry for additional files
|
||||
self._add_log(
|
||||
workflow_id,
|
||||
f"{len(additional_files)} zusätzliche Dateien hinzugefügt",
|
||||
f"{len(additional_files)} additional files added",
|
||||
"info",
|
||||
"user_agent",
|
||||
user_name
|
||||
)
|
||||
|
||||
# Füge neue Dateien zu den bestehenden Dateikontexten und -inhalten hinzu
|
||||
# Add new files to existing file contexts and contents
|
||||
existing_file_contexts = self.workflows[workflow_id].get("file_contexts", [])
|
||||
existing_file_contents = self.workflows[workflow_id].get("file_contents", {})
|
||||
|
||||
# Alte IDs speichern, um Duplikate zu vermeiden
|
||||
# Store existing IDs to avoid duplicates
|
||||
existing_ids = {fc["id"] for fc in existing_file_contexts}
|
||||
|
||||
# Neue Dateikontexte hinzufügen (nur wenn ID noch nicht existiert)
|
||||
# Add new file contexts (only if ID doesn't already exist)
|
||||
for file_context in file_contexts:
|
||||
if file_context["id"] not in existing_ids:
|
||||
existing_file_contexts.append(file_context)
|
||||
existing_ids.add(file_context["id"])
|
||||
|
||||
# Neue Dateiinhalte hinzufügen
|
||||
# Add new file contents
|
||||
existing_file_contents.update(file_contents)
|
||||
|
||||
# Aktualisierte Dateikontexte und -inhalte im Workflow speichern
|
||||
# Update file contexts and contents in workflow
|
||||
self.workflows[workflow_id]["file_contexts"] = existing_file_contexts
|
||||
self.workflows[workflow_id]["file_contents"] = existing_file_contents
|
||||
|
||||
# Kombinierte Nachricht erstellen (Benutzernachricht + ggf. zusätzliche Dateien)
|
||||
# Combined message (user message + additional files)
|
||||
combined_message = message + additional_context
|
||||
|
||||
# Schätzung der gesendeten Bytes
|
||||
# Check for duplicate user messages in chat history
|
||||
# This prevents the user's message from appearing twice
|
||||
chat_history = self.workflows[workflow_id].get("chat_history", [])
|
||||
last_message = chat_history[-1] if chat_history else None
|
||||
|
||||
# Only add user message if the last message wasn't already from the user with the same content
|
||||
is_duplicate = False
|
||||
if last_message and last_message.get("role") == "user" and last_message.get("content", "").startswith(f"[User Agent: {user_name}]"):
|
||||
# Check for content similarity to avoid duplicates
|
||||
last_content = last_message.get("content", "").replace(f"[User Agent: {user_name}] ", "", 1)
|
||||
if last_content == message:
|
||||
is_duplicate = True
|
||||
logger.warning(f"Detected duplicate user message, skipping addition to chat history")
|
||||
|
||||
# Add user message to chat history if not duplicate
|
||||
if not is_duplicate:
|
||||
chat_history.append({
|
||||
"role": "user",
|
||||
"content": f"[User Agent: {user_name}] {message}"
|
||||
})
|
||||
|
||||
# Update chat history in workflow
|
||||
self.workflows[workflow_id]["chat_history"] = chat_history
|
||||
|
||||
# Estimate sent bytes
|
||||
message_size = len(combined_message)
|
||||
self.workflows[workflow_id]["data_stats"]["sent_bytes"] += message_size
|
||||
|
||||
# Fortsetzung des Workflows einleiten
|
||||
# Wir starten eine neue Aufgabe, um den Workflow fortzusetzen
|
||||
# Save the current state before continuing
|
||||
results.save_workflow_results(self.workflows, workflow_id, self.results_dir)
|
||||
|
||||
# Start a new task to continue the workflow
|
||||
asyncio.create_task(
|
||||
self._continue_workflow_after_user_input(
|
||||
workflow_id,
|
||||
|
|
@ -708,52 +946,44 @@ class AgentService:
|
|||
return True
|
||||
|
||||
|
||||
|
||||
async def _continue_workflow_after_user_input(self, workflow_id: str, user_message: str, user_name: str) -> None:
|
||||
"""
|
||||
Setzt einen Workflow nach einer Benutzereingabe fort.
|
||||
Nutzt die gemeinsame _run_moderator_cycle Methode für die Fortsetzung.
|
||||
Enhanced function to continue a workflow after user input.
|
||||
Ensures proper state tracking and prevents duplicate processing.
|
||||
|
||||
Args:
|
||||
workflow_id: ID des Workflows
|
||||
user_message: Nachricht des Benutzers
|
||||
user_name: Name des Benutzers
|
||||
workflow_id: ID of the workflow
|
||||
user_message: User message
|
||||
user_name: User name
|
||||
"""
|
||||
if workflow_id not in self.workflows:
|
||||
logger.warning(f"Workflow {workflow_id} nicht gefunden")
|
||||
logger.warning(f"Workflow {workflow_id} not found")
|
||||
return
|
||||
|
||||
# Workflow-Status aktualisieren
|
||||
# Update workflow status
|
||||
self.workflows[workflow_id]["status"] = "running"
|
||||
|
||||
# Log-Eintrag für die Fortsetzung
|
||||
# Log entry for continuation
|
||||
self._add_log(
|
||||
workflow_id,
|
||||
"Workflow wird nach Benutzereingabe fortgesetzt",
|
||||
"Workflow continues after user input",
|
||||
"info"
|
||||
)
|
||||
|
||||
# Hole die benötigten Daten aus dem Workflow
|
||||
# Get required data from workflow
|
||||
chat_history = self.workflows[workflow_id].get("chat_history", [])
|
||||
available_agents = self.workflows[workflow_id].get("available_agents", {})
|
||||
file_contexts = self.workflows[workflow_id].get("file_contexts", [])
|
||||
file_contents = self.workflows[workflow_id].get("file_contents", {})
|
||||
|
||||
# Benutzereingabe zum Chatverlauf hinzufügen
|
||||
chat_history.append({
|
||||
"role": "user",
|
||||
"content": f"[User Agent: {user_name}] {user_message}"
|
||||
})
|
||||
|
||||
# Speichere den aktualisierten Chat-Verlauf im Workflow
|
||||
self.workflows[workflow_id]["chat_history"] = chat_history
|
||||
|
||||
# User-Antwort als Ergebnis speichern
|
||||
# Create user result (store agent result in the workflow)
|
||||
user_result = results.create_agent_result(
|
||||
workflow_id,
|
||||
{"id": "user_agent", "name": "User Agent", "type": "user"},
|
||||
len(self.workflows[workflow_id].get("results", [])),
|
||||
"Benutzereingabe",
|
||||
file_contexts, # Dateikontexte übergeben
|
||||
"User input",
|
||||
file_contexts,
|
||||
user_message,
|
||||
self.mandate_id,
|
||||
self.user_id
|
||||
|
|
@ -761,69 +991,148 @@ class AgentService:
|
|||
|
||||
self.workflows[workflow_id]["results"].append(user_result)
|
||||
|
||||
# Markiere den User-Agent als verwendet
|
||||
# Mark User Agent as used
|
||||
if "user_agent" in available_agents:
|
||||
available_agents["user_agent"]["used"] = True
|
||||
|
||||
# Markiere, dass wir nicht mehr auf eine Benutzereingabe warten
|
||||
|
||||
# Mark that we're no longer waiting for user input - CRITICAL
|
||||
self.workflows[workflow_id]["waiting_for_user"] = False
|
||||
|
||||
# Hole die aktuelle Rundenzahl und maximale Rundenzahl
|
||||
# Get current round and max rounds
|
||||
current_round = self.workflows[workflow_id].get("current_round", 0)
|
||||
max_rounds = 12 # Gleicher Wert wie in execute_workflow
|
||||
max_rounds = 12 # Same value as in execute_workflow
|
||||
|
||||
# Prüfe, ob wir schon die maximale Anzahl von Runden erreicht haben
|
||||
# Check if we've reached the maximum number of rounds
|
||||
if current_round >= max_rounds:
|
||||
self._add_log(workflow_id, f"Workflow nach {max_rounds} Runden und Benutzereingabe automatisch beendet", "info")
|
||||
self._add_log(workflow_id, f"Workflow automatically ended after {max_rounds} rounds and user input", "info")
|
||||
self.workflows[workflow_id]["status"] = "completed"
|
||||
self.workflows[workflow_id]["progress"] = 1.0
|
||||
self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat()
|
||||
|
||||
# Speichere Ergebnisse
|
||||
# Save results
|
||||
results.save_workflow_results(self.workflows, workflow_id, self.results_dir)
|
||||
return
|
||||
|
||||
# Nächste Runde starten
|
||||
# Start next round
|
||||
current_round += 1
|
||||
self.workflows[workflow_id]["current_round"] = current_round
|
||||
self._add_log(workflow_id, f"Starte Runde {current_round} nach Benutzereingabe", "info")
|
||||
self._add_log(workflow_id, f"Starting round {current_round} after user input", "info")
|
||||
|
||||
# Führe einen Moderator-Zyklus durch
|
||||
# Run a moderator cycle
|
||||
workflow_complete, waiting_for_user_input, selected_agent_id = await self._run_moderator_cycle(
|
||||
workflow_id,
|
||||
chat_history,
|
||||
available_agents,
|
||||
file_contexts,
|
||||
file_contents,
|
||||
is_user_input_continuation=True, # Dies ist eine Fortsetzung nach Benutzereingabe
|
||||
is_user_input_continuation=True,
|
||||
user_message=user_message
|
||||
)
|
||||
|
||||
# Workflow abschließen, wenn er vollständig ist oder nicht auf Benutzereingabe wartet
|
||||
if workflow_complete or not waiting_for_user_input:
|
||||
# Prüfe, ob wir weitere Runden durchführen sollten
|
||||
if not workflow_complete and not waiting_for_user_input and current_round < max_rounds:
|
||||
# Starte eine neue Aufgabe für die nächste Moderator-Runde
|
||||
asyncio.create_task(
|
||||
self._continue_workflow_after_user_input(
|
||||
workflow_id,
|
||||
"", # Leere Nachricht für die nächste Runde
|
||||
user_name
|
||||
)
|
||||
# Update workflow state based on moderator cycle result
|
||||
if workflow_complete:
|
||||
self.workflows[workflow_id]["status"] = "completed"
|
||||
self._add_log(workflow_id, "Workflow successfully completed after user input", "success")
|
||||
self.workflows[workflow_id]["progress"] = 1.0
|
||||
self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat()
|
||||
elif waiting_for_user_input:
|
||||
# User Agent was selected again, we're waiting for more input
|
||||
pass
|
||||
elif current_round < max_rounds:
|
||||
# Continue with more agent rounds if not waiting for input
|
||||
asyncio.create_task(
|
||||
self._continue_workflow_after_moderator_cycle(
|
||||
workflow_id,
|
||||
selected_agent_id
|
||||
)
|
||||
else:
|
||||
# Workflow wurde entweder abgeschlossen oder wartet auf Benutzereingabe
|
||||
if workflow_complete:
|
||||
self.workflows[workflow_id]["status"] = "completed"
|
||||
self._add_log(workflow_id, "Workflow nach Benutzereingabe erfolgreich beendet", "success")
|
||||
self.workflows[workflow_id]["progress"] = 1.0
|
||||
self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat()
|
||||
)
|
||||
|
||||
# Fortschritt aktualisieren
|
||||
# Update progress
|
||||
if not self.workflows[workflow_id]["completed_at"]:
|
||||
self.workflows[workflow_id]["progress"] = min(0.9, 0.1 + (current_round / max_rounds) * 0.8)
|
||||
|
||||
# Speichere Ergebnisse
|
||||
# Save results
|
||||
results.save_workflow_results(self.workflows, workflow_id, self.results_dir)
|
||||
|
||||
|
||||
async def _continue_workflow_after_moderator_cycle(self, workflow_id: str, selected_agent_id: str) -> None:
|
||||
"""
|
||||
New method to continue workflow after a moderator cycle when not waiting for user input.
|
||||
This facilitates smoother agent transitions and addresses the issue of the workflow not continuing properly.
|
||||
|
||||
Args:
|
||||
workflow_id: ID of the workflow
|
||||
selected_agent_id: ID of the selected agent
|
||||
"""
|
||||
if workflow_id not in self.workflows:
|
||||
logger.warning(f"Workflow {workflow_id} not found")
|
||||
return
|
||||
|
||||
# Get required data from workflow
|
||||
chat_history = self.workflows[workflow_id].get("chat_history", [])
|
||||
available_agents = self.workflows[workflow_id].get("available_agents", {})
|
||||
file_contexts = self.workflows[workflow_id].get("file_contexts", [])
|
||||
file_contents = self.workflows[workflow_id].get("file_contents", {})
|
||||
|
||||
# Get current round and max rounds
|
||||
current_round = self.workflows[workflow_id].get("current_round", 0)
|
||||
max_rounds = 12 # Same value as in execute_workflow
|
||||
|
||||
# Check if we've reached the maximum number of rounds
|
||||
if current_round >= max_rounds:
|
||||
self._add_log(workflow_id, f"Workflow automatically ended after {max_rounds} rounds", "info")
|
||||
self.workflows[workflow_id]["status"] = "completed"
|
||||
self.workflows[workflow_id]["progress"] = 1.0
|
||||
self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat()
|
||||
|
||||
# Save results
|
||||
results.save_workflow_results(self.workflows, workflow_id, self.results_dir)
|
||||
return
|
||||
|
||||
# Start next round
|
||||
current_round += 1
|
||||
self.workflows[workflow_id]["current_round"] = current_round
|
||||
self._add_log(workflow_id, f"Starting round {current_round} (continuation)", "info")
|
||||
|
||||
# Run another moderator cycle
|
||||
workflow_complete, waiting_for_user_input, new_selected_agent_id = await self._run_moderator_cycle(
|
||||
workflow_id,
|
||||
chat_history,
|
||||
available_agents,
|
||||
file_contexts,
|
||||
file_contents,
|
||||
is_user_input_continuation=False
|
||||
)
|
||||
|
||||
# Update workflow state based on moderator cycle result
|
||||
if workflow_complete:
|
||||
self.workflows[workflow_id]["status"] = "completed"
|
||||
self._add_log(workflow_id, "Workflow successfully completed", "success")
|
||||
self.workflows[workflow_id]["progress"] = 1.0
|
||||
self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat()
|
||||
elif waiting_for_user_input:
|
||||
# User Agent was selected, we're waiting for input
|
||||
pass
|
||||
elif current_round < max_rounds:
|
||||
# Continue with more agent rounds if not waiting for input and not at max rounds
|
||||
asyncio.create_task(
|
||||
self._continue_workflow_after_moderator_cycle(
|
||||
workflow_id,
|
||||
new_selected_agent_id
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Max rounds reached
|
||||
self.workflows[workflow_id]["status"] = "completed"
|
||||
self._add_log(workflow_id, f"Workflow completed after reaching maximum rounds ({max_rounds})", "info")
|
||||
self.workflows[workflow_id]["progress"] = 1.0
|
||||
self.workflows[workflow_id]["completed_at"] = datetime.now().isoformat()
|
||||
|
||||
# Update progress
|
||||
if not self.workflows[workflow_id]["completed_at"]:
|
||||
self.workflows[workflow_id]["progress"] = min(0.9, 0.1 + (current_round / max_rounds) * 0.8)
|
||||
|
||||
# Save results
|
||||
results.save_workflow_results(self.workflows, workflow_id, self.results_dir)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -6,111 +6,200 @@ from typing import Dict, Any, List, Optional, Tuple
|
|||
# Logger konfigurieren
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def read_file_contents(
|
||||
async def read_file_contents(
|
||||
file_contexts: List[Dict[str, Any]],
|
||||
upload_dir: str,
|
||||
workflow_id: str = None,
|
||||
add_log_func = None
|
||||
add_log_func = None,
|
||||
ai_service = None # Added AI service parameter for image analysis
|
||||
) -> Dict[str, str]:
|
||||
"""
|
||||
Liest die Inhalte aller Dateien und bereitet sie für die Verwendung vor.
|
||||
Enhanced function to read the contents of all files with proper image and document analysis.
|
||||
|
||||
Args:
|
||||
file_contexts: Liste der Dateikontexte mit Metadaten
|
||||
upload_dir: Verzeichnis für Uploads
|
||||
workflow_id: Optional ID des Workflows für Logging
|
||||
add_log_func: Optionale Funktion zum Hinzufügen von Logs
|
||||
file_contexts: List of file contexts with metadata
|
||||
upload_dir: Directory for uploads
|
||||
workflow_id: Optional ID of the workflow for logging
|
||||
add_log_func: Optional function for adding logs
|
||||
ai_service: Optional AI service for image analysis
|
||||
|
||||
Returns:
|
||||
Dictionary mit Dateiinhalten (file_id -> Inhalt)
|
||||
Dictionary with file contents (file_id -> content)
|
||||
"""
|
||||
file_contents = {}
|
||||
|
||||
for file in file_contexts:
|
||||
file_id = file["id"]
|
||||
file_name = file["name"]
|
||||
file_type = file["type"]
|
||||
file_type = file.get("type", "unknown")
|
||||
file_path = file.get("path", "")
|
||||
|
||||
# Wenn Pfad nicht gesetzt, versuche ihn aus dem Upload-Verzeichnis abzuleiten
|
||||
# If path is not set, try to derive it from the upload directory
|
||||
if not file_path and file_name:
|
||||
possible_path = os.path.join(upload_dir, file_name)
|
||||
if os.path.exists(possible_path):
|
||||
file_path = possible_path
|
||||
logger.debug(f"Pfad für Datei {file_name} gefunden: {file_path}")
|
||||
file["path"] = file_path # Update the path in context
|
||||
logger.debug(f"Found path for file {file_name}: {file_path}")
|
||||
|
||||
# Dateiinhalt lesen, wenn der Pfad verfügbar ist
|
||||
# Read file content if path is available
|
||||
if file_path and os.path.exists(file_path):
|
||||
try:
|
||||
# Text-basierte Dateien direkt lesen
|
||||
if file_type == "document":
|
||||
# Einfache Textdateien
|
||||
if file_name.endswith(('.txt', '.md', '.json')):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
file_contents[file_id] = f.read()
|
||||
_log(add_log_func, workflow_id, f"Datei {file_name} gelesen", "info")
|
||||
# Image files - always perform image analysis if AI service is available
|
||||
if file_type == "image" or file_name.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')):
|
||||
if ai_service:
|
||||
try:
|
||||
_log(add_log_func, workflow_id, f"Analyzing image {file_name}...", "info")
|
||||
image_analysis = await ai_service.analyze_image(file_path, "Describe this image in detail")
|
||||
file_contents[file_id] = f"Image Analysis:\n{image_analysis}"
|
||||
_log(add_log_func, workflow_id, f"Image {file_name} analyzed successfully", "info")
|
||||
except Exception as e:
|
||||
logger.error(f"Error analyzing image {file_name}: {str(e)}")
|
||||
_log(add_log_func, workflow_id, f"Error analyzing image {file_name}: {str(e)}", "error")
|
||||
file_contents[file_id] = f"Image file: {file_name} (Analysis failed: {str(e)})"
|
||||
else:
|
||||
file_contents[file_id] = f"Image file: {file_name} (AI analysis not available)"
|
||||
|
||||
# Document files
|
||||
elif file_type == "document" or not file_type:
|
||||
# Simple text files
|
||||
if file_name.endswith(('.txt', '.md', '.json', '.xml', '.html', '.htm', '.css', '.js')):
|
||||
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
content = f.read()
|
||||
file_contents[file_id] = content
|
||||
_log(add_log_func, workflow_id, f"Text file {file_name} read successfully", "info")
|
||||
|
||||
# Excel-Dateien
|
||||
# Excel files
|
||||
elif file_name.endswith(('.xlsx', '.xls')):
|
||||
try:
|
||||
df = pd.read_excel(file_path)
|
||||
file_contents[file_id] = f"Excel-Datei mit {len(df)} Zeilen und {len(df.columns)} Spalten.\n"
|
||||
file_contents[file_id] += f"Spalten: {', '.join(df.columns.tolist())}\n"
|
||||
file_contents[file_id] += df.to_string() # Vollständige Tabelle
|
||||
_log(add_log_func, workflow_id, f"Excel-Datei {file_name} gelesen", "info")
|
||||
file_contents[file_id] = f"Excel file with {len(df)} rows and {len(df.columns)} columns.\n"
|
||||
file_contents[file_id] += f"Columns: {', '.join(df.columns.tolist())}\n\n"
|
||||
file_contents[file_id] += df.to_string(index=False) # Full table
|
||||
_log(add_log_func, workflow_id, f"Excel file {file_name} read successfully", "info")
|
||||
except Exception as e:
|
||||
_log(add_log_func, workflow_id, f"Fehler beim Lesen der Excel-Datei {file_name}: {str(e)}", "error")
|
||||
logger.error(f"Error reading Excel file {file_name}: {str(e)}")
|
||||
_log(add_log_func, workflow_id, f"Error reading Excel file {file_name}: {str(e)}", "error")
|
||||
file_contents[file_id] = f"Excel file: {file_name} (Reading failed: {str(e)})"
|
||||
|
||||
# CSV-Dateien
|
||||
# CSV files
|
||||
elif file_name.endswith('.csv'):
|
||||
try:
|
||||
df = pd.read_csv(file_path)
|
||||
file_contents[file_id] = f"CSV-Datei mit {len(df)} Zeilen und {len(df.columns)} Spalten.\n"
|
||||
file_contents[file_id] += f"Spalten: {', '.join(df.columns.tolist())}\n"
|
||||
file_contents[file_id] += df.to_string() # Vollständige Tabelle
|
||||
_log(add_log_func, workflow_id, f"CSV-Datei {file_name} gelesen", "info")
|
||||
# Try various encodings and delimiters for robust CSV parsing
|
||||
try:
|
||||
df = pd.read_csv(file_path, encoding='utf-8')
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
df = pd.read_csv(file_path, encoding='latin1')
|
||||
except:
|
||||
df = pd.read_csv(file_path, encoding='cp1252')
|
||||
|
||||
file_contents[file_id] = f"CSV file with {len(df)} rows and {len(df.columns)} columns.\n"
|
||||
file_contents[file_id] += f"Columns: {', '.join(df.columns.tolist())}\n\n"
|
||||
file_contents[file_id] += df.to_string(index=False) # Full table
|
||||
_log(add_log_func, workflow_id, f"CSV file {file_name} read successfully", "info")
|
||||
except Exception as e:
|
||||
_log(add_log_func, workflow_id, f"Fehler beim Lesen der CSV-Datei {file_name}: {str(e)}", "error")
|
||||
logger.error(f"Error reading CSV file {file_name}: {str(e)}")
|
||||
_log(add_log_func, workflow_id, f"Error reading CSV file {file_name}: {str(e)}", "error")
|
||||
file_contents[file_id] = f"CSV file: {file_name} (Reading failed: {str(e)})"
|
||||
|
||||
# PDF-Dateien
|
||||
# PDF files - with enhanced extraction and AI analysis
|
||||
elif file_name.endswith('.pdf'):
|
||||
try:
|
||||
# Falls PyPDF2 installiert ist
|
||||
# Try PyPDF2 first
|
||||
try:
|
||||
from PyPDF2 import PdfReader
|
||||
reader = PdfReader(file_path)
|
||||
num_pages = len(reader.pages)
|
||||
text = ""
|
||||
for page in reader.pages:
|
||||
text += page.extract_text() + "\n\n"
|
||||
file_contents[file_id] = f"PDF mit {len(reader.pages)} Seiten.\nInhalt:\n{text}"
|
||||
_log(add_log_func, workflow_id, f"PDF-Datei {file_name} gelesen", "info")
|
||||
|
||||
# If AI service is available, also analyze images in PDF
|
||||
if ai_service:
|
||||
_log(add_log_func, workflow_id, f"Analyzing PDF images in {file_name}...", "info")
|
||||
try:
|
||||
image_analysis_results = await ai_service.extract_and_analyze_pdf_images(
|
||||
file_path,
|
||||
"Describe this image in the context of the document"
|
||||
)
|
||||
|
||||
if image_analysis_results:
|
||||
image_analysis_text = "\n\n=== PDF IMAGE ANALYSIS ===\n"
|
||||
for result in image_analysis_results:
|
||||
image_analysis_text += f"\nImage on page {result['page']}: {result['response']}\n"
|
||||
text += image_analysis_text
|
||||
_log(add_log_func, workflow_id,
|
||||
f"Successfully analyzed {len(image_analysis_results)} images in PDF",
|
||||
"info")
|
||||
except Exception as img_error:
|
||||
logger.error(f"Error analyzing PDF images: {str(img_error)}")
|
||||
_log(add_log_func, workflow_id,
|
||||
f"Error analyzing PDF images: {str(img_error)}",
|
||||
"warning")
|
||||
|
||||
file_contents[file_id] = f"PDF with {num_pages} pages.\nContent:\n{text}"
|
||||
_log(add_log_func, workflow_id, f"PDF file {file_name} read successfully", "info")
|
||||
|
||||
except ImportError:
|
||||
_log(add_log_func, workflow_id,
|
||||
"PyPDF2 nicht installiert. PDF-Inhalt kann nicht extrahiert werden.", "warning")
|
||||
file_contents[file_id] = f"PDF-Datei (Inhalt nicht verfügbar, PyPDF2 fehlt)"
|
||||
# Try to use a different PDF library if available
|
||||
try:
|
||||
import fitz # PyMuPDF
|
||||
doc = fitz.open(file_path)
|
||||
text = ""
|
||||
for page in doc:
|
||||
text += page.get_text() + "\n\n"
|
||||
file_contents[file_id] = f"PDF with {len(doc)} pages.\nContent:\n{text}"
|
||||
_log(add_log_func, workflow_id, f"PDF file {file_name} read with PyMuPDF", "info")
|
||||
except ImportError:
|
||||
_log(add_log_func, workflow_id,
|
||||
"No PDF library installed. Cannot extract PDF content.", "warning")
|
||||
file_contents[file_id] = f"PDF file (content not available, PDF libraries missing)"
|
||||
except Exception as e:
|
||||
_log(add_log_func, workflow_id, f"Fehler beim Lesen der PDF-Datei {file_name}: {str(e)}", "error")
|
||||
logger.error(f"Error reading PDF file {file_name}: {str(e)}")
|
||||
_log(add_log_func, workflow_id, f"Error reading PDF file {file_name}: {str(e)}", "error")
|
||||
file_contents[file_id] = f"PDF file: {file_name} (Reading failed: {str(e)})"
|
||||
|
||||
# Andere Dokumenttypen
|
||||
# Other document types
|
||||
else:
|
||||
_log(add_log_func, workflow_id, f"Nicht unterstütztes Dokumentformat: {file_name}", "warning")
|
||||
file_contents[file_id] = f"Dateiinhalt nicht verfügbar (Nicht unterstütztes Format)"
|
||||
try:
|
||||
# Try to read as binary first to check file type
|
||||
with open(file_path, 'rb') as f:
|
||||
first_bytes = f.read(8) # Read first few bytes to identify file type
|
||||
|
||||
# Try to read as text if it appears to be text-based
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
content = f.read()
|
||||
file_contents[file_id] = content
|
||||
_log(add_log_func, workflow_id, f"File {file_name} read as text", "info")
|
||||
except Exception:
|
||||
file_contents[file_id] = f"File content not available (Binary or unsupported format)"
|
||||
_log(add_log_func, workflow_id, f"File {file_name} appears to be binary or has unknown format", "warning")
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing file {file_name}: {str(e)}")
|
||||
_log(add_log_func, workflow_id, f"Error processing file {file_name}: {str(e)}", "error")
|
||||
file_contents[file_id] = f"File content not available (Error: {str(e)})"
|
||||
|
||||
# Bilddateien werden nicht direkt gelesen, nur Metadaten gespeichert
|
||||
elif file_type == "image":
|
||||
file_contents[file_id] = f"Bilddatei: {file_name} (Inhalt nicht als Text verfügbar)"
|
||||
# Other file types - just store metadata
|
||||
else:
|
||||
file_contents[file_id] = f"File: {file_name} (Type: {file_type}, content not available)"
|
||||
_log(add_log_func, workflow_id, f"Unsupported file type: {file_type} for {file_name}", "warning")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Lesen der Datei {file_name}: {str(e)}")
|
||||
_log(add_log_func, workflow_id, f"Fehler beim Lesen der Datei {file_name}: {str(e)}", "error")
|
||||
logger.error(f"Error reading file {file_name}: {str(e)}")
|
||||
_log(add_log_func, workflow_id, f"Error reading file {file_name}: {str(e)}", "error")
|
||||
file_contents[file_id] = f"File content not available (Error: {str(e)})"
|
||||
else:
|
||||
if file_path:
|
||||
_log(add_log_func, workflow_id, f"Datei {file_name} nicht gefunden: {file_path}", "warning")
|
||||
_log(add_log_func, workflow_id, f"File {file_name} not found: {file_path}", "warning")
|
||||
else:
|
||||
_log(add_log_func, workflow_id, f"Kein Pfad für Datei {file_name} verfügbar", "warning")
|
||||
file_contents[file_id] = f"Dateiinhalt nicht verfügbar"
|
||||
_log(add_log_func, workflow_id, f"No path available for file {file_name}", "warning")
|
||||
file_contents[file_id] = f"File content not available (File not found)"
|
||||
|
||||
return file_contents
|
||||
|
||||
|
||||
def format_file_context_text(file_contexts: List[Dict[str, Any]], file_contents: Dict[str, str]) -> str:
|
||||
"""
|
||||
Erstellt eine formatierte Textdarstellung aller Dateien und ihrer Inhalte
|
||||
|
|
@ -179,21 +268,76 @@ async def prepare_message_for_ai(
|
|||
service_aichat
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Bereitet eine vollständige Nachricht mit allen Dateiinhalten für das AI-Modell vor.
|
||||
Benutzt den AI-Connector, um spezielle Datei-Analysen (wie Bild-Analysen) auszuführen.
|
||||
Enhanced function to prepare a complete message with all file contents for the AI model.
|
||||
Ensures proper file content integration and handles image analysis results.
|
||||
|
||||
Args:
|
||||
file_contexts: Liste der Dateikontexte mit Metadaten
|
||||
prompt_text: Der Text-Prompt
|
||||
file_contents: Dictionary mit bereits geladenen Dateiinhalten
|
||||
service_aichat: Die AI-Service-Instanz für spezielle Analysen
|
||||
file_contexts: List of file contexts with metadata
|
||||
prompt_text: The text prompt
|
||||
file_contents: Dictionary with file contents
|
||||
service_aichat: The AI service instance for special analyses
|
||||
|
||||
Returns:
|
||||
Eine vollständig formatierte Nachricht für das AI-Modell
|
||||
A fully formatted message for the AI model
|
||||
"""
|
||||
# Rufe die Methode des AI-Connectors auf, um die Nachricht zu erstellen
|
||||
return await service_aichat.parse_filedata(file_contexts, prompt_text, file_contents)
|
||||
|
||||
# Use the AI connector to create the message
|
||||
try:
|
||||
message = await service_aichat.parse_filedata(file_contexts, prompt_text, file_contents)
|
||||
|
||||
# Ensure file contents are correctly integrated
|
||||
if isinstance(message, dict) and message.get("content") and isinstance(message["content"], list):
|
||||
# For each file context, ensure its content is included
|
||||
for file_context in file_contexts:
|
||||
file_id = file_context["id"]
|
||||
file_name = file_context["name"]
|
||||
|
||||
# Check if file content is already included
|
||||
file_mentioned = False
|
||||
for content_item in message["content"]:
|
||||
if isinstance(content_item, dict) and content_item.get("type") == "text":
|
||||
if file_name in content_item.get("text", ""):
|
||||
file_mentioned = True
|
||||
break
|
||||
|
||||
# If file is not mentioned but we have its content, add it
|
||||
if not file_mentioned and file_id in file_contents:
|
||||
content = file_contents[file_id]
|
||||
message["content"].append({
|
||||
"type": "text",
|
||||
"text": f"--- FILE: {file_name} ---\n\n{content}"
|
||||
})
|
||||
logger.info(f"Added missing file content for {file_name} to message")
|
||||
|
||||
return message
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error preparing message for AI: {str(e)}")
|
||||
|
||||
# Create a basic message structure if the AI connector fails
|
||||
message = {
|
||||
"role": "user",
|
||||
"content": prompt_text + "\n\n"
|
||||
}
|
||||
|
||||
# Manually add file contents
|
||||
if file_contents:
|
||||
file_content_text = "\n\n=== FILE CONTENTS ===\n\n"
|
||||
for file_id, content in file_contents.items():
|
||||
# Find file name from contexts
|
||||
file_name = next((f["name"] for f in file_contexts if f["id"] == file_id), f"File {file_id}")
|
||||
file_content_text += f"--- FILE: {file_name} ---\n\n{content}\n\n"
|
||||
|
||||
# Append to message
|
||||
if isinstance(message["content"], str):
|
||||
message["content"] += file_content_text
|
||||
elif isinstance(message["content"], list):
|
||||
message["content"].append({
|
||||
"type": "text",
|
||||
"text": file_content_text
|
||||
})
|
||||
|
||||
return message
|
||||
|
||||
def _log(add_log_func, workflow_id, message, log_type, agent_id=None, agent_name=None):
|
||||
"""Hilfsfunktion zum Loggen mit unterschiedlichen Log-Funktionen"""
|
||||
# Log über die Logger-Instanz
|
||||
|
|
|
|||
Loading…
Reference in a new issue