Spaces:
Runtime error
Runtime error
chnages
Browse files- app/agents/context_agent.py +1 -1
- app/core/config.py +7 -15
- app/database/connection.py +4 -1
- app/nodes/check_token_count_node.py +1 -8
- app/nodes/store_memory_data_node.py +22 -3
- app/nodes/summarise_email_body_node.py +3 -16
- app/prompts/email_writing_agent_prompt.py +4 -1
- app/state/state.py +1 -1
- app/utils/token_utils.py +30 -0
app/agents/context_agent.py
CHANGED
|
@@ -16,7 +16,7 @@ context_agent = create_agent(
|
|
| 16 |
middleware=[
|
| 17 |
ToolCallLimitMiddleware[Any,None](
|
| 18 |
tool_name="search_memory",
|
| 19 |
-
run_limit=
|
| 20 |
thread_limit=10,
|
| 21 |
)
|
| 22 |
] ,
|
|
|
|
| 16 |
middleware=[
|
| 17 |
ToolCallLimitMiddleware[Any,None](
|
| 18 |
tool_name="search_memory",
|
| 19 |
+
run_limit=5,
|
| 20 |
thread_limit=10,
|
| 21 |
)
|
| 22 |
] ,
|
app/core/config.py
CHANGED
|
@@ -5,24 +5,16 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
| 5 |
|
| 6 |
class Settings(BaseSettings):
|
| 7 |
PROJECT_NAME: str = "Email Assistant Project"
|
| 8 |
-
|
| 9 |
GROQ_API_KEY: str
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
# CLOUDINARY_CLOUD_NAME: str
|
| 13 |
-
# CLOUDINARY_API_KEY: str
|
| 14 |
-
# CLOUDINARY_API_SECRET: str
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
# DB_URL_FOR_CHECKPOINTER_STORE: str
|
| 18 |
|
| 19 |
-
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
|
| 27 |
settings = Settings()
|
| 28 |
|
|
|
|
| 5 |
|
| 6 |
class Settings(BaseSettings):
|
| 7 |
PROJECT_NAME: str = "Email Assistant Project"
|
|
|
|
| 8 |
GROQ_API_KEY: str
|
| 9 |
+
DB_URL_FOR_CHECKPOINTER_STORE: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
+
DB_URL_FOR_SQL_AL:str
|
| 12 |
|
| 13 |
+
model_config = SettingsConfigDict(
|
| 14 |
+
env_file=str(BASE_DIR / ".env"),
|
| 15 |
+
env_file_encoding="utf-8",
|
| 16 |
+
extra="ignore"
|
| 17 |
+
)
|
| 18 |
|
| 19 |
settings = Settings()
|
| 20 |
|
app/database/connection.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
| 1 |
from sqlalchemy import create_engine
|
| 2 |
from sqlalchemy.orm import sessionmaker, Session
|
| 3 |
-
from app.core.config import
|
|
|
|
|
|
|
|
|
|
| 4 |
engine = create_engine(
|
| 5 |
DB_URL_FOR_SQL_AL,
|
| 6 |
echo=False,
|
|
|
|
| 1 |
from sqlalchemy import create_engine
|
| 2 |
from sqlalchemy.orm import sessionmaker, Session
|
| 3 |
+
from app.core.config import settings
|
| 4 |
+
|
| 5 |
+
DB_URL_FOR_SQL_AL=settings.DB_URL_FOR_SQL_AL
|
| 6 |
+
|
| 7 |
engine = create_engine(
|
| 8 |
DB_URL_FOR_SQL_AL,
|
| 9 |
echo=False,
|
app/nodes/check_token_count_node.py
CHANGED
|
@@ -1,14 +1,7 @@
|
|
| 1 |
from app.state.state import EmailAgentState
|
| 2 |
from langchain_groq import ChatGroq
|
|
|
|
| 3 |
|
| 4 |
-
llm_for_token_count=ChatGroq(
|
| 5 |
-
model="meta-llama/llama-4-scout-17b-16e-instruct",
|
| 6 |
-
temperature=0.1,
|
| 7 |
-
)
|
| 8 |
-
|
| 9 |
-
def count_input_tokens(subject:str ,body:str) -> int:
|
| 10 |
-
text=subject+body
|
| 11 |
-
return llm_for_token_count.get_num_tokens(text)
|
| 12 |
|
| 13 |
def check_token_count_node(state: EmailAgentState):
|
| 14 |
"""
|
|
|
|
| 1 |
from app.state.state import EmailAgentState
|
| 2 |
from langchain_groq import ChatGroq
|
| 3 |
+
from app.utils.token_utils import count_input_tokens
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
def check_token_count_node(state: EmailAgentState):
|
| 7 |
"""
|
app/nodes/store_memory_data_node.py
CHANGED
|
@@ -6,6 +6,8 @@ from app.state.state import EmailAgentState
|
|
| 6 |
from app.agents.memory_manager_agent import memory_manager_agent
|
| 7 |
from app.database.connection import get_session
|
| 8 |
from app.database.utils import save_sent_email, save_received_email
|
|
|
|
|
|
|
| 9 |
|
| 10 |
def store_memory_and_data_node(state: EmailAgentState, config: RunnableConfig):
|
| 11 |
"""
|
|
@@ -13,21 +15,38 @@ def store_memory_and_data_node(state: EmailAgentState, config: RunnableConfig):
|
|
| 13 |
"""
|
| 14 |
print("--- Memory Node: Persisting interaction to DB ---")
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
# 1. Prepare the memory summary
|
| 17 |
prompt = memory_agent_template.invoke({
|
| 18 |
"user_name": state["user_name"],
|
| 19 |
"senders_email_id": state["sender_email_id"],
|
| 20 |
"user_email_id": state["user_email_id"],
|
| 21 |
-
"sent_email_body":
|
| 22 |
"incoming_email_body": state["sender_email_body"],
|
| 23 |
})
|
| 24 |
|
| 25 |
# 2. Invoke memory agent logic
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
{"messages": prompt.to_messages()},
|
| 28 |
config=config
|
| 29 |
)
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
# 3. Robust Database Operations
|
| 32 |
sender_id = state['user_id']
|
| 33 |
thread_id = config.get("configurable", {}).get("thread_id")
|
|
@@ -44,4 +63,4 @@ def store_memory_and_data_node(state: EmailAgentState, config: RunnableConfig):
|
|
| 44 |
print(f"--- Memory Node Error: {e} ---")
|
| 45 |
raise e
|
| 46 |
|
| 47 |
-
return {"memory_agent_messages": [AIMessage(content=
|
|
|
|
| 6 |
from app.agents.memory_manager_agent import memory_manager_agent
|
| 7 |
from app.database.connection import get_session
|
| 8 |
from app.database.utils import save_sent_email, save_received_email
|
| 9 |
+
from langchain_core.messages import AIMessage
|
| 10 |
+
from app.utils.token_utils import *
|
| 11 |
|
| 12 |
def store_memory_and_data_node(state: EmailAgentState, config: RunnableConfig):
|
| 13 |
"""
|
|
|
|
| 15 |
"""
|
| 16 |
print("--- Memory Node: Persisting interaction to DB ---")
|
| 17 |
|
| 18 |
+
|
| 19 |
+
body_for_memory_agent=state.get("reply_email_body")
|
| 20 |
+
reply_subject=state.get("reply_subject")
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
if count_input_tokens(body_for_memory_agent,reply_subject)>100000:
|
| 24 |
+
body_for_memory_agent=summarise_email_body(body_for_memory_agent)
|
| 25 |
+
|
| 26 |
# 1. Prepare the memory summary
|
| 27 |
prompt = memory_agent_template.invoke({
|
| 28 |
"user_name": state["user_name"],
|
| 29 |
"senders_email_id": state["sender_email_id"],
|
| 30 |
"user_email_id": state["user_email_id"],
|
| 31 |
+
"sent_email_body": body_for_memory_agent,
|
| 32 |
"incoming_email_body": state["sender_email_body"],
|
| 33 |
})
|
| 34 |
|
| 35 |
# 2. Invoke memory agent logic
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
print("invoking memory manager")
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
response = memory_manager_agent.invoke(
|
| 42 |
{"messages": prompt.to_messages()},
|
| 43 |
config=config
|
| 44 |
)
|
| 45 |
|
| 46 |
+
print(response)
|
| 47 |
+
|
| 48 |
+
memory_stored_summary = response[0]['value']['content']['summary']
|
| 49 |
+
|
| 50 |
# 3. Robust Database Operations
|
| 51 |
sender_id = state['user_id']
|
| 52 |
thread_id = config.get("configurable", {}).get("thread_id")
|
|
|
|
| 63 |
print(f"--- Memory Node Error: {e} ---")
|
| 64 |
raise e
|
| 65 |
|
| 66 |
+
return {"memory_agent_messages": [AIMessage(content=memory_stored_summary)],"email_sent": True}
|
app/nodes/summarise_email_body_node.py
CHANGED
|
@@ -5,22 +5,9 @@ from langchain_classic.text_splitter import RecursiveCharacterTextSplitter
|
|
| 5 |
from langchain_core.documents import Document
|
| 6 |
from langchain_classic.prompts import PromptTemplate
|
| 7 |
from app.agents.summarizer_agent import summarizer_agent
|
| 8 |
-
from
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
|
| 12 |
-
docs = [Document(page_content=t) for t in text_splitter.split_text(body)]
|
| 13 |
-
|
| 14 |
-
chain = load_summarize_chain(
|
| 15 |
-
llm=summarizer_agent,
|
| 16 |
-
chain_type="refine",
|
| 17 |
-
question_prompt=summarize_agent_initial_prompt,
|
| 18 |
-
refine_prompt=summarize_agent_refine_prompt,
|
| 19 |
-
document_variable_name="body" # <--- ADD THIS LINE
|
| 20 |
-
)
|
| 21 |
-
summary = chain.invoke(docs)
|
| 22 |
-
return summary['output_text']
|
| 23 |
-
|
| 24 |
|
| 25 |
def summarise_email_body_node(state:EmailAgentState)->dict:
|
| 26 |
|
|
|
|
| 5 |
from langchain_core.documents import Document
|
| 6 |
from langchain_classic.prompts import PromptTemplate
|
| 7 |
from app.agents.summarizer_agent import summarizer_agent
|
| 8 |
+
from langchain_groq import ChatGroq
|
| 9 |
+
from app.prompts.summarizer_agent_prompt import *
|
| 10 |
+
from app.utils.token_utils import summarise_email_body
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
def summarise_email_body_node(state:EmailAgentState)->dict:
|
| 13 |
|
app/prompts/email_writing_agent_prompt.py
CHANGED
|
@@ -24,11 +24,14 @@ Output: "Stored."
|
|
| 24 |
<rules>
|
| 25 |
1. NEW DRAFT: Call `create_gmail_draft` first.
|
| 26 |
2. REJECTION: If "DRAFT REJECTED", rewrite and call `create_gmail_draft_tool` again.
|
| 27 |
-
3. SENDING: Only call `send_draft_by_id` when user explicitly orders to send.
|
| 28 |
4. ARCHIVING: After `send_draft_by_id` returns a Message ID.
|
| 29 |
important:You are not allowed to send until the user explicitly orders to send.
|
| 30 |
5.Use **{sender_email_id}** as the recipient not the name.
|
|
|
|
| 31 |
</rules>
|
|
|
|
|
|
|
| 32 |
|
| 33 |
""")
|
| 34 |
])
|
|
|
|
| 24 |
<rules>
|
| 25 |
1. NEW DRAFT: Call `create_gmail_draft` first.
|
| 26 |
2. REJECTION: If "DRAFT REJECTED", rewrite and call `create_gmail_draft_tool` again.
|
| 27 |
+
3. SENDING: Only call `send_draft_by_id` when user explicitly orders to send.
|
| 28 |
4. ARCHIVING: After `send_draft_by_id` returns a Message ID.
|
| 29 |
important:You are not allowed to send until the user explicitly orders to send.
|
| 30 |
5.Use **{sender_email_id}** as the recipient not the name.
|
| 31 |
+
**dont call send_draft_by_id until user allows you to send the draft**
|
| 32 |
</rules>
|
| 33 |
+
|
| 34 |
+
|
| 35 |
|
| 36 |
""")
|
| 37 |
])
|
app/state/state.py
CHANGED
|
@@ -61,7 +61,7 @@ class EmailAgentState(TypedDict):
|
|
| 61 |
|
| 62 |
email_sent: Optional[bool]
|
| 63 |
|
| 64 |
-
|
| 65 |
human_approved: Optional[bool]
|
| 66 |
reply_email_body:Optional[str]
|
| 67 |
|
|
|
|
| 61 |
|
| 62 |
email_sent: Optional[bool]
|
| 63 |
|
| 64 |
+
|
| 65 |
human_approved: Optional[bool]
|
| 66 |
reply_email_body:Optional[str]
|
| 67 |
|
app/utils/token_utils.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain_groq import ChatGroq
|
| 2 |
+
from langchain_classic.chains.summarize import load_summarize_chain
|
| 3 |
+
from langchain_classic.text_splitter import RecursiveCharacterTextSplitter
|
| 4 |
+
from langchain_core.documents import Document
|
| 5 |
+
from app.agents.summarizer_agent import summarizer_agent
|
| 6 |
+
from app.prompts.summarizer_agent_prompt import *
|
| 7 |
+
|
| 8 |
+
llm_for_token_count=ChatGroq(
|
| 9 |
+
model="meta-llama/llama-4-scout-17b-16e-instruct",
|
| 10 |
+
temperature=0.1,
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
def count_input_tokens(subject:str ,body:str) -> int:
|
| 14 |
+
text=subject+body
|
| 15 |
+
return llm_for_token_count.get_num_tokens(text)
|
| 16 |
+
|
| 17 |
+
def summarise_email_body(body: str):
|
| 18 |
+
# Tip: 100 is very small for an email; 500-1000 is usually better
|
| 19 |
+
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
|
| 20 |
+
docs = [Document(page_content=t) for t in text_splitter.split_text(body)]
|
| 21 |
+
|
| 22 |
+
chain = load_summarize_chain(
|
| 23 |
+
llm=summarizer_agent,
|
| 24 |
+
chain_type="refine",
|
| 25 |
+
question_prompt=summarize_agent_initial_prompt,
|
| 26 |
+
refine_prompt=summarize_agent_refine_prompt,
|
| 27 |
+
document_variable_name="body" # <--- ADD THIS LINE
|
| 28 |
+
)
|
| 29 |
+
summary = chain.invoke(docs)
|
| 30 |
+
return summary['output_text']
|