mizuko
0
0
自アプリケーションのAIチャットにツールを登録し自動実行してもらうための実装メモまとめ
Go言語でAIツールインターフェースを定義する方法
9日前
// Tool はAIが使用できるツールのインターフェース type Tool interface { GetDefinition() ToolDefinition Execute(ctx context.Context, userID uint, args map[string]interface{}) (*ToolResult, error) } // ToolDefinition はツールの定義情報 type ToolDefinition struct { Name string // ツール名(例: search_memos) Description string // ツールの説明 Parameters map[string]Parameter // パラメータ定義 } // Parameter はツールのパラメータ情報 type Parameter struct { Type string // データ型(string, integer, boolean等) Description string // パラメータの説明 Required bool // 必須かどうか Default interface{} // デフォルト値(オプション) } // ToolResult はツール実行結果 type ToolResult struct { Success bool // 実行成功したか Data map[string]interface{} // 結果データ Error string // エラーメッセージ(失敗時) }
Gemini APIでツール定義を変換する実装パターン
9日前
// Gemini用の変換関数
func ConvertAIToolsToGemini(aiTools []aitool.ToolDefinition) []*genai.Tool {
functionDeclarations := make([]*genai.FunctionDeclaration, 0, len(aiTools))
for _, aiTool := range aiTools {
funcDecl := &genai.FunctionDeclaration{
Name: aiTool.Name,
Description: aiTool.Description,
Parameters: &genai.Schema{
Type: genai.TypeObject,
Properties: make(map[string]*genai.Schema),
Required: []string{},
},
}
// パラメータを変換
for name, param := range aiTool.Parameters {
var schemaType genai.Type
switch param.Type {
case "string":
schemaType = genai.TypeString
case "integer", "number":
schemaType = genai.TypeNumber
case "boolean":
schemaType = genai.TypeBoolean
}
funcDecl.Parameters.Properties[name] = &genai.Schema{
Type: schemaType,
Description: param.Description,
}
if param.Required {
funcDecl.Parameters.Required = append(funcDecl.Parameters.Required, name)
}
}
functionDeclarations = append(functionDeclarations, funcDecl)
}
// 全ての関数宣言を1つのToolオブジェクトにまとめる
return []*genai.Tool{{FunctionDeclarations: functionDeclarations}}
}
AIチャットでツールを自動実行するシステムの実装
9日前
// AIツール管理クラス
type AITools struct {
tools map[string]aitool.Tool
}
// ツールの登録
func NewAITools(memoRepo, categoryRepo, userCategoryRepo, skillSheetRepo) *AITools {
tools := make(map[string]aitool.Tool)
// 各ツールをインスタンス化して登録
memoTool := NewMemoTool(memoRepo)
tools[memoTool.GetDefinition().Name] = memoTool
categoryTool := NewCategoryTool(categoryRepo, userCategoryRepo)
tools[categoryTool.GetDefinition().Name] = categoryTool
skillSheetTool := NewSkillSheetTool(skillSheetRepo)
tools[skillSheetTool.GetDefinition().Name] = skillSheetTool
return &AITools{tools: tools}
}
// ツール定義を取得(AIに渡すため)
func (m *AITools) GetToolDefinitions() []aitool.ToolDefinition {
definitions := make([]aitool.ToolDefinition, 0, len(m.tools))
for _, tool := range m.tools {
definitions = append(definitions, tool.GetDefinition())
}
return definitions
}
// ツールを実行
func (m *AITools) CallTool(ctx context.Context, userID uint, toolName string, args map[string]interface{}) (*aitool.ToolResult, error) {
tool, exists := m.tools[toolName]
if !exists {
return &aitool.ToolResult{
Success: false,
Error: fmt.Sprintf("ツール '%s' が見つかりません", toolName),
}, nil
}
return tool.Execute(ctx, userID, args)
}
// Geminiでツールを使用
func handleGeminiWithTools(ctx context.Context, userID uint, message string, aiToolDefs []aitool.ToolDefinition) {
// ツールをGemini形式に変換
geminiTools := ConvertAIToolsToGemini(aiToolDefs)
// ツール実行関数を定義
toolExecutor := func(ctx context.Context, fc *genai.FunctionCall) (map[string]any, error) {
result, err := aiTools.CallTool(ctx, userID, fc.Name, fc.Args)
if err != nil {
return nil, err
}
if result.Success && result.Data != nil {
return convertToolResultForGemini(result.Data), nil
}
return map[string]any{"error": result.Error}, nil
}
// Gemini APIを呼び出し(ツール実行も含めて自動処理)
response, err := geminiService.ExecuteToolsNonStreaming(
ctx, systemPrompt, message, history, geminiTools, toolExecutor,
)
}
ツール実行結果のストリーミング配信の実装方法
9日前
// ストリーミングレスポンスの型定義
type AIChatStreamResponse struct {
Content string
SessionID uint
}
// ツール実行を含むメッセージ送信
func SendMessageWithTools(ctx context.Context, userID uint, sessionID uint, message string) (chan *AIChatStreamResponse, error) {
// AIツール定義を取得
aiToolDefs := aiTools.GetToolDefinitions()
// レスポンス用のチャンネル作成
responseChan := make(chan *AIChatStreamResponse, 100)
// 非同期でツール実行とレスポンス生成
go handleGeminiWithTools(ctx, userID, sessionID, message, aiToolDefs, responseChan)
return responseChan, nil
}
// Geminiでのツール実行とストリーミング
func handleGeminiWithTools(ctx context.Context, userID uint, sessionID uint, message string, aiToolDefs []aitool.ToolDefinition, responseChan chan *AIChatStreamResponse) {
defer close(responseChan)
// ツール実行を含む処理(非ストリーミング)
fullResponse, err := geminiService.ExecuteToolsNonStreaming(
ctx, systemPrompt, message, history, geminiTools, toolExecutor,
)
if err != nil {
responseChan <- &AIChatStreamResponse{
Content: fmt.Sprintf("エラーが発生しました: %v", err),
SessionID: sessionID,
}
return
}
// レスポンスをチャンクに分割してストリーミング送信
const chunkSize = 100
runes := []rune(fullResponse)
for i := 0; i < len(runes); i += chunkSize {
end := min(i + chunkSize, len(runes))
chunk := string(runes[i:end])
responseChan <- &AIChatStreamResponse{
Content: chunk,
SessionID: sessionID,
}
}
}
// クライアント側での受信例
func receiveStreamingResponse(responseChan chan *AIChatStreamResponse) {
for response := range responseChan {
// チャンクごとに表示を更新
fmt.Print(response.Content)
}
}