Spaces:
Sleeping
Sleeping
quachtiensinh27 commited on
Commit ·
7fee926
1
Parent(s): 7a4edb9
feat: upgrade tool and add doc
Browse files- tools/__init__.py +1 -0
- tools/summarizer.py +20 -13
- tools/utils.py +43 -4
- tools/web.py +62 -0
tools/__init__.py
CHANGED
|
@@ -7,6 +7,7 @@ from .base import get_tool_schemas, execute_tool, get_langchain_tools, get_llm
|
|
| 7 |
from .scheduler import tool_get_schedule, tool_add_event
|
| 8 |
from .summarizer import tool_summarize_chat
|
| 9 |
from .memory import tool_save_memory, tool_get_memories
|
|
|
|
| 10 |
|
| 11 |
# The actual list to be used by the agent can be derived from the registry
|
| 12 |
# or explicitly exported here.
|
|
|
|
| 7 |
from .scheduler import tool_get_schedule, tool_add_event
|
| 8 |
from .summarizer import tool_summarize_chat
|
| 9 |
from .memory import tool_save_memory, tool_get_memories
|
| 10 |
+
from .web import tool_read_link
|
| 11 |
|
| 12 |
# The actual list to be used by the agent can be derived from the registry
|
| 13 |
# or explicitly exported here.
|
tools/summarizer.py
CHANGED
|
@@ -10,44 +10,42 @@ from langchain_core.output_parsers import JsonOutputParser
|
|
| 10 |
from langchain_core.prompts import ChatPromptTemplate
|
| 11 |
|
| 12 |
from .base import register_tool, get_llm
|
| 13 |
-
from .utils import preprocess_messages
|
| 14 |
-
from ..redis_client import redis_client
|
| 15 |
-
|
| 16 |
-
logger = logging.getLogger(__name__)
|
| 17 |
|
| 18 |
# --- Pydantic Schemas ---
|
| 19 |
class ThreadSummary(BaseModel):
|
| 20 |
"""Schema cho tóm tắt của một thread."""
|
| 21 |
thread_id: str = Field(description="ID hoặc tên chủ đề của thread")
|
| 22 |
main_discussion: str = Field(description="Nội dung chính đang thảo luận")
|
|
|
|
| 23 |
status: str = Field(description="Trạng thái: 'Đã chốt' hoặc 'Chưa chốt'")
|
| 24 |
conclusion: str = Field(description="Kết luận cuối cùng")
|
| 25 |
|
| 26 |
class TLDRResponse(BaseModel):
|
| 27 |
"""Schema cho response JSON tổng thể."""
|
| 28 |
-
summary: list[ThreadSummary] = Field(description="Danh sách tóm tắt")
|
|
|
|
|
|
|
| 29 |
|
| 30 |
# --- System Prompt ---
|
| 31 |
SYSTEM_PROMPT_TLDR = """
|
| 32 |
Bạn là một THƯ KÝ DỰ ÁN chuyên nghiệp.
|
| 33 |
Nhiệm vụ: tóm tắt các đoạn chat nhóm thành báo cáo chính xác tuyệt đối.
|
|
|
|
| 34 |
- Chỉ chốt các thông tin có keyword "ok", "chốt", "thống nhất"...
|
| 35 |
- Nếu đang tranh luận chưa ngã ngũ -> status = "Chưa chốt".
|
| 36 |
- Bỏ qua tin nhắn rác.
|
| 37 |
- Trả về JSON đúng schema.
|
| 38 |
"""
|
| 39 |
|
| 40 |
-
|
| 41 |
-
"""Helper to extract token usage."""
|
| 42 |
-
# (Simplified for now, can be expanded if needed)
|
| 43 |
-
return response_metadata.get("token_usage", {"total_tokens": 0})
|
| 44 |
|
| 45 |
@register_tool(
|
| 46 |
name="summarize_chat",
|
| 47 |
-
description="Tóm tắt
|
| 48 |
parameters=[
|
| 49 |
-
{"name": "room_id", "type": "string", "description": "ID của phòng chat
|
| 50 |
-
{"name": "limit", "type": "integer", "description": "Số lượng tin nhắn tối đa
|
| 51 |
]
|
| 52 |
)
|
| 53 |
def tool_summarize_chat(
|
|
@@ -64,9 +62,14 @@ def tool_summarize_chat(
|
|
| 64 |
messages = redis_client.get_room_messages(room_id, limit)
|
| 65 |
|
| 66 |
if not messages:
|
| 67 |
-
return {"status": "success", "data": {"summary": []}, "metrics": {"processing_time_sec": 0}}
|
| 68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
formatted_threads = preprocess_messages(messages)
|
|
|
|
| 70 |
llm = get_llm()
|
| 71 |
parser = JsonOutputParser(pydantic_object=TLDRResponse)
|
| 72 |
|
|
@@ -81,6 +84,10 @@ def tool_summarize_chat(
|
|
| 81 |
"format_instructions": parser.get_format_instructions(),
|
| 82 |
})
|
| 83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
processing_time = round(time.time() - start_time, 2)
|
| 85 |
return {
|
| 86 |
"status": "success",
|
|
|
|
| 10 |
from langchain_core.prompts import ChatPromptTemplate
|
| 11 |
|
| 12 |
from .base import register_tool, get_llm
|
| 13 |
+
from .utils import preprocess_messages, extract_metadata_from_messages
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
# --- Pydantic Schemas ---
|
| 16 |
class ThreadSummary(BaseModel):
|
| 17 |
"""Schema cho tóm tắt của một thread."""
|
| 18 |
thread_id: str = Field(description="ID hoặc tên chủ đề của thread")
|
| 19 |
main_discussion: str = Field(description="Nội dung chính đang thảo luận")
|
| 20 |
+
conversation_flow: str = Field(description="Tóm tắt diễn biến cuộc hội thoại (ai hỏi, ai trả lời, mâu thuẫn/đồng thuận)")
|
| 21 |
status: str = Field(description="Trạng thái: 'Đã chốt' hoặc 'Chưa chốt'")
|
| 22 |
conclusion: str = Field(description="Kết luận cuối cùng")
|
| 23 |
|
| 24 |
class TLDRResponse(BaseModel):
|
| 25 |
"""Schema cho response JSON tổng thể."""
|
| 26 |
+
summary: list[ThreadSummary] = Field(description="Danh sách tóm tắt các thread")
|
| 27 |
+
links_found: list[str] = Field(description="Danh sách các liên kết (URL) được tìm thấy")
|
| 28 |
+
files_found: list[dict] = Field(description="Danh sách các tệp tin/ảnh được tìm thấy")
|
| 29 |
|
| 30 |
# --- System Prompt ---
|
| 31 |
SYSTEM_PROMPT_TLDR = """
|
| 32 |
Bạn là một THƯ KÝ DỰ ÁN chuyên nghiệp.
|
| 33 |
Nhiệm vụ: tóm tắt các đoạn chat nhóm thành báo cáo chính xác tuyệt đối.
|
| 34 |
+
- Xác định rõ 'conversation_flow': mô tả cách cuộc thảo luận diễn ra (ví dụ: A đề xuất, B phản đối, cuối cùng cả nhóm đồng ý).
|
| 35 |
- Chỉ chốt các thông tin có keyword "ok", "chốt", "thống nhất"...
|
| 36 |
- Nếu đang tranh luận chưa ngã ngũ -> status = "Chưa chốt".
|
| 37 |
- Bỏ qua tin nhắn rác.
|
| 38 |
- Trả về JSON đúng schema.
|
| 39 |
"""
|
| 40 |
|
| 41 |
+
# ... (execute_tool part)
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
@register_tool(
|
| 44 |
name="summarize_chat",
|
| 45 |
+
description="Tóm tắt tin nhắn, diễn biến cuộc hội thoại và trích xuất Link/File bằng AI. Cần cung cấp room_id.",
|
| 46 |
parameters=[
|
| 47 |
+
{"name": "room_id", "type": "string", "description": "ID của phòng chat.", "required": True},
|
| 48 |
+
{"name": "limit", "type": "integer", "description": "Số lượng tin nhắn tối đa (Mặc định: 100).", "required": False}
|
| 49 |
]
|
| 50 |
)
|
| 51 |
def tool_summarize_chat(
|
|
|
|
| 62 |
messages = redis_client.get_room_messages(room_id, limit)
|
| 63 |
|
| 64 |
if not messages:
|
| 65 |
+
return {"status": "success", "data": {"summary": [], "links_found": [], "files_found": []}, "metrics": {"processing_time_sec": 0}}
|
| 66 |
|
| 67 |
+
# 1. Trích xuất Metadata (Links/Files)
|
| 68 |
+
metadata = extract_metadata_from_messages(messages)
|
| 69 |
+
|
| 70 |
+
# 2. Tiền xử lý văn bản cho LLM
|
| 71 |
formatted_threads = preprocess_messages(messages)
|
| 72 |
+
|
| 73 |
llm = get_llm()
|
| 74 |
parser = JsonOutputParser(pydantic_object=TLDRResponse)
|
| 75 |
|
|
|
|
| 84 |
"format_instructions": parser.get_format_instructions(),
|
| 85 |
})
|
| 86 |
|
| 87 |
+
# Merge metadata vào kết quả nếu LLM chưa tự điền (LLM thường chỉ tóm tắt text)
|
| 88 |
+
result["links_found"] = list(set(result.get("links_found", []) + metadata["links"]))
|
| 89 |
+
result["files_found"] = metadata["files"]
|
| 90 |
+
|
| 91 |
processing_time = round(time.time() - start_time, 2)
|
| 92 |
return {
|
| 93 |
"status": "success",
|
tools/utils.py
CHANGED
|
@@ -1,10 +1,49 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
from collections import defaultdict
|
| 6 |
|
| 7 |
def group_messages_by_thread(messages: list[dict]) -> dict[str, list[dict]]:
|
|
|
|
| 8 |
"""
|
| 9 |
Gom nhóm các tin nhắn có cùng roomId lại với nhau.
|
| 10 |
"""
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
def extract_metadata_from_messages(messages: list[dict]) -> dict:
|
| 5 |
+
"""
|
| 6 |
+
Trích xuất danh sách Link và File từ danh sách tin nhắn.
|
| 7 |
+
"""
|
| 8 |
+
links = []
|
| 9 |
+
files = []
|
| 10 |
+
|
| 11 |
+
# Regex đơn giản cho URL
|
| 12 |
+
url_pattern = r'https?://[^\s]+'
|
| 13 |
+
|
| 14 |
+
for msg in messages:
|
| 15 |
+
content = msg.get("content", "")
|
| 16 |
+
# Tìm links
|
| 17 |
+
found_links = re.findall(url_pattern, content)
|
| 18 |
+
links.extend(found_links)
|
| 19 |
+
|
| 20 |
+
# Tìm files từ trường attachment
|
| 21 |
+
attachment_raw = msg.get("attachment")
|
| 22 |
+
if attachment_raw and attachment_raw != "null":
|
| 23 |
+
try:
|
| 24 |
+
# Trường attachment có thể là string JSON hoặc dict tùy vào RedisClient
|
| 25 |
+
if isinstance(attachment_raw, str):
|
| 26 |
+
att = json.loads(attachment_raw)
|
| 27 |
+
else:
|
| 28 |
+
att = attachment_raw
|
| 29 |
+
|
| 30 |
+
files.append({
|
| 31 |
+
"id": att.get("id"),
|
| 32 |
+
"type": att.get("type"),
|
| 33 |
+
"name": att.get("name"),
|
| 34 |
+
"sender": msg.get("senderName")
|
| 35 |
+
})
|
| 36 |
+
except:
|
| 37 |
+
pass
|
| 38 |
+
|
| 39 |
+
return {
|
| 40 |
+
"links": list(set(links)), # Loại bỏ trùng
|
| 41 |
+
"files": files
|
| 42 |
+
}
|
| 43 |
|
|
|
|
| 44 |
|
| 45 |
def group_messages_by_thread(messages: list[dict]) -> dict[str, list[dict]]:
|
| 46 |
+
# ... (rest of the file)
|
| 47 |
"""
|
| 48 |
Gom nhóm các tin nhắn có cùng roomId lại với nhau.
|
| 49 |
"""
|
tools/web.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Web scraping tools for fetching content from URLs.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import logging
|
| 6 |
+
import requests
|
| 7 |
+
from bs4 import BeautifulSoup
|
| 8 |
+
from .base import register_tool
|
| 9 |
+
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
+
@register_tool(
|
| 13 |
+
name="read_link",
|
| 14 |
+
description="Truy cập và bóc tách nội dung chính từ một đường dẫn (URL). Giúp Agent hiểu nội dung bài báo, tài liệu online.",
|
| 15 |
+
parameters=[
|
| 16 |
+
{"name": "url", "type": "string", "description": "Đường dẫn (URL) cần đọc nội dung.", "required": True}
|
| 17 |
+
]
|
| 18 |
+
)
|
| 19 |
+
def tool_read_link(url: str) -> dict:
|
| 20 |
+
"""
|
| 21 |
+
Fetches and parses a URL to extract the main content.
|
| 22 |
+
"""
|
| 23 |
+
try:
|
| 24 |
+
# 1. Fetch content with timeout
|
| 25 |
+
headers = {
|
| 26 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
| 27 |
+
}
|
| 28 |
+
response = requests.get(url, headers=headers, timeout=10)
|
| 29 |
+
response.raise_for_status()
|
| 30 |
+
|
| 31 |
+
# 2. Parse with BeautifulSoup
|
| 32 |
+
soup = BeautifulSoup(response.text, 'html.parser')
|
| 33 |
+
|
| 34 |
+
# Remove script and style elements
|
| 35 |
+
for script_or_style in soup(["script", "style", "nav", "footer", "header"]):
|
| 36 |
+
script_or_style.decompose()
|
| 37 |
+
|
| 38 |
+
# 3. Extract title and main text
|
| 39 |
+
title = soup.title.string if soup.title else "No Title"
|
| 40 |
+
|
| 41 |
+
# Simple extraction: get all paragraphs
|
| 42 |
+
paragraphs = soup.find_all('p')
|
| 43 |
+
text_content = "\n".join([p.get_text().strip() for p in paragraphs if p.get_text().strip()])
|
| 44 |
+
|
| 45 |
+
# Truncate content for LLM context (approx 3000 chars)
|
| 46 |
+
if len(text_content) > 3000:
|
| 47 |
+
text_content = text_content[:3000] + "..."
|
| 48 |
+
|
| 49 |
+
return {
|
| 50 |
+
"status": "success",
|
| 51 |
+
"data": {
|
| 52 |
+
"url": url,
|
| 53 |
+
"title": title.strip(),
|
| 54 |
+
"content": text_content
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
except requests.exceptions.Timeout:
|
| 59 |
+
return {"status": "error", "message": "Yêu cầu quá hạn (timeout) khi truy cập URL."}
|
| 60 |
+
except Exception as e:
|
| 61 |
+
logger.error(f"Error in read_link: {e}")
|
| 62 |
+
return {"status": "error", "message": f"Không thể đọc nội dung từ link: {str(e)}"}
|