AIを活用した就職支援プラットフォームをフルスタックで作った話
はじめに
「自分に合った企業がわからない」「面接が怖い」「ポートフォリオをどう見せればいいかわからない」
就活生が抱えるこんな悩みをAIで解決しようと、チームで就職支援プラットフォーム SOC AI Agent を開発しました。
この記事では、システム全体の構成と、それぞれの機能をどう実装したかを紹介します。
作ったもの:SOC AI Agent
就職活動に必要な4つをまるごとカバーするWebアプリです。
| 機能 | 内容 |
|---|---|
| AIチャット分析 | 質問に答えると職種・興味をAIが分析して企業マッチング |
| 音声面接練習 | リアルタイム音声対話 + 3Dアバターで面接練習 |
| 職務経歴書レビュー | RAGでAIがフィードバックを生成 |
| GitHubポートフォリオ | READMEをAIが要約してスキルを可視化 |
システム全体の構成
┌──────────────────────────────────────────────┐
│ Frontend: Next.js 16 + React 19 + MUI │
│ (TypeScript, Three.js, React Flow) │
├──────────────────────────────────────────────┤
│ Backend: Go + GORM + net/http │
│ (Controllers / Services / Repositories) │
├───────────────┬──────────────────────────────┤
│ RAG: Python │ Scraper: Go │
│ FastAPI │ Mynavi / Rikunabi / │
│ + CrewAI │ gBizINFO etc. │
├───────────────┴──────────────────────────────┤
│ DB: MySQL 8.0 │
├──────────────────────────────────────────────┤
│ AI: OpenAI (gpt-4o-mini, Realtime API) │
│ Cloud: AWS ECS / RDS / S3 / Secrets Manager │
└──────────────────────────────────────────────┘
バックエンド(Go)、RAGサービス(Python/FastAPI)、スクレイパー(Go)がそれぞれ独立して動き、Docker Composeで統合します。本番環境はAWS ECS Fargateで動かしています。
機能①:AIチャット分析 → 企業マッチング
ユーザーがチャット形式の質問に答えると、AIが職種や興味を分析して企業をマッチングする機能です。
データフロー
ユーザーが質問に回答
↓
ChatService が回答を記録
↓
OpenAI で次の質問を自動生成
↓
AnalysisScoringService が職種スコアを更新
↓
分析完了 → MatchingService が企業マッチング
↓
EmailService でレポートをメール配信
バックエンド(Go)の実装ポイント
バックエンドは Controller → Service → Repository の3層構成です。
// controllers/chat_controller.go
func (c *ChatController) SendMessage(w http.ResponseWriter, r *http.Request) {
var req ChatMessageRequest
json.NewDecoder(r.Body).Decode(&req)
response, err := c.chatService.ProcessMessage(r.Context(), req.UserID, req.Message)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
// services/chat_service.go
func (s *ChatService) ProcessMessage(ctx context.Context, userID uint, message string) (*ChatResponse, error) {
// 回答を保存
if err := s.chatRepo.SaveMessage(userID, message); err != nil {
return nil, err
}
// OpenAI で次の質問を生成
nextQuestion, err := s.generateNextQuestion(ctx, userID)
if err != nil {
return nil, err
}
// スコア更新
s.scoringService.UpdateScores(userID, message)
return &ChatResponse{NextQuestion: nextQuestion}, nil
}
機能②:音声面接練習(Realtime API + 3Dアバター)
OpenAI の Realtime API を使ったリアルタイム音声対話と、Three.js + wawa-lipsync による3Dアバター表示を組み合わせた機能です。
仕組み
ユーザーが話す
↓
WebRTC で音声をリアルタイム送信
↓
OpenAI Realtime API が処理
↓
AI の音声応答 + テキスト
↓
wawa-lipsync でアバターの口を同期
↓
Three.js で3Dアバターとして表示
フロントエンド側の実装ポイント
Realtime API のトークンはバックエンドで発行し、フロントエンドに渡します。
// interview/page.tsx
const startInterview = async () => {
// バックエンドからRealtimeトークンを取得
const res = await fetch(`${BACKEND_URL}/api/realtime/token?user_id=${userId}`)
const { token } = await res.json()
// WebRTC接続
const pc = new RTCPeerConnection()
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
pc.addTrack(stream.getTracks()[0])
// データチャネルでテキスト受信
const dc = pc.createDataChannel('oai-events')
dc.onmessage = (e) => {
const event = JSON.parse(e.data)
if (event.type === 'response.audio.delta') {
// アバターの口パクを更新
lipsync.update(event.delta)
}
}
}
ハマりポイント: 面接動画の録画データはS3にアップロードしますが、ファイルサイズが大きくなりがちです。バックエンドで 32MBの上限チェックを設けています。
面接動画のS3管理
// services/interview_service.go
func (s *InterviewService) UploadVideo(userID uint, sessionID uint, file multipart.File) error {
// ファイルサイズチェック(32MB上限)
buf := make([]byte, 32<<20+1)
n, _ := file.Read(buf)
if n > 32<<20 {
return errors.New("file size exceeds 32MB limit")
}
key := fmt.Sprintf("%s/%d/%d/video.webm", s.s3Prefix, userID, sessionID)
_, err := s.s3Client.PutObject(ctx, &s3.PutObjectInput{
Bucket: &s.bucket,
Key: &key,
Body: bytes.NewReader(buf[:n]),
})
return err
}
管理者が動画を視聴するときは、15分間有効な Presigned URL を発行してセキュアにアクセスします。
機能③:職務経歴書レビュー(RAG)
RAG(Retrieval-Augmented Generation)を使って、企業情報を検索しながら職務経歴書のフィードバックを生成します。
RAGの構成(Python / FastAPI)
# rag/main.py
@app.post("/resume/review")
async def review_resume(request: ReviewRequest):
# Chromadb でキャッシュ確認
cached = chroma_collection.query(
query_texts=[request.company_name],
n_results=1
)
if cached['distances'][0][0] < 0.2:
company_info = cached['documents'][0][0]
else:
# DuckDuckGo でリアルタイム検索
results = ddg_search.run(f"{request.company_name} 企業情報 事業内容")
company_info = results
# 次回のためにキャッシュ保存
chroma_collection.add(
documents=[company_info],
ids=[request.company_name]
)
# CrewAI エージェントでレビュー生成
crew = Crew(
agents=[reviewer_agent],
tasks=[review_task],
)
result = crew.kickoff(inputs={
"resume": request.resume_text,
"company_info": company_info,
"job_type": request.job_type,
})
return {"review": result}
ポイント:Chromadbキャッシュで高速化
同じ企業への問い合わせは2回目以降キャッシュから返すため、レスポンスが大幅に高速化されます。ベクトルの類似度が0.2以下なら「同じ企業の情報あり」と判断してキャッシュを使います。
機能④:GitHubポートフォリオ AI要約
GitHubのREADMEをAIが読んで、3つの観点で技術サマリーを自動生成します(直近で実装した機能です)。
| 項目 | 内容 |
|---|---|
| 技術選定の理由 | なぜその技術スタックを選んだか |
| 技術的チャレンジ | 難しかった部分や工夫した点 |
| 実績・学び | 得られた成果や学習内容 |
詳しい実装はこちらの記事で解説しています。
→ GitHubのREADMEをAIで要約してポートフォリオに表示する機能を作った話
機能⑤:企業関係図(React Flow + スクレイピング)
複数の就活サイトからデータを取得して、企業間の関係図をインタラクティブに表示します。
スクレイピングパイプライン(Go)
// tools/company-graph/pipeline.go
func (p *Pipeline) Run() ([]Company, error) {
var all []Company
// 複数ソースから並行取得
sources := []Scraper{
&MynaviScraper{},
&RikunabiScraper{},
&CareerTasuScraper{},
&GBizInfoScraper{}, // 政府公開API
}
for _, s := range sources {
companies, err := s.Fetch()
if err != nil {
continue // 1つ失敗しても続行
}
all = append(all, companies...)
}
// 重複排除・スコアリング
return p.deduplicate(all), nil
}
フロントエンドでReact Flowを使った関係図表示
// app/Correlation-diagram/page.tsx
export default function CorrelationDiagram() {
const [nodes, setNodes] = useState<Node[]>([])
const [edges, setEdges] = useState<Edge[]>([])
useEffect(() => {
fetch(`${BACKEND_URL}/api/company-relation`)
.then(res => res.json())
.then(data => {
setNodes(data.companies.map(c => ({
id: String(c.id),
data: { label: c.name },
position: { x: c.x, y: c.y },
})))
setEdges(data.relations.map(r => ({
id: `${r.from}-${r.to}`,
source: String(r.from),
target: String(r.to),
})))
})
}, [])
return <ReactFlow nodes={nodes} edges={edges} fitView />
}
インフラ:AWS ECS Fargate で本番運用
本番環境はすべてAWSで動かしています。
ユーザー
↓
ALB (Application Load Balancer)
↓
ECS Fargate タスク
├── Backend コンテナ (Go / ポート8080)
│ DB接続: RDS MySQL
│ 動画: S3
│ シークレット: Secrets Manager
└── Frontend コンテナ (Next.js / ポート3000)
ECS タスク定義のポイント
機密情報(DBパスワード、APIキー)はSecrets Managerから取得します。
{
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:XXXX:secret:soc/db:password::"
},
{
"name": "OPENAI_API_KEY",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:XXXX:secret:soc/openai:api_key::"
}
]
}
ハマりポイント: Secrets Managerのシークレットは secret_name:json_key:: の形式でJSONのフィールドを指定します。末尾の :: を忘れるとエラーになります。
認証:Google / GitHub OAuth2
どちらもOAuth2でシングルサインオンできます。
// config/oauth.go
func LoadOAuthConfig() *OAuthConfig {
return &OAuthConfig{
Google: &oauth2.Config{
ClientID: os.Getenv("GOOGLE_CLIENT_ID"),
ClientSecret: os.Getenv("GOOGLE_CLIENT_SECRET"),
RedirectURL: baseURL + "/api/auth/google/callback",
Scopes: []string{
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
},
Endpoint: google.Endpoint,
},
GitHub: &oauth2.Config{
ClientID: os.Getenv("GITHUB_CLIENT_ID"),
ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
RedirectURL: baseURL + "/api/auth/github/callback",
Scopes: []string{
"user:email",
"read:user",
"read:org", // 組織リポジトリ取得に必要
},
Endpoint: github.Endpoint,
},
}
}
Docker Composeでローカル開発
# compose.yml(抜粋)
services:
app:
build: ./Backend
ports: ["8080:8080"]
depends_on: [db]
frontend:
build: ./frontend
ports: ["3000:3000"]
db:
image: mysql:8.0
ports: ["3306:3306"]
rag-review:
build: ./rag
ports: ["9000:9000"]
profiles: ["rag"] # オプションで起動
company-graph:
build: ./tools/company-graph
ports: ["9100:9100"]
profiles: ["company-graph"] # オプションで起動
必要なサービスだけ起動できる profiles を活用することで、開発時のリソース消費を抑えています。
使った技術スタックまとめ
| カテゴリ | 技術 |
|---|---|
| フロントエンド | Next.js 16, React 19, TypeScript, MUI, Three.js, React Flow |
| バックエンド | Go, GORM, net/http |
| RAG | Python, FastAPI, CrewAI, Chromadb, DuckDuckGo Search |
| スクレイピング | Go, Chromedp |
| データベース | MySQL 8.0 |
| AI | OpenAI (gpt-4o-mini, Realtime API) |
| クラウド | AWS ECS, RDS, S3, Secrets Manager, CloudWatch |
| コンテナ | Docker, Docker Compose |
| IaC | Terraform (ECS タスク定義) |
| 認証 | OAuth2 (Google, GitHub) |
おわりに
このプロジェクトを通して、単にAIを使うだけでなく、どこにAIを組み込むと価値が生まれるかを考える経験ができました。
- チャット分析 → 企業マッチング(構造化データ生成)
- 音声面接 → Realtime APIで低遅延を実現
- RAG → キャッシュで速度と精度を両立
- ポートフォリオ要約 → 開発者の技術力を言語化
それぞれ異なるAIの使い方をしており、学びも多かったです。同じような機能を作ろうとしている方の参考になれば幸いです。