콘텐츠로 이동

42. Function Call 활용

Video

준비 중

Note

프롬프트

developer message

당신은 일정 메모 비서입니다.
사용자의 일정을 기록하고 관리해야 합니다.

tools - Function

{
  "name": "read_memo",
  "description": "고정된 메모 파일을 읽습니다. 파일 전체 내용을 반환하며, 옵션으로 줄 번호 포함 여부를 지정할 수 있습니다. 줄 번호는 실제 파일에는 저장되지 않고 반환 시 참고용으로만 포함됩니다.",
  "strict": false,
  "parameters": {
    "type": "object",
    "properties": {
      "with_lines": {
        "type": "boolean",
        "description": "true면 줄 번호와 각 줄 내용을 함께 반환",
        "default": false
      }
    },
    "additionalProperties": false
  }
}
{
  "name": "write_memo",
  "description": "고정된 메모 파일에 씁니다. mode로 덮어쓰기/삽입/추가를 선택할 수 있습니다.",
  "strict": false,
  "parameters": {
    "type": "object",
    "properties": {
      "content": {
        "type": "string",
        "description": "기록할 내용 (덮어쓰기 전체 내용 또는 삽입/추가할 한 줄)"
      },
      "mode": {
        "type": "string",
        "enum": ["overwrite", "insert", "append"],
        "description": "overwrite=전체 덮어쓰기, insert=특정 줄 삽입, append=끝에 추가",
        "default": "overwrite"
      },
      "line_number": {
        "type": "integer",
        "description": "insert 모드에서 삽입할 줄 번호(1부터). 범위를 벗어나면 끝에 추가"
      }
    },
    "required": ["content"],
    "additionalProperties": false
  }
}
{
  "name": "delete_memo",
  "description": "고정된 메모 파일에서 여러 줄을 삭제합니다. 줄 번호는 1부터 시작합니다.",
  "strict": true,
  "parameters": {
    "type": "object",
    "properties": {
      "line_numbers": {
        "type": "array",
        "items": { "type": "integer" },
        "minItems": 1,
        "description": "삭제할 줄 번호 목록 (예: [2, 4, 5])"
      }
    },
    "required": ["line_numbers"],
    "additionalProperties": false
  }
}

function_call.py

function_call.py
from pathlib import Path
from typing import Dict, Any, List

MEMO_FILE = Path("test/memo.txt")

def read_memo(with_lines: bool = False) -> Dict[str, Any]:
    if not MEMO_FILE.exists():
        return {"ok": True, "action": "read", "text": ""} if not with_lines else \
               {"ok": True, "action": "read", "text": "", "lines": []}
    lines = MEMO_FILE.read_text(encoding="utf-8").splitlines()
    result = {"ok": True, "action": "read", "text": "\n".join(lines)}
    if with_lines:
        result["lines"] = [{"line": i + 1, "text": line} for i, line in enumerate(lines)]
    return result

def write_memo(content: str, mode: str = "overwrite", line_number: int | None = None) -> Dict[str, Any]:
    mode = mode.lower()
    if mode == "overwrite":
        MEMO_FILE.write_text(content, encoding="utf-8")
        return {"ok": True, "action": "overwrite", "text": content}
    if mode == "append":
        with MEMO_FILE.open("a", encoding="utf-8") as f:
            f.write(content)
        return {"ok": True, "action": "append", "content": content,
                "lines": read_memo(with_lines=True)["lines"]}
    if mode == "insert":
        lines: List[str] = []
        if MEMO_FILE.exists():
            lines = MEMO_FILE.read_text(encoding="utf-8").splitlines()
        idx = max(0, min((line_number or len(lines)+1) - 1, len(lines)))
        lines.insert(idx, content)
        MEMO_FILE.write_text("\n".join(lines), encoding="utf-8")
        return {"ok": True, "action": "insert", "inserted_line": line_number,
                "content": content, "lines": read_memo(with_lines=True)["lines"]}
    return {"ok": False, "action": "write", "error": f"알 수 없는 mode: {mode}"}

def delete_memo(line_numbers: List[int]) -> Dict[str, Any]:
    """
    여러 줄 삭제 (줄 번호 1부터). 예: [2, 4, 5]
    - 중복은 자동 제거
    - 범위를 벗어난 줄은 skipped로 반환
    """
    if not MEMO_FILE.exists():
        return {"ok": False, "action": "delete", "error": "파일 없음"}

    if not line_numbers:
        return {"ok": False, "action": "delete", "error": "line_numbers가 비어 있습니다."}

    lines = MEMO_FILE.read_text(encoding="utf-8").splitlines()
    n = len(lines)

    req_set = set(line_numbers)                    # 중복 제거
    valid = sorted([ln for ln in req_set if 1 <= ln <= n], reverse=True)  # 뒤에서부터 삭제
    skipped = sorted([ln for ln in req_set if ln < 1 or ln > n])
    deleted_items = []

    for ln in valid:
        deleted_items.append({"line": ln, "text": lines[ln - 1]})
        del lines[ln - 1]

    MEMO_FILE.write_text("\n".join(lines), encoding="utf-8")

    return {
        "ok": True,
        "action": "delete",
        "requested_lines": sorted(req_set),
        "deleted_lines": sorted([x["line"] for x in deleted_items]),
        "deleted_items": sorted(deleted_items, key=lambda x: x["line"]),
        "skipped_lines": skipped,
        "lines": read_memo(with_lines=True)["lines"]
    }

test.py

test.py
import yaml
from dotenv import load_dotenv
from openai import OpenAI

with open("config.yaml") as f:
    config = yaml.safe_load(f)

load_dotenv()
client = OpenAI()


def time_now(time_zone="Asia/Seoul"):
    from datetime import datetime
    from zoneinfo import ZoneInfo
    return datetime.now(ZoneInfo(time_zone)).strftime("%Y-%m-%d %H:%M:%S")

import json
import function_call

previous_response_id = None
function_call_outputs = []
while True:
    if function_call_outputs:
        response = client.responses.create(
            input=function_call_outputs,
            previous_response_id=previous_response_id,
            **config
        )
    else:
        input_message = input("User: ")
        input_message += f"\n(현재 시각: {time_now()})"
        response = client.responses.create(
            input=input_message,
            previous_response_id=previous_response_id,
            **config
        )

    previous_response_id = response.id
    output_text = response.output_text
    if output_text:
        print("Assistant: ", output_text)

    function_call_outputs = []
    for output in response.output:
        if output.type == "function_call":
            try:
                func = getattr(function_call, output.name)
                args = json.loads(output.arguments)
                func_output = str(func(**args))
            except Exception as e:
                func_output = str(e)
            finally:
                print(f"\n[Function Call: {output.name}]\n{func_output}\n")
                function_call_outputs.append({
                    "type": "function_call_output",
                    "call_id": output.call_id,
                    "output": func_output
                })
test.py
import yaml
from dotenv import load_dotenv
from openai import OpenAI

with open("config.yaml") as f:
    config = yaml.safe_load(f)

load_dotenv()
client = OpenAI()


def time_now(time_zone="Asia/Seoul"):
    from datetime import datetime
    from zoneinfo import ZoneInfo
    return datetime.now(ZoneInfo(time_zone)).strftime("%Y-%m-%d %H:%M:%S")

import json
import function_call

previous_response_id = None
function_call_outputs = []
while True:
    if function_call_outputs:
        response = client.responses.create(
            input=function_call_outputs,
            previous_response_id=previous_response_id,
            stream=True,
            **config
        )
    else:
        input_message = input("\nUser: ")
        input_message += f"\n(현재 시각: {time_now()})"
        response = client.responses.create(
            input=input_message,
            previous_response_id=previous_response_id,
            stream=True,
            **config
        )

    function_call_outputs = []
    print("Assistant: ", end="", flush=True)
    for event in response:
        if event.type == "response.output_text.delta":
            print(event.delta, end="", flush=True)
        elif event.type == "response.completed":
            previous_response_id = event.response.id
        elif (event.type == "response.output_item.done") and (event.item.type == "function_call"):
            try:
                func = getattr(function_call, event.item.name)
                args = json.loads(event.item.arguments)
                func_output = str(func(**args))
            except Exception as e:
                func_output = str(e)
            finally:
                print(f"\n[Function Call: {event.item.name}]\n{func_output}\n")
                function_call_outputs.append({
                    "type": "function_call_output",
                    "call_id": event.item.call_id,
                    "output": func_output
                })

Resources