AdithyaVardan commited on
Commit
7eb57e9
·
1 Parent(s): af72f4d

Integrate tools agent (GitHub/Slack/Notion) into main app under /tools prefix

Browse files
.env CHANGED
@@ -44,6 +44,12 @@ NEO4J_USERNAME=e9b0658c
44
  NEO4J_PASSWORD=IAyPRvHVdtVpHhnlTpEuO069aCK04wQv7S_J7hXcyu8
45
  NEO4J_DATABASE=e9b0658c
46
 
 
 
 
 
 
 
47
  # --- GITHUB ---
48
  GITHUB_TOKEN=
49
  GITHUB_PATH_FILTER=docs/
 
44
  NEO4J_PASSWORD=IAyPRvHVdtVpHhnlTpEuO069aCK04wQv7S_J7hXcyu8
45
  NEO4J_DATABASE=e9b0658c
46
 
47
+ # --- OPENAI (tools agent) ---
48
+ OPENAI_API_KEY=
49
+ GITHUB_REPO=samyuktha2004/Godspeed
50
+ SLACK_BOT_TOKEN=
51
+ NOTION_API_TOKEN=
52
+
53
  # --- GITHUB ---
54
  GITHUB_TOKEN=
55
  GITHUB_PATH_FILTER=docs/
main.py CHANGED
@@ -9,6 +9,7 @@ from graph_store.api import router as graph_router
9
  from graph_store.stream import router as graph_stream_router
10
  from ingestion.api import router as ingestion_router
11
  from src.confluence_agent.router import router as confluence_router
 
12
  from src.file_agent.router import router as file_router
13
  from src.jira_agent.router import router as jira_router
14
 
@@ -38,3 +39,4 @@ app.include_router(graph_stream_router)
38
  app.include_router(jira_router)
39
  app.include_router(confluence_router)
40
  app.include_router(file_router)
 
 
9
  from graph_store.stream import router as graph_stream_router
10
  from ingestion.api import router as ingestion_router
11
  from src.confluence_agent.router import router as confluence_router
12
+ from toolsforgitnotionslack.router import router as tools_router
13
  from src.file_agent.router import router as file_router
14
  from src.jira_agent.router import router as jira_router
15
 
 
39
  app.include_router(jira_router)
40
  app.include_router(confluence_router)
41
  app.include_router(file_router)
42
+ app.include_router(tools_router)
requirements.txt CHANGED
@@ -65,6 +65,9 @@ python-multipart==0.0.27
65
  # Graph store
66
  neo4j==5.27.0
67
 
 
 
 
68
  # Existing project dependencies
69
  beautifulsoup4==4.12.2
70
  lxml==5.3.0
 
65
  # Graph store
66
  neo4j==5.27.0
67
 
68
+ # Tools agent (GitHub / Slack / Notion via GPT-4o-mini)
69
+ openai>=1.30.0
70
+
71
  # Existing project dependencies
72
  beautifulsoup4==4.12.2
73
  lxml==5.3.0
toolsforgitnotionslack/__init__.py ADDED
File without changes
toolsforgitnotionslack/router.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import json
5
+ import os
6
+ import time
7
+ import uuid
8
+ from typing import Optional
9
+
10
+ from fastapi import APIRouter, HTTPException
11
+ from pydantic import BaseModel
12
+
13
+ from toolsforgitnotionslack.agent.cache import Cache
14
+ from toolsforgitnotionslack.agent.planner import build_system_prompt
15
+ from toolsforgitnotionslack.tools.github_tools import GITHUB_TOOLS, GITHUB_TOOL_FNS
16
+ from toolsforgitnotionslack.tools.notion_tools import NOTION_TOOLS, NOTION_TOOL_FNS
17
+ from toolsforgitnotionslack.tools.slack_tools import SLACK_TOOLS, SLACK_TOOL_FNS
18
+
19
+ router = APIRouter(prefix="/tools", tags=["tools"])
20
+
21
+ MODEL = "gpt-4o-mini"
22
+ GITHUB_REPO = os.environ.get("GITHUB_REPO", "")
23
+
24
+ ALL_TOOLS = GITHUB_TOOLS + SLACK_TOOLS + NOTION_TOOLS
25
+ ALL_FNS = {**GITHUB_TOOL_FNS, **SLACK_TOOL_FNS, **NOTION_TOOL_FNS}
26
+
27
+ _tool_cache = Cache(ttl_seconds=300)
28
+ _sessions: dict[str, list[dict]] = {}
29
+
30
+
31
+ def _get_client():
32
+ from openai import AsyncOpenAI
33
+ return AsyncOpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
34
+
35
+
36
+ class ChatRequest(BaseModel):
37
+ message: str
38
+ session_id: Optional[str] = None
39
+
40
+
41
+ class ToolCall(BaseModel):
42
+ name: str
43
+ args: dict
44
+ result: str
45
+ cached: bool
46
+
47
+
48
+ class ChatResponse(BaseModel):
49
+ answer: str
50
+ session_id: str
51
+ elapsed_seconds: float
52
+ tool_calls: list[ToolCall]
53
+
54
+
55
+ class ClearResponse(BaseModel):
56
+ cleared: bool
57
+ session_id: str
58
+
59
+
60
+ async def _dispatch(name: str, args: dict) -> tuple[str, bool]:
61
+ key = f"{name}:{json.dumps(args, sort_keys=True)}"
62
+ cached = _tool_cache.get(key)
63
+ if cached:
64
+ return cached, True
65
+ fn = ALL_FNS.get(name)
66
+ result = await fn(**args) if fn else f"Unknown tool: {name}"
67
+ _tool_cache.set(key, result)
68
+ return result, False
69
+
70
+
71
+ async def _run_agent(history: list[dict]) -> tuple[str, list[ToolCall]]:
72
+ client = _get_client()
73
+ system = build_system_prompt(GITHUB_REPO)
74
+ messages = [{"role": "system", "content": system}] + history
75
+ tool_log: list[ToolCall] = []
76
+
77
+ for _ in range(14):
78
+ response = await client.chat.completions.create(
79
+ model=MODEL,
80
+ messages=messages,
81
+ tools=ALL_TOOLS,
82
+ tool_choice="auto",
83
+ )
84
+ msg = response.choices[0].message
85
+ tool_calls = msg.tool_calls or []
86
+
87
+ if not tool_calls:
88
+ return msg.content or "", tool_log
89
+
90
+ messages.append(msg)
91
+
92
+ names_and_args = [
93
+ (tc.function.name, json.loads(tc.function.arguments))
94
+ for tc in tool_calls
95
+ ]
96
+
97
+ results = await asyncio.gather(*[
98
+ _dispatch(name, args) for name, args in names_and_args
99
+ ])
100
+
101
+ for tc, (name, args), (result, was_cached) in zip(tool_calls, names_and_args, results):
102
+ tool_log.append(ToolCall(name=name, args=args, result=result[:500], cached=was_cached))
103
+ messages.append({"role": "tool", "tool_call_id": tc.id, "content": result})
104
+
105
+ return "Reached reasoning limit. Try a more specific question.", tool_log
106
+
107
+
108
+ @router.post("/chat", response_model=ChatResponse)
109
+ async def chat(req: ChatRequest):
110
+ if not os.environ.get("OPENAI_API_KEY"):
111
+ raise HTTPException(status_code=503, detail="OPENAI_API_KEY not configured")
112
+
113
+ session_id = req.session_id or str(uuid.uuid4())
114
+ history = _sessions.setdefault(session_id, [])
115
+ history.append({"role": "user", "content": req.message})
116
+
117
+ t0 = time.monotonic()
118
+ try:
119
+ answer, tool_log = await _run_agent(history)
120
+ except Exception as e:
121
+ raise HTTPException(status_code=500, detail=str(e))
122
+
123
+ history.append({"role": "assistant", "content": answer})
124
+ if len(history) > 60:
125
+ _sessions[session_id] = history[-60:]
126
+
127
+ return ChatResponse(
128
+ answer=answer,
129
+ session_id=session_id,
130
+ elapsed_seconds=round(time.monotonic() - t0, 2),
131
+ tool_calls=tool_log,
132
+ )
133
+
134
+
135
+ @router.delete("/chat", response_model=ClearResponse)
136
+ async def clear_session(session_id: str):
137
+ existed = session_id in _sessions
138
+ _sessions.pop(session_id, None)
139
+ _tool_cache.clear()
140
+ return ClearResponse(cleared=existed, session_id=session_id)
141
+
142
+
143
+ @router.get("/health")
144
+ async def tools_health():
145
+ return {
146
+ "status": "ok",
147
+ "model": MODEL,
148
+ "github": bool(os.environ.get("GITHUB_TOKEN")),
149
+ "slack": bool(os.environ.get("SLACK_BOT_TOKEN")),
150
+ "notion": bool(os.environ.get("NOTION_API_TOKEN")),
151
+ "active_sessions": len(_sessions),
152
+ }