mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-09-07 10:13:46 +00:00
New features: Route Stickness, Handoff, Clarification, Read-Only/Transactional, Long Term Memory
This commit is contained in:
@@ -14,38 +14,45 @@ CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
||||
# LLM - OCI Generative AI como provider principal
|
||||
###############################################################################
|
||||
# Opções: mock, oci_openai, oci_sdk, openai_compatible
|
||||
LLM_PROVIDER=oci_openai
|
||||
LLM_PROVIDER=oci_sdk
|
||||
LLM_TEMPERATURE=0.2
|
||||
LLM_MAX_TOKENS=2048
|
||||
LLM_TIMEOUT_SECONDS=120
|
||||
|
||||
# OCI OpenAI-compatible endpoint
|
||||
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/openai/v1
|
||||
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com
|
||||
OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
OCI_GENAI_API_KEY=sk-ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6ph3FgX6
|
||||
OCI_GENAI_API_KEY=sk-ph3FgX6iP3fxAQCXb9IpPIDTadkeeYAWntUWhzcWysIM6zsS
|
||||
OCI_GENAI_PROJECT_OCID=
|
||||
|
||||
#OCI_GENAI_BASE_URL=https://pegruagntaiatenddev.pe.inference.generativeai.sa-saopaulo-1.oci.oraclecloud.com
|
||||
#OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
#OCI_GENAI_API_KEY=
|
||||
#OCI_GENAI_PROJECT_OCID=
|
||||
|
||||
|
||||
# OCI_AUTH_MODE=config_file|instance_principal|resource_principal
|
||||
OCI_AUTH_MODE=config_file
|
||||
# OCI SDK / signer / profiles
|
||||
OCI_CONFIG_FILE=~/.oci/config
|
||||
OCI_PROFILE=DEFAULT
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
OCI_PROFILE=LATINOAMERICA-Chicago
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaexpiw4a7dio64mkfv2t273s2hgdl6mgfvvyv7tycalnjlvpvfl3q
|
||||
OCI_REGION=us-chicago-1
|
||||
|
||||
###############################################################################
|
||||
# Persistência
|
||||
###############################################################################
|
||||
# Opções: memory, autonomous, mongodb
|
||||
SESSION_REPOSITORY_PROVIDER=sqlite
|
||||
MEMORY_REPOSITORY_PROVIDER=sqlite
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=sqlite
|
||||
SQLITE_DB_PATH=./data/agent_framework.db
|
||||
SESSION_REPOSITORY_PROVIDER=autonomous
|
||||
MEMORY_REPOSITORY_PROVIDER=autonomous
|
||||
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
|
||||
|
||||
# Autonomous Database
|
||||
ADB_USER=admin
|
||||
ADB_PASSWORD=fjhsdf04954hf
|
||||
ADB_DSN=oradb23aidev_high
|
||||
ADB_WALLET_LOCATION=/ORACLE/DEFAULT/Wallet_ORADB23aiDev
|
||||
ADB_WALLET_PASSWORD=fjhsdf04954hf
|
||||
ADB_PASSWORD=Moniquinha19721972
|
||||
ADB_DSN=oradb23ai_high
|
||||
ADB_WALLET_LOCATION=/mnt/d/Dropbox/ORACLE/LatinoAmerica/Wallet_ORADB23ai
|
||||
ADB_WALLET_PASSWORD=Moniquinha1972
|
||||
ADB_TABLE_PREFIX=AGENTFW
|
||||
|
||||
# MongoDB - também pode representar Autonomous usando API compatível com Mongo, se habilitada no ambiente
|
||||
@@ -59,10 +66,10 @@ ENABLE_REDIS_CACHE=false
|
||||
###############################################################################
|
||||
# RAG / Vector / Graph
|
||||
###############################################################################
|
||||
VECTOR_STORE_PROVIDER=sqlite
|
||||
GRAPH_STORE_PROVIDER=sqlite
|
||||
VECTOR_STORE_PROVIDER=autonomous
|
||||
GRAPH_STORE_PROVIDER=autonomous
|
||||
RAG_TOP_K=5
|
||||
EMBEDDING_PROVIDER=mock
|
||||
EMBEDDING_PROVIDER=oci
|
||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
||||
|
||||
@@ -70,14 +77,21 @@ RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
|
||||
# Observabilidade
|
||||
###############################################################################
|
||||
ENABLE_LANGFUSE=true
|
||||
LANGFUSE_TRACE_MODE=compact # Opcional: verbose, compact
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-2f9da109-5b0f-4c78-b61d-9598ed787eba
|
||||
LANGFUSE_SECRET_KEY=sk-lf-a4cb0cdd-f2ea-4468-9911-cebeb91ba944
|
||||
# Opcional: verbose, compact
|
||||
LANGFUSE_TRACE_MODE=compact
|
||||
# Nome customizado do trace pai, ex.: backoffice.checklist.workflow ou backoffice.emulador.workflow
|
||||
LANGFUSE_COMPACT_VISIBLE_EVENT_PREFIXES=AGA.,NOC., IC.
|
||||
LANGFUSE_COMPACT_SUPPRESSED_PREFIXES=llm.chat_completion
|
||||
LANGFUSE_IGNORE_HEALTHCHECKS=true
|
||||
LANGFUSE_IGNORED_PATHS=/health,/ready,/metrics
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-4a1e3921-5158-4fd3-a16d-7a77549fb312
|
||||
LANGFUSE_SECRET_KEY=sk-lf-efc6fd59-c5ec-4858-b6ec-4aa129734915
|
||||
LANGFUSE_HOST=http://localhost:3005
|
||||
ENABLE_OTEL=false
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT=
|
||||
OTEL_SERVICE_NAME=ai-agent-template
|
||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
||||
ENABLE_LANGFUSE_ANALYTICS_PUBLISHER=false
|
||||
|
||||
###############################################################################
|
||||
# Analytics / Observer corporativo
|
||||
@@ -85,7 +99,7 @@ ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
||||
# Quando true, AgentObserver publica eventos IC.*, NOC.* e GRL.* nos providers abaixo.
|
||||
ENABLE_ANALYTICS=false
|
||||
# Providers aceitos: oci_streaming,pubsub,noop
|
||||
ANALYTICS_PROVIDERS=pubsub
|
||||
ANALYTICS_PROVIDERS=oci_streaming
|
||||
# Compatibilidade FIRST/TIM: pode informar AGENT_PUBSUB_TOPIC diretamente.
|
||||
AGENT_PUBSUB_TOPIC=
|
||||
GCP_PUBSUB_TOPIC_PATH=
|
||||
@@ -138,6 +152,17 @@ ROUTING_CONFIG_PATH=./config/routing.yaml
|
||||
# Em produção, costuma ser útil; em desenvolvimento, false evita custo e latência.
|
||||
ENABLE_LLM_ROUTER=true
|
||||
|
||||
# Semantic route stickiness (optional).
|
||||
# Uses a lightweight LLM profile to decide only CONTINUE vs ROUTE.
|
||||
# There are no regexes or deterministic language rules.
|
||||
ENABLE_ROUTE_STICKINESS=true
|
||||
ROUTE_STICKINESS_LLM_PROFILE=route_continuity
|
||||
ROUTE_STICKINESS_CONFIDENCE_THRESHOLD=0.90
|
||||
ROUTE_STICKINESS_HISTORY_TURNS=2
|
||||
ROUTE_STICKINESS_MAX_TOKENS=80
|
||||
HUMAN_HANDOFF_MESSAGE=Vou encaminhar seu atendimento para uma pessoa.
|
||||
END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato.
|
||||
|
||||
###############################################################################
|
||||
# MCP / Tools
|
||||
###############################################################################
|
||||
@@ -150,7 +175,7 @@ MCP_TOOL_TIMEOUT_SECONDS=30
|
||||
ROUTING_MODE=router
|
||||
|
||||
# Usage/cost accounting
|
||||
USAGE_REPOSITORY_PROVIDER=sqlite
|
||||
USAGE_REPOSITORY_PROVIDER=autonomous
|
||||
IDENTITY_CONFIG_PATH=./config/identity.yaml
|
||||
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml
|
||||
|
||||
@@ -167,18 +192,6 @@ MEMORY_SUMMARY_USE_LLM=true
|
||||
MEMORY_INJECT_RECENT_MESSAGES=true
|
||||
MEMORY_INJECT_SUMMARY=true
|
||||
|
||||
###############################################################################
|
||||
# MCP Gateway
|
||||
###############################################################################
|
||||
# true = framework routes tool calls to the dedicated MCP Gateway.
|
||||
# false = framework calls MCP servers directly from mcp_servers.yaml.
|
||||
MCP_GATEWAY_ENABLED=true
|
||||
MCP_GATEWAY_URL=http://localhost:8300
|
||||
MCP_GATEWAY_TIMEOUT_SECONDS=60
|
||||
# MCP_GATEWAY_TOKEN=
|
||||
MCP_GATEWAY_AGENT_ID=telecom_contas
|
||||
MCP_GATEWAY_TENANT_ID=default
|
||||
|
||||
###############################################################################
|
||||
# LONG-TERM MEMORY
|
||||
###############################################################################
|
||||
|
||||
@@ -4207,3 +4207,7 @@ A implementação está arquiteturalmente correta quando:
|
||||
```
|
||||
|
||||
Com esse desenho, adicionar um novo agente não exige reescrever o frontend nem copiar lógica entre backends. O desenvolvedor cria o backend especializado, registra no Agent Gateway e deixa o framework cuidar dos motores transversais.
|
||||
|
||||
## Política read-only/transacional
|
||||
|
||||
Este template inclui o arquivo opcional `config/tool_policies.yaml`. Use `operation_type: read_only` para consultas e `operation_type: transactional` com `require_confirmation: true` para ações que só podem executar após confirmação booleana explícita. Se o arquivo for removido ou não existir em um template antigo, os campos legados de `config/tools.yaml` continuam válidos.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -44,6 +44,36 @@ class BillingAgent(AgentRuntimeMixin):
|
||||
component="agent.billing.mcp",
|
||||
)
|
||||
|
||||
state["mcp_results"] = tool_context
|
||||
clarification_message = self.transaction_clarification_message(state)
|
||||
if clarification_message:
|
||||
return {
|
||||
"answer": f"[{self.__class__.__name__}] {clarification_message}",
|
||||
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
confirmation_message = self.transaction_confirmation_message(state)
|
||||
if confirmation_message:
|
||||
result = {
|
||||
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
|
||||
"next_state": state.get("next_state"),
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
return result
|
||||
|
||||
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="BillingAgent")
|
||||
if direct_answer:
|
||||
return {
|
||||
"answer": direct_answer,
|
||||
"next_state": state.get("next_state") or "ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
rag_context, rag_metadata = await self._retrieve_rag_context(state)
|
||||
if rag_metadata.get("enabled"):
|
||||
await self._emit_ic(
|
||||
@@ -79,6 +109,7 @@ class BillingAgent(AgentRuntimeMixin):
|
||||
"mcp_results": tool_context,
|
||||
"rag": rag_metadata,
|
||||
"memory_context_metadata": state.get("memory_context_metadata"),
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
await self._emit_ic(
|
||||
|
||||
@@ -44,6 +44,36 @@ class OrdersAgent(AgentRuntimeMixin):
|
||||
component="agent.orders.mcp",
|
||||
)
|
||||
|
||||
state["mcp_results"] = tool_context
|
||||
clarification_message = self.transaction_clarification_message(state)
|
||||
if clarification_message:
|
||||
return {
|
||||
"answer": f"[{self.__class__.__name__}] {clarification_message}",
|
||||
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
confirmation_message = self.transaction_confirmation_message(state)
|
||||
if confirmation_message:
|
||||
result = {
|
||||
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
|
||||
"next_state": state.get("next_state"),
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
return result
|
||||
|
||||
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="OrdersAgent")
|
||||
if direct_answer:
|
||||
return {
|
||||
"answer": direct_answer,
|
||||
"next_state": state.get("next_state") or "ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
rag_context, rag_metadata = await self._retrieve_rag_context(state)
|
||||
if rag_metadata.get("enabled"):
|
||||
await self._emit_ic(
|
||||
@@ -79,6 +109,7 @@ class OrdersAgent(AgentRuntimeMixin):
|
||||
"mcp_results": tool_context,
|
||||
"rag": rag_metadata,
|
||||
"memory_context_metadata": state.get("memory_context_metadata"),
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
await self._emit_ic(
|
||||
|
||||
@@ -44,6 +44,36 @@ class ProductAgent(AgentRuntimeMixin):
|
||||
component="agent.product.mcp",
|
||||
)
|
||||
|
||||
state["mcp_results"] = tool_context
|
||||
clarification_message = self.transaction_clarification_message(state)
|
||||
if clarification_message:
|
||||
return {
|
||||
"answer": f"[{self.__class__.__name__}] {clarification_message}",
|
||||
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
confirmation_message = self.transaction_confirmation_message(state)
|
||||
if confirmation_message:
|
||||
result = {
|
||||
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
|
||||
"next_state": state.get("next_state"),
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
return result
|
||||
|
||||
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="ProductAgent")
|
||||
if direct_answer:
|
||||
return {
|
||||
"answer": direct_answer,
|
||||
"next_state": state.get("next_state") or "ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
rag_context, rag_metadata = await self._retrieve_rag_context(state)
|
||||
if rag_metadata.get("enabled"):
|
||||
await self._emit_ic(
|
||||
@@ -79,6 +109,7 @@ class ProductAgent(AgentRuntimeMixin):
|
||||
"mcp_results": tool_context,
|
||||
"rag": rag_metadata,
|
||||
"memory_context_metadata": state.get("memory_context_metadata"),
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
await self._emit_ic(
|
||||
|
||||
@@ -44,6 +44,36 @@ class SupportAgent(AgentRuntimeMixin):
|
||||
component="agent.support.mcp",
|
||||
)
|
||||
|
||||
state["mcp_results"] = tool_context
|
||||
clarification_message = self.transaction_clarification_message(state)
|
||||
if clarification_message:
|
||||
return {
|
||||
"answer": f"[{self.__class__.__name__}] {clarification_message}",
|
||||
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
confirmation_message = self.transaction_confirmation_message(state)
|
||||
if confirmation_message:
|
||||
result = {
|
||||
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
|
||||
"next_state": state.get("next_state"),
|
||||
"mcp_results": tool_context,
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
return result
|
||||
|
||||
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="SupportAgent")
|
||||
if direct_answer:
|
||||
return {
|
||||
"answer": direct_answer,
|
||||
"next_state": state.get("next_state") or "ACTIVE",
|
||||
"mcp_results": tool_context,
|
||||
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
rag_context, rag_metadata = await self._retrieve_rag_context(state)
|
||||
if rag_metadata.get("enabled"):
|
||||
await self._emit_ic(
|
||||
@@ -79,6 +109,7 @@ class SupportAgent(AgentRuntimeMixin):
|
||||
"mcp_results": tool_context,
|
||||
"rag": rag_metadata,
|
||||
"memory_context_metadata": state.get("memory_context_metadata"),
|
||||
**self.transaction_state_patch(state),
|
||||
}
|
||||
|
||||
await self._emit_ic(
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -291,6 +291,11 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False)
|
||||
"conversation_key": agent_session_id,
|
||||
"workflow_id": workflow_id,
|
||||
"agent_profile": normalized_context["agent_profile"],
|
||||
# Chave estável de LTM. Nunca use session_id como identidade de longo prazo.
|
||||
"long_term_memory_subject_key": business_context.customer_key or session.user_id,
|
||||
"customer_key": business_context.customer_key,
|
||||
"user_id": session.user_id,
|
||||
"business_context": business_context.model_dump(),
|
||||
"user_text": msg.text,
|
||||
"history": history,
|
||||
"context": {
|
||||
@@ -365,6 +370,13 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False)
|
||||
"identity_missing": missing_identity_keys,
|
||||
"judges": result.get("judge_results"),
|
||||
"guardrails": result.get("guardrail_decisions"),
|
||||
"long_term_memory": {
|
||||
"subject_key": business_context.customer_key or session.user_id,
|
||||
"loaded": result.get("long_term_memories", []),
|
||||
"context": result.get("long_term_memory_context", ""),
|
||||
"load_error": result.get("long_term_memory_load_error"),
|
||||
"write_result": result.get("long_term_memory_write_result", {}),
|
||||
},
|
||||
},
|
||||
)
|
||||
rendered = await gateway.render(response)
|
||||
@@ -387,6 +399,14 @@ async def health():
|
||||
"sse_enabled": settings.ENABLE_SSE,
|
||||
"session_repository": settings.SESSION_REPOSITORY_PROVIDER,
|
||||
"memory_repository": settings.MEMORY_REPOSITORY_PROVIDER,
|
||||
"long_term_memory": {
|
||||
"enabled": getattr(settings, "ENABLE_LONG_TERM_MEMORY", False),
|
||||
"provider": getattr(settings, "LONG_TERM_MEMORY_PROVIDER", None),
|
||||
"sqlite_path": getattr(settings, "LONG_TERM_MEMORY_SQLITE_PATH", None),
|
||||
"table": getattr(settings, "LONG_TERM_MEMORY_TABLE", None),
|
||||
"auto_extract": getattr(settings, "LONG_TERM_MEMORY_AUTO_EXTRACT", None),
|
||||
"inject_context": getattr(settings, "LONG_TERM_MEMORY_INJECT_CONTEXT", None),
|
||||
},
|
||||
"checkpoint_repository": settings.CHECKPOINT_REPOSITORY_PROVIDER,
|
||||
"usage_repository": settings.USAGE_REPOSITORY_PROVIDER,
|
||||
"identity_config_path": settings.IDENTITY_CONFIG_PATH,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -23,9 +23,22 @@ class AgentState(TypedDict, total=False):
|
||||
domain: str
|
||||
mcp_tools: list[str]
|
||||
mcp_results: list[dict[str, Any]]
|
||||
available_mcp_tools: list[str]
|
||||
selected_tool_call: dict[str, Any]
|
||||
pending_tool_call: dict[str, Any]
|
||||
transaction_status: str
|
||||
confirmation_required: bool
|
||||
confirmation_received: bool
|
||||
tool_policy_result: dict[str, Any]
|
||||
missing_parameters: list[str]
|
||||
supervisor_plan: dict[str, Any]
|
||||
supervisor_results: list[dict[str, Any]]
|
||||
active_agent: str
|
||||
route_bypassed: bool
|
||||
continuity_signal: dict[str, Any]
|
||||
session_control: str
|
||||
session_ended: bool
|
||||
human_handoff_requested: bool
|
||||
blocked: bool
|
||||
supervisor_action: str
|
||||
supervisor_guidance: str
|
||||
@@ -36,3 +49,5 @@ class AgentState(TypedDict, total=False):
|
||||
long_term_memories: list[dict[str, Any]]
|
||||
long_term_memory_context: str
|
||||
long_term_memory_write_result: dict[str, Any]
|
||||
long_term_memory_subject_key: str
|
||||
long_term_memory_load_error: str
|
||||
|
||||
Binary file not shown.
@@ -139,12 +139,15 @@ class AgentWorkflow:
|
||||
def _build_graph(self):
|
||||
builder = StateGraph(AgentState)
|
||||
builder.add_node("input_guardrails", self._node("input_guardrails", self.input_guardrails))
|
||||
builder.add_node("load_long_term_memory", self._node("load_long_term_memory", self.load_long_term_memory))
|
||||
builder.add_node("routing_decision", self._node("routing_decision", self.routing_decision))
|
||||
builder.add_node("billing_agent", self._node("billing_agent", self.billing_agent))
|
||||
builder.add_node("product_agent", self._node("product_agent", self.product_agent))
|
||||
builder.add_node("orders_agent", self._node("orders_agent", self.orders_agent))
|
||||
builder.add_node("support_agent", self._node("support_agent", self.support_agent))
|
||||
builder.add_node("handoff", self._node("handoff", self.handoff))
|
||||
builder.add_node("human_handoff", self._node("human_handoff", self.human_handoff))
|
||||
builder.add_node("end_session", self._node("end_session", self.end_session))
|
||||
builder.add_node("supervisor_agent", self._node("supervisor_agent", self.supervisor_agent))
|
||||
builder.add_node("output_supervisor", self._node("output_supervisor", self.output_supervisor))
|
||||
builder.add_node("output_guardrails", self._node("output_guardrails", self.output_guardrails))
|
||||
@@ -157,8 +160,9 @@ class AgentWorkflow:
|
||||
builder.add_conditional_edges(
|
||||
"input_guardrails",
|
||||
self._after_input_guardrails,
|
||||
{"blocked": "persist", "continue": "routing_decision"},
|
||||
{"blocked": "persist", "continue": "load_long_term_memory"},
|
||||
)
|
||||
builder.add_edge("load_long_term_memory", "routing_decision")
|
||||
builder.add_conditional_edges(
|
||||
"routing_decision",
|
||||
lambda s: s.get("route", "billing_agent"),
|
||||
@@ -168,6 +172,8 @@ class AgentWorkflow:
|
||||
"orders_agent": "orders_agent",
|
||||
"support_agent": "support_agent",
|
||||
"handoff": "handoff",
|
||||
"human_handoff": "human_handoff",
|
||||
"end_session": "end_session",
|
||||
"supervisor_agent": "supervisor_agent",
|
||||
},
|
||||
)
|
||||
@@ -176,6 +182,8 @@ class AgentWorkflow:
|
||||
builder.add_edge("orders_agent", "output_supervisor")
|
||||
builder.add_edge("support_agent", "output_supervisor")
|
||||
builder.add_edge("handoff", "output_supervisor")
|
||||
builder.add_edge("human_handoff", "output_supervisor")
|
||||
builder.add_edge("end_session", "output_supervisor")
|
||||
builder.add_edge("supervisor_agent", "output_supervisor")
|
||||
builder.add_edge("output_supervisor", "output_guardrails")
|
||||
builder.add_edge("output_guardrails", "judge")
|
||||
@@ -190,6 +198,24 @@ class AgentWorkflow:
|
||||
return "blocked" if state.get("blocked") else "continue"
|
||||
|
||||
async def input_guardrails(self, state):
|
||||
if state.get("session_ended") is True:
|
||||
answer = str(getattr(
|
||||
self.settings,
|
||||
"SESSION_ALREADY_ENDED_MESSAGE",
|
||||
"Este atendimento já foi encerrado. Inicie uma nova sessão para continuar.",
|
||||
))
|
||||
await self.telemetry.event(
|
||||
"session.message.rejected_after_end",
|
||||
{"session_id": state.get("conversation_key") or state.get("session_id")},
|
||||
)
|
||||
return {
|
||||
"answer": answer,
|
||||
"final_answer": answer,
|
||||
"blocked": True,
|
||||
"session_control": "END_SESSION",
|
||||
"session_ended": True,
|
||||
"next_state": "SESSION_ENDED",
|
||||
}
|
||||
async with self.telemetry.span(
|
||||
"workflow.input_guardrails",
|
||||
session_id=state.get("conversation_key") or state.get("session_id"),
|
||||
@@ -326,6 +352,17 @@ class AgentWorkflow:
|
||||
"domain": decision.domain,
|
||||
"mcp_tools": decision.mcp_tools,
|
||||
"next_state": decision.next_state,
|
||||
"active_agent": decision.agent,
|
||||
"route_bypassed": decision.method == "continuity",
|
||||
"session_control": (decision.metadata or {}).get("session_control", ""),
|
||||
"human_handoff_requested": (decision.metadata or {}).get("session_control") == "HUMAN_HANDOFF",
|
||||
"session_ended": (decision.metadata or {}).get("session_control") == "END_SESSION",
|
||||
"continuity_signal": {
|
||||
"decision": (decision.metadata or {}).get("continuity_decision"),
|
||||
"confidence": decision.confidence if decision.method == "continuity" else None,
|
||||
"reason": decision.reason if decision.method == "continuity" else None,
|
||||
"profile": (decision.metadata or {}).get("continuity_profile"),
|
||||
} if decision.method == "continuity" else {},
|
||||
}
|
||||
|
||||
async def billing_agent(self, state):
|
||||
@@ -415,6 +452,48 @@ class AgentWorkflow:
|
||||
)
|
||||
return {"answer": answer}
|
||||
|
||||
async def human_handoff(self, state):
|
||||
session_id = state.get("conversation_key") or state.get("session_id")
|
||||
async with self.telemetry.span("workflow.human_handoff", session_id=session_id):
|
||||
answer = str(getattr(self.settings, "HUMAN_HANDOFF_MESSAGE", "Vou encaminhar seu atendimento para uma pessoa."))
|
||||
await self.telemetry.event(
|
||||
"session.human_handoff.requested",
|
||||
{
|
||||
"session_id": session_id,
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"reason": (state.get("route_decision") or {}).get("reason"),
|
||||
},
|
||||
)
|
||||
return {
|
||||
"answer": answer,
|
||||
"session_control": "HUMAN_HANDOFF",
|
||||
"human_handoff_requested": True,
|
||||
"session_ended": False,
|
||||
"next_state": "HUMAN_HANDOFF_REQUESTED",
|
||||
}
|
||||
|
||||
async def end_session(self, state):
|
||||
session_id = state.get("conversation_key") or state.get("session_id")
|
||||
async with self.telemetry.span("workflow.end_session", session_id=session_id):
|
||||
answer = str(getattr(self.settings, "END_SESSION_MESSAGE", "Atendimento encerrado. Obrigado pelo contato."))
|
||||
await self.telemetry.event(
|
||||
"session.end.requested",
|
||||
{
|
||||
"session_id": session_id,
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"reason": (state.get("route_decision") or {}).get("reason"),
|
||||
},
|
||||
)
|
||||
return {
|
||||
"answer": answer,
|
||||
"session_control": "END_SESSION",
|
||||
"session_ended": True,
|
||||
"human_handoff_requested": False,
|
||||
"next_state": "SESSION_ENDED",
|
||||
}
|
||||
|
||||
async def output_supervisor(self, state):
|
||||
"""Valida a resposta candidata com o OutputSupervisor corporativo.
|
||||
|
||||
@@ -576,8 +655,34 @@ class AgentWorkflow:
|
||||
session_id=state.get("conversation_key") or state.get("session_id"),
|
||||
input={"question": state.get("user_text"), "answer": state.get("final_answer")},
|
||||
):
|
||||
judge_context = dict(state.get("context", {}) or {})
|
||||
judge_context["mcp_results"] = state.get("mcp_results", [])
|
||||
judge_context["evidence"] = state.get("mcp_results", []) or judge_context.get("evidence")
|
||||
judge_context["route"] = state.get("route")
|
||||
judge_context["intent"] = state.get("intent")
|
||||
# Judge sampling must see the finalized transaction state. These
|
||||
# fields are populated by the agent/tool runtime before this node.
|
||||
for key in (
|
||||
"transaction_status",
|
||||
"confirmation_required",
|
||||
"confirmation_received",
|
||||
"tool_policy_result",
|
||||
"selected_tool_call",
|
||||
"pending_tool_call",
|
||||
):
|
||||
judge_context[key] = state.get(key)
|
||||
judge_context["transactional_tools"] = [
|
||||
result.get("tool_name")
|
||||
for result in state.get("mcp_results", [])
|
||||
if isinstance(result, dict)
|
||||
and (
|
||||
(result.get("metadata") or {}).get("operation_type") == "transactional"
|
||||
or result.get("awaiting_confirmation")
|
||||
or result.get("transaction_status")
|
||||
)
|
||||
]
|
||||
results = await self.judges.evaluate_all(
|
||||
state["user_text"], state["final_answer"], state.get("context", {})
|
||||
state["user_text"], state["final_answer"], judge_context
|
||||
)
|
||||
for _result in results:
|
||||
await self.judge_telemetry.evaluated(_result)
|
||||
@@ -607,9 +712,78 @@ class AgentWorkflow:
|
||||
)
|
||||
return {"final_answer": answer if ok else answer}
|
||||
|
||||
async def load_long_term_memory(self, state):
|
||||
"""Carrega LTM antes do roteamento e mantém o resultado no estado.
|
||||
|
||||
A carga explícita evita depender apenas do agente selecionado para realizar
|
||||
a recuperação e facilita o diagnóstico de identidade/namespace.
|
||||
"""
|
||||
try:
|
||||
memories = await self.long_term_memory_manager.load(state)
|
||||
serialized = []
|
||||
context_lines = []
|
||||
for item in memories or []:
|
||||
if hasattr(item, "model_dump"):
|
||||
data = item.model_dump(mode="json")
|
||||
elif hasattr(item, "__dict__"):
|
||||
data = dict(item.__dict__)
|
||||
elif isinstance(item, dict):
|
||||
data = dict(item)
|
||||
else:
|
||||
data = {"value": str(item)}
|
||||
serialized.append(data)
|
||||
key = data.get("key") or data.get("memory_key") or data.get("category") or "memory"
|
||||
value = data.get("value") or data.get("memory_value")
|
||||
if value not in (None, ""):
|
||||
context_lines.append(f"- {key}: {value}")
|
||||
|
||||
return {
|
||||
"long_term_memories": serialized,
|
||||
"long_term_memory_context": "\n".join(context_lines),
|
||||
}
|
||||
except Exception as exc:
|
||||
await self.telemetry.event(
|
||||
"long_term_memory.load.failed",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"subject_key": state.get("long_term_memory_subject_key"),
|
||||
"error": str(exc),
|
||||
},
|
||||
)
|
||||
return {
|
||||
"long_term_memories": [],
|
||||
"long_term_memory_context": "",
|
||||
"long_term_memory_load_error": str(exc),
|
||||
}
|
||||
|
||||
async def persist_long_term_memory(self, state):
|
||||
result = await self.long_term_memory_manager.persist_turn(state)
|
||||
return {"long_term_memory_write_result": result}
|
||||
try:
|
||||
result = await self.long_term_memory_manager.persist_turn(state)
|
||||
await self.telemetry.event(
|
||||
"long_term_memory.persist.completed",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"subject_key": state.get("long_term_memory_subject_key"),
|
||||
"result": result,
|
||||
},
|
||||
)
|
||||
return {"long_term_memory_write_result": result}
|
||||
except Exception as exc:
|
||||
await self.telemetry.event(
|
||||
"long_term_memory.persist.failed",
|
||||
{
|
||||
"session_id": state.get("conversation_key") or state.get("session_id"),
|
||||
"tenant_id": state.get("tenant_id"),
|
||||
"agent_id": state.get("agent_id"),
|
||||
"subject_key": state.get("long_term_memory_subject_key"),
|
||||
"error": str(exc),
|
||||
},
|
||||
)
|
||||
return {"long_term_memory_write_result": {"saved": 0, "error": str(exc)}}
|
||||
|
||||
async def persist(self, state):
|
||||
async with self.telemetry.span(
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
enabled: true
|
||||
fail_closed: true
|
||||
profile: judge
|
||||
|
||||
judges:
|
||||
- name: response_quality
|
||||
enabled: true
|
||||
threshold: 0.7
|
||||
|
||||
- name: groundedness
|
||||
enabled: true
|
||||
threshold: 0.6
|
||||
|
||||
- name: sentiment
|
||||
enabled: true
|
||||
fail_on_negative: false
|
||||
|
||||
- name: tone
|
||||
enabled: true
|
||||
fail_closed: true
|
||||
- name: response_quality
|
||||
enabled: true
|
||||
threshold: 0.7
|
||||
- name: groundedness
|
||||
enabled: true
|
||||
threshold: 0.6
|
||||
- name: sentiment
|
||||
enabled: true
|
||||
fail_on_negative: false
|
||||
- name: tone
|
||||
enabled: true
|
||||
fail_closed: true
|
||||
sample_rate: 0.25
|
||||
always_run_for_transactional: true
|
||||
|
||||
@@ -8,18 +8,16 @@ mcp_parameter_mapping:
|
||||
contract_key: invoice_id
|
||||
interaction_key: ura_call_id
|
||||
session_key: session_id
|
||||
|
||||
extract:
|
||||
mes_referencia:
|
||||
from: message
|
||||
type: int
|
||||
strategy: month_name_pt
|
||||
description: >
|
||||
Extrair mês citado na mensagem.
|
||||
janeiro=1, fevereiro=2, março=3,
|
||||
abril=4, maio=5, junho=6,
|
||||
julho=7, agosto=8, setembro=9,
|
||||
outubro=10, novembro=11, dezembro=12.
|
||||
description: 'Extrair mês citado na mensagem. janeiro=1, fevereiro=2, março=3,
|
||||
abril=4, maio=5, junho=6, julho=7, agosto=8, setembro=9, outubro=10, novembro=11,
|
||||
dezembro=12.
|
||||
|
||||
'
|
||||
consultar_pagamentos:
|
||||
map:
|
||||
customer_key: msisdn
|
||||
@@ -38,21 +36,57 @@ mcp_parameter_mapping:
|
||||
consultar_pedido:
|
||||
map:
|
||||
customer_key: customer_id
|
||||
contract_key: order_id
|
||||
session_key: session_id
|
||||
extract:
|
||||
order_id:
|
||||
from: message
|
||||
type: string
|
||||
strategy: hybrid
|
||||
description: Extraia somente o identificador do pedido informado explicitamente
|
||||
pelo usuário. Retorne null quando não houver identificador de pedido na
|
||||
mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
consultar_entrega:
|
||||
map:
|
||||
contract_key: order_id
|
||||
session_key: session_id
|
||||
extract:
|
||||
order_id:
|
||||
from: message
|
||||
type: string
|
||||
strategy: hybrid
|
||||
description: Extraia somente o identificador do pedido informado explicitamente
|
||||
pelo usuário. Retorne null quando não houver identificador de pedido na
|
||||
mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
solicitar_troca:
|
||||
map:
|
||||
contract_key: order_id
|
||||
session_key: session_id
|
||||
defaults:
|
||||
reason: Solicitação aberta pelo atendimento conversacional.
|
||||
extract:
|
||||
order_id:
|
||||
from: message
|
||||
type: string
|
||||
strategy: hybrid
|
||||
description: Extraia somente o identificador do pedido informado explicitamente
|
||||
pelo usuário. Retorne null quando não houver identificador de pedido na
|
||||
mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
solicitar_devolucao:
|
||||
map:
|
||||
contract_key: order_id
|
||||
session_key: session_id
|
||||
defaults:
|
||||
reason: Solicitação aberta pelo atendimento conversacional.
|
||||
extract:
|
||||
order_id:
|
||||
from: message
|
||||
type: string
|
||||
strategy: hybrid
|
||||
description: Extraia somente o identificador do pedido informado explicitamente
|
||||
pelo usuário. Retorne null quando não houver identificador de pedido na
|
||||
mensagem.
|
||||
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
|
||||
group: 1
|
||||
|
||||
@@ -20,6 +20,18 @@ state_policies:
|
||||
- state: WAITING_SUPPORT_CONFIRMATION
|
||||
agent: support_agent
|
||||
description: Mantém confirmações no fluxo de suporte retail.
|
||||
- state: COLLECTING_BILLING_PARAMETERS
|
||||
agent: billing_agent
|
||||
description: Mantém a coleta de parâmetros no fluxo de faturamento.
|
||||
- state: COLLECTING_PRODUCT_PARAMETERS
|
||||
agent: product_agent
|
||||
description: Mantém a coleta de parâmetros no fluxo de produtos e serviços.
|
||||
- state: COLLECTING_ORDER_PARAMETERS
|
||||
agent: orders_agent
|
||||
description: Mantém a coleta de parâmetros no fluxo de pedidos.
|
||||
- state: COLLECTING_SUPPORT_PARAMETERS
|
||||
agent: support_agent
|
||||
description: Mantém a coleta de parâmetros no fluxo transacional de suporte retail.
|
||||
|
||||
intents:
|
||||
- name: billing_invoice_explanation
|
||||
@@ -55,7 +67,6 @@ intents:
|
||||
- listar_servicos
|
||||
keywords:
|
||||
- plano
|
||||
- produto
|
||||
- serviço
|
||||
- pacote
|
||||
- internet
|
||||
@@ -94,12 +105,15 @@ intents:
|
||||
domain: retail
|
||||
agent: support_agent
|
||||
description: Suporte, troca, devolução, garantia e problema com produto.
|
||||
priority: 40
|
||||
priority: 25
|
||||
mcp_tools:
|
||||
- consultar_pedido
|
||||
- solicitar_troca
|
||||
- solicitar_devolucao
|
||||
keywords:
|
||||
- solicitar devolução
|
||||
- devolver pedido
|
||||
- solicitar troca
|
||||
- troca
|
||||
- devolução
|
||||
- devolver
|
||||
|
||||
21
templates/agent_template_backend/config/tool_policies.yaml
Normal file
21
templates/agent_template_backend/config/tool_policies.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
version: 1
|
||||
|
||||
# Arquivo opcional da aplicação. A ausência mantém o comportamento dos
|
||||
# templates anteriores e as políticas legadas declaradas em tools.yaml.
|
||||
defaults:
|
||||
operation_type: read_only
|
||||
require_confirmation: false
|
||||
|
||||
tool_policies:
|
||||
solicitar_troca:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
|
||||
solicitar_devolucao:
|
||||
operation_type: transactional
|
||||
require_confirmation: true
|
||||
|
||||
# Exemplo para uma operação real que só pode executar após confirmação:
|
||||
# cancelar_servico:
|
||||
# operation_type: transactional
|
||||
# require_confirmation: true
|
||||
@@ -6,14 +6,19 @@ tools:
|
||||
args_schema:
|
||||
msisdn: string
|
||||
invoice_id: string
|
||||
|
||||
selection_keywords:
|
||||
- fatura
|
||||
- conta
|
||||
- boleto
|
||||
consultar_pagamentos:
|
||||
description: Consulta histórico de pagamentos do cliente.
|
||||
mcp_server: telecom
|
||||
enabled: true
|
||||
args_schema:
|
||||
msisdn: string
|
||||
|
||||
selection_keywords:
|
||||
- pagamento
|
||||
- pagamentos
|
||||
consultar_plano:
|
||||
description: Consulta plano ativo e atributos comerciais.
|
||||
mcp_server: telecom
|
||||
@@ -21,14 +26,18 @@ tools:
|
||||
args_schema:
|
||||
msisdn: string
|
||||
asset_id: string
|
||||
|
||||
selection_keywords:
|
||||
- plano
|
||||
listar_servicos:
|
||||
description: Lista serviços ativos e adicionais VAS.
|
||||
mcp_server: telecom
|
||||
enabled: true
|
||||
args_schema:
|
||||
msisdn: string
|
||||
|
||||
selection_keywords:
|
||||
- serviços
|
||||
- servicos
|
||||
- vas
|
||||
consultar_pedido:
|
||||
description: Consulta pedido de varejo por order_id/customer_id.
|
||||
mcp_server: retail
|
||||
@@ -36,33 +45,57 @@ tools:
|
||||
args_schema:
|
||||
order_id: string
|
||||
customer_id: string
|
||||
|
||||
selection_keywords:
|
||||
- consultar pedido
|
||||
- status do pedido
|
||||
- pedido
|
||||
consultar_entrega:
|
||||
description: Consulta entrega e rastreamento do pedido.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
args_schema:
|
||||
order_id: string
|
||||
|
||||
selection_keywords:
|
||||
- entrega
|
||||
- rastreio
|
||||
- rastreamento
|
||||
- transportadora
|
||||
- previsão
|
||||
solicitar_troca:
|
||||
description: Simula abertura de solicitação de troca.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
tool_type: action
|
||||
requires: [order_id, reason]
|
||||
confirmation_required: false
|
||||
requires:
|
||||
- order_id
|
||||
- reason
|
||||
confirmation_required: true
|
||||
args_schema:
|
||||
order_id: string
|
||||
reason: string
|
||||
|
||||
selection_keywords:
|
||||
- solicitar troca
|
||||
- trocar
|
||||
- troca
|
||||
- defeito
|
||||
- quebrado
|
||||
solicitar_devolucao:
|
||||
description: Simula abertura de solicitação de devolução.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
tool_type: action
|
||||
requires: [order_id, reason]
|
||||
confirmation_required: false
|
||||
requires:
|
||||
- order_id
|
||||
- reason
|
||||
confirmation_required: true
|
||||
args_schema:
|
||||
order_id: string
|
||||
reason: string
|
||||
|
||||
selection_keywords:
|
||||
- solicitar devolução
|
||||
- solicitar devolucao
|
||||
- devolver pedido
|
||||
- devolver
|
||||
- devolução
|
||||
- devolucao
|
||||
- arrependimento
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
# Exemplos implementados no template
|
||||
|
||||
Este projeto entrega as capacidades transversais habilitadas como referência:
|
||||
|
||||
- route stickiness semântica com o perfil `route_continuity`;
|
||||
- decisões `CONTINUE`, `ROUTE`, `HUMAN_HANDOFF` e `END_SESSION`;
|
||||
- nós globais `human_handoff` e `end_session`;
|
||||
- persistência de `active_agent`, `route_bypassed`, `continuity_signal` e controle de sessão;
|
||||
- rejeição de novas mensagens depois de `session_ended=true`;
|
||||
- políticas MCP `read_only` e `transactional` no backend;
|
||||
- exemplo `solicitar_devolucao` com `require_confirmation: true`.
|
||||
|
||||
Para confirmar a transação, envie `confirmed: true` ou `confirmation: true` como booleano. Handoff e encerramento não chamam agentes de domínio nem MCP.
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# Teste e diagnóstico de Long-Term Memory
|
||||
|
||||
## O que foi corrigido
|
||||
|
||||
1. A LTM agora é carregada explicitamente antes do roteamento.
|
||||
2. O estado recebe uma chave estável em `long_term_memory_subject_key`, baseada em `business_context.customer_key` e, como fallback, `user_id`.
|
||||
3. O resultado de carga e persistência aparece em `metadata.long_term_memory` da resposta.
|
||||
4. `/health` informa a configuração efetiva de LTM carregada pelo processo.
|
||||
5. Falhas de leitura e gravação geram eventos `long_term_memory.load.failed` e `long_term_memory.persist.failed`.
|
||||
|
||||
## Teste
|
||||
|
||||
Primeira sessão:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8000/gateway/message \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel":"web",
|
||||
"payload":{
|
||||
"text":"Meu nome preferido é Cris e minha linguagem preferida é Python.",
|
||||
"session_id":"ltm-session-001",
|
||||
"user_id":"ltm-user-001",
|
||||
"customer_id":"ltm-customer-001"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
Verifique na resposta:
|
||||
|
||||
```json
|
||||
"long_term_memory": {
|
||||
"subject_key": "ltm-customer-001",
|
||||
"write_result": {
|
||||
"saved": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Nova sessão, mesma identidade:
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8000/gateway/message \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"channel":"web",
|
||||
"payload":{
|
||||
"text":"Qual é meu nome preferido e qual linguagem eu prefiro?",
|
||||
"session_id":"ltm-session-002",
|
||||
"user_id":"ltm-user-001",
|
||||
"customer_id":"ltm-customer-001"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
Na segunda resposta, confira:
|
||||
|
||||
- `metadata.long_term_memory.subject_key` igual à primeira chamada;
|
||||
- `metadata.long_term_memory.loaded` com registros;
|
||||
- `metadata.long_term_memory.context` preenchido;
|
||||
- ausência de `load_error`.
|
||||
|
||||
## Diagnóstico rápido
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:8000/health
|
||||
```
|
||||
|
||||
A seção `long_term_memory` deve mostrar:
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"provider": "sqlite",
|
||||
"sqlite_path": "./data/agent_framework.db",
|
||||
"table": "agentfw_long_term_memory",
|
||||
"auto_extract": true,
|
||||
"inject_context": true
|
||||
}
|
||||
```
|
||||
|
||||
Execute o backend com o diretório do projeto como diretório de trabalho. Como o caminho SQLite é relativo, iniciar a aplicação em outro diretório pode criar ou consultar outro arquivo `./data/agent_framework.db`.
|
||||
@@ -1,88 +1,80 @@
|
||||
# Optional file. If this file is absent, the backend keeps using .env exactly as before.
|
||||
# If present, each inference point can override provider/model/params.
|
||||
profiles:
|
||||
default:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0.2
|
||||
max_tokens: 2048
|
||||
|
||||
# Workflow/routing
|
||||
supervisor:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 700
|
||||
|
||||
route_continuity:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 80
|
||||
timeout_seconds: 5
|
||||
router:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 500
|
||||
|
||||
# Safety / evaluation
|
||||
guardrail:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 600
|
||||
|
||||
grl:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 700
|
||||
|
||||
judge:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 800
|
||||
|
||||
# RAG
|
||||
rag_rewriter:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 300
|
||||
|
||||
rag_compressor:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 1200
|
||||
|
||||
rag_generation:
|
||||
provider: oci_openai
|
||||
model: xopenai.gpt-4.1
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0.1
|
||||
max_tokens: 1800
|
||||
|
||||
# Memory / operations
|
||||
summary_memory:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0.1
|
||||
max_tokens: 1200
|
||||
|
||||
noc:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 700
|
||||
|
||||
# Agent-specific overrides
|
||||
billing_agent:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0.2
|
||||
|
||||
product_agent:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0.2
|
||||
|
||||
backoffice_agent:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0.2
|
||||
mcp_parameter_extraction:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1-mini
|
||||
temperature: 0
|
||||
max_tokens: 80
|
||||
timeout_seconds: 5
|
||||
|
||||
Reference in New Issue
Block a user