AI agent

νšŒμ‚¬μ—μ„œ λ°”λ‘œ μ“°λŠ” μ—…λ¬΄μžλ™ν™” AI μ—μ΄μ „νŠΈ (w. n8n, LangGraph) κ°•μ˜λ₯Ό μš”μ•½ν•œ λ‚΄μš©μž…λ‹ˆλ‹€.

n8n

n8n

Flexible AI workflow automation

λ‹€μ–‘ν•œ μ›Ή μ„œλΉ„μŠ€, μ•±, API듀을 μ—°κ²°ν•˜μ—¬ μžλ™ν™” μ›Œν¬ν”Œλ‘œμš°λ₯Ό λ§Œλ“œλŠ” μ˜€ν”ˆμ†ŒμŠ€ 톡합 ν”Œλž«νΌ

n8n은 μ›Œν¬ν”Œλ‘œμš°λ₯Ό κ΅¬μ„±ν•˜λŠ” λ…Έλ“œ(Node)λ“€λ‘œ 이루어져 있으며, 각 λ…Έλ“œλŠ” νŠΉμ • μž‘μ—…μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€.

πŸ‘‰πŸ» n8n μ»¨ν…Œμ΄λ„ˆ ꡬ동

docker volume create n8n_data

docker run -d -it --rm --name n8n -p 5678:5678 -v n8n_data:/home/node/.n8n docker.n8n.io/n8nio/n8n

Email Agent

πŸ‘‰πŸ» Gmail Actions

Gmail API μ‚¬μš©ν•˜κΈ°

  • GCP console β†’ API 및 μ„œλΉ„μŠ€ β†’ API 및 μ„œλΉ„μŠ€ μ‚¬μš© μ„€μ • β†’ Gmail API -> ν™œμ„±ν™”

  • μ‚¬μš©μž 인증 정보 λ§Œλ“€κΈ° β†’ OAuth ν΄λΌμ΄μ–ΈνŠΈ ID λ§Œλ“€κΈ°

Gmail API둜 μ΄μš©ν•  수 μžˆλŠ” λ‹€μ–‘ν•œ action, trigger κΈ°λŠ₯을 연동 κ°€λŠ₯

πŸ‘‰πŸ» Basic LLM Chain

λ‹΅μž₯ μ—¬λΆ€ νŒλ‹¨

λͺ¨λΈ μ—°κ²° β†’ Prompt β†’

"=μ•„λž˜ 이메일 λ‚΄μš©μ„ 보고, λ‹΅μž₯이 ν•„μš”ν•œ 이메일인지 νŒλ‹¨ν•΄μ£Όμ„Έμš”. λ§Œμ•½ λ‹Ήμ‹ μ˜ 였판으둜 μ œκ°€ 이메일에 λ‹΅μž₯을 ν•˜μ§€ λͺ»ν•˜κ²Œλ˜λ©΄ 업무상 차질이 생길 수 μžˆμœΌλ‹ˆ μ£Όμ˜ν•΄μ£Όμ„Έμš”.\n\n1. 업무 ν˜‘μ—… λ©”μΌμ—λŠ” 가급적이면 λ‹΅μž₯을 ν•΄μ•Όν•©λ‹ˆλ‹€. \n2. λ§ˆμΌ€νŒ…μ΄λ‚˜ λ‰΄μŠ€λ ˆν„°μ˜ κ²½μš°μ—λŠ” λ‹΅μž₯을 ν•  ν•„μš”κ°€ μ—†μŠ΅λ‹ˆλ‹€.\n\n이메일 λ‚΄μš©:\n {{ $json.text }}",

Chat Messages

"λ‹Ήμ‹ μ˜ μ—…λ¬΄λŠ” 이메일에 λ‹΅μž₯을 ν•΄μ•Όν•˜λŠ”μ§€ λ§μ•„μ•Όν•˜λŠ”μ§€ κ²°μ •ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. λ‹΅μž₯이 ν•„μš”ν•˜λ‹€λ©΄ true, λ‹΅μž₯이 ν•„μš” μ—†λ‹€λ©΄ falseλ₯Ό λ¦¬ν„΄ν•΄μ£Όμ„Έμš”"

Require Specific Output Format

{
	"type": "object",
	"properties": {
		"need_reply": {
			"type": "boolean"
		}
	}
}

이메일 λ‹΅μž₯ μž‘μ„±

Prompt

"=μ•„λž˜ 이메일을 보고 λ‹΅μž₯을 μž‘μ„±ν•΄μ£Όμ„Έμš”\n\n졜근 메일:\n{{ $('Gmail Trigger').item.json.text }}\n\n이메일 νžˆμŠ€ν† λ¦¬:\n{{ $json.messages.filter(item => item.snippet) }}",

Require Specific Output Format

{
	"type": "object",
	"properties": {
		"title": {
			"type": "string"
		},
        "content": {
			"type": "string"
		}
	}
}

이메일 μž‘μ„± 봇

System Messages

"당신은 이메일 μž‘μ„± λ„μš°λ―Έμž…λ‹ˆλ‹€. μ‚¬μš©μžμ˜ μ§ˆλ¬Έμ„ λ°”νƒ•μœΌλ‘œ, μ‚¬μš©μžμ—κ²Œ ν•„μš”ν•œ 이메일을 μž‘μ„±ν•΄μ£Όμ„Έμš” \n\n당신이 이메일을 μž‘μ„±ν•˜λŠ”λ° ν•„μš”ν•œ λͺ¨λ“  정보λ₯Ό 얻을 λ•ŒκΉŒμ§€ μ‚¬μš©μžμ—κ²Œ μ§ˆλ¬Έμ„ ν•œ 후에, ν•„μš”ν•œ 정보λ₯Ό λͺ¨λ‘ μ–»κ³  λ‚˜μ„œ 이메일을 μž‘μ„±ν•΄μ£Όμ„Έμš”"

News Agent (Brave Search API)

πŸ‘‰πŸ» Schedule Trigger

μ›Œν¬ν”Œλ‘œμš°λ₯Ό νŠΉμ • μ‹œκ°„μ— μžλ™μœΌλ‘œ μ‹œμž‘μ‹œν‚€λŠ” λ…Έλ“œ

  • μ§€μ •λœ μ‹œκ°„ 간격(예: 맀일 μ˜€μ „ 9μ‹œ, λ§€μ£Ό μ›”μš”μΌ, 15λΆ„λ§ˆλ‹€ λ“±)에 맞좰 λ‹€μŒ λ…Έλ“œλ‘œ 데이터λ₯Ό μ „λ‹¬ν•˜λ©° μ›Œν¬ν”Œλ‘œμš°λ₯Ό μ‹€ν–‰

  • America/New_York Timezone μ΄λ―€λ‘œ ν•œκ΅­ μ‹œκ°„λŒ€ 계산이 ν•„μš”

Brave Search 엔진을 μ΄μš©ν•΄ μ›Ή 검색을 μˆ˜ν–‰ν•˜κ³ , κ·Έ κ²°κ³Όλ₯Ό n8n μ›Œν¬ν”Œλ‘œμš°λ‘œ κ°€μ Έμ˜€λŠ” λ…Έλ“œ

  • 검색 κ²°κ³Όλ₯Ό JSON ν˜•νƒœλ‘œ λ°›μ•„μ™€μ„œ λ‹€λ₯Έ λ…Έλ“œμ—μ„œ ν™œμš©

πŸ‘‰πŸ» Code

μ›Œν¬ν”Œλ‘œμš° λ‚΄μ—μ„œ JavaScript μ½”λ“œλ₯Ό 직접 μ‹€ν–‰ν•  수 μžˆλŠ” λ…Έλ“œ

  • n8n의 κΈ°λ³Έ λ…Έλ“œλ§ŒμœΌλ‘œλŠ” ν•΄κ²°ν•˜κΈ° μ–΄λ €μš΄ λ³΅μž‘ν•œ λ‘œμ§μ„ κ΅¬ν˜„ν•˜κ±°λ‚˜, 데이터λ₯Ό νŠΉμ • ν˜•νƒœλ‘œ 가곡해야 ν•  λ•Œ 유용

  • n8n의 λͺ¨λ“  λ…Έλ“œμ—μ„œ 전달받은 데이터λ₯Ό μ½”λ“œ λ…Έλ“œμ—μ„œ 처리 κ°€λŠ₯

    return $input.first().json.results

πŸ‘‰πŸ» Loop Over Items

μ›Œν¬ν”Œλ‘œμš°κ°€ λ°°μ—΄(리슀트) ν˜•νƒœμ˜ 데이터 μ•„μ΄ν…œλ“€μ„ ν•˜λ‚˜μ”© μˆœνšŒν•˜λ©° 반볡 μž‘μ—…μ„ μˆ˜ν–‰ν•˜λ„λ‘ ν•˜λŠ” λ…Έλ“œ

  • 각 μ•„μ΄ν…œμ— λŒ€ν•΄ λ™μΌν•œ 일련의 μž‘μ—…μ„ 적용 κ°€λŠ₯

  • Batch Size μ§€μ •

πŸ‘‰πŸ» Basic LLM Chain

LangChainJS 라이브러리λ₯Ό 기반으둜 LLMκ³Ό μƒν˜Έμž‘μš©ν•˜λŠ” λ…Έλ“œ

  • λ³΅μž‘ν•œ μ½”λ”© 없이도 ν”„λ‘¬ν”„νŠΈ ν…œν”Œλ¦Ώκ³Ό LLM을 μ—°κ²°ν•˜μ—¬ ν…μŠ€νŠΈλ₯Ό μƒμ„±ν•˜κ±°λ‚˜, 질문-λ‹΅λ³€ κΈ°λŠ₯을 μ›Œν¬ν”Œλ‘œμš°μ— 톡합 κ°€λŠ₯

Prompt

"Categorize below news article by reading the description into \"positive,\" \"negative,\", \"neutral\":\n\nNews Article:\nTitle: {{ $json.title }}\nDescription: {{ $json.description }}\nURL: {{ $json.url }}",

Chat Messages

"Your job is to analyze the sentiment of a news article from a user every morning and report to the executives and share with my colleagues\n\nAfter analyzing the sentiment, use the Google Sheet Tool provided to you to insert the analysis into the spreadsheet for other colleagues to see"

Require Specific Output Format

{
	"type": "object",
	"properties": {
		"sentiment": {
			"type": "string"
		},
      "title": {
			"type": "string"
		},
      "description": {
			"type": "string"
		},
      "url": {
			"type": "string"
		}
	}
}

πŸ‘‰πŸ» Append row in sheet

Google Sheets, Excel λ“± μŠ€ν”„λ ˆλ“œμ‹œνŠΈ μ„œλΉ„μŠ€μ— μƒˆλ‘œμš΄ 행을 μΆ”κ°€ν•˜λŠ” λ…Έλ“œ

  • μ›Œν¬ν”Œλ‘œμš°μ—μ„œ 처리된 데이터λ₯Ό μ΅œμ’…μ μœΌλ‘œ μŠ€ν”„λ ˆλ“œμ‹œνŠΈμ— 정리할 λ•Œ μ‚¬μš©

Google Drive & Google Sheets API

  • GCP console β†’ API 및 μ„œλΉ„μŠ€ β†’ API 및 μ„œλΉ„μŠ€ μ‚¬μš© μ„€μ •

    • Google Drive API

    • Google Sheets API

  • μ‚¬μš©μž 인증 정보 λ§Œλ“€κΈ° β†’ OAuth ν΄λΌμ΄μ–ΈνŠΈ ID λ§Œλ“€κΈ° (κΈ°μ‘΄ ID μ‚¬μš© κ°€λŠ₯)

  • μ‹œνŠΈμ— 헀더 μΆ”κ°€ β†’ Document & Sheet 선택 β†’ 헀더 정보 μž…λ ₯

Result

  • Brave News μΆ”μΆœ (Brave Search)

  • μΆ”μΆœλœ 정보λ₯Ό JSON ν˜•νƒœλ‘œ λ³€ν™˜ (Code)

  • Loop Over Items

  • λ‰΄μŠ€ 감정 뢄석 (Basic LLM Chain)

  • κ²°κ³Όλ₯Ό μ‹œνŠΈμ— μΆ”κ°€ (Sheets)

News Agent (Naver HTTP Request)

πŸ‘‰πŸ» HTTP Request

μ›Ήμ—μ„œ 데이터λ₯Ό κ°€μ Έμ˜€κ±°λ‚˜ λ³΄λ‚΄λŠ” κ°€μž₯ 기본적인 λ…Έλ“œ

  • νŠΉμ • URL둜 HTTP μš”μ²­(GET, POST, PUT, DELETE λ“±)을 보내고, κ·Έ 응닡을 λ°›μ•„ μ›Œν¬ν”Œλ‘œμš°μ— 톡합

  • APIμ™€μ˜ μƒν˜Έμž‘μš©μ€ λŒ€λΆ€λΆ„ 이 λ…Έλ“œλ₯Ό 톡해 이루어짐

πŸ‘‰πŸ» HTML

HTML λ¬Έμ„œμ—μ„œ νŠΉμ • 데이터λ₯Ό μΆ”μΆœν•˜λŠ” 데 μ‚¬μš©

  • μ›Ή μŠ€ν¬λž˜ν•‘(Web Scraping)의 핡심 λ…Έλ“œ

  • CSS μ…€λ ‰ν„°(Selector)λ₯Ό μ΄μš©ν•΄ μ›ν•˜λŠ” νƒœκ·Έλ‚˜ 클래슀, 아이디λ₯Ό κ°€μ§„ μš”μ†Œλ₯Ό μ„ νƒν•˜κ³  κ·Έ μ•ˆμ˜ ν…μŠ€νŠΈλ‚˜ 속성 값을 μΆ”μΆœ

πŸ‘‰πŸ» Filter

데이터λ₯Ό νŠΉμ • 쑰건에 따라 κ±ΈλŸ¬λ‚΄λŠ” λ…Έλ“œ

  • μž…λ ₯ 데이터가 μ„€μ •λœ 쑰건을 λ§Œμ‘±ν•˜λŠ”μ§€ ν™•μΈν•˜μ—¬, 쑰건을 λ§Œμ‘±ν•˜λŠ” λ°μ΄ν„°λ§Œ λ‹€μŒ λ…Έλ“œλ‘œ 전달

  • 쑰건은 λ“±ν˜Έ, λΆ€λ“±ν˜Έ, 포함 μ—¬λΆ€, μ •κ·œμ‹ λ“± λ‹€μ–‘ν•˜κ²Œ μ„€μ •

πŸ‘‰πŸ» Notion

Notion λ°μ΄ν„°λ² μ΄μŠ€ λ˜λŠ” νŽ˜μ΄μ§€μ™€ μƒν˜Έμž‘μš©ν•˜λŠ” λ…Έλ“œ

  • Notion 계정과 μ—°κ²°ν•˜μ—¬ λ°μ΄ν„°λ² μ΄μŠ€μ— μƒˆλ‘œμš΄ ν•­λͺ©μ„ μΆ”κ°€ν•˜κ±°λ‚˜, κΈ°μ‘΄ νŽ˜μ΄μ§€μ˜ λ‚΄μš©μ„ μ—…λ°μ΄νŠΈν•˜κ±°λ‚˜, νŠΉμ • 데이터λ₯Ό κ²€μƒ‰ν•˜λŠ” λ“±μ˜ μž‘μ—…μ„ μˆ˜ν–‰

Notion Credential

notion integration β†’ View my integrations β†’ New API integration β†’ Notion μ˜΅μ…˜ β†’ μ—°κ²° β†’ API integration μ—°κ²°

Result

  • 기사 λͺ©λ‘ μš”μ²­ (HTTP Request)

  • Loop Over Items

  • 기사 제λͺ© & URL μΆ”μΆœ (HTML)

  • μΆ”μΆœλœ 정보λ₯Ό JSON ν˜•νƒœλ‘œ λ³€ν™˜ (Code)

  • Loop Over Items

  • 기사 URL μš”μ²­ (HTTP Request)

  • 기사 λ‚΄μš© μΆ”μΆœ (HTML)

  • μ—°κ΄€μ„± νŒŒμ•… (Basic LLM Chain)

  • 연관성이 μžˆλŠ”μ§€ 필터링 (Filter)

  • λ‰΄μŠ€ 감정 뢄석 (Basic LLM Chain)

  • λ…Έμ…˜ λ°μ΄ν„°λ² μ΄μŠ€μ— μΆ”κ°€ (Notion)

n8n을 ν™œμš©ν•œ 사내 QnA Bot

데이터 μ €μž₯

πŸ‘‰πŸ» Get rows in sheet

Google Sheets, Excel λ“± μŠ€ν”„λ ˆλ“œμ‹œνŠΈμ—μ„œ νŠΉμ • 행듀을 μ½μ–΄μ˜€λŠ” λ…Έλ“œ

  • 필터링 쑰건을 μ„€μ •ν•˜μ—¬ νŠΉμ • ν–‰λ§Œ μ„ νƒμ μœΌλ‘œ κ°€μ Έμ˜¬ 수 있으며, κ°€μ Έμ˜¨ λ°μ΄ν„°λŠ” μ›Œν¬ν”Œλ‘œμš°μ˜ λ‹€μŒ λ…Έλ“œλ‘œ μ „λ‹¬λ˜μ–΄ κ°€κ³΅λ˜κ±°λ‚˜ ν™œμš©

  • ex) 파일 링크 λͺ©λ‘ κ°€μ Έμ˜€κΈ°

πŸ‘‰πŸ» Download file

νŠΉμ • URLμ—μ„œ νŒŒμΌμ„ λ‹€μš΄λ‘œλ“œν•˜μ—¬ μ›Œν¬ν”Œλ‘œμš°λ‘œ κ°€μ Έμ˜€λŠ” λ…Έλ“œ

  • λ‹€μš΄λ‘œλ“œλœ νŒŒμΌμ€ λ‹€μŒ λ…Έλ“œμ—μ„œ μ²˜λ¦¬ν•  수 μžˆλŠ” ν˜•νƒœλ‘œ λ³€ν™˜

  • ex) μΆ”μΆœλœ 파일 링크λ₯Ό 읽고 ν•΄λ‹Ή νŒŒμΌμ„ λ‹€μš΄λ‘œλ“œ

πŸ‘‰πŸ» Pinecone Vector Store

μ „λ¬Έ 벑터 λ°μ΄ν„°λ² μ΄μŠ€μ— 데이터λ₯Ό μ €μž₯ν•˜κ±°λ‚˜ 검색

  • LLM μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ λ°©λŒ€ν•œ μ–‘μ˜ λΉ„μ •ν˜• 데이터λ₯Ό 효율적으둜 κ²€μƒ‰ν•˜κ³  κ΄€λ¦¬ν•˜λŠ” 데 μ‚¬μš©

Gemini

  • Pinecone Vector Store λ…Έλ“œμ™€ ν•¨κ»˜ μ‚¬μš©λ˜λŠ” Gemini λ…Έλ“œλŠ” Google의 Gemini AI λͺ¨λΈμ„ μ‚¬μš©ν•˜μ—¬ ν…μŠ€νŠΈ 데이터λ₯Ό λ²‘ν„°λ‘œ λ³€ν™˜ν•˜λŠ” 역할을

  • λ²‘ν„°ν™”λœ λ°μ΄ν„°λŠ” Pinecone λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯λ˜μ–΄ μœ μ‚¬λ„ 검색에 μ‚¬μš©

Default Data Loader

  • λ‹€μ–‘ν•œ μ†ŒμŠ€(μ›Ήμ‚¬μ΄νŠΈ, PDF, CSV 파일 λ“±)μ—μ„œ 데이터λ₯Ό λΆˆλŸ¬μ™€μ„œ 처리 κ°€λŠ₯ν•œ ν˜•νƒœλ‘œ λ³€ν™˜ν•˜λŠ” μ—­ν• 

  • 이 λ…Έλ“œλŠ” 특히 LLM μ›Œν¬ν”Œλ‘œμš°μ—μ„œ μ™ΈλΆ€ 데이터λ₯Ό λͺ¨λΈμ— μž…λ ₯ν•˜κΈ° 전에 μ „μ²˜λ¦¬ν•˜λŠ” 데 μ‚¬μš©

  • Recursive Character Text Splitter

    • κΈΈκ³  λ³΅μž‘ν•œ ν…μŠ€νŠΈλ₯Ό LLM이 ν•œ λ²ˆμ— μ²˜λ¦¬ν•˜κΈ° μ ν•©ν•œ 크기의 '덩어리(chunk)'둜 λ‚˜λˆ„λŠ” μ—­ν• 

    • νŠΉμ • 문자(예: '\n\n', '\n', '.' λ“±)λ₯Ό κΈ°μ€€μœΌλ‘œ ν…μŠ€νŠΈλ₯Ό μž¬κ·€μ μœΌλ‘œ λΆ„ν• ν•˜μ—¬, 의미 μžˆλŠ” λ‹¨μœ„κ°€ μ†μƒλ˜μ§€ μ•Šλ„λ‘ 함

Bot using LLM Chain

πŸ‘‰πŸ» When Chat message received

  • ν…”λ ˆκ·Έλž¨, μŠ¬λž™ λ“± νŠΉμ • λ©”μ‹ μ € μ±„λ„μ—μ„œ μƒˆλ‘œμš΄ λ©”μ‹œμ§€κ°€ μˆ˜μ‹ λ˜μ—ˆμ„ λ•Œ μ›Œν¬ν”Œλ‘œμš°λ₯Ό μžλ™μœΌλ‘œ μ‹œμž‘ν•˜λŠ” 트리거 λ…Έλ“œ

  • μ±„νŒ… 기반의 μžλ™ν™” μ›Œν¬ν”Œλ‘œμš°λ₯Ό κ΅¬μΆ•ν•˜λŠ” 데 ν•„μˆ˜μ 

πŸ‘‰πŸ» Basic LLM Chain

  • λ³΅μž‘ν•œ λ¬Έμ„œλ‚˜ 데이터 μ†ŒμŠ€λ₯Ό λ°”νƒ•μœΌλ‘œ μ§ˆλ¬Έμ— λŒ€ν•œ 닡변을 μƒμ„±ν•˜λŠ” κ³ κΈ‰ λ…Έλ“œ

  • μ‚¬μš©μžμ˜ μ§ˆλ¬Έμ„ μž…λ ₯으둜 λ°›μ•„, μ—°κ²°λœ 데이터 μ†ŒμŠ€μ—μ„œ κ΄€λ ¨ 정보λ₯Ό μ°Ύμ•„λ‚΄κ³ , 이λ₯Ό λ°”νƒ•μœΌλ‘œ LLM이 μžμ—°μ–΄ 닡변을 생성

  • RAG(Retrieval Augmented Generation) κΈ°μˆ μ„ κ΅¬ν˜„ν•˜λŠ” 데 μ‚¬μš©

Vector Store Retriever

  • 질문과 κ΄€λ ¨λœ 정보λ₯Ό 벑터 λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ 효율적으둜 κ²€μƒ‰ν•˜λŠ” μ—­ν• 

  • μ‚¬μš©μžμ˜ μ§ˆλ¬Έμ„ λ²‘ν„°λ‘œ λ³€ν™˜ν•œ ν›„, 이 벑터와 μœ μ‚¬ν•œ 의미λ₯Ό κ°€μ§„ κΈ°μ‘΄ λ¬Έμ„œ 벑터듀을 λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ 탐색

  • 이 λ…Έλ“œλ₯Ό 톡해 LLM은 λ°©λŒ€ν•œ 데이터 μ†μ—μ„œ ν•„μš”ν•œ μ •λ³΄λ§Œ '검색'

Pinecone Vector Store

  • 벑터 λ°μ΄ν„°λ² μ΄μŠ€μ˜ ν•œ μ’…λ₯˜λ‘œ, Vector Store Retriever λ…Έλ“œμ™€ ν•¨κ»˜ μ‚¬μš©

  • 이곳에 μ €μž₯된 λ¬Έμ„œ 벑터듀을 기반으둜 검색

  • LLM이 ν•™μŠ΅ν•˜μ§€ μ•Šμ€ μ΅œμ‹  λ°μ΄ν„°λ‚˜ μ „λ¬Έ λ¬Έμ„œμ— λŒ€ν•œ μ§ˆμ˜μ‘λ‹΅ κΈ°λŠ₯을 κ΅¬ν˜„ν•  λ•Œ 주둜 ν™œμš©

Embeddings Google Gemini

  • ν…μŠ€νŠΈ 데이터λ₯Ό κ΅¬κΈ€μ˜ Gemini AI λͺ¨λΈμ„ μ‚¬μš©ν•˜μ—¬ λ²‘ν„°λ‘œ λ³€ν™˜ν•˜λŠ” μ—­ν• 

  • 질문과 λ¬Έμ„œ λͺ¨λ‘ 이 λ…Έλ“œλ₯Ό 거쳐 λ²‘ν„°ν™”λ˜λ©°, 이 λ²‘ν„°λŠ” Vector Store Retriever λ…Έλ“œλ₯Ό 톡해 Pinecone λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯λ˜κ±°λ‚˜ 검색에 μ‚¬μš©

Bot using AI Agent

πŸ‘‰πŸ» AI Agent

LLM을 μ΄μš©ν•΄ λ³΅μž‘ν•œ μž‘μ—…μ„ 슀슀둜 κ³„νšν•˜κ³  μ‹€ν–‰ν•˜κ²Œ ν•˜λŠ” κ³ κΈ‰ λ…Έλ“œ

  • λ‹¨μˆœνžˆ ν…μŠ€νŠΈλ₯Ό μƒμ„±ν•˜λŠ” 것을 λ„˜μ–΄, μ—¬λŸ¬ 도ꡬλ₯Ό μ‘°ν•©ν•˜κ³  순차적으둜 μ‹€ν–‰ν•˜μ—¬ λͺ©ν‘œλ₯Ό 달성

System Message

You are a helpful assistant. Use the tools that are available to you in order to answer the user's question

Pinecone Vector Store tool

  • AI Agentκ°€ Pinecone 벑터 λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό '도ꡬ'둜 μ‚¬μš©

  • AI AgentλŠ” 이 도ꡬλ₯Ό μ΄μš©ν•΄ λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ νŠΉμ • 정보λ₯Ό κ²€μƒ‰ν•˜κ±°λ‚˜, μƒˆλ‘œμš΄ 데이터λ₯Ό μ €μž₯ν•˜λŠ” λ“±μ˜ μž‘μ—…μ„ μ›Œν¬ν”Œλ‘œμš° λ‚΄μ—μ„œ μˆ˜ν–‰

tool Description

The documents within this knowledge base contains information about company's policy such as HR, IT Support, and so on

Text-to-SQL

Text-to-SQL

  • μžμ—°μ–΄(μ˜μ–΄λ‚˜ ν•œκ΅­μ–΄)λ₯Ό SQL둜 λ³€ν™˜ν•˜λŠ” 기술

  • AIλ₯Ό ν†΅ν•œ λ°μ΄ν„°λ² μ΄μŠ€ μ ‘κ·Ό

πŸ‘‰πŸ» When chat message received

πŸ‘‰πŸ» ν…Œμ΄λΈ” 이름 κ°€μ Έμ˜€κΈ°

Postgres Execute a SQL query

SELECT 
    col.table_name,
    COALESCE(obj_description(c.oid), 'No description available') AS table_description,
    string_agg(col.column_name, ', ' ORDER BY col.ordinal_position) AS all_columns
FROM information_schema.columns col
LEFT JOIN pg_class c ON c.relname = col.table_name
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE col.table_schema = 'public'
AND n.nspname = 'public'
GROUP BY col.table_name, c.oid
ORDER BY col.table_name;

πŸ‘‰πŸ» ν…Œμ΄λΈ” 정보 ν•©μΉ˜κΈ°

Code

let schema = '';

for (const item of $input.all()) {
  schema += `tableName: ${item.json.table_name}
tableDescription: ${item.json.table_description}
columnList: ${item.json.all_columns}`;
}

return [{schema}];

πŸ‘‰πŸ» ν…Œμ΄λΈ” 리슀트 μΆ”μΆœ

Basic LLM Chain

Prompt

{{ $('When chat message received').first().json.chatInput }}

System Message

TABLE DESCRIPTION:
{{ $json.schema }}


By looking at the table description above, which contains information about the name of tables and their relative descriptions along with the list of columns, return the list of names of the tables that you need to access in order to retrieve data related to the user's question

structured Output Parser

{
	"type": "object",
	"properties": {
		"tables": {
			"type": "array",
			"table": {
				"type": "string"
			}
		}
	}
}

πŸ‘‰πŸ» μŠ€ν‚€λ§ˆ 쑰회

Postgres Execute a SQL query

SELECT
    'CREATE TABLE ' || table_name || ' (' || E'\n' ||
    -- 각 μ»¬λŸΌμ— λŒ€ν•œ 정보λ₯Ό μ‘°ν•©ν•©λ‹ˆλ‹€.
    string_agg(
        '    ' || column_name || ' ' || 
        CASE 
            WHEN data_type = 'character varying' THEN 'VARCHAR(' || character_maximum_length || ')'
            WHEN data_type = 'integer' AND column_default LIKE 'nextval%' THEN 'SERIAL PRIMARY KEY'
            WHEN data_type = 'numeric' THEN 'DECIMAL(' || numeric_precision || ',' || numeric_scale || ')'
            WHEN data_type = 'timestamp without time zone' THEN 'TIMESTAMP'
            WHEN data_type = 'boolean' THEN 'BOOLEAN'
            ELSE UPPER(data_type)
        END ||
        CASE WHEN is_nullable = 'NO' AND column_default NOT LIKE 'nextval%' THEN ' NOT NULL' ELSE '' END ||
        CASE WHEN column_default IS NOT NULL AND column_default NOT LIKE 'nextval%' 
             THEN ' DEFAULT ' || column_default ELSE '' END,
        E',\n' ORDER BY ordinal_position
    ) || E'\n' || ');' || E'\n\n' AS create_statement
FROM 
    information_schema.columns 
WHERE 
    table_schema = 'public'
AND TABLE_NAME IN ({{  $json.output.tables.map((tableName) => `'${tableName}'`) }})
GROUP BY 
    table_name
ORDER BY 
    table_name;

πŸ‘‰πŸ» μŠ€ν‚€λ§ˆ 정보 ν•©μΉ˜κΈ°

Code

let schema = ''
for (const item of $input.all()) {
  schema += `${item.json.create_statement}\n\n`
}

return [{schema}]

πŸ‘‰πŸ» 쿼리 생성

Basic LLM Chain

Prompt

{{ $('When chat message received').first().json.chatInput }}

System message

DATABASE SCHEMA:
{{ $json.schema }}

Looking at the database schema above, convert a user's question into a SQL query to fetch data from the database. return the SQL query only 

Structured Output Parser

{
	"type": "object",
	"properties": {
		"query": {
			"type": "string"
		}
	}
}

πŸ‘‰πŸ» 쿼리 μ‹€ν–‰

Postgres Execute a SQL query

{{ $json.output.query }}

πŸ‘‰πŸ» 쿼리 κ²°κ³Ό 닀듬기

Basic LLM Chain

Prompt

QUERY RESULT:
{{ JSON.stringify($json) }}


Original Question:  
{{ $('When chat message received').first().json.chatInput }}

System Message

Look at the query result and the user's question and return a user friendly message

MCP

Introducing the Model Context Protocol

MCP Doc.

MCP μš©μ–΄

Protocol

  • 톡신 κ³„μ•½μ„œ

  • μ›Ή 개발의 HTTP 톡신과 μœ μ‚¬

  • HTTP = Hypertext Transfer Protocol (μ›Ή 톡신 계약)

  • MCP = λͺ¨λΈκ³Ό μ»¨ν…μŠ€νŠΈ κ°„μ˜ 톡신 계약

Model

  • LLM λͺ¨λΈμ„ 의미

  • GPT, Anthropic의 Claude, Gemini λ“±

  • ν•œκ΅­μ˜ Upstage Solar λͺ¨λΈ λ“± 포함

Context

  • LLM에 μ›ν•˜λŠ” κ²°κ³Όλ₯Ό μ–»κΈ° μœ„ν•΄ 잘 전달해야 ν•˜λŠ” 정보

  • MCPλ₯Ό 톡해 ν”„λ‘¬ν”„νŠΈμ™€ 툴 관리 κ°€λŠ₯

  • ν•œκ΅­μ–΄λ‘œλŠ” "AIμ—κ²Œ μ»¨ν…μŠ€νŠΈλ₯Ό μ „λ‹¬ν•˜λŠ” ν”„λ‘œν† μ½œ"

MCP의 μž₯점

ν”„λ‘œν† μ½œμ˜ λ²”μš©μ„±

  • HTTP ν†΅μ‹ μ²˜λŸΌ μ„œλ²„ μ–Έμ–΄(Java, Python, Node)에 관계없이 톡신 κ°€λŠ₯

  • λ‹€μ–‘ν•œ λͺ¨λΈ 지원 κ°€λŠ₯

USB-C 포트 λΉ„μœ 

  • 곡식 λ¬Έμ„œμ—μ„œ "AI μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μœ„ν•œ USB-C 포트"둜 ν‘œν˜„

  • μ–΄λŒ‘ν„°λ₯Ό 톡해 λͺ¨λ“  MCP μ„œλ²„μ™€ λͺ¨λ“  MCP ν΄λΌμ΄μ–ΈνŠΈ μ—°κ²° κ°€λŠ₯

Code Review Agent with MCP

github-mcp-server

Slack MCP Server

ref. MCPλ₯Ό ν™œμš©ν•œ μ½”λ“œλ¦¬λ·° μ—μ΄μ „νŠΈ μƒμ„±ν•˜κΈ°

##########################################
# ν™˜κ²½λ³€μˆ˜ 뢈러였기
from dotenv import load_dotenv

load_dotenv()

##########################################
# MCP Client μ„ΈνŒ…
import os
from langchain_mcp_adapters.client import MultiServerMCPClient

github_pat = os.getenv("GITHUB_PAT")
slack_bot_token = os.getenv("SLACK_BOT_TOKEN")
slack_team_id = os.getenv("SLACK_TEAM_ID")
slack_channel_ids = os.getenv("SLACK_CHANNEL_IDS")

mcp_client = MultiServerMCPClient({
    "github": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "GITHUB_TOOLSETS",
        "-e",
        "GITHUB_PERSONAL_ACCESS_TOKEN",
        "ghcr.io/github/github-mcp-server"
      ],
      "env": {
        "GITHUB_TOOLSETS": "context,pull_requests",
        "GITHUB_PERSONAL_ACCESS_TOKEN": github_pat
      },
      "transport": "stdio"
    },
     "slack": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "SLACK_BOT_TOKEN",
        "-e",
        "SLACK_TEAM_ID",
        "-e",
        "SLACK_CHANNEL_IDS",
        "mcp/slack"
      ],
      "env": {
        "SLACK_BOT_TOKEN": slack_bot_token,
        "SLACK_TEAM_ID": slack_team_id,
        "SLACK_CHANNEL_IDS": slack_channel_ids
      },
      "transport": "stdio"
    }
})

##########################################
# MCP Client Tool 확인
tool_list = await mcp_client.get_tools()

##########################################
# create react agent ν™œμš©
from langgraph.prebuilt import create_react_agent

agent = create_react_agent(
    model="openai:gpt-4.1",
    tools=tool_list,
    prompt="Use the tools provided to you to answer the user's question"
)

##########################################
# μŠ€νŠΈλ¦Όν•œ κ²°κ³Όλ₯Ό 보기 μœ„ν•œ 헬퍼 ν•¨μˆ˜
async def process_stream(stream_generator):
    results = []
    try:
        async for chunk in stream_generator:

            key = list(chunk.keys())[0]
            if key == 'agent':
                # Agent λ©”μ‹œμ§€μ˜ λ‚΄μš©μ„ κ°€μ Έμ˜΄. λ©”μ„Έμ§€κ°€ λΉ„μ–΄μžˆλŠ” 경우 μ–΄λ–€ 도ꡬλ₯Ό μ–΄λ–»κ²Œ ν˜ΈμΆœν• μ§€ 정보λ₯Ό κ°€μ Έμ˜΄
                content = chunk['agent']['messages'][0].content if chunk['agent']['messages'][0].content != '' else chunk['agent']['messages'][0].additional_kwargs
                print(f"'agent': '{content}'")
            
            elif key == 'tools':
                # 도ꡬ λ©”μ‹œμ§€μ˜ λ‚΄μš©μ„ κ°€μ Έμ˜΄
                for tool_msg in chunk['tools']['messages']:
                    print(f"'tools': '{tool_msg.content}'")
            
            results.append(chunk)
        return results
    except Exception as e:
        print(f"Error processing stream: {e}")
        return results

##########################################
# ν”„λ‘¬ν”„λ‘œ 생성
from langchain_core.messages import HumanMessage

human_message = """κΉƒν—™μ˜ Pull Requestλ₯Ό ν™•μΈν•˜κ³  μ½”λ“œ 리뷰λ₯Ό μž‘μ„±ν•΄μ£Όμ„Έμš”. 
PR의 μ½”λ“œλ₯Ό λ¦¬λ·°ν•œ 후에, μ•„λž˜ ν•­λͺ©μ„ ν™•μΈν•΄μ£Όμ„Έμš”;
1. μ½”λ“œκ°€ κ°œμ„ λ˜μ—ˆλŠ”μ§€
2. μ˜ˆμΈ‘ν•˜μ§€ λͺ»ν•œ side effectκ°€ μžˆλŠ”μ§€
3. λ³΄μ•ˆμƒ λ¬Έμ œκ°€ 될 수 μžˆλŠ” 뢀뢄이 μ—†λŠ”μ§€

μœ„ λ‚΄μš©μ„ ν™•μΈν•΄μ„œ PR에 μ½”λ©˜νŠΈλ‘œ λ‚¨κ²¨μ£Όμ„Έμš”.
그리고 μ½”λ©˜νŠΈλ₯Ό 남긴 후에, μŠ¬λž™ 채널에도 λ©”μ„Έμ§€λ₯Ό μ „μ†‘ν•΄μ„œ μ—”μ§€λ‹ˆμ–΄μ—κ²Œ μ•Œλ €μ£Όμ„Έμš”
채널: <@C092QEJH6LB>
μ—”μ§€λ‹ˆμ–΄: <@U092VN0DHBP>

PR URL:https://github.com/jasonkang14/sat-reading-client/pull/3 """

stream_generator = agent.astream({"messages": [HumanMessage(human_message)]}, stream_mode="updates")

##########################################
# κ²°κ³Ό 확인
all_chunks = await process_stream(stream_generator)

if all_chunks:
    final_result = all_chunks[-1]
    print("\nFinal result:", final_result)

Video Summary Agent

OpenAI Whisperλ₯Ό ν™œμš©ν•œ μ˜μƒ μš”μ•½

Reference

uv Libeary

  • An extremely fast Python package and project manager, written in Rust.

zerox

  • A dead simple way of OCR-ing a document for AI ingestion. Documents are meant to be a visual representation after all. With weird layouts, tables, charts, etc. The vision models just make sense!

Lecture Materials

Last updated