SENODROOM commited on
Commit
aff9c7f
·
1 Parent(s): 2176bf7

fixed much

Browse files
.env.example CHANGED
@@ -1,4 +1,9 @@
1
  GITHUB_TOKEN=
 
 
 
 
2
  LANGSMITH_API_KEY=
3
  APP_HOST=0.0.0.0
4
  APP_PORT=8000
 
 
1
  GITHUB_TOKEN=
2
+ GITHUB_API_URL=https://api.github.com/graphql
3
+ GITHUB_REST_API_URL=https://api.github.com
4
+ GITHUB_PUBLIC_REPO_LIMIT=30
5
+ GITHUB_FETCH_COMMIT_COUNTS=true
6
  LANGSMITH_API_KEY=
7
  APP_HOST=0.0.0.0
8
  APP_PORT=8000
9
+ SCORING_BACKEND=heuristic
README.md CHANGED
@@ -1,13 +1,57 @@
1
- # AI Pipeline Research & GraphQL Exploration Prototype
2
 
3
- This repository contains the local prototype deliverables for the AI Pipeline research task, focusing on local CodeBERT embedding generation and GitHub GraphQL schema validation.
4
 
5
- ---
6
 
7
- ## 🛠️ Local Environment & Pipeline Setup
8
 
9
- 1. **Virtual Environment Configuration:**
10
- Managed dependencies using a isolated local environment:
11
- ```cmd
12
- python -m venv venv
13
- venv\Scripts\activate
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GitHub Profile AI Reviewer
2
 
3
+ FastAPI service that rates a public GitHub user profile out of 100.
4
 
5
+ The API accepts a GitHub username and returns a `rating_score`, developer level, language breakdown, contribution signals, streak data, and model metadata.
6
 
7
+ ## Token Behavior
8
 
9
+ `GITHUB_TOKEN` is optional.
10
+
11
+ When `GITHUB_TOKEN` is set, the service uses GitHub GraphQL for richer profile and contribution data. When it is empty, users can still rate any public GitHub username through GitHub's public REST API. Public REST mode is limited by GitHub's unauthenticated rate limits and cannot see private contribution data.
12
+
13
+ ## Environment
14
+
15
+ Copy `.env.example` to `.env` and adjust only what you need:
16
+
17
+ ```env
18
+ GITHUB_TOKEN=
19
+ GITHUB_API_URL=https://api.github.com/graphql
20
+ GITHUB_REST_API_URL=https://api.github.com
21
+ GITHUB_PUBLIC_REPO_LIMIT=30
22
+ GITHUB_FETCH_COMMIT_COUNTS=true
23
+ APP_HOST=0.0.0.0
24
+ APP_PORT=8000
25
+ SCORING_BACKEND=heuristic
26
+ ```
27
+
28
+ Keep `SCORING_BACKEND=heuristic` unless you have trained model weights. The neural backend is experimental.
29
+
30
+ ## Run Locally
31
+
32
+ ```bash
33
+ pip install -r requirements.txt
34
+ uvicorn app.main:app --host 0.0.0.0 --port 8000
35
+ ```
36
+
37
+ Request a rating:
38
+
39
+ ```bash
40
+ curl -X POST http://localhost:8000/analyze \
41
+ -H "Content-Type: application/json" \
42
+ -d '{"username":"octocat"}'
43
+ ```
44
+
45
+ ## Deploy With Docker
46
+
47
+ ```bash
48
+ docker compose up --build -d
49
+ ```
50
+
51
+ The API will be available on `http://localhost:${APP_PORT:-8000}`.
52
+
53
+ ## Response Fields
54
+
55
+ - `rating_score`: main score from 0 to 100.
56
+ - `hiring_readiness_score`: compatibility alias for the same 0 to 100 score.
57
+ - `model_info.data_source`: `graphql` when a server token is configured, otherwise `rest-public`.
app/ai/scoring.py CHANGED
@@ -2,6 +2,8 @@ from __future__ import annotations
2
 
3
  from dataclasses import dataclass
4
 
 
 
5
  try:
6
  import torch
7
  import torch.nn as nn
@@ -49,7 +51,7 @@ class ScoringEngine:
49
  self._model = DeveloperScoringModel(input_dim=input_dim)
50
 
51
  def infer(self, embedding: list[float], activity_score: float, consistency_score: float) -> ScoreOutput:
52
- if torch is None or nn is None or not isinstance(self._model, nn.Module):
53
  return self._heuristic(activity_score, consistency_score)
54
 
55
  x = torch.tensor([embedding], dtype=torch.float32)
 
2
 
3
  from dataclasses import dataclass
4
 
5
+ from app.core.config import settings
6
+
7
  try:
8
  import torch
9
  import torch.nn as nn
 
51
  self._model = DeveloperScoringModel(input_dim=input_dim)
52
 
53
  def infer(self, embedding: list[float], activity_score: float, consistency_score: float) -> ScoreOutput:
54
+ if settings.scoring_backend.lower() != "neural" or torch is None or nn is None or not isinstance(self._model, nn.Module):
55
  return self._heuristic(activity_score, consistency_score)
56
 
57
  x = torch.tensor([embedding], dtype=torch.float32)
app/clients/github_graphql.py CHANGED
@@ -1,8 +1,7 @@
1
  from __future__ import annotations
2
 
 
3
  from typing import Any
4
- import json
5
- from pathlib import Path
6
 
7
  import httpx
8
 
@@ -14,21 +13,38 @@ class GitHubAPIError(RuntimeError):
14
  pass
15
 
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  class GitHubGraphQLClient:
18
  async def analyze_user(self, username: str) -> dict[str, Any]:
19
- # If token missing, use local sample so app can run offline for testing.
20
  if not settings.github_token:
21
- sample_path = Path(__file__).with_name("sample_user.json")
22
- if sample_path.exists():
23
- with sample_path.open("r", encoding="utf-8") as fh:
24
- return json.load(fh)
25
- raise GitHubAPIError("Missing GITHUB_TOKEN in environment and no sample_user.json available.")
26
 
27
- headers = {"Authorization": f"Bearer {settings.github_token}", "Accept": "application/vnd.github+json"}
28
  payload = {"query": ANALYZE_PROFILE_QUERY, "variables": {"username": username}}
29
 
30
  async with httpx.AsyncClient(timeout=settings.request_timeout_seconds) as client:
31
- response = await client.post(settings.github_api_url, headers=headers, json=payload)
32
 
33
  if response.status_code >= 400:
34
  raise GitHubAPIError(f"GitHub API status={response.status_code}: {response.text}")
@@ -39,4 +55,138 @@ class GitHubGraphQLClient:
39
  if data.get("data", {}).get("user") is None:
40
  raise GitHubAPIError(f"GitHub user '{username}' not found.")
41
 
 
42
  return data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from __future__ import annotations
2
 
3
+ from datetime import UTC, datetime, timedelta
4
  from typing import Any
 
 
5
 
6
  import httpx
7
 
 
13
  pass
14
 
15
 
16
+ def _headers() -> dict[str, str]:
17
+ headers = {"Accept": "application/vnd.github+json"}
18
+ if settings.github_token:
19
+ headers["Authorization"] = f"Bearer {settings.github_token}"
20
+ return headers
21
+
22
+
23
+ def _extract_total_count(response: httpx.Response, fallback: int) -> int:
24
+ link = response.headers.get("Link", "")
25
+ for part in link.split(","):
26
+ if 'rel="last"' not in part:
27
+ continue
28
+ marker = "page="
29
+ if marker not in part:
30
+ continue
31
+ value = part.split(marker, 1)[1].split("&", 1)[0].split(">", 1)[0]
32
+ try:
33
+ return int(value)
34
+ except ValueError:
35
+ return fallback
36
+ return fallback
37
+
38
+
39
  class GitHubGraphQLClient:
40
  async def analyze_user(self, username: str) -> dict[str, Any]:
 
41
  if not settings.github_token:
42
+ return await self._analyze_public_user(username)
 
 
 
 
43
 
 
44
  payload = {"query": ANALYZE_PROFILE_QUERY, "variables": {"username": username}}
45
 
46
  async with httpx.AsyncClient(timeout=settings.request_timeout_seconds) as client:
47
+ response = await client.post(settings.github_api_url, headers=_headers(), json=payload)
48
 
49
  if response.status_code >= 400:
50
  raise GitHubAPIError(f"GitHub API status={response.status_code}: {response.text}")
 
55
  if data.get("data", {}).get("user") is None:
56
  raise GitHubAPIError(f"GitHub user '{username}' not found.")
57
 
58
+ data["source"] = "graphql"
59
  return data
60
+
61
+ async def _analyze_public_user(self, username: str) -> dict[str, Any]:
62
+ base_url = settings.github_rest_api_url.rstrip("/")
63
+ async with httpx.AsyncClient(base_url=base_url, timeout=settings.request_timeout_seconds, headers=_headers()) as client:
64
+ user_response = await client.get(f"/users/{username}")
65
+ if user_response.status_code == 404:
66
+ raise GitHubAPIError(f"GitHub user '{username}' not found.")
67
+ if user_response.status_code >= 400:
68
+ raise GitHubAPIError(f"GitHub API status={user_response.status_code}: {user_response.text}")
69
+
70
+ repos_response = await client.get(
71
+ f"/users/{username}/repos",
72
+ params={
73
+ "per_page": min(max(settings.github_public_repo_limit, 1), 100),
74
+ "sort": "updated",
75
+ "direction": "desc",
76
+ "type": "owner",
77
+ },
78
+ )
79
+ if repos_response.status_code >= 400:
80
+ raise GitHubAPIError(f"GitHub API status={repos_response.status_code}: {repos_response.text}")
81
+
82
+ events_response = await client.get(f"/users/{username}/events/public", params={"per_page": 100})
83
+ events = events_response.json() if events_response.status_code < 400 else []
84
+
85
+ user = user_response.json()
86
+ repos = repos_response.json()
87
+ repo_nodes = await self._build_public_repositories(client, username, repos)
88
+ weeks = _weeks_from_events(events)
89
+
90
+ return {
91
+ "source": "rest-public",
92
+ "data": {
93
+ "user": {
94
+ "login": user.get("login", username),
95
+ "name": user.get("name"),
96
+ "bio": user.get("bio"),
97
+ "createdAt": user.get("created_at"),
98
+ "followers": {"totalCount": int(user.get("followers") or 0)},
99
+ "contributionsCollection": {
100
+ "totalCommitContributions": sum(_event_commit_count(event) for event in events),
101
+ "totalPullRequestContributions": sum(1 for event in events if event.get("type") == "PullRequestEvent"),
102
+ "contributionCalendar": {
103
+ "totalContributions": len(events),
104
+ "weeks": weeks,
105
+ },
106
+ },
107
+ "repositories": {"nodes": repo_nodes},
108
+ "pullRequests": {"totalCount": sum(1 for event in events if event.get("type") == "PullRequestEvent")},
109
+ }
110
+ },
111
+ }
112
+
113
+ async def _build_public_repositories(self, client: httpx.AsyncClient, username: str, repos: list[dict[str, Any]]) -> list[dict[str, Any]]:
114
+ nodes: list[dict[str, Any]] = []
115
+ for repo in repos:
116
+ owner = repo.get("owner", {}).get("login") or username
117
+ name = repo.get("name")
118
+ if not name:
119
+ continue
120
+
121
+ languages = await self._fetch_repo_languages(client, owner, name)
122
+ total_commits = await self._fetch_commit_count(client, owner, name, repo.get("default_branch"))
123
+ nodes.append(
124
+ {
125
+ "name": name,
126
+ "nameWithOwner": repo.get("full_name") or f"{owner}/{name}",
127
+ "description": repo.get("description"),
128
+ "stargazerCount": int(repo.get("stargazers_count") or 0),
129
+ "forkCount": int(repo.get("forks_count") or 0),
130
+ "primaryLanguage": {"name": repo.get("language")} if repo.get("language") else None,
131
+ "languages": {
132
+ "edges": [
133
+ {"size": int(size), "node": {"name": language}}
134
+ for language, size in sorted(languages.items(), key=lambda item: item[1], reverse=True)[:5]
135
+ ]
136
+ },
137
+ "defaultBranchRef": {
138
+ "target": {
139
+ "history": {"totalCount": total_commits},
140
+ }
141
+ },
142
+ }
143
+ )
144
+ return nodes
145
+
146
+ async def _fetch_repo_languages(self, client: httpx.AsyncClient, owner: str, repo: str) -> dict[str, int]:
147
+ response = await client.get(f"/repos/{owner}/{repo}/languages")
148
+ if response.status_code >= 400:
149
+ return {}
150
+ return {str(language): int(size) for language, size in response.json().items()}
151
+
152
+ async def _fetch_commit_count(self, client: httpx.AsyncClient, owner: str, repo: str, branch: str | None) -> int:
153
+ if not settings.github_fetch_commit_counts:
154
+ return 0
155
+ params: dict[str, Any] = {"per_page": 1}
156
+ if branch:
157
+ params["sha"] = branch
158
+ response = await client.get(f"/repos/{owner}/{repo}/commits", params=params)
159
+ if response.status_code >= 400:
160
+ return 0
161
+ return _extract_total_count(response, len(response.json()))
162
+
163
+
164
+ def _event_commit_count(event: dict[str, Any]) -> int:
165
+ if event.get("type") != "PushEvent":
166
+ return 0
167
+ return len(event.get("payload", {}).get("commits", []))
168
+
169
+
170
+ def _weeks_from_events(events: list[dict[str, Any]]) -> list[dict[str, Any]]:
171
+ today = datetime.now(UTC).date()
172
+ start = today - timedelta(days=55)
173
+ counts: dict[str, int] = {}
174
+ for event in events:
175
+ created_at = event.get("created_at")
176
+ if not created_at:
177
+ continue
178
+ try:
179
+ day = datetime.fromisoformat(created_at.replace("Z", "+00:00")).date()
180
+ except ValueError:
181
+ continue
182
+ if day >= start:
183
+ counts[day.isoformat()] = counts.get(day.isoformat(), 0) + 1
184
+
185
+ weeks: list[dict[str, Any]] = []
186
+ for offset in range(0, 56, 7):
187
+ days = []
188
+ for day_offset in range(7):
189
+ date = start + timedelta(days=offset + day_offset)
190
+ days.append({"date": date.isoformat(), "contributionCount": counts.get(date.isoformat(), 0)})
191
+ weeks.append({"contributionDays": days})
192
+ return weeks
app/core/config.py CHANGED
@@ -12,9 +12,13 @@ class Settings(BaseSettings):
12
 
13
  github_token: str = Field(default="", alias="GITHUB_TOKEN")
14
  github_api_url: str = "https://api.github.com/graphql"
 
 
 
15
 
16
  codebert_model: str = "microsoft/codebert-base"
17
  embedding_dim: int = 768
 
18
  request_timeout_seconds: float = 30.0
19
 
20
  model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore", populate_by_name=True)
 
12
 
13
  github_token: str = Field(default="", alias="GITHUB_TOKEN")
14
  github_api_url: str = "https://api.github.com/graphql"
15
+ github_rest_api_url: str = "https://api.github.com"
16
+ github_public_repo_limit: int = 30
17
+ github_fetch_commit_counts: bool = True
18
 
19
  codebert_model: str = "microsoft/codebert-base"
20
  embedding_dim: int = 768
21
+ scoring_backend: str = "heuristic"
22
  request_timeout_seconds: float = 30.0
23
 
24
  model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore", populate_by_name=True)
app/graph/workflow.py CHANGED
@@ -6,6 +6,7 @@ from typing import Any
6
  from app.ai.embeddings import CodeEmbeddingService
7
  from app.ai.scoring import ScoringEngine
8
  from app.clients.github_graphql import GitHubGraphQLClient
 
9
  from app.clients.streak import compute_streak_from_calendar
10
  from app.graph.state import ProfileState
11
 
@@ -76,6 +77,7 @@ class AnalyzerWorkflow:
76
 
77
  state["final_report"] = {
78
  "username": username,
 
79
  "developer_level": scored.level,
80
  "confidence": scored.confidence,
81
  "strongest_language": strongest_language,
@@ -93,9 +95,10 @@ class AnalyzerWorkflow:
93
  },
94
  "model_info": {
95
  "embedding_model": "microsoft/codebert-base",
96
- "scoring_model": "DeveloperScoringModel",
97
  "embedding_dim": self._embedder.embedding_dim,
98
  "embedding_backend": "transformers" if self._embedder.ready else "deterministic-fallback",
 
99
  },
100
  }
101
  return state
 
6
  from app.ai.embeddings import CodeEmbeddingService
7
  from app.ai.scoring import ScoringEngine
8
  from app.clients.github_graphql import GitHubGraphQLClient
9
+ from app.core.config import settings
10
  from app.clients.streak import compute_streak_from_calendar
11
  from app.graph.state import ProfileState
12
 
 
77
 
78
  state["final_report"] = {
79
  "username": username,
80
+ "rating_score": scored.hiring_score,
81
  "developer_level": scored.level,
82
  "confidence": scored.confidence,
83
  "strongest_language": strongest_language,
 
95
  },
96
  "model_info": {
97
  "embedding_model": "microsoft/codebert-base",
98
+ "scoring_model": settings.scoring_backend,
99
  "embedding_dim": self._embedder.embedding_dim,
100
  "embedding_backend": "transformers" if self._embedder.ready else "deterministic-fallback",
101
+ "data_source": raw.get("source", "graphql"),
102
  },
103
  }
104
  return state
app/schemas.py CHANGED
@@ -22,12 +22,13 @@ class StreakData(BaseModel):
22
 
23
  class AnalyzeResponse(BaseModel):
24
  username: str
 
25
  developer_level: str
26
  confidence: float
27
  strongest_language: str
28
  language_breakdown: dict[str, int]
29
- hiring_readiness_score: int
30
- consistency_score: int
31
  graphql_signals: GraphQLSignals
32
  streak_data: StreakData
33
  model_info: dict[str, Any]
 
22
 
23
  class AnalyzeResponse(BaseModel):
24
  username: str
25
+ rating_score: int = Field(ge=0, le=100)
26
  developer_level: str
27
  confidence: float
28
  strongest_language: str
29
  language_breakdown: dict[str, int]
30
+ hiring_readiness_score: int = Field(ge=0, le=100)
31
+ consistency_score: int = Field(ge=0, le=100)
32
  graphql_signals: GraphQLSignals
33
  streak_data: StreakData
34
  model_info: dict[str, Any]
backend/graph/nodes/.gitkeep DELETED
@@ -1 +0,0 @@
1
-
 
 
backend/graph/nodes/github_node.py CHANGED
@@ -1,84 +1,87 @@
1
- import requests
2
-
3
- def github_node(state: dict) -> dict:
4
- """
5
- LangGraph Node to fetch developer profile metrics from GitHub GraphQL API.
6
- Accepts state containing 'username' and updates state with raw profile insights.
7
- """
8
- # 1. State se input username nikalna
9
- username = state.get("username", "octocat")
10
- print(f" [GitHub Node] Executing GraphQL query for user: {username}")
11
-
12
- # 2. Configuration & Query Structure
13
- GITHUB_TOKEN = state.get("github_token", "YOUR_GITHUB_TOKEN_HERE")
14
- url = "https://api.github.com/graphql"
15
- headers = {"Authorization": f"Bearer {GITHUB_TOKEN}"}
16
-
17
- query = """
18
- query($username: String!) {
19
- user(login: $username) {
20
- name
21
- bio
22
- repositories(first: 10, orderBy: {field: STARGAZERS, direction: DESC}) {
23
- nodes {
24
- name
25
- stargazerCount
26
- primaryLanguage {
27
- name
28
- }
29
- }
30
- }
31
- contributionsCollection {
32
- contributionCalendar {
33
- totalContributions
34
- }
35
- }
36
- }
37
- }
38
- """
39
- variables = {"username": username}
40
-
41
- # 3. Execution Logic
42
- try:
43
- response = requests.post(url, json={'query': query, 'variables': variables}, headers=headers)
44
-
45
- if response.status_code == 200:
46
- raw_data = response.json()
47
- # State update karna custom key ke sath
48
- state["github_raw_data"] = raw_data.get("data", {})
49
- state["github_status"] = "SUCCESS"
50
- print(" [GitHub Node] Query executed successfully.")
51
- elif response.status_code == 401:
52
- # Structuring dynamic mock fallback for initial pipeline validation
53
- state["github_raw_data"] = {
54
- "user": {
55
- "name": f"Mock {username.capitalize()}",
56
- "bio": "Automated pipeline validation user context.",
57
- "repositories": {
58
- "nodes": [
59
- {"name": "ai-pipeline-core", "stargazerCount": 42, "primaryLanguage": {"name": "Python"}}
60
- ]
61
- },
62
- "contributionsCollection": {"contributionCalendar": {"totalContributions": 150}}
63
- }
64
- }
65
- state["github_status"] = "UNAUTHORIZED_FALLBACK_VALIDATION_PASSED"
66
- print("[GitHub Node] Unauthorized code 401: Injected validated mock structure for LangGraph system continuation.")
67
- else:
68
- state["github_status"] = f"ERROR_{response.status_code}"
69
- state["github_raw_data"] = {}
70
-
71
- except Exception as e:
72
- state["github_status"] = f"CONNECTION_FAILED: {str(e)}"
73
- state["github_raw_data"] = {}
74
-
75
- # 4. Mandatory LangGraph output format: Return the updated state
76
- return state
77
-
78
-
79
- # ---- Dynamic Dummy Execution Box for Verification ----
80
- if __name__ == "__main__":
81
- initial_state = {"username": "test_user"}
82
- print(" Testing GitHub Node locally with dummy state input...")
83
- final_state = github_node(initial_state)
84
- print(f" Final State Output Keys: {list(final_state.keys())}\n")
 
 
 
 
1
+ import os
2
+ import requests
3
+
4
+ def github_node(state: dict) -> dict:
5
+ """
6
+ LangGraph Node to fetch developer profile metrics from GitHub GraphQL API.
7
+ Accepts state containing 'username' and updates state with raw profile insights.
8
+ """
9
+ # 1. State se input username nikalna
10
+ username = state.get("username", "octocat")
11
+ print(f" [GitHub Node] Executing GraphQL query for user: {username}")
12
+
13
+ # 2. Configuration & Query Structure
14
+ github_token = state.get("github_token") or os.getenv("GITHUB_TOKEN", "")
15
+ url = os.getenv("GITHUB_API_URL", "https://api.github.com/graphql")
16
+ headers = {"Accept": "application/vnd.github+json"}
17
+ if github_token:
18
+ headers["Authorization"] = f"Bearer {github_token}"
19
+
20
+ query = """
21
+ query($username: String!) {
22
+ user(login: $username) {
23
+ name
24
+ bio
25
+ repositories(first: 10, orderBy: {field: STARGAZERS, direction: DESC}) {
26
+ nodes {
27
+ name
28
+ stargazerCount
29
+ primaryLanguage {
30
+ name
31
+ }
32
+ }
33
+ }
34
+ contributionsCollection {
35
+ contributionCalendar {
36
+ totalContributions
37
+ }
38
+ }
39
+ }
40
+ }
41
+ """
42
+ variables = {"username": username}
43
+
44
+ # 3. Execution Logic
45
+ try:
46
+ response = requests.post(url, json={'query': query, 'variables': variables}, headers=headers)
47
+
48
+ if response.status_code == 200:
49
+ raw_data = response.json()
50
+ # State update karna custom key ke sath
51
+ state["github_raw_data"] = raw_data.get("data", {})
52
+ state["github_status"] = "SUCCESS"
53
+ print(" [GitHub Node] Query executed successfully.")
54
+ elif response.status_code == 401:
55
+ # Structuring dynamic mock fallback for initial pipeline validation
56
+ state["github_raw_data"] = {
57
+ "user": {
58
+ "name": f"Mock {username.capitalize()}",
59
+ "bio": "Automated pipeline validation user context.",
60
+ "repositories": {
61
+ "nodes": [
62
+ {"name": "ai-pipeline-core", "stargazerCount": 42, "primaryLanguage": {"name": "Python"}}
63
+ ]
64
+ },
65
+ "contributionsCollection": {"contributionCalendar": {"totalContributions": 150}}
66
+ }
67
+ }
68
+ state["github_status"] = "UNAUTHORIZED_FALLBACK_VALIDATION_PASSED"
69
+ print("[GitHub Node] Unauthorized code 401: Injected validated mock structure for LangGraph system continuation.")
70
+ else:
71
+ state["github_status"] = f"ERROR_{response.status_code}"
72
+ state["github_raw_data"] = {}
73
+
74
+ except Exception as e:
75
+ state["github_status"] = f"CONNECTION_FAILED: {str(e)}"
76
+ state["github_raw_data"] = {}
77
+
78
+ # 4. Mandatory LangGraph output format: Return the updated state
79
+ return state
80
+
81
+
82
+ # ---- Dynamic Dummy Execution Box for Verification ----
83
+ if __name__ == "__main__":
84
+ initial_state = {"username": "test_user"}
85
+ print(" Testing GitHub Node locally with dummy state input...")
86
+ final_state = github_node(initial_state)
87
+ print(f" Final State Output Keys: {list(final_state.keys())}\n")
docker-compose.yml CHANGED
@@ -3,17 +3,19 @@ services:
3
  api:
4
  build: .
5
  ports:
6
- - "8000:8000"
7
  environment:
 
 
8
  - GITHUB_TOKEN=${GITHUB_TOKEN}
 
 
 
 
9
  - LANGSMITH_API_KEY=${LANGSMITH_API_KEY}
 
10
  volumes:
11
  - hf_cache:/root/.cache/huggingface
12
- deploy:
13
- resources:
14
- reservations:
15
- devices:
16
- - capabilities: [gpu]
17
  redis:
18
  image: redis:7-alpine
19
  ports:
 
3
  api:
4
  build: .
5
  ports:
6
+ - "${APP_PORT:-8000}:8000"
7
  environment:
8
+ - APP_HOST=0.0.0.0
9
+ - APP_PORT=8000
10
  - GITHUB_TOKEN=${GITHUB_TOKEN}
11
+ - GITHUB_API_URL=${GITHUB_API_URL:-https://api.github.com/graphql}
12
+ - GITHUB_REST_API_URL=${GITHUB_REST_API_URL:-https://api.github.com}
13
+ - GITHUB_PUBLIC_REPO_LIMIT=${GITHUB_PUBLIC_REPO_LIMIT:-30}
14
+ - GITHUB_FETCH_COMMIT_COUNTS=${GITHUB_FETCH_COMMIT_COUNTS:-true}
15
  - LANGSMITH_API_KEY=${LANGSMITH_API_KEY}
16
+ - SCORING_BACKEND=${SCORING_BACKEND:-heuristic}
17
  volumes:
18
  - hf_cache:/root/.cache/huggingface
 
 
 
 
 
19
  redis:
20
  image: redis:7-alpine
21
  ports:
github_api.py CHANGED
@@ -1,55 +1,57 @@
1
- import requests
2
- import json
3
-
4
- # 1. GitHub Token and Configuration
5
- GITHUB_TOKEN = "YOUR_GITHUB_TOKEN_HERE"
6
- USERNAME = "octocat"
7
-
8
- url = "https://api.github.com/graphql"
9
- headers = {"Authorization": f"Bearer {GITHUB_TOKEN}"}
10
-
11
-
12
- query = """
13
- query($username: String!) {
14
- user(login: $username) {
15
- name
16
- bio
17
- repositories(first: 10, orderBy: {field: STARGAZERS, direction: DESC}) {
18
- nodes {
19
- name
20
- stargazerCount
21
- primaryLanguage {
22
- name
23
- }
24
- }
25
- }
26
- contributionsCollection {
27
- contributionCalendar {
28
- totalContributions
29
- }
30
- }
31
- }
32
- }
33
- """
34
-
35
- variables = {"username": USERNAME}
36
-
37
- print(f"Fetching GitHub data for user: {USERNAME}...")
38
-
39
- # 3. API Request
40
- try:
41
- response = requests.post(url, json={'query': query, 'variables': variables}, headers=headers)
42
-
43
- if response.status_code == 200:
44
- data = response.json()
45
- print("\nGraphQL Query Success!")
46
- print(json.dumps(data, indent=2))
47
- elif response.status_code == 401:
48
- print("\nAPI Error: Unauthorized. (Token missing ya invalid hai, which is expected right now!)")
49
- print(" Schema validation checker success! Query structure is 100% correct.")
50
- else:
51
- print(f"\nAPI Error: Status Code {response.status_code}")
52
- print(response.text)
53
-
54
- except Exception as e:
55
- print(f"Connection Error: {str(e)}")
 
 
 
1
+ import os
2
+ import requests
3
+ import json
4
+
5
+ GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "")
6
+ USERNAME = os.getenv("GITHUB_USERNAME", "octocat")
7
+
8
+ url = os.getenv("GITHUB_API_URL", "https://api.github.com/graphql")
9
+ headers = {"Accept": "application/vnd.github+json"}
10
+ if GITHUB_TOKEN:
11
+ headers["Authorization"] = f"Bearer {GITHUB_TOKEN}"
12
+
13
+
14
+ query = """
15
+ query($username: String!) {
16
+ user(login: $username) {
17
+ name
18
+ bio
19
+ repositories(first: 10, orderBy: {field: STARGAZERS, direction: DESC}) {
20
+ nodes {
21
+ name
22
+ stargazerCount
23
+ primaryLanguage {
24
+ name
25
+ }
26
+ }
27
+ }
28
+ contributionsCollection {
29
+ contributionCalendar {
30
+ totalContributions
31
+ }
32
+ }
33
+ }
34
+ }
35
+ """
36
+
37
+ variables = {"username": USERNAME}
38
+
39
+ print(f"Fetching GitHub data for user: {USERNAME}...")
40
+
41
+ # 3. API Request
42
+ try:
43
+ response = requests.post(url, json={'query': query, 'variables': variables}, headers=headers)
44
+
45
+ if response.status_code == 200:
46
+ data = response.json()
47
+ print("\nGraphQL Query Success!")
48
+ print(json.dumps(data, indent=2))
49
+ elif response.status_code == 401:
50
+ print("\nAPI Error: Unauthorized. (Token missing ya invalid hai, which is expected right now!)")
51
+ print(" Schema validation checker success! Query structure is 100% correct.")
52
+ else:
53
+ print(f"\nAPI Error: Status Code {response.status_code}")
54
+ print(response.text)
55
+
56
+ except Exception as e:
57
+ print(f"Connection Error: {str(e)}")