From b3f87abce1191ca8a6c254f1d0a432c37b92a036 Mon Sep 17 00:00:00 2001 From: Davidson Gomes Date: Fri, 25 Apr 2025 19:10:26 -0300 Subject: [PATCH] structure saas with tools and mcps --- .env | 13 +- src/api/__pycache__/routes.cpython-310.pyc | Bin 6091 -> 5071 bytes src/api/routes.py | 39 +-- .../__pycache__/settings.cpython-310.pyc | Bin 1237 -> 1639 bytes src/config/settings.py | 14 + src/models/__pycache__/models.cpython-310.pyc | Bin 2084 -> 1762 bytes src/models/models.py | 12 +- .../__pycache__/schemas.cpython-310.pyc | Bin 3063 -> 2488 bytes src/schemas/schemas.py | 17 +- .../__pycache__/agent_builder.cpython-310.pyc | Bin 4338 -> 8523 bytes .../__pycache__/agent_runner.cpython-310.pyc | Bin 2891 -> 2985 bytes src/services/agent_builder.py | 285 +++++++++++++++--- src/services/agent_runner.py | 4 +- src/services/message_service.py | 35 --- src/utils/__pycache__/logger.cpython-310.pyc | Bin 1599 -> 1719 bytes src/utils/logger.py | 28 +- 16 files changed, 299 insertions(+), 148 deletions(-) delete mode 100644 src/services/message_service.py diff --git a/.env b/.env index 83e371ce..c79ed9a6 100644 --- a/.env +++ b/.env @@ -1,3 +1,14 @@ OPENAI_API_KEY=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 -POSTGRES_CONNECTION_STRING=postgresql://postgres:root@localhost:5432/google-a2a-saas \ No newline at end of file +POSTGRES_CONNECTION_STRING=postgresql://postgres:root@localhost:5432/google-a2a-saas + +TENANT_ID=45cffb85-51c8-41ed-aa8d-710970a7ce50 +KNOWLEDGE_API_URL=http://localhost:5540 +KNOWLEDGE_API_KEY=79405047-7a5e-4b18-b25a-4af149d747dc + +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_DB=3 +REDIS_PASSWORD= + +LOG_LEVEL=DEBUG \ No newline at end of file diff --git a/src/api/__pycache__/routes.cpython-310.pyc b/src/api/__pycache__/routes.cpython-310.pyc index 162c691b2e4b174412a09f573c6517b2eb4aefbc..7fbe2fc735b5184cf76fcb0284dc42fb6c31c40a 100644 GIT binary patch literal 5071 zcma(U%W@mXaUa0q4ZbOoq9lrFYJIN3a45HqA!-UZ;DE8yQjBVC}cJG zJ^EMf+24+6+COj-{~`4rJiatFjcH7GHBYy7&#(>8v`rmxhMVw`c9O=XYk4UtbHsI(EIZHpdMvxZ`@_92vNG{qBP}anS>FMc zDampB((Dp@pH^O{m6!MJ@IlPJ57|e=d4udbm^0GIVprHz;=W1tU5?oomwAm{C$3v$ z-wi&{W8Y0a7}|G>-6p=ocbDCxm3L_6?7kiD$LyP9^Tc_V>^qn<(#T>9 z>;ZA#Bl~7!_QhpBWRHkzmL*oT(&9VJ`jRO#v&)Ms&33?rOwIEaZ!ljL9zA)oyzo_> zw*sfxDCsh_%6(rA6CXQ%umrd?s|7r8JPu=Pg9lZ%R;b2@YH`HP>te zwR#{6;UL5(XE%^JM=yl~m5OzeQT4g_(y4RVS68b$h(ZmQMN?Rjn|oFZR`?ff?&H=p z@mtM?&t+jjh^AQKY6`k$M7vQX0WcY)otK)ygJ!$Ih!wC$78{>%uPHWXMc}N1-m6Lz zAdSw~g4$Zm=b=Zsl#ytWTG}O~eI5i(W5b8#7F(;fg}ark)Imv^^qP#jCDZw{u4(W7 zir}9BN`@?oP~$4GfY#yMmnjh%YOuAsfyWsB67a_Q{dai$KLYrjzI8OvwzcOvJO(^w zkO-22wXN@@7NL?q(q0+c2GfJooAj>sCbJ8*{TuUBZBV$V@ja65=G%rja(Z?lfv{H8QLk z@UH@?^S7F~0_1ORR-QFIUSYK_9p*P1m1WU<&g+3+@q;$oyy(;_wac}Oeys)=QLofm zPDNl&`sLQ9Okk+YfEfP*0OM=JGV}!f4PE`S|2A@lp4HRe6|H_f{dG}CX#7W9g9Tfe zipnRyQqsi_T$8&-@^ckHWj+5}95`GZ?DFKnYt?!zv znz#w5NRb@YoY1n8DeeME+(Uo|vQHP82c=oWy#Ne8DpfSZeRypRcRch`39LHomCSU; zJ&r;hJC4F*={kzCNo#AOKY*-a1~V~Rw$Xu!C5r5C`{$^(O*NXq)H>E2aRx+y{^ZMRd9n+nU&dfRn3JH{wtk1D%`S7a(29%_}i0ZXEY?uRkldtqv+>kb0_C&+R@Jc|_|{2Gb3 z5#VfVq`P@6itFga=!av}8DnY>wUm|ok)F0=(b>Woq_DqZ~Q+GgmUx8X7W?}RI; zkPKI^;f8pm{f77{yt>L(UUsYZD{&rZJr0W}o0tb`Cy@r`lKdrlHh4DC{ug0^DL50nWD4 z6}pHa;*&HuT-$iO55Q8H596KIL9|y<=t0|l285SLq-e+4)=;$V4~ccLii^X`gSH;V z*j9>nBU?O0w%+rQs(dJsvnbfcs{#iT!@~l75+v zeo^qEWEH$=2wvK8zp7cvIU?;KHwEqnYJ^%uKP&=K^;dyg2Mh$RI+HMP)dt85Wu5l! zo(^S2ubad(;QSLjDr|AqTpFlrJE7)v7`!29@%0#>EFx4pzL914V zDzfful;JLH}A$5)rhF;)1?X>g&MU4#iSbn_vf1$L5xrN@+s2NYiNq)XvGo)2EB`)b>cT z)eflTz;6g_0O+Eo3zIG)s_Rr`shWw;P$nj~z`UWT78oPa6o~{oQ0TKQ^iNn=^XeJg(=hX+3S> M8*%6U4{lojFP*}>SO5S3 literal 6091 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 AhX4Qo diff --git a/src/config/settings.py b/src/config/settings.py index 5c97d6b1..4d5ace35 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -28,6 +28,20 @@ class Settings(BaseSettings): LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") LOG_DIR: str = "logs" + # Configurações da API de Conhecimento + KNOWLEDGE_API_URL: str = os.getenv("KNOWLEDGE_API_URL", "http://localhost:5540") + KNOWLEDGE_API_KEY: str = os.getenv("KNOWLEDGE_API_KEY", "") + TENANT_ID: str = os.getenv("TENANT_ID", "") + + # Configurações do Redis + REDIS_HOST: str = os.getenv("REDIS_HOST", "localhost") + REDIS_PORT: int = int(os.getenv("REDIS_PORT", 6379)) + REDIS_DB: int = int(os.getenv("REDIS_DB", 0)) + REDIS_PASSWORD: Optional[str] = os.getenv("REDIS_PASSWORD") + + # TTL do cache de ferramentas em segundos (1 hora) + TOOLS_CACHE_TTL: int = int(os.getenv("TOOLS_CACHE_TTL", 3600)) + class Config: env_file = ".env" case_sensitive = True diff --git a/src/models/__pycache__/models.cpython-310.pyc b/src/models/__pycache__/models.cpython-310.pyc index 442793ffa8a749d3a626e6cc3370a53736b3cacb..4e0bb6f9bfe2b183bcf6fc36eb9eb44738bd62e4 100644 GIT binary patch delta 626 zcmb7>!A`lBFc#1rv{k1L@U^ zi7#M$0WUs)*B*@z;0qX?sm5^NVmJHs-`UAb`_Dt>%}RTgl~mxl|1{4}Id<3RZ^ks2Rgfd;@+Sb&UW3 delta 896 zcmb7?KX21O7{+}U$9Ddl>!z(Kp`|SZgiDdih=eLwU_ca!KTDP>${k>l*wxvUC`*LO zh9b4{7XXPR8;B3Uz|6*U6XJ`&`#41iDGRplckkVMe$V&Z^HY2X{8Wg#MbFns{`Ccq_1!G6OEC(A{;bO)fs=TMCyI``6E+M*1 z5Y#fC;8`}?4EZwi=nYtz?9==|54OBTEjuoL@Y`+a$=aBAo61&=(^yR7;n;`F?2YgWRnrl$Nw}jI5pETB^{2s!V#C1ai#0fiCxL1J!W)%k481>>(GUIXeB6H9W&*>n|p6bhm;AVOAhgjdtYT) z@0l{*a2jNfBd%MtjS;u#@j(N)09*uSNp*t~a8h>mkBxlpmo92EjbyY?TzBFJrF&tw j(psf{6sdW+XYRZAy!Xy|?<;keh<9VLQH8#x&*(u#>&LU|qtdTF7|m2e z@!Z=QYykxFRGkJ-UZ~}SP23TRKqC-}258G0!SMuSA~!beS5q_ z9G3!_jHRFcrjg`nIh+=Zku+1h$XR@&+v)A@^%m*Bf7ssfagt@R*a9!eGKA8v4VDh9 zdVHR$T*Ms1JZ<_0Q}WWRI#n(#G4Ms;3`33e=PHyIDS|iwJGIHH7`Uhmxp2%f5qzBG zgO(}fg#$CozcdHusOT9l`g*(7>EVBG;K~P)gg+y^$&eBB*kPTD3)~1T@XYrTawWQ6 z;-^G@7vw8PzE5Ps7zznDtDozsg%`1U+-;s5wbp>TL7Ko)Y#5_qsaAxBY3U&fJMs-L CCTFYw delta 941 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 I%abqvACJL7-~a#s delta 1836 zcmaJ>O>7%Q6y6z+?akU=|0HQ_=g( zO2ASgr3X0lfJQ>%1{b7W5LK?+IQGDa%ZeM~2ofAgk$7)6KWfL%z-U=Lf3(>IEt)-QW%4Xo z6?hK#JP{#2p46h6@AOp?Pe6p_%w3`<7V<=dRYj7A@ zuf}BojARBN7;W}F-|bkw>pIqE|Hsb9o;1iAJv{y1bf{=u19{Ylhd~ zjQo2+`b4&1%L1xLPL={gY(w!pztP+}B6AT>3E5;VNRV}6>pcY;56J=-iZ#}0ju^B| z#;Ce7@fTTA7mAmkJxR&kSU_t}M;?jwXsV25$M*cD+v%;WsjrJG%R$0%+imDBRq$IROJ!?<{o~5AzjyxILalJEiJwf27coGh(c7@lX9%URu2nI_YBG?7Q!S4 z+ogLXjDET%FM)C>*2@o1F)#8(fDII0QvJ!sMA%xhFt5gk^w~XQy1k7l}@02cHj_8~uN_bCX4WP=R0wV?aGD3Ln5MlN6RQdW1(0Ho=bgw|m z)Pw>V*kCZyg#Akydtk)>Zw0I#&^SBHCDrrO2iF(jsC8f#hI$pJJubRFjAG%Xm3 zd>!F5gis30lm`X+@$eE>XE&omTU+A6zWAUAOIsT7z%Z!pFp` zId*i3!`mO^JlU-EecJ&=*rU==_h(BarGB5SkhDrvW=Te!tIT{DKANWYvD0vxw?t>B x=E{!5AOZs(9batwj=dR?d;?Zx*a{fS;}`_`GKAB2#0mimudK|u64Pmc{s)*sfD8Zt diff --git a/src/services/__pycache__/agent_runner.cpython-310.pyc b/src/services/__pycache__/agent_runner.cpython-310.pyc index c84a348c327b0d535adbcc61ea5527e66fccb97c..8362fc0ea08c1ce962cc253e702641dd97a1c877 100644 GIT binary patch delta 1059 zcmY*XO>7%Q6n<~K-d*q7`{Um@R3eE7gw(S+_QZ`Vy}-;GP&3ke^WOLVXXe-DH?6aA zrQ(2EU;pV|KjQ&B*2>dMaj>R#Lh#<%yDkC+S{}5A^Bebmd0|Z{!C-0F+2|m!IriKE zZYC0L!8OZcVVSRcL1hA|({P(Hxl z&wIn1=_QPRyxnJ0La+9%r~0ZYSvUTZZY>}HpW*H!`GBnMnF0<-W~Fe1)DTuwxDT|b zPD7pMVeuFdghk;N@R!pfwFD^MJ}eyIKBlHHMNviD!alNub&Tt3+_&Lu0$OiF6lQ*&s3H9S%HRCz3J8b)Ae^J>G0bK~7`cjjc#_+J9mu1bdE5RKxC92I z#ql2qS%|P(R@NGArF9V==<(i}%@^*Y>N)%*+(G5uxQk=!-+*cgQH{?1A3$iE&QFNC z+~$na4>F6D-x@z-TPDQ|_^kY{xNk10N9P^+s`;p;|H0H4Z4TMgNXIuA545c&7p)J? z(^_1XpIe=kP>HKm-qcJ>!<>e;hItLoXgDPwTI+sCiBl_KNwOV}_)BupUdD^^E&K9P zLs^nL3dpCVWT1z}-$B742G*5i8v#1o~v<>HNQJ;fxFM|G@h2nzc}vDj*Obc5VIrvEyFW8*Jg!5OKZ3Bq<~%B<-b0 z#>pYR*gIX5*1n%T6-GZL{ zpBh*s5!g&2ZX4&3B-=_D@1~_>X#%0Me4LrpxGc`%B;AgjgUbiP1WRldA~7J$PR#dwztX7F6MR-L;uwFR zUmDDah`%a8xvS_ZI;d{D5mQkyBP?BYQRmxwO*< Optional[LlmResponse]: + """ + Callback executado antes do modelo gerar uma resposta. + Sempre executa a busca na base de conhecimento antes de prosseguir. + """ + try: + agent_name = callback_context.agent_name + logger.debug(f"🔄 Before model call for agent: {agent_name}") + + # Extrai a última mensagem do usuário + last_user_message = "" + if llm_request.contents and llm_request.contents[-1].role == "user": + if llm_request.contents[-1].parts: + last_user_message = llm_request.contents[-1].parts[0].text + logger.debug(f"📝 Última mensagem do usuário: {last_user_message}") + + # Extrai e formata o histórico de mensagens + history = [] + for content in llm_request.contents: + if content.parts and content.parts[0].text: + # Substitui 'model' por 'assistant' no role + role = "assistant" if content.role == "model" else content.role + history.append( + { + "role": role, + "content": { + "type": "text", + "text": content.parts[0].text, + }, + } + ) + + # loga o histórico de mensagens + logger.debug(f"📝 Histórico de mensagens: {history}") + + if last_user_message: + logger.info("🔍 Executando busca na base de conhecimento") + # Executa a busca na base de conhecimento de forma síncrona + search_results = search_knowledge_base_function_sync( + last_user_message, history + ) + + if search_results: + logger.info("✅ Resultados encontrados, adicionando ao contexto") + + # Obtém a instrução original do sistema + original_instruction = llm_request.config.system_instruction or "" + + # Adiciona os resultados da busca e o histórico ao contexto do sistema + modified_text = ( + original_instruction + + "\n\n\n" + + str(search_results) + + "\n\n\n\n" + + str(history) + + "\n" + ) + llm_request.config.system_instruction = modified_text + + logger.debug( + f"📝 Instrução do sistema atualizada com resultados da busca e histórico" + ) + else: + logger.warning("⚠️ Nenhum resultado encontrado na busca") + else: + logger.warning("⚠️ Nenhuma mensagem do usuário encontrada") + + logger.info("✅ Before_model_callback finalizado") + return None + except Exception as e: + logger.error(f"❌ Erro no before_model_callback: {str(e)}", exc_info=True) + return None + + +def search_knowledge_base_function_sync(query: str, history=[]): + """ + Search knowledge base de forma síncrona. + + Args: + query (str): The search query, with user message and history messages, all in one string + + Returns: + dict: The search results + """ + try: + logger.info("🔍 Iniciando busca na base de conhecimento") + logger.debug(f"Query recebida: {query}") + + # url = os.getenv("KNOWLEDGE_API_URL") + "/api/v1/search" + url = os.getenv("KNOWLEDGE_API_URL") + "/api/v1/knowledge" + tenant_id = os.getenv("TENANT_ID") + url = url + "?tenant_id=" + tenant_id + logger.debug(f"URL da API: {url}") + logger.debug(f"Tenant ID: {tenant_id}") + + headers = { + "x-api-key": f"{os.getenv('KNOWLEDGE_API_KEY')}", + "Content-Type": "application/json", + } + logger.debug(f"Headers configurados: {headers}") + + payload = { + "gemini_api_key": os.getenv("GOOGLE_API_KEY"), + "gemini_model": "gemini-2.0-flash-lite-001", + "gemini_temperature": 0.7, + "query": query, + "tenant_id": tenant_id, + "history": history, + } + + logger.debug(f"Payload da requisição: {payload}") + + # Usando requests para fazer a requisição síncrona com timeout + logger.info("🔄 Fazendo requisição síncrona para a API de conhecimento") + # response = requests.post(url, headers=headers, json=payload) + response = requests.get(url, headers=headers, timeout=10) + + if response.status_code == 200: + logger.info("✅ Busca realizada com sucesso") + result = response.json() + logger.debug(f"Resultado da busca: {result}") + return result + else: + logger.error( + f"❌ Erro ao realizar busca. Status code: {response.status_code}" + ) + return None + except requests.exceptions.Timeout: + logger.error("❌ Timeout ao realizar busca na base de conhecimento") + return None + except requests.exceptions.RequestException as e: + logger.error(f"❌ Erro na requisição: {str(e)}", exc_info=True) + return None + except Exception as e: + logger.error(f"❌ Erro ao realizar busca: {str(e)}", exc_info=True) + return None + + class AgentBuilder: - def __init__(self, db: Session): + def __init__(self, db: Session, memory_service: InMemoryMemoryService): self.db = db self.custom_tool_builder = CustomToolBuilder() self.mcp_service = MCPService() + self.memory_service = memory_service - async def _create_llm_agent(self, agent) -> Tuple[LlmAgent, Optional[AsyncExitStack]]: + async def _create_llm_agent( + self, agent + ) -> Tuple[LlmAgent, Optional[AsyncExitStack]]: """Cria um agente LLM a partir dos dados do agente.""" # Obtém ferramentas personalizadas da configuração custom_tools = [] @@ -34,81 +185,129 @@ class AgentBuilder: # Combina todas as ferramentas all_tools = custom_tools + mcp_tools - return LlmAgent( - name=agent.name, - model=LiteLlm(model=agent.model, api_key=agent.api_key), - instruction=agent.instruction, - description=agent.config.get("description", ""), - tools=all_tools, - ), mcp_exit_stack + # Verifica se load_memory está habilitado + before_model_callback_func = None + if agent.config.get("load_memory") == True: + before_model_callback_func = before_model_callback + + now = datetime.now() + current_datetime = now.strftime("%d/%m/%Y %H:%M") + current_day_of_week = now.strftime("%A") + current_date_iso = now.strftime("%Y-%m-%d") + current_time = now.strftime("%H:%M") - async def _get_sub_agents(self, sub_agent_ids: List[str]) -> List[Tuple[LlmAgent, Optional[AsyncExitStack]]]: + # Substitui as variáveis no prompt + formatted_prompt = agent.instruction.format( + current_datetime=current_datetime, + current_day_of_week=current_day_of_week, + current_date_iso=current_date_iso, + current_time=current_time, + ) + + return ( + LlmAgent( + name=agent.name, + model=LiteLlm(model=agent.model, api_key=agent.api_key), + instruction=formatted_prompt, + description=agent.description, + tools=all_tools, + before_model_callback=before_model_callback_func, + ), + mcp_exit_stack, + ) + + async def _get_sub_agents( + self, sub_agent_ids: List[str] + ) -> List[Tuple[LlmAgent, Optional[AsyncExitStack]]]: """Obtém e cria os sub-agentes LLM.""" sub_agents = [] for sub_agent_id in sub_agent_ids: agent = get_agent(self.db, sub_agent_id) - + if agent is None: raise AgentNotFoundError(f"Agente com ID {sub_agent_id} não encontrado") - + if agent.type != "llm": - raise ValueError(f"Agente {agent.name} (ID: {agent.id}) não é um agente LLM") - + raise ValueError( + f"Agente {agent.name} (ID: {agent.id}) não é um agente LLM" + ) + sub_agent, exit_stack = await self._create_llm_agent(agent) sub_agents.append((sub_agent, exit_stack)) - + return sub_agents - async def build_llm_agent(self, root_agent) -> Tuple[LlmAgent, Optional[AsyncExitStack]]: + async def build_llm_agent( + self, root_agent + ) -> Tuple[LlmAgent, Optional[AsyncExitStack]]: """Constrói um agente LLM com seus sub-agentes.""" logger.info("Criando agente LLM") - + sub_agents = [] if root_agent.config.get("sub_agents"): - sub_agents_with_stacks = await self._get_sub_agents(root_agent.config.get("sub_agents")) + sub_agents_with_stacks = await self._get_sub_agents( + root_agent.config.get("sub_agents") + ) sub_agents = [agent for agent, _ in sub_agents_with_stacks] - + root_llm_agent, exit_stack = await self._create_llm_agent(root_agent) if sub_agents: root_llm_agent.sub_agents = sub_agents - + return root_llm_agent, exit_stack - async def build_composite_agent(self, root_agent) -> Tuple[SequentialAgent | ParallelAgent | LoopAgent, Optional[AsyncExitStack]]: + async def build_composite_agent( + self, root_agent + ) -> Tuple[SequentialAgent | ParallelAgent | LoopAgent, Optional[AsyncExitStack]]: """Constrói um agente composto (Sequential, Parallel ou Loop) com seus sub-agentes.""" logger.info(f"Processando sub-agentes para agente {root_agent.type}") - - sub_agents_with_stacks = await self._get_sub_agents(root_agent.config.get("sub_agents", [])) + + sub_agents_with_stacks = await self._get_sub_agents( + root_agent.config.get("sub_agents", []) + ) sub_agents = [agent for agent, _ in sub_agents_with_stacks] - + if root_agent.type == "sequential": logger.info("Criando SequentialAgent") - return SequentialAgent( - name=root_agent.name, - sub_agents=sub_agents, - description=root_agent.config.get("description", ""), - ), None + return ( + SequentialAgent( + name=root_agent.name, + sub_agents=sub_agents, + description=root_agent.config.get("description", ""), + ), + None, + ) elif root_agent.type == "parallel": logger.info("Criando ParallelAgent") - return ParallelAgent( - name=root_agent.name, - sub_agents=sub_agents, - description=root_agent.config.get("description", ""), - ), None + return ( + ParallelAgent( + name=root_agent.name, + sub_agents=sub_agents, + description=root_agent.config.get("description", ""), + ), + None, + ) elif root_agent.type == "loop": logger.info("Criando LoopAgent") - return LoopAgent( - name=root_agent.name, - sub_agents=sub_agents, - description=root_agent.config.get("description", ""), - max_iterations=root_agent.config.get("max_iterations", 5), - ), None + return ( + LoopAgent( + name=root_agent.name, + sub_agents=sub_agents, + description=root_agent.config.get("description", ""), + max_iterations=root_agent.config.get("max_iterations", 5), + ), + None, + ) else: raise ValueError(f"Tipo de agente inválido: {root_agent.type}") - async def build_agent(self, root_agent) -> Tuple[LlmAgent | SequentialAgent | ParallelAgent | LoopAgent, Optional[AsyncExitStack]]: + async def build_agent( + self, root_agent + ) -> Tuple[ + LlmAgent | SequentialAgent | ParallelAgent | LoopAgent, Optional[AsyncExitStack] + ]: """Constrói o agente apropriado baseado no tipo do agente root.""" if root_agent.type == "llm": return await self.build_llm_agent(root_agent) else: - return await self.build_composite_agent(root_agent) \ No newline at end of file + return await self.build_composite_agent(root_agent) diff --git a/src/services/agent_runner.py b/src/services/agent_runner.py index e3e2af8c..2a88e590 100644 --- a/src/services/agent_runner.py +++ b/src/services/agent_runner.py @@ -6,6 +6,7 @@ from google.adk.agents import SequentialAgent, ParallelAgent, LoopAgent from google.adk.runners import Runner from google.genai.types import Content, Part from google.adk.sessions import DatabaseSessionService +from google.adk.memory import InMemoryMemoryService from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService from src.utils.logger import setup_logger from src.core.exceptions import AgentNotFoundError, InternalServerError @@ -23,6 +24,7 @@ async def run_agent( message: str, session_service: DatabaseSessionService, artifacts_service: InMemoryArtifactService, + memory_service: InMemoryMemoryService, db: Session, ): try: @@ -40,7 +42,7 @@ async def run_agent( raise AgentNotFoundError(f"Agente com ID {agent_id} não encontrado") # Usando o AgentBuilder para criar o agente - agent_builder = AgentBuilder(db) + agent_builder = AgentBuilder(db, memory_service) root_agent, exit_stack = await agent_builder.build_agent(get_root_agent) logger.info("Configurando Runner") diff --git a/src/services/message_service.py b/src/services/message_service.py deleted file mode 100644 index 4e80e536..00000000 --- a/src/services/message_service.py +++ /dev/null @@ -1,35 +0,0 @@ -from sqlalchemy.orm import Session -from src.models.models import Message -from src.schemas.schemas import MessageCreate -from typing import List -import uuid - -def get_message(db: Session, message_id: int) -> Message: - return db.query(Message).filter(Message.id == message_id).first() - -def get_messages_by_session(db: Session, session_id: uuid.UUID, skip: int = 0, limit: int = 100) -> List[Message]: - return db.query(Message).filter(Message.session_id == session_id).offset(skip).limit(limit).all() - -def create_message(db: Session, message: MessageCreate) -> Message: - db_message = Message(**message.model_dump()) - db.add(db_message) - db.commit() - db.refresh(db_message) - return db_message - -def update_message(db: Session, message_id: int, message: MessageCreate) -> Message: - db_message = db.query(Message).filter(Message.id == message_id).first() - if db_message: - for key, value in message.model_dump().items(): - setattr(db_message, key, value) - db.commit() - db.refresh(db_message) - return db_message - -def delete_message(db: Session, message_id: int) -> bool: - db_message = db.query(Message).filter(Message.id == message_id).first() - if db_message: - db.delete(db_message) - db.commit() - return True - return False \ No newline at end of file diff --git a/src/utils/__pycache__/logger.cpython-310.pyc b/src/utils/__pycache__/logger.cpython-310.pyc index 2276fbac133c01f07cfaaec471674bc8ad8c615e..d404ff3ee6fd685b8f41f3c0fe46f2fff189757c 100644 GIT binary patch delta 771 zcmY*XziSjh6rLZuvp;Sx7f+DGM$zIpBB_FviV$Kf4pImTi?A#=lTD7j-DP$zplnQW z0UH}xvEFO#tStQ#MC_*ZAF#Ia&29qc4DXxy-kWdc&71iieCuE2{l1T&{ruZI89503 zHe|C|K%Rn?Apl1lrzq@39O9OfkibY%y0AhEn;1>)1q&HQhlpF;zC_#>Xzr|N=yE1J z?f~Lcq_{mXhBX>WRT7)=je9!#H2hSG(Uw3i4q&67k(?ViLLPlYVCJxfI_ zwF9wODXB?bY6e7PXOe|Xzf?rNS9v@c6bxH{{K)9$&ERr@j8 c)Z9mloG(-H#5{dD+yZowi(MQ*ACl4NANjPjA^-pY delta 666 zcmY*X&5G1O5bo;EPbZVv1uw3M8W6?}yHBvlvbf+6$m~J!kjoIZd$KcRlA+TP79xAt zJ&CuRyv^S9;K7p@LBxj;;z{)4%}200Q5Xxlx~jhVs;VpbF#Hsp6~m!VP=3B0oP4zj z`C*)OanU)zQ$9r{h=7d5{S7goPcyn;F$3~|2qvrxA}mSf);Wu9yqzfsTe`xLO!8|) zxES-Mj^M((uw!3bg(oEPe_?4dj-cg7OFb*{BzynnH^4^onjYF7Vq493`U>18XFIno z^py#!jsMlAaTzMR`N-~^8LPGOWFcESlh0+=I!fwF<;N6}e`2bc%tJio0V+*aP?Jk| zL2J6AHCzIan$B5y6A??>a|bb2vz4p)Ip!I3<<+dW@HJ3<#xj2M(t5TXqIA9Y!ts}P zI7298m=$TbE#x$*GQAf*8s9&Pns3&}KaDNiH&}%BHq= z`nUOs5D)ukWT{(7RiWWK*uaoCkBhu4GCBE6UE7ygRU2UOYB}k%Z(yV}3-t#+I4qz6 z_+U}Bi=k-H4W>qh<>=GpWR|DOY*BHe%>LAM1F}-9md$eAI{gbe{#xVilXczs-9;g) TOzr~`OY;wM!XbbwbhPyc&})>& diff --git a/src/utils/logger.py b/src/utils/logger.py index f012078d..48e08107 100644 --- a/src/utils/logger.py +++ b/src/utils/logger.py @@ -1,6 +1,8 @@ import logging +import os import sys from typing import Optional +from src.config.settings import settings class CustomFormatter(logging.Formatter): """Formatação personalizada para logs""" @@ -26,25 +28,33 @@ class CustomFormatter(logging.Formatter): formatter = logging.Formatter(log_fmt) return formatter.format(record) -def setup_logger(name: str, level: Optional[int] = logging.INFO) -> logging.Logger: +def setup_logger(name: str) -> logging.Logger: """ Configura um logger personalizado Args: name: Nome do logger - level: Nível de log (default: INFO) Returns: logging.Logger: Logger configurado """ logger = logging.getLogger(name) - logger.setLevel(level) - # Evitar duplicação de handlers - if not logger.handlers: - # Handler para console - console_handler = logging.StreamHandler(sys.stdout) - console_handler.setFormatter(CustomFormatter()) - logger.addHandler(console_handler) + # Remove handlers existentes para evitar duplicação + if logger.handlers: + logger.handlers.clear() + + # Configura o nível do logger baseado na variável de ambiente ou configuração + log_level = getattr(logging, os.getenv("LOG_LEVEL", settings.LOG_LEVEL).upper()) + logger.setLevel(log_level) + + # Handler para console + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setFormatter(CustomFormatter()) + console_handler.setLevel(log_level) + logger.addHandler(console_handler) + + # Impede que os logs sejam propagados para o logger root + logger.propagate = False return logger \ No newline at end of file