Humanlearning commited on
Commit
e29da2b
·
1 Parent(s): 3847bc0

feat: Implement interactive query and expiry sweep agents with a Gradio UI, add a Hugging Face pre-push hook, and refine .gitignore.

Browse files
.gitignore CHANGED
@@ -3,7 +3,4 @@
3
  __pycache__/
4
  *.pyc
5
  .DS_Store
6
-
7
  *.cpython*
8
- *.pyc
9
- *.cpython-313.pyc
 
3
  __pycache__/
4
  *.pyc
5
  .DS_Store
 
6
  *.cpython*
 
 
githooks/pre-push CHANGED
File without changes
src/credentialwatch_agent/__pycache__/__init__.cpython-310.pyc DELETED
Binary file (186 Bytes)
 
src/credentialwatch_agent/__pycache__/__init__.cpython-313.pyc DELETED
Binary file (196 Bytes)
 
src/credentialwatch_agent/__pycache__/main.cpython-313.pyc DELETED
Binary file (6.09 kB)
 
src/credentialwatch_agent/__pycache__/mcp_client.cpython-310.pyc DELETED
Binary file (3.63 kB)
 
src/credentialwatch_agent/__pycache__/mcp_client.cpython-313.pyc DELETED
Binary file (9.7 kB)
 
src/credentialwatch_agent/agents/__pycache__/common.cpython-310.pyc DELETED
Binary file (1.13 kB)
 
src/credentialwatch_agent/agents/__pycache__/common.cpython-313.pyc DELETED
Binary file (1.49 kB)
 
src/credentialwatch_agent/agents/__pycache__/expiry_sweep.cpython-310.pyc DELETED
Binary file (2.54 kB)
 
src/credentialwatch_agent/agents/__pycache__/expiry_sweep.cpython-313.pyc DELETED
Binary file (4.07 kB)
 
src/credentialwatch_agent/agents/__pycache__/interactive_query.cpython-310.pyc DELETED
Binary file (3.05 kB)
 
src/credentialwatch_agent/agents/__pycache__/interactive_query.cpython-313.pyc DELETED
Binary file (2.6 kB)
 
src/credentialwatch_agent/agents/interactive_query.py CHANGED
@@ -17,7 +17,7 @@ from credentialwatch_agent.agents.common import AgentState
17
  # We can use the prebuilt AgentState or our custom one.
18
  # For simplicity, we'll use a state compatible with ToolNode (requires 'messages').
19
 
20
- def get_interactive_query_graph():
21
  """
22
  Factory function to create the graph with dynamic tools.
23
  """
@@ -57,4 +57,4 @@ def get_interactive_query_graph():
57
  )
58
  workflow.add_edge("tools", "agent")
59
 
60
- return workflow.compile()
 
17
  # We can use the prebuilt AgentState or our custom one.
18
  # For simplicity, we'll use a state compatible with ToolNode (requires 'messages').
19
 
20
+ def get_interactive_query_graph(checkpointer=None):
21
  """
22
  Factory function to create the graph with dynamic tools.
23
  """
 
57
  )
58
  workflow.add_edge("tools", "agent")
59
 
60
+ return workflow.compile(checkpointer=checkpointer)
src/credentialwatch_agent/main.py CHANGED
@@ -9,10 +9,16 @@ load_dotenv()
9
 
10
  from langchain_core.messages import HumanMessage, AIMessage
11
 
 
 
 
12
  # Configure logging for main
13
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
14
  logger = logging.getLogger("credentialwatch_agent")
15
 
 
 
 
16
  from credentialwatch_agent.mcp_client import mcp_client
17
  from credentialwatch_agent.agents.expiry_sweep import expiry_sweep_graph
18
  from credentialwatch_agent.agents.interactive_query import get_interactive_query_graph
@@ -50,31 +56,23 @@ async def run_expiry_sweep(window_days: int = 90) -> Dict[str, Any]:
50
  "errors": final_state.get("errors")
51
  }
52
 
53
- async def run_chat_turn(message: str, history: List[List[str]]) -> str:
54
  """
55
  Runs a turn of the interactive query agent.
 
56
  """
57
- logger.info(f"Starting chat turn with message: {message}")
58
  await mcp_client.connect()
59
- # Convert history to LangChain format
60
- messages = []
61
- for item in history:
62
- if isinstance(item, (list, tuple)) and len(item) >= 2:
63
- human = item[0]
64
- ai = item[1]
65
- messages.append(HumanMessage(content=str(human)))
66
- messages.append(AIMessage(content=str(ai)))
67
- else:
68
- # Fallback for unexpected format
69
- print(f"Warning: Skipping malformed history item: {item}")
70
- messages.append(HumanMessage(content=message))
71
 
72
- initial_state = {"messages": messages}
 
73
 
74
- # Run the graph
75
  logger.info("Invoking interactive_query_graph...")
76
- interactive_query_graph = get_interactive_query_graph()
77
- final_state = await interactive_query_graph.ainvoke(initial_state)
 
 
78
  logger.info("Interactive query graph completed.")
79
 
80
  # Extract the last message
@@ -100,7 +98,11 @@ with gr.Blocks(title="CredentialWatch") as demo:
100
 
101
  with gr.Tab("Interactive Query"):
102
  gr.Markdown("Ask questions about provider credentials, e.g., 'Who has expiring licenses?'")
103
- chat_interface = gr.ChatInterface(fn=run_chat_turn)
 
 
 
 
104
 
105
  with gr.Tab("Expiry Sweep"):
106
  gr.Markdown("Run a batch sweep to check for expiring credentials and create alerts.")
 
9
 
10
  from langchain_core.messages import HumanMessage, AIMessage
11
 
12
+ import uuid
13
+ from langgraph.checkpoint.memory import InMemorySaver
14
+
15
  # Configure logging for main
16
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
17
  logger = logging.getLogger("credentialwatch_agent")
18
 
19
+ # In-memory checkpointer to preserve tool call context within a session
20
+ checkpointer = InMemorySaver()
21
+
22
  from credentialwatch_agent.mcp_client import mcp_client
23
  from credentialwatch_agent.agents.expiry_sweep import expiry_sweep_graph
24
  from credentialwatch_agent.agents.interactive_query import get_interactive_query_graph
 
56
  "errors": final_state.get("errors")
57
  }
58
 
59
+ async def run_chat_turn(message: str, history: List[List[str]], thread_id: str) -> str:
60
  """
61
  Runs a turn of the interactive query agent.
62
+ Uses checkpointer with thread_id to preserve tool call context within a session.
63
  """
64
+ logger.info(f"Starting chat turn with message: {message} (thread_id: {thread_id})")
65
  await mcp_client.connect()
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
+ # Only pass the new message - checkpointer handles full history including tool calls
68
+ initial_state = {"messages": [HumanMessage(content=message)]}
69
 
70
+ # Run the graph with checkpointer
71
  logger.info("Invoking interactive_query_graph...")
72
+ interactive_query_graph = get_interactive_query_graph(checkpointer=checkpointer)
73
+
74
+ config = {"configurable": {"thread_id": thread_id}}
75
+ final_state = await interactive_query_graph.ainvoke(initial_state, config=config)
76
  logger.info("Interactive query graph completed.")
77
 
78
  # Extract the last message
 
98
 
99
  with gr.Tab("Interactive Query"):
100
  gr.Markdown("Ask questions about provider credentials, e.g., 'Who has expiring licenses?'")
101
+ thread_id_state = gr.State(lambda: str(uuid.uuid4()))
102
+ chat_interface = gr.ChatInterface(
103
+ fn=run_chat_turn,
104
+ additional_inputs=[thread_id_state]
105
+ )
106
 
107
  with gr.Tab("Expiry Sweep"):
108
  gr.Markdown("Run a batch sweep to check for expiring credentials and create alerts.")