-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagent.py
More file actions
167 lines (149 loc) · 6.24 KB
/
Copy pathagent.py
File metadata and controls
167 lines (149 loc) · 6.24 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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import os
from typing import TypedDict, Annotated
from dotenv import load_dotenv
import asyncio
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langgraph.checkpoint.memory import InMemorySaver
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.prompt import Prompt
from rich.markdown import Markdown
# Load env vars
load_dotenv()
# Note:
# The credentials included in this example are for demonstration only.
# You must use your own API keys, database credentials, and LLM configuration when running the project.
# os.environ["OPENAI_API_KEY"] = os.getenv(
# "GITHUB_TOKEN", "YOUR_GITHUB_TOKEN"
# )
# os.environ["OPENAI_API_BASE"] = "https://models.github.ai/inference"
# LLM
llm = ChatOpenAI(
model="openai/gpt-4.1",
api_key=os.environ["OPENAI_API_KEY"],
base_url=os.environ["OPENAI_API_BASE"],
temperature=0.2
)
# Rich console
console = Console()
# MCP client setup (absolute path to server)
mcp_server_path = os.path.join(
os.path.dirname(__file__), "mcp-database-server", "dist", "src", "index.js"
)
mcp_client = MultiServerMCPClient({
"database": {
"command": "node",
"args": [
mcp_server_path,
"--mysql",
"--host", os.getenv("DB_HOST", "localhost"),
"--database", os.getenv("DB_NAME", "YOUR_DATABASE_NAME"),
"--port", os.getenv("DB_PORT", "3306"),
"--user", os.getenv("DB_USER", "root"),
"--password", os.getenv("DB_PASSWORD", "YOUR_PASSWORD")
],
"transport": "stdio"
}
})
# Conversation state type
class ChatState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
async def main():
# Load system role prompt
prompt_path = os.path.join(os.path.dirname(__file__), "prompts", "hospital_receptionist.txt")
try:
with open(prompt_path, "r", encoding="utf-8") as f:
system_prompt = f.read().strip()
except FileNotFoundError:
system_prompt = (
"You are CareDesk, the hospital's AI Receptionist. Enforce privacy; require patient_id for any "
"patient-specific info. Help with appointments and general info."
)
system_messages = [SystemMessage(content=system_prompt)]
# Acquire MCP tools
try:
tools = await mcp_client.get_tools()
console.print(Panel("[bold green]MCP Database Server connected[/bold green]", border_style="green"))
table = Table(title="Available Database Tools", title_style="bold cyan")
table.add_column("Tool", style="bold", no_wrap=True)
table.add_column("Description", style="dim")
for t in tools:
table.add_row(t.name, t.description or "")
console.print(table)
except Exception as e:
console.print(Panel(f"[red]Failed to get MCP tools:[/red] {e}", border_style="red"))
return
# Bind LLM to tools
llm_with_tools = llm.bind_tools(tools)
# Nodes
async def chat_node(state: ChatState):
try:
# Prepend system prompt to every turn
resp = await llm_with_tools.ainvoke(system_messages + state["messages"])
return {"messages": [resp]}
except Exception as e:
return {"messages": [AIMessage(content=f"Error: {str(e)}")]}
tool_node = ToolNode(tools)
def should_continue(state: ChatState):
last = state["messages"][-1]
if hasattr(last, "tool_calls") and last.tool_calls:
return "tools"
return END
# Graph
graph = StateGraph(ChatState)
graph.add_node("chat", chat_node)
graph.add_node("tools", tool_node)
graph.add_edge(START, "chat")
graph.add_conditional_edges("chat", should_continue, {"tools": "tools", END: END})
graph.add_edge("tools", "chat")
app = graph.compile(checkpointer=InMemorySaver())
# Interactive loop
console.print(Panel(
"🤖 [bold magenta]Customer Support Agent (DB-enabled)[/bold magenta]\n[dim]Ask: 'List tables', 'Show doctors', 'Describe table patients'[/dim]\n[green]Type 'exit' to quit[/green]",
border_style="magenta"
))
console.print(Panel("Loaded role prompt: [italic]hospital_receptionist.txt[/italic]", border_style="blue"))
# Friendly welcome message for first-time users
console.print(Panel(
"[bold]Welcome to CareDesk![/bold]\n\n"
"I am the hospital's AI Receptionist. I can:\n"
"• Book, reschedule, or cancel appointments\n"
"• Share your own appointment, billing, or report info (with your patient_id)\n"
"• Answer general questions about departments, doctors, and services\n\n"
"[italic]Privacy first:[/italic] I will never share any patient's details without a valid patient_id.",
border_style="cyan"
))
config = {"configurable": {"thread_id": "config-thread"}}
try:
while True:
user = Prompt.ask("[bold cyan]You[/bold cyan]").strip()
if user.lower() in {"exit", "quit", "bye"}:
console.print("👋 Goodbye!", style="bold yellow")
break
if not user:
continue
console.print(Panel(Markdown(user), title="You", title_align="left", border_style="cyan"))
with console.status("Thinking...", spinner="dots"):
result = await app.ainvoke({"messages": [HumanMessage(content=user)]}, config=config)
msg = result["messages"][-1]
content = getattr(msg, "content", "(no content)")
console.print(Panel(Markdown(content), title="Agent", title_align="left", border_style="green"))
finally:
# Safely close MCP client if supported
try:
closer = getattr(mcp_client, "close", None)
if callable(closer):
maybe_coro = closer()
if asyncio.iscoroutine(maybe_coro):
await maybe_coro
except Exception:
pass
console.print("🔌 MCP connection closed.", style="dim")
if __name__ == "__main__":
asyncio.run(main())