| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | 31 |
- list comprehension
- AGENT.PY
- 함수 signature
- # 암호(비밀번호) 분실 # 암호(비밀번호) 찾기 #오피스(doc
- LLM
- #선진국 대한민국 #선진국 #대한민국 #아이들 #청소년 #고민 #해결 #심리
- tool schema
- __repr()
- 에이전트
- MAIN.PY
- python함수 자동분석
- 인공지능
- #다산 정약용 #유배지에서 보낸 편지 #도덕 #용기 #염 #주역 #호연지기 #효제 #근검
- AI
- call model (history)
- JSON
- 엑셀
- AI Agent
- 함수 annotation
- LLM.PY
- TOOLS.PY
- xls
- 파이썬
- 함수 docstring
- Agent class
- AI MODEL
- ppt) 파일 #오피스(워드
- 파워포인트) #집(zip)파일 #아래한글(HWP) #brute-force(무차별 대입)
- 티스토리챌린지
- 오블완
- Today
- Total
아톨러브
XIII. Python 함수 자체를 분석하여, AI가 사용할 수 있는 Tool 설명서(schema)를 자동으로 만드는 방법 본문
XIII. Python 함수 자체를 분석하여, AI가 사용할 수 있는 Tool 설명서(schema)를 자동으로 만드는 방법
아톨 2026. 8. 25. 23:21
#-------- 1. Type Hint 그리고 __annotations__
def search(query:str, limit:int=3, safe:bool=True)->str:
mode = "safe" if safe else "open"
return f"'{query}'에 대해 {limit}개의 결과물({mode})"
print(search("환불 정책"))
print(search("개점 시간", limit=1, safe=False))
print(search.__annotations__)
#-------- 2. History 분석
def summarise(messages:list[dict], limit:int|None=None)->dict[str,int]:
if limit is not None:
messages=messages[-limit:]
counts:dict[str,int]={}
for m in messages:
role=m["role"]
counts[role]=counts.get(role,0)+1
return counts
history=[
{"role":"user", "content":"a"},
{"role":"assistant", "content":"b"},
{"role":"user", "content":"c"},
{"role":"tool", "content":"d"},
]
print(summarise(history))
print(summarise(history, limit=2))
#-------- 3. Docstring
def read_file(path:str, max_chars:int=2000)->str:
"""디스크에서 텍스트 파일을 읽어 그 내용을 반환합니다.
사용자가 특정 파일 이름을 지정할 때 이것을 사용합니다.
URL이나 아직 존재하지 않는 파일에는 그것을 사용하지 마십시오.
인수(Args):
path: 프로젝트 폴더를 기준으로 한 파일 경로
max_chars: 잘리기 전에 반환할 최대 문자 수
반환값(Returns):
파일 내용 또는 ERROR로 시작하는 오류 메시지
# 위내용을 영어로(아래).
Read a text file from disk and return its contents.
Use this when the user refers to a specific file by name.
Do not use it for URLs or for files that do not exist yet.
Args:
path: Path to the file, relative to the project folder.
max_chars: Maximum characters to return before truncating.
Returns:
The file contents, or an error message starting with ERROR.
"""
return f"contents of {path} (up to {max_chars} chars)"
print(read_file.__doc__.strip().splitlines()[0])
print("-----")
print(read_file("notes.txt"))
#-------- 4. 수동으로 Tool Schema 만들기
import json
schema={
"name":"read_file",
"description":"Read a file from disk and return its contents.",
"input_schema":{
"type":"object",
"properties":{
"path":{
"type":"string",
"description":"Path to the file, relative to the project folder.",
},
"max_chars":{
"type":"integer",
"description":"Maximum characters to return before truncating.",
},
},
"required":["path"]
},
}
print(json.dumps(schema, indent=2))
#-------- 5. inspect.signature() 그리고 inspect.getdoc()
import inspect
def search(query:str, limit:int=3)->str:
"""Search the knowledge base for a phrase."""
return "..."
sig=inspect.signature(search)
for name, param in sig.parameters.items():
required=param.default is inspect.Parameter.empty
print(f"{name:<8} type={param.annotation.__name__:<5} required={required}")
print("summary: ", inspect.getdoc(search))
#-------- 6. check_args() : Tool 실행 전, 인자검사
import inspect
def check_args(func, args):
sig=inspect.signature(func)
required={n for n, p in sig.parameters.items() if p.default is inspect.Parameter.empty}
allowed=set(sig.parameters)
missing=required-set(args)
unknown=set(args)-allowed
if missing:
return f"missing required argument(s): {sorted(missing)}"
if unknown:
return f"unexpected argument(s): {sorted(unknown)}"
return None
def search(query:str, limit:int=3)->str:
"""Search the knowledge base."""
return f"{limit} hits for {query}"
for args in [{"query":"refunds"}, {"limit":5}, {"query":"x", "sort":"asc"}]:
problem=check_args(search,args)
print(args, "->", problem or search(**args))
#-------- 7. Tool Schema 자동 생성
import inspect
import json
JSON_TYPES = {
str: "string",
int: "integer",
float: "number",
bool: "boolean",
list: "array",
dict: "object",
}
def describe_tool(func):
sig=inspect.signature(func)
doc=inspect.getdoc(func) or "No description provided"
summary=doc.strip().splitlines()[0]
properties={}
required=[]
for name, param in sig.parameters.items():
json_type=JSON_TYPES.get(param.annotation,"string")
entry={"type":json_type, "description":f"{name}({json_type})"}
if param.default is inspect.Parameter.empty:
required.append(name)
else:
entry["description"] += f", defaults to {param.default!r}"
properties[name] = entry
return {
"name":func.__name__,
"description":summary,
"input_schema":{
"type":"object",
"properties":properties,
"required":required,
},
}
def calculator(expr:str)->str:
"""Evaluate a simple arithmetic expression such as 20*5."""
return str(eval(expr))
def read_file(path:str, max_chars:int=2000)->str:
"""Read a text file from disk. Do not use this for URLs."""
return f"contents of {path}"
REGISTRY={"calculator":calculator, "read_file":read_file}
schemas=[describe_tool(f) for f in REGISTRY.values()]
print(json.dumps(schemas, indent=2))
이 부분은 앞으로 실제 OpenAI API와 Tool Calling을 연결할 때 매우 중요합니다.
먼저 전체 그림부터 보겠습니다
우리가 이전까지는 Tool을 이렇게 만들었습니다.
def calculator(expr):
return eval(expr)
TOOLS = {
"calculator": calculator
}
그런데 AI 입장에서는 문제가 있습니다.
AI:
"calculator라는 Tool이 있군."
그런데...
"어떤 값을 넣어야 하지?"
"expr이라는 인자가 필요한가?"
"숫자인가? 문자열인가?"
"필수인가?"
그래서 AI에게 Tool 설명서를 줘야 합니다.
Tool 설명서
│
├── 이름: calculator
│
├── 설명:
│ 수식을 계산합니다
│
└── 입력값
│
└── expr
│
├── type: string
└── required: true
이번 코드 전체는 바로 이 과정을 배우는 것입니다.
전체 학습 흐름
이번 코드를 순서대로 보면:
1. Type Hint
↓
2. 함수 Annotation
↓
3. 함수 Docstring
↓
4. 함수 Signature 검사
↓
5. 함수 인자 검증
↓
6. Python 함수 자동 분석
↓
7. Tool Schema 자동 생성
↓
8. 여러 Tool을 AI에게 전달
즉, 마지막 목표는:
Python 함수
↓
inspect
↓
자동 분석
↓
JSON Schema
↓
LLM에게 Tool 설명 전달
입니다.
1. 첫 번째 코드 — Type Hint
def search(query:str, limit:int=3, safe:bool=True)->str:
이 코드가 이번 학습의 시작입니다.
천천히 분해해 보겠습니다.
def search(
query: str,
limit: int = 3,
safe: bool = True
) -> str:
각 부분의 의미
search
│
├── query
│ └── str
│
├── limit
│ ├── int
│ └── 기본값 3
│
├── safe
│ ├── bool
│ └── 기본값 True
│
└── 반환값
└── str
query: str
query: str
뜻은:
query라는 변수는 문자열(str)을 사용하는 것이 좋습니다.
예:
search("환불 정책")
여기서:
query
↓
"환불 정책"
↓
str
limit: int = 3
limit: int = 3
뜻:
자료형 → int
기본값 → 3
따라서:
search("환불 정책")
이면:
query = "환불 정책"
limit = 3
자동으로 됩니다.
하지만:
search("환불 정책", limit=10)
하면:
limit = 10
으로 바뀝니다.
safe: bool = True
safe: bool = True
뜻:
safe
│
├── True
└── False
두 가지 값 중 하나를 사용하는 Boolean 값이라는 뜻입니다.
-> str
def search(...) -> str:
이 함수가 최종적으로 문자열을 반환한다는 의미입니다.
예:
return "검색 결과입니다."
중요한 점
Type Hint는 일반적으로 Python이 자동으로 강제하지 않습니다.
예:
def search(query: str):
...
라고 해도:
search(123)
이 무조건 즉시 오류가 나는 것은 아닙니다.
즉:
Type Hint
=
"이 값을 이런 타입으로 사용하는 것이 좋습니다"
+
도구가 함수 구조를 이해하는 데 도움
입니다.
함수 실행
def search(query:str, limit:int=3, safe:bool=True)->str:
mode = "safe" if safe else "open"
return f"'{query}'에 대해 {limit}개의 결과물({mode})"
삼항 조건문
mode = "safe" if safe else "open"
이것은 전에 배운 조건문을 한 줄로 쓴 것입니다.
풀어서 쓰면:
if safe:
mode = "safe"
else:
mode = "open"
입니다.
실행 1
search("환불 정책")
기본값:
query = "환불 정책"
limit = 3
safe = True
따라서:
mode = "safe"
결과:
'환불 정책'에 대해 3개의 결과물(safe)
실행 2
search("개점 시간", limit=1, safe=False)
query = "개점 시간"
limit = 1
safe = False
따라서:
mode = "open"
결과:
'개점 시간'에 대해 1개의 결과물(open)
2. __annotations__
print(search.__annotations__)
이것이 이번 코드에서 중요한 부분입니다.
함수를 만들 때:
def search(
query: str,
limit: int = 3,
safe: bool = True
) -> str:
Python은 Annotation 정보를 저장해 둡니다.
대략:
{
"query": str,
"limit": int,
"safe": bool,
"return": str
}
입니다.
구조를 그림으로 보면:
search 함수
│
├── query → str
├── limit → int
├── safe → bool
└── return → str
왜 이것이 중요한가?
앞으로 우리가 만들고 싶은 것은:
Python 함수
def search(query: str, limit: int = 3):
를 자동으로 읽어서:
{
"name": "search",
"parameters": {
"query": {
"type": "string"
},
"limit": {
"type": "integer"
}
}
}
로 바꾸는 것입니다.
즉:
Python Type Hint → AI Tool Schema
로 발전하게 됩니다.

3. 두 번째 코드 — History 분석
def summarise(
messages:list[dict],
limit:int|None=None
)->dict[str,int]:
이번에는 Type Hint가 조금 더 발전했습니다.
messages:list[dict]
messages: list[dict]
뜻:
messages는 Dictionary가 들어 있는 List입니다.
우리가 지금까지 Agent에서 사용한 History와 정확히 같습니다.
history = [
{"role":"user", "content":"a"},
{"role":"assistant", "content":"b"},
{"role":"user", "content":"c"},
{"role":"tool", "content":"d"},
]
그림으로 보면:
history
│
├── [0]
│ ├── role = user
│ └── content = a
│
├── [1]
│ ├── role = assistant
│ └── content = b
│
├── [2]
│ ├── role = user
│ └── content = c
│
└── [3]
├── role = tool
└── content = d
limit: int | None = None
조금 중요합니다.
limit: int | None = None
뜻:
limit에는
int
또는
None
둘 중 하나가 들어올 수 있다.
예:
summarise(history, limit=3)
또는:
summarise(history, limit=None)
가능합니다.
예전 방식
Python에서는 예전에는 보통:
Optional[int]
또는:
Union[int, None]
를 사용했습니다.
하지만 현재는:
int | None
처럼 간단하게 쓸 수 있습니다.
반환값
-> dict[str, int]
뜻:
Dictionary
Key → str
Value → int
예:
{
"user": 2,
"assistant": 1,
"tool": 1
}
입니다.
함수 내부
if limit is not None:
messages = messages[-limit:]
이 부분은 Agent History와 직접 연결됩니다.
예:
messages = [
A,
B,
C,
D
]
그리고:
limit = 2
이면:
messages[-2:]
결과:
[C, D]
입니다.
왜 Agent에서 중요한가?
Agent는 대화가 길어질 수 있습니다.
1번째 메시지
2번째 메시지
3번째 메시지
...
100번째 메시지
...
1000번째 메시지
이 모든 것을 LLM에 보내면:
- 비용 증가
- 응답 속도 감소
- Context Window 문제
가 발생합니다.
그래서:
messages = messages[-limit:]
처럼 최근 기록만 유지할 수 있습니다.
이것이 이전에 배운:
History Trim
과 연결됩니다.
counts
counts:dict[str,int] = {}
뜻:
counts는 Dictionary
Key → 문자열
Value → 정수
처음에는:
{}
빈 Dictionary입니다.
for 반복
for m in messages:
예:
messages = [
{"role":"user"},
{"role":"assistant"},
{"role":"user"}
]
실행:
1회차
m = {"role":"user"}
2회차
m = {"role":"assistant"}
3회차
m = {"role":"user"}
role 추출
role = m["role"]
예:
m = {
"role":"user",
"content":"a"
}
그러면:
role = "user"
가장 중요한 부분
counts[role] = counts.get(role, 0) + 1
이 부분을 천천히 보겠습니다.
첫 번째 user
처음:
counts = {}
role = "user"
그러면:
counts.get("user", 0)
"현재 user라는 Key가 있니?"
없습니다.
그러면 기본값:
0
따라서:
counts["user"] = 0 + 1
결과:
{
"user": 1
}
assistant
role = "assistant"
현재:
{
"user": 1
}
assistant 없음.
counts.get("assistant", 0)
↓
0
따라서:
counts["assistant"] = 1
결과:
{
"user": 1,
"assistant": 1
}
다시 user
counts.get("user", 0)
↓
1
따라서:
counts["user"] = 1 + 1
↓
2
최종:
{
"user": 2,
"assistant": 1,
"tool": 1
}
4. Docstring
다음 코드입니다.
def read_file(path:str, max_chars:int=2000)->str:
"""디스크에서 텍스트 파일을 읽어 그 내용을 반환합니다.
함수 안에 들어 있는 여러 줄 문자열입니다.
이것을 Docstring이라고 합니다.
일반 문자열과 Docstring의 차이
일반:
text = "hello"
Docstring:
def hello():
"""이 함수는 인사합니다."""
Docstring은 함수 설명으로 사용됩니다.
__doc__
print(read_file.__doc__)
하면 함수의 설명을 가져올 수 있습니다.
즉:
read_file 함수
│
├── 이름
│
├── 코드
│
└── Docstring
입니다.
이번 코드
print(
read_file.__doc__
.strip()
.splitlines()[0]
)
체인처럼 연결되어 있습니다.
하나씩 보겠습니다.
read_file.__doc__
Docstring 전체를 가져옵니다.
↓
디스크에서 텍스트 파일을 읽어...
...
.strip()
앞뒤 공백과 줄바꿈 제거
.splitlines()
줄 단위로 나눕니다.
[
"디스크에서 텍스트 파일을 읽어 그 내용을 반환합니다.",
"",
"사용자가 특정 파일 이름을 지정할 때..."
]
[0]
첫 번째 줄
디스크에서 텍스트 파일을 읽어 그 내용을 반환합니다.
왜 Tool에서 Docstring이 중요한가?
AI에게:
Tool 이름: read_file
만 알려주면 부족합니다.
AI가:
이걸 언제 사용하지?
라고 생각할 수 있습니다.
그래서:
Tool: read_file
설명:
Read a text file from disk.
언제 사용하는가:
사용자가 특정 파일 이름을 언급했을 때
언제 사용하지 않는가:
URL에는 사용하지 말 것
같은 설명을 제공합니다.
이 설명이 바로 LLM의 Tool 선택 능력을 높입니다.
5. 수동으로 Tool Schema 만들기
schema = {
"name":"read_file",
"description":"Read a file from disk and return its contents.",
이제 본격적으로 AI에게 Tool 설명서를 만들기 시작합니다.
구조:
schema
│
├── name
│
├── description
│
└── input_schema
name
"name": "read_file"
AI가 Tool을 호출할 때:
"read_file라는 Tool을 사용하고 싶습니다."
라고 지정할 수 있습니다.
description
"description":
"Read a file from disk and return its contents."
AI에게:
이 Tool은 파일을 읽는 도구입니다.
라고 알려줍니다.
input_schema
"input_schema": {
"type":"object",
뜻:
입력값 전체가 Object 구조입니다.
Python 관점에서는 거의:
dict
라고 생각하면 됩니다.
예:
{
"path":"notes.txt",
"max_chars":2000
}
properties
"properties": {
실제 입력값의 구조를 정의합니다.
properties
│
├── path
│
└── max_chars
path
"path": {
"type":"string",
"description":"Path to the file..."
}
AI에게:
path라는 값을 넣어야 합니다.
자료형:
string
설명:
프로젝트 폴더 기준 파일 경로
max_chars
"max_chars": {
"type":"integer"
}
max_chars
↓
integer
입니다.
required
"required":["path"]
뜻:
반드시 필요한 인자
path
반면:
max_chars
는 기본값이 있으므로 선택사항입니다.
왜 Schema가 필요한가?
AI에게 이렇게 말하는 것과:
read_file라는 함수가 있어.
이렇게 말하는 것은 차이가 큽니다.
{
"name": "read_file",
"description": "Read a file from disk",
"input_schema": {
"properties": {
"path": {
"type": "string"
},
"max_chars": {
"type": "integer"
}
},
"required": [
"path"
]
}
}
AI는:
무슨 Tool인가?
→ read_file
언제 사용하는가?
→ 파일을 읽을 때
무슨 값을 넣어야 하는가?
→ path
어떤 타입인가?
→ string
필수인가?
→ yes
를 알 수 있습니다.
6. inspect.signature()
이제 굉장히 재미있는 부분입니다.
import inspect
inspect는 Python 코드 자체를 조사하는 도구입니다.
예
def search(query:str, limit:int=3)->str:
inspect를 사용하면 Python이 함수에게 묻습니다.
"너 이름이 뭐야?"
"어떤 인자가 필요해?"
"기본값은 뭐야?"
"자료형은 뭐야?"
"Docstring은 뭐야?"
signature
sig = inspect.signature(search)
대략:
sig
(
query: str,
limit: int = 3
) -> str
라는 함수 구조 정보를 가져옵니다.
parameters
sig.parameters
대략:
{
"query": Parameter(...),
"limit": Parameter(...)
}
입니다.
반복문
for name, param in sig.parameters.items():
첫 번째:
name = "query"
param = query의 상세 정보
두 번째:
name = "limit"
param = limit의 상세 정보
필수 인자 판단
required = (
param.default is inspect.Parameter.empty
)
이 부분이 매우 중요합니다.
query
query: str
기본값이 없습니다.
따라서:
param.default
↓
Parameter.empty
그래서:
required = True
limit
limit: int = 3
기본값이 있습니다.
param.default
↓
3
따라서:
required = False
출력
print(
f"{name:<8} "
f"type={param.annotation.__name__:<5} "
f"required={required}"
)
여기서:
{name:<8}
이전에 배운 format specifier입니다.
<8
↓
왼쪽 정렬
8칸
예:
query type=str required=True
limit type=int required=False
보기 좋게 정렬됩니다.
7. inspect.getdoc()
print(
"summary: ",
inspect.getdoc(search)
)
이 함수의 Docstring을 가져옵니다.
def search(...):
"""Search the knowledge base for a phrase."""
결과:
Search the knowledge base for a phrase.
지금까지 중요한 연결
여기까지:
Python 함수
def search(
query: str,
limit: int = 3
) -> str:
"""Search the knowledge base."""
에서 자동으로:
inspect
↓
이름: search
↓
인자: query, limit
↓
타입: str, int
↓
기본값: 없음, 3
↓
Docstring: Search the knowledge base
를 얻었습니다.
이 정보를 나중에 JSON으로 만들면 Tool Schema가 됩니다.
8. check_args()
이제 실제 Tool 실행 전에 인자를 검사합니다.
def check_args(func, args):
입력:
func
↓
실행할 함수
args
↓
AI가 보내온 인자
예:
func = search
args = {
"query": "refunds"
}
함수 구조 조사
sig = inspect.signature(func)
예:
search(query:str, limit:int=3)
를 분석합니다.
required 만들기
required = {
n
for n, p in sig.parameters.items()
if p.default is inspect.Parameter.empty
}
이것은 Set Comprehension입니다.
결과:
{"query"}
입니다.
왜냐하면:
query
→ 기본값 없음
→ 필수
limit
→ 기본값 3
→ 선택
이기 때문입니다.
allowed
allowed = set(sig.parameters)
결과:
{
"query",
"limit"
}
즉:
이 함수가 허용하는 인자 목록
입니다.
missing
missing = required - set(args)
예:
args = {
"limit": 5
}
그러면:
required
=
{"query"}
set(args)
=
{"limit"}
따라서:
{"query"} - {"limit"}
=
{"query"}
즉:
query가 빠졌다!
입니다.
unknown
unknown = set(args) - allowed
예:
args = {
"query":"x",
"sort":"asc"
}
그러면:
set(args)
=
{"query", "sort"}
allowed
=
{"query", "limit"}
따라서:
{"query", "sort"}
-
{"query", "limit"}
=
{"sort"}
즉:
sort라는 예상하지 않은 인자가 있다.
입니다.
실행 결과
정상
{"query":"refunds"}
↓
search(query="refunds")
↓
3 hits for refunds
필수 인자 없음
{"limit":5}
↓
missing required argument(s): ['query']
이상한 인자
{
"query":"x",
"sort":"asc"
}
↓
unexpected argument(s): ['sort']
이것이 Agent에서 왜 중요한가?
LLM이 Tool을 호출할 때:
{
"tool": "search",
"arguments": {
"query": "환불 정책",
"sort": "price"
}
}
라고 할 수 있습니다.
하지만 실제 함수는:
def search(query, limit=3):
입니다.
sort라는 인자는 없습니다.
따라서 실행 전에:
LLM
↓
Tool 요청
↓
check_args()
↓
정상?
│
├── YES → Tool 실행
│
└── NO
↓
Error Observation
↓
다시 LLM
이 구조가 됩니다.
9. 마지막 코드 — Tool Schema 자동 생성
이제 이번 코드의 핵심입니다.
JSON_TYPES = {
str: "string",
int: "integer",
float: "number",
bool: "boolean",
list: "array",
dict: "object",
}
Python Type과 JSON Type을 연결하는 Dictionary입니다.
구조
Python Type
↓
JSON Type
str
↓
string
int
↓
integer
float
↓
number
bool
↓
boolean
list
↓
array
dict
↓
object
왜 필요한가?
Python 함수는:
def search(
query: str,
limit: int
):
이지만 AI Tool Schema는:
{
"query": {
"type": "string"
},
"limit": {
"type": "integer"
}
}
를 원합니다.
따라서 변환기가 필요합니다.
Python
str
↓
JSON_TYPES
↓
string
describe_tool()
def describe_tool(func):
이 함수의 목표는 단 하나입니다.
Python 함수
↓
describe_tool()
↓
Tool Schema Dictionary
함수 Signature 가져오기
sig = inspect.signature(func)
예:
def read_file(
path: str,
max_chars: int = 2000
):
↓
sig
│
├── path
│ ├── annotation = str
│ └── default = 없음
│
└── max_chars
├── annotation = int
└── default = 2000
Docstring 가져오기
doc = inspect.getdoc(func) or "No description provided"
뜻:
Docstring이 있으면
↓
사용
없으면
↓
"No description provided"
첫 번째 줄만 사용
summary = doc.strip().splitlines()[0]
예:
"""Read a text file from disk.
Do not use this for URLs."""
↓
첫 번째 줄:
Read a text file from disk.
이것을 Tool 설명으로 사용합니다.
properties와 required 준비
properties = {}
required = []
처음에는:
properties = {}
required = []
입니다.
모든 Parameter 분석
for name, param in sig.parameters.items():
예:
def read_file(
path: str,
max_chars: int = 2000
)
첫 번째:
name = path
두 번째:
name = max_chars
Python Type → JSON Type
json_type = JSON_TYPES.get(
param.annotation,
"string"
)
예:
path
annotation = str
JSON_TYPES[str]
↓
string
max_chars
annotation = int
JSON_TYPES[int]
↓
integer
entry 만들기
entry = {
"type": json_type,
"description": f"{name}({json_type})"
}
예:
path
↓
{
"type": "string",
"description": "path(string)"
}
필수 인자인가?
if param.default is inspect.Parameter.empty:
예:
path: str
기본값 없음.
required.append(name)
↓
required = [
"path"
]
기본값이 있다면?
max_chars: int = 2000
↓
entry["description"] += (
f", defaults to {param.default!r}"
)
여기서:
!r
는 전에 배운 repr()과 관련됩니다.
예:
2000
은 그대로 표시됩니다.
문자열이라면:
'hello'
처럼 표현됩니다.
결과:
max_chars(integer), defaults to 2000
properties에 저장
properties[name] = entry
첫 번째:
properties = {
"path": {
"type":"string",
"description":"path(string)"
}
}
두 번째까지 추가하면:
properties = {
"path": {
"type":"string",
"description":"path(string)"
},
"max_chars": {
"type":"integer",
"description":"max_chars(integer), defaults to 2000"
}
}
최종 반환
return {
"name": func.__name__,
"description": summary,
"input_schema": {
"type": "object",
"properties": properties,
"required": required,
},
}
결과 구조:
Tool Schema
│
├── name
│
├── description
│
└── input_schema
│
├── type
│
├── properties
│ │
│ ├── path
│ └── max_chars
│
└── required
실제 Tool 두 개
def calculator(expr:str)->str:
Tool 1:
calculator
│
└── expr
└── string
def read_file(
path:str,
max_chars:int=2000
)->str:
Tool 2:
read_file
│
├── path
│ └── string
│
└── max_chars
└── integer
Registry
REGISTRY = {
"calculator": calculator,
"read_file": read_file
}
우리가 이미 배운 구조입니다.
REGISTRY
│
├── calculator
│ ↓
│ calculator 함수
│
└── read_file
↓
read_file 함수
모든 Tool 자동 분석
schemas = [
describe_tool(f)
for f in REGISTRY.values()
]
이것은 List Comprehension입니다.
풀어서 쓰면:
schemas = []
for f in REGISTRY.values():
schema = describe_tool(f)
schemas.append(schema)
입니다.
전체 흐름
REGISTRY
│
├── calculator
│ ↓
│ describe_tool()
│ ↓
│ Tool Schema
│
└── read_file
↓
describe_tool()
↓
Tool Schema
↓
schemas = [
calculator schema,
read_file schema
]
최종 결과의 의미
결국 Python 코드:
def calculator(expr: str) -> str:
"""Evaluate a simple arithmetic expression."""
를 AI가 이해할 수 있는:
{
"name": "calculator",
"description": "Evaluate a simple arithmetic expression.",
"input_schema": {
"type": "object",
"properties": {
"expr": {
"type": "string"
}
},
"required": [
"expr"
]
}
}
로 자동 변환한 것입니다.
이번 코드가 Agent 전체에서 어디에 있는가?
매우 중요합니다.
이제 Agent 전체 구조를 보면:
Python Tool
def calculator(expr: str):
...
│
▼
Type Hint
Docstring
Signature
│
▼
inspect
│
▼
describe_tool()
│
▼
Tool Schema
│
▼
LLM에게 전달
│
▼
LLM:
"calculator를 사용해야겠다"
│
▼
{
"tool": "calculator",
"arguments": {
"expr": "7 * 24"
}
}
│
▼
check_args()
│
├── 문제 있음
│ ↓
│ Error
│
▼
REGISTRY
│
▼
TOOLS["calculator"](**args)
│
▼
calculator("7 * 24")
│
▼
Observation
│
▼
History
│
▼
LLM 재호출
이번 코드의 가장 중요한 핵심 3가지
① Type Hint는 단순한 설명이 아니다
처음에는:
query: str
이 단순히:
query는 문자열입니다.
라고 알려주는 용도로 보입니다.
하지만 Agent에서는:
Type Hint
↓
inspect
↓
자동 분석
↓
JSON Schema
↓
LLM이 Tool 사용법 이해
로 연결됩니다.
② Docstring은 사람뿐 아니라 AI에게도 중요하다
def read_file(...):
"""Read a text file from disk."""
이 설명은:
사람
↓
이 함수가 뭔지 이해
AI
↓
언제 이 Tool을 사용할지 판단
하는 데 사용됩니다.
③ Python 함수에서 AI Tool을 자동으로 만들 수 있다
이것이 이번 코드의 가장 중요한 목적입니다.
우리가 직접:
schema = {
...
}
를 일일이 만들 수도 있지만,
함수
↓
inspect
↓
자동 Schema 생성
이 가능해집니다.
'AI, 클라우드, 문서, 자동화 > AI_AGENT' 카테고리의 다른 글
| XII. Python 프로그램이 인터넷 너머의 API 서버와 대화하는 방법 → 오류, 재시도 ...→ 결국 실제 LLM 호출 함수 call_model()로 발전시키는 과정 (1) | 2026.08.25 |
|---|---|
| XI. Agent의 핵심 부품을 여러 파일로 나누어 실제 프로젝트 구조 만들기 (0) | 2026.08.24 |
| X. Agent의 여러 요소를 하나의 class Agent로 묶어가는 과정. (0) | 2026.08.24 |
| IX. AI Agent에 “기억과 기록” 기능을 붙이는 단계 (0) | 2026.08.23 |
| VIII. Agent Loop + Tool Dispatcher 다음 단계인 예외 처리(Exception), 재시도(Retry), Logging 스터디 (0) | 2026.08.23 |
