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
})