-
Notifications
You must be signed in to change notification settings - Fork 0
Closed
Description
目的
ユーザーの外部サービス活動実績とポートフォリオを元に、LLMを使用してエンジニアランク(0-9)を判定する機能を実装。
概念実証優先で、まず動くことを重視し、判定精度の向上は後続Issueで対応。
背景
- Issue #32でLangChain基盤完成
- product-spec SKILLの「ランク判定ロジック (AI主導)」を実装
- ユーザーフローの最初のステップ(オンボーディング後の初回分析)
ランク定義(10段階)
0: 種子 (初心者/未経験)
1: 苗木
2: 若木
3: 巨木
4: 母樹
5: 林
6: 森
7: 霊樹
8: 古樹
9: 世界樹 (Top Tier)
判定ロジック(AI主導)
- 相対評価: 取得した情報を総合的に分析し、「エンジニア全体の上位何%に位置するか」をAIが推定
- ランクマッピング: パーセンタイル → 10段階ランクに変換
実装内容
1. プロンプトテンプレート(app/core/prompts.py)
RANK_ANALYSIS_TEMPLATEを実装済みの基本構造から、実際のプロンプトに拡張:
RANK_ANALYSIS_TEMPLATE = """
あなたはエンジニアのスキルレベルを評価する専門家です。
以下の情報を元に、このエンジニアが全体の上位何%に位置するかを推定してください。
## 評価対象の情報
- GitHub Username: {github_username}
- ポートフォリオ: {portfolio_text}
- Qiita ID: {qiita_id}
- その他活動: {other_info}
## 評価基準
1. 技術の幅(使用言語・フレームワークの多様性)
2. 実装の深さ(プロジェクトの複雑さ、設計力)
3. 継続性(コミット頻度、学習習慣)
4. アウトプット(記事執筆、コミュニティ貢献)
## 出力形式(JSON)
{{
"percentile": 65.0,
"rank": 4,
"rank_name": "巨木",
"reasoning": "判定理由を200文字程度で説明"
}}
percentileは0.0〜100.0の数値で推定してください。
rankは以下のマッピングで決定:
- 0-10%: rank 0 (種子)
- 10-20%: rank 1 (苗木)
- 20-35%: rank 2 (若木)
- 35-50%: rank 3 (巨木)
- 50-65%: rank 4 (母樹)
- 65-75%: rank 5 (林)
- 75-85%: rank 6 (森)
- 85-92%: rank 7 (霊樹)
- 92-97%: rank 8 (古樹)
- 97-100%: rank 9 (世界樹)
"""
2. サービス層(app/services/rank_service.py 新規作成)
from app.core.llm import get_llm, invoke_llm
from app.core.prompts import RANK_ANALYSIS_TEMPLATE
import json
async def analyze_user_rank(
github_username: str,
portfolio_text: str = "",
qiita_id: str = "",
other_info: str = ""
) -> dict:
"""
LLMを使用してユーザーのランクを判定
Returns:
{
"percentile": float,
"rank": int,
"rank_name": str,
"reasoning": str
}
"""
llm = get_llm()
prompt = RANK_ANALYSIS_TEMPLATE.format(
github_username=github_username,
portfolio_text=portfolio_text or "未入力",
qiita_id=qiita_id or "未入力",
other_info=other_info or "未入力"
)
response = await invoke_llm(llm, prompt)
# JSONパース(エラーハンドリング付き)
try:
result = json.loads(response)
return result
except json.JSONDecodeError:
# LLMがJSON以外を返した場合のフォールバック
return {
"percentile": 50.0,
"rank": 3,
"rank_name": "巨木",
"reasoning": "判定結果の解析に失敗したため、デフォルト値を返却"
}
3. APIエンドポイント(app/api/endpoints/analyze.py 新規作成)
from fastapi import APIRouter, HTTPException
from app.schemas.analyze import RankAnalysisRequest, RankAnalysisResponse
from app.services.rank_service import analyze_user_rank
router = APIRouter()
@router.post("/rank", response_model=RankAnalysisResponse)
async def analyze_rank(request: RankAnalysisRequest):
"""
ユーザーのランクを判定
"""
try:
result = await analyze_user_rank(
github_username=request.github_username,
portfolio_text=request.portfolio_text,
qiita_id=request.qiita_id,
other_info=request.other_info
)
return RankAnalysisResponse(**result)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Rank analysis failed: {str(e)}")
4. スキーマ定義(app/schemas/analyze.py 新規作成)
from pydantic import BaseModel, Field
class RankAnalysisRequest(BaseModel):
github_username: str = Field(..., min_length=1, max_length=100)
portfolio_text: str = Field(default="", max_length=5000)
qiita_id: str = Field(default="", max_length=100)
other_info: str = Field(default="", max_length=2000)
class RankAnalysisResponse(BaseModel):
percentile: float = Field(..., ge=0.0, le=100.0)
rank: int = Field(..., ge=0, le=9)
rank_name: str
reasoning: str
5. ルーター登録(app/api/api.py)
from app.api.endpoints import analyze
api_router.include_router(analyze.router, prefix="/analyze", tags=["analyze"])
MVP制限(概念実証優先)
- GitHub API統合なし: まずはユーザー入力の文字列のみで判定(GitHub APIは後続Issue)
- DB保存なし: まずはAPIレスポンス返却のみ(DB保存は後続Issue)
- 判定精度は問わない: とにかく動くことを優先、プロンプト調整は後続Issue
実装タスク
-
app/core/prompts.py: RANK_ANALYSIS_TEMPLATE拡張 -
app/services/rank_service.py: analyze_user_rank実装 -
app/api/endpoints/analyze.py: POST /analyze/rank実装 -
app/schemas/analyze.py: Request/Responseスキーマ -
app/api/api.py: ルーター登録 - テスト:
tests/test_services/test_rank_service.py(モック使用) - テスト:
tests/test_api/test_analyze.py(エンドポイント動作確認) -
.env.example: OPENAI_API_KEY設定確認 - Swagger UI動作確認(実APIキー使用)
セキュリティ考慮
- Request Bodyサイズ制限(portfolio_text: 5KB、全体: 10KB)
- Rate Limiting方針をコメント記載(将来実装)
- LLMレスポンスのサニタイズ(JSONパースエラー時のフォールバック)
テスト要件
- モックテスト: LLM呼び出しをモック化し、CIでAPIキー不要
- 手動テスト: 実際のOpenAI APIを使用してSwagger UIで動作確認
完了条件(DoD)
- POST /analyze/rank実装完了
- Swagger UIで実際のLLM呼び出し成功確認(スクリーンショット)
- テスト2件以上、全パス(モック使用)
- PR説明にEvidence添付(Swagger UIスクリーンショット + レスポンス例)
依存
- Issue feat: AI基盤セットアップ (LangChain + LLM統合) #32(AI基盤セットアップ)マージ済み ✅
- Issue Backend: DB設計とモデル実装 (Rankマスタ/JSON対応) #31(DBモデル)との並行開発可能
次のステップ(後続Issue)
- Issue Backend: AI実装 - ランク判定(概念実証) #36: GitHub API統合(リポジトリ情報取得)
- Issue docs: git-workflowにブランチ命名規則を追加 #37: ランク判定の精度向上(プロンプトエンジニアリング)
- Issue docs: #37 git-workflowにブランチ命名規則を追加 #38: DB保存機能(Profileテーブル、User.rank更新)
- Issue feat : #36 LLMを使用したエンジニアランク判定機能の実装 #39: スキルツリー生成AI
- Issue docs: PRテンプレート拡張とADR導入 #40: 演習生成AI
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels