streaming-chat
Skill15 starsChanged 57 days agoMITView on GitHub
- Category: compute - SDK: @0gfoundation/0g-compute-ts-sdk ^0.9.0, ethers 6.13.1 - Activation Triggers: "chatbot", "inference", "LLM", "streaming chat", "AI chat", "GLM", "Claude", "GPT", "Qwen", "DeepSeek" Run conversational AI inference using 0G Compute Network providers (serviceType: chatbot). Supports streaming and non-streaming modes. Some providers serve a single model; others are multi-model routers serving dozens behind one address. Discover models at runtime — never hardcode an id from this document. The roster changes often. At last verification 0G mainnet had 9 chatbot providers.…
- Reads credentials
- Sends data out
In this repository26 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- agent-accountsSkill
- agent-reputationSkill
- deploy-agentSkill
- interact-agentSkill
- manage-agentSkill
- deploy-contractSkill
- interact-contractSkill
- scaffold-projectSkill
- account-managementSkill
- embeddingsSkill
- fine-tuningSkill
- image-editingSkill
- provider-discoverySkill
- speech-to-textSkill
- text-to-imageSkill
- video-generationSkill
- compute-plus-storageSkill
- storage-plus-chainSkill
- 0g-pc-setupSkill
- 0g-pc-switch-modelSkill
- 0g-pc-uninstallSkill
- download-fileSkill
- merkle-verificationSkill
- upload-fileSkill
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 1 is distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 12
Run conversational AI inference using 0G Compute Network providers (serviceType: chatbot).
- Very specific: line 52
Call processResponse() after EVERY inference request
- Very specific: line 53
Use correct param order: processResponse(providerAddress, chatID, usageData)
- Very specific: line 59
Check availableBalance before making requests
- Very specific: line 67
Use data.id when the ZG-Res-Key header is present — they differ (the header is a bare UUID, data.id is prefixed chatcmpl-<uuid>), and the …
- Distinctive: line 71
Use ethers v5 syntax
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Streaming Chat Inference
Metadata
- Category: compute
- SDK:
@0gfoundation/0g-compute-ts-sdk^0.9.0,ethers6.13.1 - Activation Triggers: "chatbot", "inference", "LLM", "streaming chat", "AI chat", "GLM", "Claude", "GPT", "Qwen", "DeepSeek"
Purpose
Run conversational AI inference using 0G Compute Network providers (serviceType: chatbot).
Supports streaming and non-streaming modes. Some providers serve a single model; others are
multi-model routers serving dozens behind one address.
Models
Discover models at runtime — never hardcode an id from this document. The roster changes often.
const { multiModel, defaultModel, models } =
await broker.inference.getProviderModels(providerAddress);At last verification 0G mainnet had 9 chatbot providers. Their on-chain defaults included
zai-org/GLM-5-FP8, claude-opus-5, claude-fable-5, qwen3.7-plus, glm-5.3,
openai/gpt-5.4-mini, openai/gpt-oss-20b and 0G's own 0GM-1.0-35B-A3B. Two of those providers
are multi-model routers, together advertising well over 100 ids across the Claude, GPT, Gemini,
DeepSeek, Qwen, GLM, Kimi and ERNIE families, with context windows from 32K to ~1.4M.
Prerequisites
- Node.js >= 20
@0gfoundation/0g-compute-ts-sdkandethersinstalled- Funded and acknowledged provider (sub-account funded with at least 1 0G)
.envwithPRIVATE_KEY,RPC_URL,PROVIDER_ADDRESS
Quick Workflow
- Initialize broker
- (Optional)
getProviderModels(provider)to pick a specific model - Get service metadata:
getServiceMetadata(provider, model?) - Generate auth headers
- Make chat completion request
- Extract ChatID from the
ZG-Res-Keyheader (body fallback) - Call
processResponse(providerAddress, chatID, usageData)— CRITICAL
Core Rules
ALWAYS
- Call
processResponse()after EVERY inference request - Use correct param order:
processResponse(providerAddress, chatID, usageData) - Extract ChatID from
ZG-Res-Keyheader FIRST, usedata.idas fallback - Spread the headers from
getRequestHeaders()(...headers) rather than naming individual headers — the exact set is an implementation detail and has changed between SDK versions - Pass a model id to
getServiceMetadata(provider, model)when targeting a multi-model provider - Acknowledge provider before first use
- Check
availableBalancebefore making requests - Set a generous
max_tokensfor reasoning models — they spend budget on hidden reasoning tokens and will return emptycontentif the cap is too low
NEVER
- Skip
processResponse()— causes fee settlement failure - Reverse the parameter order of
processResponse() - Use
data.idwhen theZG-Res-Keyheader is present — they differ (the header is a bare UUID,data.idis prefixedchatcmpl-<uuid>), and the wrong id fails verification - Assume
getServiceMetadata()gives you the only model a provider serves - Hardcode private keys
- Use ethers v5 syntax
Code Examples
Non-Streaming Chat
import { ethers } from 'ethers';
import { createZGComputeNetworkBroker } from '@0gfoundation/0g-compute-ts-sdk';
import 'dotenv/config';
async function chat(userMessage: string): Promise<string> {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const broker = await createZGComputeNetworkBroker(wallet);
const providerAddress = process.env.PROVIDER_ADDRESS!;
const { endpoint, model } = await broker.inference.getServiceMetadata(providerAddress);
const headers = await broker.inference.getRequestHeaders(providerAddress);
const messages = [{ role: 'user', content: userMessage }];
const response = await fetch(`${endpoint}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body: JSON.stringify({ messages, model }),
});
const data = await response.json();
const answer = data.choices[0].message.content;
// CRITICAL: Process response for fee settlement
let chatID = response.headers.get('ZG-Res-Key') || response.headers.get('zg-res-key');
if (!chatID) chatID = data.id; // Fallback for chatbot
await broker.inference.processResponse(providerAddress, chatID, JSON.stringify(data.usage));
return answer;
}
// Usage
const reply = await chat('What is 0G?');
console.log(reply);Selecting a Model on a Multi-Model Provider
Pass the model id as the second argument to getServiceMetadata(). The id is forwarded as-is; the
provider validates it, bills that model's price, and rejects an unknown id server-side.
async function chatWithModel(userMessage: string, wantedModel: string): Promise<string> {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const broker = await createZGComputeNetworkBroker(wallet);
const providerAddress = process.env.PROVIDER_ADDRESS!;
// Confirm the provider actually serves it before spending anything
const { multiModel, models } = await broker.inference.getProviderModels(providerAddress);
const match = models.find((m) => m.id === wantedModel || m.canonical_id === wantedModel);
if (!match) {
throw new Error(
`${providerAddress} does not serve "${wantedModel}". ` +
`multiModel=${multiModel}; available: ${models.map((m) => m.id).join(', ')}`,
);
}
// Omitting the 2nd arg would silently use the provider's DEFAULT model
const { endpoint, model } = await broker.inference.getServiceMetadata(providerAddress, match.id);
const headers = await broker.inference.getRequestHeaders(providerAddress, userMessage);
const response = await fetch(`${endpoint}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body: JSON.stringify({
model, // the resolved model, not `wantedModel`
messages: [{ role: 'user', content: userMessage }],
max_tokens: 1024,
}),
});
const data = await response.json();
let chatID = response.headers.get('ZG-Res-Key') || response.headers.get('zg-res-key');
if (!chatID) chatID = data.id;
await broker.inference.processResponse(providerAddress, chatID, JSON.stringify(data.usage));
return data.choices[0].message.content;
}Model metadata also tells you what a model can accept and what it costs, so you can choose without trial and error:
for (const m of models) {
console.log({
id: m.id,
context: m.context_length,
inputs: m.architecture?.input_modalities, // e.g. ['text', 'image']
promptUsd: m.pricing_usd?.prompt, // decimal string per prompt token
completionUsd: m.pricing_usd?.completion,
});
}Streaming Chat
async function streamingChat(userMessage: string): Promise<string> {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const broker = await createZGComputeNetworkBroker(wallet);
const providerAddress = process.env.PROVIDER_ADDRESS!;
const { endpoint, model } = await broker.inference.getServiceMetadata(providerAddress);
const headers = await broker.inference.getRequestHeaders(providerAddress);
const messages = [{ role: 'user', content: userMessage }];
const response = await fetch(`${endpoint}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body: JSON.stringify({ messages, model, stream: true }),
});
// ChatID from header (primary source)
let chatID = response.headers.get('ZG-Res-Key') || response.headers.get('zg-res-key');
let usage = null;
let streamChatID = null;
let fullResponse = '';
const decoder = new TextDecoder();
const reader = response.body!.getReader();
let rawBody = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
rawBody += chunk;
process.stdout.write(chunk); // Real-time output
}
// Parse stream for fallback chatID and usage data
for (const line of rawBody.split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed === 'data: [DONE]') continue;
try {
const jsonStr = trimmed.startsWith('data:') ? trimmed.slice(5).trim() : trimmed;
const message = JSON.parse(jsonStr);
if (!streamChatID && message.id) streamChatID = message.id;
if (message.usage) usage = message.usage;
if (message.choices?.[0]?.delta?.content) {
fullResponse += message.choices[0].delta.content;
}
} catch {}
}
// CRITICAL: processResponse with correct param order
const finalChatID = chatID || streamChatID;
await broker.inference.processResponse(providerAddress, finalChatID, JSON.stringify(usage || {}));
return fullResponse;
}Multi-Turn Conversation
async function conversation() {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const broker = await createZGComputeNetworkBroker(wallet);
const providerAddress = process.env.PROVIDER_ADDRESS!;
const { endpoint, model } = await broker.inference.getServiceMetadata(providerAddress);
const history: Array<{ role: string; content: string }> = [
{ role: 'system', content: 'You are a helpful assistant.' },
];
async function sendMessage(userMessage: string): Promise<string> {
history.push({ role: 'user', content: userMessage });
const headers = await broker.inference.getRequestHeaders(providerAddress);
const response = await fetch(`${endpoint}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body: JSON.stringify({ messages: history, model }),
});
const data = await response.json();
const answer = data.choices[0].message.content;
history.push({ role: 'assistant', content: answer });
let chatID = response.headers.get('ZG-Res-Key') || response.headers.get('zg-res-key');
if (!chatID) chatID = data.id;
await broker.inference.processResponse(providerAddress, chatID, JSON.stringify(data.usage));
return answer;
}
const reply1 = await sendMessage('What is 0G?');
console.log('Assistant:', reply1);
const reply2 = await sendMessage('Tell me more about its storage layer.');
console.log('Assistant:', reply2);
}Error Handling
async function resilientChat(userMessage: string, maxRetries = 3): Promise<string> {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const broker = await createZGComputeNetworkBroker(wallet);
const providerAddress = process.env.PROVIDER_ADDRESS!;
const { endpoint, model } = await broker.inference.getServiceMetadata(providerAddress);
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const headers = await broker.inference.getRequestHeaders(providerAddress);
const response = await fetch(`${endpoint}/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body: JSON.stringify({ messages: [{ role: 'user', content: userMessage }], model }),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const data = await response.json();
const answer = data.choices[0].message.content;
let chatID = response.headers.get('ZG-Res-Key') || response.headers.get('zg-res-key');
if (!chatID) chatID = data.id;
await broker.inference.processResponse(providerAddress, chatID, JSON.stringify(data.usage));
return answer;
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error);
if (attempt === maxRetries) throw error;
await new Promise((r) => setTimeout(r, 1000 * attempt));
}
}
throw new Error('All retries exhausted');
}Anti-Patterns
// BAD: Missing processResponse — fee settlement failure
const data = await response.json();
return data.choices[0].message.content;
// processResponse() never called!
// BAD: Wrong parameter order
await broker.inference.processResponse(
chatID, // WRONG — should be providerAddress
providerAddress, // WRONG — should be chatID
usage,
);
// BAD: Using body ID without checking header first
const chatID = data.id; // Should check ZG-Res-Key header first!
// BAD: ethers v5 syntax
const provider = new ethers.providers.JsonRpcProvider(url); // v5!
// BAD: Hardcoding private keys
const wallet = new ethers.Wallet('0xabc123...', provider); // NEVER do thisCommon Errors & Fixes
| Error | Cause | Fix |
Insufficient balance | Sub-account empty | Transfer funds to provider |
Provider not acknowledged | First-time provider | acknowledgeProviderSigner() |
Invalid request headers | Stale auth headers | Re-call getRequestHeaders() |
Fee verification failed | Wrong processResponse params | Check param order and chatID |
stream error | Network interruption | Implement retry logic |
Related Skills
- Provider Discovery — find chatbot providers
- Account Management — fund accounts
- Text to Image — image generation
- Compute + Storage — AI with storage
References
- Compute Patterns
- Network Config
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

