昨天提到 Prompt
function calling 跟 FC (native)
的差別。最後決定要走 FC,因為簡單、省 token,而且在 agentic domain 上分數直接碾壓。
今天就來實際分享一下我怎麼改 Reasoning state,把 open ai 的 FC 用進來。
拿 openai 的圖來解釋,反正 function calling 就是透過提供固定 format 的 tools,讓 llm 根據prompt去選擇要選擇使用哪些 tools。
由於之後還是打算把 mcp server 帶進來,所以先理解一下 MCP tool list 的格式
首先要解釋一下,MCP (multi-component protocol) 的 tool list 跟 open ai 的 tools
參數格式其實不一樣。
MCP tool list:通常會包含比較多 metadata,例如:
{
name: string; // Unique identifier for the tool
description?: string; // Human-readable description
inputSchema: { // JSON Schema for the tool's parameters
type: "object",
properties: { ... } // Tool-specific parameters
}
}
ref: https://modelcontextprotocol.info/docs/concepts/tools/
可以看到它的 schema key 是 inputSchema
。
OpenAI tools 格式:
定義:
小小例子
{
"type": "function",
"name": "get_weather",
"description": "Retrieves current weather for the given location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and country e.g. Bogotá, Colombia"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Units the temperature will be returned in."
}
},
"required": ["location", "units"],
"additionalProperties": false
},
"strict": true
}
ref: https://platform.openai.com/docs/guides/function-calling
是有看到一些 adapter 的 repo,但感覺有點小眾 就不貼了。
看樣子是有人想要整合 function 的格式呢XD
Unified Tool Integration for LLMs: A Protocol-Agnostic Approach to Function Calling
接下來就是改 code。
我寫了一個 get_response
function,用來呼叫 open ai,然後 parse 出工具調用:
async def get_response(model_name: str, messages: list[Message], tools: list[dict] | None = None):
respone = await client.chat.completions.create(model=model_name, messages=messages, tools=tools)
tool_raw_infos = respone.choices[0].message.tool_calls
tool_infos = (
[
{"name": tool_call.function.name, "args": json.loads(tool_call.function.arguments)}
for tool_call in tool_raw_infos
if tool_call.type == "function"
]
if tool_raw_infos
else None
)
return (
respone.choices[0].message.content,
tool_infos,
)
然後在 ReasoningState
裡就就改呼叫 get_response
class ReasoningState(State):
async def run(self, memory: "Memory") -> AsyncIterator[str]:
messages = await get_messages(memory)
content, tools = await get_response(
model_name=REASONING_MODEL, messages=messages, tools=mcp_to_openai_tools(memory.list_tools())
)
if content:
for chunk in content:
yield chunk
await memory.update([Message(role=Role.ASSISTANT, content=content)])
yield str(tools)
if tools:
memory.next_actions = [Action(**tool) for tool in tools]
async def next_state(self, memory: "Memory") -> Enum:
if memory.next_actions:
return ReAct.ACTION
return ReAct.ANSWER
答案是 Yes。
tools
,裡面是 function
+ parameters
。tool_choice
,定義方式又不一樣。function_declarations
。明天真的就會跑跑看 toolhop dataset,看 react 的分數了...