Commit ·
da8c40b
1
Parent(s): b70637a
feat(agent): add answer extraction module and integrate into run function
Browse filesAdd new answer_extractor module that processes agent responses to extract clean answers.
Refactor main entry point to use the simplified run() function instead of interactive loop.
- agent/agent.py +19 -15
- agent/agents/answer_extractor.py +84 -0
agent/agent.py
CHANGED
|
@@ -7,6 +7,7 @@ from agent.tools.math_solver import math_solver
|
|
| 7 |
|
| 8 |
from agent.agents.websearchagents import web_search_agents
|
| 9 |
from agent.agents.websearchagent import websearch_agent
|
|
|
|
| 10 |
|
| 11 |
load_dotenv()
|
| 12 |
|
|
@@ -35,21 +36,24 @@ def run(query: str) -> str:
|
|
| 35 |
result = agent.invoke({"messages": [HumanMessage(content=query)]})
|
| 36 |
content = result["messages"][-1].content
|
| 37 |
if isinstance(content, list):
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
| 40 |
|
| 41 |
|
| 42 |
if __name__ == "__main__":
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
| 7 |
|
| 8 |
from agent.agents.websearchagents import web_search_agents
|
| 9 |
from agent.agents.websearchagent import websearch_agent
|
| 10 |
+
from agent.agents.answer_extractor import extract_answer
|
| 11 |
|
| 12 |
load_dotenv()
|
| 13 |
|
|
|
|
| 36 |
result = agent.invoke({"messages": [HumanMessage(content=query)]})
|
| 37 |
content = result["messages"][-1].content
|
| 38 |
if isinstance(content, list):
|
| 39 |
+
content = content[0].get("text", "")
|
| 40 |
+
else:
|
| 41 |
+
content = str(content)
|
| 42 |
+
return extract_answer(content, query)
|
| 43 |
|
| 44 |
|
| 45 |
if __name__ == "__main__":
|
| 46 |
+
run(input("Query:"))
|
| 47 |
+
# agent = supervisor_agent()
|
| 48 |
+
# chat_history: list = []
|
| 49 |
+
# while True:
|
| 50 |
+
# query = input("\nYou: ")
|
| 51 |
+
# if query.lower() in ("exit", "quit"):
|
| 52 |
+
# break
|
| 53 |
+
# chat_history.append(HumanMessage(content=query))
|
| 54 |
+
# result = agent.invoke({"messages": chat_history})
|
| 55 |
+
# chat_history = result["messages"]
|
| 56 |
+
# content = chat_history[-1].content
|
| 57 |
+
# if isinstance(content, list):
|
| 58 |
+
# content = content[0].get("text", "")
|
| 59 |
+
# print(f"Agent: {content}")
|
agent/agents/answer_extractor.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from colorama import Fore, Style # type: ignore[import]
|
| 2 |
+
from langchain.agents import create_agent
|
| 3 |
+
from pydantic import BaseModel, Field
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class ExtractedAnswer(BaseModel):
|
| 7 |
+
"""Structured output for answer extraction."""
|
| 8 |
+
|
| 9 |
+
extract_process_description: str = Field(
|
| 10 |
+
description="Description of the extraction process and reasoning."
|
| 11 |
+
)
|
| 12 |
+
answer: str = Field(
|
| 13 |
+
description="The pure, concise answer without any extra text or spaces."
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def extract_answer(raw_answer: str, question: str) -> str:
|
| 18 |
+
"""Extract a concise, plain-text answer from a verbose agent response."""
|
| 19 |
+
print(f"{Fore.CYAN}[AnswerExtractor] Extracting...{Style.RESET_ALL}")
|
| 20 |
+
|
| 21 |
+
agent = create_agent(
|
| 22 |
+
model="google_genai:gemini-3-flash-preview",
|
| 23 |
+
response_format=ExtractedAnswer,
|
| 24 |
+
system_prompt="""\
|
| 25 |
+
You are part of a system that demands extreme precision.
|
| 26 |
+
Your job is to extract the pure answer from a verbose agent response.
|
| 27 |
+
The answer field must contain ONLY the answer itself —
|
| 28 |
+
no extra description, no explanation, no units unless explicitly required,
|
| 29 |
+
no leading or trailing whitespace, no punctuation beyond what is part of the answer.
|
| 30 |
+
Do NOT add any surrounding text. Do NOT pad with spaces.
|
| 31 |
+
|
| 32 |
+
```example
|
| 33 |
+
Input:
|
| 34 |
+
'... the answer is 114. Thank.'
|
| 35 |
+
|
| 36 |
+
Output:
|
| 37 |
+
'114'
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
```example
|
| 41 |
+
Input:
|
| 42 |
+
'... Serial number is BC34ACD2 and ...'
|
| 43 |
+
|
| 44 |
+
Output:
|
| 45 |
+
'BC34ACD2'
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
```example
|
| 49 |
+
Input:
|
| 50 |
+
'Result is 6, 7, 1, 10'
|
| 51 |
+
|
| 52 |
+
Output:
|
| 53 |
+
'6, 7, 1, 10'
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
```example
|
| 57 |
+
Input:
|
| 58 |
+
'Answer is 73!'
|
| 59 |
+
|
| 60 |
+
Output:
|
| 61 |
+
'73'
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
""",
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
result = agent.invoke(
|
| 68 |
+
{
|
| 69 |
+
"messages": [
|
| 70 |
+
{
|
| 71 |
+
"role": "user",
|
| 72 |
+
"content": (
|
| 73 |
+
f"Question: {question}\n\nAgent's verbose answer:\n{raw_answer}"
|
| 74 |
+
),
|
| 75 |
+
}
|
| 76 |
+
]
|
| 77 |
+
}
|
| 78 |
+
)
|
| 79 |
+
extracted: ExtractedAnswer = result["structured_response"]
|
| 80 |
+
print(
|
| 81 |
+
f"{Fore.CYAN}[AnswerExtractor] Process: {extracted.extract_process_description}{Style.RESET_ALL}"
|
| 82 |
+
)
|
| 83 |
+
print(f"{Fore.CYAN}[AnswerExtractor] Answer: {extracted.answer}{Style.RESET_ALL}")
|
| 84 |
+
return extracted.answer
|