Skip to content

Commit b57955a

Browse files
committed
refactor: update configuration from Azure OpenAI to OpenAI and enhance agent service initialization
Signed-off-by: Andre Bossard <anbossar@microsoft.com>
1 parent e691d02 commit b57955a

5 files changed

Lines changed: 131 additions & 86 deletions

File tree

.env.example

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
# Azure OpenAI Configuration
2-
# Get your API key from: https://portal.azure.com
1+
# OpenAI Configuration
2+
# Get your API key from: https://platform.openai.com/api-keys
33

4-
# Your Azure OpenAI API key (Bearer token)
5-
AZURE_API_KEY=your-api-key-here
4+
OPENAI_API_KEY=your-openai-api-key-here
5+
OPENAI_MODEL=gpt-4o-mini
6+
# Optional override
7+
# OPENAI_BASE_URL=https://api.openai.com/v1
68

79
# Optional: Frontend build path override
810
# FRONTEND_DIST=/path/to/custom/frontend/dist

backend/agents.py

Lines changed: 115 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""
2-
Agent Management Module with Azure OpenAI and LangGraph
2+
Agent Management Module with OpenAI and LangGraph
33
44
Provides LangGraph-based agents for task automation:
55
- Type-safe data models with Pydantic
66
- Self-documenting schemas for REST and MCP
7-
- Azure OpenAI integration via langchain-openai
7+
- OpenAI integration via langchain-openai
88
- ReAct agent pattern with automatic tool discovery
99
1010
Following "Grokking Simplicity" and "A Philosophy of Software Design":
@@ -39,18 +39,28 @@
3939

4040
load_dotenv()
4141

42+
from uuid import UUID
43+
4244
# Ensure operations register before we request LangChain tools
4345
import operations # noqa: F401
46+
4447
# Local - Import operations registry for automatic tool discovery
4548
from api_decorators import get_langchain_tools
49+
50+
# Local CSV service
51+
from csv_data import get_csv_ticket_service
52+
4653
# Third-party - FastMCP client for external MCP servers
4754
from fastmcp import Client as MCPClient
4855
from langchain_core.tools import StructuredTool
56+
4957
# Third-party - LangChain and LangGraph
50-
from langchain_openai import AzureChatOpenAI
58+
from langchain_openai import ChatOpenAI
5159
from langgraph.prebuilt import create_react_agent
60+
5261
# Third-party - Pydantic for validation
5362
from pydantic import BaseModel, Field, create_model, field_validator
63+
from tickets import TicketStatus
5464

5565
# ============================================================================
5666
# DATA MODELS - Pydantic for validation and schema generation
@@ -133,14 +143,12 @@ class AgentResponse(BaseModel):
133143

134144

135145
# ============================================================================
136-
# CONFIGURATION - Azure OpenAI settings (hardcoded except API key)
146+
# CONFIGURATION - OpenAI settings
137147
# ============================================================================
138148

139-
# Azure OpenAI configuration - only API key from environment
140-
AZURE_OPENAI_ENDPOINT = "https://can-i-haz-houze-resource.cognitiveservices.azure.com"
141-
AZURE_OPENAI_API_KEY = os.getenv("AZURE_API_KEY", "")
142-
AZURE_OPENAI_DEPLOYMENT = os.getenv("AZURE_OPENAI_DEPLOYMENT", "gpt-5-mini")
143-
AZURE_OPENAI_API_VERSION = os.getenv("AZURE_OPENAI_API_VERSION", "2025-04-01-preview")
149+
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
150+
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
151+
OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL", "") # optional override
144152

145153
# External MCP server URL for ticket management (hardcoded)
146154
TICKET_MCP_SERVER_URL = "https://yodrrscbpxqnslgugwow.supabase.co/functions/v1/mcp/a7f2b8c4-d3e9-4f1a-b5c6-e8d9f0123456"
@@ -241,7 +249,7 @@ class AgentService:
241249
Agent service handling all LLM agent operations.
242250
243251
Consolidated operations:
244-
- Agent initialization with Azure OpenAI
252+
- Agent initialization with OpenAI
245253
- Tool discovery from @operation registry
246254
- External MCP server tool integration (persistent connection)
247255
- ReAct agent execution with task tools
@@ -253,71 +261,39 @@ class AgentService:
253261

254262
def __init__(self):
255263
"""
256-
Initialize the agent service with Azure OpenAI.
264+
Initialize the agent service with OpenAI.
257265
258266
Validates that required environment variables are set and creates
259267
the LLM client for agent execution.
260268
261269
Raises:
262-
ValueError: If Azure OpenAI configuration is incomplete
270+
ValueError: If OpenAI configuration is incomplete
263271
"""
264272
# Validate configuration
265-
if not AZURE_OPENAI_API_KEY:
273+
if not OPENAI_API_KEY:
266274
raise ValueError(
267-
"Azure OpenAI API key not set. "
268-
"Please set AZURE_API_KEY environment variable."
275+
"OpenAI API key not set. "
276+
"Please set OPENAI_API_KEY environment variable."
269277
)
270278

271-
# Initialize AzureChatOpenAI with hardcoded endpoint
272-
self.llm = AzureChatOpenAI(
273-
azure_endpoint=AZURE_OPENAI_ENDPOINT,
274-
api_key=AZURE_OPENAI_API_KEY, # type: ignore
275-
azure_deployment=AZURE_OPENAI_DEPLOYMENT,
276-
api_version=AZURE_OPENAI_API_VERSION,
279+
# Initialize ChatOpenAI
280+
self.llm = ChatOpenAI(
281+
model=OPENAI_MODEL,
282+
api_key=OPENAI_API_KEY,
283+
base_url=OPENAI_BASE_URL or None,
284+
temperature=0.0,
277285
)
278286

279-
# Load LangGraph tools from operation registry
280-
# These are the actual Python functions decorated with @tool
281-
self.tools = get_langchain_tools()
282-
283-
# Ticket MCP client state (lazy initialization)
287+
# CSV tools only (do not expose operations or external MCP)
288+
self.tools = self._build_csv_tools()
289+
290+
# Ticket MCP client state (unused)
284291
self._ticket_mcp_client: Optional[MCPClient] = None
285292
self._ticket_mcp_tools_loaded = False
286293

287294
async def _ensure_ticket_mcp_connection(self):
288-
"""
289-
Ensure ticket MCP client is connected and tools are loaded.
290-
291-
Opens a persistent connection to the external ticket MCP server and
292-
converts its tools to LangChain format. Called lazily on first
293-
agent run.
294-
"""
295-
if self._ticket_mcp_tools_loaded:
296-
return
297-
298-
try:
299-
# Create and connect ticket MCP client (keep connection open)
300-
client = MCPClient(TICKET_MCP_SERVER_URL)
301-
await client.__aenter__()
302-
self._ticket_mcp_client = client
303-
304-
# Fetch and convert ticket MCP tools
305-
mcp_tools = await client.list_tools()
306-
print(f"\n{'='*60}")
307-
print(f"🎫 TICKET MCP SERVER CONNECTED")
308-
print(f"{'='*60}")
309-
print(f" URL: {TICKET_MCP_SERVER_URL}")
310-
print(f" Tools available: {len(mcp_tools)}")
311-
for tool in mcp_tools:
312-
lc_tool = _mcp_tool_to_langchain(client, tool)
313-
self.tools.append(lc_tool)
314-
print(f" ✓ {tool.name}: {(tool.description or '')[:60]}...")
315-
print(f"{'='*60}\n")
316-
self._ticket_mcp_tools_loaded = True
317-
318-
except Exception as e:
319-
print(f"WARNING: Failed to load ticket MCP tools from {TICKET_MCP_SERVER_URL}: {e}")
320-
# Continue without ticket MCP tools - local tools still work
295+
"""No-op: external MCP tools not exposed."""
296+
return
321297

322298
async def close(self):
323299
"""Close the ticket MCP client connection."""
@@ -327,7 +303,78 @@ async def close(self):
327303
except Exception:
328304
pass
329305
self._ticket_mcp_client = None
330-
306+
307+
def _build_csv_tools(self) -> list[StructuredTool]:
308+
"""Build LangChain tools backed by CSVTicketService."""
309+
import json
310+
service = get_csv_ticket_service()
311+
312+
def _csv_list_tickets(status: str | None = None, assigned_group: str | None = None, has_assignee: bool | None = None) -> str:
313+
try:
314+
status_enum = TicketStatus(status.lower()) if status else None
315+
except Exception:
316+
status_enum = None
317+
tickets = service.list_tickets(status=status_enum, assigned_group=assigned_group, has_assignee=has_assignee)
318+
return json.dumps([t.model_dump() for t in tickets[:200]], default=str)
319+
320+
def _csv_get_ticket(ticket_id: str) -> str:
321+
try:
322+
tid = UUID(ticket_id)
323+
except Exception:
324+
return json.dumps({"error": "invalid ticket id"})
325+
ticket = service.get_ticket(tid)
326+
if not ticket:
327+
return json.dumps({"error": "not found"})
328+
return json.dumps(ticket.model_dump(), default=str)
329+
330+
def _csv_search_tickets(query: str, limit: int = 50) -> str:
331+
q = query.lower()
332+
tickets = service.list_tickets()
333+
matched = []
334+
for t in tickets:
335+
text = " ".join([
336+
t.summary or "",
337+
t.description or "",
338+
t.notes or "",
339+
t.resolution or "",
340+
t.requester_name or "",
341+
t.assigned_group or "",
342+
t.city or "",
343+
]).lower()
344+
if q in text:
345+
matched.append(t.model_dump())
346+
if len(matched) >= limit:
347+
break
348+
return json.dumps(matched, default=str)
349+
350+
def _csv_ticket_fields() -> str:
351+
# Use Ticket model fields as schema
352+
from tickets import Ticket
353+
return json.dumps(list(Ticket.model_fields.keys()))
354+
355+
return [
356+
StructuredTool.from_function(
357+
func=_csv_list_tickets,
358+
name="csv_list_tickets",
359+
description="List tickets from CSV with optional filters: status (new, assigned, in_progress, pending, resolved, closed, cancelled), assigned_group, has_assignee (true/false). Returns JSON array.",
360+
),
361+
StructuredTool.from_function(
362+
func=_csv_get_ticket,
363+
name="csv_get_ticket",
364+
description="Get a ticket by UUID (id). Returns JSON object including notes/resolution.",
365+
),
366+
StructuredTool.from_function(
367+
func=_csv_search_tickets,
368+
name="csv_search_tickets",
369+
description="Search tickets by text across summary, description, notes, resolution, requester, group, city. Returns JSON array.",
370+
),
371+
StructuredTool.from_function(
372+
func=_csv_ticket_fields,
373+
name="csv_ticket_fields",
374+
description="List available ticket fields (schema) as JSON array of field names.",
375+
),
376+
]
377+
331378
async def run_agent(self, request: AgentRequest) -> AgentResponse:
332379
"""
333380
Run a ReAct agent with the given request using LangGraph.
@@ -352,9 +399,6 @@ async def run_agent(self, request: AgentRequest) -> AgentResponse:
352399
Raises:
353400
ValueError: If agent execution fails
354401
"""
355-
# Ensure ticket MCP tools are loaded (lazy initialization)
356-
await self._ensure_ticket_mcp_connection()
357-
358402
try:
359403
# Create ReAct agent with LangGraph tools
360404
# The tools are the actual Python functions with @tool decorator
@@ -363,22 +407,17 @@ async def run_agent(self, request: AgentRequest) -> AgentResponse:
363407
# System message to guide the agent's behavior
364408
system_msg = (
365409
"You are a support ticket management assistant. "
366-
"Your primary role is to help users manage, search, and evaluate support tickets. "
367-
"You MUST use the available tools to perform all actions - NEVER simulate or pretend. "
410+
"Use the CSV tools: csv_list_tickets, csv_search_tickets, csv_get_ticket, csv_ticket_fields. "
411+
"You MUST use the available tools to perform all actions - NEVER simulate or invent data. "
368412
"\n\n"
369413
"TICKET CAPABILITIES:\n"
370-
"- Search and list tickets by status, priority, city, service\n"
371-
"- Get detailed ticket information and work logs\n"
372-
"- Analyze ticket statistics and trends\n"
373-
"- Request modifications to tickets (status, priority, assignee, etc.)\n"
374-
"- Review and approve/reject modification requests\n"
375-
"\n"
376-
"TASK CAPABILITIES:\n"
377-
"- Create, update, delete, and list tasks\n"
378-
"- Get task statistics\n"
414+
"- List tickets with filters (status, assigned_group, has_assignee)\n"
415+
"- Search tickets across summary/description/notes/resolution/requester/group/city\n"
416+
"- Get a ticket by id (UUID) including notes and resolution\n"
417+
"- Inspect available ticket fields (schema)\n"
379418
"\n"
380-
"When users ask about tickets, use the ticket tools (list_tickets, get_ticket, search_tickets, etc.). "
381-
"When users ask about tasks, use task tools (create_task, list_tasks, etc.). "
419+
"When users ask about tickets, call the csv_* tools and summarize results. "
420+
"If a field is not present, state that explicitly. "
382421
"Always confirm actions based on actual tool results."
383422
)
384423

backend/app.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
# Import unified operation system
3131

32-
# Agent service for Azure OpenAI LangGraph agents
32+
# Agent service for OpenAI LangGraph agents
3333
from agents import AgentRequest, AgentResponse, agent_service
3434
from api_decorators import operation
3535

@@ -157,12 +157,12 @@ async def rest_get_stats():
157157

158158

159159
# ============================================================================
160-
# AGENT ENDPOINT - Azure OpenAI LangGraph Agent
160+
# AGENT ENDPOINT - OpenAI LangGraph Agent
161161
# ============================================================================
162162

163163
@app.route("/api/agents/run", methods=["POST"])
164164
async def rest_run_agent():
165-
"""REST wrapper: run AI agent with Azure OpenAI.
165+
"""REST wrapper: run AI agent with OpenAI.
166166
167167
The agent has access to task tools and ticket MCP tools.
168168
"""

backend/test_agents.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Test script for LangGraph agent integration.
44
55
This script tests:
6-
1. Agent service initialization (without Azure OpenAI)
6+
1. Agent service initialization (without OpenAI)
77
2. Operation to LangChain tool conversion
88
3. MCP tool schema generation
99
@@ -66,7 +66,7 @@ def test_agent_import():
6666
print()
6767
except ImportError as e:
6868
print(f" ⚠ Could not import agents module: {e}")
69-
print(" This is expected if Azure OpenAI dependencies are not configured")
69+
print(" This is expected if OpenAI dependencies are not configured")
7070
print()
7171

7272
def test_task_service():
@@ -103,7 +103,7 @@ def main():
103103
print()
104104
print("Next steps:")
105105
print("1. Copy .env.example to .env")
106-
print("2. Configure Azure OpenAI credentials in .env")
106+
print("2. Configure OPENAI_API_KEY in .env")
107107
print("3. Start the server: python app.py")
108108
print("4. Test agent: POST /api/agents/run with {\"prompt\": \"List all tasks\"}")
109109
print()

frontend/src/App.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ import {
1616
tokens,
1717
} from '@fluentui/react-components'
1818
import {
19+
Bot24Regular,
1920
DataHistogram24Regular,
2021
Info24Regular,
2122
Table24Regular,
2223
} from '@fluentui/react-icons'
2324
import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
25+
import AgentChat from './features/agent/AgentChat'
2426
import CSVTicketTable from './features/csvtickets/CSVTicketTable'
2527
import FieldsDocs from './features/fields/FieldsDocs'
2628
import KitchenSink from './features/kitchensink/KitchenSink'
@@ -63,6 +65,7 @@ export default function App() {
6365
{ value: 'csvtickets', label: 'Tickets', icon: <Table24Regular />, path: '/csvtickets', testId: 'tab-csvtickets' },
6466
{ value: 'kitchensink', label: 'Kitchen Sink', icon: <DataHistogram24Regular />, path: '/kitchensink', testId: 'tab-kitchensink' },
6567
{ value: 'fields', label: 'Fields', icon: <Info24Regular />, path: '/fields', testId: 'tab-fields' },
68+
{ value: 'agent', label: 'Agent', icon: <Bot24Regular />, path: '/agent', testId: 'tab-agent' },
6669
]
6770
const activeTab = tabs.find((tab) => location.pathname.startsWith(tab.path))?.value ?? 'csvtickets'
6871

@@ -100,6 +103,7 @@ export default function App() {
100103
<Route path="/csvtickets" element={<CSVTicketTable />} />
101104
<Route path="/kitchensink" element={<KitchenSink />} />
102105
<Route path="/fields" element={<FieldsDocs />} />
106+
<Route path="/agent" element={<AgentChat />} />
103107
<Route path="*" element={<Navigate to="/csvtickets" replace />} />
104108
</Routes>
105109
</main>

0 commit comments

Comments
 (0)