Skip to content

Commit 5eab8a8

Browse files
committed
examples: add voice agent example
# Conflicts: # examples/cerina_bindu/cbt/README.md # examples/multilingual-collab-agent/agent_config.json # examples/multilingual-collab-agent/main.py # examples/multilingual-collab-agent/skills/collaborate/skill.yaml # examples/multilingual-collab-agent/skills/research/skill.yaml # examples/multilingual-collab-agent/skills/translate/skill.yaml # examples/pdf_research_agent/README.md # examples/pdf_research_agent/pdf_research_agent.py # examples/pdf_research_agent/skills/pdf-research-skill/skill.yaml
1 parent 5661846 commit 5eab8a8

16 files changed

Lines changed: 745 additions & 75 deletions

File tree

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ For full URL override, use `BINDU_DEPLOYMENT_URL` (e.g. `http://127.0.0.1:5001`)
6363
- `speech-to-text/` - Audio transcription using Gemini 2.0 Flash (MP3, WAV, OGG, M4A)
6464
- `ai-data-analysis-agent/` - Autonomous data analyst with CSV profiling and visualization
6565
- `cybersecurity-newsletter/` - Security news aggregator with CVE tracking
66+
- `voice-agent/` - Voice-enabled agent with WebSocket voice session endpoints
6667

6768
### TypeScript (Language-Agnostic via gRPC)
6869
- `typescript-openai-agent/` - OpenAI SDK agent bindufied with TypeScript SDK

examples/cerina_bindu/cbt/README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,32 @@ cp .env.example .env
5757
uv run python supervisor_cbt.py
5858
```
5959

60-
Agent starts at `http://localhost:3773`. See [`cbt/README.md`](cbt/README.md) for full usage instructions including example curl commands.
60+
Agent starts at `http://localhost:3773`.
61+
62+
Minimal smoke test:
63+
64+
```bash
65+
curl -X POST http://localhost:3773/ \
66+
-H "Content-Type: application/json" \
67+
-d '{
68+
"jsonrpc": "2.0",
69+
"method": "message/send",
70+
"params": {
71+
"message": {
72+
"role": "user",
73+
"parts": [{"kind": "text", "text": "I feel overwhelmed and procrastinate constantly. Give me a short CBT exercise."}],
74+
"kind": "message",
75+
"messageId": "msg-cbt-1",
76+
"contextId": "ctx-cbt-1",
77+
"taskId": "task-cbt-1"
78+
},
79+
"configuration": {"acceptedOutputModes": ["application/json"]}
80+
},
81+
"id": "1"
82+
}'
83+
```
84+
85+
Expected result: HTTP 200 with a JSON-RPC response containing a task/result payload with structured CBT guidance.
6186

6287
---
6388

examples/document-analyzer/document_analyzer.py

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@
1616
import os
1717
import io
1818
import base64
19+
from typing import Any
1920

2021
from pypdf import PdfReader
2122
from docx import Document
23+
from bindu.utils.logging import get_logger
24+
25+
logger = get_logger("examples.document_analyzer")
2226

2327
load_dotenv()
2428

2529
# Define LLM agent
2630
agent = Agent(
27-
instructions = """
31+
instructions="""
2832
You are an advanced document analysis assistant.
2933
3034
Your job is to analyze uploaded documents and answer the user's prompt
@@ -42,12 +46,13 @@
4246
- If the prompt asks for summary, provide concise bullet points
4347
- Do not hallucinate information outside the document
4448
""",
45-
model = OpenRouter(
46-
id = "arcee-ai/trinity-large-preview:free",
49+
model=OpenRouter(
50+
id="arcee-ai/trinity-large-preview:free",
4751
api_key=os.getenv("OPENROUTER_API_KEY"),
4852
),
4953
)
5054

55+
5156
# Document Parsing
5257
def extract_text_from_pdf(file_bytes):
5358
"""Extract text from pdf bytes"""
@@ -67,10 +72,16 @@ def extract_text_from_pdf(file_bytes):
6772

6873
return "\n".join(text)
6974

75+
7076
def extract_text_from_docx(file_bytes):
7177
"""Extract text from docx bytes"""
72-
doc = Document(io.BytesIO(file_bytes))
73-
return "\n".join([p.text for p in doc.paragraphs])
78+
try:
79+
doc = Document(io.BytesIO(file_bytes))
80+
return "\n".join([p.text for p in doc.paragraphs])
81+
except Exception as e:
82+
logger.error(f"Error extracting DOCX text: {e}")
83+
return ""
84+
7485

7586
def extract_document_text(file_bytes, mime_type):
7687
"""Parse document according to their mime type"""
@@ -84,6 +95,7 @@ def extract_document_text(file_bytes, mime_type):
8495

8596
raise ValueError(f"Unsupported file type: {mime_type}")
8697

98+
8799
# FilePart processing
88100
def get_file_bytes(part):
89101
"""Extract file bytes from FilePart"""
@@ -98,39 +110,38 @@ def get_file_bytes(part):
98110

99111
if isinstance(data, str):
100112
import base64
113+
101114
return base64.b64decode(data)
102115

103116
return data
104117

105-
# Handler
106-
def handler(messages: list[dict]):
107-
"""
108-
Receives task.history — a list of A2A Message objects.
109-
Each message has: role, parts[], kind, messageId, contextId, taskId
110-
Each part has: kind="text"|"file", and either text or file.bytes+mimeType
111-
"""
112-
if not messages:
113-
return "No messages received."
114-
import json
115-
print("DEBUG messages:", json.dumps(messages, indent=2, default=str))
116118

117-
prompt = ""
118-
extracted_docs = []
119+
# Handler
120+
def _collect_prompt_and_documents(
121+
messages: list[dict[str, Any]],
122+
) -> tuple[str, list[str], list[str]]:
123+
"""Support both raw A2A messages and runtime chat-format messages."""
124+
prompt_parts: list[str] = []
125+
extracted_docs: list[str] = []
126+
errors: list[str] = []
119127

120128
for msg in messages:
121-
# if a role is provided, only process user messages; treat missing
122-
# roles as coming from the user so that tests/clients without a role
123-
# field still work.
124129
role = msg.get("role")
125130
if role is not None and role != "user":
126131
continue
127132

128-
# be defensive: parts could be None or omitted
133+
# Runtime path: manifest worker passes chat-format messages.
134+
content = msg.get("content")
135+
if isinstance(content, str) and content.strip():
136+
prompt_parts.append(content)
137+
138+
# Compatibility path: raw A2A messages with parts.
129139
parts = msg.get("parts") or []
130140
for part in parts:
131141
if part.get("kind") == "text":
132-
prompt = part.get("text", "")
133-
142+
text = part.get("text", "")
143+
if text:
144+
prompt_parts.append(text)
134145
elif part.get("kind") == "file":
135146
try:
136147
file_info = part.get("file", {})
@@ -147,30 +158,46 @@ def handler(messages: list[dict]):
147158
)
148159
doc_text = extract_document_text(file_bytes, mime_type)
149160
extracted_docs.append(doc_text)
150-
151161
except Exception as e:
152-
extracted_docs.append(f"Error processing file: {str(e)}")
162+
errors.append(str(e))
163+
164+
return "\n".join(prompt_parts).strip(), extracted_docs, errors
165+
166+
167+
def handler(messages: list[dict]):
168+
"""
169+
Receives task.history — a list of A2A Message objects.
170+
Each message has: role, parts[], kind, messageId, contextId, taskId
171+
Each part has: kind="text"|"file", and either text or file.bytes+mimeType
172+
"""
173+
if not messages:
174+
return "No messages received."
175+
prompt, extracted_docs, errors = _collect_prompt_and_documents(messages)
153176

154177
if not extracted_docs:
178+
if errors:
179+
return "Failed to process documents:\n" + "\n".join(errors)
155180
return "No valid document found in the messages."
156181

157182
combined_document = "\n\n".join(extracted_docs)
158-
result = agent.run(input=f"""
183+
result = agent.run(
184+
input=f"""
159185
User Prompt:
160186
{prompt}
161187
162188
Document Content:
163189
{combined_document}
164190
165191
Provide analysis based on the prompt.
166-
""")
192+
"""
193+
)
167194
return result
168195

169196

170197
# Bindu config
171198
config = {
172-
"author" : "vyomrohila@gmail.com",
173-
"name" : "document_analyzer_agent",
199+
"author": "vyomrohila@gmail.com",
200+
"name": "document_analyzer_agent",
174201
"description": "AI agent that analyzes uploaded PDF or DOCX documents based on a user prompt.",
175202
"deployment": {
176203
"url": "http://localhost:3773",

examples/multilingual-collab-agent/main.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
# |---------------------------------------------------------|
2-
# | |
3-
# | Give Feedback / Get Help |
4-
# | https://github.com/getbindu/Bindu/issues/new/choose |
5-
# | |
6-
# |---------------------------------------------------------|
7-
#
8-
# Thank you users! We ❤️ you! - 🌻
9-
101
"""Multilingual Collaborative Agent v2 — A Bindu Agent.
112
123
An identity-aware agent that detects user language (English, Hindi, Bengali)
@@ -35,6 +26,8 @@
3526
agent: Agent | None = None
3627
_initialized = False
3728
_init_lock = asyncio.Lock()
29+
DEFAULT_MODEL = "openai/gpt-oss-120b"
30+
_mem0_enabled = False
3831

3932

4033
def load_config() -> dict:
@@ -46,9 +39,11 @@ def load_config() -> dict:
4639

4740
def build_agent() -> Agent:
4841
"""Build and return the multilingual agent instance."""
42+
global _mem0_enabled
43+
4944
openrouter_api_key = os.getenv("OPENROUTER_API_KEY")
5045
mem0_api_key = os.getenv("MEM0_API_KEY")
51-
model_name = os.getenv("MODEL_NAME", "openai/gpt-oss-120b")
46+
model_name = os.getenv("MODEL_NAME", DEFAULT_MODEL)
5247

5348
if not openrouter_api_key:
5449
raise ValueError("OPENROUTER_API_KEY environment variable is required.")
@@ -160,6 +155,8 @@ async def handler(messages: list[dict[str, str]]) -> Any:
160155
print("🔧 Initializing multilingual agent...")
161156
agent = build_agent()
162157
_initialized = True
158+
if _mem0_enabled:
159+
print(" Memory: Mem0 persistent memory enabled")
163160
print("✅ Agent initialized")
164161

165162
response = await agent.arun(messages)
@@ -170,9 +167,8 @@ def main() -> None:
170167
"""Start the Bindu agent server."""
171168
config = load_config()
172169
print("🌍 Starting Multilingual Collaborative Agent...")
173-
print(f" Supported languages: English, Hindi (हिन्दी), Bengali (বাংলা)")
174-
print(f" Model: {os.getenv('MODEL_NAME', 'openai/gpt-4o-mini')}")
175-
print(f" Memory: Mem0 persistent memory enabled")
170+
print(" Supported languages: English, Hindi (हिन्दी), Bengali (বাংলা)")
171+
print(f" Model: {os.getenv('MODEL_NAME', DEFAULT_MODEL)}")
176172
bindufy(config, handler)
177173

178174

examples/multilingual-collab-agent/skills/collaborate/skill.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ requirements:
6464
- "agno>=2.5.2"
6565
- "mem0ai>=0.1.0"
6666
system: []
67-
min_memory_mb: 256
67+
min_memory_mb: 1280
6868

6969
performance:
7070
avg_processing_time_ms: 2500

examples/multilingual-collab-agent/skills/research/skill.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ requirements:
5454
- "duckduckgo-search>=8.1.1"
5555
- "agno>=2.5.2"
5656
system: []
57-
min_memory_mb: 256
57+
min_memory_mb: 640
5858

5959
performance:
6060
avg_processing_time_ms: 3000
@@ -63,6 +63,7 @@ performance:
6363
scalability: horizontal
6464

6565
allowed_tools:
66+
# "Read" covers web access/search in this runtime.
6667
- Read
6768

6869
documentation:

examples/multilingual-collab-agent/skills/translate/skill.yaml

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ documentation:
7676
Particularly useful for technical content about Bindu and AI agents where
7777
standard translators produce poor results for domain-specific terms.
7878
79+
Configuration: requires `OPENROUTER_API_KEY` in the environment (and optionally
80+
`OPENROUTER_API_BASE` / `OPENROUTER_API_URL` if using a non-default endpoint).
81+
7982
use_cases:
8083
when_to_use:
8184
- "User provides text and asks to translate to a specific language"
@@ -89,19 +92,33 @@ documentation:
8992
- "Real-time speech translation (text only)"
9093

9194
input_structure: |
92-
Text with explicit translation instruction.
95+
Text with explicit translation instruction (text/plain) OR JSON (application/json).
9396
9497
Examples:
9598
"Translate to Hindi: [text]"
9699
"हिंदी में अनुवाद करें: [text]"
97100
"বাংলায় অনুবাদ করুন: [text]"
98101
"Translate this to English: [text in any language]"
99102
103+
JSON schema (application/json):
104+
{
105+
"text": "string",
106+
"target_language": "string",
107+
"source_language": "string (optional)"
108+
}
109+
100110
output_format: |
101-
Translated text in the target language.
111+
Translated text in the target language (text/plain) OR JSON (application/json).
102112
For technical terms without direct translation:
103113
Original term kept + brief explanation in target language.
104114
115+
JSON schema (application/json):
116+
{
117+
"translated_text": "string",
118+
"source_language": "string",
119+
"target_language": "string"
120+
}
121+
105122
error_handling:
106123
- "Unsupported language requested: informs user of supported languages (EN/HI/BN)"
107124
- "Ambiguous source language: asks for clarification"

examples/pdf_research_agent/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ pip install bindu agno pypdf python-dotenv
3636
### 3. Set Up Environment Variables
3737
```bash
3838
# Create .env file
39-
echo "OPENROUTER_API_KEY=your_api_key_here" > .env
39+
echo "OPENROUTER_API_KEY=your_api_key_here" > .env # pragma: allowlist secret
4040

4141
# Or export directly
42-
export OPENROUTER_API_KEY="your_api_key_here" # pragma: allowlist secret
42+
export OPENROUTER_API_KEY="your_api_key_here" # pragma: allowlist secret
4343
```
4444

4545
## 🎯 Usage
@@ -268,7 +268,7 @@ uv add pypdf
268268

269269
#### "OPENROUTER_API_KEY not set"
270270
```bash
271-
export OPENROUTER_API_KEY="your_api_key_here" # pragma: allowlist secret
271+
export OPENROUTER_API_KEY="your_api_key_here" # pragma: allowlist secret
272272
```
273273

274274
#### "Port 3773 already in use"

0 commit comments

Comments
 (0)