Code: Select all | Expand
#include "FiveWin.ch"
#include "hbcurl.ch"
// Activar depuración (comentar esta línea para desactivarla)
#define DEBUG
FUNCTION Main()
local oLlama, oAgent
oLlama = TOLlama():New()
// Agente para mostrar la hora
oAgent = TAgent():New( "time", { {"get_time", {|hParams| GetCurrentTime(hParams)} } } )
AAdd( oLlama:aAgents, oAgent )
// Agente para filesystem con múltiples tools
oAgent = TAgent():New( "filesystem", { ;
{"create_folder", {|hParams| CreateFolder(hParams)} },;
{"create_file", {|hParams| CreateFile(hParams)} },;
{"modify_file", {|hParams| ModifyFile(hParams)} } } )
AAdd( oLlama:aAgents, oAgent )
#ifdef DEBUG
? "DEBUG: Probando 'What time is it?'"
#endif
fw_memoEdit( oLlama:Send( "What time is it?" ) )
#ifdef DEBUG
? "DEBUG: Probando 'Create a folder named test'"
#endif
fw_memoEdit( oLlama:Send( "Create a folder named 'test'" ) )
#ifdef DEBUG
? "DEBUG: Probando 'Create a file called test.txt'"
#endif
fw_memoEdit( oLlama:Send( "Create a file called 'test.txt'" ) )
#ifdef DEBUG
? "DEBUG: Probando 'Modify the file test.txt with content Hello World'"
#endif
fw_memoEdit( oLlama:Send( "Modify the file test.txt with content Hello World" ) )
oLlama:End()
return nil
FUNCTION GetCurrentTime( hParams )
local cTime := Time()
return "The current time is " + cTime // No necesita parámetros, pero acepta hParams por consistencia
FUNCTION CreateFolder( hParams )
local cFolder
if hb_HHasKey( hParams, "folder_name" ) .and. ! Empty( hParams[ "folder_name" ] )
cFolder = hParams[ "folder_name" ]
DirMake( cFolder )
return "Folder '" + cFolder + "' created successfully"
endif
return "Failed to create folder: no name specified"
FUNCTION CreateFile( hParams )
local cFile
if hb_HHasKey( hParams, "filename" ) .and. ! Empty( hParams[ "filename" ] )
cFile = hParams[ "filename" ]
hb_MemoWrit( cFile, "" )
return "File '" + cFile + "' created successfully"
endif
return "Failed to create file: no name specified"
FUNCTION ModifyFile( hParams )
local cFile, cContent
if hb_HHasKey( hParams, "filename" ) .and. ! Empty( hParams[ "filename" ] ) .and. ;
hb_HHasKey( hParams, "content" ) .and. ! Empty( hParams[ "content" ] )
cFile = hParams[ "filename" ]
cContent = hParams[ "content" ]
hb_MemoWrit( cFile, cContent )
return "File '" + cFile + "' modified with content: " + cContent
endif
return "Failed to modify file: missing file name or content"
CLASS TOLlama
DATA cModel
DATA cPrompt
DATA cResponse
DATA cUrl
DATA hCurl
DATA nError INIT 0
DATA nHttpCode INIT 0
DATA aAgents INIT {}
METHOD New( cModel )
METHOD Send( cPrompt, cImageFileName, bWriteFunction )
METHOD GetPromptCategory( cPrompt )
METHOD GetToolName( cPrompt, oAgent )
METHOD End()
METHOD GetValue()
ENDCLASS
METHOD New( cModel ) CLASS TOLlama
DEFAULT cModel := "gemma3"
::cModel = cModel
::cUrl = "http://localhost:11434/api/chat"
::hCurl = curl_easy_init()
return Self
METHOD GetPromptCategory( cPrompt ) CLASS TOLlama
local cJson, hRequest := { => }, hMessage := { => }
local cCategoryResponse, hResponse
local nError, cCategories, nI, nJ
cCategories = ""
if ! Empty( ::aAgents )
for nI = 1 to Len( ::aAgents )
cCategories += "'" + ::aAgents[ nI ]:cCategory + "'"
if nI < Len( ::aAgents )
cCategories += ", "
endif
next
else
cCategories = "'general'"
endif
curl_easy_reset( ::hCurl )
curl_easy_setopt( ::hCurl, HB_CURLOPT_POST, .T. )
curl_easy_setopt( ::hCurl, HB_CURLOPT_URL, ::cUrl )
curl_easy_setopt( ::hCurl, HB_CURLOPT_HTTPHEADER, { "Content-Type: application/json" } )
curl_easy_setopt( ::hCurl, HB_CURLOPT_USERNAME, '' )
curl_easy_setopt( ::hCurl, HB_CURLOPT_DL_BUFF_SETUP )
curl_easy_setopt( ::hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F. )
hRequest[ "model" ] = ::cModel
hMessage[ "role" ] = "user"
hMessage[ "content" ] = "Classify this prompt: '" + cPrompt + "' into one of these categories: " + ;
cCategories + ". Respond with only the category name."
hRequest[ "messages" ] = { hMessage }
hRequest[ "stream" ] = .F.
hRequest[ "temperature" ] = 0.5
cJson = hb_jsonEncode( hRequest )
curl_easy_setopt( ::hCurl, HB_CURLOPT_POSTFIELDS, cJson )
nError = curl_easy_perform( ::hCurl )
if nError == HB_CURLE_OK
cCategoryResponse = curl_easy_dl_buff_get( ::hCurl )
hb_jsonDecode( cCategoryResponse, @hResponse )
#ifdef DEBUG
? "DEBUG: Categoría devuelta por IA:", hResponse[ "message" ][ "content" ]
#endif
return hResponse[ "message" ][ "content" ]
endif
#ifdef DEBUG
? "DEBUG: Error en GetPromptCategory:", nError
#endif
return nil
METHOD GetToolName( cPrompt, oAgent ) CLASS TOLlama
local cJson, hRequest := { => }, hMessage := { => }
local cToolResponse, hResponse, hToolInfo
local nError, cTools := "", nI
if ! Empty( oAgent:aTools )
for nI = 1 to Len( oAgent:aTools )
cTools += "'" + oAgent:aTools[ nI ][ 1 ] + "'"
if nI < Len( oAgent:aTools )
cTools += ", "
endif
next
endif
curl_easy_reset( ::hCurl )
curl_easy_setopt( ::hCurl, HB_CURLOPT_POST, .T. )
curl_easy_setopt( ::hCurl, HB_CURLOPT_URL, ::cUrl )
curl_easy_setopt( ::hCurl, HB_CURLOPT_HTTPHEADER, { "Content-Type: application/json" } )
curl_easy_setopt( ::hCurl, HB_CURLOPT_USERNAME, '' )
curl_easy_setopt( ::hCurl, HB_CURLOPT_DL_BUFF_SETUP )
curl_easy_setopt( ::hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F. )
hRequest[ "model" ] = ::cModel
hMessage[ "role" ] = "user"
hMessage[ "content" ] = "Given this prompt: '" + cPrompt + "' and category '" + oAgent:cCategory + "', " + ;
"select the appropriate tool from: " + cTools + " and extract any relevant parameters. " + ;
"Respond with a JSON object containing 'tool' (the tool name) and 'params' (a hash of parameters)."
hRequest[ "messages" ] = { hMessage }
hRequest[ "stream" ] = .F.
hRequest[ "temperature" ] = 0.5
cJson = hb_jsonEncode( hRequest )
curl_easy_setopt( ::hCurl, HB_CURLOPT_POSTFIELDS, cJson )
nError = curl_easy_perform( ::hCurl )
if nError == HB_CURLE_OK
cToolResponse = curl_easy_dl_buff_get( ::hCurl )
hb_jsonDecode( cToolResponse, @hResponse )
#ifdef DEBUG
? "DEBUG: Respuesta cruda de IA:", cToolResponse
? "DEBUG: hResponse tras decodificar:", hb_jsonEncode( hResponse )
? "DEBUG: Contenido de hResponse[ 'message' ][ 'content' ]:", hResponse[ "message" ][ "content" ]
#endif
hResponse[ "message" ][ "content" ] = SubStr( hResponse[ "message" ][ "content" ], 9 )
hResponse[ "message" ][ "content" ] = SubStr( hResponse[ "message" ][ "content" ], 1, Len( hResponse[ "message" ][ "content" ] ) - 3 )
hb_jsonDecode( hResponse[ "message" ][ "content" ], @hToolInfo )
#ifdef DEBUG
? "DEBUG: hToolInfo tras procesar:", hb_jsonEncode( hToolInfo )
? "DEBUG: Tipo de hToolInfo:", ValType( hToolInfo )
if ValType( hToolInfo ) == "H"
? "DEBUG: Claves en hToolInfo:", hb_HKeys( hToolInfo )
endif
#endif
return hToolInfo
endif
#ifdef DEBUG
? "DEBUG: Error en GetToolName:", nError
#endif
return nil
METHOD Send( cPrompt, cImageFileName, bWriteFunction ) CLASS TOLlama
local aHeaders, cJson, hRequest := { => }, hMessage := { => }
local cBase64Image
local oAgent, cToolResult, nI, hToolInfo, cToolName, nTool
local cCategory
if ! Empty( cPrompt )
::cPrompt = cPrompt
endif
if ! Empty( ::aAgents )
cCategory = ::GetPromptCategory( cPrompt )
#ifdef DEBUG
? "DEBUG: Categoría obtenida (sin limpiar):", cCategory
#endif
cCategory = AllTrim( StrTran( StrTran( cCategory, Chr(13), "" ), Chr(10), "" ) )
#ifdef DEBUG
? "DEBUG: Categoría obtenida (limpia):", cCategory
#endif
if ! Empty( cCategory )
for nI = 1 to Len( ::aAgents )
oAgent = ::aAgents[ nI ]
#ifdef DEBUG
? "DEBUG: Comparando categoría del agente:", oAgent:cCategory, "con categoría obtenida:", cCategory
? "DEBUG: Longitud de oAgent:cCategory:", Len( oAgent:cCategory ), "Longitud de cCategory:", Len( cCategory )
? "DEBUG: oAgent:cCategory en hex:", hb_StrToHex( oAgent:cCategory )
? "DEBUG: cCategory en hex:", hb_StrToHex( cCategory )
? "DEBUG: Lower(oAgent:cCategory):", Lower( oAgent:cCategory ), "Lower(cCategory):", Lower( cCategory )
#endif
if Lower( AllTrim( oAgent:cCategory ) ) == Lower( AllTrim( cCategory ) )
#ifdef DEBUG
? "DEBUG: ¡Coincidencia encontrada para categoría!"
#endif
if ! Empty( oAgent:aTools )
hToolInfo = ::GetToolName( cPrompt, oAgent )
#ifdef DEBUG
? "DEBUG: hToolInfo recibido:", hb_jsonEncode( hToolInfo )
#endif
if ValType( hToolInfo ) == "H" .and. hb_HHasKey( hToolInfo, "tool" )
cToolName = AllTrim( StrTran( StrTran( hToolInfo[ "tool" ], Chr(13), "" ), Chr(10), "" ) )
#ifdef DEBUG
? "DEBUG: Tool obtenida (limpia):", cToolName
? "DEBUG: Parámetros extraídos:", hb_jsonEncode( hToolInfo[ "params" ] )
#endif
if ! Empty( cToolName )
nTool = 0
for nJ = 1 to Len( oAgent:aTools )
#ifdef DEBUG
? "DEBUG: Comparando tool del agente:", oAgent:aTools[ nJ ][ 1 ], "con tool obtenida:", cToolName
? "DEBUG: Longitud de oAgent:aTools[", nJ, "][1]:", Len( oAgent:aTools[ nJ ][ 1 ] ), "Longitud de cToolName:", Len( cToolName )
? "DEBUG: oAgent:aTools[", nI, "][1] en hex:", hb_StrToHex( oAgent:aTools[ nJ ][ 1 ] )
? "DEBUG: cToolName en hex:", hb_StrToHex( cToolName )
? "DEBUG: Lower(oAgent:aTools[", nJ, "][1]):", Lower( oAgent:aTools[ nJ ][ 1 ] ), "Lower(cToolName):", Lower( cToolName )
#endif
if Lower( AllTrim( oAgent:aTools[ nJ ][ 1 ] ) ) == Lower( AllTrim( cToolName ) )
nTool = nJ
#ifdef DEBUG
? "DEBUG: ¡Coincidencia encontrada para tool!"
#endif
exit
endif
next
if nTool > 0
cToolResult = Eval( oAgent:aTools[ nTool ][ 2 ], hToolInfo[ "params" ] )
#ifdef DEBUG
? "DEBUG: Resultado de la tool:", cToolResult
#endif
::cResponse = hb_jsonEncode( { "message" => { "content" => cToolResult }, "done" => .T. } )
return ::cResponse
else
#ifdef DEBUG
? "DEBUG: Tool '" + cToolName + "' no encontrada en el agente '" + oAgent:cCategory + "'"
#endif
::cResponse = hb_jsonEncode( { "message" => { "content" => "Tool not found" }, "done" => .T. } )
return ::cResponse
endif
else
#ifdef DEBUG
? "DEBUG: No se obtuvo nombre de tool válido"
#endif
::cResponse = hb_jsonEncode( { "message" => { "content" => "No tool selected" }, "done" => .T. } )
return ::cResponse
endif
else
#ifdef DEBUG
? "DEBUG: Respuesta de GetToolName no válida o sin 'tool'. Tipo:", ValType( hToolInfo )
if ValType( hToolInfo ) == "H"
? "DEBUG: Claves en hToolInfo:", hb_HKeys( hToolInfo )
endif
#endif
::cResponse = hb_jsonEncode( { "message" => { "content" => "Invalid tool response" }, "done" => .T. } )
return ::cResponse
endif
else
#ifdef DEBUG
? "DEBUG: El agente '" + oAgent:cCategory + "' no tiene tools"
#endif
::cResponse = hb_jsonEncode( { "message" => { "content" => "No tools available" }, "done" => .T. } )
return ::cResponse
endif
else
#ifdef DEBUG
? "DEBUG: No hay coincidencia entre '" + Lower( AllTrim( oAgent:cCategory ) ) + "' y '" + Lower( AllTrim( cCategory ) ) + "'"
#endif
endif
next
#ifdef DEBUG
? "DEBUG: No se encontró agente para la categoría '" + cCategory + "'"
#endif
::cResponse = hb_jsonEncode( { "message" => { "content" => "Agent not found" }, "done" => .T. } )
return ::cResponse
else
#ifdef DEBUG
? "DEBUG: No se obtuvo categoría válida"
#endif
endif
else
#ifdef DEBUG
? "DEBUG: No hay agentes definidos"
#endif
endif
#ifdef DEBUG
? "DEBUG: Llamando a la API de Ollama"
#endif
curl_easy_reset( ::hCurl )
curl_easy_setopt( ::hCurl, HB_CURLOPT_POST, .T. )
curl_easy_setopt( ::hCurl, HB_CURLOPT_URL, ::cUrl )
aHeaders := { "Content-Type: application/json" }
curl_easy_setopt( ::hCurl, HB_CURLOPT_HTTPHEADER, aHeaders )
curl_easy_setopt( ::hCurl, HB_CURLOPT_USERNAME, '' )
curl_easy_setopt( ::hCurl, HB_CURLOPT_SSL_VERIFYPEER, .F. )
hRequest[ "model" ] = ::cModel
hMessage[ "role" ] = "user"
hMessage[ "content" ] = ::cPrompt
hRequest[ "messages" ] = { hMessage }
hRequest[ "temperature" ] = 0.5
if ! Empty( cImageFileName )
if File( cImageFileName )
cBase64Image = hb_base64Encode( memoRead( cImageFileName ) )
hMessage[ "images" ] = { cBase64Image }
else
MsgAlert( "Image " + cImageFileName + " not found" )
return nil
endif
endif
if bWriteFunction != nil
hRequest[ "stream" ] = .T.
curl_easy_setopt( ::hCurl, HB_CURLOPT_WRITEFUNCTION, bWriteFunction )
else
hRequest[ "stream" ] = .F.
curl_easy_setopt( ::hCurl, HB_CURLOPT_DL_BUFF_SETUP )
endif
cJson = hb_jsonEncode( hRequest )
curl_easy_setopt( ::hCurl, HB_CURLOPT_POSTFIELDS, cJson )
::nError = curl_easy_perform( ::hCurl )
curl_easy_getinfo( ::hCurl, HB_CURLINFO_RESPONSE_CODE, @::nHttpCode )
if ::nError == HB_CURLE_OK
if bWriteFunction == nil
::cResponse = curl_easy_dl_buff_get( ::hCurl )
endif
else
::cResponse = "Error code " + Str( ::nError )
endif
return ::cResponse
METHOD End() CLASS TOLlama
curl_easy_cleanup( ::hCurl )
::hCurl = nil
return nil
METHOD GetValue() CLASS TOLlama
local hResponse, uValue
hb_jsonDecode( ::cResponse, @hResponse )
TRY
uValue = hResponse[ "message" ][ "content" ]
CATCH
uValue = hResponse[ "error" ][ "message" ]
END
return uValue
CLASS TAgent
DATA cCategory
DATA aTools
METHOD New( cCategory, aTools )
ENDCLASS
METHOD New( cCategory, aTools ) CLASS TAgent
::cCategory = cCategory
::aTools = aTools
return Self