Function calling là gì — và tại sao nó là não của mọi AI coding assistant ?
Bạn có bao giờ tự hỏi tại sao Claude Code hay Cursor biết đọc file của bạn, chạy command, rồi tự sửa bug — trong khi ChatGPT chỉ... trả lời bằng text? Câu trả lời nằm ở function calling. Và khi bạn hiểu nó, mọi AI agents sẽ không còn là hộp đen nữa.
Vấn đề: LLM là cái hộp kín
Một LLM thuần túy hoạt động rất đơn giản:
Input: text
Output: text
Không hơn, không kém. Nó không thể đọc file của bạn. Không thể chạy command. Không thể biết project của bạn đang có gì. Thử tưởng tượng bạn nhắn với ChatGPT:
"Đọc file
src/main.tsvà fix bug cho tôi" Phản hồi có thể nhận được: "Bạn có thể paste nội dung file vào đây không? Tôi không đọc file trực tiếp được..."
Đây chính xác là giới hạn của một LLM không có function calling — biết nghĩ nhưng không làm được gì với thế giới thật.
Function calling thay đổi gì?
Function calling là cơ chế cho phép developer định nghĩa một tập tools, và để LLM tự quyết định khi nào cần dùng tool nào. Quan trọng: LLM không chạy tool — LLM quyết định tool nào cần gọi. Developer viết code thật để execute. LLM chỉ ra lệnh.
Developer định nghĩa:
→ read_file(path)
→ write_file(path, content)
→ run_command(cmd)
→ search_code(query)
LLM giờ có thể nói:
"Tôi cần gọi read_file('src/main.ts') để xem nội dung"
Giống như não bộ ra lệnh cho tay chân — não nghĩ, tay làm.
Tool definition trông như thế nào?
Từ repo coding-agent, một tool definition cơ bản:
// tools.ts
const tools = [
{
type: "function",
function: {
name: "read_file",
description: "Read the contents of a file at the given path",
parameters: {
type: "object",
properties: {
path: {
type: "string",
description: "The file path to read"
}
},
required: ["path"]
}
}
},
{
type: "function",
function: {
name: "write_file",
description: "Write content to a file",
parameters: {
type: "object",
properties: {
path: { type: "string" },
content: { type: "string" }
},
required: ["path", "content"]
}
}
}
]
LLM nhận danh sách tools này và biết: "À, mình có thể dùng những thứ này."
LLM response có tool call trông như thế nào?
Khi LLM quyết định cần dùng tool, response không phải là plain text — mà là một structured object:
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "read_file",
"arguments": "{\"path\": \"src/main.ts\"}"
}
}
]
}
Agent nhận object này, execute tool tương ứng, rồi feed kết quả trở lại LLM.
Agentic loop — vòng lặp khiến mọi thứ hoạt động
Đây là phần quan trọng nhất. Không phải một lần gọi — mà là một vòng lặp liên tục cho đến khi task xong.
1. User: "Tìm tất cả any type trong src và fix"
↓
2. LLM quyết định: cần list_dir("src") trước
↓
3. Agent execute → trả kết quả về LLM
↓
4. LLM quyết định: cần read_file từng file
↓
5. Agent execute → trả kết quả về LLM
↓
6. LLM quyết định: cần str_replace để fix
↓
7. Agent execute → trả kết quả về LLM
↓
8. LLM trả về plain text: "Đã fix 3 any type trong 2 files"
↓
9. Done
Từ core.ts của repo:
// core.ts — agentic loop
async function runAgent(userMessage: string) {
messages.push({ role: "user", content: userMessage })
while (true) {
const response = await ollama.chat({
model: config.model,
messages,
tools,
stream: false
})
messages.push(response.message)
// Nếu không có tool call → LLM đã xong
if (!response.message.tool_calls?.length) {
return response.message.content
}
// Có tool call → execute và loop lại
for (const toolCall of response.message.tool_calls) {
const result = await executeTool(
toolCall.function.name,
toolCall.function.arguments
)
messages.push({
role: "tool",
content: result
})
}
// Loop tiếp
}
}
Đây chính xác là pattern mà Claude Code, Cursor, Copilot dùng. Chỉ khác ở model bên dưới.
Safety guards
Khi bạn cho LLM quyền chạy tool, một câu mà có lẽ bạn đã từng nghe:
"LLM đã xoá toàn bộ source code của tôi !"
Liệu có cách nào ngăn LLM thực hiện những tác vụ nghiêm ngặt ? Câu trả lời: có — và guard sinh ra để giúp LLM tuân thủ quy trình.
Đây là phần quyết định LLM có được phép / không được phép thực hiện 1 số tác vụ cần xác thực:
// guards.ts
export function checkPathTraversal(path: string): void {
const resolved = resolve(path)
const projectDir = resolve(config.projectDir)
if (!resolved.startsWith(projectDir)) {
throw new Error(`Path traversal detected: ${path}`)
}
}
export function checkDangerousCommand(cmd: string): void {
const dangerous = [
'rm -rf', 'dd if=', 'mkfs', ':(){:|:&};:',
'chmod -R 777', '> /dev/sda'
]
if (dangerous.some(d => cmd.includes(d))) {
throw new Error(`Dangerous command blocked: ${cmd}`)
}
}
export function checkReadonlyFile(path: string): void {
const readonly = ['.env', '.git/config', 'package-lock.json']
if (readonly.some(f => path.endsWith(f))) {
throw new Error(`Readonly file protected: ${path}`)
}
}
Không có phần này → agent là security hole. Production-ready agent cần ít nhất:
- Path traversal protection — không cho đọc file ngoài project dir
- Dangerous command blocking — chặn các command phá hệ thống
- Readonly file guard — bảo vệ
.env, config nhạy cảm - File size limit — tránh đọc file quá lớn làm tràn context
Chạy thử — demo thực tế
Setup chỉ cần Ollama (local, không cần API key):
git clone https://github.com/phuoctrung-ppt/coding-agent.git
cd coding-agent
npm install
ollama pull llama3.1:8b
ollama create fullstack-dev -f Modelfile
cp .env.example .env
# Edit .env: PROJECT_DIR=./path/to/your/project
npm run dev
Thử một số task:
You: list all files in src and explain the project structure
You: add soft delete to UserService
You: find all console.log and remove them
You: fix all TypeScript any types
Tại sao điều này quan trọng?
Function calling không chỉ là kỹ thuật. Nó là thứ biến LLM từ:
"Chatbot biết nhiều" → "Agent làm được việc"
Hiểu được điều này:
- Bạn hiểu được mọi AI coding tool đang dùng gì bên dưới
- Bạn có thể tự build tool phù hợp với workflow riêng
- Bạn không bị phụ thuộc vào subscription mãi mãi
Repo này chạy 100% offline, không cần API key, không tốn $20/month.
Bước tiếp theo
Bài này chỉ cover agentic loop cơ bản với local model. Những thứ thú vị hơn:
- Tool design tốt trông như thế nào?
- Làm sao để agent không bị stuck trong vòng lặp vô hạn?
- Context management khi conversation dài ra?
- Swap Ollama bằng Anthropic/OpenAI SDK thế nào?
Mã nguồn đầy đủ: github.com/phuoctrung-ppt/coding-agent
Nếu bài này hữu ích, star repo để tôi có động lực viết tiếp phần 2 — RAG pipeline và LSP integration.
All rights reserved