메뉴
BL
MarkTechPost 12일 전

OpenAI API로 고급 에이전트 AI 시스템 구축하기

IMP
8/10
핵심 요약

OpenAI API를 활용해 계획 수립, 도구 사용, 메모리 및 자기 비평 기능을 갖춘 고급 에이전트 AI 시스템을 구축하는 튜토리얼입니다. 전략, 실행, 품질 관리 역할을 분리한 파이프라인 구조를 채택하여 작업의 정확도와 신뢰성을 높였습니다. 이는 자율형 AI 에이전트를 복잡한 실무 환경에 안정적으로 적용하려는 개발자들에게 중요한 참고 자료가 됩니다.

번역된 본문

에디터 추천 에이전트 AI 튜토리얼

이 튜토리얼에서는 OpenAI API와 API 키 입력을 숨기는 터미널 프롬프트를 사용하여 고급 에이전트 AI 시스템을 구축합니다. 에이전트를 전문적인 역할을 수행하는 작은 파이프라인으로 설계합니다. 즉, 플래너(Planner, 계획 수립), 도구 사용 실행자(Tool-using Executor), 비평가(Critic)로 구성하여 전략, 실행, 품질 관리를 분리합니다. 또한 구조화된 도구(계산기, 미니 지식 베이스 검색, JSON 추출, 파일 쓰기)를 통합하여 에이전트가 안정적으로 계산하고, 지침을 검색하며, 구조화된 출력을 생성하고, 최종 결과물을 아티팩트로 저장할 수 있도록 합니다.

코드 복사 완료 [코드 스니펫] !pip -q install -U openai ... (이하 코드 생략)

OpenAI SDK를 설치하고, Colab에서 노트북을 가볍고 재현 가능하게 유지하기 위해 필요한 모듈만 임포트합니다. getpass()를 통해 API 키를 입력받아, 키가 노트북 출력이나 코드에 노출되지 않도록 숨깁니다. 그런 다음 OpenAI 클라이언트를 생성하고 모델 문자열을 한 번 설정하여 시스템의 나머지 부분에서 일관되게 재사용할 수 있도록 합니다.

코드 복사 완료 [코드 스니펫] KB = [ ... ] ... (이하 코드 생략)

에이전트가 도구 호출을 통해 참조할 수 있도록 플레이북이나 팀 문서를 시뮬레이션하는 소규모 내부 '지식 베이스(Knowledge Base)'를 정의합니다. 도구의 출력 결과를 머신이 읽을 수 있고 견고하게 유지하기 위해 구조화된 딕셔너리 형태로 반환하는 도구들을 구현합니다. 여기에는 안전한 계산기, 키워드 기반 KB 검색, 구조화된 파싱을 위한 JSON 추출기, 최종 결과물을 아티팩트로 저장하기 위한 파일 작성기가 포함됩니다.

코드 복사 완료 [코드 스니펫] TOOLS = { ... } ... (이하 코드 생략)

원문 보기
원문 보기 (영어)
Editors Pick Agentic AI Tutorials In this tutorial, we build an advanced agentic AI system using the OpenAI API and a hidden terminal prompt for the API key. We design the agent as a small pipeline of specialized roles: planner, tool-using executor, and critic, so that we can separate strategy, action, and quality control. We also integrate structured tools (calculator, mini knowledge-base search, JSON extraction, and file writing) so the agent can reliably compute, retrieve guidance, produce structured outputs, and save artifacts as deliverables. Copy Code Copied Use a different Browser !pip -q install -U openai import os, json, re, math, hashlib from dataclasses import dataclass, field from typing import Any, Dict, List from getpass import getpass from openai import OpenAI if not os.environ.get("OPENAI_API_KEY"): os.environ["OPENAI_API_KEY"] = getpass("Enter OPENAI_API_KEY (hidden): ").strip() assert os.environ["OPENAI_API_KEY"], "OPENAI_API_KEY required" client = OpenAI() MODEL = "gpt-5.2" We install the OpenAI SDK and import only what we need to keep the notebook lightweight and reproducible in Colab. We take the API key via getpass() so it stays hidden and never appears in the notebook output or code. We then create an OpenAI client and set the model string once so the rest of the system can reuse it consistently. Copy Code Copied Use a different Browser KB = [ {"title": "Agent Protocol: Execution", "text": "Use tools only when necessary. Prefer short intermediate notes. Always verify numeric results."}, {"title": "Policy: Output Quality", "text": "Final answers must include steps, checks, and deliverables. Emails must include subject and next steps."}, {"title": "Playbook: Meeting Follow-up", "text": "Summarize decisions. List action items with owner and due date. Draft concise follow-up."}, ] def _safe_calc(expr: str): allowed = set("0123456789+-*/().% eE") if any(ch not in allowed for ch in expr): return {"ok": False, "error": "Invalid characters"} if re.search(r"[A-Za-z_]", expr): return {"ok": False, "error": "Variables not allowed"} try: val = eval(expr, {"__builtins__": {}}, {"math": math}) return {"ok": True, "expression": expr, "value": val} except Exception as e: return {"ok": False, "error": str(e)} def _kb_search(query: str, k: int = 3): q = query.lower() scored = [] for item in KB: hay = (item["title"] + " " + item["text"]).lower() score = sum(1 for tok in set(re.findall(r"\w+", q)) if tok in hay) scored.append((score, item)) scored.sort(key=lambda x: x[0], reverse=True) return {"ok": True, "results": [it for _, it in scored[:k]]} def _extract_json(text: str): m = re.search(r"\{.*\}", text, flags=re.DOTALL) if not m: return {"ok": False, "error": "No JSON found"} try: return {"ok": True, "json": json.loads(m.group(0))} except Exception as e: return {"ok": False, "error": str(e), "raw": m.group(0)[:1500]} def _write_file(path: str, content: str): os.makedirs(os.path.dirname(path) or ".", exist_ok=True) with open(path, "w", encoding="utf-8") as f: f.write(content) sha = hashlib.sha256(content.encode()).hexdigest()[:16] return {"ok": True, "path": path, "sha16": sha, "bytes": len(content.encode("utf-8"))} We define a small internal “knowledge base” to simulate playbooks or team documentation that the agent can consult via a tool call. We implement tools that return structured dictionaries to keep tool outputs machine-readable and robust. We include a safe calculator, a keyword-based KB search, a JSON extractor for structured parsing, and a file writer to save final deliverables as artifacts. Copy Code Copied Use a different Browser TOOLS = { "calc": lambda expression: _safe_calc(expression), "kb_search": lambda query, k=3: _kb_search(query, int(k)), "extract_json": lambda text: _extract_json(text), "write_file": lambda path, content: _write_file(path, content), } TOOL_SCHEMAS = [ {"type": "function","function":{"name":"calc","description":"Safely compute a numeric expression.","parameters":{"type":"object","properties":{"expression":{"type":"string"}},"required":["expression"]}}}, {"type": "function","function":{"name":"kb_search","description":"Search internal mini knowledge base.","parameters":{"type":"object","properties":{"query":{"type":"string"},"k":{"type":"integer","default":3}},"required":["query"]}}}, {"type": "function","function":{"name":"extract_json","description":"Extract and parse first JSON object from text.","parameters":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"]}}}, {"type": "function","function":{"name":"write_file","description":"Write content to a file path.","parameters":{"type":"object","properties":{"path":{"type":"string"},"content":{"type":"string"}},"required":["path","content"]}}}, ] @dataclass class AgentState: goal: str memory: List[str] = field(default_factory=list) trace: List[Dict[str, Any]] = field(default_factory=list) def chat(messages, tools=None, tool_choice="auto", temperature=0.2): kwargs = dict( model=MODEL, messages=messages, temperature=temperature, ) if tools is not None: kwargs["tools"] = tools kwargs["tool_choice"] = tool_choice return client.chat.completions.create(**kwargs) def run_tool(name, args): fn = TOOLS.get(name) if not fn: return {"ok": False, "error": f"Unknown tool: {name}"} try: return fn(**args) except Exception as e: return {"ok": False, "error": str(e), "args": args} We register our Python tools in a mapping so we can call them by name during function-calling. We declare tool schemas so the model can call tools with correct argument structures. We define AgentState to store the goal, memory, and tool-call trace, allowing us to inspect what happened and debug the agent’s behavior. We implement a safe chat() wrapper that only includes tool_choice when tools are provided, preventing the 400 error you saw. Copy Code Copied Use a different Browser PLANNER_SYS = """You are a senior planner. Return STRICT JSON with keys: objective (string), steps (array of strings), tool_checkpoints (array of strings).""" EXECUTOR_SYS = """You are a tool-using executor. Use tools when needed. Keep intermediate notes short. When done, return: 1) DRAFT output 2) Verification checklist""" CRITIC_SYS = """You are a critic. Given goal + draft, return: - Issues (bullets) - Fixes (bullets) - Improved final answer (clean)""" def plan(state: AgentState): r = chat( [{"role":"system","content":PLANNER_SYS},{"role":"user","content":state.goal}], tools=None, temperature=0.1, ) txt = r.choices[0].message.content or "" parsed = _extract_json(txt) if not parsed.get("ok"): return {"objective": state.goal, "steps": ["Proceed directly (planner JSON parse failed)."], "tool_checkpoints": []} return parsed["json"] def execute(state: AgentState, plan_obj: Dict[str, Any]): msgs = [ {"role":"system","content":EXECUTOR_SYS}, {"role":"user","content":f"GOAL:\n{state.goal}\n\nPLAN:\n{json.dumps(plan_obj, indent=2)}\n\nMEMORY:\n" + "\n".join(f"- {m}" for m in state.memory[-10:])} ] for _ in range(12): r = chat(msgs, tools=TOOL_SCHEMAS, tool_choice="auto", temperature=0.2) msg = r.choices[0].message tool_calls = getattr(msg, "tool_calls", None) if tool_calls: msgs.append({"role":"assistant","content":msg.content or "", "tool_calls": tool_calls}) for tc in tool_calls: name = tc.function.name args = json.loads(tc.function.arguments or "{}") out = run_tool(name, args) state.trace.append({"tool": name, "args": args, "out": out}) msgs.append({"role":"tool","tool_call_id": tc.id, "content": json.dumps(out)}) continue return msg.content or "" return "Executor stopped (iteration limit reached)." We define three role prompts to separate the agent’s responsibilities: the planner produces a structured plan, the executor performs the task and uses tools as needed, and the critic improves the final output. We implement a plan to request strict JSON, a loop to execute model calls, detect tool calls, execute them in Python, and then feed their outputs back to the model. This creates a true tool-using agent