-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathecomm_agent.py
More file actions
96 lines (79 loc) · 4.18 KB
/
Copy pathecomm_agent.py
File metadata and controls
96 lines (79 loc) · 4.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
from __future__ import annotations # postponed evaluation of type hints (avoids name-not-defined errors)
import os # used to read environment variables and interact with the OS
import json # used to parse JSON strings into Python dicts/lists and vice versa
import operator # imports standard mathematical & logical operator functions
from typing import Annotated, Sequence, TypedDict, Optional # imports type-hinting constructs for defining data models
from dotenv import load_dotenv
load_dotenv()
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, ToolMessage
from langchain_groq import ChatGroq
from langchain.tools import tool
from langgraph.graph import StateGraph
# Tools: Define functions the AI can 'choose' to execute
@tool
def send_customer_message(order_id: str, text: str) -> str:
"""Send a plain response to the customer."""
print(f"[TOOL] send_customer_message -> {text}")
return "Sent"
@tool
def issue_refund(order_id: str, amount: float) -> str:
"""Issue a refund for the given order."""
print(f"[TOOL] issue_refund(order_id={order_id}, amount={amount})")
return "refund_queued"
@tool
def cancel_order(order_id: str) -> str:
"""Cancel the given order."""
print(f"[TOOL] cancel_order(order_id={order_id})")
return "cancelled"
@tool
def update_address_for_order(order_id: str, shipping_address: dict) -> str:
"""Change the shipping address for the given pending order."""
print(f"[TOOL] update_address_for_order(order_id={order_id}, shipping_address={shipping_address})")
return "address_updated"
# Collect all tools into a list for the LLM
TOOLS = [send_customer_message, issue_refund, cancel_order, update_address_for_order]
# LLM: initialize the model and bind the tools so it knows how to call them
llm = ChatGroq(
model="llama-3.3-70b-versatile",
temperature=0.0
).bind_tools(TOOLS)
# State: defines the data structures that moves through the graph
class AgentState(TypedDict):
order: Optional[dict] # Current order info
messages: Annotated[Sequence[BaseMessage], operator.add] # The messages field is a sequence of messages. Whenever a node returns new messages, do not overwrite the old list. Instead, use operator.add to concatenate (append) the new messages to the existing list.
# Agent brain: the logic function that interacts with the LLM
def call_model(state: AgentState):
history = state["messages"]
# Handle the case where the order data might be missing
order = state.get("order", {})
if not order:
order = {"order_id": "UNKNOWN", "status": "UNKNOWN", "amount": 0.0}
order_json = json.dumps(order, ensure_ascii=False)
# Instructions for the AI's behavior
system_prompt = (
"You are a helpful e-commerce support agent.\n\n"
"You have access to these tools:\n"
" - issue_refund: ONLY if customer explicitly complains about a damaged/wrong item or explicitly asks for a refund\n"
" - cancel_order: ONLY if customer as explicitly to cancel the order\n"
" - update_address_for_order: ONLY if customer explicitly ask to change the address\n"
" - send_customer_message: ONLY after calling a business tool above\n\n"
"If the customer is just asking a question (e.g. order status, tracking, delivery time), "
"respond directly in plain text WITHOUT calling any tools.\n\n"
"NEVER assume there is a problem. NEVER call a tool unless the customer explicitly requests it.\n\n"
f"ORDER: {order_json}"
)
# Combine system prompt with message histroy
full = [SystemMessage(content=system_prompt)] + list(history)
messages = []
# ReAct loop: allow the model to call tools and see results (max 5 iterations)
current_input = full
for _ in range(5):
response = llm.invoke(current_input)
messages.append(response)
# If the AI didn't request a tool, it's done talking
if not getattr(response, "tool_calls", None):
break
# Iterate through requested tool calls and execute the python functionalities
for tc in response.tool_calls:
print(f"Calling tool: {tc['name']} with args: {tc['args']}")
fc =